//------------------------------------------------------------------------------
// CERN BE-CO-HT
// White Rabbit Trigger Distribution (WRTD)
// https://ohwr.org/projects/wrtd
//------------------------------------------------------------------------------
//
// unit name:   dut_env
//
// description: Wrapper for the Device Under Test (SVEC-based FMC TDC+FD WRTD
// reference design) and its peripherals.
//
//------------------------------------------------------------------------------
// 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 "vhd_wishbone_master.svh"

module simple_tdc_driver
  (
   input        clk,
   input [3:0]  addr,
   inout [27:0] data,
   input        wr,
   input        rd,
   output reg   ef1,
   output reg   ef2,
   input        tstart,
   output       intflag
   );

   typedef struct    {
      int            channel;
      time           ts;
   } acam_fifo_entry;

   acam_fifo_entry pulses[$];

   logic [27:0]      fifos[2][$];

   reg [7:0]         start = 0;
   time              start_time;
   reg [16:0]        start01 = 0;
   reg [7:0]         start_rep = 0;
   const int         start_timer = 15 + 1;
   reg               restart_pulse = 0;
   time              restart_time;
   reg [27:0]        rdata;

   assign data = rdata;
   assign intflag = start[7];

   task push_pulse(int channel, time ts);
      pulses.push_back('{channel: channel, ts: ts});
   endtask

   //  Convert trigger to ACAM data
   always begin
      acam_fifo_entry t;
      time now;

      wait (pulses.size() != 0)
        ;

      t = pulses.pop_front();
      now = $time;

      if (t.ts <= now)
        $display("[DUT] <%t> TDC: pulse in the past (%t now=%t)!", $realtime, t.ts, now);
      else
        begin
           const int fifo_n = t.channel / 4;
           logic [27:0] val;

           #(t.ts - now) ;

           val[27:26] = t.channel & 2'b11;
           val[25:18] = start;
           val[17] = 1'b1;
           val[16:0] = (t.ts - start_time) / 81ps;

           $display("[DUT] <%t> TDC: pulse at %t for channel %0d (start #0x%x, time_data 0x%x, start_time %t)",
                    $realtime, t.ts, t.channel, start, val[16:0], start_time);

           fifos[t.channel / 4].push_back(val);
        end
   end

   initial begin
      rdata = 28'bz;
   end

   always@(posedge tstart) begin
      restart_pulse = 1;
      restart_time = $time;
   end;

   // Internal start retrigger
   always@(posedge clk) begin
      start_rep++;
      if (start_rep == start_timer) begin
         start_rep = 0;
         if (restart_pulse) begin
            start = 1;
            restart_pulse = 0;
            start01 = ($time - restart_time) / 81ps;
         end
         else begin
            start_time = $time;
            start++;
         end
      end
   end

   always@(rd) begin
      rdata <= 28'bz;
      if (rd == 1'b0) begin
         if (addr == 8) begin
            rdata <= fifos[0].pop_front();
         end
         else if (addr == 9) begin
            rdata <= fifos[1].pop_front();
         end
         else if (addr == 10) begin
            rdata <= start01;
         end
         else begin
            $display("[DUT] <%t> invalid ACAM read 0x%x", $realtime, addr);
         end
      end
   end

   assign ef1 = fifos[0].size() == 0;
   assign ef2 = fifos[1].size() == 0;

   always@(negedge wr) begin
      /* Not supposed to have writes to the ACAM if
       firmware is compiled with -DSIMULATION */
      $display("[DUT] <%t> invalid ACAM write 0x%x <- 0x%08x", $realtime, addr, data);
   end

endmodule // simple_tdc_driver

module simple_fdelay_mon
  (
   input [3:0] len,
   input [9:0] val,
   input [3:0] pulse
   );

   typedef struct    {
      int            channel;
      time           ts;
   } fifo_entry;

   fifo_entry pulses[$];

   task push_pulse(int channel, time ts);
      pulses.push_back('{channel: channel, ts: ts});
   endtask

   initial begin
      reg [3:0] prev;
      time      now;

      //  Do nothing before 100us, hw is not initialized.
      # 100us ;

      prev = 0;

      while (pulses.size() != 0) begin
         @pulse ;

         now = $time;
         $display("[FDEL] <%t> Pulse: len=%x, val=%x, out=%x", now, len, val, pulse);
         for(int i = 0; i < 4; i++) begin
            if (prev[i] == 1'b0 && pulse[i] == 1'b1) begin
               automatic fifo_entry e = pulses.pop_front();
               automatic time diff;

               $display("[FDEL]   pulse on channel %0d", i);
               if (e.channel != i) begin
                  $display("FAIL: [FDEL] Bad channel (expected %0d)", e.channel);
                  $finish(1);
               end
               diff = now - e.ts;
               if (diff > 2us && diff < -2us) begin
                  $display("FAIL: [FDEL] Bad timestamp: pulse at %t, expected at %t", now, e.ts);
                  $finish(1);
               end
            end
         end
         prev = pulse;
      end
      $display("SUCCESS: done");
      $finish(0);
   end

endmodule // simple_fdelay_mon

module dut_env
  (
   IVHDWishboneMaster host,
   output clk_sys, rst_sys_n,
          sfp_txp_o, sfp_txn_o,
   input  sfp_rxp_i, sfp_rxn_i
   );

   reg clk_125m_pll  = 0;
   reg clk_125m_gtp  = 0;
   reg clk_20m_vcxo  = 0;
   reg clk_62m5_sys  = 0;
   reg clk_31m5_acam = 0;

   always #4ns    clk_125m_pll  <= ~clk_125m_pll;
   always #4ns    clk_125m_gtp  <= ~clk_125m_gtp;
   always #8ns    clk_62m5_sys  <= ~clk_62m5_sys;
   always #16ns   clk_31m5_acam <= ~clk_31m5_acam;
   always #25ns   clk_20m_vcxo  <= ~clk_20m_vcxo;

   wire tdc_ef1, tdc_ef2;
   wire tdc_rd_n, tdc_wr_n;
   wire tdc_int, tdc_start;
   wire [3:0] tdc_addr;
   wire [27:0] tdc_data;

   wire [3:0]  fdl_len, fdl_pulse;
   wire [9:0]  fdl_val;

   wire sfp_scl, sfp_sda, sfp_sda_en;

   assign clk_sys   = DUT.clk_sys_62m5;
   assign rst_sys_n = DUT.rst_sys_62m5_n;

   //---------------------------------------------------------------------------
   // The DUT
   //---------------------------------------------------------------------------

   wrtd_ref_svec_tdc_fd #
     (
      .g_SIMULATION     (1),
      .g_SIM_BYPASS_VME (1),
      .g_WRPC_INITF     ("../../../dependencies/wr-cores/bin/wrpc/wrc_phy8_sim.bram")
      )
   DUT
     (
      .rst_n_i                    (1'b1),
      .vme_sysreset_n_i           (1'b1),
      .clk_125m_pllref_p_i        (clk_125m_pll),
      .clk_125m_pllref_n_i        (~clk_125m_pll),
      .clk_125m_gtp_p_i           (clk_125m_gtp),
      .clk_125m_gtp_n_i           (~clk_125m_gtp),
      .clk_20m_vcxo_i             (clk_20m_vcxo),
      .sim_wb_i                   (host.out),
      .sim_wb_o                   (host.in),
      .carrier_onewire_b          (),
      .sfp_txp_o                  (sfp_txp_o),
      .sfp_txn_o                  (sfp_txn_o),
      .sfp_rxp_i                  (sfp_rxp_i),
      .sfp_rxn_i                  (sfp_rxn_i),
      .sfp_mod_def0_i             (1'b0),
      .sfp_mod_def1_b             (sfp_scl),
      .sfp_mod_def2_b             (sfp_sda),
      .sfp_rate_select_o          (),
      .sfp_tx_fault_i             (1'b0),
      .sfp_tx_disable_o           (),
      .sfp_los_i                  (1'b0),
      .spi_sclk_o                 (),
      .spi_ncs_o                  (),
      .spi_mosi_o                 (),
      .spi_miso_i                 (1'b0),
      .uart_rxd_i                 (1'b0),
      .uart_txd_o                 (),
      .fmc0_tdc_pll_status_i      (1'b1),
      .fmc0_tdc_125m_clk_p_i      (clk_125m_pll),
      .fmc0_tdc_125m_clk_n_i      (~clk_125m_pll),
      .fmc0_tdc_acam_refclk_p_i   (clk_31m5_acam),
      .fmc0_tdc_acam_refclk_n_i   (~clk_31m5_acam),
      .fmc0_tdc_start_from_fpga_o (tdc_start),
      .fmc0_tdc_ef1_i             (tdc_ef1),
      .fmc0_tdc_ef2_i             (tdc_ef2),
      .fmc0_tdc_err_flag_i        (1'b0),
      .fmc0_tdc_int_flag_i        (tdc_int),
      .fmc0_tdc_data_bus_io       (tdc_data),
      .fmc0_tdc_address_o         (tdc_addr),
      .fmc0_tdc_rd_n_o            (tdc_rd_n),
      .fmc0_tdc_wr_n_o            (tdc_wr_n),
      .fmc1_fd_clk_ref_p_i        (clk_125m_pll),
      .fmc1_fd_clk_ref_n_i        (~clk_125m_pll),
      .fmc1_fd_delay_len_o        (fdl_len),
      .fmc1_fd_delay_val_o        (fdl_val),
      .fmc1_fd_delay_pulse_o      (fdl_pulse)
      );

   //---------------------------------------------------------------------------
   // TDC driver
   //---------------------------------------------------------------------------

   simple_tdc_driver
     TDC
       (
        .clk(clk_31m5_acam),
        .addr(tdc_addr),
        .data(tdc_data),
        .wr(tdc_wr_n),
        .rd(tdc_rd_n),
        .ef1(tdc_ef1),
        .ef2(tdc_ef2),
        .tstart(tdc_start),
        .intflag(tdc_int)
        );

   //---------------------------------------------------------------------------
   // Fine Delay monitor
   //---------------------------------------------------------------------------

   simple_fdelay_mon
     FDL
       (
        .len   (fdl_len),
        .val   (fdl_val),
        .pulse (fdl_pulse)
        );


   //---------------------------------------------------------------------------
   // SFP I2C Adapter
   //---------------------------------------------------------------------------

   gc_sfp_i2c_adapter
     SFP_I2C
       (
        .clk_i           (clk_125m_pll),
        .rst_n_i         (1'b1),
        .scl_i           (sfp_scl),
        .sda_i           (sfp_sda),
        .sda_en_o        (sfp_sda_en),
        .sfp_det_valid_i (1'b1),
        .sfp_data_i      (128'h0123456789ABCDEF0123456789ABCDEF)
        );

   assign sfp_sda = (sfp_sda_en) ? 1'b0:1'bz;

   //---------------------------------------------------------------------------
   // Initial processes
   //---------------------------------------------------------------------------

   task push_pulse(int channel, time ts);
      TDC.push_pulse(channel, ts);
      FDL.push_pulse(channel, ts + 100us); // Delay time (from fd firmware)
   endtask

   initial begin
      //  No pulse before: WR (300us) + TDC setup.
      automatic time start = 480us;

      push_pulse(0, start + 10us);
      push_pulse(1, start + 30us);
      push_pulse(2, start + 50us);

      #(start + 1000us) ;
      //  Should have been finished by the FD monitor.
      $fatal(1, "FAILED");
   end

   initial begin
      // Skip WR SoftPLL lock
      force DUT.cmp_xwrc_board_svec.cmp_board_common.cmp_xwr_core.
        WRPC.U_SOFTPLL.U_Wrapped_Softpll.out_locked_o = 3'b111;
      // Silence Xilinx unisim DSP48A1 warnings about invalid OPMODE
      force DUT.cmp_xwrc_board_svec.cmp_board_common.cmp_xwr_core.
        WRPC.LM32_CORE.gen_profile_medium_icache.U_Wrapped_LM32.cpu.
          multiplier.D1.OPMODE_dly = 0;
      force DUT.cmp_xwrc_board_svec.cmp_board_common.cmp_xwr_core.
        WRPC.LM32_CORE.gen_profile_medium_icache.U_Wrapped_LM32.cpu.
          multiplier.D2.OPMODE_dly = 0;
      force DUT.cmp_xwrc_board_svec.cmp_board_common.cmp_xwr_core.
        WRPC.LM32_CORE.gen_profile_medium_icache.U_Wrapped_LM32.cpu.
          multiplier.D3.OPMODE_dly = 0;
   end // initial begin

endmodule // dut_env
