aboutsummaryrefslogtreecommitdiff
path: root/rv64cpu/tb
diff options
context:
space:
mode:
authorRichard Qin <richard.qin.x@gmail.com>2026-11-07 03:30:17 +0800
committerRichard Qin <richard.qin.x@gmail.com>2026-11-07 03:31:03 +0800
commit2a4c290c55b683352a16747432a7fc4635597cc6 (patch)
treea150caea2ade12af4c5dcd70f09bacd3318f4d43 /rv64cpu/tb
parente0948e3fc10029ba4bb4d48d7b5daae94fe575b9 (diff)
downloadrcc-project-2a4c290c55b683352a16747432a7fc4635597cc6.tar.gz
rcc-project-2a4c290c55b683352a16747432a7fc4635597cc6.tar.xz
refactor: reset software and test directories
Diffstat (limited to 'rv64cpu/tb')
-rw-r--r--rv64cpu/tb/CMakeLists.txt275
-rw-r--r--rv64cpu/tb/compliance/CMakeLists.txt60
-rw-r--r--rv64cpu/tb/compliance/compliance_runner.cc634
-rw-r--r--rv64cpu/tb/core/tb_core.cc1136
-rw-r--r--rv64cpu/tb/unit/tb_alu.cc136
-rw-r--r--rv64cpu/tb/unit/tb_branch.cc100
-rw-r--r--rv64cpu/tb/unit/tb_csr.cc770
-rw-r--r--rv64cpu/tb/unit/tb_decoder.cc353
-rw-r--r--rv64cpu/tb/unit/tb_ex_mem_reg.cc182
-rw-r--r--rv64cpu/tb/unit/tb_ex_stage.cc218
-rw-r--r--rv64cpu/tb/unit/tb_hazard_unit.cc364
-rw-r--r--rv64cpu/tb/unit/tb_id_ex_reg.cc177
-rw-r--r--rv64cpu/tb/unit/tb_id_stage.cc194
-rw-r--r--rv64cpu/tb/unit/tb_if_id_reg.cc127
-rw-r--r--rv64cpu/tb/unit/tb_if_stage.cc132
-rw-r--r--rv64cpu/tb/unit/tb_mem_stage.cc305
-rw-r--r--rv64cpu/tb/unit/tb_mem_wb_reg.cc165
-rw-r--r--rv64cpu/tb/unit/tb_mmu_top.cc236
-rw-r--r--rv64cpu/tb/unit/tb_ptw.cc206
-rw-r--r--rv64cpu/tb/unit/tb_regfile.cc192
-rw-r--r--rv64cpu/tb/unit/tb_tlb.cc172
-rw-r--r--rv64cpu/tb/unit/tb_trap_ctrl.cc284
-rw-r--r--rv64cpu/tb/unit/tb_wb_stage.cc163
23 files changed, 0 insertions, 6581 deletions
diff --git a/rv64cpu/tb/CMakeLists.txt b/rv64cpu/tb/CMakeLists.txt
deleted file mode 100644
index 33a6224..0000000
--- a/rv64cpu/tb/CMakeLists.txt
+++ /dev/null
@@ -1,275 +0,0 @@
-# ─── Testbench CMake ──────────────────────────────────────────────────────────────
-#
-# Each testbench is registered as a Verilator simulation target + CTest.
-#
-# To add a new unit test, call:
-# add_verilator_target(
-# NAME tb_<module>
-# TOP_MODULE <module>
-# SOURCES ${RTL_CORE_DIR}/<module>.sv [... dependencies ...]
-# INCLUDE_DIRS ${RTL_INCLUDE_DIR}
-# TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_<module>.cpp
-# )
-#
-
-enable_testing()
-
-# ─── Collect common RTL include dirs ─────────────────────────────────────────────
-set(RTL_COMMON_INCLUDES
- ${RTL_INCLUDE_DIR}
-)
-
-# ─── Collect all RTL sources for SoC-level sim ───────────────────────────────────
-file(GLOB_RECURSE ALL_RTL_SOURCES
- ${RTL_DIR}/*.sv
-)
-
-# ═══════════════════════════════════════════════════════════════════════════════════
-# Unit Tests
-# ═══════════════════════════════════════════════════════════════════════════════════
-#
-# Uncomment and fill in as you implement each module, for example:
-#
-add_verilator_target(
- NAME tb_alu
- TOP_MODULE alu
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/alu.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_alu.cc
-)
-
-add_verilator_target(
- NAME tb_decoder
- TOP_MODULE decoder
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/decoder.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_decoder.cc
-)
-
-add_verilator_target(
- NAME tb_regfile
- TOP_MODULE regfile
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/regfile.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_regfile.cc
-)
-
-add_verilator_target(
- NAME tb_branch
- TOP_MODULE branch_unit
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/branch_unit.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_branch.cc
-)
-
-add_verilator_target(
- NAME tb_if_stage
- TOP_MODULE if_stage
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/if_stage.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_if_stage.cc
-)
-
-add_verilator_target(
- NAME tb_id_stage
- TOP_MODULE id_stage
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/decoder.sv
- ${RTL_CORE_DIR}/regfile.sv
- ${RTL_CORE_DIR}/fp_regfile.sv
- ${RTL_CORE_DIR}/id_stage.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_id_stage.cc
-)
-
-add_verilator_target(
- NAME tb_if_id_reg
- TOP_MODULE if_id_reg
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/if_id_reg.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_if_id_reg.cc
-)
-
-add_verilator_target(
- NAME tb_id_ex_reg
- TOP_MODULE id_ex_reg
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/id_ex_reg.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_id_ex_reg.cc
-)
-
-add_verilator_target(
- NAME tb_ex_stage
- TOP_MODULE ex_stage
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/alu.sv
- ${RTL_CORE_DIR}/branch_unit.sv
- ${RTL_CORE_DIR}/ex_stage.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_ex_stage.cc
-)
-
-add_verilator_target(
- NAME tb_ex_mem_reg
- TOP_MODULE ex_mem_reg
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/ex_mem_reg.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_ex_mem_reg.cc
-)
-
-add_verilator_target(
- NAME tb_mem_stage
- TOP_MODULE mem_stage
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/mem_stage.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_mem_stage.cc
-)
-
-add_verilator_target(
- NAME tb_mem_wb_reg
- TOP_MODULE mem_wb_reg
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/mem_wb_reg.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_mem_wb_reg.cc
-)
-
-add_verilator_target(
- NAME tb_wb_stage
- TOP_MODULE wb_stage
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/wb_stage.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_wb_stage.cc
-)
-
-add_verilator_target(
- NAME tb_hazard_unit
- TOP_MODULE hazard_unit
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/hazard_unit.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_hazard_unit.cc
-)
-
-add_verilator_target(
- NAME tb_trap_ctrl
- TOP_MODULE trap_ctrl
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/include/csr_pkg.sv
- ${RTL_CORE_DIR}/trap_ctrl.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_trap_ctrl.cc
-)
-#
-add_verilator_target(
- NAME tb_csr
- TOP_MODULE csr_unit
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/include/csr_pkg.sv
- ${RTL_CORE_DIR}/csr_unit.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_csr.cc
-)
-
-add_verilator_target(
- NAME tb_tlb
- TOP_MODULE tlb
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/mmu/tlb.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_tlb.cc
-)
-
-add_verilator_target(
- NAME tb_ptw
- TOP_MODULE ptw
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/mmu/ptw.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_ptw.cc
-)
-
-add_verilator_target(
- NAME tb_mmu_top
- TOP_MODULE mmu_top
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/mmu/tlb.sv
- ${RTL_CORE_DIR}/mmu/ptw.sv
- ${RTL_CORE_DIR}/mmu/mmu_top.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_mmu_top.cc
- EXTRA_FLAGS -Wno-VARHIDDEN
-)
-
-# ═══════════════════════════════════════════════════════════════════════════════════
-# Core Integration Test
-# ═══════════════════════════════════════════════════════════════════════════════════
-
-add_verilator_target(
- NAME tb_core
- TOP_MODULE richard_core
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/alu.sv
- ${RTL_CORE_DIR}/branch_unit.sv
- ${RTL_CORE_DIR}/compressed_decoder.sv
- ${RTL_CORE_DIR}/decoder.sv
- ${RTL_CORE_DIR}/regfile.sv
- ${RTL_CORE_DIR}/fp_regfile.sv
- ${RTL_CORE_DIR}/if_stage.sv
- ${RTL_CORE_DIR}/if_id_reg.sv
- ${RTL_CORE_DIR}/id_stage.sv
- ${RTL_CORE_DIR}/id_ex_reg.sv
- ${RTL_CORE_DIR}/ex_stage.sv
- ${RTL_CORE_DIR}/fpu/fpu_unit.sv
- ${RTL_CORE_DIR}/ex_mem_reg.sv
- ${RTL_CORE_DIR}/mem_stage.sv
- ${RTL_CORE_DIR}/mem_wb_reg.sv
- ${RTL_CORE_DIR}/wb_stage.sv
- ${RTL_CORE_DIR}/hazard_unit.sv
- ${RTL_CORE_DIR}/include/csr_pkg.sv
- ${RTL_CORE_DIR}/csr_unit.sv
- ${RTL_CORE_DIR}/pmp_checker.sv
- ${RTL_CORE_DIR}/mmu/tlb.sv
- ${RTL_CORE_DIR}/mmu/ptw.sv
- ${RTL_CORE_DIR}/mmu/mmu_top.sv
- ${RTL_CORE_DIR}/trap_ctrl.sv
- ${RTL_CORE_DIR}/richard_core.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/core/tb_core.cc
- EXTRA_FLAGS -Wno-PINCONNECTEMPTY
-)
-
-add_test(NAME tb_core_delegation COMMAND $<TARGET_FILE:tb_core> --case mret_sret_redirect)
-add_test(NAME tb_core_interrupt_priority COMMAND $<TARGET_FILE:tb_core> --case branch_interrupt_priority)
-add_test(NAME tb_core_double_exception COMMAND $<TARGET_FILE:tb_core> --case double_exception_flush)
-add_test(NAME tb_core_mem_fault COMMAND $<TARGET_FILE:tb_core> --case mem_fault_pipeline)
-add_test(NAME tb_core_mie_masking COMMAND $<TARGET_FILE:tb_core> --case mie_masking_response)
-
-# ═══════════════════════════════════════════════════════════════════════════════════
-# SoC Integration Test
-# ═══════════════════════════════════════════════════════════════════════════════════
-#
-# add_verilator_target(
-# NAME tb_soc
-# TOP_MODULE richard_soc
-# SOURCES ${ALL_RTL_SOURCES}
-# INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
-# TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/soc/tb_soc.cc
-# EXTRA_FLAGS --threads 2
-# )
-
-# ═══════════════════════════════════════════════════════════════════════════════════
-# Compliance Tests
-# ═══════════════════════════════════════════════════════════════════════════════════
-
-add_subdirectory(compliance)
diff --git a/rv64cpu/tb/compliance/CMakeLists.txt b/rv64cpu/tb/compliance/CMakeLists.txt
deleted file mode 100644
index 677cae6..0000000
--- a/rv64cpu/tb/compliance/CMakeLists.txt
+++ /dev/null
@@ -1,60 +0,0 @@
-# ─── Compliance Test CMake ───────────────────────────────────────────────────────
-#
-# Builds the compliance test runner and registers riscv-arch-test tests.
-#
-# ─────────────────────────────────────────────────────────────────────────────────
-
-# Minimal compliance runner target (core-only for bootstrap stage).
-# NOTE: This uses richard_core until SoC top and memory/peripheral harness are ready.
-add_verilator_target(
- NAME compliance_runner
- TOP_MODULE richard_core
- SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
- ${RTL_CORE_DIR}/alu.sv
- ${RTL_CORE_DIR}/branch_unit.sv
- ${RTL_CORE_DIR}/compressed_decoder.sv
- ${RTL_CORE_DIR}/decoder.sv
- ${RTL_CORE_DIR}/regfile.sv
- ${RTL_CORE_DIR}/fp_regfile.sv
- ${RTL_CORE_DIR}/if_stage.sv
- ${RTL_CORE_DIR}/if_id_reg.sv
- ${RTL_CORE_DIR}/id_stage.sv
- ${RTL_CORE_DIR}/id_ex_reg.sv
- ${RTL_CORE_DIR}/ex_stage.sv
- ${RTL_CORE_DIR}/fpu/fpu_unit.sv
- ${RTL_CORE_DIR}/ex_mem_reg.sv
- ${RTL_CORE_DIR}/mem_stage.sv
- ${RTL_CORE_DIR}/mem_wb_reg.sv
- ${RTL_CORE_DIR}/wb_stage.sv
- ${RTL_CORE_DIR}/hazard_unit.sv
- ${RTL_CORE_DIR}/include/csr_pkg.sv
- ${RTL_CORE_DIR}/csr_unit.sv
- ${RTL_CORE_DIR}/pmp_checker.sv
- ${RTL_CORE_DIR}/mmu/tlb.sv
- ${RTL_CORE_DIR}/mmu/ptw.sv
- ${RTL_CORE_DIR}/mmu/mmu_top.sv
- ${RTL_CORE_DIR}/trap_ctrl.sv
- ${RTL_CORE_DIR}/richard_core.sv
- INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
- TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/compliance_runner.cc
- EXTRA_FLAGS -Wno-PINCONNECTEMPTY
-)
-
-# ─── Register individual compliance tests via CTest ──────────────────────────────
-# This is done dynamically by the compliance_runner binary and run_compliance.py.
-# You can also manually register tests like:
-#
-# set(RISCV_ARCH_TEST_DIR ${CMAKE_SOURCE_DIR}/sw/riscv-arch-test)
-#
-# file(GLOB RV64I_TESTS ${RISCV_ARCH_TEST_DIR}/riscv-test-suite/rv64i_m/I/src/*.S)
-# foreach(test_src ${RV64I_TESTS})
-# get_filename_component(test_name ${test_src} NAME_WE)
-# add_test(
-# NAME compliance_rv64i_${test_name}
-# COMMAND compliance_runner --test ${test_src}
-# )
-# set_tests_properties(compliance_rv64i_${test_name} PROPERTIES
-# LABELS "compliance;rv64i"
-# TIMEOUT 30
-# )
-# endforeach()
diff --git a/rv64cpu/tb/compliance/compliance_runner.cc b/rv64cpu/tb/compliance/compliance_runner.cc
deleted file mode 100644
index ba52647..0000000
--- a/rv64cpu/tb/compliance/compliance_runner.cc
+++ /dev/null
@@ -1,634 +0,0 @@
-// 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 <https://www.gnu.org/licenses/>.
-//
-// File path: tb/compliance/compliance_runner.cc
-// Minimal compliance runner bootstrap.
-// This runner intentionally keeps behavior simple so the toolchain flow can be
-// integrated first, then replaced with full ELF/signature handling.
-
-#include <cinttypes>
-#include <cstdint>
-#include <cstring>
-#include <fstream>
-#include <iomanip>
-#include <iostream>
-#include <map>
-#include <string>
-#include <sstream>
-#include <vector>
-#include <verilated.h>
-
-#include "Vrichard_core.h"
-#include "Vrichard_core___024root.h"
-
-namespace {
-
-struct Options {
- uint64_t max_cycles = 2000;
- std::string elf_path;
- std::string signature_path;
- uint64_t trace_pc_count = 0;
- bool show_help = false;
-};
-
-struct Symbols {
- uint64_t begin_signature = 0;
- uint64_t end_signature = 0;
- uint64_t tohost = 0;
-};
-
-class ByteMemory {
-public:
- static constexpr uint64_t UART_THR = 0x10000000ULL;
- static constexpr uint64_t UART_LSR = 0x10000005ULL;
- static constexpr uint64_t CLINT_MTIME = 0x0200BFF8ULL;
-
- void write_byte(uint64_t addr, uint8_t value) {
- if (addr == UART_THR) {
- return;
- }
- mem_[addr] = value;
- }
-
- uint8_t read_byte(uint64_t addr) const {
- if (addr == UART_LSR) {
- return 0x20;
- }
- auto it = mem_.find(addr);
- if (it == mem_.end()) {
- return 0;
- }
- return it->second;
- }
-
- uint32_t read_u32(uint64_t addr) const {
- uint32_t value = 0;
- for (int i = 0; i < 4; ++i) {
- value |= static_cast<uint32_t>(read_byte(addr + i)) << (8 * i);
- }
- return value;
- }
-
- uint64_t read_u64(uint64_t addr) const {
- uint64_t value = 0;
- for (int i = 0; i < 8; ++i) {
- value |= static_cast<uint64_t>(read_byte(addr + i)) << (8 * i);
- }
- return value;
- }
-
- void write_store(uint64_t addr, uint64_t wdata, uint8_t size_code) {
- uint64_t bytes = 0;
- switch (size_code) {
- case 0b000:
- case 0b100:
- bytes = 1;
- break;
- case 0b001:
- case 0b101:
- bytes = 2;
- break;
- case 0b010:
- case 0b110:
- bytes = 4;
- break;
- case 0b011:
- default:
- bytes = 8;
- break;
- }
-
- const uint64_t offset = addr & 0x7ULL;
- const uint64_t aligned = addr & ~0x7ULL;
- for (uint64_t i = 0; i < bytes; ++i) {
- const uint64_t lane = offset + i;
- const uint8_t byte_value = static_cast<uint8_t>((wdata >> (lane * 8)) & 0xFFULL);
- write_byte(aligned + lane, byte_value);
- }
- }
-
-private:
- std::map<uint64_t, uint8_t> mem_;
-};
-
-#pragma pack(push, 1)
-struct Elf64Ehdr {
- unsigned char e_ident[16];
- uint16_t e_type;
- uint16_t e_machine;
- uint32_t e_version;
- uint64_t e_entry;
- uint64_t e_phoff;
- uint64_t e_shoff;
- uint32_t e_flags;
- uint16_t e_ehsize;
- uint16_t e_phentsize;
- uint16_t e_phnum;
- uint16_t e_shentsize;
- uint16_t e_shnum;
- uint16_t e_shstrndx;
-};
-
-struct Elf64Phdr {
- uint32_t p_type;
- uint32_t p_flags;
- uint64_t p_offset;
- uint64_t p_vaddr;
- uint64_t p_paddr;
- uint64_t p_filesz;
- uint64_t p_memsz;
- uint64_t p_align;
-};
-
-struct Elf64Shdr {
- uint32_t sh_name;
- uint32_t sh_type;
- uint64_t sh_flags;
- uint64_t sh_addr;
- uint64_t sh_offset;
- uint64_t sh_size;
- uint32_t sh_link;
- uint32_t sh_info;
- uint64_t sh_addralign;
- uint64_t sh_entsize;
-};
-
-struct Elf64Sym {
- uint32_t st_name;
- unsigned char st_info;
- unsigned char st_other;
- uint16_t st_shndx;
- uint64_t st_value;
- uint64_t st_size;
-};
-#pragma pack(pop)
-
-constexpr uint32_t PT_LOAD = 1;
-constexpr uint32_t SHT_SYMTAB = 2;
-
-Options parse_args(int argc, char** argv) {
- Options opts;
- for (int i = 1; i < argc; ++i) {
- const std::string arg = argv[i];
- if (arg == "--help" || arg == "-h") {
- opts.show_help = true;
- continue;
- }
- if (arg == "--max-cycles" && (i + 1) < argc) {
- opts.max_cycles = static_cast<uint64_t>(std::stoull(argv[++i]));
- continue;
- }
- if (arg == "--elf" && (i + 1) < argc) {
- opts.elf_path = argv[++i];
- continue;
- }
- if (arg == "--signature" && (i + 1) < argc) {
- opts.signature_path = argv[++i];
- continue;
- }
- if (arg == "--trace-pc" && (i + 1) < argc) {
- opts.trace_pc_count = static_cast<uint64_t>(std::stoull(argv[++i]));
- continue;
- }
- }
- return opts;
-}
-
-void print_help() {
- std::cout
- << "compliance_runner (bootstrap)\n"
- << "Usage:\n"
- << " compliance_runner --elf PATH [--max-cycles N] [--signature PATH] [--trace-pc N]\n";
-}
-
-std::string hex64(uint64_t value) {
- std::ostringstream out;
- out << "0x" << std::hex << std::setfill('0') << std::setw(16) << value;
- return out.str();
-}
-
-void dump_selected_gprs(const Vrichard_core* top, const char* prefix) {
- const auto& regs = top->rootp->richard_core__DOT__u_id_stage__DOT__u_regfile__DOT__regs;
- std::cerr << prefix << ": selected GPRs\n"
- << " x2/sp=" << hex64(regs[2])
- << " x3/gp=" << hex64(regs[3])
- << " x4/tp=" << hex64(regs[4])
- << " x5/t0=" << hex64(regs[5]) << "\n"
- << " x6/t1=" << hex64(regs[6])
- << " x7/t2=" << hex64(regs[7])
- << " x8/s0=" << hex64(regs[8])
- << " x9/s1=" << hex64(regs[9]) << "\n"
- << " x10/a0=" << hex64(regs[10])
- << " x11/a1=" << hex64(regs[11])
- << " x20/s4=" << hex64(regs[20])
- << " x21/s5=" << hex64(regs[21]) << "\n";
- std::cerr << " mstatus=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__mstatus_reg)
- << " sstatus=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__sstatus_reg)
- << " mcause=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__mcause_reg)
- << " mepc=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__mepc_reg)
- << "\n";
- std::cerr << " priv=" << static_cast<unsigned>(top->rootp->richard_core__DOT__u_csr_unit__DOT__priv_mode_q)
- << " medeleg=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__medeleg_reg)
- << " mideleg=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__mideleg_reg)
- << " mtvec=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__mtvec_reg)
- << " stvec=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__stvec_reg)
- << "\n";
- std::cerr << " mstateen0=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__mstateen_reg[0])
- << " sstateen0=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__sstateen_reg[0])
- << " menvcfg=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__menvcfg_reg)
- << " senvcfg=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__senvcfg_reg)
- << "\n";
-}
-
-bool load_elf64(const std::string& path, ByteMemory* memory, Symbols* symbols) {
- std::ifstream in(path, std::ios::binary);
- if (!in) {
- std::cerr << "Failed to open ELF: " << path << "\n";
- return false;
- }
-
- in.seekg(0, std::ios::end);
- const std::streamsize size = in.tellg();
- in.seekg(0, std::ios::beg);
- if (size <= 0) {
- std::cerr << "Empty ELF file: " << path << "\n";
- return false;
- }
-
- std::vector<uint8_t> data(static_cast<size_t>(size));
- in.read(reinterpret_cast<char*>(data.data()), size);
- if (!in) {
- std::cerr << "Failed to read ELF contents: " << path << "\n";
- return false;
- }
-
- if (data.size() < sizeof(Elf64Ehdr)) {
- std::cerr << "ELF too small: " << path << "\n";
- return false;
- }
-
- const auto* ehdr = reinterpret_cast<const Elf64Ehdr*>(data.data());
- if (!(ehdr->e_ident[0] == 0x7F && ehdr->e_ident[1] == 'E' && ehdr->e_ident[2] == 'L' && ehdr->e_ident[3] == 'F')) {
- std::cerr << "Invalid ELF magic: " << path << "\n";
- return false;
- }
- if (ehdr->e_ident[4] != 2 || ehdr->e_ident[5] != 1) {
- std::cerr << "Only ELF64 little-endian is supported: " << path << "\n";
- return false;
- }
-
- const uint64_t ph_end = ehdr->e_phoff + static_cast<uint64_t>(ehdr->e_phnum) * ehdr->e_phentsize;
- if (ph_end > data.size()) {
- std::cerr << "Program header table out of range\n";
- return false;
- }
-
- for (uint16_t i = 0; i < ehdr->e_phnum; ++i) {
- const uint64_t off = ehdr->e_phoff + static_cast<uint64_t>(i) * ehdr->e_phentsize;
- const auto* phdr = reinterpret_cast<const Elf64Phdr*>(data.data() + off);
- if (phdr->p_type != PT_LOAD) {
- continue;
- }
- if (phdr->p_offset + phdr->p_filesz > data.size()) {
- std::cerr << "LOAD segment out of range\n";
- return false;
- }
-
- const uint64_t base = (phdr->p_paddr != 0) ? phdr->p_paddr : phdr->p_vaddr;
- for (uint64_t j = 0; j < phdr->p_filesz; ++j) {
- memory->write_byte(base + j, data[phdr->p_offset + j]);
- }
- for (uint64_t j = phdr->p_filesz; j < phdr->p_memsz; ++j) {
- memory->write_byte(base + j, 0);
- }
- }
-
- const uint64_t sh_end = ehdr->e_shoff + static_cast<uint64_t>(ehdr->e_shnum) * ehdr->e_shentsize;
- if (ehdr->e_shoff == 0 || ehdr->e_shnum == 0 || sh_end > data.size()) {
- return true;
- }
-
- for (uint16_t i = 0; i < ehdr->e_shnum; ++i) {
- const uint64_t off = ehdr->e_shoff + static_cast<uint64_t>(i) * ehdr->e_shentsize;
- const auto* shdr = reinterpret_cast<const Elf64Shdr*>(data.data() + off);
- if (shdr->sh_type != SHT_SYMTAB || shdr->sh_entsize == 0) {
- continue;
- }
- if (shdr->sh_offset + shdr->sh_size > data.size() || shdr->sh_link >= ehdr->e_shnum) {
- continue;
- }
-
- const uint64_t str_off = ehdr->e_shoff + static_cast<uint64_t>(shdr->sh_link) * ehdr->e_shentsize;
- const auto* str_shdr = reinterpret_cast<const Elf64Shdr*>(data.data() + str_off);
- if (str_shdr->sh_offset + str_shdr->sh_size > data.size()) {
- continue;
- }
-
- const char* strtab = reinterpret_cast<const char*>(data.data() + str_shdr->sh_offset);
- const uint64_t sym_count = shdr->sh_size / shdr->sh_entsize;
- for (uint64_t s = 0; s < sym_count; ++s) {
- const uint64_t sym_off = shdr->sh_offset + s * shdr->sh_entsize;
- const auto* sym = reinterpret_cast<const Elf64Sym*>(data.data() + sym_off);
- if (sym->st_name >= str_shdr->sh_size) {
- continue;
- }
- const std::string name(strtab + sym->st_name);
- if (name == "begin_signature") {
- symbols->begin_signature = sym->st_value;
- } else if (name == "end_signature") {
- symbols->end_signature = sym->st_value;
- } else if (name == "tohost") {
- symbols->tohost = sym->st_value;
- }
- }
- }
-
- return true;
-}
-
-void drive_memory_response(Vrichard_core* top, ByteMemory* memory) {
- top->imem_rdata = memory->read_u32(top->imem_addr);
- top->dmem_rdata = top->dmem_read_en ? memory->read_u64(top->dmem_addr & ~0x7ULL) : 0;
-}
-
-void tick(Vrichard_core* top, ByteMemory* memory) {
- drive_memory_response(top, memory);
- top->m_timer_interrupt = 0;
- top->m_software_interrupt = 0;
- top->m_external_interrupt = 0;
- top->s_timer_interrupt = 0;
- top->s_software_interrupt = 0;
- top->s_external_interrupt = 0;
-
- top->clk = 0;
- top->eval();
- drive_memory_response(top, memory);
- top->eval();
- top->clk = 1;
- top->eval();
-
- if (top->dmem_write_en) {
- memory->write_store(top->dmem_addr, top->dmem_wdata, top->dmem_size);
- if ((top->dmem_addr <= ByteMemory::CLINT_MTIME + 7ULL) &&
- (top->dmem_addr + 7ULL >= ByteMemory::CLINT_MTIME)) {
- top->rootp->richard_core__DOT__u_csr_unit__DOT__time_reg =
- memory->read_u64(ByteMemory::CLINT_MTIME);
- }
- }
-}
-
-void dump_signature(const ByteMemory& memory, const Symbols& symbols, const std::string& signature_path) {
- if (signature_path.empty()) {
- return;
- }
-
- std::ofstream sig(signature_path);
- if (!sig) {
- std::cerr << "Failed to open signature file for writing: " << signature_path << "\n";
- return;
- }
-
- if (symbols.begin_signature == 0 || symbols.end_signature <= symbols.begin_signature) {
- sig << "00000000\n";
- return;
- }
-
- const uint64_t bytes = symbols.end_signature - symbols.begin_signature;
- for (uint64_t off = 0; off + 8 <= bytes; off += 8) {
- const uint64_t value = memory.read_u64(symbols.begin_signature + off);
- sig << std::hex << std::setfill('0') << std::setw(16) << value << "\n";
- }
-}
-
-} // namespace
-
-int main(int argc, char** argv) {
- Verilated::commandArgs(argc, argv);
- const Options opts = parse_args(argc, argv);
-
- if (opts.show_help) {
- print_help();
- return 0;
- }
- if (opts.elf_path.empty()) {
- // Keep compatibility with CTest smoke invocation that runs the binary
- // without arguments to validate toolchain/linking only.
- std::cout << "compliance_runner: no --elf provided, smoke mode pass\n";
- return 0;
- }
-
- auto* top = new Vrichard_core;
- ByteMemory memory;
- Symbols symbols;
-
- if (!load_elf64(opts.elf_path, &memory, &symbols)) {
- delete top;
- return 2;
- }
-
- top->rst = 1;
- for (int i = 0; i < 5; ++i) {
- tick(top, &memory);
- }
- top->rst = 0;
-
- bool saw_tohost = false;
- bool test_pass = false;
- uint64_t store_trace_printed = 0;
- uint64_t read_trace_printed = 0;
- uint64_t wb_trace_printed = 0;
- uint64_t trap_trace_printed = 0;
- std::string uart_trace;
- std::vector<uint64_t> pc_trace;
- if (opts.trace_pc_count != 0) {
- pc_trace.reserve(static_cast<size_t>(opts.trace_pc_count));
- }
-
- for (uint64_t i = 0; i < opts.max_cycles; ++i) {
- if (opts.trace_pc_count != 0) {
- if (pc_trace.size() == opts.trace_pc_count) {
- pc_trace.erase(pc_trace.begin());
- }
- pc_trace.push_back(top->imem_addr);
- }
- tick(top, &memory);
- if (opts.trace_pc_count != 0 &&
- top->dmem_read_en &&
- (top->dmem_addr >= 0x000000008004cd20ULL) &&
- (top->dmem_addr < 0x000000008004cd40ULL) &&
- read_trace_printed < 32) {
- std::cerr << "load cycle=" << i
- << " pc=" << hex64(top->imem_addr)
- << " addr=" << hex64(top->dmem_addr)
- << " rdata=" << hex64(top->dmem_rdata)
- << " size=" << static_cast<unsigned>(top->dmem_size)
- << "\n";
- ++read_trace_printed;
- }
- if (opts.trace_pc_count != 0 && top->dmem_write_en && top->dmem_addr == ByteMemory::UART_THR) {
- uart_trace.push_back(static_cast<char>(top->dmem_wdata & 0xFFULL));
- }
- if (opts.trace_pc_count != 0 &&
- top->dmem_write_en &&
- top->dmem_addr >= 0x00000000800115e0ULL &&
- top->dmem_addr < 0x0000000080011608ULL) {
- std::cerr << "scratch-store cycle=" << i
- << " pc=" << hex64(top->imem_addr)
- << " addr=" << hex64(top->dmem_addr)
- << " data=" << hex64(top->dmem_wdata)
- << " size=" << static_cast<unsigned>(top->dmem_size)
- << "\n";
- }
- if (opts.trace_pc_count != 0 &&
- top->rootp->richard_core__DOT__csr_req &&
- (((top->rootp->richard_core__DOT__wb_instr_in >> 20) & 0xFFFU) == 0x341 ||
- ((top->rootp->richard_core__DOT__wb_instr_in >> 20) & 0xFFFU) == 0x141 ||
- ((top->rootp->richard_core__DOT__wb_instr_in >> 20) & 0xFFFU) == 0x300 ||
- ((top->rootp->richard_core__DOT__wb_instr_in >> 20) & 0xFFFU) == 0x100)) {
- std::cerr << "csr cycle=" << i
- << " pc=" << hex64(top->rootp->richard_core__DOT__wb_pc_in)
- << " addr=0x" << std::hex << std::setw(3) << std::setfill('0')
- << ((top->rootp->richard_core__DOT__wb_instr_in >> 20) & 0xFFFU)
- << " op=" << std::dec
- << static_cast<unsigned>((top->rootp->richard_core__DOT__wb_instr_in >> 12) & 0x7U)
- << " rs1=" << hex64(top->rootp->richard_core__DOT__csr_rs1_data)
- << " mepc_old=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__mepc_reg)
- << " sepc_old=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__sepc_reg)
- << " instr=0x" << std::hex << std::setw(8) << std::setfill('0')
- << top->rootp->richard_core__DOT__wb_instr_in
- << std::dec << std::setfill(' ') << "\n";
- }
- if (opts.trace_pc_count != 0 &&
- (top->rootp->richard_core__DOT__trap_ctrl_mret_en ||
- top->rootp->richard_core__DOT__trap_ctrl_sret_en)) {
- std::cerr << "xret cycle=" << i
- << " pc=" << hex64(top->rootp->richard_core__DOT__wb_pc_in)
- << " mret=" << static_cast<unsigned>(top->rootp->richard_core__DOT__trap_ctrl_mret_en)
- << " sret=" << static_cast<unsigned>(top->rootp->richard_core__DOT__trap_ctrl_sret_en)
- << " target=" << hex64(top->rootp->richard_core__DOT__hu_pc_redirect_target)
- << " mepc=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__mepc_reg)
- << " sepc=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__sepc_reg)
- << "\n";
- }
- if (opts.trace_pc_count != 0 &&
- top->rootp->richard_core__DOT__wb_pc_in == 0x000000008000e1b4ULL &&
- trap_trace_printed < 128) {
- const auto& regs = top->rootp->richard_core__DOT__u_id_stage__DOT__u_regfile__DOT__regs;
- const uint64_t sig_ptr = regs[6];
- std::cerr << "trap-check cycle=" << i
- << " t1=" << hex64(sig_ptr)
- << " cause=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__mcause_reg)
- << " mepc=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__mepc_reg)
- << " stored_msig=" << hex64(memory.read_u64(0x00000000800115f0ULL))
- << " exp_status=" << hex64(memory.read_u64(sig_ptr))
- << " exp_cause=" << hex64(memory.read_u64(sig_ptr + 8))
- << " exp_epc=" << hex64(memory.read_u64(sig_ptr + 16))
- << "\n";
- ++trap_trace_printed;
- }
- if (opts.trace_pc_count != 0 &&
- (top->rootp->richard_core__DOT__wb_pc_in == 0x000000008000eb84ULL ||
- top->rootp->richard_core__DOT__wb_pc_in == 0x000000008000eb88ULL ||
- top->rootp->richard_core__DOT__wb_pc_in == 0x000000008000ebc8ULL ||
- top->rootp->richard_core__DOT__wb_pc_in == 0x000000008000ebccULL ||
- top->rootp->richard_core__DOT__wb_pc_in == 0x000000008000ebecULL ||
- top->rootp->richard_core__DOT__wb_pc_in == 0x000000008000ec60ULL) &&
- trap_trace_printed < 256) {
- const auto& regs = top->rootp->richard_core__DOT__u_id_stage__DOT__u_regfile__DOT__regs;
- std::cerr << "strap-debug cycle=" << i
- << " pc=" << hex64(top->rootp->richard_core__DOT__wb_pc_in)
- << " sp=" << hex64(regs[2])
- << " t1=" << hex64(regs[6])
- << " t2=" << hex64(regs[7])
- << " s0=" << hex64(regs[8])
- << " s1=" << hex64(regs[9])
- << " a0=" << hex64(regs[10])
- << " a1=" << hex64(regs[11])
- << " sscratch=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__sscratch_reg)
- << " scause=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__scause_reg)
- << " sepc=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__sepc_reg)
- << " mem[Strap_sig]=" << hex64(memory.read_u64(0x00000000800115f0ULL))
- << " mem[Ssig_bgn]=" << hex64(memory.read_u64(0x00000000800115d0ULL))
- << " mem[Sbase]=" << hex64(memory.read_u64(0x00000000800115b0ULL))
- << "\n";
- ++trap_trace_printed;
- }
- if (opts.trace_pc_count != 0 &&
- top->rootp->richard_core__DOT__wb_reg_write_en_out &&
- (top->rootp->richard_core__DOT__wb_rd_addr_in == 8 ||
- top->rootp->richard_core__DOT__wb_rd_addr_in == 4 ||
- top->rootp->richard_core__DOT__wb_rd_addr_in == 13 ||
- top->rootp->richard_core__DOT__wb_rd_addr_in == 20) &&
- wb_trace_printed < 64) {
- std::cerr << "wb cycle=" << i
- << " pc=" << hex64(top->rootp->richard_core__DOT__wb_pc_in)
- << " rd=x" << static_cast<unsigned>(top->rootp->richard_core__DOT__wb_rd_addr_in)
- << " data=" << hex64(top->rootp->richard_core__DOT__wb_rd_wdata)
- << " instr=0x" << std::hex << std::setw(8) << std::setfill('0')
- << top->rootp->richard_core__DOT__wb_instr_in
- << std::dec << std::setfill(' ') << "\n";
- ++wb_trace_printed;
- }
- if (top->dmem_write_en && symbols.tohost != 0) {
- const uint64_t addr = top->dmem_addr;
- if (opts.trace_pc_count != 0 && store_trace_printed < 128) {
- std::cerr << "store cycle=" << i
- << " pc=" << hex64(top->imem_addr)
- << " addr=" << hex64(addr)
- << " data=" << hex64(top->dmem_wdata)
- << " size=" << static_cast<unsigned>(top->dmem_size)
- << "\n";
- ++store_trace_printed;
- }
- const bool overlaps_tohost = (addr <= symbols.tohost + 3ULL) && (addr + 7ULL >= symbols.tohost);
- if (overlaps_tohost) {
- const uint32_t status = static_cast<uint32_t>(memory.read_u64(symbols.tohost) & 0xFFFFFFFFULL);
- if (status == 1U) {
- saw_tohost = true;
- test_pass = true;
- break;
- }
- if (status == 3U) {
- saw_tohost = true;
- test_pass = false;
- break;
- }
- }
- }
- }
-
- dump_signature(memory, symbols, opts.signature_path);
-
- if (opts.trace_pc_count != 0 && !uart_trace.empty()) {
- std::cerr << "uart output:\n" << uart_trace << "\n";
- }
- if (!saw_tohost) {
- if (!pc_trace.empty()) {
- std::cerr << "timeout: last " << pc_trace.size() << " fetch PCs\n";
- for (uint64_t pc : pc_trace) {
- std::cerr << " " << hex64(pc) << "\n";
- }
- dump_selected_gprs(top, "timeout");
- }
- delete top;
- return 3;
- }
- if (opts.trace_pc_count != 0 && !test_pass) {
- dump_selected_gprs(top, "fail");
- }
- delete top;
- return test_pass ? 0 : 1;
-}
diff --git a/rv64cpu/tb/core/tb_core.cc b/rv64cpu/tb/core/tb_core.cc
deleted file mode 100644
index da758c1..0000000
--- a/rv64cpu/tb/core/tb_core.cc
+++ /dev/null
@@ -1,1136 +0,0 @@
-// 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 <https://www.gnu.org/licenses/>.
-
-// File path: tb/core/tb_core.cc
-// Description: Richard CPU core top-level integration test
-//
-// This testbench drives the richard_core module through its external interfaces
-// (instruction memory + data memory) to verify multi-cycle pipeline behavior.
-// It acts as a simple memory model, feeding instructions and returning data.
-
-#include <algorithm>
-#include <iostream>
-#include <iomanip>
-#include <cstdint>
-#include <iterator>
-#include <vector>
-#include <map>
-#include <string>
-#include <verilated.h>
-#include "Vrichard_core.h"
-
-#if VM_TRACE
-#include <verilated_vcd_c.h>
-#endif
-
-// ---------------------------------
-// CSR addresses & cause codes used in tests
-// ---------------------------------
-constexpr uint16_t CSR_MSTATUS = 0x300;
-constexpr uint16_t CSR_MISA = 0x301;
-constexpr uint16_t CSR_MEDELEG = 0x302;
-constexpr uint16_t CSR_MIDELEG = 0x303;
-constexpr uint16_t CSR_MIE = 0x304;
-constexpr uint16_t CSR_MTVEC = 0x305;
-constexpr uint16_t CSR_MEPC = 0x341;
-constexpr uint16_t CSR_MSCRATCH = 0x340;
-constexpr uint16_t CSR_MCAUSE = 0x342;
-constexpr uint16_t CSR_MTVAL = 0x343;
-constexpr uint16_t CSR_SSTATUS = 0x100;
-constexpr uint16_t CSR_STVEC = 0x105;
-constexpr uint16_t CSR_SSCRATCH = 0x140;
-constexpr uint16_t CSR_SEPC = 0x141;
-constexpr uint16_t CSR_SCAUSE = 0x142;
-constexpr uint16_t CSR_STVAL = 0x143;
-
-constexpr int MSTATUS_MIE_BIT = 3;
-constexpr int MSTATUS_MPIE_BIT = 7;
-constexpr int MSTATUS_MPP_LO = 11;
-constexpr int SSTATUS_SIE_BIT = 1;
-constexpr int SSTATUS_SPIE_BIT = 5;
-constexpr int SSTATUS_SPP_BIT = 8;
-
-constexpr uint64_t EXC_INST_ILLEGAL = 2ULL;
-constexpr uint64_t EXC_BREAKPOINT = 3ULL;
-constexpr uint64_t EXC_LOAD_FAULT = 5ULL;
-constexpr uint64_t EXC_STORE_FAULT = 7ULL;
-constexpr uint64_t EXC_ECALL_FROM_U = 8ULL;
-constexpr uint64_t EXC_ECALL_FROM_M = 11ULL;
-constexpr uint64_t INTERRUPT_BIT = 1ULL << 63;
-constexpr uint64_t INTERRUPT_M_EXTERNAL = 11ULL;
-constexpr uint64_t INTERRUPT_M_TIMER = 7ULL;
-
-constexpr uint64_t LOG_ENTRY_STRIDE = 24ULL;
-constexpr uint64_t MACHINE_TRAP_VECTOR = 0x00000000ULL;
-constexpr uint64_t SUPERVISOR_TRAP_VECTOR = 0x00000100ULL;
-constexpr uint64_t MACHINE_LOG_PTR_ADDR = 0x000000000000FFF0ULL;
-constexpr uint64_t SUPERVISOR_LOG_PTR_ADDR = 0x000000000000FFE0ULL;
-
-constexpr uint64_t PRIV_MODE_U = 0ULL;
-constexpr uint64_t PRIV_MODE_S = 1ULL;
-constexpr uint64_t PRIV_MODE_M = 3ULL;
-
-// -----------------------
-// RISC-V Instruction Encoding Helpers
-// -----------------------
-
-// R-type: funct7[6:0] | rs2[4:0] | rs1[4:0] | funct3[2:0] | rd[4:0] | opcode[6:0]
-static uint32_t rv_r_type(uint32_t funct7, uint32_t rs2, uint32_t rs1, uint32_t funct3, uint32_t rd, uint32_t opcode) {
- return (funct7 << 25) | (rs2 << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | opcode;
-}
-
-// I-type: imm[11:0] | rs1[4:0] | funct3[2:0] | rd[4:0] | opcode[6:0]
-static uint32_t rv_i_type(uint32_t imm12, uint32_t rs1, uint32_t funct3, uint32_t rd, uint32_t opcode) {
- return ((imm12 & 0xFFF) << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | opcode;
-}
-
-// S-type: imm[11:5] | rs2[4:0] | rs1[4:0] | funct3[2:0] | imm[4:0] | opcode[6:0]
-static uint32_t rv_s_type(uint32_t imm12, uint32_t rs2, uint32_t rs1, uint32_t funct3, uint32_t opcode) {
- uint32_t imm_hi = (imm12 >> 5) & 0x7F;
- uint32_t imm_lo = imm12 & 0x1F;
- return (imm_hi << 25) | (rs2 << 20) | (rs1 << 15) | (funct3 << 12) | (imm_lo << 7) | opcode;
-}
-
-// U-type: imm[31:12] | rd[4:0] | opcode[6:0]
-static uint32_t rv_u_type(uint32_t imm20, uint32_t rd, uint32_t opcode) {
- return ((imm20 & 0xFFFFF) << 12) | (rd << 7) | opcode;
-}
-
-// J-type: imm[20] | imm[10:1] | imm[11] | imm[19:12] | rd[4:0] | opcode[6:0]
-static uint32_t rv_j_type(int32_t offset, uint32_t rd, uint32_t opcode) {
- uint32_t imm = static_cast<uint32_t>(offset);
- uint32_t bit20 = (imm >> 20) & 0x1;
- uint32_t bit10_1 = (imm >> 1) & 0x3FF;
- uint32_t bit11 = (imm >> 11) & 0x1;
- uint32_t bit19_12 = (imm >> 12) & 0xFF;
- return (bit20 << 31) | (bit10_1 << 21) | (bit11 << 20) | (bit19_12 << 12) | (rd << 7) | opcode;
-}
-
-// B-type: imm[12|10:5] | rs2[4:0] | rs1[4:0] | funct3[2:0] | imm[4:1|11] | opcode[6:0]
-static uint32_t rv_b_type(int32_t offset, uint32_t rs2, uint32_t rs1, uint32_t funct3, uint32_t opcode) {
- uint32_t imm = static_cast<uint32_t>(offset);
- uint32_t bit12 = (imm >> 12) & 0x1;
- uint32_t bit10_5 = (imm >> 5) & 0x3F;
- uint32_t bit4_1 = (imm >> 1) & 0xF;
- uint32_t bit11 = (imm >> 11) & 0x1;
- return (bit12 << 31) | (bit10_5 << 25) | (rs2 << 20) | (rs1 << 15) |
- (funct3 << 12) | (bit4_1 << 8) | (bit11 << 7) | opcode;
-}
-
-// Common instruction shorthands
-static uint32_t NOP() { return 0x00000013; } // addi x0, x0, 0
-static uint32_t ILLEGAL() { return 0xFFFFFFFF; }
-static uint32_t ADDI(uint32_t rd, uint32_t rs1, int32_t imm) {
- return rv_i_type(static_cast<uint32_t>(imm) & 0xFFF, rs1, 0x0, rd, 0x13);
-}
-static uint32_t ADD(uint32_t rd, uint32_t rs1, uint32_t rs2) {
- return rv_r_type(0x00, rs2, rs1, 0x0, rd, 0x33);
-}
-static uint32_t SUB(uint32_t rd, uint32_t rs1, uint32_t rs2) {
- return rv_r_type(0x20, rs2, rs1, 0x0, rd, 0x33);
-}
-static uint32_t LD(uint32_t rd, uint32_t rs1, int32_t imm) {
- return rv_i_type(static_cast<uint32_t>(imm) & 0xFFF, rs1, 0x3, rd, 0x03);
-}
-static uint32_t SD(uint32_t rs2, uint32_t rs1, int32_t imm) {
- return rv_s_type(static_cast<uint32_t>(imm) & 0xFFF, rs2, rs1, 0x3, 0x23);
-}
-static uint32_t BEQ(uint32_t rs1, uint32_t rs2, int32_t offset) {
- return rv_b_type(offset, rs2, rs1, 0x0, 0x63);
-}
-static uint32_t BNE(uint32_t rs1, uint32_t rs2, int32_t offset) {
- return rv_b_type(offset, rs2, rs1, 0x1, 0x63);
-}
-static uint32_t LUI(uint32_t rd, uint32_t imm) {
- return rv_u_type(imm, rd, 0x37);
-}
-static uint32_t JAL(uint32_t rd, int32_t offset) {
- return rv_j_type(offset, rd, 0x6f);
-}
-static uint32_t JALR(uint32_t rd, uint32_t rs1, int32_t imm) {
- return rv_i_type(static_cast<uint32_t>(imm) & 0xFFF, rs1, 0x0, rd, 0x67);
-}
-static uint32_t SYSTEM(uint32_t funct12, uint32_t rs1, uint32_t funct3, uint32_t rd) {
- return ((funct12 & 0xFFF) << 20) | ((rs1 & 0x1F) << 15) | ((funct3 & 0x7) << 12) | ((rd & 0x1F) << 7) | 0x73;
-}
-static uint32_t ECALL() { return SYSTEM(0x000, 0, 0x0, 0); }
-static uint32_t EBREAK() { return SYSTEM(0x001, 0, 0x0, 0); }
-static uint32_t MRET() { return SYSTEM(0x302, 0, 0x0, 0); }
-static uint32_t SRET() { return SYSTEM(0x102, 0, 0x0, 0); }
-static uint32_t CSRInstrReg(uint32_t csr, uint32_t funct3, uint32_t rd, uint32_t rs1) {
- return ((csr & 0xFFF) << 20) | ((rs1 & 0x1F) << 15) | ((funct3 & 0x7) << 12) | ((rd & 0x1F) << 7) | 0x73;
-}
-static uint32_t CSRRW(uint32_t rd, uint32_t csr, uint32_t rs1) {
- return CSRInstrReg(csr, 0b001, rd, rs1);
-}
-static uint32_t CSRRS(uint32_t rd, uint32_t csr, uint32_t rs1) {
- return CSRInstrReg(csr, 0b010, rd, rs1);
-}
-static uint32_t CSRRWI(uint32_t rd, uint32_t csr, uint32_t zimm) {
- return CSRInstrReg(csr, 0b101, rd, zimm & 0x1F);
-}
-
-static void emit_load_imm64(std::vector<uint32_t>& prog, uint32_t rd, uint64_t imm) {
- const uint64_t upper = (imm + 0x800ULL) >> 12;
- int32_t lower = static_cast<int32_t>(imm & 0xFFFULL);
- if (lower >= 2048) {
- lower -= 4096;
- }
- prog.push_back(LUI(rd, static_cast<uint32_t>(upper)));
- prog.push_back(ADDI(rd, rd, lower));
-}
-
-static void emit_nops(std::vector<uint32_t>& prog, int count) {
- for (int i = 0; i < count; ++i) {
- prog.push_back(NOP());
- }
-}
-
-static void emit_csrrw_imm64(std::vector<uint32_t>& prog, uint32_t tmp_reg, uint32_t csr, uint64_t value) {
- // CSR source operand is read in ID stage, so we need a short drain window after loading tmp_reg.
- emit_load_imm64(prog, tmp_reg, value);
- emit_nops(prog, 6);
- prog.push_back(CSRRW(0, csr, tmp_reg));
-}
-
-// -----------------------
-// Test Infrastructure
-// -----------------------
-static int g_test_count = 0;
-static int g_pass_count = 0;
-
-#define ASSERT_EQ(actual, expected, msg) do { \
- g_test_count++; \
- if ((actual) != (expected)) { \
- std::cerr << " FAIL [" << g_test_count << "] " << msg \
- << ": expected 0x" << std::hex << (expected) \
- << ", got 0x" << std::hex << (actual) << std::dec << std::endl; \
- } else { \
- g_pass_count++; \
- } \
-} while(0)
-
-// -----------------------
-// Simple Memory Model
-// -----------------------
-class MemoryModel {
-public:
- // Instruction memory: map from byte_addr -> 32-bit word
- std::map<uint64_t, uint32_t> imem;
- // Data memory: map from byte_addr -> 64-bit doubleword
- std::map<uint64_t, uint64_t> dmem;
-
- void load_program(uint64_t base_addr, const std::vector<uint32_t>& program) {
- for (size_t i = 0; i < program.size(); i++) {
- imem[base_addr + i * 4] = program[i];
- }
- }
-
- uint32_t read_instr(uint64_t addr) {
- auto it = imem.find(addr);
- if (it != imem.end()) return it->second;
- return NOP(); // Return NOP for unmapped addresses
- }
-
- uint64_t read_data(uint64_t addr) const {
- // Align to doubleword
- uint64_t aligned = addr & ~0x7ULL;
- auto it = dmem.find(aligned);
- if (it != dmem.end()) return it->second;
- return 0;
- }
-
- void write_data(uint64_t addr, uint64_t data) {
- uint64_t aligned = addr & ~0x7ULL;
- dmem[aligned] = data;
- }
-};
-
-static std::vector<uint32_t> make_machine_trap_handler_program() {
- constexpr uint32_t REG_PTR = 5;
- constexpr uint32_t REG_MCAUSE = 6;
- constexpr uint32_t REG_MEPC = 7;
- constexpr uint32_t REG_MTVAL = 28;
- constexpr uint32_t REG_TMP = 29;
- constexpr uint32_t REG_SLOT_ADDR = 30;
-
- std::vector<uint32_t> prog;
- emit_load_imm64(prog, REG_SLOT_ADDR, MACHINE_LOG_PTR_ADDR);
- prog.push_back(LD(REG_PTR, REG_SLOT_ADDR, 0));
- emit_nops(prog, 12);
- prog.push_back(CSRRS(REG_MCAUSE, CSR_MCAUSE, 0));
- emit_nops(prog, 4);
- prog.push_back(SD(REG_MCAUSE, REG_PTR, 0));
- prog.push_back(CSRRS(REG_MEPC, CSR_MEPC, 0));
- emit_nops(prog, 4);
- prog.push_back(SD(REG_MEPC, REG_PTR, 8));
- prog.push_back(CSRRS(REG_MTVAL, CSR_MTVAL, 0));
- emit_nops(prog, 4);
- prog.push_back(SD(REG_MTVAL, REG_PTR, 16));
- prog.push_back(ADDI(REG_PTR, REG_PTR, static_cast<int32_t>(LOG_ENTRY_STRIDE)));
- prog.push_back(SD(REG_PTR, REG_SLOT_ADDR, 0));
- prog.push_back(CSRRS(REG_TMP, CSR_MEPC, 0));
- prog.push_back(ADDI(REG_TMP, REG_TMP, 4));
- emit_nops(prog, 12);
- prog.push_back(CSRRW(0, CSR_MEPC, REG_TMP));
- prog.push_back(MRET());
- prog.push_back(NOP());
- prog.push_back(NOP());
- return prog;
-}
-
-static std::vector<uint32_t> make_supervisor_trap_handler_program() {
- constexpr uint32_t REG_PTR = 5;
- constexpr uint32_t REG_SCAUSE = 6;
- constexpr uint32_t REG_SEPC = 7;
- constexpr uint32_t REG_STVAL = 28;
- constexpr uint32_t REG_TMP = 29;
- constexpr uint32_t REG_SLOT_ADDR = 30;
-
- std::vector<uint32_t> prog;
- emit_load_imm64(prog, REG_SLOT_ADDR, SUPERVISOR_LOG_PTR_ADDR);
- prog.push_back(LD(REG_PTR, REG_SLOT_ADDR, 0));
- emit_nops(prog, 12);
- prog.push_back(CSRRS(REG_SCAUSE, CSR_SCAUSE, 0));
- emit_nops(prog, 4);
- prog.push_back(SD(REG_SCAUSE, REG_PTR, 0));
- prog.push_back(CSRRS(REG_SEPC, CSR_SEPC, 0));
- emit_nops(prog, 4);
- prog.push_back(SD(REG_SEPC, REG_PTR, 8));
- prog.push_back(CSRRS(REG_STVAL, CSR_STVAL, 0));
- emit_nops(prog, 4);
- prog.push_back(SD(REG_STVAL, REG_PTR, 16));
- prog.push_back(ADDI(REG_PTR, REG_PTR, static_cast<int32_t>(LOG_ENTRY_STRIDE)));
- prog.push_back(SD(REG_PTR, REG_SLOT_ADDR, 0));
- prog.push_back(CSRRS(REG_TMP, CSR_SEPC, 0));
- prog.push_back(ADDI(REG_TMP, REG_TMP, 4));
- emit_nops(prog, 12);
- prog.push_back(CSRRW(0, CSR_SEPC, REG_TMP));
- prog.push_back(SRET());
- prog.push_back(NOP());
- prog.push_back(NOP());
- return prog;
-}
-
-static void install_trap_handlers(MemoryModel& mem) {
- mem.load_program(MACHINE_TRAP_VECTOR, make_machine_trap_handler_program());
- mem.load_program(SUPERVISOR_TRAP_VECTOR, make_supervisor_trap_handler_program());
-}
-
-struct TrapRecord {
- uint64_t mcause;
- uint64_t mepc;
- uint64_t mtval;
-};
-
-static TrapRecord fetch_trap_record(const MemoryModel& mem, uint64_t base, int index) {
- const uint64_t addr = base + static_cast<uint64_t>(index) * LOG_ENTRY_STRIDE;
- TrapRecord rec;
- rec.mcause = mem.read_data(addr + 0);
- rec.mepc = mem.read_data(addr + 8);
- rec.mtval = mem.read_data(addr + 16);
- return rec;
-}
-
-// -----------------------
-// Core Simulation Wrapper
-// -----------------------
-class CoreSim {
-public:
- Vrichard_core* top;
- MemoryModel mem;
- uint64_t sim_time;
- bool m_timer_interrupt_level;
- bool m_software_interrupt_level;
- bool m_external_interrupt_level;
- bool s_timer_interrupt_level;
- bool s_software_interrupt_level;
- bool s_external_interrupt_level;
-#if VM_TRACE
- VerilatedVcdC* tfp;
-#endif
-
- CoreSim() : sim_time(0),
- m_timer_interrupt_level(false),
- m_software_interrupt_level(false),
- m_external_interrupt_level(false),
- s_timer_interrupt_level(false),
- s_software_interrupt_level(false),
- s_external_interrupt_level(false) {
- top = new Vrichard_core;
-#if VM_TRACE
- Verilated::traceEverOn(true);
- tfp = new VerilatedVcdC;
- top->trace(tfp, 99);
- tfp->open("waves/tb_core.vcd");
-#endif
- }
-
- ~CoreSim() {
-#if VM_TRACE
- if (tfp) { tfp->close(); delete tfp; }
-#endif
- delete top;
- }
-
- void reset(int cycles = 3) {
- top->rst = 1;
- top->imem_rdata = NOP();
- top->dmem_rdata = 0;
- clear_interrupts();
- for (int i = 0; i < cycles; i++) {
- tick();
- }
- top->rst = 0;
- }
-
- void tick() {
- // Drive instruction memory response from model
- top->imem_rdata = mem.read_instr(top->imem_addr);
-
- // Drive data memory response
- if (top->dmem_read_en) {
- top->dmem_rdata = mem.read_data(top->dmem_addr);
- } else {
- top->dmem_rdata = 0;
- }
-
- top->m_timer_interrupt = m_timer_interrupt_level;
- top->m_software_interrupt = m_software_interrupt_level;
- top->m_external_interrupt = m_external_interrupt_level;
- top->s_timer_interrupt = s_timer_interrupt_level;
- top->s_software_interrupt = s_software_interrupt_level;
- top->s_external_interrupt = s_external_interrupt_level;
-
- // Rising edge
- top->clk = 0;
- top->eval();
-#if VM_TRACE
- tfp->dump(sim_time++);
-#endif
-
- top->clk = 1;
- top->eval();
-#if VM_TRACE
- tfp->dump(sim_time++);
-#endif
-
- // Handle writes on the falling edge conceptually
- if (top->dmem_write_en) {
- mem.write_data(top->dmem_addr, top->dmem_wdata);
- }
- }
-
- // Run N cycles
- void run(int n) {
- for (int i = 0; i < n; i++) {
- tick();
- }
- }
-
- void set_interrupt_lines(bool timer, bool software, bool external) {
- m_timer_interrupt_level = timer;
- m_software_interrupt_level = software;
- m_external_interrupt_level = external;
- }
-
- void clear_interrupts() {
- set_interrupt_lines(false, false, false);
- set_supervisor_interrupt_lines(false, false, false);
- }
-
- void set_supervisor_interrupt_lines(bool timer, bool software, bool external) {
- s_timer_interrupt_level = timer;
- s_software_interrupt_level = software;
- s_external_interrupt_level = external;
- }
-};
-
-static std::vector<uint64_t> collect_pc_trace(CoreSim& sim, int cycles) {
- std::vector<uint64_t> trace;
- trace.reserve(static_cast<size_t>(cycles));
- for (int i = 0; i < cycles; ++i) {
- sim.tick();
- trace.push_back(sim.top->imem_addr);
- }
- return trace;
-}
-
-// ═══════════════════════════════════════════════════════════════
-// Test Cases
-// ═══════════════════════════════════════════════════════════════
-
-// -----------------------
-// Test 1: Reset behavior
-// -----------------------
-void test_reset(CoreSim& sim) {
- std::cout << "Test 1: Reset behavior..." << std::endl;
-
- // After reset, PC should be at RESET_VECTOR (0x80000000)
- // Note: if_stage updates PC on posedge clk. After reset deasserts,
- // the first tick() advances PC to 0x80000004 (next_pc = PC+4).
- // So imem_addr = 0x80000000 is visible DURING reset, not after tick.
- sim.reset(3);
- // After 3 reset cycles, the PC is held at 0x80000000.
- // The next tick will fetch from 0x80000000 and advance PC to 0x80000004.
- sim.tick();
- // Now PC has advanced: imem_addr shows the NEXT fetch address
- ASSERT_EQ(sim.top->imem_addr, 0x0000000080000004ULL, "PC after reset+1 tick = 0x80000004");
-
- // No data memory operations during NOP flow
- ASSERT_EQ(sim.top->dmem_read_en, 0, "No dmem read during NOP flow");
- ASSERT_EQ(sim.top->dmem_write_en, 0, "No dmem write during NOP flow");
-}
-
-// -----------------------
-// Test 2: NOP pipeline flow
-// -----------------------
-void test_nop_flow(CoreSim& sim) {
- std::cout << "Test 2: NOP pipeline flow (PC increment)..." << std::endl;
-
- std::vector<uint32_t> program = {
- NOP(), NOP(), NOP(), NOP(), NOP(), NOP(), NOP(), NOP()
- };
- sim.mem.load_program(0x80000000ULL, program);
- sim.reset();
-
- // After reset, first tick fetches from 0x80000000 and advances PC.
- // imem_addr shows the CURRENT fetch address when clk=0 (before tick),
- // but after tick() returns, PC has already advanced.
- sim.tick();
- ASSERT_EQ(sim.top->imem_addr, 0x80000004ULL, "Cycle 1: PC = 0x80000004");
-
- sim.tick();
- ASSERT_EQ(sim.top->imem_addr, 0x80000008ULL, "Cycle 2: PC = 0x80000008");
-
- sim.tick();
- ASSERT_EQ(sim.top->imem_addr, 0x8000000CULL, "Cycle 3: PC = 0x8000000C");
-
- sim.tick();
- ASSERT_EQ(sim.top->imem_addr, 0x80000010ULL, "Cycle 4: PC = 0x80000010");
-
- // No memory operations
- ASSERT_EQ(sim.top->dmem_read_en, 0, "NOP: no dmem read");
- ASSERT_EQ(sim.top->dmem_write_en, 0, "NOP: no dmem write");
-}
-
-// -----------------------
-// Test 3: ADDI instruction
-// Verifies: Decoder → ID/EX → EX (ALU) → EX/MEM → MEM → MEM/WB → WB (regfile write)
-// -----------------------
-void test_addi(CoreSim& sim) {
- std::cout << "Test 3: ADDI x1, x0, 42 → verify pipeline propagation..." << std::endl;
-
- // Program: addi x1, x0, 42 followed by NOPs
- std::vector<uint32_t> program = {
- ADDI(1, 0, 42), // x1 = 0 + 42 = 42
- NOP(), NOP(), NOP(), NOP(), NOP()
- };
- sim.mem.load_program(0x80000000ULL, program);
- sim.reset();
-
- // Run through full pipeline drain (5 stages + some margin)
- sim.run(7);
-
- // After 5+ cycles, the ADDI should have fully committed
- // No dmem activity for ADDI
- ASSERT_EQ(sim.top->dmem_read_en, 0, "ADDI: no dmem read");
- ASSERT_EQ(sim.top->dmem_write_en, 0, "ADDI: no dmem write");
-
- // PC should have advanced past the program
- // After reset (PC=0x80000000), 7 ticks → PC ≈ 0x80000000 + 7*4 = 0x8000001C
- ASSERT_EQ(sim.top->imem_addr, 0x8000001CULL, "ADDI: PC after 7 cycles");
-}
-
-// -----------------------
-// Test 4: Two dependent ADDIs (data forwarding from WB)
-// addi x1, x0, 10
-// addi x2, x1, 20 ← depends on x1 (2 cycles later, forwarded from WB)
-// -----------------------
-void test_forwarding_wb(CoreSim& sim) {
- std::cout << "Test 4: Forwarding from WB stage (2-cycle gap)..." << std::endl;
-
- std::vector<uint32_t> program = {
- ADDI(1, 0, 10), // x1 = 10
- NOP(), // bubble
- NOP(), // bubble
- ADDI(2, 1, 20), // x2 = x1 + 20 = 30 (forward from WB)
- NOP(), NOP(), NOP(), NOP(), NOP()
- };
- sim.mem.load_program(0x80000000ULL, program);
- sim.reset();
-
- sim.run(10);
-
- // Verify no crashes and pipeline continues
- ASSERT_EQ(sim.top->dmem_read_en, 0, "FWD_WB: no dmem read");
- ASSERT_EQ(sim.top->dmem_write_en, 0, "FWD_WB: no dmem write");
-}
-
-// -----------------------
-// Test 5: Back-to-back dependent ADDIs (MEM forwarding)
-// addi x1, x0, 10
-// addi x2, x1, 5 ← depends on x1 (1 cycle later, forwarded from MEM)
-// -----------------------
-void test_forwarding_mem(CoreSim& sim) {
- std::cout << "Test 5: Forwarding from MEM stage (1-cycle gap)..." << std::endl;
-
- std::vector<uint32_t> program = {
- ADDI(1, 0, 10), // x1 = 10
- NOP(), // 1 bubble
- ADDI(2, 1, 5), // x2 = x1 + 5 = 15 (forward from MEM)
- NOP(), NOP(), NOP(), NOP(), NOP()
- };
- sim.mem.load_program(0x80000000ULL, program);
- sim.reset();
-
- sim.run(10);
-
- ASSERT_EQ(sim.top->dmem_read_en, 0, "FWD_MEM: no dmem read");
- ASSERT_EQ(sim.top->dmem_write_en, 0, "FWD_MEM: no dmem write");
-}
-
-// -----------------------
-// Test 6: Load-Use hazard stall
-// addi x1, x0, 0x100 ← x1 = 0x100 (base address for load)
-// ld x2, 0(x1) ← load from memory
-// add x3, x2, x0 ← depends on load result → load-use stall
-// -----------------------
-void test_load_use_stall(CoreSim& sim) {
- std::cout << "Test 6: Load-use stall detection..." << std::endl;
-
- // Pre-load data memory
- sim.mem.write_data(0x100, 0xDEADBEEFCAFEBABEULL);
-
- std::vector<uint32_t> program = {
- ADDI(1, 0, 0x100), // x1 = 0x100
- NOP(),
- NOP(),
- LD(2, 1, 0), // x2 = mem[x1+0] (load)
- ADD(3, 2, 0), // x3 = x2 + x0 → load-use stall expected
- NOP(), NOP(), NOP(), NOP(), NOP()
- };
- sim.mem.load_program(0x80000000ULL, program);
- sim.reset();
-
- sim.run(12);
-
- // After enough cycles, the load should have triggered dmem activity
- // We can't directly read registers, but verify no crash and dmem was read
- // The load instruction at cycle ~4-5 should trigger dmem_read_en
- // This is a pipeline timing test - success means no crashes/hangs
- std::cout << " Load-use stall test: pipeline survived without hang" << std::endl;
- g_test_count++;
- g_pass_count++; // If we got here, the pipeline didn't crash
-}
-
-// -----------------------
-// Test 7: Store operation
-// addi x1, x0, 0x200
-// addi x2, x0, 0x55
-// sd x2, 0(x1) ← store x2 to mem[0x200]
-// -----------------------
-void test_store(CoreSim& sim) {
- std::cout << "Test 7: Store (SD) instruction..." << std::endl;
-
- // x1 and x2 need to fully commit to regfile before SD uses them.
- // Pipeline: ADDI takes 5 stages to write back. SD reads rs1/rs2 in ID.
- // We need enough NOPs so that both ADDIs have written back before SD is decoded.
- std::vector<uint32_t> program = {
- ADDI(1, 0, 0x200), // x1 = 0x200
- ADDI(2, 0, 0x55), // x2 = 0x55
- NOP(), // Bubble
- NOP(), // Bubble
- NOP(), // Bubble — ensures both x1/x2 are in regfile
- SD(2, 1, 0), // mem[x1+0] = x2
- NOP(), NOP(), NOP(), NOP(), NOP()
- };
- sim.mem.load_program(0x80000000ULL, program);
- sim.reset();
-
- sim.run(14);
-
- // Verify that the store wrote to data memory
- uint64_t stored = sim.mem.read_data(0x200);
- ASSERT_EQ(stored, 0x55ULL, "Store: mem[0x200] should be 0x55");
-}
-
-// -----------------------
-// Test 8: Branch taken (BEQ with equal registers → jumps)
-// addi x1, x0, 5
-// addi x2, x0, 5
-// (bubbles)
-// beq x1, x2, +8 → branch taken, skip next instr
-// addi x3, x0, 99 ← should be flushed
-// addi x4, x0, 77 ← branch target
-// -----------------------
-void test_branch_taken(CoreSim& sim) {
- std::cout << "Test 8: Branch taken (BEQ x1==x2)..." << std::endl;
-
- std::vector<uint32_t> program = {
- ADDI(1, 0, 5), // x1 = 5
- ADDI(2, 0, 5), // x2 = 5
- NOP(),
- NOP(),
- BEQ(1, 2, 8), // if x1==x2, branch +8 (skip next instr)
- ADDI(3, 0, 99), // should be flushed (unreachable)
- ADDI(4, 0, 77), // branch target
- NOP(), NOP(), NOP(), NOP(), NOP()
- };
- sim.mem.load_program(0x80000000ULL, program);
- sim.reset();
-
- // Run enough cycles for branch to resolve
- sim.run(12);
-
- // PC should have jumped past the flushed instruction
- // Key test: pipeline doesn't crash on branch flush
- std::cout << " Branch taken test: pipeline survived branch flush" << std::endl;
- g_test_count++;
- g_pass_count++;
-}
-
-// -----------------------
-// Test 9: Branch not-taken (BEQ with unequal registers → falls through)
-// -----------------------
-void test_branch_not_taken(CoreSim& sim) {
- std::cout << "Test 9: Branch not-taken (BEQ x1!=x2)..." << std::endl;
-
- std::vector<uint32_t> program = {
- ADDI(1, 0, 5), // x1 = 5
- ADDI(2, 0, 10), // x2 = 10
- NOP(),
- NOP(),
- BEQ(1, 2, 8), // x1 != x2 → fall through
- ADDI(3, 0, 99), // should execute (not flushed)
- ADDI(4, 0, 77),
- NOP(), NOP(), NOP(), NOP(), NOP()
- };
- sim.mem.load_program(0x80000000ULL, program);
- sim.reset();
-
- sim.run(15);
-
- // PC should proceed sequentially since branch not taken
- std::cout << " Branch not-taken test: pipeline continues sequentially" << std::endl;
- g_test_count++;
- g_pass_count++;
-}
-
-// -----------------------
-// Test 10: Pipeline stress — multiple instructions, mixed operations
-// Tests overall pipeline integrity with a realistic instruction mix
-// -----------------------
-void test_pipeline_stress(CoreSim& sim) {
- std::cout << "Test 10: Pipeline stress test..." << std::endl;
-
- sim.mem.write_data(0x300, 0x1234567890ABCDEFULL);
-
- std::vector<uint32_t> program = {
- ADDI(1, 0, 100), // x1 = 100
- ADDI(2, 0, 200), // x2 = 200
- ADDI(3, 0, 0x300), // x3 = 0x300 (data addr)
- NOP(),
- ADD(4, 1, 2), // x4 = x1 + x2 = 300 (forwarding test)
- SUB(5, 2, 1), // x5 = x2 - x1 = 100 (forwarding test)
- NOP(),
- LD(6, 3, 0), // x6 = mem[0x300]
- NOP(), // Load-use bubble
- ADD(7, 6, 1), // x7 = x6 + x1 (uses load result)
- SD(4, 3, 8), // mem[0x308] = x4
- NOP(),
- BNE(1, 2, 8), // x1 != x2 → branch taken
- ADDI(8, 0, 0xFF), // should be flushed
- ADDI(9, 0, 0x42), // branch target
- NOP(), NOP(), NOP(), NOP(), NOP()
- };
- sim.mem.load_program(0x80000000ULL, program);
- sim.reset();
-
- sim.run(25);
-
- // Verify store result
- uint64_t stored = sim.mem.read_data(0x308);
- // x4 = 300, but forwarding may or may not work perfectly depending on pipeline timing
- // The key test is that the pipeline doesn't crash
- std::cout << " Pipeline stress: completed " << 25 << " cycles without crash" << std::endl;
- std::cout << " mem[0x308] = 0x" << std::hex << stored << std::dec << std::endl;
- g_test_count++;
- g_pass_count++;
-}
-
-// -----------------------
-// Test 11: Hazard unit flush signal verification
-// Verify that branch flush properly inserts bubbles
-// -----------------------
-void test_branch_flush_timing(CoreSim& sim) {
- std::cout << "Test 11: Branch flush timing..." << std::endl;
-
- std::vector<uint32_t> program = {
- ADDI(1, 0, 0), // x1 = 0
- NOP(),
- NOP(),
- NOP(),
- BEQ(1, 0, 12), // x1 == x0 (both 0), branch +12
- ADDI(10, 0, 1), // flushed
- ADDI(11, 0, 2), // flushed
- ADDI(12, 0, 3), // branch target (PC+12 from BEQ)
- NOP(), NOP(), NOP(), NOP()
- };
- sim.mem.load_program(0x80000000ULL, program);
- sim.reset();
-
- sim.run(15);
-
- ASSERT_EQ(sim.top->dmem_read_en, 0, "Flush: no dmem read after branch");
- ASSERT_EQ(sim.top->dmem_write_en, 0, "Flush: no dmem write after branch");
-}
-
-// -----------------------
-// Test 12: Verify reset clears pipeline state
-// -----------------------
-void test_reset_mid_execution(CoreSim& sim) {
- std::cout << "Test 12: Reset during execution..." << std::endl;
-
- std::vector<uint32_t> program = {
- ADDI(1, 0, 42),
- ADDI(2, 0, 100),
- ADD(3, 1, 2),
- NOP(), NOP(), NOP(), NOP(), NOP()
- };
- sim.mem.load_program(0x80000000ULL, program);
- sim.reset();
- sim.run(3); // Partially execute
-
- // Reset mid execution
- sim.reset(3);
- sim.tick();
-
- // After reset+tick, PC has advanced to 0x80000004
- ASSERT_EQ(sim.top->imem_addr, 0x80000004ULL, "Mid-reset: PC = 0x80000004 after reset+1 tick");
- ASSERT_EQ(sim.top->dmem_read_en, 0, "Mid-reset: no dmem read");
- ASSERT_EQ(sim.top->dmem_write_en, 0, "Mid-reset: no dmem write");
-}
-
-// Test 13: RV64 Fibonacci Sequence Integration Test
-void test_fibonacci(CoreSim& sim) {
- std::cout << "Test 13: RV64 Fibonacci Sequence..." << std::endl;
-
- std::vector<uint32_t> program = {
- ADDI(5, 0, 10), // addi x5, x0, 10
- LUI(6, 0x10000), // lui x6, 0x10000
- ADDI(7, 0, 0), // addi x7, x0, 0
- ADDI(8, 0, 1), // addi x8, x0, 1
- BEQ(5, 0, 32), // beq x5, x0, 32
- SD(7, 6, 0), // sd x7, 0(x6)
- ADD(9, 7, 8), // add x9, x7, x8
- ADDI(7, 8, 0), // addi x7, x8, 0
- ADDI(8, 9, 0), // addi x8, x9, 0
- ADDI(6, 6, 8), // addi x6, x6, 8
- ADDI(5, 5, -1), // addi x5, x5, -1
- JAL(0, -28), // jal x0, -28
- JAL(0, 0) // jal x0, 0
- };
-
- sim.mem.load_program(0x80000000ULL, program);
- sim.reset();
-
- sim.run(200);
-
- uint64_t expected_fib[10] = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34};
-
- for (int i = 0; i < 10; i++) {
- uint64_t addr = 0x10000000ULL + (i * 8);
- uint64_t actual_data = sim.mem.read_data(addr);
-
- ASSERT_EQ(actual_data, expected_fib[i], "Fibonacci value mismatch");
- }
-}
-
-// ═══════════════════════════════════════════════════════════════
-// Targeted Trap/Interrupt Scenarios (ctest-selectable)
-// ═══════════════════════════════════════════════════════════════
-
-static void run_mem_fault_pipeline_case() {
- std::cout << "[CASE] mem_fault_pipeline" << std::endl;
- CoreSim sim;
- install_trap_handlers(sim.mem);
-
- const uint64_t boot_pc = 0x80000000ULL;
- const uint64_t load_addr = 0x0000000000001003ULL;
- const uint64_t store_addr = 0x0000000000002005ULL;
- const uint64_t log_base = 0x0000000000009000ULL;
- sim.mem.write_data(MACHINE_LOG_PTR_ADDR, log_base);
- ASSERT_EQ(sim.mem.read_data(MACHINE_LOG_PTR_ADDR), log_base, "machine log pointer seeded");
-
- std::vector<uint32_t> program;
- emit_nops(program, 8);
- emit_csrrw_imm64(program, 1, CSR_MTVEC, MACHINE_TRAP_VECTOR);
- emit_nops(program, 8);
- emit_load_imm64(program, 3, load_addr);
- program.push_back(LD(5, 3, 0));
- program.push_back(JAL(0, -4));
-
- sim.mem.load_program(boot_pc, program);
- sim.reset();
- sim.run(80);
- ASSERT_EQ(sim.mem.read_data(MACHINE_LOG_PTR_ADDR), log_base + LOG_ENTRY_STRIDE, "machine log pointer advanced");
-
- const TrapRecord load_trap = fetch_trap_record(sim.mem, log_base, 0);
- ASSERT_EQ(load_trap.mcause, EXC_LOAD_FAULT, "load fault cause code");
- ASSERT_EQ(load_trap.mtval, load_addr, "load fault address latched");
- ASSERT_EQ(sim.mem.read_data(store_addr & ~0x7ULL), 0ULL, "misaligned store suppressed");
-}
-
-static void run_double_exception_flush_case() {
- std::cout << "[CASE] double_exception_flush" << std::endl;
- CoreSim sim;
- install_trap_handlers(sim.mem);
-
- const uint64_t boot_pc = 0x80000000ULL;
- const uint64_t log_base = 0x0000000000009100ULL;
- sim.mem.write_data(MACHINE_LOG_PTR_ADDR, log_base);
- std::vector<uint32_t> program;
- emit_nops(program, 8);
- emit_csrrw_imm64(program, 1, CSR_MTVEC, MACHINE_TRAP_VECTOR);
- emit_nops(program, 8);
- program.push_back(ILLEGAL());
- program.push_back(NOP());
- program.push_back(JAL(0, -4));
-
- sim.mem.load_program(boot_pc, program);
- sim.reset();
- sim.run(80);
-
- const TrapRecord illegal_trap = fetch_trap_record(sim.mem, log_base, 0);
- ASSERT_EQ(illegal_trap.mcause, EXC_INST_ILLEGAL, "first cause illegal");
- ASSERT_EQ(sim.mem.read_data(MACHINE_LOG_PTR_ADDR), log_base + LOG_ENTRY_STRIDE, "single trap logged");
-}
-
-static void run_mret_sret_redirect_case() {
- std::cout << "[CASE] mret_sret_redirect" << std::endl;
- CoreSim sim;
- install_trap_handlers(sim.mem);
-
- const uint64_t boot_pc = 0x80000000ULL;
- const uint64_t m_log_base = 0x0000000000009200ULL;
- const uint64_t post_store_addr = 0x0000000000009400ULL;
- const uint64_t post_trap_addr = 0x0000000000009408ULL;
- sim.mem.write_data(MACHINE_LOG_PTR_ADDR, m_log_base);
-
- std::vector<uint32_t> boot;
- emit_nops(boot, 8);
- emit_csrrw_imm64(boot, 1, CSR_MTVEC, MACHINE_TRAP_VECTOR);
- emit_nops(boot, 8);
- emit_load_imm64(boot, 11, post_store_addr);
- emit_load_imm64(boot, 12, 0xCAFECAFEULL);
- boot.push_back(SD(12, 11, 0));
- boot.push_back(ECALL());
- emit_load_imm64(boot, 13, post_trap_addr);
- emit_load_imm64(boot, 14, 0xBEEFBEEFULL);
- boot.push_back(SD(14, 13, 0));
- boot.push_back(JAL(0, 0));
- sim.mem.load_program(boot_pc, boot);
-
- sim.reset();
- sim.run(300);
-
- const TrapRecord m_trap = fetch_trap_record(sim.mem, m_log_base, 0);
- ASSERT_EQ(m_trap.mcause, EXC_ECALL_FROM_M, "ecall from machine traps to machine");
- ASSERT_EQ(sim.mem.read_data(post_store_addr), 0xFFFFFFFFCAFECAFEULL, "post-trap store committed once");
-}
-
-static void run_branch_interrupt_priority_case() {
- std::cout << "[CASE] branch_interrupt_priority" << std::endl;
- CoreSim sim;
- install_trap_handlers(sim.mem);
-
- const uint64_t boot_pc = 0x80000000ULL;
- const uint64_t log_base = 0x0000000000009600ULL;
- sim.mem.write_data(MACHINE_LOG_PTR_ADDR, log_base);
-
- std::vector<uint32_t> boot;
- emit_nops(boot, 8);
- emit_csrrw_imm64(boot, 1, CSR_MTVEC, MACHINE_TRAP_VECTOR);
- emit_csrrw_imm64(boot, 2, CSR_MSTATUS, (1ULL << MSTATUS_MIE_BIT));
- emit_csrrw_imm64(boot, 3, CSR_MIE, (1ULL << INTERRUPT_M_EXTERNAL));
- boot.push_back(JAL(0, 0));
- sim.mem.load_program(boot_pc, boot);
-
- sim.reset();
- sim.run(10);
- sim.set_interrupt_lines(false, false, true);
- sim.run(40);
- sim.clear_interrupts();
- sim.run(20);
-
- const TrapRecord interrupt_trap = fetch_trap_record(sim.mem, log_base, 0);
- ASSERT_EQ(interrupt_trap.mcause, INTERRUPT_BIT | INTERRUPT_M_EXTERNAL, "external interrupt cause");
- ASSERT_EQ(interrupt_trap.mtval, 0ULL, "interrupt mtval should be zero");
-}
-
-static void run_mie_masking_case() {
- std::cout << "[CASE] mie_masking_response" << std::endl;
- CoreSim sim;
- install_trap_handlers(sim.mem);
-
- const uint64_t boot_pc = 0x80000000ULL;
- const uint64_t log_base = 0x0000000000009700ULL;
- sim.mem.write_data(MACHINE_LOG_PTR_ADDR, log_base);
- for (int i = 0; i < 3; ++i) {
- sim.mem.write_data(log_base + static_cast<uint64_t>(i) * 8ULL, 0ULL);
- }
-
- std::vector<uint32_t> program;
- emit_nops(program, 8);
- emit_csrrw_imm64(program, 1, CSR_MTVEC, MACHINE_TRAP_VECTOR);
- emit_csrrw_imm64(program, 2, CSR_MIE, (1ULL << INTERRUPT_M_EXTERNAL));
- program.push_back(CSRRW(0, CSR_MSTATUS, 0));
- emit_nops(program, 8);
- emit_nops(program, 48);
- emit_csrrw_imm64(program, 4, CSR_MSTATUS, (1ULL << MSTATUS_MIE_BIT));
- program.push_back(JAL(0, 0));
-
- sim.mem.load_program(boot_pc, program);
- sim.reset();
-
- sim.set_interrupt_lines(false, false, true);
- sim.run(20);
- ASSERT_EQ(sim.mem.read_data(log_base), 0ULL, "no trap while MIE=0");
- sim.run(100);
- const TrapRecord interrupt_trap = fetch_trap_record(sim.mem, log_base, 0);
- ASSERT_EQ(interrupt_trap.mcause, INTERRUPT_BIT | INTERRUPT_M_EXTERNAL, "external interrupt cause");
-}
-
-// TODO: Add EXPECT_FAIL once trap_load_page_fault/trap_store_page_fault are driven by the MMU.
-
-struct CaseDispatchEntry {
- const char* name;
- void (*fn)();
-};
-
-static const CaseDispatchEntry kCaseDispatchTable[] = {
- {"mem_fault_pipeline", run_mem_fault_pipeline_case},
- {"double_exception_flush", run_double_exception_flush_case},
- {"mret_sret_redirect", run_mret_sret_redirect_case},
- {"branch_interrupt_priority", run_branch_interrupt_priority_case},
- {"mie_masking_response", run_mie_masking_case}
-};
-
-// ═══════════════════════════════════════════════════════════════
-// Main
-// ═══════════════════════════════════════════════════════════════
-int main(int argc, char** argv) {
- Verilated::commandArgs(argc, argv);
-
- std::string requested_case;
- for (int i = 1; i < argc; ++i) {
- if (std::string(argv[i]) == "--case" && (i + 1) < argc) {
- requested_case = argv[++i];
- }
- }
-
- if (!requested_case.empty()) {
- const auto* entry = std::find_if(
- std::begin(kCaseDispatchTable),
- std::end(kCaseDispatchTable),
- [&](const CaseDispatchEntry& item) { return requested_case == item.name; }
- );
- if (entry == std::end(kCaseDispatchTable)) {
- std::cerr << "Unknown --case value: " << requested_case << std::endl;
- return 2;
- }
- entry->fn();
- std::cout << "================================================================" << std::endl;
- std::cout << " Results: " << g_pass_count << "/" << g_test_count << " assertions passed" << std::endl;
- std::cout << "================================================================" << std::endl;
- return (g_pass_count == g_test_count) ? 0 : 1;
- }
-
- std::cout << "================================================================" << std::endl;
- std::cout << " Richard CPU Core Integration Test" << std::endl;
- std::cout << "================================================================" << std::endl;
-
- {
- CoreSim sim;
- test_reset(sim);
- }
- {
- CoreSim sim;
- test_nop_flow(sim);
- }
- {
- CoreSim sim;
- test_addi(sim);
- }
- {
- CoreSim sim;
- test_forwarding_wb(sim);
- }
- {
- CoreSim sim;
- test_forwarding_mem(sim);
- }
- {
- CoreSim sim;
- test_load_use_stall(sim);
- }
- {
- CoreSim sim;
- test_store(sim);
- }
- {
- CoreSim sim;
- test_branch_taken(sim);
- }
- {
- CoreSim sim;
- test_branch_not_taken(sim);
- }
- {
- CoreSim sim;
- test_pipeline_stress(sim);
- }
- {
- CoreSim sim;
- test_branch_flush_timing(sim);
- }
- {
- CoreSim sim;
- test_reset_mid_execution(sim);
- }
- {
- CoreSim sim;
- test_fibonacci(sim);
- }
-
- run_mem_fault_pipeline_case();
- run_double_exception_flush_case();
- run_mret_sret_redirect_case();
- run_branch_interrupt_priority_case();
- run_mie_masking_case();
-
- std::cout << "================================================================" << std::endl;
- std::cout << " Results: " << g_pass_count << "/" << g_test_count << " assertions passed" << std::endl;
- std::cout << "================================================================" << std::endl;
-
- if (g_pass_count == g_test_count) {
- std::cout << " ALL TESTS PASSED" << std::endl;
- return 0;
- }
-
- std::cout << " SOME TESTS FAILED" << std::endl;
- return 1;
-}
diff --git a/rv64cpu/tb/unit/tb_alu.cc b/rv64cpu/tb/unit/tb_alu.cc
deleted file mode 100644
index 0ed5059..0000000
--- a/rv64cpu/tb/unit/tb_alu.cc
+++ /dev/null
@@ -1,136 +0,0 @@
-// 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 <https://www.gnu.org/licenses/>.
-
-// File path: tb/unit/tb_alu.cc
-// Description: ALU unit test
-#include "Valu.h"
-#include <cassert>
-#include <cstdint>
-#include <iostream>
-#include <verilated.h>
-
-enum AluOp {
- ALU_ADD = 0,
- ALU_SUB = 8,
- ALU_SLL = 1,
- ALU_SLT = 2,
- ALU_SLTU = 3,
- ALU_XOR = 4,
- ALU_SRL = 5,
- ALU_SRA = 13,
- ALU_OR = 6,
- ALU_AND = 7,
- ALU_MUL = 16,
- ALU_MULH = 17,
- ALU_MULHSU = 18,
- ALU_MULHU = 19,
- ALU_DIV = 20,
- ALU_DIVU = 21,
- ALU_REM = 22,
- ALU_REMU = 23
-};
-
-void test_alu(Valu* alu, uint8_t op, uint64_t rs1, uint64_t rs2, uint64_t expected, const char* test_name) {
- alu->alu_op = op;
- alu->operand_a = rs1;
- alu->operand_b = rs2;
-
- alu->eval();
-
- if (alu->alu_result == expected) {
- std::cout << "\033[32m";
- std::cout << "[PASS] " << test_name << " | "
- << rs1 << " op " << rs2 << " = " << expected << "\033[0m" << std::endl;
- } else {
- std::cout << "\033[31m";
- std::cerr << "[FAIL] " << test_name << " | "
- << "Expected: " << expected << ", Got: " << alu->alu_result << "\033[0m" << std::endl;
- assert(false);
- }
-}
-
-int main(int argc, char** argv) {
- Verilated::commandArgs(argc, argv);
- Valu* alu = new Valu;
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << " ALU Unit Test" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- test_alu(alu, ALU_ADD, 100, 50, 150, "ADD_Positive");
- test_alu(alu, ALU_SUB, 100, 50, 50, "SUB_Positive");
-
- test_alu(alu, ALU_ADD, (uint64_t)-5, 10, 5, "ADD_Negative");
-
- test_alu(alu, ALU_AND, 0xFF00, 0x0F0F, 0x0F00, "AND_Mask");
- test_alu(alu, ALU_XOR, 0xAAAA, 0xFFFF, 0x5555, "XOR_Invert");
-
- test_alu(alu, ALU_SLL, 1, 10, 1024, "SLL_By_10");
-
- uint64_t minus_one = (uint64_t)-1;
- uint64_t five = 5;
-
- test_alu(alu, ALU_SLTU, minus_one, five, 0, "SLTU (Unsigned: -1 < 5 is False)");
- test_alu(alu, ALU_SLT, minus_one, five, 1, "SLT (Signed: -1 < 5 is True)");
-
-
- // Missing Basic Operations
- test_alu(alu, ALU_OR, 0xAAAA, 0x5555, 0xFFFF, "OR_All_Bits");
- test_alu(alu, ALU_SRL, 0xF000000000000000ULL, 4, 0x0F00000000000000ULL, "SRL_By_4");
-
- // Arithmetic Right Shift (SRA)
- test_alu(alu, ALU_SRA, 0xF000000000000000ULL, 4, 0xFF00000000000000ULL, "SRA_Negative_By_4");
- test_alu(alu, ALU_SRA, 0x7000000000000000ULL, 4, 0x0700000000000000ULL, "SRA_Positive_By_4");
-
- // Shift Edge Cases
- test_alu(alu, ALU_SLL, 1, 0, 1, "SLL_By_0");
- test_alu(alu, ALU_SLL, 1, 63, 0x8000000000000000ULL, "SLL_By_63");
- test_alu(alu, ALU_SRL, 0x8000000000000000ULL, 63, 1, "SRL_By_63");
- test_alu(alu, ALU_SRA, 0x8000000000000000ULL, 63, 0xFFFFFFFFFFFFFFFFULL, "SRA_By_63_SignExt");
-
- // All 0 / All 1 Cases
- test_alu(alu, ALU_ADD, 0, 0, 0, "ADD_Zero");
- test_alu(alu, ALU_ADD, 0xFFFFFFFFFFFFFFFFULL, 1, 0, "ADD_Overflow");
- test_alu(alu, ALU_XOR, 0xFFFFFFFFFFFFFFFFULL, 0xFFFFFFFFFFFFFFFFULL, 0, "XOR_Same");
-
- // SLT / SLTU Same Value
- test_alu(alu, ALU_SLT, 100, 100, 0, "SLT_Same_Value");
- test_alu(alu, ALU_SLTU, 100, 100, 0, "SLTU_Same_Value");
-
- // SLT Edge Cases
- test_alu(alu, ALU_SLT, 0x8000000000000000ULL, 0x7FFFFFFFFFFFFFFFULL, 1, "SLT_Min_max");
- test_alu(alu, ALU_SLT, 0x7FFFFFFFFFFFFFFFULL, 0x8000000000000000ULL, 0, "SLT_Max_Min");
-
- // M extension
- test_alu(alu, ALU_MUL, 7, 6, 42, "MUL_Positive");
- test_alu(alu, ALU_MUL, 0xFFFFFFFFFFFFFFFFULL, 2, 0xFFFFFFFFFFFFFFFEULL, "MUL_NegativeLow");
- test_alu(alu, ALU_MULH, 0xFFFFFFFFFFFFFFFFULL, 2, 0xFFFFFFFFFFFFFFFFULL, "MULH_Negative");
- test_alu(alu, ALU_MULHU, 0xFFFFFFFFFFFFFFFFULL, 2, 1, "MULHU_UnsignedHigh");
- test_alu(alu, ALU_DIV, (uint64_t)-21, 4, (uint64_t)-5, "DIV_SignedTruncTowardZero");
- test_alu(alu, ALU_DIVU, 21, 4, 5, "DIVU");
- test_alu(alu, ALU_REM, (uint64_t)-21, 4, (uint64_t)-1, "REM_Signed");
- test_alu(alu, ALU_REMU, 21, 4, 1, "REMU");
- test_alu(alu, ALU_DIV, 123, 0, 0xFFFFFFFFFFFFFFFFULL, "DIV_ByZero");
- test_alu(alu, ALU_REM, 123, 0, 123, "REM_ByZero");
- test_alu(alu, ALU_DIV, 0x8000000000000000ULL, 0xFFFFFFFFFFFFFFFFULL,
- 0x8000000000000000ULL, "DIV_Overflow");
- test_alu(alu, ALU_REM, 0x8000000000000000ULL, 0xFFFFFFFFFFFFFFFFULL, 0, "REM_Overflow");
- std::cout << "----------------------------------" << std::endl;
- std::cout << "\033[32mAll tests passed!\033[0m" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- delete alu;
- return 0;
-}
diff --git a/rv64cpu/tb/unit/tb_branch.cc b/rv64cpu/tb/unit/tb_branch.cc
deleted file mode 100644
index e47bf98..0000000
--- a/rv64cpu/tb/unit/tb_branch.cc
+++ /dev/null
@@ -1,100 +0,0 @@
-// 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 <https://www.gnu.org/licenses/>.
-
-// File path: tb/unit/tb_branch.cc
-// Description: Branch Unit test
-
-#include <iostream>
-#include <iomanip>
-#include <cstdint>
-#include <cassert>
-#include <verilated.h>
-#include "Vbranch_unit.h"
-
-// Enums must match rv64_pkg::branch_op_t
-enum BranchOp {
- BRANCH_EQ = 0b000,
- BRANCH_NE = 0b001,
- BRANCH_LT = 0b100,
- BRANCH_GE = 0b101,
- BRANCH_LTU = 0b110,
- BRANCH_GEU = 0b111
-};
-
-void test_branch(Vbranch_unit* dut, uint8_t is_branch, uint8_t op, uint64_t a, uint64_t b, uint8_t expected, const char* name) {
- dut->is_branch = is_branch;
- dut->branch_op = op;
- dut->operand_a = a;
- dut->operand_b = b;
- dut->eval();
-
- if (dut->branch_taken == expected) {
- std::cout << "\033[32m[PASS] " << name << " | Expected: " << (int)expected << " Got: " << (int)dut->branch_taken << "\033[0m" << std::dec << std::endl;
- } else {
- std::cout << "\033[31m[FAIL] " << name << " | Expected: " << (int)expected << " Got: " << (int)dut->branch_taken << "\033[0m" << std::endl;
- assert(false);
- }
-}
-
-int main(int argc, char** argv) {
- Verilated::commandArgs(argc, argv);
- Vbranch_unit* dut = new Vbranch_unit;
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << " Branch Unit Test" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- // --- Not a branch ---
- test_branch(dut, 0, BRANCH_EQ, 10, 10, 0, "Not_A_Branch");
-
- // --- BEQ / BNE ---
- test_branch(dut, 1, BRANCH_EQ, 10, 10, 1, "BEQ_Equal");
- test_branch(dut, 1, BRANCH_EQ, 10, 20, 0, "BEQ_Not_Equal");
- test_branch(dut, 1, BRANCH_NE, 10, 20, 1, "BNE_Not_Equal");
- test_branch(dut, 1, BRANCH_NE, 10, 10, 0, "BNE_Equal");
-
- uint64_t minus_one = (uint64_t)-1; // 0xFFFFFFFFFFFFFFFF
- uint64_t one = 1;
-
- // --- BLT / BGE (Signed) ---
- // -1 is less than 1 (Signed)
- test_branch(dut, 1, BRANCH_LT, minus_one, one, 1, "BLT_Signed_Negative_vs_Positive");
- test_branch(dut, 1, BRANCH_GE, minus_one, one, 0, "BGE_Signed_Negative_vs_Positive");
-
- // 1 is greater than -1 (Signed)
- test_branch(dut, 1, BRANCH_LT, one, minus_one, 0, "BLT_Signed_Positive_vs_Negative");
- test_branch(dut, 1, BRANCH_GE, one, minus_one, 1, "BGE_Signed_Positive_vs_Negative");
-
- // Equal signed
- test_branch(dut, 1, BRANCH_LT, minus_one, minus_one, 0, "BLT_Signed_Equal");
- test_branch(dut, 1, BRANCH_GE, minus_one, minus_one, 1, "BGE_Signed_Equal");
-
- // --- BLTU / BGEU (Unsigned) ---
- // 0xFFFFFFFFFFFFFFFF is NOT less than 1 (Unsigned)
- test_branch(dut, 1, BRANCH_LTU, minus_one, one, 0, "BLTU_Unsigned_Max_vs_1");
- // 0xFFFFFFFFFFFFFFFF is greater than or equal to 1 (Unsigned)
- test_branch(dut, 1, BRANCH_GEU, minus_one, one, 1, "BGEU_Unsigned_Max_vs_1");
-
- // Equal unsigned
- test_branch(dut, 1, BRANCH_LTU, minus_one, minus_one, 0, "BLTU_Unsigned_Equal");
- test_branch(dut, 1, BRANCH_GEU, minus_one, minus_one, 1, "BGEU_Unsigned_Equal");
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << "\033[32mAll tests passed!\033[0m" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- delete dut;
- return 0;
-}
diff --git a/rv64cpu/tb/unit/tb_csr.cc b/rv64cpu/tb/unit/tb_csr.cc
deleted file mode 100644
index e86ad9b..0000000
--- a/rv64cpu/tb/unit/tb_csr.cc
+++ /dev/null
@@ -1,770 +0,0 @@
-// 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 <https://www.gnu.org/licenses/>.
-
-// File path: tb/unit/tb_csr.cc
-// Description: CSR unit testbench
-
-#include <array>
-#include <cassert>
-#include <cstdint>
-#include <iostream>
-#include <verilated.h>
-
-#include "Vcsr_unit.h"
-
-namespace {
-constexpr uint32_t CSR_OP_WRITE = 0b001;
-constexpr uint32_t CSR_OP_SET = 0b010;
-constexpr uint32_t CSR_OP_CLEAR = 0b011;
-
-constexpr uint16_t CSR_MSTATUS = 0x300;
-constexpr uint16_t CSR_MISA = 0x301;
-constexpr uint16_t CSR_MEDELEG = 0x302;
-constexpr uint16_t CSR_MIDELEG = 0x303;
-constexpr uint16_t CSR_MIE = 0x304;
-constexpr uint16_t CSR_MTVEC = 0x305;
-constexpr uint16_t CSR_MCOUNTEREN= 0x306;
-constexpr uint16_t CSR_MENVCFG = 0x30A;
-constexpr uint16_t CSR_MSTATEEN0 = 0x30C;
-constexpr uint16_t CSR_MCOUNTINHIBIT = 0x320;
-constexpr uint16_t CSR_MSCRATCH = 0x340;
-constexpr uint16_t CSR_MEPC = 0x341;
-constexpr uint16_t CSR_MCAUSE = 0x342;
-constexpr uint16_t CSR_MTVAL = 0x343;
-constexpr uint16_t CSR_MIP = 0x344;
-constexpr uint16_t CSR_MCYCLE = 0xB00;
-constexpr uint16_t CSR_MINSTRET = 0xB02;
-constexpr uint16_t CSR_SSTATUS = 0x100;
-constexpr uint16_t CSR_SIE = 0x104;
-constexpr uint16_t CSR_STVEC = 0x105;
-constexpr uint16_t CSR_SCOUNTEREN= 0x106;
-constexpr uint16_t CSR_SENVCFG = 0x10A;
-constexpr uint16_t CSR_SSCRATCH = 0x140;
-constexpr uint16_t CSR_SEPC = 0x141;
-constexpr uint16_t CSR_SCAUSE = 0x142;
-constexpr uint16_t CSR_STVAL = 0x143;
-constexpr uint16_t CSR_SIP = 0x144;
-constexpr uint16_t CSR_SATP = 0x180;
-constexpr uint16_t CSR_CYCLE = 0xC00;
-constexpr uint16_t CSR_INSTRET = 0xC02;
-constexpr uint16_t CSR_FFLAGS = 0x001;
-constexpr uint16_t CSR_FRM = 0x002;
-constexpr uint16_t CSR_FCSR = 0x003;
-constexpr uint16_t CSR_MVENDORID = 0xF11;
-
-constexpr uint64_t EXC_INST_ILLEGAL = 2ULL;
-constexpr uint64_t EXC_ECALL_FROM_U = 8ULL;
-constexpr uint64_t EXC_ECALL_FROM_S = 9ULL;
-constexpr uint64_t EXC_LOAD_FAULT = 5ULL;
-constexpr uint64_t EXC_STORE_FAULT = 7ULL;
-
-constexpr uint64_t INTERRUPT_BIT = 1ULL << 63;
-constexpr uint64_t INTERRUPT_M_SOFTWARE = 3ULL;
-constexpr uint64_t INTERRUPT_M_TIMER = 7ULL;
-constexpr uint64_t INTERRUPT_M_EXTERNAL = 11ULL;
-constexpr uint64_t INTERRUPT_S_SOFTWARE = 1ULL;
-constexpr uint64_t INTERRUPT_S_TIMER = 5ULL;
-constexpr uint64_t INTERRUPT_S_EXTERNAL = 9ULL;
-
-constexpr int MSTATUS_MIE_BIT = 3;
-constexpr int MSTATUS_MPIE_BIT = 7;
-constexpr int MSTATUS_MPP_LO = 11;
-constexpr int MSTATUS_MPP_HI = 12;
-constexpr int MSTATUS_TVM_BIT = 20;
-constexpr int MSTATUS_TW_BIT = 21;
-constexpr int MSTATUS_TSR_BIT = 22;
-constexpr int MSTATUS_UXL_LO = 32;
-constexpr int MSTATUS_SXL_LO = 34;
-constexpr int SSTATUS_SIE_BIT = 1;
-constexpr int SSTATUS_SPIE_BIT = 5;
-constexpr int SSTATUS_SPP_BIT = 8;
-constexpr int MCOUNTINHIBIT_CY_BIT = 0;
-constexpr int MCOUNTINHIBIT_IR_BIT = 2;
-constexpr int STATEEN0_ENVCFG_BIT = 62;
-
-constexpr uint64_t STATUS_XLEN_64 = 2ULL;
-constexpr uint64_t MSTATUS_RESET_VALUE = (STATUS_XLEN_64 << MSTATUS_UXL_LO) |
- (STATUS_XLEN_64 << MSTATUS_SXL_LO);
-constexpr uint64_t SSTATUS_RESET_VALUE = (STATUS_XLEN_64 << MSTATUS_UXL_LO);
-
-constexpr uint64_t PRIV_ENCODE_U = 0ULL;
-constexpr uint64_t PRIV_ENCODE_S = 1ULL;
-constexpr uint64_t PRIV_ENCODE_M = 3ULL;
-constexpr uint64_t MEDELEG_WRITABLE_MASK =
- (1ULL << EXC_ECALL_FROM_U) |
- (1ULL << EXC_ECALL_FROM_S) |
- (1ULL << EXC_LOAD_FAULT) |
- (1ULL << EXC_STORE_FAULT);
-constexpr uint64_t MEDELEG_READ_ONLY_ONE_MASK = (1ULL << EXC_INST_ILLEGAL);
-constexpr uint64_t MIDELEG_WRITABLE_MASK =
- (1ULL << INTERRUPT_S_SOFTWARE) |
- (1ULL << INTERRUPT_S_TIMER) |
- (1ULL << INTERRUPT_S_EXTERNAL);
-constexpr uint64_t MCOUNTINHIBIT_WRITABLE_MASK =
- (1ULL << MCOUNTINHIBIT_CY_BIT) |
- (1ULL << MCOUNTINHIBIT_IR_BIT) |
- (0x1FFFFFFFULL << 3);
-constexpr uint64_t COUNTEREN_WRITABLE_MASK = 0xFFFFFFFFULL;
-}
-
-void tick(Vcsr_unit* dut) {
- dut->clk = 0;
- dut->eval();
- dut->clk = 1;
- dut->eval();
- dut->clk = 0;
- dut->eval();
-}
-
-void drive_defaults(Vcsr_unit* dut) {
- dut->clk = 0;
- dut->rst = 0;
- dut->csr_req = 0;
- dut->csr_op = 0;
- dut->csr_addr = 0;
- dut->csr_rs1_data = 0;
- dut->trap_ctrl_trigger = 0;
- dut->trap_ctrl_to_s_mode = 0;
- dut->trap_ctrl_mepc = 0;
- dut->trap_ctrl_mcause = 0;
- dut->trap_ctrl_mtval = 0;
- dut->trap_ctrl_mret_en = 0;
- dut->trap_ctrl_sret_en = 0;
- dut->m_timer_interrupt = 0;
- dut->m_software_interrupt = 0;
- dut->m_external_interrupt = 0;
- dut->s_timer_interrupt = 0;
- dut->s_software_interrupt = 0;
- dut->s_external_interrupt = 0;
-}
-
-void apply_reset(Vcsr_unit* dut) {
- dut->rst = 1;
- tick(dut);
- dut->rst = 0;
- tick(dut);
-}
-
-void clear_trap_lines(Vcsr_unit* dut) {
- dut->trap_ctrl_trigger = 0;
- dut->trap_ctrl_to_s_mode = 0;
- dut->trap_ctrl_mepc = 0;
- dut->trap_ctrl_mcause = 0;
- dut->trap_ctrl_mtval = 0;
- dut->trap_ctrl_mret_en = 0;
- dut->trap_ctrl_sret_en = 0;
-}
-
-void trigger_trap(
- Vcsr_unit* dut,
- uint64_t mepc,
- uint64_t mcause,
- uint64_t mtval,
- bool to_s_mode
-) {
- dut->trap_ctrl_trigger = 1;
- dut->trap_ctrl_mepc = mepc;
- dut->trap_ctrl_mcause = mcause;
- dut->trap_ctrl_mtval = mtval;
- dut->trap_ctrl_to_s_mode = to_s_mode ? 1 : 0;
- tick(dut);
- dut->trap_ctrl_trigger = 0;
- dut->trap_ctrl_mepc = 0;
- dut->trap_ctrl_mcause = 0;
- dut->trap_ctrl_mtval = 0;
- dut->trap_ctrl_to_s_mode = 0;
-}
-
-void trigger_interrupt(Vcsr_unit* dut, uint64_t mepc, uint64_t interrupt_cause) {
- trigger_trap(dut, mepc, (INTERRUPT_BIT | interrupt_cause), 0ULL, false);
-}
-
-template <typename Fn>
-void run_csr_scenario(Vcsr_unit* dut, const char* name, Fn&& body) {
- clear_trap_lines(dut);
- std::cout << "[RUN] CSR scenario: " << name << std::endl;
- body(dut);
- std::cout << "[PASS] CSR scenario: " << name << std::endl;
-}
-
-uint64_t read_csr(Vcsr_unit* dut, uint16_t addr) {
- dut->csr_addr = addr;
- dut->csr_req = 0;
- dut->csr_op = 0;
- dut->csr_rs1_data = 0;
- dut->eval();
- return dut->csr_rdata;
-}
-
-void csr_op(Vcsr_unit* dut, uint16_t addr, uint32_t op, uint64_t data) {
- dut->csr_addr = addr;
- dut->csr_op = op;
- dut->csr_rs1_data = data;
- dut->csr_req = 1;
- tick(dut);
- dut->csr_req = 0;
- dut->csr_op = 0;
- dut->csr_rs1_data = 0;
- dut->csr_addr = 0;
-}
-
-void advance_cycles(Vcsr_unit* dut, int cycles) {
- for (int i = 0; i < cycles; ++i) {
- tick(dut);
- }
-}
-
-void force_priv_mode_via_mret(Vcsr_unit* dut, uint64_t encoded_mode) {
- if (dut->csr_priv_mode != PRIV_ENCODE_M) {
- trigger_trap(dut, 0x100ULL, EXC_INST_ILLEGAL, 0ULL, false);
- }
- uint64_t mstatus = read_csr(dut, CSR_MSTATUS);
- mstatus &= ~(0x3ULL << MSTATUS_MPP_LO);
- mstatus |= (encoded_mode << MSTATUS_MPP_LO);
- csr_op(dut, CSR_MSTATUS, CSR_OP_WRITE, mstatus);
- dut->trap_ctrl_mret_en = 1;
- tick(dut);
- dut->trap_ctrl_mret_en = 0;
-}
-
-uint64_t arbitrate_trap_priority(
- bool ext_external,
- bool ext_software,
- bool ext_timer,
- bool has_sync_exception,
- uint64_t sync_cause,
- bool& chose_interrupt
-) {
- if (ext_external) {
- chose_interrupt = true;
- return INTERRUPT_BIT | INTERRUPT_M_EXTERNAL;
- }
- if (ext_software) {
- chose_interrupt = true;
- return INTERRUPT_BIT | INTERRUPT_M_SOFTWARE;
- }
- if (ext_timer) {
- chose_interrupt = true;
- return INTERRUPT_BIT | INTERRUPT_M_TIMER;
- }
- chose_interrupt = false;
- return has_sync_exception ? sync_cause : 0ULL;
-}
-
-int main(int argc, char** argv) {
- Verilated::commandArgs(argc, argv);
- Vcsr_unit* dut = new Vcsr_unit;
-
- drive_defaults(dut);
- apply_reset(dut);
- clear_trap_lines(dut);
-
- std::cout << "----------------------------------\n";
- std::cout << " CSR Unit Testbench\n";
- std::cout << "----------------------------------\n";
-
- // Reset sanity
- assert(read_csr(dut, CSR_MSTATUS) == MSTATUS_RESET_VALUE);
- assert(read_csr(dut, CSR_MIE) == 0);
- assert(read_csr(dut, CSR_MEPC) == 0);
- assert(read_csr(dut, CSR_SSTATUS) == SSTATUS_RESET_VALUE);
- assert(read_csr(dut, CSR_SATP) == 0);
- std::cout << "[PASS] Reset clears CSR state" << std::endl;
-
- // CSRRW basic write
- csr_op(dut, CSR_MTVEC, CSR_OP_WRITE, 0x1020ULL);
- assert(read_csr(dut, CSR_MTVEC) == 0x1020ULL);
- csr_op(dut, CSR_MSCRATCH, CSR_OP_WRITE, 0xCAFEBABEULL);
- assert(read_csr(dut, CSR_MSCRATCH) == 0xCAFEBABEULL);
- std::cout << "[PASS] CSRRW updates mutable CSRs" << std::endl;
-
- // CSRRS / CSRRC behavior
- csr_op(dut, CSR_MIE, CSR_OP_WRITE, 0);
- csr_op(dut, CSR_MIE, CSR_OP_SET, (1ULL << INTERRUPT_M_SOFTWARE) | (1ULL << INTERRUPT_M_TIMER));
- assert(read_csr(dut, CSR_MIE) == ((1ULL << INTERRUPT_M_SOFTWARE) | (1ULL << INTERRUPT_M_TIMER)));
- csr_op(dut, CSR_MIE, CSR_OP_CLEAR, (1ULL << INTERRUPT_M_SOFTWARE));
- assert(read_csr(dut, CSR_MIE) == (1ULL << INTERRUPT_M_TIMER));
- csr_op(dut, CSR_MIE, CSR_OP_SET, 0x0ULL);
- assert(read_csr(dut, CSR_MIE) == (1ULL << INTERRUPT_M_TIMER));
- std::cout << "[PASS] CSRRS/CSRRC honor masks and ignore zero data" << std::endl;
-
- csr_op(
- dut,
- CSR_MSTATUS,
- CSR_OP_WRITE,
- (1ULL << MSTATUS_TVM_BIT) | (1ULL << MSTATUS_TW_BIT) | (1ULL << MSTATUS_TSR_BIT)
- );
- assert((read_csr(dut, CSR_MSTATUS) & ((1ULL << MSTATUS_TVM_BIT) |
- (1ULL << MSTATUS_TW_BIT) |
- (1ULL << MSTATUS_TSR_BIT))) == 0ULL);
- std::cout << "[PASS] Unsupported mstatus trap-control bits are read-zero" << std::endl;
-
- // Hardwired WARL CSRs may ignore writes without taking an illegal-instruction trap.
- const uint64_t misa_ro_snapshot = read_csr(dut, CSR_MISA);
- csr_op(dut, CSR_MISA, CSR_OP_WRITE, 0ULL);
- assert(read_csr(dut, CSR_MISA) == misa_ro_snapshot);
- std::cout << "[PASS] Hardwired WARL CSRs ignore unsupported writes" << std::endl;
-
- // Machine counters are writable through machine CSR addresses; user aliases are read-only.
- csr_op(dut, CSR_MCYCLE, CSR_OP_WRITE, 0x40ULL);
- assert(read_csr(dut, CSR_MCYCLE) == 0x40ULL);
- csr_op(dut, CSR_CYCLE, CSR_OP_WRITE, 0x100ULL);
- assert(read_csr(dut, CSR_MCYCLE) == 0x41ULL);
- advance_cycles(dut, 5);
- assert(read_csr(dut, CSR_MCYCLE) == 0x46ULL);
- const uint64_t instret_before = read_csr(dut, CSR_MINSTRET);
- advance_cycles(dut, 3);
- assert(read_csr(dut, CSR_MINSTRET) == instret_before + 3);
- std::cout << "[PASS] Cycle/instret counters tick correctly" << std::endl;
-
- csr_op(dut, CSR_MCOUNTINHIBIT, CSR_OP_WRITE, ~0ULL);
- assert(read_csr(dut, CSR_MCOUNTINHIBIT) == MCOUNTINHIBIT_WRITABLE_MASK);
- const uint64_t inhibited_cycle = read_csr(dut, CSR_MCYCLE);
- const uint64_t inhibited_instret = read_csr(dut, CSR_MINSTRET);
- advance_cycles(dut, 3);
- assert(read_csr(dut, CSR_MCYCLE) == inhibited_cycle);
- assert(read_csr(dut, CSR_MINSTRET) == inhibited_instret);
- csr_op(dut, CSR_MCOUNTINHIBIT, CSR_OP_WRITE, 0ULL);
- advance_cycles(dut, 1);
- assert(read_csr(dut, CSR_MCYCLE) == inhibited_cycle + 1);
- assert(read_csr(dut, CSR_MINSTRET) == inhibited_instret + 1);
- std::cout << "[PASS] mcountinhibit controls machine counters" << std::endl;
-
- csr_op(dut, CSR_MCOUNTEREN, CSR_OP_WRITE, ~0ULL);
- assert(read_csr(dut, CSR_MCOUNTEREN) == COUNTEREN_WRITABLE_MASK);
- csr_op(dut, CSR_SCOUNTEREN, CSR_OP_WRITE, ~0ULL);
- assert(read_csr(dut, CSR_SCOUNTEREN) == COUNTEREN_WRITABLE_MASK);
- std::cout << "[PASS] counteren CSRs keep only implemented counter bits" << std::endl;
-
- // Trap should freeze minstret for that cycle
- uint64_t mstatus_cfg = read_csr(dut, CSR_MSTATUS) | (1ULL << MSTATUS_MIE_BIT);
- csr_op(dut, CSR_MSTATUS, CSR_OP_WRITE, mstatus_cfg);
- const uint64_t instret_pre_trap = read_csr(dut, CSR_MINSTRET);
- trigger_trap(dut, 0xDEADULL, EXC_INST_ILLEGAL, 0xDEADULL, false);
- assert(read_csr(dut, CSR_MINSTRET) == instret_pre_trap);
- advance_cycles(dut, 1);
- assert(read_csr(dut, CSR_MINSTRET) == instret_pre_trap + 1);
- std::cout << "[PASS] Trap cycle does not increment minstret" << std::endl;
-
- // Trap bookkeeping for illegal instruction
- assert(read_csr(dut, CSR_MEPC) == 0xDEADULL);
- assert(read_csr(dut, CSR_MCAUSE) == EXC_INST_ILLEGAL);
- assert(read_csr(dut, CSR_MTVAL) == 0xDEADULL);
- const uint64_t mstatus_after_trap = read_csr(dut, CSR_MSTATUS);
- assert(((mstatus_after_trap >> MSTATUS_MIE_BIT) & 1ULL) == 0ULL);
- assert(((mstatus_after_trap >> MSTATUS_MPIE_BIT) & 1ULL) == 1ULL);
- assert(((mstatus_after_trap >> MSTATUS_MPP_LO) & 0x3ULL) == PRIV_ENCODE_M);
- std::cout << "[PASS] Illegal trap updates mepc/mcause/mtval/mstatus" << std::endl;
-
- // mret restore flow
- dut->trap_ctrl_mret_en = 1;
- tick(dut);
- dut->trap_ctrl_mret_en = 0;
- const uint64_t mstatus_after_mret_reset = read_csr(dut, CSR_MSTATUS);
- assert(((mstatus_after_mret_reset >> MSTATUS_MIE_BIT) & 1ULL) == 1ULL);
- assert(((mstatus_after_mret_reset >> MSTATUS_MPIE_BIT) & 1ULL) == 1ULL);
- assert(((mstatus_after_mret_reset >> MSTATUS_MPP_LO) & 0x3ULL) == PRIV_ENCODE_U);
- std::cout << "[PASS] mret restores interrupt enable bits" << std::endl;
-
- // sret restore flow
- uint64_t sstatus_cfg = (1ULL << SSTATUS_SIE_BIT) | (1ULL << SSTATUS_SPP_BIT);
- csr_op(dut, CSR_SSTATUS, CSR_OP_WRITE, sstatus_cfg);
- dut->trap_ctrl_sret_en = 1;
- tick(dut);
- dut->trap_ctrl_sret_en = 0;
- const uint64_t sstatus_after_sret_reset = read_csr(dut, CSR_SSTATUS);
- assert(((sstatus_after_sret_reset >> SSTATUS_SIE_BIT) & 1ULL) == 0ULL);
- assert(((sstatus_after_sret_reset >> SSTATUS_SPIE_BIT) & 1ULL) == 1ULL);
- assert(((sstatus_after_sret_reset >> SSTATUS_SPP_BIT) & 1ULL) == 0ULL);
- std::cout << "[PASS] sret updates supervisor status bits" << std::endl;
-
- // Ecall causes depend on current privilege mode
- force_priv_mode_via_mret(dut, PRIV_ENCODE_S);
- trigger_trap(dut, 0x600ULL, EXC_ECALL_FROM_S, 0ULL, false);
- assert(read_csr(dut, CSR_MCAUSE) == EXC_ECALL_FROM_S);
- assert(read_csr(dut, CSR_MTVAL) == 0ULL);
- assert(read_csr(dut, CSR_MEPC) == 0x600ULL);
- assert(((read_csr(dut, CSR_MSTATUS) >> MSTATUS_MPP_LO) & 0x3ULL) == PRIV_ENCODE_S);
-
- force_priv_mode_via_mret(dut, PRIV_ENCODE_U);
- trigger_trap(dut, 0x700ULL, EXC_ECALL_FROM_U, 0ULL, false);
- assert(read_csr(dut, CSR_MCAUSE) == EXC_ECALL_FROM_U);
- assert(read_csr(dut, CSR_MTVAL) == 0ULL);
- assert(read_csr(dut, CSR_MEPC) == 0x700ULL);
- assert(((read_csr(dut, CSR_MSTATUS) >> MSTATUS_MPP_LO) & 0x3ULL) == PRIV_ENCODE_U);
- std::cout << "[PASS] Ecall encodes mcause per privilege" << std::endl;
-
- // Trap delegation from U to S via medeleg
- csr_op(dut, CSR_MEDELEG, CSR_OP_WRITE, 0ULL);
- csr_op(dut, CSR_MEDELEG, CSR_OP_SET, (1ULL << EXC_ECALL_FROM_U));
- csr_op(dut, CSR_STVEC, CSR_OP_WRITE, 0x4000ULL);
- assert(read_csr(dut, CSR_MEDELEG) & (1ULL << EXC_ECALL_FROM_U));
- force_priv_mode_via_mret(dut, PRIV_ENCODE_U);
- assert(dut->csr_priv_mode == PRIV_ENCODE_U);
- trigger_trap(dut, 0x900ULL, EXC_ECALL_FROM_U, 0ULL, true);
- assert(dut->csr_trap_to_s_mode);
- assert(dut->csr_trap_vector == 0x4000ULL);
- assert(read_csr(dut, CSR_SEPC) == 0x900ULL);
- assert(read_csr(dut, CSR_SCAUSE) == EXC_ECALL_FROM_U);
- assert(read_csr(dut, CSR_STVAL) == 0ULL);
- assert(dut->csr_priv_mode == PRIV_ENCODE_S);
- std::cout << "[PASS] medeleg routes ecall to supervisor" << std::endl;
- force_priv_mode_via_mret(dut, PRIV_ENCODE_M);
-
- csr_op(dut, CSR_MEDELEG, CSR_OP_WRITE, ~0ULL);
- assert((read_csr(dut, CSR_MEDELEG) & MEDELEG_WRITABLE_MASK) == MEDELEG_WRITABLE_MASK);
- assert((read_csr(dut, CSR_MEDELEG) & MEDELEG_READ_ONLY_ONE_MASK) == MEDELEG_READ_ONLY_ONE_MASK);
- assert((read_csr(dut, CSR_MEDELEG) & (1ULL << 11)) == 0ULL);
- csr_op(dut, CSR_MEDELEG, CSR_OP_WRITE, 0ULL);
- assert((read_csr(dut, CSR_MEDELEG) & MEDELEG_READ_ONLY_ONE_MASK) == MEDELEG_READ_ONLY_ONE_MASK);
- csr_op(dut, CSR_MIDELEG, CSR_OP_WRITE, ~0ULL);
- assert((read_csr(dut, CSR_MIDELEG) & MIDELEG_WRITABLE_MASK) == MIDELEG_WRITABLE_MASK);
- assert((read_csr(dut, CSR_MIDELEG) & (1ULL << INTERRUPT_M_TIMER)) == 0ULL);
- std::cout << "[PASS] Delegation CSRs expose only writable standard bits" << std::endl;
-
- // Basic supervisor CSRs touching for completeness
- const uint64_t s_interrupt_mask = (1ULL << INTERRUPT_S_SOFTWARE) |
- (1ULL << INTERRUPT_S_TIMER) |
- (1ULL << INTERRUPT_S_EXTERNAL);
- csr_op(dut, CSR_SIE, CSR_OP_WRITE, ~0ULL);
- csr_op(dut, CSR_SSCRATCH, CSR_OP_WRITE, 0xAAULL);
- csr_op(dut, CSR_SEPC, CSR_OP_WRITE, 0x1234ULL);
- csr_op(dut, CSR_SCAUSE, CSR_OP_WRITE, 0x21ULL);
- csr_op(dut, CSR_SIP, CSR_OP_WRITE, ~0ULL);
- csr_op(dut, CSR_SATP, CSR_OP_WRITE, 0x8000000000000000ULL);
- assert(read_csr(dut, CSR_SIE) == s_interrupt_mask);
- assert(read_csr(dut, CSR_SSCRATCH) == 0xAAULL);
- assert(read_csr(dut, CSR_SEPC) == 0x1234ULL);
- assert(read_csr(dut, CSR_SCAUSE) == 0x21ULL);
- assert(read_csr(dut, CSR_SIP) == s_interrupt_mask);
- assert(read_csr(dut, CSR_SATP) == 0x8000000000000000ULL);
- std::cout << "[PASS] Supervisor space CSR read/write" << std::endl;
-
- csr_op(dut, CSR_MIE, CSR_OP_WRITE, 0ULL);
- csr_op(dut, CSR_SIE, CSR_OP_WRITE, (1ULL << INTERRUPT_S_TIMER));
- assert(read_csr(dut, CSR_SIE) == (1ULL << INTERRUPT_S_TIMER));
- assert(read_csr(dut, CSR_MIE) == (1ULL << INTERRUPT_S_TIMER));
-
- csr_op(dut, CSR_MIP, CSR_OP_WRITE, 0ULL);
- dut->s_timer_interrupt = 1;
- dut->eval();
- assert(read_csr(dut, CSR_SIP) == (1ULL << INTERRUPT_S_TIMER));
- assert(read_csr(dut, CSR_MIP) == (1ULL << INTERRUPT_S_TIMER));
- dut->s_timer_interrupt = 0;
-
- dut->m_external_interrupt = 1;
- dut->eval();
- assert((read_csr(dut, CSR_MIP) & (1ULL << INTERRUPT_M_EXTERNAL)) != 0ULL);
- assert((read_csr(dut, CSR_SIP) & (1ULL << INTERRUPT_M_EXTERNAL)) == 0ULL);
- dut->m_external_interrupt = 0;
- std::cout << "[PASS] Interrupt pending and enable CSRs expose delegated views" << std::endl;
-
- // Writing SSTATUS should mirror MSTATUS subset bits
- csr_op(dut, CSR_SSTATUS, CSR_OP_WRITE, (1ULL << SSTATUS_SIE_BIT));
- assert(((read_csr(dut, CSR_MSTATUS) >> SSTATUS_SIE_BIT) & 1ULL) == 1ULL);
- csr_op(dut, CSR_SSTATUS, CSR_OP_WRITE, 0ULL);
- assert(((read_csr(dut, CSR_MSTATUS) >> SSTATUS_SIE_BIT) & 1ULL) == 0ULL);
- std::cout << "[PASS] SSTATUS write mirrors MSTATUS" << std::endl;
-
- // Floating-point CSR block
- csr_op(dut, CSR_FFLAGS, CSR_OP_WRITE, 0x1FULL);
- assert((read_csr(dut, CSR_FFLAGS) & 0x1FULL) == 0x1FULL);
- csr_op(dut, CSR_FRM, CSR_OP_WRITE, 0x7ULL);
- assert((read_csr(dut, CSR_FRM) & 0x7ULL) == 0x7ULL);
- csr_op(dut, CSR_FCSR, CSR_OP_WRITE, 0xABULL);
- assert((read_csr(dut, CSR_FFLAGS) & 0x1FULL) == (0xABULL & 0x1FULL));
- assert((read_csr(dut, CSR_FRM) & 0x7ULL) == ((0xABULL >> 5) & 0x7ULL));
- std::cout << "[PASS] FCSR/FM/FFLAGS read/write" << std::endl;
-
- std::cout << "[INFO] Extending CSR trap coverage" << std::endl;
-
- run_csr_scenario(dut, "interrupt_priority_matrix", [&](Vcsr_unit* target) {
- struct PriorityScenario {
- const char* name;
- bool ext_external;
- bool ext_software;
- bool ext_timer;
- bool sync_illegal;
- uint64_t mepc;
- uint64_t mtval;
- uint64_t expected_cause;
- };
-
- const std::array<PriorityScenario, 5> suite = {{
- {"external_over_others", true, true, true, true, 0xA000ULL, 0x1111ULL, INTERRUPT_BIT | INTERRUPT_M_EXTERNAL},
- {"software_over_timer", false, true, true, true, 0xA040ULL, 0x2222ULL, INTERRUPT_BIT | INTERRUPT_M_SOFTWARE},
- {"timer_over_sync", false, false, true, true, 0xA080ULL, 0x3333ULL, INTERRUPT_BIT | INTERRUPT_M_TIMER},
- {"interrupt_only", true, false, false, false, 0xA0C0ULL, 0x0ULL, INTERRUPT_BIT | INTERRUPT_M_EXTERNAL},
- {"sync_fallback", false, false, false, true, 0xA100ULL, 0x4444ULL, EXC_INST_ILLEGAL}
- }};
-
- for (const auto& scenario : suite) {
- bool chose_interrupt = false;
- const uint64_t chosen_cause = arbitrate_trap_priority(
- scenario.ext_external,
- scenario.ext_software,
- scenario.ext_timer,
- scenario.sync_illegal,
- EXC_INST_ILLEGAL,
- chose_interrupt
- );
- assert(chosen_cause == scenario.expected_cause);
- trigger_trap(
- target,
- scenario.mepc,
- chosen_cause,
- scenario.sync_illegal ? scenario.mtval : 0ULL,
- false
- );
- const uint64_t mcause = read_csr(target, CSR_MCAUSE);
- assert(mcause == scenario.expected_cause);
- assert(read_csr(target, CSR_MEPC) == scenario.mepc);
- const bool mcause_is_interrupt = (mcause & INTERRUPT_BIT) != 0ULL;
- assert(mcause_is_interrupt == chose_interrupt);
- if (scenario.sync_illegal && !mcause_is_interrupt) {
- assert(read_csr(target, CSR_MTVAL) == scenario.mtval);
- }
- assert(target->csr_priv_mode == PRIV_ENCODE_M);
- }
- });
-
- run_csr_scenario(dut, "delegated_multitrap_context", [&](Vcsr_unit* target) {
- csr_op(target, CSR_MEDELEG, CSR_OP_WRITE, 0ULL);
- csr_op(target, CSR_MEDELEG, CSR_OP_SET, (1ULL << EXC_ECALL_FROM_U));
- csr_op(target, CSR_STVEC, CSR_OP_WRITE, 0x4000ULL);
- csr_op(target, CSR_MTVEC, CSR_OP_WRITE, 0x8000ULL);
-
- force_priv_mode_via_mret(target, PRIV_ENCODE_S);
- assert(target->csr_priv_mode == PRIV_ENCODE_S);
-
- const uint64_t delegated_pc = 0x900ULL;
- trigger_trap(target, delegated_pc, EXC_ECALL_FROM_U, 0ULL, true);
- assert(target->csr_trap_to_s_mode);
- assert(target->csr_trap_vector == 0x4000ULL);
- assert(read_csr(target, CSR_SEPC) == delegated_pc);
- assert(read_csr(target, CSR_SCAUSE) == EXC_ECALL_FROM_U);
- assert(target->csr_priv_mode == PRIV_ENCODE_S);
-
- const uint64_t machine_pc = 0xA00ULL;
- trigger_trap(target, machine_pc, EXC_INST_ILLEGAL, 0ULL, false);
- assert(!target->csr_trap_to_s_mode);
- assert(target->csr_trap_vector == 0x8000ULL);
- assert(read_csr(target, CSR_MEPC) == machine_pc);
- assert(read_csr(target, CSR_SEPC) == delegated_pc);
- assert(target->csr_priv_mode == PRIV_ENCODE_M);
- });
-
- run_csr_scenario(dut, "nested_return_sequence", [&](Vcsr_unit* target) {
- force_priv_mode_via_mret(target, PRIV_ENCODE_S);
- csr_op(target, CSR_SSTATUS, CSR_OP_WRITE, (1ULL << SSTATUS_SIE_BIT));
- force_priv_mode_via_mret(target, PRIV_ENCODE_U);
-
- const uint64_t first_trap_pc = 0xB00ULL;
- trigger_trap(target, first_trap_pc, EXC_ECALL_FROM_U, 0ULL, true);
- uint64_t sstatus_snapshot = read_csr(target, CSR_SSTATUS);
- assert(((sstatus_snapshot >> SSTATUS_SIE_BIT) & 1ULL) == 0ULL);
- assert(((sstatus_snapshot >> SSTATUS_SPIE_BIT) & 1ULL) == 1ULL);
- assert(((sstatus_snapshot >> SSTATUS_SPP_BIT) & 1ULL) == 0ULL);
- assert(read_csr(target, CSR_SEPC) == first_trap_pc);
-
- target->trap_ctrl_sret_en = 1;
- tick(target);
- target->trap_ctrl_sret_en = 0;
- uint64_t sstatus_after_sret = read_csr(target, CSR_SSTATUS);
- assert(((sstatus_after_sret >> SSTATUS_SIE_BIT) & 1ULL) == 1ULL);
- assert(((sstatus_after_sret >> SSTATUS_SPIE_BIT) & 1ULL) == 1ULL);
- assert(target->csr_priv_mode == PRIV_ENCODE_U);
-
- // Enable machine interrupts so the subsequent machine trap captures pre-trap MIE=1
- force_priv_mode_via_mret(target, PRIV_ENCODE_M);
- csr_op(target, CSR_MSTATUS, CSR_OP_SET, (1ULL << MSTATUS_MIE_BIT));
- force_priv_mode_via_mret(target, PRIV_ENCODE_U);
- const uint64_t second_trap_pc = 0xB80ULL;
- trigger_trap(target, second_trap_pc, EXC_INST_ILLEGAL, 0ULL, false);
- uint64_t mstatus_snapshot = read_csr(target, CSR_MSTATUS);
- assert(((mstatus_snapshot >> MSTATUS_MIE_BIT) & 1ULL) == 0ULL);
- assert(((mstatus_snapshot >> MSTATUS_MPIE_BIT) & 1ULL) == 1ULL);
- assert(((mstatus_snapshot >> MSTATUS_MPP_LO) & 0x3ULL) == PRIV_ENCODE_U);
- assert(read_csr(target, CSR_MEPC) == second_trap_pc);
-
- target->trap_ctrl_mret_en = 1;
- tick(target);
- target->trap_ctrl_mret_en = 0;
- uint64_t mstatus_after_mret = read_csr(target, CSR_MSTATUS);
- assert(((mstatus_after_mret >> MSTATUS_MIE_BIT) & 1ULL) == 1ULL);
- assert(((mstatus_after_mret >> MSTATUS_MPIE_BIT) & 1ULL) == 1ULL);
- assert(((mstatus_after_mret >> MSTATUS_MPP_LO) & 0x3ULL) == PRIV_ENCODE_U);
- assert(target->csr_priv_mode == PRIV_ENCODE_U);
- });
-
- run_csr_scenario(dut, "illegal_csr_access_guard", [&](Vcsr_unit* target) {
- force_priv_mode_via_mret(target, PRIV_ENCODE_M);
- assert(target->csr_priv_mode == PRIV_ENCODE_M);
- const uint64_t vendor_snapshot = read_csr(target, CSR_MVENDORID);
- target->csr_addr = CSR_MVENDORID;
- target->csr_op = CSR_OP_WRITE;
- target->csr_rs1_data = 0x1234ULL;
- target->csr_req = 1;
- tick(target);
- target->csr_req = 0;
- target->csr_op = 0;
- target->csr_rs1_data = 0;
- target->csr_addr = 0;
- assert(target->csr_illegal_access);
- assert(read_csr(target, CSR_MVENDORID) == vendor_snapshot);
- assert(!target->csr_trap_to_s_mode);
- assert(target->csr_priv_mode == PRIV_ENCODE_M);
- assert(target->csr_trap_vector_next == read_csr(target, CSR_MTVEC));
- tick(target);
- assert(!target->csr_illegal_access);
-
- force_priv_mode_via_mret(target, PRIV_ENCODE_S);
- assert(target->csr_priv_mode == PRIV_ENCODE_S);
- target->csr_addr = CSR_MSTATUS;
- target->csr_op = CSR_OP_WRITE;
- target->csr_rs1_data = 0ULL;
- target->csr_req = 1;
- target->eval();
- assert(target->csr_illegal_access);
- tick(target);
- target->csr_req = 0;
- target->csr_op = 0;
- target->csr_rs1_data = 0;
- target->csr_addr = 0;
- target->eval();
- assert(!target->csr_illegal_access);
-
- force_priv_mode_via_mret(target, PRIV_ENCODE_M);
- csr_op(target, CSR_MCOUNTEREN, CSR_OP_WRITE, 0ULL);
- force_priv_mode_via_mret(target, PRIV_ENCODE_S);
- target->csr_addr = CSR_CYCLE;
- target->csr_op = 0;
- target->csr_rs1_data = 0ULL;
- target->csr_req = 1;
- target->eval();
- assert(target->csr_illegal_access);
- tick(target);
- target->csr_req = 0;
- target->csr_addr = 0;
- target->eval();
-
- force_priv_mode_via_mret(target, PRIV_ENCODE_M);
- csr_op(target, CSR_MCOUNTEREN, CSR_OP_WRITE, 1ULL);
- csr_op(target, CSR_SCOUNTEREN, CSR_OP_WRITE, 0ULL);
- force_priv_mode_via_mret(target, PRIV_ENCODE_U);
- target->csr_addr = CSR_CYCLE;
- target->csr_op = 0;
- target->csr_rs1_data = 0ULL;
- target->csr_req = 1;
- target->eval();
- assert(target->csr_illegal_access);
- tick(target);
- target->csr_req = 0;
- target->csr_addr = 0;
- target->eval();
-
- force_priv_mode_via_mret(target, PRIV_ENCODE_M);
- csr_op(target, CSR_MSTATEEN0, CSR_OP_WRITE, 0ULL);
- force_priv_mode_via_mret(target, PRIV_ENCODE_S);
- target->csr_addr = CSR_SENVCFG;
- target->csr_op = CSR_OP_WRITE;
- target->csr_rs1_data = 1ULL;
- target->csr_req = 1;
- target->eval();
- assert(target->csr_illegal_access);
- tick(target);
- target->csr_req = 0;
- target->csr_op = 0;
- target->csr_rs1_data = 0;
- target->csr_addr = 0;
- target->eval();
-
- force_priv_mode_via_mret(target, PRIV_ENCODE_M);
- csr_op(target, CSR_MSTATEEN0, CSR_OP_WRITE, (1ULL << STATEEN0_ENVCFG_BIT));
- force_priv_mode_via_mret(target, PRIV_ENCODE_S);
- csr_op(target, CSR_SENVCFG, CSR_OP_WRITE, 1ULL);
- assert((read_csr(target, CSR_SENVCFG) & 1ULL) == 1ULL);
- });
-
- run_csr_scenario(dut, "delegation_cross_over", [&](Vcsr_unit* target) {
- csr_op(target, CSR_MTVEC, CSR_OP_WRITE, 0x1000ULL);
- csr_op(target, CSR_STVEC, CSR_OP_WRITE, 0x2000ULL);
- csr_op(target, CSR_MEDELEG, CSR_OP_WRITE, 0ULL);
- csr_op(target, CSR_MIDELEG, CSR_OP_WRITE, 0ULL);
-
- struct DelegationProbe {
- const char* name;
- bool is_interrupt;
- uint64_t cause;
- bool delegate_to_s;
- uint64_t pc;
- };
-
- const std::array<DelegationProbe, 4> probes = {{
- {"medeleg_set", false, EXC_ECALL_FROM_U, true, 0xD000ULL},
- {"medeleg_clear", false, EXC_ECALL_FROM_U, false, 0xD040ULL},
- {"mideleg_set", true, INTERRUPT_S_TIMER, true, 0xD080ULL},
- {"mideleg_clear", true, INTERRUPT_S_TIMER, false, 0xD0C0ULL}
- }};
-
- for (const auto& probe : probes) {
- if (probe.is_interrupt) {
- const uint64_t mask = (1ULL << probe.cause);
- if (probe.delegate_to_s) {
- csr_op(target, CSR_MIDELEG, CSR_OP_SET, mask);
- } else {
- csr_op(target, CSR_MIDELEG, CSR_OP_CLEAR, mask);
- }
- force_priv_mode_via_mret(target, PRIV_ENCODE_S);
- trigger_trap(target, probe.pc, (INTERRUPT_BIT | probe.cause), 0ULL, probe.delegate_to_s);
- if (probe.delegate_to_s) {
- assert(target->csr_trap_to_s_mode);
- assert(read_csr(target, CSR_SEPC) == probe.pc);
- } else {
- assert(!target->csr_trap_to_s_mode);
- assert(read_csr(target, CSR_MEPC) == probe.pc);
- }
- } else {
- const uint64_t mask = (1ULL << probe.cause);
- if (probe.delegate_to_s) {
- csr_op(target, CSR_MEDELEG, CSR_OP_SET, mask);
- } else {
- csr_op(target, CSR_MEDELEG, CSR_OP_CLEAR, mask);
- }
- force_priv_mode_via_mret(target, PRIV_ENCODE_U);
- trigger_trap(target, probe.pc, probe.cause, 0ULL, probe.delegate_to_s);
- if (probe.delegate_to_s) {
- assert(target->csr_trap_to_s_mode);
- assert(read_csr(target, CSR_SEPC) == probe.pc);
- } else {
- assert(!target->csr_trap_to_s_mode);
- assert(read_csr(target, CSR_MEPC) == probe.pc);
- }
- }
-
- const uint64_t expected_vector = probe.delegate_to_s ? read_csr(target, CSR_STVEC)
- : read_csr(target, CSR_MTVEC);
- assert(target->csr_trap_vector == expected_vector);
- }
- });
-
- std::cout << "----------------------------------\n";
- std::cout << "All CSR unit tests passed!\n";
- std::cout << "----------------------------------\n";
-
- delete dut;
- return 0;
-}
diff --git a/rv64cpu/tb/unit/tb_decoder.cc b/rv64cpu/tb/unit/tb_decoder.cc
deleted file mode 100644
index 1dcfb82..0000000
--- a/rv64cpu/tb/unit/tb_decoder.cc
+++ /dev/null
@@ -1,353 +0,0 @@
-// 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 <https://www.gnu.org/licenses/>.
-
-// File path: tb/unit/tb_decoder.cc
-// Description: Decoder unit test
-
-#include <iostream>
-#include <iomanip>
-#include <cstdint>
-#include <cstdlib>
-#include <verilated.h>
-#include "Vdecoder.h"
-
-static int test_count = 0;
-static int fail_count = 0;
-
-#define CHECK(dut, signal, expected, test_name) \
- do { \
- uint64_t _got = (uint64_t)(dut->signal); \
- uint64_t _exp = (uint64_t)(expected); \
- if (_got != _exp) { \
- std::cerr << "\033[31m[FAIL] " << test_name \
- << " | " << #signal << " expected: 0x" \
- << std::hex << _exp << " got: 0x" << _got \
- << std::dec << "\033[0m" << std::endl; \
- fail_count++; \
- } \
- } while(0)
-
-void decode(Vdecoder* dut, uint32_t instr, const char* name, uint8_t priv_mode = 3) {
- dut->priv_mode = priv_mode & 0x3;
- dut->instr = instr;
- dut->eval();
- test_count++;
- std::cout << "\033[32m[PASS] [TEST " << std::setw(2) << test_count << "] " << name
- << " (0x" << std::hex << std::setw(8) << std::setfill('0')
- << instr << std::dec << std::setfill(' ') << ")\033[0m" << std::endl;
-}
-
-int main(int argc, char** argv) {
- Verilated::commandArgs(argc, argv);
- Vdecoder* dut = new Vdecoder;
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << " Decoder Unit Test" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- // 1. R-type: ADD x1, x2, x3
- // funct7=0000000 rs2=00011 rs1=00010 funct3=000 rd=00001 op=0110011
- decode(dut, 0x003100B3, "ADD x1, x2, x3");
- CHECK(dut, rs1_addr, 2, "ADD");
- CHECK(dut, rs2_addr, 3, "ADD");
- CHECK(dut, rd_addr, 1, "ADD");
- CHECK(dut, reg_write_en, 1, "ADD"); // R-type writes rd
- CHECK(dut, alu_op, 0, "ADD"); // ALU_ADD = 0b0000
- CHECK(dut, alu_src1_sel, 0, "ADD"); // operand1 = rs1
- CHECK(dut, alu_src2_sel, 0, "ADD"); // operand2 = rs2
- CHECK(dut, wb_sel, 0, "ADD"); // writeback from ALU
- CHECK(dut, illegal_instr,0, "ADD");
-
- // 2. R-type: SUB x3, x1, x2
- // funct7=0100000 rs2=00010 rs1=00001 funct3=000 rd=00011 op=0110011
- // Tests: funct7[5] distinguishes ADD vs SUB
- decode(dut, 0x402081B3, "SUB x3, x1, x2");
- CHECK(dut, alu_op, 8, "SUB"); // ALU_SUB = 0b1000
- CHECK(dut, reg_write_en, 1, "SUB");
- CHECK(dut, alu_src2_sel, 0, "SUB"); // operand2 = rs2
-
- // 3. R-type: SRA x1, x2, x3 (vs SRL)
- // funct7=0100000 rs2=00011 rs1=00010 funct3=101 rd=00001 op=0110011
- // Tests: funct7[5] distinguishes SRL vs SRA for funct3=101
- decode(dut, 0x403150B3, "SRA x1, x2, x3");
- CHECK(dut, alu_op, 13, "SRA"); // ALU_SRA = 0b1101
-
- // 4. R-type: SRL x1, x2, x3
- // funct7=0000000 rs2=00011 rs1=00010 funct3=101 rd=00001 op=0110011
- decode(dut, 0x003150B3, "SRL x1, x2, x3");
- CHECK(dut, alu_op, 5, "SRL"); // ALU_SRL = 0b0101
-
- // 5. I-type ALU: ADDI x1, x0, 42
- // imm=000000101010 rs1=00000 funct3=000 rd=00001 op=0010011
- // Tests: I-type sets alu_src2_sel=1 (immediate), reg_write=1
- decode(dut, 0x02A00093, "ADDI x1, x0, 42");
- CHECK(dut, reg_write_en, 1, "ADDI");
- CHECK(dut, alu_op, 0, "ADDI"); // ALU_ADD
- CHECK(dut, alu_src1_sel, 0, "ADDI"); // rs1
- CHECK(dut, alu_src2_sel, 1, "ADDI"); // immediate
- CHECK(dut, imm, 42, "ADDI");
- CHECK(dut, rd_addr, 1, "ADDI");
- CHECK(dut, rs1_addr, 0, "ADDI");
-
- // 6. I-type ALU: ADDI x1, x0, -1 (negative immediate sign extension)
- // imm=111111111111 rs1=00000 funct3=000 rd=00001 op=0010011
- // Tests: 12-bit immediate sign-extended to 64-bit
- decode(dut, 0xFFF00093, "ADDI x1, x0, -1");
- CHECK(dut, imm, (uint64_t)-1, "ADDI -1"); // 0xFFFFFFFFFFFFFFFF
-
- // 7. I-type ALU: SRAI x1, x2, 3
- // imm=010000_000011 rs1=00010 funct3=101 rd=00001 op=0010011
- // Tests: funct7[5]=1 selects SRA for funct3=101 in OP_IMM
- decode(dut, 0x40315093, "SRAI x1, x2, 3");
- CHECK(dut, alu_op, 13, "SRAI"); // ALU_SRA = 0b1101
- CHECK(dut, alu_src2_sel, 1, "SRAI"); // immediate (shamt)
-
- // 8. Load: LD x5, 8(x10)
- // imm=000000001000 rs1=01010 funct3=011 rd=00101 op=0000011
- // Tests: is_load, mem_size, wb_sel=MEM, alu computes address
- decode(dut, 0x00853283, "LD x5, 8(x10)");
- CHECK(dut, is_load, 1, "LD");
- CHECK(dut, reg_write_en, 1, "LD");
- CHECK(dut, alu_src2_sel, 1, "LD"); // imm for address calc
- CHECK(dut, imm, 8, "LD");
- CHECK(dut, mem_size, 3, "LD"); // funct3=011 → doubleword
- CHECK(dut, wb_sel, 1, "LD"); // writeback from MEM
- CHECK(dut, alu_op, 0, "LD"); // ALU_ADD for addr
-
- // 9. Store: SD x5, 16(x10)
- // imm[11:5]=0000000 rs2=00101 rs1=01010 funct3=011 imm[4:0]=10000 op=0100011
- // Tests: is_store, reg_write=0, S-type immediate extraction
- decode(dut, 0x00553823, "SD x5, 16(x10)");
- CHECK(dut, is_store, 1, "SD");
- CHECK(dut, reg_write_en, 0, "SD"); // stores don't write rd
- CHECK(dut, alu_src2_sel, 1, "SD"); // imm for address calc (NOT rs2)
- CHECK(dut, imm, 16, "SD"); // S-type immediate = 16
- CHECK(dut, mem_size, 3, "SD"); // funct3=011 → doubleword
- CHECK(dut, rs1_addr, 10, "SD"); // base register
- CHECK(dut, rs2_addr, 5, "SD"); // data to store
-
- // 10. Branch: BEQ x1, x2, -4
- // Tests: is_branch, branch_op=funct3, negative B-type immediate
- decode(dut, 0xFE208EE3, "BEQ x1, x2, -4");
- CHECK(dut, is_branch, 1, "BEQ");
- CHECK(dut, reg_write_en, 0, "BEQ");
- CHECK(dut, alu_src1_sel, 1, "BEQ"); // PC for target calc
- CHECK(dut, branch_op, 0, "BEQ"); // funct3=000 → BEQ
- CHECK(dut, imm, (uint64_t)(-4), "BEQ"); // sign-extended -4
-
- // 11. Branch: BNE x1, x2, +8
- // imm[12]=0 imm[10:5]=000000 rs2=00010 rs1=00001 funct3=001
- // imm[4:1]=0100 imm[11]=0 op=1100011
- // Tests: branch_op correctly passes funct3 for BNE
- decode(dut, 0x00209463, "BNE x1, x2, +8");
- CHECK(dut, is_branch, 1, "BNE");
- CHECK(dut, branch_op, 1, "BNE"); // funct3=001 → BNE
- CHECK(dut, imm, 8, "BNE");
-
- // 12. LUI x1, 0x12345
- // imm[31:12]=0x12345 rd=00001 op=0110111
- // Tests: U-type immediate, reg_write, alu_src2_sel=imm
- decode(dut, 0x123450B7, "LUI x1, 0x12345");
- CHECK(dut, reg_write_en, 1, "LUI");
- CHECK(dut, alu_src2_sel, 1, "LUI"); // immediate
- CHECK(dut, imm, 0x12345000ULL, "LUI"); // U-type: imm << 12
- CHECK(dut, wb_sel, 0, "LUI"); // ALU result
- CHECK(dut, rd_addr, 1, "LUI");
-
- // 13. AUIPC x2, 0x00001
- // imm[31:12]=0x00001 rd=00010 op=0010111
- // Tests: alu_src1_sel=PC (distinguishes from LUI)
- decode(dut, 0x00001117, "AUIPC x2, 0x1");
- CHECK(dut, reg_write_en, 1, "AUIPC");
- CHECK(dut, alu_src1_sel, 1, "AUIPC"); // PC ← key difference from LUI
- CHECK(dut, alu_src2_sel, 1, "AUIPC"); // immediate
- CHECK(dut, imm, 0x1000ULL, "AUIPC"); // 0x00001 << 12 = 0x1000
-
- // 14. JAL x1, +8
- // J-type imm encoding: imm[20|10:1|11|19:12]
- // offset=8 → imm[20]=0 imm[10:1]=0000000100 imm[11]=0 imm[19:12]=00000000
- // rd=00001 op=1101111
- // Tests: is_jump, wb_sel=PC+4, alu_src1=PC
- decode(dut, 0x008000EF, "JAL x1, +8");
- CHECK(dut, is_jump, 1, "JAL");
- CHECK(dut, reg_write_en, 1, "JAL");
- CHECK(dut, alu_src1_sel, 1, "JAL"); // PC
- CHECK(dut, alu_src2_sel, 1, "JAL"); // immediate
- CHECK(dut, wb_sel, 2, "JAL"); // writeback PC+4
- CHECK(dut, imm, 8, "JAL");
-
- // 15. JALR x1, x5, 4
- // imm=000000000100 rs1=00101 funct3=000 rd=00001 op=1100111
- // Tests: is_jump, alu_src1=rs1 (distinguishes from JAL)
- decode(dut, 0x004280E7, "JALR x1, x5, 4");
- CHECK(dut, is_jump, 1, "JALR");
- CHECK(dut, reg_write_en, 1, "JALR");
- CHECK(dut, alu_src1_sel, 0, "JALR"); // rs1 ← key difference from JAL
- CHECK(dut, alu_src2_sel, 1, "JALR"); // immediate
- CHECK(dut, wb_sel, 2, "JALR"); // writeback PC+4
- CHECK(dut, imm, 4, "JALR");
- CHECK(dut, rs1_addr, 5, "JALR");
-
- // 16. ADDIW x1, x2, 10 (RV64 word-width I-type)
- // imm=000000001010 rs1=00010 funct3=000 rd=00001 op=0011011
- // Tests: is_word_op flag for 32-bit operation
- decode(dut, 0x00A1009B, "ADDIW x1, x2, 10");
- CHECK(dut, reg_write_en, 1, "ADDIW");
- CHECK(dut, is_word_op, 1, "ADDIW"); // 32-bit + sext
- CHECK(dut, alu_op, 0, "ADDIW"); // ALU_ADD
- CHECK(dut, alu_src2_sel, 1, "ADDIW"); // immediate
- CHECK(dut, imm, 10, "ADDIW");
-
- // 17. ADDW x3, x1, x2 (RV64 word-width R-type)
- // funct7=0000000 rs2=00010 rs1=00001 funct3=000 rd=00011 op=0111011
- // Tests: OP_32 path, is_word_op + R-type (alu_src2=rs2)
- decode(dut, 0x002081BB, "ADDW x3, x1, x2");
- CHECK(dut, reg_write_en, 1, "ADDW");
- CHECK(dut, is_word_op, 1, "ADDW");
- CHECK(dut, alu_op, 0, "ADDW"); // ALU_ADD
- CHECK(dut, alu_src2_sel, 0, "ADDW"); // rs2 (R-type!)
-
- // 18. SUBW x3, x1, x2 (RV64 word-width R-type with funct7[5]=1)
- // funct7=0100000 rs2=00010 rs1=00001 funct3=000 rd=00011 op=0111011
- decode(dut, 0x402081BB, "SUBW x3, x1, x2");
- CHECK(dut, alu_op, 8, "SUBW"); // ALU_SUB
- CHECK(dut, is_word_op, 1, "SUBW");
-
- decode(dut, 0x023100B3, "MUL x1, x2, x3");
- CHECK(dut, alu_op, 16, "MUL");
- CHECK(dut, reg_write_en, 1, "MUL");
- CHECK(dut, illegal_instr,0, "MUL");
-
- decode(dut, 0x023140B3, "DIV x1, x2, x3");
- CHECK(dut, alu_op, 20, "DIV");
- CHECK(dut, illegal_instr,0, "DIV");
-
- decode(dut, 0x023160B3, "REM x1, x2, x3");
- CHECK(dut, alu_op, 22, "REM");
- CHECK(dut, illegal_instr,0, "REM");
-
- decode(dut, 0x023100BB, "MULW x1, x2, x3");
- CHECK(dut, alu_op, 16, "MULW");
- CHECK(dut, is_word_op, 1, "MULW");
- CHECK(dut, illegal_instr,0, "MULW");
-
- decode(dut, 0x0021A0AF, "AMOADD.W x1, x2, (x3)");
- CHECK(dut, is_amo, 1, "AMOADD.W");
- CHECK(dut, amo_op, 4, "AMOADD.W");
- CHECK(dut, mem_size, 2, "AMOADD.W");
- CHECK(dut, wb_sel, 1, "AMOADD.W");
- CHECK(dut, reg_write_en, 1, "AMOADD.W");
- CHECK(dut, illegal_instr,0, "AMOADD.W");
-
- decode(dut, 0x100332AF, "LR.D x5, (x6)");
- CHECK(dut, is_amo, 1, "LR.D");
- CHECK(dut, amo_op, 1, "LR.D");
- CHECK(dut, mem_size, 3, "LR.D");
- CHECK(dut, rs2_addr, 0, "LR.D");
- CHECK(dut, illegal_instr,0, "LR.D");
-
- decode(dut, 0x1884B3AF, "SC.D x7, x8, (x9)");
- CHECK(dut, is_amo, 1, "SC.D");
- CHECK(dut, amo_op, 2, "SC.D");
- CHECK(dut, mem_size, 3, "SC.D");
- CHECK(dut, illegal_instr,0, "SC.D");
-
- decode(dut, 0x0EB6252F, "AMOSWAP.W.AQRL x10, x11, (x12)");
- CHECK(dut, is_amo, 1, "AMOSWAP.W.AQRL");
- CHECK(dut, amo_op, 3, "AMOSWAP.W.AQRL");
- CHECK(dut, illegal_instr,0, "AMOSWAP.W.AQRL");
-
- decode(dut, 0x105332AF, "Illegal LR.D with rs2 != x0");
- CHECK(dut, is_amo, 0, "Illegal LR.D");
- CHECK(dut, reg_write_en, 0, "Illegal LR.D");
- CHECK(dut, illegal_instr,1, "Illegal LR.D");
-
- // 19. CSRRW x1, mstatus(0x300), x2
- // imm=001100000000 rs1=00010 funct3=001 rd=00001 op=1110011
- // Tests: is_csr, csr_op=funct3, reg_write=1
- decode(dut, 0x300110F3, "CSRRW x1, mstatus, x2");
- CHECK(dut, is_csr, 1, "CSRRW");
- CHECK(dut, csr_op, 1, "CSRRW"); // funct3=001 → CSRRW
- CHECK(dut, reg_write_en, 1, "CSRRW");
- CHECK(dut, illegal_instr,0, "CSRRW");
-
- decode(dut, 0x300110F3, "CSRRW x1, mstatus, x2 (U-mode)", 0);
- CHECK(dut, illegal_instr,1, "CSRRW U-mode illegal");
- CHECK(dut, is_csr, 0, "CSRRW U-mode is_csr cleared");
-
- // 20. ECALL
- // all-zero fields + opcode=1110011
- decode(dut, 0x00000073, "ECALL");
- CHECK(dut, is_ecall, 1, "ECALL");
- CHECK(dut, reg_write_en, 0, "ECALL");
- CHECK(dut, illegal_instr,0, "ECALL");
-
- // 21. EBREAK
- // funct7=0000000 rs2=00001 rs1=00000 funct3=000 rd=00000 op=1110011
- decode(dut, 0x00100073, "EBREAK");
- CHECK(dut, is_ebreak, 1, "EBREAK");
- CHECK(dut, reg_write_en, 0, "EBREAK");
-
- // 22. MRET
- // funct7=0011000 rs2=00010 rs1=00000 funct3=000 rd=00000 op=1110011
- // Tests: privileged instruction decode from {funct7, rs2}
- decode(dut, 0x30200073, "MRET");
- CHECK(dut, is_mret, 1, "MRET");
- CHECK(dut, reg_write_en, 0, "MRET");
- CHECK(dut, illegal_instr,0, "MRET");
-
- // 23. SRET
- // funct7=0001000 rs2=00010 rs1=00000 funct3=000 rd=00000 op=1110011
- decode(dut, 0x10200073, "SRET", 1);
- CHECK(dut, is_sret, 1, "SRET");
- CHECK(dut, reg_write_en, 0, "SRET");
-
- // 24. WFI
- // funct7=0001000 rs2=00101 rs1=00000 funct3=000 rd=00000 op=1110011
- decode(dut, 0x10500073, "WFI");
- CHECK(dut, is_wfi, 1, "WFI");
-
- // 25. FENCE
- // pred=1111 succ=1111 rs1=00000 funct3=000 rd=00000 op=0001111
- // Tests: MISC_MEM path, is_fence flag
- decode(dut, 0x0FF0000F, "FENCE");
- CHECK(dut, is_fence, 1, "FENCE");
- CHECK(dut, illegal_instr,0, "FENCE");
-
- // 26. FENCE.I (Zifencei)
- // funct3=001 op=0001111
- // Tests: is_fence_i flag (requires I-Cache flush in pipeline)
- decode(dut, 0x0000100F, "FENCE.I");
- CHECK(dut, is_fence_i, 1, "FENCE.I");
-
- // 27. Illegal instruction (opcode=0000000)
- // Tests: default case sets illegal_instr, reg_write stays 0
- decode(dut, 0x00000000, "Illegal (all-zero)");
- CHECK(dut, illegal_instr,1, "Illegal");
- CHECK(dut, reg_write_en, 0, "Illegal");
-
- // Results
- std::cout << "----------------------------------" << std::endl;
- if (fail_count == 0) {
- std::cout << "\033[32mAll " << test_count << " tests passed!\033[0m" << std::endl;
- } else {
- std::cout << "\033[31m" << fail_count << " check(s) FAILED out of "
- << test_count << " tests.\033[0m" << std::endl;
- }
- std::cout << "----------------------------------" << std::endl;
-
- delete dut;
- return (fail_count > 0) ? 1 : 0;
-}
diff --git a/rv64cpu/tb/unit/tb_ex_mem_reg.cc b/rv64cpu/tb/unit/tb_ex_mem_reg.cc
deleted file mode 100644
index 75e5502..0000000
--- a/rv64cpu/tb/unit/tb_ex_mem_reg.cc
+++ /dev/null
@@ -1,182 +0,0 @@
-// 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 <https://www.gnu.org/licenses/>.
-
-// File path: tb/unit/tb_ex_mem_reg.cc
-// Description: Execution/Memory Stage Pipeline Register unit test
-
-#include <iostream>
-#include <cassert>
-#include <verilated.h>
-#include "Vex_mem_reg.h"
-
-int pass_count = 0;
-
-void tick(Vex_mem_reg* dut) {
- dut->clk = 0;
- dut->eval();
- dut->clk = 1;
- dut->eval();
- dut->clk = 0;
- dut->eval();
-}
-
-void check(bool cond, const char* name) {
- if (cond) {
- std::cout << "\033[32m[PASS] " << name << "\033[0m" << std::endl;
- pass_count++;
- } else {
- std::cout << "\033[31m[FAIL] " << name << "\033[0m" << std::endl;
- assert(false);
- }
-}
-
-int main(int argc, char** argv) {
- Verilated::commandArgs(argc, argv);
- Vex_mem_reg* dut = new Vex_mem_reg;
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << " EX/MEM Register Test" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- // 1. Init / Reset
- dut->stall_en = 0;
- dut->flush_en = 0;
-
- // Set some random dangerous EX inputs
- dut->ex_pc = 0x80001000;
- dut->ex_rs1_rdata = 0x11111111;
- dut->ex_rs2_rdata = 0x22222222;
- dut->ex_rs1_addr = 31;
- dut->ex_alu_result = 0x88888888;
- dut->ex_branch_taken = 1;
- dut->ex_branch_target = 0x80001020;
- dut->ex_is_store = 1;
- dut->ex_is_load = 1;
- dut->ex_reg_write_en = 1;
- dut->ex_is_csr = 1;
- dut->ex_illegal_instr = 1;
-
- dut->rst = 1;
- tick(dut);
- dut->rst = 0;
-
- // Verify reset clears out dangerous signals
- check(dut->mem_pc == 0, "Reset_Clears_PC");
- check(dut->mem_branch_taken == 0, "Reset_Clears_BranchTaken");
- check(dut->mem_reg_write_en == 0, "Reset_Clears_RegWrite");
- check(dut->mem_is_store == 0, "Reset_Clears_IsStore");
- check(dut->mem_is_load == 0, "Reset_Clears_IsLoad");
- check(dut->mem_is_csr == 0, "Reset_Clears_IsCSR");
- check(dut->mem_illegal_instr == 0, "Reset_Clears_IllegalInstr");
-
- // 2. Normal Propagation
- dut->ex_pc = 0x80002000;
- dut->ex_rs1_rdata = 0xAAAAAAAA;
- dut->ex_rs2_rdata = 0xBBBBBBBB;
- dut->ex_rs1_addr = 15;
- dut->ex_alu_result = 0xCAFEBABE;
- dut->ex_branch_taken = 1;
- dut->ex_branch_target = 0x80002008;
- dut->ex_is_store = 0;
- dut->ex_is_load = 1;
- dut->ex_reg_write_en = 1;
- dut->ex_rd_addr = 15;
- dut->ex_wb_sel = 1;
-
- tick(dut);
-
- check(dut->mem_pc == 0x80002000, "Propagate_PC");
- check(dut->mem_rs1_rdata == 0xAAAAAAAA, "Propagate_RS1");
- check(dut->mem_rs2_rdata == 0xBBBBBBBB, "Propagate_RS2");
- check(dut->mem_rs1_addr == 15, "Propagate_RS1_Addr");
- check(dut->mem_alu_result == 0xCAFEBABE, "Propagate_ALU_Result");
- check(dut->mem_branch_taken == 1, "Propagate_BranchTaken");
- check(dut->mem_branch_target == 0x80002008, "Propagate_BranchTarget");
- check(dut->mem_is_load == 1, "Propagate_IsLoad");
- check(dut->mem_reg_write_en == 1, "Propagate_RegWriteEn");
- check(dut->mem_rd_addr == 15, "Propagate_RdAddr");
- check(dut->mem_wb_sel == 1, "Propagate_WbSel");
-
- // 3. Stall (Freeze)
- // Change input, but assert stall
- dut->stall_en = 1;
- dut->ex_pc = 0x80003000;
- dut->ex_rs1_rdata = 0xCCCCCCCC;
- dut->ex_rs1_addr = 20;
- dut->ex_branch_taken = 0;
- dut->ex_rd_addr = 20;
- dut->ex_reg_write_en = 0;
- dut->ex_is_load = 0;
-
- tick(dut);
-
- check(dut->mem_pc == 0x80002000, "Stall_Maintains_PC");
- check(dut->mem_rs1_rdata == 0xAAAAAAAA, "Stall_Maintains_RS1");
- check(dut->mem_rs1_addr == 15, "Stall_Maintains_RS1_Addr");
- check(dut->mem_branch_taken == 1, "Stall_Maintains_BranchTaken");
- check(dut->mem_rd_addr == 15, "Stall_Maintains_RdAddr");
- check(dut->mem_reg_write_en == 1, "Stall_Maintains_RegWriteEn");
- check(dut->mem_is_load == 1, "Stall_Maintains_IsLoad");
-
- // Release Stall
- dut->stall_en = 0;
- tick(dut);
- check(dut->mem_pc == 0x80003000, "ReleaseStall_Captures_PC");
- check(dut->mem_rs1_rdata == 0xCCCCCCCC, "ReleaseStall_Captures_RS1");
- check(dut->mem_rs1_addr == 20, "ReleaseStall_Captures_RS1_Addr");
- check(dut->mem_branch_taken == 0, "ReleaseStall_Captures_BranchTaken");
- check(dut->mem_rd_addr == 20, "ReleaseStall_Captures_RdAddr");
- check(dut->mem_reg_write_en == 0, "ReleaseStall_Captures_RegWriteEn");
-
- // 4. Flush (Insert Bubble/NOP)
- // Change input, but assert flush
- dut->flush_en = 1;
- dut->ex_pc = 0x80004000;
- dut->ex_branch_taken = 1;
- dut->ex_reg_write_en = 1;
- dut->ex_is_store = 1;
-
- tick(dut);
-
- check(dut->mem_pc == 0x80003000, "Flush_Maintains_PC");
- check(dut->mem_branch_taken == 0, "Flush_Clears_BranchTaken");
- check(dut->mem_reg_write_en == 0, "Flush_Clears_RegWrite");
- check(dut->mem_is_store == 0, "Flush_Clears_StoreEn");
-
- // 5. Flush Overrides Stall
- dut->flush_en = 0;
- dut->ex_pc = 0x80005000;
- dut->ex_reg_write_en = 1;
- tick(dut);
- check(dut->mem_pc == 0x80005000, "Pre_FlushStall_Setup");
-
- dut->flush_en = 1;
- dut->stall_en = 1;
- dut->ex_pc = 0x80006000;
- dut->ex_branch_taken = 1;
- tick(dut);
-
- check(dut->mem_pc == 0x80005000, "Flush_Overrides_Stall_PC");
- check(dut->mem_branch_taken == 0, "Flush_Overrides_Stall_BranchTaken");
- check(dut->mem_reg_write_en == 0, "Flush_Overrides_Stall_RegWrite");
- check(dut->mem_is_store == 0, "Flush_Overrides_Stall_Store");
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << "\033[32mAll " << pass_count << " tests passed!\033[0m" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- delete dut;
- return 0;
-}
diff --git a/rv64cpu/tb/unit/tb_ex_stage.cc b/rv64cpu/tb/unit/tb_ex_stage.cc
deleted file mode 100644
index 9090a53..0000000
--- a/rv64cpu/tb/unit/tb_ex_stage.cc
+++ /dev/null
@@ -1,218 +0,0 @@
-// 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 <https://www.gnu.org/licenses/>.
-
-// File path: tb/unit/tb_ex_stage.cc
-// Description: Execution Stage unit test
-
-#include <iostream>
-#include <iomanip>
-#include <cstdint>
-#include <cassert>
-#include <verilated.h>
-#include "Vex_stage.h"
-
-int pass_count = 0;
-
-void eval(Vex_stage* dut) {
- dut->eval();
-}
-
-void check(bool cond, const char* name) {
- if (cond) {
- std::cout << "\033[32m[PASS] " << name << "\033[0m" << std::endl;
- pass_count++;
- } else {
- std::cout << "\033[31m[FAIL] " << name << "\033[0m" << std::endl;
- assert(false);
- }
-}
-
-// Emulate rv64_pkg enums
-enum AluOp { ALU_ADD = 0, ALU_SUB = 8, ALU_SLL = 1, ALU_SLT = 2, ALU_SLTU = 3,
- ALU_XOR = 4, ALU_SRL = 5, ALU_SRA = 13, ALU_OR = 6, ALU_AND = 7 };
-enum BranchOp { BRANCH_EQ = 0, BRANCH_NE = 1, BRANCH_LT = 4, BRANCH_GE = 5, BRANCH_LTU = 6, BRANCH_GEU = 7 };
-
-int main(int argc, char** argv) {
- Verilated::commandArgs(argc, argv);
- Vex_stage* dut = new Vex_stage;
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << " Execute Stage Test" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- // Default inputs
- dut->ex_pc = 0;
- dut->ex_rs1_rdata = 0;
- dut->ex_rs2_rdata = 0;
- dut->ex_imm = 0;
- dut->ex_alu_op = ALU_ADD;
- dut->ex_alu_src1_sel = 0;
- dut->ex_alu_src2_sel = 0;
- dut->ex_is_branch = 0;
- dut->ex_branch_op = 0;
- dut->ex_is_jump = 0;
- dut->ex_is_word_op = 0;
-
- // 1. Basic R-Type (ADD)
- // rs1 + rs2
- dut->ex_rs1_rdata = 50;
- dut->ex_rs2_rdata = 25;
- dut->ex_alu_src1_sel = 0; // rs1
- dut->ex_alu_src2_sel = 0; // rs2
- dut->ex_alu_op = ALU_ADD;
-
- eval(dut);
- check(dut->ex_alu_result == 75, "R_Type_ADD");
- check(dut->ex_branch_taken == 0, "R_Type_Not_Branch");
-
- // 2. Basic I-Type (ADDI)
- // rs1 + imm
- dut->ex_rs1_rdata = 100;
- dut->ex_imm = 42;
- dut->ex_alu_src1_sel = 0; // rs1
- dut->ex_alu_src2_sel = 1; // imm
- dut->ex_alu_op = ALU_ADD;
-
- eval(dut);
- check(dut->ex_alu_result == 142, "I_Type_ADDI");
-
- // 3. Branch Taken (BEQ: rs1 == rs2)
- // target = PC + imm_b
- dut->ex_is_branch = 1;
- dut->ex_branch_op = BRANCH_EQ;
- dut->ex_rs1_rdata = 88;
- dut->ex_rs2_rdata = 88; // Equal!
-
- dut->ex_pc = 0x80001000;
- dut->ex_imm = 0x10;
- dut->ex_alu_src1_sel = 1; // PC
- dut->ex_alu_src2_sel = 1; // imm
- dut->ex_alu_op = ALU_ADD;
-
- eval(dut);
- check(dut->ex_branch_taken == 1, "Branch_BEQ_Taken");
- check(dut->ex_branch_target == 0x80001010, "Branch_Target_Calculation");
-
- // 4. Branch Not Taken (BEQ: rs1 != rs2)
- dut->ex_rs1_rdata = 88;
- dut->ex_rs2_rdata = 99; // Not Equal
- eval(dut);
- check(dut->ex_branch_taken == 0, "Branch_BEQ_Not_Taken");
-
- // 5. Jump (JAL)
- // target = PC + imm_j (always taken)
- dut->ex_is_branch = 0;
- dut->ex_is_jump = 1;
- dut->ex_pc = 0x80002000;
- dut->ex_imm = 0x24; // Forward jump 36 bytes
- dut->ex_alu_src1_sel = 1; // PC
- dut->ex_alu_src2_sel = 1; // imm
- dut->ex_alu_op = ALU_ADD;
-
- eval(dut);
- check(dut->ex_branch_taken == 1, "Jump_JAL_Taken");
- check(dut->ex_branch_target == 0x80002024, "Jump_JAL_Target");
-
- // 6. Jump Register (JALR)
- // target = (rs1 + imm_i) & ~1
- dut->ex_is_jump = 1;
- dut->ex_rs1_rdata = 0x80003001; // Intentionally unaligned
- dut->ex_imm = 4;
- dut->ex_alu_src1_sel = 0; // rs1
- dut->ex_alu_src2_sel = 1; // imm
- dut->ex_alu_op = ALU_ADD;
-
- eval(dut);
- // 0x80003001 + 4 = 0x80003005. Target -> 0x80003004.
- check(dut->ex_branch_taken == 1, "Jump_JALR_Taken");
- check(dut->ex_branch_target == 0x80003004, "Jump_JALR_Clears_LSB");
- check(dut->ex_alu_result == 0x80003005, "JALR_Raw_ALU_Result");
-
- // 7. Word Operation (ADDW)
- // Force 32-bit addition with sign extension
- dut->ex_is_jump = 0;
- dut->ex_is_word_op = 1;
- dut->ex_rs1_rdata = 0x000000007FFFFFFF;
- dut->ex_rs2_rdata = 0x0000000000000001;
- dut->ex_alu_src1_sel = 0; // rs1
- dut->ex_alu_src2_sel = 0; // rs2
- dut->ex_alu_op = ALU_ADD;
-
- // 0x7FFFFFFF + 1 = 0x80000000. Under 64-bit ADD this is positive 0x80000000.
- // However, ADDW requires sign-extending the lower 32 bits. Therefore, it becomes 0xFFFFFFFF80000000.
- eval(dut);
- check(dut->ex_alu_result == 0xFFFFFFFF80000000ULL, "Word_Op_ADDW_SignExt");
-
- // 8. Load/Store Address Calculation
- // target = rs1 + imm
- dut->ex_is_word_op = 0;
- dut->ex_rs1_rdata = 0x8000A000;
- dut->ex_imm = 0xFFFFFFFFFFFFFFF0ULL; // -16
- dut->ex_alu_src1_sel = 0; // rs1
- dut->ex_alu_src2_sel = 1; // imm
- dut->ex_alu_op = ALU_ADD;
-
- eval(dut);
- check(dut->ex_alu_result == 0x80009FF0, "LoadStore_Address_Mem");
-
- // 9. Shift Left Logical (SLL)
- dut->ex_is_word_op = 0;
- dut->ex_rs1_rdata = 0x000000000000FFFFULL;
- dut->ex_rs2_rdata = 16; // Shift by 16
- dut->ex_alu_src1_sel = 0;
- dut->ex_alu_src2_sel = 0;
- dut->ex_alu_op = ALU_SLL;
- eval(dut);
- check(dut->ex_alu_result == 0x00000000FFFF0000ULL, "ALU_SLL_64bit");
-
- // 10. Shift Right Arithmetic (SRA)
- dut->ex_rs1_rdata = 0xFFFFFFFF00000000ULL;
- dut->ex_rs2_rdata = 16;
- dut->ex_alu_op = ALU_SRA;
- eval(dut);
- check(dut->ex_alu_result == 0xFFFFFFFFFFFF0000ULL, "ALU_SRA_64bit");
-
- // 11. Word Shift Right Arithmetic (SRAW)
- dut->ex_is_word_op = 1;
- dut->ex_rs1_rdata = 0xFFFFFFFF80000000ULL; // -2147483648
- dut->ex_rs2_rdata = 4;
- dut->ex_alu_op = ALU_SRA;
- eval(dut);
- // 0x80000000 arithmetically shifted right by 4 is 0xF8000000. Sign extended to 64-bit.
- check(dut->ex_alu_result == 0xFFFFFFFFF8000000ULL, "ALU_SRAW_32bit");
-
- // 12. Word Shift Left Logical (SLLW) with large shamt
- dut->ex_rs1_rdata = 0x000000000000FFFFULL;
- dut->ex_rs2_rdata = 0x24; // 36 = 100100 in binary. Masked to 5 bits = 4.
- dut->ex_alu_op = ALU_SLL;
- eval(dut);
- check(dut->ex_alu_result == 0x00000000000FFFF0ULL, "ALU_SLLW_Shamt_Masking");
-
- // 13. Word Shift Right Logical (SRLW) with negative 32-bit
- // Zero extends the 32-bit source, avoiding sign extension during logical shift
- dut->ex_rs1_rdata = 0xFFFFFFFF80000000ULL;
- dut->ex_rs2_rdata = 4;
- dut->ex_alu_op = ALU_SRL;
- eval(dut);
- // 0x80000000 logically shifted right by 4 is 0x08000000. Sign extended to 64-bit -> positive.
- check(dut->ex_alu_result == 0x0000000008000000ULL, "ALU_SRLW_ZeroExtend");
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << "\033[32mAll " << pass_count << " tests passed!\033[0m" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- delete dut;
- return 0;
-}
diff --git a/rv64cpu/tb/unit/tb_hazard_unit.cc b/rv64cpu/tb/unit/tb_hazard_unit.cc
deleted file mode 100644
index 6314ab8..0000000
--- a/rv64cpu/tb/unit/tb_hazard_unit.cc
+++ /dev/null
@@ -1,364 +0,0 @@
-// 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 <https://www.gnu.org/licenses/>.
-
-// File path: tb/unit/tb_hazard_unit.cc
-// Description: Hazard Unit testbench — exhaustive forwarding, stall, and flush tests
-
-#include <iostream>
-#include <cassert>
-#include <verilated.h>
-#include "Vhazard_unit.h"
-
-int pass_count = 0;
-
-void check(bool cond, const char* name) {
- if (cond) {
- std::cout << "\033[32m[PASS] " << name << "\033[0m" << std::endl;
- pass_count++;
- } else {
- std::cout << "\033[31m[FAIL] " << name << "\033[0m" << std::endl;
- assert(false);
- }
-}
-
-void reset(Vhazard_unit* dut) {
- dut->id_rs1_addr = 0;
- dut->id_rs2_addr = 0;
- dut->ex_rs1_addr = 0;
- dut->ex_rs2_addr = 0;
- dut->ex_rd_addr = 0;
- dut->ex_reg_write_en = 0;
- dut->ex_is_load = 0;
- dut->ex_branch_taken = 0;
- dut->ex_branch_target = 0;
- dut->mem_rd_addr = 0;
- dut->mem_reg_write_en = 0;
- dut->mem_result_late = 0;
- dut->mem_stall_req = 0;
- dut->trap_flush_req = 0;
- dut->trap_pc_sel = 0;
- dut->trap_target_pc = 0;
- dut->wb_rd_addr = 0;
- dut->wb_reg_write_en = 0;
- dut->eval();
-}
-
-int main(int argc, char** argv) {
- Verilated::commandArgs(argc, argv);
- Vhazard_unit* dut = new Vhazard_unit;
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << " Hazard Unit Test" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- // -----------------------
- // 1. No hazard: all quiet
- // -----------------------
- reset(dut);
- check(dut->forward_a_sel == 0, "NoHazard_FwdA_None");
- check(dut->forward_b_sel == 0, "NoHazard_FwdB_None");
- check(dut->if_stall_en == 0, "NoHazard_NoStall");
- check(dut->if_id_flush_en == 0, "NoHazard_NoFlush");
- check(dut->id_ex_flush_en == 0, "NoHazard_NoIDEXFlush");
- check(dut->id_ex_stall_en == 0, "NoHazard_NoIDEXStall");
- check(dut->ex_mem_stall_en == 0, "NoHazard_NoEXMEMStall");
- check(dut->pc_redirect_sel == 0, "NoHazard_NoRedirect");
- check(dut->pc_redirect_is_trap == 0, "NoHazard_NoTrapRedirect");
-
- // -----------------------
- // 2. Forward A from MEM stage
- // -----------------------
- reset(dut);
- dut->ex_rs1_addr = 5;
- dut->mem_rd_addr = 5;
- dut->mem_reg_write_en = 1;
- dut->eval();
- check(dut->forward_a_sel == 1, "FwdA_From_MEM");
- check(dut->forward_b_sel == 0, "FwdA_From_MEM_B_None");
-
- // -----------------------
- // 3. Forward A from WB stage
- // -----------------------
- reset(dut);
- dut->ex_rs1_addr = 7;
- dut->wb_rd_addr = 7;
- dut->wb_reg_write_en = 1;
- dut->eval();
- check(dut->forward_a_sel == 2, "FwdA_From_WB");
-
- // -----------------------
- // 4. Forward B from MEM stage
- // -----------------------
- reset(dut);
- dut->ex_rs2_addr = 10;
- dut->mem_rd_addr = 10;
- dut->mem_reg_write_en = 1;
- dut->eval();
- check(dut->forward_b_sel == 1, "FwdB_From_MEM");
- check(dut->forward_a_sel == 0, "FwdB_From_MEM_A_None");
-
- // -----------------------
- // 5. Forward B from WB stage
- // -----------------------
- reset(dut);
- dut->ex_rs2_addr = 12;
- dut->wb_rd_addr = 12;
- dut->wb_reg_write_en = 1;
- dut->eval();
- check(dut->forward_b_sel == 2, "FwdB_From_WB");
-
- // -----------------------
- // 6. MEM > WB priority for same register
- // -----------------------
- reset(dut);
- dut->ex_rs1_addr = 3;
- dut->mem_rd_addr = 3;
- dut->mem_reg_write_en = 1;
- dut->wb_rd_addr = 3;
- dut->wb_reg_write_en = 1;
- dut->eval();
- check(dut->forward_a_sel == 1, "FwdA_MEM_Priority_Over_WB");
-
- reset(dut);
- dut->ex_rs2_addr = 3;
- dut->mem_rd_addr = 3;
- dut->mem_reg_write_en = 1;
- dut->wb_rd_addr = 3;
- dut->wb_reg_write_en = 1;
- dut->eval();
- check(dut->forward_b_sel == 1, "FwdB_MEM_Priority_Over_WB");
-
- reset(dut);
- dut->ex_rs1_addr = 3;
- dut->mem_rd_addr = 3;
- dut->mem_reg_write_en = 1;
- dut->mem_result_late = 1;
- dut->wb_rd_addr = 3;
- dut->wb_reg_write_en = 1;
- dut->eval();
- check(dut->forward_a_sel == 2, "LateResult_ForwardA_From_WB");
-
- reset(dut);
- dut->ex_rs2_addr = 3;
- dut->mem_rd_addr = 3;
- dut->mem_reg_write_en = 1;
- dut->mem_result_late = 1;
- dut->wb_rd_addr = 3;
- dut->wb_reg_write_en = 1;
- dut->eval();
- check(dut->forward_b_sel == 2, "LateResult_ForwardB_From_WB");
-
- // -----------------------
- // 7. No forward to x0 (hardwired zero)
- // -----------------------
- reset(dut);
- dut->ex_rs1_addr = 0;
- dut->mem_rd_addr = 0;
- dut->mem_reg_write_en = 1;
- dut->eval();
- check(dut->forward_a_sel == 0, "NoFwd_x0_A_MEM");
-
- reset(dut);
- dut->ex_rs2_addr = 0;
- dut->wb_rd_addr = 0;
- dut->wb_reg_write_en = 1;
- dut->eval();
- check(dut->forward_b_sel == 0, "NoFwd_x0_B_WB");
-
- // -----------------------
- // 8. No forward if reg_write_en is 0
- // -----------------------
- reset(dut);
- dut->ex_rs1_addr = 5;
- dut->mem_rd_addr = 5;
- dut->mem_reg_write_en = 0;
- dut->eval();
- check(dut->forward_a_sel == 0, "NoFwd_Write_Disabled_MEM");
-
- reset(dut);
- dut->ex_rs2_addr = 5;
- dut->wb_rd_addr = 5;
- dut->wb_reg_write_en = 0;
- dut->eval();
- check(dut->forward_b_sel == 0, "NoFwd_Write_Disabled_WB");
-
- // -----------------------
- // 9. Forward A and B simultaneously
- // -----------------------
- reset(dut);
- dut->ex_rs1_addr = 8;
- dut->ex_rs2_addr = 9;
- dut->mem_rd_addr = 8;
- dut->mem_reg_write_en = 1;
- dut->wb_rd_addr = 9;
- dut->wb_reg_write_en = 1;
- dut->eval();
- check(dut->forward_a_sel == 1, "FwdA_MEM_And_FwdB_WB_A");
- check(dut->forward_b_sel == 2, "FwdA_MEM_And_FwdB_WB_B");
-
- // -----------------------
- // 10. Load-Use stall (rs1 match)
- // -----------------------
- reset(dut);
- dut->ex_is_load = 1;
- dut->ex_rd_addr = 4;
- dut->id_rs1_addr = 4;
- dut->eval();
- check(dut->if_stall_en == 1, "LoadUse_RS1_IF_Stall");
- check(dut->if_id_stall_en == 1, "LoadUse_RS1_IFID_Stall");
- check(dut->id_ex_flush_en == 1, "LoadUse_RS1_IDEX_Flush");
- check(dut->id_ex_stall_en == 0, "LoadUse_RS1_IDEX_NoStall");
-
- // -----------------------
- // 11. Load-Use stall (rs2 match)
- // -----------------------
- reset(dut);
- dut->ex_is_load = 1;
- dut->ex_rd_addr = 6;
- dut->id_rs2_addr = 6;
- dut->eval();
- check(dut->if_stall_en == 1, "LoadUse_RS2_IF_Stall");
- check(dut->if_id_stall_en == 1, "LoadUse_RS2_IFID_Stall");
- check(dut->id_ex_flush_en == 1, "LoadUse_RS2_IDEX_Flush");
-
- // -----------------------
- // 12. No load-use stall to x0
- // -----------------------
- reset(dut);
- dut->ex_is_load = 1;
- dut->ex_rd_addr = 0;
- dut->id_rs1_addr = 0;
- dut->eval();
- check(dut->if_stall_en == 0, "LoadUse_x0_NoStall");
-
- // -----------------------
- // 13. No load-use stall if not a load
- // -----------------------
- reset(dut);
- dut->ex_is_load = 0;
- dut->ex_rd_addr = 4;
- dut->id_rs1_addr = 4;
- dut->eval();
- check(dut->if_stall_en == 0, "NotLoad_NoStall");
-
- // -----------------------
- // 14. Branch taken flush
- // -----------------------
- reset(dut);
- dut->ex_branch_taken = 1;
- dut->ex_branch_target = 0x400;
- dut->eval();
- check(dut->if_id_flush_en == 1, "Branch_IFID_Flush");
- check(dut->id_ex_flush_en == 1, "Branch_IDEX_Flush");
- check(dut->if_stall_en == 0, "Branch_IF_NoStall");
- check(dut->if_id_stall_en == 0, "Branch_IFID_NoStall");
- check(dut->pc_redirect_sel == 1, "Branch_Redirect_Valid");
- check(dut->pc_redirect_is_trap == 0, "Branch_Redirect_NotTrap");
- check(dut->pc_redirect_target == 0x400, "Branch_Redirect_Target");
-
- // -----------------------
- // 15. Branch overrides load-use stall
- // -----------------------
- reset(dut);
- dut->ex_branch_taken = 1;
- dut->ex_is_load = 1;
- dut->ex_rd_addr = 4;
- dut->id_rs1_addr = 4;
- dut->eval();
- check(dut->if_id_flush_en == 1, "BranchOverride_IFID_Flush");
- check(dut->id_ex_flush_en == 1, "BranchOverride_IDEX_Flush");
- check(dut->if_stall_en == 0, "BranchOverride_IF_NoStall");
- check(dut->if_id_stall_en == 0, "BranchOverride_IFID_NoStall");
-
- // -----------------------
- // 16. Memory stall (D-Cache miss)
- // -----------------------
- reset(dut);
- dut->mem_stall_req = 1;
- dut->eval();
- check(dut->if_stall_en == 1, "MemStall_IF_Stall");
- check(dut->if_id_stall_en == 1, "MemStall_IFID_Stall");
- check(dut->id_ex_stall_en == 1, "MemStall_IDEX_Stall");
- check(dut->ex_mem_stall_en == 1, "MemStall_EXMEM_Stall");
- check(dut->if_id_flush_en == 0, "MemStall_NoFlush");
- check(dut->id_ex_flush_en == 0, "MemStall_NoIDEXFlush");
-
- // -----------------------
- // 17. Mem stall suppresses load-use flush
- // -----------------------
- reset(dut);
- dut->mem_stall_req = 1;
- dut->ex_is_load = 1;
- dut->ex_rd_addr = 4;
- dut->id_rs1_addr = 4;
- dut->eval();
- check(dut->if_stall_en == 1, "MemStall_Suppresses_LoadUse_Stall");
- check(dut->id_ex_stall_en == 1, "MemStall_Suppresses_LoadUse_IDEXStall");
- check(dut->id_ex_flush_en == 0, "MemStall_Suppresses_LoadUse_Flush");
-
- // -----------------------
- // 18. Branch overrides mem stall for flush
- // -----------------------
- reset(dut);
- dut->ex_branch_taken = 1;
- dut->ex_branch_target = 0x410;
- dut->mem_stall_req = 1;
- dut->eval();
- check(dut->if_id_flush_en == 1, "Branch_MemStall_IFID_Flush");
- check(dut->id_ex_flush_en == 1, "Branch_MemStall_IDEX_Flush");
- check(dut->if_stall_en == 0, "Branch_MemStall_IF_NoStall");
-
- // -----------------------
- // 19. Trap redirect overrides branch and stalls
- // -----------------------
- reset(dut);
- dut->trap_pc_sel = 1;
- dut->trap_flush_req = 1;
- dut->trap_target_pc = 0x800;
- dut->ex_branch_taken = 1;
- dut->ex_branch_target = 0x900;
- dut->mem_stall_req = 1;
- dut->eval();
- check(dut->pc_redirect_sel == 1, "Trap_Redirect_Valid");
- check(dut->pc_redirect_is_trap == 1, "Trap_Redirect_IsTrap");
- check(dut->pc_redirect_target == 0x800, "Trap_Redirect_Target");
- check(dut->if_id_flush_en == 1, "Trap_Flush_IFID");
- check(dut->id_ex_flush_en == 1, "Trap_Flush_IDEX");
- check(dut->if_stall_en == 0, "Trap_Clears_IF_Stall");
- check(dut->if_id_stall_en == 0, "Trap_Clears_IFID_Stall");
- check(dut->id_ex_stall_en == 0, "Trap_Clears_IDEX_Stall");
- check(dut->ex_mem_stall_en == 0, "Trap_Clears_EXMEM_Stall");
-
- // -----------------------
- // 20. Trap flush overrides load-use stall even without redirect
- // -----------------------
- reset(dut);
- dut->trap_flush_req = 1;
- dut->ex_is_load = 1;
- dut->ex_rd_addr = 4;
- dut->id_rs1_addr = 4;
- dut->eval();
- check(dut->if_id_flush_en == 1, "TrapFlush_IFID");
- check(dut->id_ex_flush_en == 1, "TrapFlush_IDEX");
- check(dut->if_stall_en == 0, "TrapFlush_NoIFStall");
- check(dut->if_id_stall_en == 0, "TrapFlush_NoIFIDStall");
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << "\033[32mAll " << pass_count << " tests passed!\033[0m" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- delete dut;
- return 0;
-}
diff --git a/rv64cpu/tb/unit/tb_id_ex_reg.cc b/rv64cpu/tb/unit/tb_id_ex_reg.cc
deleted file mode 100644
index d9cc329..0000000
--- a/rv64cpu/tb/unit/tb_id_ex_reg.cc
+++ /dev/null
@@ -1,177 +0,0 @@
-// 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 <https://www.gnu.org/licenses/>.
-
-// File path: tb/unit/tb_id_ex_reg.cc
-// Description: ID/EX Pipeline Register unit test
-
-#include <iostream>
-#include <iomanip>
-#include <cstdint>
-#include <cassert>
-#include <verilated.h>
-#include "Vid_ex_reg.h"
-
-int pass_count = 0;
-
-void tick(Vid_ex_reg* dut) {
- dut->clk = 0;
- dut->eval();
- dut->clk = 1;
- dut->eval();
- dut->clk = 0;
- dut->eval();
-}
-
-void check(bool cond, const char* name) {
- if (cond) {
- std::cout << "\033[32m[PASS] " << name << "\033[0m" << std::endl;
- pass_count++;
- } else {
- std::cout << "\033[31m[FAIL] " << name << "\033[0m" << std::endl;
- assert(false);
- }
-}
-
-// Emulate rv64_pkg::alu_op_t
-enum AluOp { ALU_ADD = 0, ALU_SUB = 8, ALU_SLL = 1, ALU_SLT = 2, ALU_SLTU = 3,
- ALU_XOR = 4, ALU_SRL = 5, ALU_SRA = 13, ALU_OR = 6, ALU_AND = 7 };
-
-int main(int argc, char** argv) {
- Verilated::commandArgs(argc, argv);
- Vid_ex_reg* dut = new Vid_ex_reg;
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << " ID/EX Register Test" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- // 1. Init / Reset
- dut->stall_en = 0;
- dut->flush_en = 0;
-
- // Set some random ID stage inputs with dangerous states
- dut->id_pc = 0x80001000;
- dut->id_rs1_rdata = 0x11111111;
- dut->id_rs2_rdata = 0x22222222;
- dut->id_rs1_addr = 31;
- dut->id_rs2_addr = 15;
- dut->id_imm = 0x1A2B3C;
- dut->id_alu_op = ALU_XOR;
- dut->id_reg_write_en = 1;
- dut->id_is_store = 1;
- dut->id_is_load = 1;
- dut->id_is_branch = 1;
- dut->id_is_jump = 1;
- dut->id_is_csr = 1;
- dut->id_illegal_instr = 1;
-
- dut->rst = 1;
- tick(dut);
- dut->rst = 0;
-
- // Verify reset clears out dangerous signals
- check(dut->ex_pc == 0, "Reset_Clears_PC");
- check(dut->ex_reg_write_en == 0, "Reset_Clears_RegWrite");
- check(dut->ex_is_store == 0, "Reset_Clears_IsStore");
- check(dut->ex_is_load == 0, "Reset_Clears_IsLoad");
- check(dut->ex_is_branch == 0, "Reset_Clears_IsBranch");
- check(dut->ex_is_jump == 0, "Reset_Clears_IsJump");
- check(dut->ex_is_csr == 0, "Reset_Clears_IsCSR");
- check(dut->ex_illegal_instr == 0, "Reset_Clears_IllegalInstr");
-
- // 2. Normal Propagation
- dut->id_pc = 0x80002000;
- dut->id_rs1_rdata = 0xAAAAAAAA;
- dut->id_rs2_rdata = 0xBBBBBBBB;
- dut->id_rs1_addr = 10;
- dut->id_rs2_addr = 11;
- dut->id_imm = 42;
- dut->id_alu_op = ALU_ADD;
- dut->id_reg_write_en = 1;
- dut->id_rd_addr = 5;
- dut->id_is_load = 1;
- dut->id_is_branch = 0;
-
- tick(dut);
-
- check(dut->ex_pc == 0x80002000, "Propagate_PC");
- check(dut->ex_rs1_rdata == 0xAAAAAAAA, "Propagate_RS1");
- check(dut->ex_rs2_rdata == 0xBBBBBBBB, "Propagate_RS2");
- check(dut->ex_rs1_addr == 10, "Propagate_RS1_Addr");
- check(dut->ex_rs2_addr == 11, "Propagate_RS2_Addr");
- check(dut->ex_imm == 42, "Propagate_IMM");
- check(dut->ex_alu_op == ALU_ADD, "Propagate_ALU_OP");
- check(dut->ex_reg_write_en == 1, "Propagate_RegWriteEn");
- check(dut->ex_rd_addr == 5, "Propagate_RdAddr");
- check(dut->ex_is_load == 1, "Propagate_IsLoad");
-
- // 3. Stall (Freeze)
- // Change input, but assert stall
- dut->stall_en = 1;
- dut->id_pc = 0x80003000;
- dut->id_rd_addr = 10;
- dut->id_reg_write_en = 0;
- dut->id_is_load = 0;
-
- tick(dut);
-
- check(dut->ex_pc == 0x80002000, "Stall_Maintains_PC");
- check(dut->ex_rd_addr == 5, "Stall_Maintains_RdAddr");
- check(dut->ex_reg_write_en == 1, "Stall_Maintains_RegWriteEn");
- check(dut->ex_is_load == 1, "Stall_Maintains_IsLoad");
-
- // Release Stall
- dut->stall_en = 0;
- tick(dut);
- check(dut->ex_pc == 0x80003000, "ReleaseStall_Captures_PC");
- check(dut->ex_rd_addr == 10, "ReleaseStall_Captures_RdAddr");
- check(dut->ex_reg_write_en == 0, "ReleaseStall_Captures_RegWriteEn");
-
- // 4. Flush (Insert Bubble/NOP)
- // Change input, but assert flush
- dut->flush_en = 1;
- dut->id_pc = 0x80004000;
- dut->id_reg_write_en = 1;
- dut->id_is_store = 1;
- dut->id_is_branch = 1;
-
- tick(dut);
-
- check(dut->ex_pc == 0x80003000, "Flush_Maintains_PC");
- check(dut->ex_reg_write_en == 0, "Flush_Clears_RegWrite");
- check(dut->ex_is_store == 0, "Flush_Clears_StoreEn");
- check(dut->ex_is_branch == 0, "Flush_Clears_BranchEn");
-
- // 5. Flush Overrides Stall
- dut->flush_en = 0;
- dut->id_pc = 0x80005000;
- dut->id_reg_write_en = 1;
- tick(dut);
- check(dut->ex_pc == 0x80005000, "Pre_FlushStall_Setup");
-
- dut->flush_en = 1;
- dut->stall_en = 1;
- dut->id_pc = 0x80006000;
- tick(dut);
-
- check(dut->ex_pc == 0x80005000, "Flush_Overrides_Stall_PC");
- check(dut->ex_reg_write_en == 0, "Flush_Overrides_Stall_RegWrite");
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << "\033[32mAll " << pass_count << " tests passed!\033[0m" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- delete dut;
- return 0;
-}
diff --git a/rv64cpu/tb/unit/tb_id_stage.cc b/rv64cpu/tb/unit/tb_id_stage.cc
deleted file mode 100644
index 26dfd97..0000000
--- a/rv64cpu/tb/unit/tb_id_stage.cc
+++ /dev/null
@@ -1,194 +0,0 @@
-// 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 <https://www.gnu.org/licenses/>.
-
-// File path: tb/unit/tb_id_stage.cc
-// Description: ID Stage integration test
-// Verifies that id_stage correctly wires decoder + regfile:
-// 1. Decoder control signal pass-through
-// 2. RegFile write-back and read-out
-// 3. PC pass-through
-// 4. Immediate extraction
-
-#include <iostream>
-#include <cstdint>
-#include <cassert>
-#include <verilated.h>
-#include "Vid_stage.h"
-
-static int pass_count = 0;
-
-void tick(Vid_stage* dut) {
- dut->clk = 0;
- dut->eval();
- dut->clk = 1;
- dut->eval();
- dut->clk = 0;
- dut->eval();
-}
-
-void check(bool cond, const char* name) {
- if (cond) {
- std::cout << "\033[32m[PASS] " << name << "\033[0m" << std::endl;
- pass_count++;
- } else {
- std::cout << "\033[31m[FAIL] " << name << "\033[0m" << std::endl;
- assert(false);
- }
-}
-
-// ALU op encoding must match rv64_pkg
-enum AluOp { ALU_ADD = 0, ALU_SUB = 8, ALU_SLL = 1, ALU_SLT = 2, ALU_SLTU = 3,
- ALU_XOR = 4, ALU_SRL = 5, ALU_SRA = 13, ALU_OR = 6, ALU_AND = 7 };
-
-int main(int argc, char** argv) {
- Verilated::commandArgs(argc, argv);
- Vid_stage* dut = new Vid_stage;
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << " ID Stage Test" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- // Initialize
- dut->clk = 0;
- dut->rst = 0;
- dut->wb_reg_write_en = 0;
- dut->wb_rd_addr = 0;
- dut->wb_rd_wdata = 0;
- dut->if_pc = 0;
- dut->if_instr = 0;
-
- // Reset the regfile
- dut->rst = 1;
- tick(dut);
- dut->rst = 0;
-
- // -----------------
- // Test 1: PC Pass-through
- dut->if_pc = 0x80001000ULL;
- dut->if_instr = 0x00000013; // NOP (ADDI x0, x0, 0)
- dut->eval();
- check(dut->id_pc == 0x80001000ULL, "PC_Passthrough");
-
- // -----------------
- // Test 2: Decode ADDI x1, x0, 42 (0x02A00093)
- dut->if_instr = 0x02A00093;
- dut->eval();
- check(dut->id_rd_addr == 1, "ADDI_rd_addr");
- check(dut->id_alu_op == ALU_ADD, "ADDI_alu_op");
- check(dut->id_alu_src2_sel == 1, "ADDI_alu_src2_sel");
- check(dut->id_imm == 42, "ADDI_imm");
- check(dut->id_reg_write_en == 1, "ADDI_reg_write_en");
- check(dut->id_is_branch == 0, "ADDI_not_branch");
- check(dut->id_is_jump == 0, "ADDI_not_jump");
- check(dut->id_illegal_instr == 0, "ADDI_not_illegal");
-
- // -----------------
- // Test 3: RegFile write-back then read
- // Write 0xDEADBEEF to x5 via WB port, then decode an instruction that reads x5
- dut->wb_reg_write_en = 1;
- dut->wb_rd_addr = 5;
- dut->wb_rd_wdata = 0xDEADBEEF;
- tick(dut);
- dut->wb_reg_write_en = 0;
-
- // ADD x1, x5, x0 → 0x000280B3
- // rs1=x5, rs2=x0, rd=x1
- dut->if_instr = 0x000280B3;
- dut->eval();
- check(dut->id_rs1_addr == 5, "R_Type_rs1_addr");
- check(dut->id_rs2_addr == 0, "R_Type_rs2_addr");
- check(dut->id_rs1_rdata == 0xDEADBEEF, "RegFile_Read_x5");
- check(dut->id_rs2_rdata == 0, "RegFile_Read_x0");
- check(dut->id_alu_src2_sel == 0, "R_Type_alu_src2_sel");
-
- // -----------------
- // Test 4: Dual-port read via RegFile
- // Write 0x11111111 to x10, then decode ADD x1, x5, x10
- dut->wb_reg_write_en = 1;
- dut->wb_rd_addr = 10;
- dut->wb_rd_wdata = 0x11111111;
- tick(dut);
- dut->wb_reg_write_en = 0;
-
- // ADD x1, x5, x10 → funct7=0000000, rs2=01010, rs1=00101, funct3=000, rd=00001, op=0110011
- // = 0x00A280B3
- dut->if_instr = 0x00A280B3;
- dut->eval();
- check(dut->id_rs1_rdata == 0xDEADBEEF, "DualRead_rs1_x5");
- check(dut->id_rs2_rdata == 0x11111111, "DualRead_rs2_x10");
-
- // -----------------
- // Test 5: Branch BEQ x1, x2, -4
- // Encoding: 0xFE208EE3
- dut->if_instr = 0xFE208EE3;
- dut->eval();
- check(dut->id_is_branch == 1, "BEQ_is_branch");
- check(dut->id_reg_write_en == 0, "BEQ_no_reg_write");
- check(dut->id_branch_op == 0, "BEQ_branch_op");
- check(dut->id_imm == (uint64_t)(-4),"BEQ_imm_neg4");
-
- // -----------------
- // Test 6: JAL x1, +8 (0x008000EF)
- dut->if_instr = 0x008000EF;
- dut->eval();
- check(dut->id_is_jump == 1, "JAL_is_jump");
- check(dut->id_reg_write_en == 1, "JAL_reg_write_en");
- check(dut->id_wb_sel == 2, "JAL_wb_sel_PC4");
- check(dut->id_imm == 8, "JAL_imm_8");
-
- // -----------------
- // Test 7: LD x5, 8(x10) (0x00853283)
- dut->if_instr = 0x00853283;
- dut->eval();
- check(dut->id_is_load == 1, "LD_is_load");
- check(dut->id_reg_write_en == 1, "LD_reg_write_en");
- check(dut->id_wb_sel == 1, "LD_wb_sel_MEM");
- check(dut->id_imm == 8, "LD_imm_8");
- check(dut->id_mem_size == 3, "LD_mem_size_dword");
-
- // -----------------
- // Test 8: SD x5, 16(x10) (0x00553823)
- dut->if_instr = 0x00553823;
- dut->eval();
- check(dut->id_is_store == 1, "SD_is_store");
- check(dut->id_reg_write_en == 0, "SD_no_reg_write");
- check(dut->id_imm == 16, "SD_imm_16");
-
- // -----------------
- // Test 9: Illegal instruction (0x00000000)
- dut->if_instr = 0x00000000;
- dut->eval();
- check(dut->id_illegal_instr == 1, "Illegal_Instr");
-
- // -----------------
- // Test 10: ECALL (0x00000073)
- dut->if_instr = 0x00000073;
- dut->eval();
- check(dut->id_is_ecall == 1, "ECALL");
-
- // -----------------
- // Test 11: LUI x1, 0x12345 (0x123450B7)
- dut->if_instr = 0x123450B7;
- dut->eval();
- check(dut->id_reg_write_en == 1, "LUI_reg_write_en");
- check(dut->id_imm == 0x12345000ULL, "LUI_imm");
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << "\033[32mAll " << pass_count << " tests passed!\033[0m" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- delete dut;
- return 0;
-}
diff --git a/rv64cpu/tb/unit/tb_if_id_reg.cc b/rv64cpu/tb/unit/tb_if_id_reg.cc
deleted file mode 100644
index 6428e6f..0000000
--- a/rv64cpu/tb/unit/tb_if_id_reg.cc
+++ /dev/null
@@ -1,127 +0,0 @@
-// 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 <https://www.gnu.org/licenses/>.
-
-// File path: tb/unit/tb_if_id_reg.cc
-// Description: IF/ID Pipeline Register unit test
-
-#include <iostream>
-#include <iomanip>
-#include <cstdint>
-#include <cassert>
-#include <verilated.h>
-#include "Vif_id_reg.h"
-
-int pass_count = 0;
-
-void tick(Vif_id_reg* dut) {
- dut->clk = 0;
- dut->eval();
- dut->clk = 1;
- dut->eval();
- dut->clk = 0;
- dut->eval();
-}
-
-void check_reg(uint64_t expected_pc, uint32_t expected_instr, uint64_t actual_pc, uint32_t actual_instr, const char* test_name) {
- if (expected_pc == actual_pc && expected_instr == actual_instr) {
- std::cout << "\033[32m[PASS] " << test_name << " | PC = 0x" << std::hex << actual_pc << " Instr = 0x" << actual_instr << "\033[0m" << std::dec << std::endl;
- pass_count++;
- } else {
- std::cout << "\033[31m[FAIL] " << test_name << "\n Expected: PC=0x" << std::hex << expected_pc << " Instr=0x" << expected_instr
- << "\n Got: PC=0x" << actual_pc << " Instr=0x" << actual_instr << "\033[0m" << std::dec << std::endl;
- assert(false);
- }
-}
-
-int main(int argc, char** argv) {
- Verilated::commandArgs(argc, argv);
- Vif_id_reg* dut = new Vif_id_reg;
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << " IF/ID Register Test" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- // --- 1. Init / Reset ---
- dut->stall_en = 0;
- dut->flush_en = 0;
- dut->if_pc = 0x80000000;
- dut->if_instr = 0xDEADBEEF;
-
- dut->rst = 1;
- tick(dut);
- dut->rst = 0;
-
- // After reset, PC should be 0, instr should be NOP (0x00000013)
- check_reg(0, 0x00000013, dut->id_pc, dut->id_instr, "Reset_Inserts_NOP");
-
- // --- 2. Normal Propagation ---
- dut->if_pc = 0x80001000;
- dut->if_instr = 0x02A00093; // ADDI x1, x0, 42
- tick(dut);
- check_reg(0x80001000, 0x02A00093, dut->id_pc, dut->id_instr, "Normal_Propagation");
-
- // --- 3. Stall (Freeze) ---
- dut->if_pc = 0x80001004;
- dut->if_instr = 0x000280B3; // New incoming data
- dut->stall_en = 1;
- tick(dut); // Clock ticks but stall is active
-
- // Register should hold previous values
- check_reg(0x80001000, 0x02A00093, dut->id_pc, dut->id_instr, "Stall_Holds_Previous_Data");
-
- dut->if_pc = 0x80001008;
- dut->if_instr = 0x008000EF;
- tick(dut); // Still stalled
- check_reg(0x80001000, 0x02A00093, dut->id_pc, dut->id_instr, "Stall_Multiple_Cycles");
-
- // Release stall
- dut->stall_en = 0;
- tick(dut);
- check_reg(0x80001008, 0x008000EF, dut->id_pc, dut->id_instr, "Release_Stall_Captures_New");
-
- // --- 4. Flush (Insert NOP) ---
- dut->if_pc = 0x8000100C;
- dut->if_instr = 0xDEADBEEF; // Some invalid instruction
- dut->flush_en = 1;
- tick(dut);
-
- // PC should be 0, instr should be NOP (0x00000013)
- check_reg(0, 0x00000013, dut->id_pc, dut->id_instr, "Flush_Inserts_NOP");
-
- // --- 5. Stall overrides Flush ? OR Flush overrides Stall ? ---
- // Usually, in RISC-V pipelines, if we have a branch mispredict (Flush),
- // we want to flush even if stalled, OR flush takes priority.
- // Based on if_id_reg.sv, `flush_en` has higher priority than `stall_en`.
-
- // Pre-load good data
- dut->flush_en = 0;
- dut->if_pc = 0x80002000;
- dut->if_instr = 0x12345678;
- tick(dut);
- check_reg(0x80002000, 0x12345678, dut->id_pc, dut->id_instr, "Pre_Flush_Stall_Test");
-
- // Apply simultaneous flush & stall
- dut->flush_en = 1;
- dut->stall_en = 1;
- tick(dut);
- check_reg(0, 0x00000013, dut->id_pc, dut->id_instr, "Flush_Overrides_Stall");
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << "\033[32mAll " << pass_count << " tests passed!\033[0m" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- delete dut;
- return 0;
-}
diff --git a/rv64cpu/tb/unit/tb_if_stage.cc b/rv64cpu/tb/unit/tb_if_stage.cc
deleted file mode 100644
index f0110c9..0000000
--- a/rv64cpu/tb/unit/tb_if_stage.cc
+++ /dev/null
@@ -1,132 +0,0 @@
-// 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 <https://www.gnu.org/licenses/>.
-
-// File path: tb/unit/tb_if_stage.cc
-// Description: IF Stage unit test
-
-#include <iostream>
-#include <iomanip>
-#include <cstdint>
-#include <cassert>
-#include <verilated.h>
-#include "Vif_stage.h"
-
-void tick(Vif_stage* dut) {
- dut->clk = 0;
- dut->eval();
- dut->clk = 1;
- dut->eval();
- dut->clk = 0;
- dut->eval();
-}
-
-void check_pc(uint64_t expected_pc, uint64_t actual_pc, const char* test_name) {
- if (expected_pc == actual_pc) {
- std::cout << "\033[32m[PASS] " << test_name << " | PC = 0x" << std::hex << actual_pc << "\033[0m" << std::dec << std::endl;
- } else {
- std::cout << "\033[31m[FAIL] " << test_name << " | Expected PC = 0x" << std::hex << expected_pc << " | Got PC = 0x" << actual_pc << "\033[0m" << std::dec << std::endl;
- assert(false);
- }
-}
-
-int main(int argc, char** argv) {
- Verilated::commandArgs(argc, argv);
- Vif_stage* dut = new Vif_stage;
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << " IF Stage Test" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- // --- 1. Init / Reset ---
- dut->stall_en = 0;
- dut->pc_redirect_sel = 0;
- dut->pc_redirect_target = 0;
- dut->pc_redirect_is_trap = 0;
-
- dut->rst = 1;
- tick(dut);
- dut->rst = 0;
- check_pc(0x80000000ULL, dut->if_pc, "Reset_Boot_Address");
-
- // --- 2. Normal Execution (PC + 4) ---
- tick(dut);
- check_pc(0x80000004ULL, dut->if_pc, "Normal_Increment_1");
- tick(dut);
- check_pc(0x80000008ULL, dut->if_pc, "Normal_Increment_2");
-
- // --- 3. Pipeline Stall ---
- dut->stall_en = 1;
- tick(dut);
- check_pc(0x80000008ULL, dut->if_pc, "Stall_Maintains_PC");
- tick(dut);
- check_pc(0x80000008ULL, dut->if_pc, "Stall_Maintains_PC_Again");
-
- // Release Stall
- dut->stall_en = 0;
- tick(dut);
- check_pc(0x8000000CULL, dut->if_pc, "Release_Stall_Normal_Increment");
-
- // --- 4. Branch Taken ---
- dut->pc_redirect_sel = 1;
- dut->pc_redirect_is_trap = 0;
- dut->pc_redirect_target = 0x80000100ULL;
- tick(dut);
- check_pc(0x80000100ULL, dut->if_pc, "Branch_Taken_Updates_PC");
-
- // Return to normal
- dut->pc_redirect_sel = 0;
- tick(dut);
- check_pc(0x80000104ULL, dut->if_pc, "Post_Branch_Normal_Increment");
-
- // --- 5. Stall and Branch simultaneous priority ---
- // If a branch is taken but the pipeline is stalled, the PC should freeze.
- // However, if the stall clears later, the branch target should theoretically be taken.
- // In our RV64 simple pipeline design, stall_en freezes the PC reg completely.
- dut->pc_redirect_sel = 1;
- dut->pc_redirect_is_trap = 0;
- dut->pc_redirect_target = 0x80000200ULL;
- dut->stall_en = 1;
- tick(dut);
- check_pc(0x80000104ULL, dut->if_pc, "Stall_Overrides_Branch_Target_Load");
-
- dut->stall_en = 0;
- tick(dut);
- check_pc(0x80000200ULL, dut->if_pc, "Branch_Target_Loaded_After_Stall_Clears");
-
- // --- 6. Trap redirect overrides stall ---
- dut->pc_redirect_sel = 0;
- dut->pc_redirect_is_trap = 0;
- dut->pc_redirect_target = 0;
-
- dut->stall_en = 1;
- dut->pc_redirect_sel = 1;
- dut->pc_redirect_is_trap = 1;
- dut->pc_redirect_target = 0x80000300ULL;
- tick(dut);
- check_pc(0x80000300ULL, dut->if_pc, "Trap_Overrides_Stall");
-
- dut->stall_en = 0;
- dut->pc_redirect_sel = 0;
- dut->pc_redirect_is_trap = 0;
- tick(dut);
- check_pc(0x80000304ULL, dut->if_pc, "Resume_After_Trap");
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << "\033[32mAll tests passed!\033[0m" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- delete dut;
- return 0;
-} \ No newline at end of file
diff --git a/rv64cpu/tb/unit/tb_mem_stage.cc b/rv64cpu/tb/unit/tb_mem_stage.cc
deleted file mode 100644
index 417cb95..0000000
--- a/rv64cpu/tb/unit/tb_mem_stage.cc
+++ /dev/null
@@ -1,305 +0,0 @@
-// 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 <https://www.gnu.org/licenses/>.
-
-// File path: tb/unit/tb_mem_stage.cc
-// Description: Memory Stage unit test
-
-#include <iostream>
-#include <cstdint>
-#include <cassert>
-#include <verilated.h>
-#include "Vmem_stage.h"
-
-int pass_count = 0;
-
-void tick(Vmem_stage* dut) {
- dut->eval();
-}
-
-void clock_tick(Vmem_stage* dut) {
- dut->clk = 0;
- dut->eval();
- dut->clk = 1;
- dut->eval();
- dut->clk = 0;
- dut->eval();
-}
-
-void check(bool cond, const char* name) {
- if (cond) {
- std::cout << "\033[32m[PASS] " << name << "\033[0m" << std::endl;
- pass_count++;
- } else {
- std::cout << "\033[31m[FAIL] " << name << "\033[0m" << std::endl;
- assert(false);
- }
-}
-
-int main(int argc, char** argv) {
- Verilated::commandArgs(argc, argv);
- Vmem_stage* dut = new Vmem_stage;
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << " MEM Stage Test" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- // 1. Initial / Default states
- dut->clk = 0;
- dut->rst = 1;
- dut->mem_alu_result = 0;
- dut->mem_rs2_rdata = 0;
- dut->mem_is_load = 0;
- dut->mem_is_store = 0;
- dut->mem_is_amo = 0;
- dut->mem_amo_op = 0;
- dut->mem_mem_size = 0;
- dut->dmem_req_ready = 1;
- dut->dmem_rsp_valid = 1;
- dut->dmem_rdata = 0;
-
- clock_tick(dut);
- dut->rst = 0;
- tick(dut);
-
- check(dut->dmem_read_req == 0, "No_Read_Req");
- check(dut->dmem_write_req == 0, "No_Write_Req");
- check(dut->mem_stall_req == 0, "No_Stall");
-
- // 2. Passthrough signals
- dut->mem_reg_write_en = 1;
- dut->mem_rd_addr = 15;
- dut->mem_wb_sel = 2; // PC+4
- dut->mem_pc = 0x80001000;
- dut->mem_is_csr = 1;
- dut->mem_csr_op = 2; // CSRRS
- dut->mem_rs1_rdata = 0x1234;
- dut->mem_rs1_addr = 5;
- dut->mem_imm = 0x777;
-
- tick(dut);
-
- check(dut->mem_wb_reg_write_en == 1, "Passthrough_RegWriteEn");
- check(dut->mem_wb_rd_addr == 15, "Passthrough_RdAddr");
- check(dut->mem_wb_wb_sel == 2, "Passthrough_WbSel");
- check(dut->mem_wb_pc == 0x80001000, "Passthrough_PC");
- check(dut->mem_wb_is_csr == 1, "Passthrough_IsCSR");
- check(dut->mem_wb_csr_op == 2, "Passthrough_CSROp");
- check(dut->mem_wb_rs1_rdata == 0x1234, "Passthrough_RS1Rdata");
- check(dut->mem_wb_rs1_addr == 5, "Passthrough_RS1Addr");
- check(dut->mem_wb_imm == 0x777, "Passthrough_Imm");
-
- // 3. Store Alignment (SB, SH, SW, SD)
- dut->mem_is_store = 1;
- dut->dmem_req_ready = 1;
- dut->mem_rs2_rdata = 0x1122334455667788ULL;
-
- // SB at offset 0
- dut->mem_mem_size = 0; // SB
- dut->mem_alu_result = 0x1000;
- tick(dut);
- check(dut->dmem_write_req == 1, "Store_Req");
- check(dut->dmem_addr == 0x1000, "Store_Addr");
- check(dut->dmem_wdata == 0x88, "SB_Data_0");
- check(dut->dmem_wstrb == 0x01, "SB_Strobe_0");
-
- // SB at offset 3
- dut->mem_alu_result = 0x1003;
- tick(dut);
- check(dut->dmem_wdata == 0x88000000ULL, "SB_Data_3");
- check(dut->dmem_wstrb == 0x08, "SB_Strobe_3");
-
- // SH at offset 2
- dut->mem_mem_size = 1; // SH
- dut->mem_alu_result = 0x1002;
- tick(dut);
- check(dut->dmem_wdata == 0x77880000ULL, "SH_Data_2");
- check(dut->dmem_wstrb == 0x0C, "SH_Strobe_2");
-
- // SW at offset 4
- dut->mem_mem_size = 2; // SW
- dut->mem_alu_result = 0x1004;
- tick(dut);
- check(dut->dmem_wdata == 0x5566778800000000ULL, "SW_Data_4");
- check(dut->dmem_wstrb == 0xF0, "SW_Strobe_4");
-
- // SD
- dut->mem_mem_size = 3; // SD
- dut->mem_alu_result = 0x1000;
- tick(dut);
- check(dut->dmem_wdata == 0x1122334455667788ULL, "SD_Data");
- check(dut->dmem_wstrb == 0xFF, "SD_Strobe");
-
- dut->mem_is_store = 0;
-
- // 4. Load Alignment and Extension (LB, LH, LW, LD, LBU, LHU, LWU)
- dut->mem_is_load = 1;
- dut->dmem_rdata = 0xAABBCCDDEEFF0011ULL;
- dut->dmem_rsp_valid = 1;
-
- // LB at offset 0 (0x11) -> sign extended
- dut->mem_mem_size = 0; // LB
- dut->mem_alu_result = 0x2000;
- tick(dut);
- check(dut->dmem_read_req == 1, "Load_Req");
- check(dut->mem_wb_mem_rdata == 0x11, "LB_Data_0_Pos");
-
- // LB at offset 1 (0x00)
- dut->mem_alu_result = 0x2001;
- tick(dut);
- check(dut->mem_wb_mem_rdata == 0x0, "LB_Data_1_Zero");
-
- // LB at offset 7 (0xAA) -> sign extended
- dut->mem_alu_result = 0x2007;
- tick(dut);
- check(dut->mem_wb_mem_rdata == 0xFFFFFFFFFFFFFFAAULL, "LB_Data_7_Neg");
-
- // LBU at offset 7 (0xAA) -> zero extended
- dut->mem_mem_size = 4; // LBU
- dut->mem_alu_result = 0x2007;
- tick(dut);
- check(dut->mem_wb_mem_rdata == 0xAA, "LBU_Data_7");
-
- // LH at offset 2 (0xEEFF) -> sign extended
- dut->mem_mem_size = 1; // LH
- dut->mem_alu_result = 0x2002;
- tick(dut);
- check(dut->mem_wb_mem_rdata == 0xFFFFFFFFFFFFEEFFULL, "LH_Data_2_Neg");
-
- // LHU at offset 2 (0xEEFF) -> zero extended
- dut->mem_mem_size = 5; // LHU
- dut->mem_alu_result = 0x2002;
- tick(dut);
- check(dut->mem_wb_mem_rdata == 0xEEFF, "LHU_Data_2");
-
- // LW at offset 4 (0xAABBCCDD) -> sign extended
- dut->mem_mem_size = 2; // LW
- dut->mem_alu_result = 0x2004;
- tick(dut);
- check(dut->mem_wb_mem_rdata == 0xFFFFFFFFAABBCCDDULL, "LW_Data_4_Neg");
-
- // LWU at offset 4 (0xAABBCCDD) -> zero extended
- dut->mem_mem_size = 6; // LWU
- dut->mem_alu_result = 0x2004;
- tick(dut);
- check(dut->mem_wb_mem_rdata == 0xAABBCCDD, "LWU_Data_4");
-
- // LD
- dut->mem_mem_size = 3; // LD
- dut->mem_alu_result = 0x2000;
- tick(dut);
- check(dut->mem_wb_mem_rdata == 0xAABBCCDDEEFF0011ULL, "LD_Data");
-
- dut->mem_is_load = 0;
-
- // 5. Atomic memory operations
- dut->mem_is_amo = 1;
- dut->mem_mem_size = 2; // AMO.W
- dut->mem_amo_op = 4; // AMO_ADD
- dut->mem_alu_result = 0x3004;
- dut->mem_rs2_rdata = 3;
- dut->dmem_rdata = 0x0000000500000000ULL;
- dut->dmem_req_ready = 1;
- dut->dmem_rsp_valid = 1;
- tick(dut);
- check(dut->dmem_read_req == 1, "AMOADDW_ReadReq");
- check(dut->dmem_write_req == 0, "AMOADDW_NoWriteDuringRead");
- check(dut->mem_stall_req == 1, "AMOADDW_StallDuringRead");
- clock_tick(dut);
-
- tick(dut);
- check(dut->dmem_read_req == 0, "AMOADDW_NoReadDuringWrite");
- check(dut->dmem_write_req == 1, "AMOADDW_WriteReq");
- check(dut->dmem_wdata == 0x0000000800000000ULL, "AMOADDW_WriteData");
- check(dut->dmem_wstrb == 0xF0, "AMOADDW_Strobe");
- check(dut->mem_wb_mem_rdata == 5, "AMOADDW_ReturnOldValue");
- check(dut->mem_stall_req == 0, "AMOADDW_NoStallDuringWrite");
- clock_tick(dut);
-
- dut->mem_mem_size = 3; // AMO.D
- dut->mem_amo_op = 1; // AMO_LR
- dut->mem_alu_result = 0x4000;
- dut->dmem_rdata = 0x123456789ABCDEF0ULL;
- tick(dut);
- check(dut->dmem_read_req == 1, "LRD_ReadReq");
- check(dut->dmem_write_req == 0, "LRD_NoWrite");
- check(dut->mem_wb_mem_rdata == 0x123456789ABCDEF0ULL, "LRD_ReturnData");
- clock_tick(dut);
-
- dut->mem_amo_op = 2; // AMO_SC
- dut->mem_rs2_rdata = 0xCAFEBABE11223344ULL;
- tick(dut);
- check(dut->dmem_read_req == 0, "SCD_NoRead");
- check(dut->dmem_write_req == 1, "SCD_SuccessWrite");
- check(dut->dmem_wdata == 0xCAFEBABE11223344ULL, "SCD_WriteData");
- check(dut->mem_wb_mem_rdata == 0, "SCD_SuccessStatus");
- clock_tick(dut);
-
- tick(dut);
- check(dut->dmem_write_req == 0, "SCD_FailNoWrite");
- check(dut->mem_wb_mem_rdata == 1, "SCD_FailStatus");
-
- dut->mem_amo_op = 1; // AMO_LR
- dut->mem_alu_result = 0x5000;
- dut->dmem_rdata = 0x1122334455667788ULL;
- tick(dut);
- check(dut->dmem_read_req == 1, "LRD_BeforeStore_ReadReq");
- clock_tick(dut);
-
- dut->mem_is_amo = 0;
- dut->mem_is_store = 1;
- dut->mem_mem_size = 0; // SB
- dut->mem_alu_result = 0x5003;
- dut->mem_rs2_rdata = 0xAA;
- tick(dut);
- check(dut->dmem_write_req == 1, "StoreAfterLR_WriteReq");
- clock_tick(dut);
-
- dut->mem_is_store = 0;
- dut->mem_is_amo = 1;
- dut->mem_mem_size = 3; // AMO.D
- dut->mem_amo_op = 2; // AMO_SC
- dut->mem_alu_result = 0x5000;
- dut->mem_rs2_rdata = 0x0102030405060708ULL;
- tick(dut);
- check(dut->dmem_write_req == 1, "SCD_AfterStore_WriteReq");
- check(dut->mem_wb_mem_rdata == 0, "SCD_AfterStore_SuccessStatus");
-
- dut->mem_is_amo = 0;
- dut->mem_is_store = 0;
-
- // 6. Stall Logic
- dut->mem_is_load = 1;
- dut->mem_mem_size = 3;
- dut->dmem_req_ready = 0; // Cache miss / AXI busy
- dut->dmem_rsp_valid = 0;
- tick(dut);
- check(dut->mem_stall_req == 1, "Stall_On_Req_NotReady");
-
- dut->dmem_req_ready = 1;
- dut->dmem_rsp_valid = 0; // Read request accepted, but data not valid yet
- tick(dut);
- check(dut->mem_stall_req == 1, "Stall_On_Rsp_NotValid");
-
- dut->dmem_rsp_valid = 1; // Read response arrived
- tick(dut);
- check(dut->mem_stall_req == 0, "No_Stall_On_RspValid");
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << "\033[32mAll " << pass_count << " tests passed!\033[0m" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- delete dut;
- return 0;
-}
diff --git a/rv64cpu/tb/unit/tb_mem_wb_reg.cc b/rv64cpu/tb/unit/tb_mem_wb_reg.cc
deleted file mode 100644
index e91a354..0000000
--- a/rv64cpu/tb/unit/tb_mem_wb_reg.cc
+++ /dev/null
@@ -1,165 +0,0 @@
-// 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 <https://www.gnu.org/licenses/>.
-
-// File path: tb/unit/tb_mem_wb_reg.cc
-// Description: MEM/WB Stage Register unit test
-
-#include <iostream>
-#include <cassert>
-#include <verilated.h>
-#include "Vmem_wb_reg.h"
-
-int pass_count = 0;
-
-void tick(Vmem_wb_reg* dut) {
- dut->clk = 0;
- dut->eval();
- dut->clk = 1;
- dut->eval();
- dut->clk = 0;
- dut->eval();
-}
-
-void check(bool cond, const char* name) {
- if (cond) {
- std::cout << "\033[32m[PASS] " << name << "\033[0m" << std::endl;
- pass_count++;
- } else {
- std::cout << "\033[31m[FAIL] " << name << "\033[0m" << std::endl;
- assert(false);
- }
-}
-
-int main(int argc, char** argv) {
- Verilated::commandArgs(argc, argv);
- Vmem_wb_reg* dut = new Vmem_wb_reg;
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << " MEM/WB Register Test" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- // 1. Init / Reset
- dut->stall_en = 0;
- dut->flush_en = 0;
-
- // Set some dangerous inputs
- dut->mem_pc = 0x80001000;
- dut->mem_alu_result = 0x11111111;
- dut->mem_mem_rdata = 0x22222222;
- dut->mem_reg_write_en = 1;
- dut->mem_rd_addr = 31;
- dut->mem_wb_sel = 1;
- dut->mem_is_csr = 1;
- dut->mem_csr_op = 2;
- dut->mem_rs1_rdata = 0x999;
- dut->mem_rs1_addr = 5;
- dut->mem_imm = 0x333;
- dut->mem_is_ecall = 1;
- dut->mem_illegal_instr = 1;
- dut->mem_is_sret = 1;
-
- dut->rst = 1;
- tick(dut);
- dut->rst = 0;
-
- check(dut->wb_pc == 0, "Reset_Clears_PC");
- check(dut->wb_alu_result == 0, "Reset_Clears_ALU");
- check(dut->wb_reg_write_en == 0, "Reset_Clears_RegWrite");
- check(dut->wb_is_csr == 0, "Reset_Clears_IsCSR");
- check(dut->wb_illegal_instr == 0, "Reset_Clears_IllegalInstr");
-
- // 2. Normal Propagation
- dut->mem_pc = 0x80002000;
- dut->mem_alu_result = 0xAAAAAAAA;
- dut->mem_mem_rdata = 0xBBBBBBBB;
- dut->mem_rd_addr = 15;
- dut->mem_reg_write_en = 1;
- dut->mem_wb_sel = 1;
- dut->mem_is_csr = 1;
- dut->mem_csr_op = 3;
- dut->mem_rs1_rdata = 0x777;
- dut->mem_rs1_addr = 6;
- dut->mem_imm = 0x888;
- dut->mem_is_ecall = 0;
-
- tick(dut);
-
- check(dut->wb_pc == 0x80002000, "Propagate_PC");
- check(dut->wb_alu_result == 0xAAAAAAAA, "Propagate_ALU_Result");
- check(dut->wb_mem_rdata == 0xBBBBBBBB, "Propagate_MemRData");
- check(dut->wb_rd_addr == 15, "Propagate_RdAddr");
- check(dut->wb_reg_write_en == 1, "Propagate_RegWriteEn");
- check(dut->wb_wb_sel == 1, "Propagate_WbSel");
- check(dut->wb_is_csr == 1, "Propagate_IsCSR");
- check(dut->wb_csr_op == 3, "Propagate_CSROp");
- check(dut->wb_rs1_rdata == 0x777, "Propagate_RS1_RData");
- check(dut->wb_rs1_addr == 6, "Propagate_RS1_Addr");
- check(dut->wb_imm == 0x888, "Propagate_Imm");
-
- // 3. Stall (Freeze)
- dut->stall_en = 1;
- dut->mem_pc = 0x80003000;
- dut->mem_rd_addr = 20;
- dut->mem_reg_write_en = 0;
-
- tick(dut);
-
- check(dut->wb_pc == 0x80002000, "Stall_Maintains_PC");
- check(dut->wb_rd_addr == 15, "Stall_Maintains_RdAddr");
- check(dut->wb_reg_write_en == 1, "Stall_Maintains_RegWriteEn");
-
- // Release Stall
- dut->stall_en = 0;
- tick(dut);
- check(dut->wb_pc == 0x80003000, "ReleaseStall_Captures_PC");
- check(dut->wb_rd_addr == 20, "ReleaseStall_Captures_RdAddr");
- check(dut->wb_reg_write_en == 0, "ReleaseStall_Captures_RegWriteEn");
-
- // 4. Flush (Insert Bubble/NOP)
- dut->flush_en = 1;
- dut->mem_pc = 0x80004000;
- dut->mem_alu_result = 0xCCCCCCCC;
- dut->mem_reg_write_en = 1;
- dut->mem_is_csr = 1;
-
- tick(dut);
-
- check(dut->wb_pc == 0x80003000, "Flush_Maintains_PC");
- check(dut->wb_alu_result == 0xAAAAAAAA, "Flush_Maintains_ALU");
- check(dut->wb_reg_write_en == 0, "Flush_Clears_RegWrite");
- check(dut->wb_is_csr == 0, "Flush_Clears_IsCSR");
-
- // 5. Flush Overrides Stall
- dut->flush_en = 0;
- dut->mem_pc = 0x80005000;
- dut->mem_reg_write_en = 1;
- tick(dut);
- check(dut->wb_pc == 0x80005000, "Pre_FlushStall_Setup");
-
- dut->flush_en = 1;
- dut->stall_en = 1;
- dut->mem_pc = 0x80006000;
- tick(dut);
-
- check(dut->wb_pc == 0x80005000, "Flush_Overrides_Stall_PC");
- check(dut->wb_reg_write_en == 0, "Flush_Overrides_Stall_RegWrite");
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << "\033[32mAll " << pass_count << " tests passed!\033[0m" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- delete dut;
- return 0;
-}
diff --git a/rv64cpu/tb/unit/tb_mmu_top.cc b/rv64cpu/tb/unit/tb_mmu_top.cc
deleted file mode 100644
index 5df90ef..0000000
--- a/rv64cpu/tb/unit/tb_mmu_top.cc
+++ /dev/null
@@ -1,236 +0,0 @@
-#include <cassert>
-#include <cstdint>
-#include <iostream>
-#include <unordered_map>
-
-#include <verilated.h>
-
-#include "Vmmu_top.h"
-
-namespace {
-
-constexpr uint8_t MODE_BARE = 0;
-constexpr uint8_t MODE_SV39 = 8;
-constexpr uint8_t MODE_SV48 = 9;
-
-constexpr uint64_t FLAG_V = 1ULL << 0;
-constexpr uint64_t FLAG_R = 1ULL << 1;
-constexpr uint64_t FLAG_W = 1ULL << 2;
-constexpr uint64_t FLAG_X = 1ULL << 3;
-constexpr uint64_t FLAG_A = 1ULL << 6;
-constexpr uint64_t FLAG_D = 1ULL << 7;
-
-int g_pass_count = 0;
-
-void check(bool cond, const char* name) {
- if (cond) {
- std::cout << "\033[32m[PASS] " << name << "\033[0m\n";
- ++g_pass_count;
- } else {
- std::cout << "\033[31m[FAIL] " << name << "\033[0m\n";
- assert(false);
- }
-}
-
-uint64_t make_satp(uint8_t mode, uint16_t asid, uint64_t ppn) {
- return (uint64_t(mode) << 60) | (uint64_t(asid) << 44) | (ppn & ((1ULL << 44) - 1ULL));
-}
-
-uint64_t make_pte(uint64_t ppn, uint64_t flags) {
- return ((ppn & ((1ULL << 44) - 1ULL)) << 10) | (flags & 0x3ffULL);
-}
-
-void tick(Vmmu_top* dut) {
- dut->clk = 0;
- dut->eval();
- dut->clk = 1;
- dut->eval();
-}
-
-void reset(Vmmu_top* dut) {
- dut->rst = 1;
- dut->i_req_valid = 0;
- dut->d_req_valid = 0;
- dut->d_req_is_store = 0;
- dut->flush_all = 0;
- dut->flush_asid_valid = 0;
- dut->flush_asid = 0;
- dut->flush_vpn_valid = 0;
- dut->flush_vpn = 0;
- dut->ptw_mem_req_ready = 1;
- dut->ptw_mem_resp_valid = 0;
- dut->ptw_mem_resp_data = 0;
- tick(dut);
- tick(dut);
- dut->rst = 0;
- tick(dut);
-}
-
-struct StepResult {
- bool issued;
- uint64_t req_addr;
- bool i_resp_valid;
- bool i_resp_hit;
- bool i_resp_page_fault;
- uint64_t i_resp_paddr;
- bool d_resp_valid;
- bool d_resp_hit;
- bool d_resp_page_fault;
- uint64_t d_resp_paddr;
-};
-
-StepResult step(Vmmu_top* dut, bool inject_resp, uint64_t resp_data) {
- dut->ptw_mem_req_ready = 1;
- dut->ptw_mem_resp_valid = inject_resp ? 1 : 0;
- dut->ptw_mem_resp_data = resp_data;
-
- dut->clk = 0;
- dut->eval();
- const bool issued = dut->ptw_mem_req_valid && dut->ptw_mem_req_ready;
- const uint64_t req_addr = dut->ptw_mem_req_addr;
- StepResult result{
- issued,
- req_addr,
- bool(dut->i_resp_valid),
- bool(dut->i_resp_hit),
- bool(dut->i_resp_page_fault),
- dut->i_resp_paddr,
- bool(dut->d_resp_valid),
- bool(dut->d_resp_hit),
- bool(dut->d_resp_page_fault),
- dut->d_resp_paddr,
- };
-
- dut->clk = 1;
- dut->eval();
-
- return result;
-}
-
-void add_sv39_mapping(
- std::unordered_map<uint64_t, uint64_t>& memory,
- uint64_t root_ppn,
- uint64_t vaddr,
- uint64_t l1_ppn,
- uint64_t l0_ppn,
- uint64_t leaf_ppn,
- uint64_t leaf_flags
-) {
- const uint64_t vpn2 = (vaddr >> 30) & 0x1ffULL;
- const uint64_t vpn1 = (vaddr >> 21) & 0x1ffULL;
- const uint64_t vpn0 = (vaddr >> 12) & 0x1ffULL;
- memory[(root_ppn << 12) + (vpn2 << 3)] = make_pte(l1_ppn, FLAG_V);
- memory[(l1_ppn << 12) + (vpn1 << 3)] = make_pte(l0_ppn, FLAG_V);
- memory[(l0_ppn << 12) + (vpn0 << 3)] = make_pte(leaf_ppn, leaf_flags);
-}
-
-StepResult run_until_response(
- Vmmu_top* dut,
- const std::unordered_map<uint64_t, uint64_t>& memory,
- bool want_d
-) {
- bool inject_resp = false;
- uint64_t next_resp = 0;
- StepResult last{};
-
- for (int cycle = 0; cycle < 100; ++cycle) {
- last = step(dut, inject_resp, next_resp);
- if ((!want_d && last.i_resp_valid) || (want_d && last.d_resp_valid)) {
- return last;
- }
-
- inject_resp = false;
- next_resp = 0;
- if (last.issued) {
- const auto it = memory.find(last.req_addr);
- next_resp = (it == memory.end()) ? 0 : it->second;
- inject_resp = true;
- }
- }
-
- check(false, "MMU_ResponseTimeout");
- return last;
-}
-
-} // namespace
-
-int main(int argc, char** argv) {
- Verilated::commandArgs(argc, argv);
- auto* dut = new Vmmu_top;
-
- std::cout << "----------------------------------\n";
- std::cout << " MMU Top Unit Test\n";
- std::cout << "----------------------------------\n";
-
- dut->satp_value = make_satp(MODE_BARE, 0, 0);
- dut->priv_mode = 1;
- dut->sum = 0;
- dut->mxr = 0;
- reset(dut);
-
- dut->i_req_valid = 1;
- dut->i_req_vaddr = 0x80001234ULL;
- dut->d_req_valid = 1;
- dut->d_req_vaddr = 0x90005678ULL;
- dut->d_req_is_store = 0;
- dut->eval();
- check(dut->i_resp_valid && dut->i_resp_hit && dut->i_resp_paddr == 0x80001234ULL, "Bare_I_Bypass");
- check(dut->d_resp_valid && dut->d_resp_hit && dut->d_resp_paddr == 0x90005678ULL, "Bare_D_Bypass");
- dut->i_req_valid = 0;
- dut->d_req_valid = 0;
- tick(dut);
-
- dut->satp_value = make_satp(MODE_SV48, 1, 0x100);
- dut->i_req_valid = 1;
- dut->i_req_vaddr = 0x0000000040401000ULL;
- dut->eval();
- check(dut->i_resp_valid && dut->i_resp_page_fault, "Sv48_Stub_IFault");
- dut->i_req_valid = 0;
- tick(dut);
-
- const uint64_t root_ppn = 0x100;
- const uint64_t i_va = 0x0000000040401234ULL;
- const uint64_t d_va = 0x0000000080505678ULL;
- const uint64_t i_leaf_ppn = 0x45670;
- const uint64_t d_leaf_ppn = 0x56780;
- std::unordered_map<uint64_t, uint64_t> memory;
- add_sv39_mapping(memory, root_ppn, i_va, 0x210, 0x220, i_leaf_ppn,
- FLAG_V | FLAG_R | FLAG_X | FLAG_A);
- add_sv39_mapping(memory, root_ppn, d_va, 0x230, 0x240, d_leaf_ppn,
- FLAG_V | FLAG_R | FLAG_W | FLAG_A | FLAG_D);
-
- dut->satp_value = make_satp(MODE_SV39, 1, root_ppn);
- dut->i_req_valid = 1;
- dut->i_req_vaddr = i_va;
- StepResult i_resp = run_until_response(dut, memory, false);
- check(i_resp.i_resp_hit && !i_resp.i_resp_page_fault, "Sv39_I_Walk_Hit");
- check(i_resp.i_resp_paddr == ((i_leaf_ppn << 12) | (i_va & 0xfffULL)), "Sv39_I_Walk_Paddr");
- dut->i_req_valid = 0;
- tick(dut);
-
- dut->d_req_valid = 1;
- dut->d_req_vaddr = d_va;
- dut->d_req_is_store = 1;
- StepResult d_resp = run_until_response(dut, memory, true);
- check(d_resp.d_resp_hit && !d_resp.d_resp_page_fault, "Sv39_D_Walk_Hit");
- check(d_resp.d_resp_paddr == ((d_leaf_ppn << 12) | (d_va & 0xfffULL)), "Sv39_D_Walk_Paddr");
-
- dut->d_req_valid = 0;
- tick(dut);
- dut->i_req_valid = 1;
- dut->i_req_vaddr = i_va;
- dut->d_req_valid = 1;
- dut->d_req_vaddr = d_va;
- dut->d_req_is_store = 1;
- dut->eval();
- check(dut->i_resp_valid && dut->i_resp_hit, "Sv39_I_TLB_Hit");
- check(dut->d_resp_valid && dut->d_resp_hit, "Sv39_D_TLB_Hit");
- check(dut->ptw_mem_req_valid == 0, "Sv39_TLB_Hit_NoPTW");
-
- std::cout << "----------------------------------\n";
- std::cout << "All " << g_pass_count << " MMU top tests passed!\n";
- std::cout << "----------------------------------\n";
-
- delete dut;
- return 0;
-}
diff --git a/rv64cpu/tb/unit/tb_ptw.cc b/rv64cpu/tb/unit/tb_ptw.cc
deleted file mode 100644
index f32de41..0000000
--- a/rv64cpu/tb/unit/tb_ptw.cc
+++ /dev/null
@@ -1,206 +0,0 @@
-#include <cassert>
-#include <cstdint>
-#include <iostream>
-#include <unordered_map>
-
-#include <verilated.h>
-
-#include "Vptw.h"
-
-namespace {
-
-constexpr uint8_t MODE_BARE = 0;
-constexpr uint8_t MODE_SV39 = 8;
-constexpr uint8_t MODE_SV48 = 9;
-constexpr uint8_t MODE_SV57 = 10;
-constexpr uint8_t MODE_SV64 = 11;
-
-constexpr uint64_t FLAG_V = 1ULL << 0;
-constexpr uint64_t FLAG_R = 1ULL << 1;
-constexpr uint64_t FLAG_W = 1ULL << 2;
-constexpr uint64_t FLAG_X = 1ULL << 3;
-constexpr uint64_t FLAG_A = 1ULL << 6;
-constexpr uint64_t FLAG_D = 1ULL << 7;
-
-int g_pass_count = 0;
-
-void check(bool cond, const char* name) {
- if (cond) {
- std::cout << "\033[32m[PASS] " << name << "\033[0m\n";
- ++g_pass_count;
- } else {
- std::cout << "\033[31m[FAIL] " << name << "\033[0m\n";
- assert(false);
- }
-}
-
-uint64_t make_satp(uint8_t mode, uint16_t asid, uint64_t ppn) {
- return (uint64_t(mode) << 60) | (uint64_t(asid) << 44) | (ppn & ((1ULL << 44) - 1ULL));
-}
-
-uint64_t make_pte(uint64_t ppn, uint64_t flags) {
- return ((ppn & ((1ULL << 44) - 1ULL)) << 10) | (flags & 0x3ffULL);
-}
-
-void tick(Vptw* dut) {
- dut->clk = 0;
- dut->eval();
- dut->clk = 1;
- dut->eval();
-}
-
-void reset(Vptw* dut) {
- dut->rst = 1;
- dut->req_valid = 0;
- dut->mem_req_ready = 1;
- dut->mem_resp_valid = 0;
- dut->mem_resp_data = 0;
- tick(dut);
- tick(dut);
- dut->rst = 0;
- tick(dut);
-}
-
-void launch(
- Vptw* dut,
- uint64_t vaddr,
- uint8_t mode,
- bool fetch,
- bool store,
- uint64_t satp
-) {
- for (int i = 0; i < 8 && !dut->req_ready; ++i) {
- dut->req_valid = 0;
- dut->mem_resp_valid = 0;
- tick(dut);
- }
-
- dut->req_valid = 1;
- dut->req_vaddr = vaddr;
- dut->req_asid = 1;
- dut->req_mode = mode;
- dut->req_priv = 1;
- dut->req_is_fetch = fetch;
- dut->req_is_store = store;
- dut->req_sum = 0;
- dut->req_mxr = 0;
- dut->satp_value = satp;
- tick(dut);
- dut->req_valid = 0;
-}
-
-void run_walk(Vptw* dut, const std::unordered_map<uint64_t, uint64_t>& memory) {
- bool inject_resp = false;
- uint64_t next_resp_data = 0;
-
- for (int cycle = 0; cycle < 80; ++cycle) {
- dut->mem_req_ready = 1;
- dut->mem_resp_valid = inject_resp ? 1 : 0;
- dut->mem_resp_data = next_resp_data;
-
- dut->clk = 0;
- dut->eval();
-
- if (dut->resp_valid) {
- dut->mem_resp_valid = 0;
- return;
- }
-
- const bool issued = dut->mem_req_valid && dut->mem_req_ready;
- const uint64_t req_addr = dut->mem_req_addr;
-
- dut->clk = 1;
- dut->eval();
-
- inject_resp = false;
- next_resp_data = 0;
- if (issued) {
- const auto it = memory.find(req_addr);
- next_resp_data = (it == memory.end()) ? 0 : it->second;
- inject_resp = true;
- }
- }
-
- check(false, "PTW_Timeout");
-}
-
-void add_sv39_mapping(
- std::unordered_map<uint64_t, uint64_t>& memory,
- uint64_t root_ppn,
- uint64_t vaddr,
- uint64_t l1_ppn,
- uint64_t l0_ppn,
- uint64_t leaf_ppn,
- uint64_t leaf_flags
-) {
- const uint64_t vpn2 = (vaddr >> 30) & 0x1ffULL;
- const uint64_t vpn1 = (vaddr >> 21) & 0x1ffULL;
- const uint64_t vpn0 = (vaddr >> 12) & 0x1ffULL;
- memory[(root_ppn << 12) + (vpn2 << 3)] = make_pte(l1_ppn, FLAG_V);
- memory[(l1_ppn << 12) + (vpn1 << 3)] = make_pte(l0_ppn, FLAG_V);
- memory[(l0_ppn << 12) + (vpn0 << 3)] = make_pte(leaf_ppn, leaf_flags);
-}
-
-} // namespace
-
-int main(int argc, char** argv) {
- Verilated::commandArgs(argc, argv);
- auto* dut = new Vptw;
-
- std::cout << "----------------------------------\n";
- std::cout << " PTW Unit Test\n";
- std::cout << "----------------------------------\n";
-
- reset(dut);
-
- launch(dut, 0x0000000040401000ULL, MODE_SV48, false, false, make_satp(MODE_BARE, 1, 0x100));
- run_walk(dut, {});
- check(dut->resp_fault == 1, "Sv48_Stub_Fault");
-
- launch(dut, 0x0000000040401000ULL, MODE_SV57, false, false, make_satp(MODE_BARE, 1, 0x100));
- run_walk(dut, {});
- check(dut->resp_fault == 1, "Sv57_Stub_Fault");
-
- launch(dut, 0x0000000040401000ULL, MODE_SV64, false, false, make_satp(MODE_BARE, 1, 0x100));
- run_walk(dut, {});
- check(dut->resp_fault == 1, "Sv64_Stub_Fault");
-
- const uint64_t root_ppn = 0x100;
- const uint64_t va = 0x0000000040401234ULL;
- const uint64_t leaf_ppn = 0x45670;
- std::unordered_map<uint64_t, uint64_t> mem_ok;
- add_sv39_mapping(mem_ok, root_ppn, va, 0x210, 0x220, leaf_ppn,
- FLAG_V | FLAG_R | FLAG_W | FLAG_A | FLAG_D);
-
- launch(dut, va, MODE_SV39, false, false, make_satp(MODE_SV39, 1, root_ppn));
- run_walk(dut, mem_ok);
- check(dut->resp_fault == 0, "Sv39_Success_NoFault");
- check(dut->resp_paddr == ((leaf_ppn << 12) | (va & 0xfffULL)), "Sv39_Success_Paddr");
- check(dut->refill_valid == 1, "Sv39_Success_Refill");
- check(dut->refill_level == 0, "Sv39_Success_Level0");
-
- std::unordered_map<uint64_t, uint64_t> mem_no_x;
- add_sv39_mapping(mem_no_x, root_ppn, va, 0x230, 0x240, 0x50000, FLAG_V | FLAG_R | FLAG_A);
- launch(dut, va, MODE_SV39, true, false, make_satp(MODE_SV39, 1, root_ppn));
- run_walk(dut, mem_no_x);
- check(dut->resp_fault == 1, "Sv39_Fetch_NoX_Fault");
- check(dut->refill_valid == 0, "Sv39_Fetch_NoX_NoRefill");
-
- std::unordered_map<uint64_t, uint64_t> mem_store_d0;
- add_sv39_mapping(mem_store_d0, root_ppn, va, 0x250, 0x260, 0x60000,
- FLAG_V | FLAG_R | FLAG_W | FLAG_A);
- launch(dut, va, MODE_SV39, false, true, make_satp(MODE_SV39, 1, root_ppn));
- run_walk(dut, mem_store_d0);
- check(dut->resp_fault == 1, "Sv39_Store_D0_Fault");
-
- launch(dut, 0x0000008000000000ULL, MODE_SV39, false, false, make_satp(MODE_SV39, 1, root_ppn));
- run_walk(dut, {});
- check(dut->resp_fault == 1, "Sv39_NonCanonical_Fault");
-
- std::cout << "----------------------------------\n";
- std::cout << "All " << g_pass_count << " PTW tests passed!\n";
- std::cout << "----------------------------------\n";
-
- delete dut;
- return 0;
-}
diff --git a/rv64cpu/tb/unit/tb_regfile.cc b/rv64cpu/tb/unit/tb_regfile.cc
deleted file mode 100644
index 8e210ec..0000000
--- a/rv64cpu/tb/unit/tb_regfile.cc
+++ /dev/null
@@ -1,192 +0,0 @@
-// 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 <https://www.gnu.org/licenses/>.
-
-// File path: tb/unit/tb_regfile.cc
-// Description: Register file unit test
-
-#include <iostream>
-#include <cassert>
-#include <verilated.h>
-#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;
- dut->reg_write_en = 0; // Clear write enable from previous test
- 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)
- // With internal bypass added, combinational read should return the NEW bypassed value immediately.
- dut->reg_write_en = 0;
- dut->rd_addr = 15;
- dut->rd_wdata = 0x55555555;
- tick(dut); // Write 0x55555555 to x15 to set old value
-
- // Setup for RAW
- dut->reg_write_en = 1;
- dut->rd_addr = 15;
- dut->rd_wdata = 0x99999999;
-
- // Before tick, read should be the new bypassed value (0x99999999)
- dut->rs1_addr = 15;
- dut->eval();
- assert(dut->rs1_rdata == 0x99999999);
-
- 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);
-} \ No newline at end of file
diff --git a/rv64cpu/tb/unit/tb_tlb.cc b/rv64cpu/tb/unit/tb_tlb.cc
deleted file mode 100644
index 0a57738..0000000
--- a/rv64cpu/tb/unit/tb_tlb.cc
+++ /dev/null
@@ -1,172 +0,0 @@
-#include <cassert>
-#include <cstdint>
-#include <iostream>
-
-#include <verilated.h>
-
-#include "Vtlb.h"
-
-namespace {
-
-constexpr uint8_t MODE_BARE = 0;
-constexpr uint8_t MODE_SV39 = 8;
-constexpr uint8_t MODE_SV48 = 9;
-constexpr uint8_t MODE_SV57 = 10;
-constexpr uint8_t MODE_SV64 = 11;
-
-int g_pass_count = 0;
-
-void check(bool cond, const char* name) {
- if (cond) {
- std::cout << "\033[32m[PASS] " << name << "\033[0m\n";
- ++g_pass_count;
- } else {
- std::cout << "\033[31m[FAIL] " << name << "\033[0m\n";
- assert(false);
- }
-}
-
-void tick(Vtlb* dut) {
- dut->clk = 0;
- dut->eval();
- dut->clk = 1;
- dut->eval();
-}
-
-void clear_req(Vtlb* dut) {
- dut->req_valid = 0;
- dut->req_vaddr = 0;
- dut->req_asid = 0;
- dut->req_mode = MODE_SV39;
- dut->req_priv = 1;
- dut->req_is_fetch = 0;
- dut->req_is_store = 0;
- dut->req_sum = 0;
- dut->req_mxr = 0;
-}
-
-void clear_refill(Vtlb* dut) {
- dut->refill_valid = 0;
- dut->refill_vpn = 0;
- dut->refill_ppn = 0;
- dut->refill_level = 0;
- dut->refill_mode = MODE_SV39;
- dut->refill_asid = 0;
- dut->refill_g = 0;
- dut->refill_u = 0;
- dut->refill_r = 0;
- dut->refill_w = 0;
- dut->refill_x = 0;
- dut->refill_a = 0;
- dut->refill_d = 0;
-}
-
-void clear_flush(Vtlb* dut) {
- dut->flush_all = 0;
- dut->flush_asid_valid = 0;
- dut->flush_asid = 0;
- dut->flush_vpn_valid = 0;
- dut->flush_vpn = 0;
- dut->flush_mode = MODE_SV39;
-}
-
-void reset(Vtlb* dut) {
- dut->rst = 1;
- clear_req(dut);
- clear_refill(dut);
- clear_flush(dut);
- tick(dut);
- tick(dut);
- dut->rst = 0;
- dut->eval();
-}
-
-uint64_t sv39_vpn(uint64_t vaddr) {
- return (vaddr >> 12) & ((1ULL << 27) - 1ULL);
-}
-
-void refill(Vtlb* dut, uint64_t vpn, uint64_t ppn, bool r, bool w, bool x, bool a, bool d) {
- clear_refill(dut);
- dut->refill_valid = 1;
- dut->refill_vpn = vpn;
- dut->refill_ppn = ppn;
- dut->refill_level = 0;
- dut->refill_mode = MODE_SV39;
- dut->refill_asid = 1;
- dut->refill_r = r;
- dut->refill_w = w;
- dut->refill_x = x;
- dut->refill_a = a;
- dut->refill_d = d;
- tick(dut);
- clear_refill(dut);
- dut->eval();
-}
-
-void request(Vtlb* dut, uint64_t vaddr, uint8_t mode, bool fetch, bool store, bool mxr = false) {
- clear_req(dut);
- dut->req_valid = 1;
- dut->req_vaddr = vaddr;
- dut->req_asid = 1;
- dut->req_mode = mode;
- dut->req_priv = 1;
- dut->req_is_fetch = fetch;
- dut->req_is_store = store;
- dut->req_mxr = mxr;
- dut->eval();
-}
-
-} // namespace
-
-int main(int argc, char** argv) {
- Verilated::commandArgs(argc, argv);
- auto* dut = new Vtlb;
-
- std::cout << "----------------------------------\n";
- std::cout << " L1 TLB Unit Test\n";
- std::cout << "----------------------------------\n";
-
- reset(dut);
-
- request(dut, 0x123456789abcdef0ULL, MODE_BARE, false, false);
- check(dut->resp_hit == 1, "Bare_Bypass_Hit");
- check(dut->resp_page_fault == 0, "Bare_Bypass_NoFault");
- check(dut->resp_paddr == 0x123456789abcdef0ULL, "Bare_Bypass_Paddr");
-
- request(dut, 0x0000000040401000ULL, MODE_SV48, false, false);
- check(dut->resp_page_fault == 1, "Sv48_Stub_Fault");
- request(dut, 0x0000000040401000ULL, MODE_SV57, false, false);
- check(dut->resp_page_fault == 1, "Sv57_Stub_Fault");
- request(dut, 0x0000000040401000ULL, MODE_SV64, false, false);
- check(dut->resp_page_fault == 1, "Sv64_Stub_Fault");
-
- const uint64_t va = 0x0000000040401234ULL;
- const uint64_t ppn = 0x45670ULL;
- request(dut, va, MODE_SV39, false, false);
- check(dut->resp_hit == 0, "Sv39_Miss_NoHit");
- check(dut->resp_need_ptw == 1, "Sv39_Miss_NeedPTW");
- check(dut->resp_page_fault == 0, "Sv39_Miss_NoFault");
-
- refill(dut, sv39_vpn(va), ppn, true, true, false, true, true);
- request(dut, va, MODE_SV39, false, false);
- check(dut->resp_hit == 1, "Sv39_Refill_Hit");
- check(dut->resp_paddr == ((ppn << 12) | (va & 0xfffULL)), "Sv39_Refill_Paddr");
-
- refill(dut, sv39_vpn(0x0000000040500000ULL), 0x50000ULL, true, true, false, true, false);
- request(dut, 0x0000000040500000ULL, MODE_SV39, false, true);
- check(dut->resp_page_fault == 1, "Sv39_Store_D0_Fault");
-
- refill(dut, sv39_vpn(0x0000000040600000ULL), 0x60000ULL, false, false, true, true, false);
- request(dut, 0x0000000040600000ULL, MODE_SV39, false, false, true);
- check(dut->resp_hit == 1, "Sv39_MXR_LoadFromXOnly");
-
- request(dut, 0x0000008000000000ULL, MODE_SV39, false, false);
- check(dut->resp_page_fault == 1, "Sv39_NonCanonical_Fault");
-
- std::cout << "----------------------------------\n";
- std::cout << "All " << g_pass_count << " TLB tests passed!\n";
- std::cout << "----------------------------------\n";
-
- delete dut;
- return 0;
-}
diff --git a/rv64cpu/tb/unit/tb_trap_ctrl.cc b/rv64cpu/tb/unit/tb_trap_ctrl.cc
deleted file mode 100644
index 62867df..0000000
--- a/rv64cpu/tb/unit/tb_trap_ctrl.cc
+++ /dev/null
@@ -1,284 +0,0 @@
-// 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 <https://www.gnu.org/licenses/>.
-
-// File path: tb/unit/tb_trap_ctrl.cc
-// Description: Standalone unit test for trap_ctrl exception/interrupt control logic
-
-#include <cassert>
-#include <cstdint>
-#include <iostream>
-#include <verilated.h>
-
-#include "Vtrap_ctrl.h"
-
-namespace {
-constexpr uint64_t EXC_INST_ILLEGAL = 2ULL;
-constexpr uint64_t EXC_ECALL_FROM_S = 9ULL;
-constexpr uint64_t EXC_LOAD_PAGE_FAULT= 13ULL;
-constexpr uint64_t EXC_STORE_PAGE_FAULT= 15ULL;
-constexpr uint64_t EXC_LOAD_FAULT = 5ULL;
-constexpr uint64_t EXC_STORE_FAULT = 7ULL;
-
-constexpr uint64_t INTERRUPT_M_SOFTWARE = 3ULL;
-constexpr uint64_t INTERRUPT_M_TIMER = 7ULL;
-constexpr uint64_t INTERRUPT_M_EXTERNAL = 11ULL;
-constexpr uint64_t INTERRUPT_S_SOFTWARE = 1ULL;
-constexpr uint64_t INTERRUPT_S_TIMER = 5ULL;
-constexpr uint64_t INTERRUPT_S_EXTERNAL = 9ULL;
-
-constexpr uint64_t INTERRUPT_BIT = 1ULL << 63;
-constexpr uint64_t PRIV_MODE_U = 0ULL;
-constexpr uint64_t PRIV_MODE_S = 1ULL;
-constexpr uint64_t PRIV_MODE_M = 3ULL;
-
-uint64_t mie_bit(uint64_t interrupt_cause) {
- return 1ULL << interrupt_cause;
-}
-
-void drive_defaults(Vtrap_ctrl* dut) {
- dut->wb_pc = 0;
- dut->trap_illegal_instr = 0;
- dut->trap_is_ecall = 0;
- dut->trap_is_ebreak = 0;
- dut->trap_is_mret = 0;
- dut->trap_is_sret = 0;
- dut->trap_load_fault = 0;
- dut->trap_store_fault = 0;
- dut->trap_instr_page_fault = 0;
- dut->trap_load_page_fault = 0;
- dut->trap_store_page_fault = 0;
- dut->trap_bad_addr = 0;
- dut->trap_bad_instr = 0;
-
- dut->csr_mstatus_mie = 0;
- dut->csr_mie = 0;
- dut->csr_sie = 0;
- dut->csr_mip = 0;
- dut->csr_sip = 0;
- dut->csr_mtvec = 0x800ULL;
- dut->csr_stvec = 0x400ULL;
- dut->csr_mepc = 0x1000ULL;
- dut->csr_sepc = 0x2000ULL;
- dut->csr_medeleg = 0ULL;
- dut->csr_mideleg = 0ULL;
- dut->csr_priv_mode = PRIV_MODE_M;
-
- dut->eval();
-}
-
-int pass_count = 0;
-
-void check(bool cond, const char* name) {
- if (cond) {
- std::cout << "\033[32m[PASS] " << name << "\033[0m" << std::endl;
- pass_count++;
- } else {
- std::cout << "\033[31m[FAIL] " << name << "\033[0m" << std::endl;
- assert(false);
- }
-}
-} // namespace
-
-int main(int argc, char** argv) {
- Verilated::commandArgs(argc, argv);
- Vtrap_ctrl* dut = new Vtrap_ctrl;
-
- std::cout << "----------------------------------\n";
- std::cout << " trap_ctrl Testbench\n";
- std::cout << "----------------------------------\n";
-
- // ---------------------------------------------------------------------
- // 1) Synchronous exceptions (illegal instruction, ecall, page fault)
- // ---------------------------------------------------------------------
- drive_defaults(dut);
- dut->csr_priv_mode = PRIV_MODE_M;
- dut->csr_mtvec = 0x880ULL;
- dut->wb_pc = 0x1234ULL;
- dut->trap_bad_instr = 0xDEADBEEFu;
- dut->trap_illegal_instr = 1;
- dut->eval();
- check(dut->trap_trigger, "IllegalInst_TriggersTrap");
- check(dut->trap_mcause == EXC_INST_ILLEGAL, "IllegalInst_Mcause");
- check(dut->trap_mtval == 0xDEADBEEFULL, "IllegalInst_Mtval");
- check(dut->trap_mepc == 0x1234ULL, "IllegalInst_Mepc");
- check(!dut->trap_to_s_mode, "IllegalInst_NoDelegation");
- check(dut->trap_next_pc == 0x880ULL, "IllegalInst_JumpsToMtvec");
-
- drive_defaults(dut);
- dut->csr_priv_mode = PRIV_MODE_S;
- dut->csr_mtvec = 0x440ULL;
- dut->wb_pc = 0x5678ULL;
- dut->trap_is_ecall = 1;
- dut->eval();
- check(dut->trap_trigger, "EcallS_TriggersTrap");
- check(dut->trap_mcause == EXC_ECALL_FROM_S, "EcallS_Mcause");
- check(dut->trap_mtval == 0ULL, "EcallS_MtvalZero");
- check(dut->trap_next_pc == 0x440ULL, "EcallS_JumpsToMtvec");
-
- drive_defaults(dut);
- dut->trap_load_page_fault = 1;
- dut->trap_bad_addr = 0xBADC0FFEE0ULL;
- dut->eval();
- check(dut->trap_mcause == EXC_LOAD_PAGE_FAULT, "LoadPageFault_Mcause");
- check(dut->trap_mtval == 0xBADC0FFEE0ULL, "LoadPageFault_Mtval");
-
- drive_defaults(dut);
- dut->trap_store_page_fault = 1;
- dut->trap_bad_addr = 0xDEADBEEF00ULL;
- dut->eval();
- check(dut->trap_mcause == EXC_STORE_PAGE_FAULT, "StorePageFault_Mcause");
- check(dut->trap_mtval == 0xDEADBEEF00ULL, "StorePageFault_Mtval");
-
- drive_defaults(dut);
- dut->trap_load_fault = 1;
- dut->trap_bad_addr = 0xABCDEF0010ULL;
- dut->eval();
- check(dut->trap_mcause == EXC_LOAD_FAULT, "LoadFault_Mcause");
- check(dut->trap_mtval == 0xABCDEF0010ULL, "LoadFault_Mtval");
-
- drive_defaults(dut);
- dut->trap_store_fault = 1;
- dut->trap_bad_addr = 0xABCDEF0020ULL;
- dut->eval();
- check(dut->trap_mcause == EXC_STORE_FAULT, "StoreFault_Mcause");
- check(dut->trap_mtval == 0xABCDEF0020ULL, "StoreFault_Mtval");
-
- // ---------------------------------------------------------------------
- // 2) Interrupt priority + enable masking
- // ---------------------------------------------------------------------
- drive_defaults(dut);
- dut->csr_mstatus_mie = 1;
- dut->csr_mie = mie_bit(INTERRUPT_M_EXTERNAL) |
- mie_bit(INTERRUPT_M_SOFTWARE) |
- mie_bit(INTERRUPT_M_TIMER);
- dut->csr_priv_mode = PRIV_MODE_M;
- dut->csr_mtvec = 0x300ULL | 0x1ULL; // vectored mode
- dut->wb_pc = 0x4000ULL;
- dut->csr_mip = mie_bit(INTERRUPT_M_EXTERNAL) |
- mie_bit(INTERRUPT_M_SOFTWARE) |
- mie_bit(INTERRUPT_M_TIMER);
- dut->eval();
- const uint64_t expected_vectored_pc = 0x300ULL + (INTERRUPT_M_EXTERNAL << 2);
- check(dut->trap_trigger, "InterruptPriority_Triggers");
- check(dut->trap_mcause == (INTERRUPT_BIT | INTERRUPT_M_EXTERNAL), "InterruptPriority_Mcause");
- check(dut->trap_mtval == 0ULL, "InterruptPriority_MtvalZero");
- check(dut->trap_next_pc == expected_vectored_pc, "InterruptPriority_VectoredJump");
-
- drive_defaults(dut);
- dut->csr_mstatus_mie = 0;
- dut->csr_mie = mie_bit(INTERRUPT_M_EXTERNAL);
- dut->csr_mip = mie_bit(INTERRUPT_M_EXTERNAL);
- dut->eval();
- check(!dut->trap_trigger, "InterruptMaskedByGlobalMie_NoTrigger");
-
- drive_defaults(dut);
- dut->csr_priv_mode = PRIV_MODE_M;
- dut->csr_mstatus_mie = 1;
- dut->csr_mie = 0;
- dut->csr_mip = mie_bit(INTERRUPT_M_EXTERNAL);
- dut->eval();
- check(!dut->trap_trigger, "InterruptMaskedByMie_NoTrigger");
-
- drive_defaults(dut);
- dut->csr_priv_mode = PRIV_MODE_S;
- dut->csr_mstatus_mie = 0;
- dut->csr_mie = mie_bit(INTERRUPT_M_EXTERNAL);
- dut->csr_mtvec = 0x440ULL;
- dut->wb_pc = 0x6780ULL;
- dut->csr_mip = mie_bit(INTERRUPT_M_EXTERNAL);
- dut->eval();
- check(dut->trap_trigger, "MachineInterruptBelowM_IgnoresMstatusMie");
- check(!dut->trap_to_s_mode, "MachineInterruptBelowM_NoDelegationWithoutMideleg");
- check(dut->trap_mcause == (INTERRUPT_BIT | INTERRUPT_M_EXTERNAL), "MachineInterruptBelowM_Mcause");
- check(dut->trap_next_pc == 0x440ULL, "MachineInterruptBelowM_JumpsToMtvec");
-
- drive_defaults(dut);
- dut->csr_priv_mode = PRIV_MODE_S;
- dut->csr_mstatus_mie = 1;
- dut->csr_mie = mie_bit(INTERRUPT_M_TIMER);
- dut->csr_mtvec = 0x880ULL | 0x1ULL; // vectored machine mode
- dut->csr_stvec = 0x400ULL | 0x1ULL;
- dut->csr_mideleg = (1ULL << INTERRUPT_M_TIMER);
- dut->wb_pc = 0xABCDULL;
- dut->csr_mip = mie_bit(INTERRUPT_M_TIMER);
- dut->eval();
- const uint64_t expected_sv_pc = 0x880ULL + (INTERRUPT_M_TIMER << 2);
- check(!dut->trap_to_s_mode, "MachineInterrupt_NoDelegation");
- check(dut->trap_next_pc == expected_sv_pc, "MachineInterrupt_UsesMtvec");
- check(dut->trap_mcause == (INTERRUPT_BIT | INTERRUPT_M_TIMER), "MachineInterrupt_Mcause");
-
- drive_defaults(dut);
- dut->csr_priv_mode = PRIV_MODE_S;
- dut->csr_mstatus = (1ULL << 1);
- dut->csr_sie = mie_bit(INTERRUPT_S_TIMER);
- dut->csr_sip = mie_bit(INTERRUPT_S_TIMER);
- dut->csr_mideleg = mie_bit(INTERRUPT_S_TIMER);
- dut->csr_stvec = 0x900ULL | 0x1ULL;
- dut->wb_pc = 0xABCEULL;
- dut->eval();
- const uint64_t expected_s_timer_pc = 0x900ULL + (INTERRUPT_S_TIMER << 2);
- check(dut->trap_to_s_mode, "SupervisorInterruptDelegated_ToSupervisor");
- check(dut->trap_next_pc == expected_s_timer_pc, "SupervisorInterruptDelegated_UsesStvec");
- check(dut->trap_mcause == (INTERRUPT_BIT | INTERRUPT_S_TIMER), "SupervisorInterruptDelegated_Mcause");
-
- drive_defaults(dut);
- dut->csr_priv_mode = PRIV_MODE_S;
- dut->csr_mstatus = 0;
- dut->csr_sie = mie_bit(INTERRUPT_S_TIMER);
- dut->csr_sip = mie_bit(INTERRUPT_S_TIMER);
- dut->csr_mideleg = mie_bit(INTERRUPT_S_TIMER);
- dut->eval();
- check(!dut->trap_trigger, "SupervisorInterruptMaskedBySstatusSie_NoTrigger");
-
- drive_defaults(dut);
- dut->csr_priv_mode = PRIV_MODE_U;
- dut->csr_mstatus = 0;
- dut->csr_sie = mie_bit(INTERRUPT_S_EXTERNAL);
- dut->csr_sip = mie_bit(INTERRUPT_S_EXTERNAL);
- dut->csr_mideleg = mie_bit(INTERRUPT_S_EXTERNAL);
- dut->csr_stvec = 0x940ULL;
- dut->eval();
- check(dut->trap_to_s_mode, "SupervisorInterruptBelowS_IgnoresSstatusSie");
- check(dut->trap_next_pc == 0x940ULL, "SupervisorInterruptBelowS_UsesStvec");
-
- // ---------------------------------------------------------------------
- // 3) MRET/SRET redirect without new traps
- // ---------------------------------------------------------------------
- drive_defaults(dut);
- dut->csr_mepc = 0xCAFEBABEULL;
- dut->trap_is_mret = 1;
- dut->eval();
- check(dut->mret_en, "Mret_PathEnabled");
- check(dut->trap_next_pc == 0xCAFEBABEULL, "Mret_RestoresMepc");
- check(dut->trap_flush_req && dut->trap_pc_sel, "Mret_ForcesFlush");
- check(!dut->trap_trigger, "Mret_NoTrapOverlap");
-
- drive_defaults(dut);
- dut->csr_priv_mode = PRIV_MODE_S;
- dut->csr_sepc = 0x13579BDFULL;
- dut->trap_is_sret = 1;
- dut->eval();
- check(dut->sret_en, "Sret_PathEnabled");
- check(dut->trap_next_pc == 0x13579BDFULL, "Sret_RestoresSepc");
- check(dut->trap_flush_req && dut->trap_pc_sel, "Sret_ForcesFlush");
-
- std::cout << "----------------------------------\n";
- std::cout << "All " << pass_count << " trap_ctrl tests passed!\n";
- std::cout << "----------------------------------\n";
-
- delete dut;
- return 0;
-}
diff --git a/rv64cpu/tb/unit/tb_wb_stage.cc b/rv64cpu/tb/unit/tb_wb_stage.cc
deleted file mode 100644
index 3f6bedb..0000000
--- a/rv64cpu/tb/unit/tb_wb_stage.cc
+++ /dev/null
@@ -1,163 +0,0 @@
-// 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 <https://www.gnu.org/licenses/>.
-
-// File path: tb/unit/tb_wb_stage.cc
-// Description: WB Stage unit test
-
-#include <iostream>
-#include <cassert>
-#include <verilated.h>
-#include "Vwb_stage.h"
-
-int pass_count = 0;
-
-void tick(Vwb_stage* dut) {
- dut->eval();
-}
-
-void check(bool cond, const char* name) {
- if (cond) {
- std::cout << "\033[32m[PASS] " << name << "\033[0m" << std::endl;
- pass_count++;
- } else {
- std::cout << "\033[31m[FAIL] " << name << "\033[0m" << std::endl;
- assert(false);
- }
-}
-
-int main(int argc, char** argv) {
- Verilated::commandArgs(argc, argv);
- Vwb_stage* dut = new Vwb_stage;
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << " WB Stage Test" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- // 1. Basic ALU writeback
- dut->mem_wb_alu_result = 0xAA55AA55;
- dut->mem_wb_mem_rdata = 0x11223344;
- dut->mem_wb_pc = 0x80000000;
- dut->mem_wb_pc_plus_4 = 0x80000004;
- dut->mem_wb_reg_write_en = 1;
- dut->mem_wb_rd_addr = 5;
- dut->mem_wb_wb_sel = 0; // ALU
- dut->mem_wb_is_csr = 0;
- dut->mem_wb_csr_op = 0;
- dut->mem_wb_rs1_rdata = 0x99;
- dut->mem_wb_rs1_addr = 1;
- dut->mem_wb_imm = 0x123;
- dut->mem_wb_instr = 0xDEADBEEFu;
- dut->mem_wb_illegal_instr = 0;
- dut->mem_wb_is_ecall = 0;
- dut->mem_wb_is_ebreak = 0;
- dut->mem_wb_is_mret = 0;
- dut->mem_wb_is_sret = 0;
- dut->mem_wb_load_fault = 0;
- dut->mem_wb_store_fault = 0;
- dut->mem_wb_fault_addr = 0;
- dut->csr_rdata = 0x0;
- dut->csr_illegal_access = 0;
-
- tick(dut);
-
- check(dut->wb_rd_wdata == 0xAA55AA55, "WB_Sel_ALU");
- check(dut->wb_rd_addr == 5, "WB_RdAddr");
- check(dut->wb_reg_write_en == 1, "WB_RegWriteEn");
- check(dut->csr_req == 0, "CSR_Req_0");
-
- // 2. Memory read writeback
- dut->mem_wb_wb_sel = 1; // MEM
- tick(dut);
- check(dut->wb_rd_wdata == 0x11223344, "WB_Sel_MEM");
-
- // 3. PC+4 writeback (JAL/JALR)
- dut->mem_wb_wb_sel = 2; // PC+4
- tick(dut);
- check(dut->wb_rd_wdata == 0x80000004, "WB_Sel_PC_Plus_4");
-
- // 4. CSR Read/Write (e.g. CSRRS)
- dut->mem_wb_is_csr = 1;
- dut->mem_wb_wb_sel = 0; // Doesn't matter because is_csr forces CSR path
- dut->mem_wb_csr_op = 2; // CSRRS
- dut->mem_wb_imm = 0x300; // mstatus
- dut->mem_wb_rs1_rdata = 0x00000008; // Set MIE
- dut->mem_wb_rs1_addr = 10;
- dut->csr_rdata = 0x00001800; // Old mstatus
-
- tick(dut);
-
- check(dut->wb_rd_wdata == 0x00001800, "CSR_Read_To_Reg");
- check(dut->csr_req == 1, "CSR_Req_1");
- check(dut->csr_op == 2, "CSR_Op_Propagated");
- check(dut->csr_addr == 0x300, "CSR_Addr_Extracted");
- check(dut->csr_rs1_data == 0x00000008, "CSR_RS1_Data_Rdata");
-
- // 5. CSR Immediate (e.g. CSRRSI)
- dut->mem_wb_csr_op = 6; // CSRRSI (funct3 = 3'b110)
- tick(dut);
- check(dut->csr_rs1_data == 10, "CSR_RS1_Data_Zimm");
-
- // 6. Exception Signals Propagation
- dut->mem_wb_illegal_instr = 1;
- dut->mem_wb_is_ecall = 1;
- dut->mem_wb_is_ebreak = 1;
- dut->mem_wb_is_mret = 1;
- dut->mem_wb_is_sret = 1;
- tick(dut);
-
- check(dut->trap_illegal_instr == 1, "Trap_Illegal_Instr");
- check(dut->trap_is_ecall == 1, "Trap_ECALL");
- check(dut->trap_is_ebreak == 1, "Trap_EBREAK");
- check(dut->trap_is_mret == 1, "Trap_MRET");
- check(dut->trap_is_sret == 1, "Trap_SRET");
- check(dut->trap_epc == 0x80000000, "Trap_EPC");
- check(dut->trap_bad_instr == 0xDEADBEEFu, "Trap_BadInstr_Payload");
- check(dut->wb_reg_write_en == 0, "Trap_Gates_RegWriteEn");
- check(dut->csr_req == 0, "Trap_Gates_CSR_Req");
-
- // 7. CSR illegal access should raise an illegal-instr trap
- dut->mem_wb_illegal_instr = 0;
- dut->mem_wb_is_ecall = 0;
- dut->mem_wb_is_ebreak = 0;
- dut->csr_illegal_access = 1;
- tick(dut);
- check(dut->trap_illegal_instr == 1, "CSR_Illegal_Raises_Trap");
- check(dut->wb_reg_write_en == 0, "CSR_Illegal_Kills_Write");
-
- // 8. Load/store faults propagate to trap outputs and block writeback
- dut->csr_illegal_access = 0;
- dut->mem_wb_load_fault = 1;
- dut->mem_wb_fault_addr = 0x80000010ULL;
- tick(dut);
- check(dut->trap_load_fault == 1, "LoadFault_AssertsTrap");
- check(dut->trap_bad_addr == 0x80000010ULL, "LoadFault_PassesAddress");
- check(dut->wb_reg_write_en == 0, "LoadFault_KillsWrite");
-
- dut->mem_wb_load_fault = 0;
- dut->mem_wb_store_fault = 1;
- dut->mem_wb_fault_addr = 0x80000020ULL;
- tick(dut);
- check(dut->trap_store_fault == 1, "StoreFault_AssertsTrap");
- check(dut->trap_bad_addr == 0x80000020ULL, "StoreFault_PassesAddress");
-
- dut->mem_wb_store_fault = 0;
-
- std::cout << "----------------------------------" << std::endl;
- std::cout << "\033[32mAll " << pass_count << " tests passed!\033[0m" << std::endl;
- std::cout << "----------------------------------" << std::endl;
-
- delete dut;
- return 0;
-}