//------------------------------------------------------------------------------
// 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_100Ms_csr.v"
`include "fmc_adc_alt_trigout.v"

`define ADC_CSR_BASE 'h5000

module main;

   IGN4124PCIMaster hostA ();
   IGN4124PCIMaster hostB ();

   reg       duta_ext_trig, dutb_ext_trig;
   wire[2:0] duta_acq_state, dutb_acq_state;

   dut_env DUTA (hostA, duta_acq_state, a2b_txp, a2b_txn, a2b_rxp, a2b_rxn, duta_ext_trig);
   dut_env DUTB (hostB, dutb_acq_state, 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;

   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" );

            // Config DUTA to trigger on external trigger and get 64 samples
            accA.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_PRE_SAMPLES,  'h00);
            accA.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_POST_SAMPLES, 'h40);
            accA.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_SHOTS,        'h01);
            accA.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_CH1_GAIN,     'h8000);
            accA.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_CH2_GAIN,     'h8000);
            accA.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_CH3_GAIN,     'h8000);
            accA.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_CH4_GAIN,     'h8000);
            accA.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_CH1_SAT,      'h7fff);
            accA.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_CH2_SAT,      'h7fff);
            accA.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_CH3_SAT,      'h7fff);
            accA.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_CH4_SAT,      'h7fff);

            val  = (1'b1 << `FMC_ADC_100MS_CSR_TRIG_EN_EXT_OFFSET);
            val |= (1'b1 << `FMC_ADC_100MS_CSR_TRIG_EN_FWD_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);
            wait (duta_acq_state == 1);
         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" );

            // Config DUTB to trigger on WRTD and get 64 samples
            accB.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_PRE_SAMPLES,  'h00);
            accB.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_POST_SAMPLES, 'h40);
            accB.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_SHOTS,        'h01);
            accB.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_CH1_GAIN,     'h8000);
            accB.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_CH2_GAIN,     'h8000);
            accB.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_CH3_GAIN,     'h8000);
            accB.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_CH4_GAIN,     'h8000);
            accB.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_CH1_SAT,      'h7fff);
            accB.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_CH2_SAT,      'h7fff);
            accB.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_CH3_SAT,      'h7fff);
            accB.write(`ADC_CSR_BASE + `ADDR_FMC_ADC_100MS_CSR_CH4_SAT,      'h7fff);

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

            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);
            wait (dutb_acq_state == 1);
         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_acq_state == 1);
            $display("[DUT:A] <%t> END ACQ 1", $realtime);

            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('h2008, val); // dma start addr

            accA.write('h200C, 'h00001000); // host addr
            accA.write('h2010, 'h00000000);

            accA.write('h2014, 'h00000100); // len << 2

            accA.write('h2018, 'h00000000); // next
            accA.write('h201C, 'h00000000);

            accA.write('h2020, 'h00000000); // attrib: pcie -> host

            accA.write('h2000, 'h00000001); // xfer start

            wait (DUTA.DUT.dma_irq[0] == 1);
            $display("[DUT:A] <%t> END DMA 1", $realtime);
         end

         begin
            wait (dutb_acq_state == 1);
            $display("[DUT:B] <%t> END ACQ 1", $realtime);

            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('h2008, val); // dma start addr

            accB.write('h200C, 'h00001000); // host addr
            accB.write('h2010, 'h00000000);

            accB.write('h2014, 'h00000100); // len << 2

            accB.write('h2018, 'h00000000); // next
            accB.write('h201C, 'h00000000);

            accB.write('h2020, 'h00000000); // attrib: pcie -> host

            accB.write('h2000, 'h00000001); // xfer start

            wait (DUTB.DUT.dma_irq[0] == 1);
            $display("[DUT:B] <%t> END DMA 1", $realtime);
         end

      join


   end

   initial begin
      forever begin
         if ( ( devA != null ) && ( devA.ready ) )
           devA.update();
         if ( ( devB != null ) && ( devB.ready ) )
           devB.update();
         #1us;
      end
   end

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

      #5000us;

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

      $finish;
   end

endmodule // main
