FPGA-RISC-V-CPU / hardware / sim / mmio_counter_tb.v
mmio_counter_tb.v
Raw
`timescale 1ns/1ns

`include "../src/riscv_core/opcode.vh"
`include "mem_path.vh"

/*
  This testbench contains small sets of RV32I instructions to verify MMIO counters are working.
  It is based on cpu_tb.v.

  How does the testbench work?
  For each test, the testbench initializes IMem with one or several instructions
  (encoded in binary format as specified in the spec) for testing.
  RegFile and DMem are also initialized with some data.

  Each test program should contain the following instructions:
  1. Reset MMIO counters (task write_reset_cntr_inst)
  2. Some user-defined instructions
  3. Load MMIO counter values to registers, set CSR to 1, and then set CSR to 0 (task write_load_cntr_insts)

  Then, the clock is advanced until the CSR is set to 1 (indicating end of program).
  If no correct result is returned after a "timeout" cycle, the testbench will be terminated (or failed).

  Once all instructions are written into CPU's IMEM, the task run_cpu should be called,
  which will reset the CPU, wait for the program to finish, and then print counter values.

  Don't just run the testbench, look at the tests, see what they do.
  The testbench is intended to provide you some examples to get started.
  Feel free to make your own change.
  Note that the testbench is by no means exhaustive.
  You should add your own tests if there are cases you think the testbench
  does not cover.
*/

module mmio_counter_tb();
  reg clk, rst;
  parameter CPU_CLOCK_PERIOD = 20;
  parameter CPU_CLOCK_FREQ   = 1_000_000_000 / CPU_CLOCK_PERIOD;

  initial clk = 0;
  always #(CPU_CLOCK_PERIOD/2) clk = ~clk;
  wire [31:0] csr;
  reg bp_enable = 1'b1;

  // Init PC with 32'h1000_0000 -- address space of IMem
  // When PC is in IMem's address space, IMem is read-only
  // DMem can be R/W as long as the addr bits [31:28] is 4'b00x1
  cpu # (
    .CPU_CLOCK_FREQ(CPU_CLOCK_FREQ),
    .RESET_PC(32'h1000_0000)
  ) cpu (
    .clk(clk),
    .rst(rst),
    .bp_enable(bp_enable),
    .serial_in(1'b1),
    .serial_out()
  );

  wire [31:0] timeout_cycle = 100;  // TODO: change this to a larger number if longer program tests are added.

  // Reset IMem, DMem, and RegFile before running new test
  task reset;
    integer i;
    begin
      for (i = 0; i < `RF_PATH.DEPTH; i = i + 1) begin
        `RF_PATH.mem[i] = 0;
      end
      for (i = 0; i < `DMEM_PATH.DEPTH; i = i + 1) begin
        `DMEM_PATH.mem[i] = 0;
      end
      for (i = 0; i < `IMEM_PATH.DEPTH; i = i + 1) begin
        `IMEM_PATH.mem[i] = 0;
      end
    end
  endtask

  task reset_cpu;
    @(negedge clk);
    rst = 1;
    @(negedge clk);
    rst = 0;
  endtask

  reg [31:0] cycle;
  reg done;
  reg [255:0] current_test_type;
  reg all_tests_passed = 0;


  // Check for timeout
  // If a test does not set CSR to 1 in a given timeout cycle, we terminate the testbench
  initial begin
    while (all_tests_passed === 0) begin
      @(posedge clk);
      if (cycle === timeout_cycle) begin
        $display("[Failed] Timeout at test %s, CSR = %x", current_test_type, `CSR_PATH);
        $finish();
      end
    end
  end

  always @(posedge clk) begin
    if (done === 0)
      cycle <= cycle + 1;
    else
      cycle <= 0;
  end

  integer i;

  reg [31:0] IMM;
  reg [14:0] INST_ADDR;

  // Display RegFile register value
  task display_result_rf;
    input [31:0]  rf_addr;
    input [255:0] test_type;
    begin
      $display("%s : %d", test_type, `RF_PATH.mem[rf_addr]);
    end
  endtask

  task display_counter_values;
    begin
      display_result_rf(5'd7, "Cycle");
      display_result_rf(5'd8, "Instruction");
      display_result_rf(5'd9, "Branch Instruction");
      display_result_rf(5'd10, "Correct Branch Prediction");
    end
  endtask

  task write_reset_cntr_inst;
    begin
      `RF_PATH.mem[1] = 32'hxxxx_xxxx;
      `RF_PATH.mem[2] = 32'h8000_0018;  // The reset counter address
      `IMEM_PATH.mem[INST_ADDR] = {7'd0, 5'd1, 5'd2, `FNC_SW, 5'd0, `OPC_STORE};
      INST_ADDR = INST_ADDR + 'd1;
    end
  endtask

  task write_load_cntr_insts;
    begin
      `RF_PATH.mem[3] = 32'h8000_0010;  // The cycle counter address
      `RF_PATH.mem[4] = 32'h8000_0014;  // The instruction counter address
      `RF_PATH.mem[5] = 32'h8000_001c;  // The branch instruction counter address
      `RF_PATH.mem[6] = 32'h8000_0020;  // The correct branch prediction counter address

      `IMEM_PATH.mem[INST_ADDR + 0] = {12'd0,   5'd3, `FNC_LW, 5'd7,  `OPC_LOAD};
      `IMEM_PATH.mem[INST_ADDR + 1] = {12'd0,   5'd4, `FNC_LW, 5'd8,  `OPC_LOAD};
      `IMEM_PATH.mem[INST_ADDR + 2] = {12'd0,   5'd5, `FNC_LW, 5'd9,  `OPC_LOAD};
      `IMEM_PATH.mem[INST_ADDR + 3] = {12'd0,   5'd6, `FNC_LW, 5'd10, `OPC_LOAD};
      `IMEM_PATH.mem[INST_ADDR + 4] = {12'h51e, 5'd1, 3'b101,  5'd0,  `OPC_CSR};
      `IMEM_PATH.mem[INST_ADDR + 5] = {12'h51e, 5'd0, 3'b101,  5'd0,  `OPC_CSR};
      INST_ADDR = INST_ADDR + 'd6;
    end
  endtask

  task write_nop_inst;
    begin
      // NOP (ADDI x0, x0, 0)
      `IMEM_PATH.mem[INST_ADDR] = {12'd0, 5'd0, `FNC_ADD_SUB, 5'd0, `OPC_ARI_ITYPE};
      INST_ADDR = INST_ADDR + 'd1;
    end
  endtask

  task write_for_loop_program_insts;
    input [11:0]  max_iter;
    reg [4:0] RVAR;
    /*
    
    Implements the following code:
    1. Initialize RVAR to max_iter (ADDI RVAR, x0, max_iter)
    2. Decrement RVAR by 1         (SUB  RVAR, RVAR, -1)
    3. Go back to #2 if RVAR != 0  (BEQ  RVAR, x0, -4)

    In other words, the pseudocode is "for (x = max_iter - 1; x != 0; x = x - 1);"
    */
    begin
      RVAR = 5'd11;
      IMM  = 32'hffff_fffc;
      `IMEM_PATH.mem[INST_ADDR + 0] = {max_iter, 5'd0, `FNC_ADD_SUB, RVAR, `OPC_ARI_ITYPE};
      `IMEM_PATH.mem[INST_ADDR + 1] = {12'hfff, RVAR, `FNC_ADD_SUB, RVAR, `OPC_ARI_ITYPE};
      `IMEM_PATH.mem[INST_ADDR + 2] = {IMM[12], IMM[10:5], 5'd0, RVAR, `FNC_BNE, IMM[4:1], IMM[11], `OPC_BRANCH};
      INST_ADDR = INST_ADDR + 'd3;
    end
  endtask

  task run_cpu;
    begin
      reset_cpu();
      done = 0;
      wait (`CSR_PATH === 1);
      done = 1;
      display_counter_values();
      wait (`CSR_PATH === 0);
    end
  endtask

  initial begin
    `ifndef IVERILOG
        $vcdpluson;
        $vcdplusmemon;
    `endif
    `ifdef IVERILOG
        $dumpfile("mmio_counter_tb.fst");
        $dumpvars(0, mmio_counter_tb);
    `endif

    #0;
    rst = 0;

    // Reset the CPU
    rst = 1;
    // Hold reset for a while
    repeat (10) @(posedge clk);

    @(negedge clk);
    rst = 0;

    // Test NOP Insts --------------------------------------------------
    current_test_type = "NOP";
    $display("Benchmarking %s Program", current_test_type);

    reset();

    // Write program
    INST_ADDR = 14'h0000;
    write_reset_cntr_inst();
    for (i = 0; i < 10; i = i + 1) begin
      write_nop_inst();
    end
    write_load_cntr_insts();

    run_cpu();

    // Test Branching Insts --------------------------------------------
    current_test_type = "For Loop";
    $display("Benchmarking %s Program", current_test_type);
    reset();

    // Write program
    INST_ADDR = 14'h0000;
    write_reset_cntr_inst();
    write_for_loop_program_insts(10);
    write_load_cntr_insts();

    run_cpu();

    // ... what else?
    all_tests_passed = 1'b1;

    repeat (100) @(posedge clk);
    $display("All tests passed!");
    $finish();
  end

endmodule