//  *********************************************************************************************
//  Description      : Verilator simulation harness
//  Project Version  : v1.0
//  Project          : [BCDC] Microtec Academy Course: Building a RISC-V CPU with SystemVerilog
//  -----
//  Copyright (c)    : 2025 Fraunhofer IIS, Department IDS
//  Created          : 12.Aug.2025  by Marcus Bednara
//  Last Modified    : 15.Oct.2025  by Hussein Elzomor [commit d0452cd]
//  -----
//  HISTORY          : Date         By         Comments
//                     -----------  ---------  -------------------------------------------------
//                     15.Oct.2025  H.Elzomor  Renamed file and module from soc_harness to cpu_harness
//                     15.Oct.2025  H.Elzomor  Added getMem function, setMem task and printMem task
//  *********************************************************************************************



timeunit 1ps;
timeprecision 1ps;

module cpu_harness;

  // local signals
  logic				  clk,
                    rst;

	logic [7:0]		  led;

  logic [6:0]		  btn;


   // toplevel instance (DUT)

   cpu u_cpu  (
                .led        (led),
                .btn        ({btn[6:1], rst}),
                .clk_25mhz  (clk)
			        ); // u_cpu


`ifdef VERILATOR

   // Declare all functions with DPI-C interface to make them accessible from the C++ testbench

   export "DPI-C" task setClk;
   export "DPI-C" task enable;
   export "DPI-C" task loadRAM;
   //  export "DPI-C" task dumpSRAM;
   export "DPI-C" task setInitial;
   export "DPI-C" function getLed;
   export "DPI-C" task setBtn;
   export "DPI-C" task setReset;
   export "DPI-C" function getMem;
   export "DPI-C" task setMem;
   export "DPI-C" task printMem;
   export "DPI-C" function getReg;
   export "DPI-C" task printReg;


   // Clocking is controlled from C++ testbench
   task setClk (input logic val);
      clk = val;
   endtask: setClk

`else

   // If not using verilator, generate clock here

   const realtime   clk_PERIOD  = 1.0ns;


   initial begin: clock_driver
      clk = '1;
      forever begin
	      #(clk_PERIOD/2.0);
	      clk <= ~clk;
      end
   end: clock_driver

   task wait_clk_cycles(int unsigned n);
      repeat(n) @(posedge clk);
   endtask

`endif


// Remaining tasks are identical for Verilator and other simulators



   task loadRAM (input string fileName);
      $display ("Initializing SRAM memory from %s", fileName);
      $readmemh(fileName, u_cpu.theMem.RAM);
   endtask: loadRAM


   /* Some helper stuff */

    task enable(int val);
    // nothing to do, we have no fetch enable in this design
    endtask: enable

    task setInitial();
       btn     = 7'b0;
       rst     = 'b0;
    endtask: setInitial

    function int getLed;
       getLed = {24'b0, led};
    endfunction: getLed

    task setBtn(int val);
       btn[6:1] = val[5:0];
    endtask: setBtn

    task setReset (input int val);
       rst = (val==1);
    endtask: setReset

    function int getMem(int location);
       getMem = u_cpu.theMem.RAM[location];
    endfunction: getMem

    task setMem(int location, int value);
       u_cpu.theMem.RAM[location] = value;
    endtask: setMem

    task printMem (int location, string prog);
      case (prog)
         "fibonacci"   :$display("Value at RAM[%0d]: 0x%h : %0d", location, getMem(location), getMem(location));
         "helloWorld"  :$display("Value at RAM[%0d]: 0x%h : %s" , location, getMem(location), getMem(location));
         default       :$display("Value at RAM[%0d]: 0x%h : %0d", location, getMem(location), getMem(location));
      endcase
    endtask: printMem

    function int getReg(int location);
       getReg = u_cpu.theRegisters.registers[location];
    endfunction: getReg

    task printReg (int location, string prog);
      case (prog)
         "primeNumber" :$display("Time =%5dns \t  : Register [%0d] updated - Finding the prime factors of %0d", $time, location, getReg(location));
         "primeFactors":$display("Time =%5dns \t  : Register [%0d] updated - %0d is a prime factor", $time, location, getReg(location));
         default       :$display("Time =%5dns \t  : Value at Reg[%0d]: 0x%h", $time, location, getReg(location));
      endcase
    endtask: printReg

endmodule: cpu_harness
