//------------------------------------------------------------------------------
// CERN BE-CO-HT
// White Rabbit Trigger Distribution (WRTD)
// https://ohwr.org/projects/wrtd
//------------------------------------------------------------------------------
//
// unit name:   main
//
// description: Testbench for the SPEC150T-based FMC ADC WRTD reference design.
//
//------------------------------------------------------------------------------
// Copyright CERN 2019
//------------------------------------------------------------------------------
// Copyright and related rights are licensed under the Solderpad Hardware
// License, Version 2.0 (the "License"); you may not use this file except
// in compliance with the License. You may obtain a copy of the License at
// http://solderpad.org/licenses/SHL-2.0.
// Unless required by applicable law or agreed to in writing, software,
// hardware and materials distributed under this License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
// or implied. See the License for the specific language governing permissions
// and limitations under the License.
//------------------------------------------------------------------------------

`timescale 1ns/1ps

`include "gn4124_bfm.svh"
`include "wrtd_driver.svh"

`include "fmc_adc_mezzanine_mmap.v"
`include "fmc_adc_100Ms_csr.v"
`include "fmc_adc_100Ms_channel_regs.v"
`include "fmc_adc_eic_regs.v"

`define DMA_BASE     'h00c0
`define VIC_BASE     'h0100
`define ADC_OFFSET   'h4000
`define ADC_CSR_BASE `ADC_OFFSET + `ADDR_FMC_ADC_MEZZANINE_MMAP_FMC_ADC_100M_CSR
`define ADC_EIC_BASE `ADC_OFFSET + `ADDR_FMC_ADC_MEZZANINE_MMAP_FMC_ADC_EIC

`define ADC_CH1_BASE `ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_FMC_ADC_CH1
`define ADC_CH2_BASE `ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_FMC_ADC_CH2
`define ADC_CH3_BASE `ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_FMC_ADC_CH3
`define ADC_CH4_BASE `ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_FMC_ADC_CH4

