//  *********************************************************************************************
//  Project Version  : v1.0
//  Project          : [BCDC] Microtec Academy Course: Building a RISC-V CPU with SystemVerilog
//  -----
//  Copyright (c)    : 2025 Fraunhofer IIS, Department IDS
//  Created          : 20.Oct.2025  by Aliakbar Merchant
//  Last Modified    : 24.Oct.2025  by Aliakbar Merchant [commit 89a5087]
//  -----
//  HISTORY          : Date         By         Comments
//                     -----------  ---------  -------------------------------------------------
//  *********************************************************************************************



`timescale 1ns/1ns

module main_mem_tb ();

  // local Parameters
    localparam string MEM_INIT_FILE      = "main_mem.hex";
    // Helpers for addressing width
    localparam logic[1:0] _byte = 2'b00;     // byte:  8 bits
    localparam logic[1:0] _half = 2'b01;     // half: 16 bits
    localparam logic[1:0] _word = 2'b10;     // word: 32 bits

  // Local Signals
    // clk and reset
    logic        clk;
    logic        reset;
    // Memory
    logic[31:0]  DAddr;
    logic[31:0]  IAddr;
    logic[31:0]  DWData;
    logic[31:0]  DRData;
    logic[31:0]  IRData;
    logic        DWE;
    logic[1:0]   DWidth;
    // Helpers for display results purpose
    logic[31:0]  value_at_0;
    logic[31:0]  value_at_1;
    logic[31:0]  value_at_2;
    logic[31:0]  value_at_3;
    logic[31:0]  value_at_1024;
    logic[31:0]  value_at_1025;
    logic[31:0]  value_at_1026;
    logic[31:0]  value_at_1027;

  // Toplevel instance (DUT)
  main_mem u_main_mem (
      .clk     (clk    ),
      .reset   (reset  ),
      .DAddr   (DAddr  ),
      .IAddr   (IAddr  ),
      .DWData  (DWData ),
      .DRData  (DRData ),
      .IRData  (IRData ),
      .DWE     (DWE    ),
      .DWidth  (DWidth )
  );

  // Clock generation
  always #1 clk = ~clk;

  // Initialize and run simulation
  initial begin
    loadMem(MEM_INIT_FILE);
    value_at_0    = main_mem_tb.u_main_mem.RAM[0];
    value_at_1    = main_mem_tb.u_main_mem.RAM[1];
    value_at_2    = main_mem_tb.u_main_mem.RAM[2];
    value_at_3    = main_mem_tb.u_main_mem.RAM[3];
    value_at_1024 = main_mem_tb.u_main_mem.RAM[1024];
    value_at_1025 = main_mem_tb.u_main_mem.RAM[1025];
    value_at_1026 = main_mem_tb.u_main_mem.RAM[1026];
    value_at_1027 = main_mem_tb.u_main_mem.RAM[1027];
    clk    = 0;
    DAddr  = 32'd4096;
    IAddr  = 0;
    DWData = 0;
    DWidth = 0;
    reset  = 1; DWE=1; #7;
    DWE    = 0;
    reset  = 0; #2;
    test_scenario1(); #30;

    $finish;
  end

  // Test scenario 1
  task test_scenario1();
    fork
      read_inst_mem(15);
      display_inst_read_results();
      write_data_mem(4096);
    join
    display_write_results();
    fork
      display_read_results();
      read_data_mem(4096);
    join
  endtask: test_scenario1

  //  Intruction memory read transactions and results display
    task read_inst_mem(int unsigned n = 1);
      repeat(n) begin
        cycles_pe(1);
        IAddr = IAddr+4;
      end
    endtask:read_inst_mem

    task display_inst_read_results();
      $display("\n----------------------------------------------------------------------------------" );
      $display("Instruction MEMORY READ RESULTS" );
      $display("15 instruction are read by testbench starting at adrress 0x0000 i.e at (RAM[0]) but only 4 displyed for simplicity" );
      $display("----------------------------------------------------------------------------------" );

      cycles_pe(2);
      $display("Time =  %0dns \t  : IAddr = 0x0000(RAM[0]): Expected value = 0x%h  Actual Value at IRData: 0x%h" , $time, value_at_0, IRData );
      cycles_pe(1);
      $display("Time =  %0dns \t  : IAddr = 0x0004(RAM[1]): Expected value = 0x%h  Actual Value at IRData: 0x%h" , $time, value_at_1, IRData );
      cycles_pe(1);
      $display("Time =  %0dns \t  : IAddr = 0x0008(RAM[2]): Expected value = 0x%h  Actual Value at IRData: 0x%h" , $time, value_at_2, IRData );
      cycles_pe(1);
      $display("Time =  %0dns \t  : IAddr = 0x000c(RAM[3]): Expected value = 0x%h  Actual Value at IRData: 0x%h" , $time, value_at_3, IRData );
    endtask:display_inst_read_results

  // Data memory write transaction and results display
    task write_data_mem(logic [31:0] daddr_init=32'd4096);
      cycles_pe(1);
      DWE    = 1;
      DWidth = 0;
      DAddr  = daddr_init+0;
      DWData = 32'hDEADBEEF;
      cycles_pe(1);
      DWE    = 1;
      DWidth = 1;
      DAddr  = daddr_init+4;
      DWData = 32'hDEADBEEF;
      cycles_pe(1);
      DWE    = 1;
      DWidth = 2;
      DAddr  = daddr_init+8;
      DWData = 32'hDEADBEEF;
      cycles_pe(1);
      DWE    = 1;
      DWidth = 3;
      DAddr  = daddr_init+12;
      DWData = 32'hDEADBEEF;
      cycles_pe(1);
      DWE    = 0;
    endtask:write_data_mem

    task display_write_results();
      $display("\n----------------------------------------------------------------------------------" );
      $display("DATA MEMORY WRITE RESULTS" );
      $display("4 Write transaction are being done by testbench starting at adrress 0x1000 i.e at (RAM[1024]) with all 4 combination of DWidth" );
      $display("Data written is always 0xDEADBEEF i.e DWData = DEADBEEF" );
      $display("----------------------------------------------------------------------------------" );

      $display("Time =  %0dns \t  : DWidth = 0 DAddr = 0x1000(RAM[1024]): Start value at RAM[1024] = 0x%h Expected value at RAM[1024]= 0xef%h  Actual Value at RAM[1024]: 0x%h" , $time, value_at_1024,value_at_1024[23:0], getMem(1024) );
      $display("Time =  %0dns \t  : DWidth = 1 DAddr = 0x1004(RAM[1025]): Start value at RAM[1025] = 0x%h Expected value at RAM[1025]= 0xbeef%h  Actual Value at RAM[1025]: 0x%h" , $time, value_at_1025,value_at_1025[15:0], getMem(1025) );
      $display("Time =  %0dns \t  : DWidth = 2 DAddr = 0x1008(RAM[1026]): Start value at RAM[1026] = 0x%h Expected value at RAM[1026]= 0xdeadbeef  Actual Value at RAM[1026]: 0x%h" , $time, value_at_1026, getMem(1026) );
      $display("Time =  %0dns \t  : DWidth = 3 DAddr = 0x100c(RAM[1027]): Start value at RAM[1027] = 0x%h Expected value at RAM[1027]= 0x%h  Actual Value at RAM[1027]: 0x%h" , $time, value_at_1027,value_at_1027, getMem(1027) );
    endtask:display_write_results

  // Data Memory read transaction and results display
    task read_data_mem(logic [31:0] daddr_init=32'd4096);
      cycles_pe(1);
      DWidth = 0;
      DAddr = daddr_init+0;
      cycles_pe(1);
      DWidth = 1;
      DAddr = daddr_init+4;
      cycles_pe(1);
      DWidth = 2;
      DAddr = daddr_init+8;
      cycles_pe(1);
      DWidth = 3;
      DAddr = daddr_init+12;
      cycles_pe(1);
      DWidth = 2;
      DAddr = daddr_init+0;
      cycles_pe(1);
      DWidth = 2;
      DAddr = daddr_init+4;
      cycles_pe(1);
      DWidth = 2;
      DAddr = daddr_init+8;
      cycles_pe(1);
      DWidth = 2;
      DAddr = daddr_init+12;
    endtask:read_data_mem

    task display_read_results();
      $display("\n----------------------------------------------------------------------------------" );
      $display("DATA MEMORY READ RESULTS" );
      $display("8 Read transaction are being done by testbench starting at adrress 0x1000 i.e at (RAM[1024])" );
      $display("----------------------------------------------------------------------------------" );

      cycles_pe(2);
      $display("Time =  %0dns \t  : DWidth = 0 DAddr = 0x1000(RAM[1024]): Expected value = 0x000000ef  Actual Value at DRData: 0x%h" , $time, DRData );
      cycles_pe(1);
      $display("Time =  %0dns \t  : DWidth = 1 DAddr = 0x1004(RAM[1025]): Expected value = 0x0000beef  Actual Value at DRData: 0x%h" , $time, DRData );
      cycles_pe(1);
      $display("Time =  %0dns \t  : DWidth = 2 DAddr = 0x1008(RAM[1026]): Expected value = 0xdeadbeef  Actual Value at DRData: 0x%h" , $time, DRData );
      cycles_pe(1);
      $display("Time =  %0dns \t  : DWidth = 3 DAddr = 0x100c(RAM[1027]): Expected value = 0xdeadbeef  Actual Value at DRData: 0x%h" , $time, DRData );
      cycles_pe(1);
      $display("Time =  %0dns \t  : DWidth = 2 DAddr = 0x1000(RAM[1024]): Expected value = 0xef%h  Actual Value at DRData: 0x%h" , $time, value_at_1024[23:0], DRData );
      cycles_pe(1);
      $display("Time =  %0dns \t  : DWidth = 2 DAddr = 0x1004(RAM[1025]): Expected value = 0xbeef%h  Actual Value at DRData: 0x%h" , $time, value_at_1025[15:0], DRData );
      cycles_pe(1);
      $display("Time =  %0dns \t  : DWidth = 2 DAddr = 0x1008(RAM[1026]): Expected value = 0xdeadbeef  Actual Value at DRData: 0x%h" , $time, DRData );
      cycles_pe(1);
      $display("Time =  %0dns \t  : DWidth = 2 DAddr = 0x100c(RAM[1027]): Expected value = 0x%h  Actual Value at DRData: 0x%h" , $time, value_at_1027, DRData );
    endtask:display_read_results


  // Helper tasks
    // Memory
    function int getMem(int location);
        getMem = u_main_mem.RAM[location];
    endfunction: getMem

    task cycles_pe(int unsigned N = 1);
      repeat(N) @(posedge clk);
    endtask: cycles_pe

    task cycles_ne(int unsigned N = 1);
      repeat(N) @(negedge clk);
    endtask: cycles_ne

    // Load hex file into memory
    task loadMem (string fileName);
      $display("\nTime =  %0dns \t  : Loading '%s' into Memory", $time, fileName);
      $readmemh(fileName, main_mem_tb.u_main_mem.RAM);
    endtask: loadMem

endmodule
