`timescale 1ns/1ns `include "../src/riscv_core/opcode.vh" `include "mem_path.vh" module bios_tb(); reg clk, rst; parameter CPU_CLOCK_PERIOD = 20; parameter CPU_CLOCK_FREQ = 1_000_000_000 / CPU_CLOCK_PERIOD; localparam BAUD_RATE = 10_000_000; localparam BAUD_PERIOD = 1_000_000_000 / BAUD_RATE; // 8680.55 ns localparam TIMEOUT_CYCLE = 100_000; initial clk = 0; always #(CPU_CLOCK_PERIOD/2) clk = ~clk; reg serial_in; wire serial_out; cpu # ( .CPU_CLOCK_FREQ(CPU_CLOCK_FREQ), .RESET_PC(32'h4000_0000), .BAUD_RATE(BAUD_RATE) ) cpu ( .clk(clk), .rst(rst), .serial_in(serial_in), // input .serial_out(serial_out) // output ); integer i, j; reg [31:0] cycle; always @(posedge clk) begin if (rst === 1) cycle <= 0; else cycle <= cycle + 1; end // Host off-chip UART --> FPGA on-chip UART (receiver) // The host (testbench) sends a character to the CPU via the serial line task host_to_fpga; input [7:0] char_in; begin serial_in = 0; #(BAUD_PERIOD); // Data bits (payload) for (i = 0; i < 8; i = i + 1) begin serial_in = char_in[i]; #(BAUD_PERIOD); end // Stop bit serial_in = 1; #(BAUD_PERIOD); $display("[time %t, sim. cycle %d] [Host (tb) --> FPGA_SERIAL_RX] Sent char 8'h%h", $time, cycle, char_in); repeat (300) @(posedge clk); end endtask reg [9:0] char_out; reg [63:0] test_status; reg [31:0] num_failed_tests = 0; // Host off-chip UART <-- FPGA on-chip UART (transmitter) // The host (testbench) expects to receive a character from the CPU via the serial line task fpga_to_host; input [7:0] expected_char; begin // Wait until serial_out is LOW (start of transaction) wait (serial_out === 1'b0); for (j = 0; j < 10; j = j + 1) begin // sample output half-way through the baud period to avoid tricky edge cases #(BAUD_PERIOD / 2); char_out[j] = serial_out; #(BAUD_PERIOD / 2); end if (expected_char === char_out[8:1]) begin test_status = "PASSED"; end else begin test_status = "FAILED"; num_failed_tests = num_failed_tests + 1; end if (expected_char != "\n" && expected_char != 8'h0d) begin $display("[time %t, sim. cycle %d] [Host (tb) <-- FPGA_SERIAL_TX] Got char 8'h%h, expected 8'h%h == %s [ %s ]", $time, cycle, char_out[8:1], expected_char, expected_char, test_status); end else begin $display("[time %t, sim. cycle %d] [Host (tb) <-- FPGA_SERIAL_TX] Got char 8'h%h, expected 8'h%h == %s [ %s ]", $time, cycle, char_out[8:1], expected_char, "newline/CR", test_status); end end endtask reg [14:0] INST_ADDR; reg [31:0] IMM; initial begin #1; // Initialize IMem with the instruction: addi x3, x0, 8 IMM[11:0] = 8; INST_ADDR = 14'h0000; `IMEM_PATH.mem[INST_ADDR + 0] = {IMM[11:0], 5'd0, `FNC_ADD_SUB, 5'd3, `OPC_ARI_ITYPE}; end initial begin $readmemh("../../software/bios/bios.hex", `BIOS_PATH.mem, 0, 4095); `ifndef IVERILOG $vcdpluson; `endif `ifdef IVERILOG $dumpfile("bios_tb.fst"); $dumpvars(0, bios_tb); `endif rst = 1; serial_in = 1; // Hold reset for a while repeat (10) @(posedge clk); @(negedge clk); rst = 0; // Delay for some time repeat (10) @(posedge clk); $display("[TEST 1] BIOS startup. Expect to see: \\r\\n151> "); // initial printout from BIOS program fpga_to_host(8'h0d); // \r fpga_to_host(8'h0a); // \n fpga_to_host(8'h31); // 1 fpga_to_host(8'h35); // 5 fpga_to_host(8'h31); // 1 fpga_to_host(8'h3e); // > fpga_to_host(8'h20); // [space] // Test invalid command $display("[TEST 2] Send an invalid command. Expect to see: \\n\\rUnrecognized token: "); fork begin host_to_fpga(8'h61); // 'a' host_to_fpga(8'h62); // 'b' host_to_fpga(8'h63); // 'c' host_to_fpga(8'h64); // 'd' host_to_fpga(8'h20); // [space] end begin // echo back the input characters ... fpga_to_host(8'h61); // 'a' fpga_to_host(8'h62); // 'b' fpga_to_host(8'h63); // 'c' fpga_to_host(8'h64); // 'd' fpga_to_host(8'h20); // '[space]' // message from BIOS program fpga_to_host(8'h0a); // '\n' fpga_to_host(8'h0d); // '\r' fpga_to_host(8'h55); // 'U' fpga_to_host(8'h6e); // 'n' fpga_to_host(8'h72); // 'r' fpga_to_host(8'h65); // 'e' fpga_to_host(8'h63); // 'c' fpga_to_host(8'h6f); // 'o' fpga_to_host(8'h67); // 'g' fpga_to_host(8'h6e); // 'n' fpga_to_host(8'h69); // 'i' fpga_to_host(8'h7a); // 'z' fpga_to_host(8'h65); // 'e' fpga_to_host(8'h64); // 'd' fpga_to_host(8'h20); // [space] fpga_to_host(8'h74); // 't' fpga_to_host(8'h6f); // 'o' fpga_to_host(8'h6b); // 'k' fpga_to_host(8'h65); // 'e' fpga_to_host(8'h6e); // 'n' fpga_to_host(8'h3a); // ':' fpga_to_host(8'h20); // [space] fpga_to_host(8'h61); // 'a' fpga_to_host(8'h62); // 'b' fpga_to_host(8'h63); // 'c' fpga_to_host(8'h64); // 'd' fpga_to_host(8'h0a); // \n fpga_to_host(8'h0d); // \r fpga_to_host(8'h31); // 1 fpga_to_host(8'h35); // 5 fpga_to_host(8'h31); // 1 fpga_to_host(8'h3e); // > fpga_to_host(8'h20); // [space] end join // Test store command in BIOS mode $display("[TEST 3] Send [sw cafeaaaa 30000004] command. Expect to write 32'hcafeaaaa to both IMem[1] and DMem[1]"); fork begin host_to_fpga(8'h73); // 's' host_to_fpga(8'h77); // 'w' host_to_fpga(8'h20); // [space] host_to_fpga(8'h63); // 'c' host_to_fpga(8'h61); // 'a' host_to_fpga(8'h66); // 'f' host_to_fpga(8'h65); // 'e' host_to_fpga(8'h61); // 'a' host_to_fpga(8'h61); // 'a' host_to_fpga(8'h61); // 'a' host_to_fpga(8'h61); // 'a' host_to_fpga(8'h20); // [space] host_to_fpga(8'h33); // '3' host_to_fpga(8'h30); // '0' host_to_fpga(8'h30); // '0' host_to_fpga(8'h30); // '0' host_to_fpga(8'h30); // '0' host_to_fpga(8'h30); // '0' host_to_fpga(8'h30); // '0' host_to_fpga(8'h34); // '4' host_to_fpga(8'h0d); // \r end begin // echo back the input characters ... fpga_to_host(8'h73); // 's' fpga_to_host(8'h77); // 'w' fpga_to_host(8'h20); // [space] fpga_to_host(8'h63); // 'c' fpga_to_host(8'h61); // 'a' fpga_to_host(8'h66); // 'f' fpga_to_host(8'h65); // 'e' fpga_to_host(8'h61); // 'a' fpga_to_host(8'h61); // 'a' fpga_to_host(8'h61); // 'a' fpga_to_host(8'h61); // 'a' fpga_to_host(8'h20); // [space] fpga_to_host(8'h33); // '3' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h34); // '4' fpga_to_host(8'h0d); // \r fpga_to_host(8'h0a); // \n fpga_to_host(8'h31); // 1 fpga_to_host(8'h35); // 5 fpga_to_host(8'h31); // 1 fpga_to_host(8'h3e); // > fpga_to_host(8'h20); // [space] end join $display("Imem[1]=%h DMem[1]=%h", `IMEM_PATH.mem[1], `DMEM_PATH.mem[1]); $display("Test Write to IMem"); if (`IMEM_PATH.mem[1] === 32'hcafeaaaa) begin $display("PASSED!"); end else $display("FAILED!"); $display("Test Write to DMem"); if (`DMEM_PATH.mem[1] === 32'hcafeaaaa) begin $display("PASSED!"); end else $display("FAILED!"); // Test load command in BIOS mode $display("[TEST 4] Send [lw 30000004] command. Expect to see: 30000004:cafeaaaa"); fork begin host_to_fpga(8'h6c); // 'l' host_to_fpga(8'h77); // 'w' host_to_fpga(8'h20); // [space] host_to_fpga(8'h33); // '3' host_to_fpga(8'h30); // '0' host_to_fpga(8'h30); // '0' host_to_fpga(8'h30); // '0' host_to_fpga(8'h30); // '0' host_to_fpga(8'h30); // '0' host_to_fpga(8'h30); // '0' host_to_fpga(8'h34); // '4' host_to_fpga(8'h0d); // \r end begin // echo back the input characters ... fpga_to_host(8'h6c); // 'l' fpga_to_host(8'h77); // 'w' fpga_to_host(8'h20); // [space] fpga_to_host(8'h33); // '3' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h34); // '4' fpga_to_host(8'h0d); // \r // message from BIOS program fpga_to_host(8'h0a); // \n fpga_to_host(8'h33); // '3' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h34); // '4' fpga_to_host(8'h3a); // ':' fpga_to_host(8'h63); // 'c' fpga_to_host(8'h61); // 'a' fpga_to_host(8'h66); // 'f' fpga_to_host(8'h65); // 'e' fpga_to_host(8'h61); // 'a' fpga_to_host(8'h61); // 'a' fpga_to_host(8'h61); // 'a' fpga_to_host(8'h61); // 'a' fpga_to_host(8'h0d); // \r fpga_to_host(8'h0a); // \n fpga_to_host(8'h31); // 1 fpga_to_host(8'h35); // 5 fpga_to_host(8'h31); // 1 fpga_to_host(8'h3e); // > fpga_to_host(8'h20); // [space] end join // Test jump command: switch to IMem address space $display("[TEST 5] Send [jal 10000000] command. Expect to see: jal 10000000"); fork begin host_to_fpga(8'h6a); // 'j' host_to_fpga(8'h61); // 'a' host_to_fpga(8'h6c); // 'l' host_to_fpga(8'h20); // [space] host_to_fpga(8'h31); // '1' host_to_fpga(8'h30); // '0' host_to_fpga(8'h30); // '0' host_to_fpga(8'h30); // '0' host_to_fpga(8'h30); // '0' host_to_fpga(8'h30); // '0' host_to_fpga(8'h30); // '0' host_to_fpga(8'h30); // '0' host_to_fpga(8'h0d); // \r end begin // echo back the input characters ... fpga_to_host(8'h6a); // 'j' fpga_to_host(8'h61); // 'a' fpga_to_host(8'h6c); // 'l' fpga_to_host(8'h20); // [space] fpga_to_host(8'h31); // '1' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h30); // '0' fpga_to_host(8'h0d); // \r end join repeat (1000) @(posedge clk); // The instruction in IMem should be executed after jumping to IMem space $display("Test RF: RF[3]=%h", `RF_PATH.mem[3]); if (`RF_PATH.mem[3] === IMM[11:0]) begin $display("PASSED!"); end else $display("FAILED!"); repeat (100) @(posedge clk); $display("BIOS testbench done! Num failed tests: %d", num_failed_tests); $finish(); end initial begin repeat (TIMEOUT_CYCLE) @(posedge clk); $display("Timeout!"); $fatal(); end endmodule