From 2a4c290c55b683352a16747432a7fc4635597cc6 Mon Sep 17 00:00:00 2001 From: Richard Qin Date: Sat, 7 Nov 2026 03:30:17 +0800 Subject: refactor: reset software and test directories --- rv64cpu/tb/CMakeLists.txt | 275 ------- rv64cpu/tb/compliance/CMakeLists.txt | 60 -- rv64cpu/tb/compliance/compliance_runner.cc | 634 ---------------- rv64cpu/tb/core/tb_core.cc | 1136 ---------------------------- rv64cpu/tb/unit/tb_alu.cc | 136 ---- rv64cpu/tb/unit/tb_branch.cc | 100 --- rv64cpu/tb/unit/tb_csr.cc | 770 ------------------- rv64cpu/tb/unit/tb_decoder.cc | 353 --------- rv64cpu/tb/unit/tb_ex_mem_reg.cc | 182 ----- rv64cpu/tb/unit/tb_ex_stage.cc | 218 ------ rv64cpu/tb/unit/tb_hazard_unit.cc | 364 --------- rv64cpu/tb/unit/tb_id_ex_reg.cc | 177 ----- rv64cpu/tb/unit/tb_id_stage.cc | 194 ----- rv64cpu/tb/unit/tb_if_id_reg.cc | 127 ---- rv64cpu/tb/unit/tb_if_stage.cc | 132 ---- rv64cpu/tb/unit/tb_mem_stage.cc | 305 -------- rv64cpu/tb/unit/tb_mem_wb_reg.cc | 165 ---- rv64cpu/tb/unit/tb_mmu_top.cc | 236 ------ rv64cpu/tb/unit/tb_ptw.cc | 206 ----- rv64cpu/tb/unit/tb_regfile.cc | 192 ----- rv64cpu/tb/unit/tb_tlb.cc | 172 ----- rv64cpu/tb/unit/tb_trap_ctrl.cc | 284 ------- rv64cpu/tb/unit/tb_wb_stage.cc | 163 ---- 23 files changed, 6581 deletions(-) delete mode 100644 rv64cpu/tb/CMakeLists.txt delete mode 100644 rv64cpu/tb/compliance/CMakeLists.txt delete mode 100644 rv64cpu/tb/compliance/compliance_runner.cc delete mode 100644 rv64cpu/tb/core/tb_core.cc delete mode 100644 rv64cpu/tb/unit/tb_alu.cc delete mode 100644 rv64cpu/tb/unit/tb_branch.cc delete mode 100644 rv64cpu/tb/unit/tb_csr.cc delete mode 100644 rv64cpu/tb/unit/tb_decoder.cc delete mode 100644 rv64cpu/tb/unit/tb_ex_mem_reg.cc delete mode 100644 rv64cpu/tb/unit/tb_ex_stage.cc delete mode 100644 rv64cpu/tb/unit/tb_hazard_unit.cc delete mode 100644 rv64cpu/tb/unit/tb_id_ex_reg.cc delete mode 100644 rv64cpu/tb/unit/tb_id_stage.cc delete mode 100644 rv64cpu/tb/unit/tb_if_id_reg.cc delete mode 100644 rv64cpu/tb/unit/tb_if_stage.cc delete mode 100644 rv64cpu/tb/unit/tb_mem_stage.cc delete mode 100644 rv64cpu/tb/unit/tb_mem_wb_reg.cc delete mode 100644 rv64cpu/tb/unit/tb_mmu_top.cc delete mode 100644 rv64cpu/tb/unit/tb_ptw.cc delete mode 100644 rv64cpu/tb/unit/tb_regfile.cc delete mode 100644 rv64cpu/tb/unit/tb_tlb.cc delete mode 100644 rv64cpu/tb/unit/tb_trap_ctrl.cc delete mode 100644 rv64cpu/tb/unit/tb_wb_stage.cc (limited to 'rv64cpu/tb') 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_ -# TOP_MODULE -# SOURCES ${RTL_CORE_DIR}/.sv [... dependencies ...] -# INCLUDE_DIRS ${RTL_INCLUDE_DIR} -# TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_.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 $ --case mret_sret_redirect) -add_test(NAME tb_core_interrupt_priority COMMAND $ --case branch_interrupt_priority) -add_test(NAME tb_core_double_exception COMMAND $ --case double_exception_flush) -add_test(NAME tb_core_mem_fault COMMAND $ --case mem_fault_pipeline) -add_test(NAME tb_core_mie_masking COMMAND $ --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 . -// -// 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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(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(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((wdata >> (lane * 8)) & 0xFFULL); - write_byte(aligned + lane, byte_value); - } - } - -private: - std::map 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(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(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(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 data(static_cast(size)); - in.read(reinterpret_cast(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(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(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(i) * ehdr->e_phentsize; - const auto* phdr = reinterpret_cast(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(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(i) * ehdr->e_shentsize; - const auto* shdr = reinterpret_cast(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(shdr->sh_link) * ehdr->e_shentsize; - const auto* str_shdr = reinterpret_cast(data.data() + str_off); - if (str_shdr->sh_offset + str_shdr->sh_size > data.size()) { - continue; - } - - const char* strtab = reinterpret_cast(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(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 pc_trace; - if (opts.trace_pc_count != 0) { - pc_trace.reserve(static_cast(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(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(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(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((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(top->rootp->richard_core__DOT__trap_ctrl_mret_en) - << " sret=" << static_cast(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(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(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(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 . - -// 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include "Vrichard_core.h" - -#if VM_TRACE -#include -#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(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(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(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(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(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(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& prog, uint32_t rd, uint64_t imm) { - const uint64_t upper = (imm + 0x800ULL) >> 12; - int32_t lower = static_cast(imm & 0xFFFULL); - if (lower >= 2048) { - lower -= 4096; - } - prog.push_back(LUI(rd, static_cast(upper))); - prog.push_back(ADDI(rd, rd, lower)); -} - -static void emit_nops(std::vector& prog, int count) { - for (int i = 0; i < count; ++i) { - prog.push_back(NOP()); - } -} - -static void emit_csrrw_imm64(std::vector& 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 imem; - // Data memory: map from byte_addr -> 64-bit doubleword - std::map dmem; - - void load_program(uint64_t base_addr, const std::vector& 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 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 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(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 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 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(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(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 collect_pc_trace(CoreSim& sim, int cycles) { - std::vector trace; - trace.reserve(static_cast(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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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(i) * 8ULL, 0ULL); - } - - std::vector 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 . - -// File path: tb/unit/tb_alu.cc -// Description: ALU unit test -#include "Valu.h" -#include -#include -#include -#include - -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 . - -// File path: tb/unit/tb_branch.cc -// Description: Branch Unit test - -#include -#include -#include -#include -#include -#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 . - -// File path: tb/unit/tb_csr.cc -// Description: CSR unit testbench - -#include -#include -#include -#include -#include - -#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 -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 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 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 . - -// File path: tb/unit/tb_decoder.cc -// Description: Decoder unit test - -#include -#include -#include -#include -#include -#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 . - -// File path: tb/unit/tb_ex_mem_reg.cc -// Description: Execution/Memory Stage Pipeline Register unit test - -#include -#include -#include -#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 . - -// File path: tb/unit/tb_ex_stage.cc -// Description: Execution Stage unit test - -#include -#include -#include -#include -#include -#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 . - -// File path: tb/unit/tb_hazard_unit.cc -// Description: Hazard Unit testbench — exhaustive forwarding, stall, and flush tests - -#include -#include -#include -#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 . - -// File path: tb/unit/tb_id_ex_reg.cc -// Description: ID/EX Pipeline Register unit test - -#include -#include -#include -#include -#include -#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 . - -// 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 -#include -#include -#include -#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 . - -// File path: tb/unit/tb_if_id_reg.cc -// Description: IF/ID Pipeline Register unit test - -#include -#include -#include -#include -#include -#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 . - -// File path: tb/unit/tb_if_stage.cc -// Description: IF Stage unit test - -#include -#include -#include -#include -#include -#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 . - -// File path: tb/unit/tb_mem_stage.cc -// Description: Memory Stage unit test - -#include -#include -#include -#include -#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 . - -// File path: tb/unit/tb_mem_wb_reg.cc -// Description: MEM/WB Stage Register unit test - -#include -#include -#include -#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 -#include -#include -#include - -#include - -#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& 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& 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 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 -#include -#include -#include - -#include - -#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& 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& 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 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 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 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 . - -// File path: tb/unit/tb_regfile.cc -// Description: Register file unit test - -#include -#include -#include -#include "Vregfile.h" - -void tick(Vregfile* dut) { - dut->clk = 0; - dut->eval(); - dut->clk = 1; - dut->eval(); - dut->clk = 0; - dut->eval(); -} - -int main(int argc, char** argv) { - Verilated::commandArgs(argc, argv); - Vregfile* dut = new Vregfile; - - dut->clk = 0; - dut->rst = 0; - dut->reg_write_en = 0; - dut->rd_addr = 0; - dut->rd_wdata = 0; - dut->rs1_addr = 0; - dut->rs2_addr = 0; - - std::cout << "----------------------------------" << std::endl; - std::cout << " Register File Unit Test" << std::endl; - std::cout << "----------------------------------" << std::endl; - - // Test 1: Reset - dut->rst = 1; - tick(dut); - dut->rst = 0; - std::cout << "\033[32m[PASS] Reset complete\033[0m" << std::endl; - - // Test 2: Write and read - dut->reg_write_en = 1; - dut->rd_addr = 5; - dut->rd_wdata = 0xDEADBEEF; - tick(dut); - - dut->reg_write_en = 0; - dut->rs1_addr = 5; - dut->eval(); - assert(dut->rs1_rdata == 0xDEADBEEF); - std::cout << "\033[32m[PASS] Write and read (x5) complete\033[0m" << std::endl; - - // Test 3: x0 - dut->reg_write_en = 1; - dut->rd_addr = 0; - dut->rd_wdata = 0x12345678; - tick(dut); - - dut->reg_write_en = 0; - dut->rs2_addr = 0; - dut->eval(); - assert(dut->rs2_rdata == 0); - std::cout << "\033[32m[PASS] Write and read (x0) complete\033[0m" << std::endl; - - // Test 4: Dual-port simultaneous read - dut->reg_write_en = 1; - dut->rd_addr = 6; - dut->rd_wdata = 0x87654321; - tick(dut); - - dut->reg_write_en = 1; - dut->rd_addr = 7; - dut->rd_wdata = 0x11223344; - tick(dut); - - dut->rs1_addr = 6; - dut->rs2_addr = 7; - dut->eval(); - - assert(dut->rs1_rdata == 0x87654321 && dut->rs2_rdata == 0x11223344); - std::cout << "\033[32m[PASS] Dual-port simultaneous read complete\033[0m" << std::endl; - - - // Test 5: Write enable = 0 (Data should not change) - dut->reg_write_en = 1; - dut->rd_addr = 10; - dut->rd_wdata = 0xCAFEBABE; - tick(dut); - - dut->reg_write_en = 0; // Disable write - dut->rd_addr = 10; - dut->rd_wdata = 0x11111111; // Attempt to overwrite - tick(dut); - - dut->rs1_addr = 10; - dut->eval(); - assert(dut->rs1_rdata == 0xCAFEBABE); - std::cout << "\033[32m[PASS] Write enable = 0 correctly ignores write\033[0m" << std::endl; - - // Test 6: Verify full reset behavior - // First, dirty up some registers - for (int i = 1; i < 32; i += 5) { - dut->reg_write_en = 1; - dut->rd_addr = i; - dut->rd_wdata = 0x1000 + i; - tick(dut); - } - - // Apply reset - dut->rst = 1; - 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 -#include -#include - -#include - -#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 . - -// File path: tb/unit/tb_trap_ctrl.cc -// Description: Standalone unit test for trap_ctrl exception/interrupt control logic - -#include -#include -#include -#include - -#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 . - -// File path: tb/unit/tb_wb_stage.cc -// Description: WB Stage unit test - -#include -#include -#include -#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; -} -- cgit v1.2.3