module main;

   IGN4124PCIMaster hostA ();
   IGN4124PCIMaster hostB ();

   reg       duta_ext_trig, dutb_ext_trig;

   dut_env DUTA (hostA, a2b_txp, a2b_txn, a2b_rxp, a2b_rxn, duta_ext_trig);
   dut_env DUTB (hostB, a2b_rxp, a2b_rxn, a2b_txp, a2b_txn, dutb_ext_trig);

   IMockTurtleIRQ MtIrqMonitorA (`MT_ATTACH_IRQ(DUTA.DUT.cmp_mock_turtle));
   IMockTurtleIRQ MtIrqMonitorB (`MT_ATTACH_IRQ(DUTB.DUT.cmp_mock_turtle));

   CBusAccessor accA, accB;

   WrtdDrv devA, devB;

   const uint64_t MT_BASE = 'h0002_0000;

   int sim_end = 0;

   initial begin

      uint64_t val, expected;

      $timeformat (-6, 3, "us", 10);

      duta_ext_trig <= 1'b0;

      wait ((hostA.ready == 1'b1) && (hostB.ready == 1'b1));

      fork
         begin
            accA = hostA.get_accessor();
            accA.set_default_xfer_size(4);

            devA = new (accA, MT_BASE, MtIrqMonitorA, "DUT:A");
            devA.init();
            devA.add_rule ( "rule0" );
            devA.set_rule ( "rule0", "LC-I5", "NET0", 0 );
            devA.enable_rule ( "rule0" );

            // Configure the EIC for an interrupt on ACQ_END
            accA.write(`ADC_EIC_BASE + 'h4, 'h2);

            // Configure the VIC
            accA.write(`VIC_BASE + 'h8, 'h7f);
            accA.write(`VIC_BASE + 'h0, 'h1);

            // Config DUTA to trigger on external trigger and get 64 samples
            accA.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_PRE_SAMPLES,    'h0000);
            accA.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_POST_SAMPLES,   'h0040);
            accA.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_SHOTS,          'h0001);
            accA.write(`ADC_CH1_BASE + `ADDR_FMC_ADC_100MS_CHANNEL_REGS_CALIB, 'h8000);
            accA.write(`ADC_CH2_BASE + `ADDR_FMC_ADC_100MS_CHANNEL_REGS_CALIB, 'h8000);
            accA.write(`ADC_CH3_BASE + `ADDR_FMC_ADC_100MS_CHANNEL_REGS_CALIB, 'h8000);
            accA.write(`ADC_CH4_BASE + `ADDR_FMC_ADC_100MS_CHANNEL_REGS_CALIB, 'h8000);
	    accA.write(`ADC_CH1_BASE + `ADDR_FMC_ADC_100MS_CHANNEL_REGS_SAT,   'h7fff);
	    accA.write(`ADC_CH2_BASE + `ADDR_FMC_ADC_100MS_CHANNEL_REGS_SAT,   'h7fff);
	    accA.write(`ADC_CH3_BASE + `ADDR_FMC_ADC_100MS_CHANNEL_REGS_SAT,   'h7fff);
	    accA.write(`ADC_CH4_BASE + `ADDR_FMC_ADC_100MS_CHANNEL_REGS_SAT,   'h7fff);

            val  = (1'b1 << `FMC_ADC_100MS_CSR_TRIG_EN_EXT_OFFSET);
            accA.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_TRIG_EN, val);

            expected = 'h39;
            accA.read(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_STA, val);
            if (val != expected)
              $fatal (1, "ADC status error (got 0x%8x, expected 0x%8x).", val, expected);

            $display ("[DUT:A] <%t> ADC configured and armed", $realtime);
         end

         begin
            accB = hostB.get_accessor();
            accB.set_default_xfer_size(4);
            devB = new (accB, MT_BASE, MtIrqMonitorB, "DUT:B");
            devB.init();
            devB.add_rule ( "rule0" );
            devB.set_rule ( "rule0", "NET0", "LC-O1", 50000 );
            devB.enable_rule ( "rule0" );

            // Configure the EIC for an interrupt on ACQ_END
            accB.write(`ADC_EIC_BASE + 'h4, 'h2);

            // Configure the VIC
            accB.write(`VIC_BASE + 'h8, 'h7f);
            accB.write(`VIC_BASE + 'h0, 'h1);

            // Config DUTB to trigger on WRTD and get 64 samples
            accB.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_PRE_SAMPLES,    'h0000);
            accB.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_POST_SAMPLES,   'h0040);
            accB.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_SHOTS,          'h0001);
            accB.write(`ADC_CH1_BASE + `ADDR_FMC_ADC_100MS_CHANNEL_REGS_CALIB, 'h8000);
            accB.write(`ADC_CH2_BASE + `ADDR_FMC_ADC_100MS_CHANNEL_REGS_CALIB, 'h8000);
            accB.write(`ADC_CH3_BASE + `ADDR_FMC_ADC_100MS_CHANNEL_REGS_CALIB, 'h8000);
            accB.write(`ADC_CH4_BASE + `ADDR_FMC_ADC_100MS_CHANNEL_REGS_CALIB, 'h8000);
	    accB.write(`ADC_CH1_BASE + `ADDR_FMC_ADC_100MS_CHANNEL_REGS_SAT,   'h7fff);
	    accB.write(`ADC_CH2_BASE + `ADDR_FMC_ADC_100MS_CHANNEL_REGS_SAT,   'h7fff);
	    accB.write(`ADC_CH3_BASE + `ADDR_FMC_ADC_100MS_CHANNEL_REGS_SAT,   'h7fff);
	    accB.write(`ADC_CH4_BASE + `ADDR_FMC_ADC_100MS_CHANNEL_REGS_SAT,   'h7fff);

            expected = 'h39;
            accB.read(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_STA, val);
            if (val != expected)
              $fatal (1, "ADC status error (got 0x%8x, expected 0x%8x).", val, expected);

            $display ("[DUT:B] <%t> ADC configured and armed", $realtime);
         end
      join

      #50us;

      $display("[DUT:B] <%t> START ACQ 1", $realtime);
      accB.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_CTL, 'h00000001); // FSM start
      $display("[DUT:A] <%t> START ACQ 1", $realtime);
      accA.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_CTL, 'h00000001); // FSM start

      #5us;
      duta_ext_trig <= 1'b1;
      #10ns;
      duta_ext_trig <= 1'b0;

      fork
         begin
            wait (DUTA.DUT.cmp0_fmc_adc_mezzanine.acq_end_irq_o == 1);
            $display("[DUT:A] <%t> END ACQ 1", $realtime);
            accA.write(`ADC_EIC_BASE + 'hc, 'h2);
            accA.write(`VIC_BASE + 'h1c, 'h0);

            accA.read(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_TRIG_POS, val);
            $display("[DUT:A] <%t> TRIG POSITION %.8x", $realtime, val);

            // DMA transfer
            accA.write(`DMA_BASE + 'h08, val); // dma start addr

            accA.write(`DMA_BASE + 'h0C, 'h00001000); // host addr
            accA.write(`DMA_BASE + 'h10, 'h00000000);

            accA.write(`DMA_BASE + 'h14, 'h00000100); // len << 2

            accA.write(`DMA_BASE + 'h18, 'h00000000); // next
            accA.write(`DMA_BASE + 'h1C, 'h00000000);

            accA.write(`DMA_BASE + 'h20, 'h00000000); // attrib: pcie -> host

            accA.write(`DMA_BASE + 'h00, 'h00000001); // xfer start

            wait (DUTA.DUT.inst_spec_base.irqs[2] == 1);
            $display("[DUT:A] <%t> END DMA 1", $realtime);
            accA.write(`DMA_BASE + 'h04, 'h04); // clear DMA IRQ
            accA.write(`VIC_BASE + 'h1c, 'h0);
         end

         begin
            wait (DUTB.DUT.cmp0_fmc_adc_mezzanine.acq_end_irq_o == 1);
            $display("[DUT:B] <%t> END ACQ 1", $realtime);
            accB.write(`ADC_EIC_BASE + 'hc, 'h2);
            accB.write(`VIC_BASE + 'h1c, 'h0);

            accB.read(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_TRIG_POS, val);
            $display("[DUT:B] <%t> TRIG POSITION %.8x", $realtime, val);

            // DMA transfer
            accB.write(`DMA_BASE + 'h08, val); // dma start addr

            accB.write(`DMA_BASE + 'h0C, 'h00001000); // host addr
            accB.write(`DMA_BASE + 'h10, 'h00000000);

            accB.write(`DMA_BASE + 'h14, 'h00000100); // len << 2

            accB.write(`DMA_BASE + 'h18, 'h00000000); // next
            accB.write(`DMA_BASE + 'h1C, 'h00000000);

            accB.write(`DMA_BASE + 'h20, 'h00000000); // attrib: pcie -> host

            accB.write(`DMA_BASE + 'h00, 'h00000001); // xfer start

            wait (DUTB.DUT.inst_spec_base.irqs[2] == 1);
            $display("[DUT:B] <%t> END DMA 1", $realtime);
            accB.write(`DMA_BASE + 'h04, 'h04); // clear DMA IRQ
            accB.write(`VIC_BASE + 'h1c, 'h0);
         end

      join

      sim_end = 1;

   end

   initial begin
      $display();
      $display("Start of simulation");
      $display("-------------------");
      $display();

      wait (sim_end == 1);

      $display();
      $display("Simulation PASSED");

      $finish;
   end

endmodule // main
