//------------------------------------------------------------------------------
// CERN BE-CO-HT
// GN4124 core for PCIe FMC carrier
// http://www.ohwr.org/projects/gn4124-core
//------------------------------------------------------------------------------
//
// unit name:   main
//
// description: This is a simple example testbench, to demonstrate how to use
// the SystemVerilog BFM of the GN4124 to perform simple accesses over wishbone.
//
// The testbench simply connects the wishbone master of the GN4124 to its own
// DMA configuration wishbone slave and attaches a pre-initialised dummy RAM
// with a wishbone interface to the pipelined DMA interface in order to perform
// a DMA read.
//
//------------------------------------------------------------------------------
// 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"

import wishbone_pkg::*;

module main;
   reg clk_125m = 0;

   logic gn4124_irq;

   t_wishbone_master_in  wb_in, wb_dma_in, wb_mem_in;
   t_wishbone_master_out wb_out, wb_dma_out, wb_mem_out;

   always #4ns clk_125m <= ~clk_125m;

   logic rst_125m_n;

   initial begin
      rst_125m_n = 0;
      #80ns rst_125m_n = 1;
   end

   IGN4124PCIMaster i_gn4124 ();

   xwb_gn4124_core
     DUT (
          .rst_n_a_i          (i_gn4124.rst_n),
          .p2l_clk_p_i        (i_gn4124.p2l_clk_p),
          .p2l_clk_n_i        (i_gn4124.p2l_clk_n),
          .p2l_data_i         (i_gn4124.p2l_data),
          .p2l_dframe_i       (i_gn4124.p2l_dframe),
          .p2l_valid_i        (i_gn4124.p2l_valid),
          .p2l_rdy_o          (i_gn4124.p2l_rdy),
          .p_wr_req_i         (i_gn4124.p_wr_req),
          .p_wr_rdy_o         (i_gn4124.p_wr_rdy),
          .rx_error_o         (i_gn4124.rx_error),
          .vc_rdy_i           (i_gn4124.vc_rdy),
          .l2p_clk_p_o        (i_gn4124.l2p_clk_p),
          .l2p_clk_n_o        (i_gn4124.l2p_clk_n),
          .l2p_data_o         (i_gn4124.l2p_data),
          .l2p_dframe_o       (i_gn4124.l2p_dframe),
          .l2p_valid_o        (i_gn4124.l2p_valid),
          .l2p_edb_o          (i_gn4124.l2p_edb),
          .l2p_rdy_i          (i_gn4124.l2p_rdy),
          .l_wr_rdy_i         (i_gn4124.l_wr_rdy),
          .p_rd_d_rdy_i       (i_gn4124.p_rd_d_rdy),
          .tx_error_i         (i_gn4124.tx_error),
          .dma_irq_o          (dma_irq),
          .irq_p_i            (1'b0),
          .irq_p_o            (gn4124_irq),
          .status_o           (),
          .wb_master_clk_i    (clk_125m),
          .wb_master_rst_n_i  (rst_125m_n),
          .wb_master_i        (wb_in),
          .wb_master_o        (wb_out),
          .wb_dma_cfg_clk_i   (clk_125m),
          .wb_dma_cfg_rst_n_i (rst_125m_n),
          .wb_dma_cfg_i       (wb_out),
          .wb_dma_cfg_o       (wb_in),
          .wb_dma_dat_clk_i   (clk_125m),
          .wb_dma_dat_rst_n_i (rst_125m_n),
          .wb_dma_dat_i       (wb_dma_in),
          .wb_dma_dat_o       (wb_dma_out)
          );

   xwb_dpram #
     (
      .g_size                  (32),
      .g_init_file             ("mem_init.bram"),
      .g_slave1_interface_mode (1), // 1 = PIPELINED
      .g_slave2_interface_mode (1),
      .g_slave1_granularity    (1), // 1 = WORD
      .g_slave2_granularity    (1)
      )
   MEM (
        .rst_n_i   (1'b1),
        .clk_sys_i (clk_125m),
        .slave1_i  (wb_dma_out),
        .slave1_o  (wb_dma_in),
        .slave2_i  (wb_mem_out),
        .slave2_o  (wb_mem_in)
        );

   CBusAccessor acc;

   task val_check(string name, uint32_t addr, val, expected);
      if (val != expected)
        begin
           $display();
           $display("Simulation FAILED");
           $fatal(1, "%s error at address 0x%.2x. Expected 0x%.8x, got 0x%.8x",
                  name, addr, expected, val);
        end
   endtask // val_check

   task reg_check(uint32_t addr, expected);
      uint64_t val;
      acc.read(addr, val);
      val_check("Register read-back", addr, val, expected);
   endtask // reg_check

   task mem_check(uint32_t addr, expected);
      uint64_t val;
      i_gn4124.host_mem_read(addr, val);
      val_check("Memory read-back", addr, val, expected);
   endtask // reg_check

   task check_irq_status;
      //  Check irq status
      reg_check('h04, 'h04);
      if (dma_irq != 1'b1)
        $fatal(1, "dma irq should be 1");
   endtask

   task clear_irq;
      acc.write('h04, 'h04);
      reg_check('h04, 'h00);
      if (dma_irq != 1'b0)
        $fatal(1, "dma irq should be 0");
   endtask

   initial begin

      automatic int ntest = 1;
      const     int tests = 7;

      uint32_t addr, val, expected;

      @(posedge i_gn4124.ready);

      acc = i_gn4124.get_accessor();

      acc.set_default_xfer_size(4);

      @(posedge clk_125m);

      $write("Test %0d/%0d: simple read/write accesses over Wishbone: ",
             ntest++, tests);

      // Verify simple read/writes over wishbone
      reg_check('h0, 'h0);

      acc.write('h0c, 'hffacce55);
      acc.write('h10, 'h1badcafe);

      reg_check('h0c, 'hffacce55);
      reg_check('h10, 'h1badcafe);

      // Reset all DMA config registers
      for (addr = 'h00; addr <= 'h20; addr += 4)
        begin
           acc.write(addr, 'h0);
        end

      $write("PASS\n");

      $write("Test %0d/%0d: 32 reads over DMA, abort after first read: ",
             ntest++, tests);

      if (dma_irq != 1'b0)
        $fatal(1, "dma irq should be 0");

      acc.write('h14, 'h80); // count
      acc.write('h00, 'h01); // start

      // Check values read from memory
      @(posedge i_gn4124.l2p_valid); // skip header
      @(posedge i_gn4124.l2p_valid);

      expected = 32'h8000001f;
      val = i_gn4124.l2p_data;
      @(posedge i_gn4124.l2p_clk_n);
      val |= i_gn4124.l2p_data << 16;
      val_check("DMA read-back", 'h20, val, expected);

      repeat(2) @(posedge clk_125m);

      // Test abort feature
      acc.write('h00, 'h02);
      reg_check('h04, 'h03);
      acc.write('h00, 'h00);

      repeat(2) @(posedge clk_125m);

      $write("PASS\n");

      $write("Test %0d/%0d: 2x32 chained reads over DMA: ",
             ntest++, tests);

      // Setup DMA chain info in BFM memory
      i_gn4124.host_mem_write('h20000, 'h00000000); // remote address
      i_gn4124.host_mem_write('h20004, 'h20000100); // hstartL
      i_gn4124.host_mem_write('h20008, 'h00000000); // hstartH
      i_gn4124.host_mem_write('h2000C, 'h80); // count
      i_gn4124.host_mem_write('h20010, 'h00); // nextL
      i_gn4124.host_mem_write('h20014, 'h00); // nextH
      i_gn4124.host_mem_write('h20018, 'h00); // attrib

      acc.write('h14, 'h80); // count
      acc.write('h20, 'h02); // attrib
      acc.write('h0c, 'h20000000); // hstartL
      acc.write('h10, 'h00000000); // hstartH

      // Point to chain info in BFM memory
      acc.write('h18, 'h20020000); // nextL
      acc.write('h1C, 'h00000000); // nextH

      acc.write('h00, 'h01); // start

      @(posedge dma_irq);

      check_irq_status;

      for (addr = 'h00; addr < 'h20; addr += 1)
        begin
           expected = 32'h80000000 + 'h20 - addr - 1;
           mem_check(4 * addr, expected);
           mem_check('h100 + 4 * addr, expected);
        end

      clear_irq;

      repeat(4) @(posedge clk_125m);

      $write("PASS\n");

      // ---------------------------------
      $write("Test %0d/%0d: 128 reads over DMA: ",
             ntest++, tests);

      // Setup DMA
      acc.write('h14, 'h100); // count
      acc.write('h20, 'h00); // attrib
      acc.write('h0c, 'h20000000); // hstartL
      acc.write('h10, 'h00000000); // hstartH

      acc.write('h00, 'h01); // start

      @(posedge dma_irq);

      check_irq_status;

      for (addr = 'h00; addr < 'h40; addr += 1)
        begin
           expected = 32'h80000000 + 'h20 - addr - 1;
           mem_check(4 * addr, expected);
        end

      clear_irq;

      repeat(4) @(posedge clk_125m);

      $write("PASS\n");

      // Check all four byte swap settings
      // ---------------------------------
      for (int i = 0; i < 4; i++) begin
         $write("Test %0d/%0d: 16KB reads over DMA (byte swap = %0d): ",
                ntest++, tests, i);

         // Restart
         acc.write('h14, 'h4000); // count
         acc.write('h20, 'h00); // attrib
         acc.write('h0c, 'h20000000 + i * 'h1000); // hstartL
         acc.write('h10, 'h00000000); // hstartH
         acc.write('h00, (i << 2) | 'h01); // start

         @(posedge dma_irq);

         check_irq_status;

         for (addr = 'h00; addr < 'h1000; addr += 1)
           begin
              expected = 32'h80000000 + 'h20 - (addr % 'h20) - 1;
              $write("ex: %x", expected);
              if (i == 1)
                expected = {<<8{expected}};
              else if (i == 2)
                expected = {<<16{expected}};
              else if (i == 3)
                expected = {<<16{{<<8{expected}}}};
              $display("ex: %x", expected);
              mem_check((i * 'h1000) + 4 * addr, expected);
           end

         clear_irq;

         repeat(4) @(posedge clk_125m);

         $write("PASS\n");

         #1us;
      end

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

      $finish;
   end
endmodule // main