//  *********************************************************************************************
//  Project Version  : v1.0
//  Project          : [BCDC] Microtec Academy Course: Building a RISC-V CPU with SystemVerilog
//  -----
//  Copyright (c)    : 2025 Fraunhofer IIS, Department IDS
//  Created          : 15.Oct.2025  by Hussein Elzomor
//  Last Modified    : 23.Oct.2025  by Hussein Elzomor [commit 2f8f03d]
//  -----
//  HISTORY          : Date         By         Comments
//                     -----------  ---------  -------------------------------------------------
//                     15.Oct.2025  H.Elzomor  Renamed file and module from soc_tb to cpu_tb
//  *********************************************************************************************



// `define fibonacci
// `define helloWorld
// `define primeFactors

`timescale 1ns/1ns

module cpu_tb ();

  // local signals
  logic[7:0]  led;
  logic[6:0]  btn;
  logic       reset;
  logic       clk;

  // Toplevel instance (DUT)
  cpu u_cpu (
    .led(led),
    .btn(btn),
    .clk_25mhz(clk)
  );

  // Tie reset signal to the reset button
  assign btn[0] = reset;

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

  // Initialize and run simulation
  initial begin
    dumpWave("wave.vcd");
    `ifdef fibonacci
      loadMem("fibonacci.hex");
    `elsif helloWorld
      loadMem("helloWorld.hex");
    `elsif primeFactors
      loadMem("primeFactors.hex");
    `endif

    clk   = 0;
    $display("\nTime =  %0dns \t  : Resetting the CPU", $time);
    reset = 0; #100;
    $display("\nTime =  %0dns \t  : Reset released", $time);
    reset = 1; #500000;
    $finish;
  end

  // Fibonacci Program Monitor
  `ifdef fibonacci
    localparam int fibonacciStartLoc = 2027;
    localparam int fibonacciLengthInWords = 10;
    always_comb begin: Monitor_Fibonacci_Series_Calculation
      $display("\nTime =%5dns\t\t\t Hex   \t Dec", $time);
      $display("Value at RAM[%0d]: 0x%h : %0d",fibonacciStartLoc+0, u_cpu.theMem.RAM[fibonacciStartLoc+0], u_cpu.theMem.RAM[fibonacciStartLoc+0]);
      $display("Value at RAM[%0d]: 0x%h : %0d",fibonacciStartLoc+1, u_cpu.theMem.RAM[fibonacciStartLoc+1], u_cpu.theMem.RAM[fibonacciStartLoc+1]);
      $display("Value at RAM[%0d]: 0x%h : %0d",fibonacciStartLoc+2, u_cpu.theMem.RAM[fibonacciStartLoc+2], u_cpu.theMem.RAM[fibonacciStartLoc+2]);
      $display("Value at RAM[%0d]: 0x%h : %0d",fibonacciStartLoc+3, u_cpu.theMem.RAM[fibonacciStartLoc+3], u_cpu.theMem.RAM[fibonacciStartLoc+3]);
      $display("Value at RAM[%0d]: 0x%h : %0d",fibonacciStartLoc+4, u_cpu.theMem.RAM[fibonacciStartLoc+4], u_cpu.theMem.RAM[fibonacciStartLoc+4]);
      $display("Value at RAM[%0d]: 0x%h : %0d",fibonacciStartLoc+5, u_cpu.theMem.RAM[fibonacciStartLoc+5], u_cpu.theMem.RAM[fibonacciStartLoc+5]);
      $display("Value at RAM[%0d]: 0x%h : %0d",fibonacciStartLoc+6, u_cpu.theMem.RAM[fibonacciStartLoc+6], u_cpu.theMem.RAM[fibonacciStartLoc+6]);
      $display("Value at RAM[%0d]: 0x%h : %0d",fibonacciStartLoc+7, u_cpu.theMem.RAM[fibonacciStartLoc+7], u_cpu.theMem.RAM[fibonacciStartLoc+7]);
      $display("Value at RAM[%0d]: 0x%h : %0d",fibonacciStartLoc+8, u_cpu.theMem.RAM[fibonacciStartLoc+8], u_cpu.theMem.RAM[fibonacciStartLoc+8]);
      $display("Value at RAM[%0d]: 0x%h : %0d",fibonacciStartLoc+9, u_cpu.theMem.RAM[fibonacciStartLoc+9], u_cpu.theMem.RAM[fibonacciStartLoc+9]);
      $display("");
    end

  // Hello World Program Monitor
  `elsif helloWorld
    localparam int helloWorldStartLoc = 1024;
    localparam int helloWorldLengthInWords = 4;
    always_comb begin: Monitor_Hello_World_Printing
      $display("\nTime =%5dns\t\t Hex   \t\t ASCII", $time);
      $display("Value at RAM[%0d]: 0x%h : %0s", helloWorldStartLoc+0, u_cpu.theMem.RAM[helloWorldStartLoc+0], u_cpu.theMem.RAM[helloWorldStartLoc+0]);
      $display("Value at RAM[%0d]: 0x%h : %0s", helloWorldStartLoc+1, u_cpu.theMem.RAM[helloWorldStartLoc+1], u_cpu.theMem.RAM[helloWorldStartLoc+1]);
      $display("Value at RAM[%0d]: 0x%h : %0s", helloWorldStartLoc+2, u_cpu.theMem.RAM[helloWorldStartLoc+2], u_cpu.theMem.RAM[helloWorldStartLoc+2]);
      $display("Value at RAM[%0d]: 0x%h : %0s", helloWorldStartLoc+3, u_cpu.theMem.RAM[helloWorldStartLoc+3], u_cpu.theMem.RAM[helloWorldStartLoc+3]);
      $display("");
    end

  // Prime Factors Program Monitor
  `elsif primeFactors
    localparam int primeNumberReg = 16;
    always_comb begin: Monitor_Factorization_Number
      if(u_cpu.theRegisters.registers[primeNumberReg] > 1)
      $display("\nTime =%5dns \t  : Register [%0d] updated - Finding the prime factors of %0d", $time,primeNumberReg, u_cpu.theRegisters.registers[primeNumberReg]);
    end
    localparam int primeFactorsReg = 17;
    always_comb begin: Monitor_Prime_Number
      if(u_cpu.theRegisters.registers[primeFactorsReg] > 1)
        $display("Time =%5dns \t  : Register [%0d] updated - %0d is a prime factor", $time,primeFactorsReg, u_cpu.theRegisters.registers[primeFactorsReg]);
    end
  `endif

  // Wave Dump Helper Task
  int i;
  task dumpWave(string fileName);
    // Open wave file and dump all signals (2D arrays not included)
    $display("\nTime =  %0dns \t  : Opening wave file '%s'", $time, fileName);
    $dumpfile(fileName);
    $display("Time =  %0dns \t  : Dumping all %s signals in wave file (2D arrays not included)", $time, "cpu_tb");
    $dumpvars(0, cpu_tb);

    // Dump Memory in wave file
    $display("\nTime =  %0dns \t  : Dumping Memory in wave file", $time);
    for (i =    0; i <  100; i++) begin $dumpvars(0, cpu_tb.u_cpu.theMem.RAM[i]); end // A part of the Instruction Memory
    for (i = 1024; i < 1124; i++) begin $dumpvars(0, cpu_tb.u_cpu.theMem.RAM[i]); end // A part of the Data Memory
    for (i = 1968; i < 2048; i++) begin $dumpvars(0, cpu_tb.u_cpu.theMem.RAM[i]); end // A part of the Stack

    // Dump registers in wave file
    $display("\nTime =  %0dns \t  : Dumping Registers in wave file", $time);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[1]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[2]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[3]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[4]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[5]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[6]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[7]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[8]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[9]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[10]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[11]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[12]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[13]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[14]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[15]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[16]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[17]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[18]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[19]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[20]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[21]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[22]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[23]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[24]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[25]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[26]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[27]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[28]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[29]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[30]);
    $dumpvars(0, cpu_tb.u_cpu.theRegisters.registers[31]);
  endtask: dumpWave

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

endmodule
