// Richard CPU // Copyright (C) 2026 Richard Qin // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . // File path: tb/unit/tb_regfile.cc // Description: Register file unit test #include #include #include #include "Vregfile.h" void tick(Vregfile* dut) { dut->clk = 0; dut->eval(); dut->clk = 1; dut->eval(); dut->clk = 0; dut->eval(); } int main(int argc, char** argv) { Verilated::commandArgs(argc, argv); Vregfile* dut = new Vregfile; dut->clk = 0; dut->rst = 0; dut->reg_write_en = 0; dut->rd_addr = 0; dut->rd_wdata = 0; dut->rs1_addr = 0; dut->rs2_addr = 0; std::cout << "----------------------------------" << std::endl; std::cout << " Register File Unit Test" << std::endl; std::cout << "----------------------------------" << std::endl; // Test 1: Reset dut->rst = 1; tick(dut); dut->rst = 0; std::cout << "\033[32m[PASS] Reset complete\033[0m" << std::endl; // Test 2: Write and read dut->reg_write_en = 1; dut->rd_addr = 5; dut->rd_wdata = 0xDEADBEEF; tick(dut); dut->reg_write_en = 0; dut->rs1_addr = 5; dut->eval(); assert(dut->rs1_rdata == 0xDEADBEEF); std::cout << "\033[32m[PASS] Write and read (x5) complete\033[0m" << std::endl; // Test 3: x0 dut->reg_write_en = 1; dut->rd_addr = 0; dut->rd_wdata = 0x12345678; tick(dut); dut->reg_write_en = 0; dut->rs2_addr = 0; dut->eval(); assert(dut->rs2_rdata == 0); std::cout << "\033[32m[PASS] Write and read (x0) complete\033[0m" << std::endl; // Test 4: Dual-port simultaneous read dut->reg_write_en = 1; dut->rd_addr = 6; dut->rd_wdata = 0x87654321; tick(dut); dut->reg_write_en = 1; dut->rd_addr = 7; dut->rd_wdata = 0x11223344; tick(dut); dut->rs1_addr = 6; dut->rs2_addr = 7; dut->eval(); assert(dut->rs1_rdata == 0x87654321 && dut->rs2_rdata == 0x11223344); std::cout << "\033[32m[PASS] Dual-port simultaneous read complete\033[0m" << std::endl; // Test 5: Write enable = 0 (Data should not change) dut->reg_write_en = 1; dut->rd_addr = 10; dut->rd_wdata = 0xCAFEBABE; tick(dut); dut->reg_write_en = 0; // Disable write dut->rd_addr = 10; dut->rd_wdata = 0x11111111; // Attempt to overwrite tick(dut); dut->rs1_addr = 10; dut->eval(); assert(dut->rs1_rdata == 0xCAFEBABE); std::cout << "\033[32m[PASS] Write enable = 0 correctly ignores write\033[0m" << std::endl; // Test 6: Verify full reset behavior // First, dirty up some registers for (int i = 1; i < 32; i += 5) { dut->reg_write_en = 1; dut->rd_addr = i; dut->rd_wdata = 0x1000 + i; tick(dut); } // Apply reset dut->rst = 1; tick(dut); dut->rst = 0; // Check all are zero bool reset_ok = true; for (int i = 1; i < 32; i++) { dut->rs1_addr = i; dut->eval(); if (dut->rs1_rdata != 0) { reset_ok = false; std::cerr << "\033[31m[FAIL] Reset failed on register x" << i << std::endl; } } assert(reset_ok); std::cout << "\033[32m[PASS] Full memory reset verified\033[0m" << std::endl; // Test 7: Read-After-Write (Same cycle collision check) // Combinational read should return the OLD value on the cycle WE is asserted // NOTE: Verilator handles combinational loops. Before the clock edge, it evaluates. dut->reg_write_en = 1; dut->rd_addr = 15; dut->rd_wdata = 0x55555555; // Pseudo // Setup for RAW dut->reg_write_en = 1; dut->rd_addr = 15; dut->rd_wdata = 0x99999999; // Before tick, read should be 0 (from previous reset) dut->rs1_addr = 15; dut->eval(); assert(dut->rs1_rdata == 0); tick(dut); // Clock edge happens dut->reg_write_en = 0; // Turn off write dut->eval(); // Combinational read update assert(dut->rs1_rdata == 0x99999999); std::cout << "\033[32m[PASS] Read-After-Write (RAW) behavior verified\033[0m" << std::endl; // Test 8: Sweep all registers (Write and Read distinct values) for (int i = 1; i < 32; i++) { dut->reg_write_en = 1; dut->rd_addr = i; dut->rd_wdata = 0xABCDE000 + i; tick(dut); } dut->reg_write_en = 0; bool sweep_ok = true; for (int i = 1; i < 32; i++) { dut->rs1_addr = i; dut->eval(); if (dut->rs1_rdata != (0xABCDE000 + i)) { sweep_ok = false; } } assert(sweep_ok); std::cout << "\033[32m[PASS] All 31 mutable registers write/read successfully without collision\033[0m" << std::endl; std::cout << "----------------------------------" << std::endl; std::cout << "\033[32mAll 8 tests passed!\033[0m" << std::endl; std::cout << "----------------------------------" << std::endl; delete dut; exit(0); }