diff options
Diffstat (limited to 'rv64cpu/tb/core/tb_core.cc')
| -rw-r--r-- | rv64cpu/tb/core/tb_core.cc | 1136 |
1 files changed, 0 insertions, 1136 deletions
diff --git a/rv64cpu/tb/core/tb_core.cc b/rv64cpu/tb/core/tb_core.cc deleted file mode 100644 index da758c1..0000000 --- a/rv64cpu/tb/core/tb_core.cc +++ /dev/null @@ -1,1136 +0,0 @@ -// Richard CPU -// Copyright (C) 2026 Richard Qin -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -// File path: tb/core/tb_core.cc -// Description: Richard CPU core top-level integration test -// -// This testbench drives the richard_core module through its external interfaces -// (instruction memory + data memory) to verify multi-cycle pipeline behavior. -// It acts as a simple memory model, feeding instructions and returning data. - -#include <algorithm> -#include <iostream> -#include <iomanip> -#include <cstdint> -#include <iterator> -#include <vector> -#include <map> -#include <string> -#include <verilated.h> -#include "Vrichard_core.h" - -#if VM_TRACE -#include <verilated_vcd_c.h> -#endif - -// --------------------------------- -// CSR addresses & cause codes used in tests -// --------------------------------- -constexpr uint16_t CSR_MSTATUS = 0x300; -constexpr uint16_t CSR_MISA = 0x301; -constexpr uint16_t CSR_MEDELEG = 0x302; -constexpr uint16_t CSR_MIDELEG = 0x303; -constexpr uint16_t CSR_MIE = 0x304; -constexpr uint16_t CSR_MTVEC = 0x305; -constexpr uint16_t CSR_MEPC = 0x341; -constexpr uint16_t CSR_MSCRATCH = 0x340; -constexpr uint16_t CSR_MCAUSE = 0x342; -constexpr uint16_t CSR_MTVAL = 0x343; -constexpr uint16_t CSR_SSTATUS = 0x100; -constexpr uint16_t CSR_STVEC = 0x105; -constexpr uint16_t CSR_SSCRATCH = 0x140; -constexpr uint16_t CSR_SEPC = 0x141; -constexpr uint16_t CSR_SCAUSE = 0x142; -constexpr uint16_t CSR_STVAL = 0x143; - -constexpr int MSTATUS_MIE_BIT = 3; -constexpr int MSTATUS_MPIE_BIT = 7; -constexpr int MSTATUS_MPP_LO = 11; -constexpr int SSTATUS_SIE_BIT = 1; -constexpr int SSTATUS_SPIE_BIT = 5; -constexpr int SSTATUS_SPP_BIT = 8; - -constexpr uint64_t EXC_INST_ILLEGAL = 2ULL; -constexpr uint64_t EXC_BREAKPOINT = 3ULL; -constexpr uint64_t EXC_LOAD_FAULT = 5ULL; -constexpr uint64_t EXC_STORE_FAULT = 7ULL; -constexpr uint64_t EXC_ECALL_FROM_U = 8ULL; -constexpr uint64_t EXC_ECALL_FROM_M = 11ULL; -constexpr uint64_t INTERRUPT_BIT = 1ULL << 63; -constexpr uint64_t INTERRUPT_M_EXTERNAL = 11ULL; -constexpr uint64_t INTERRUPT_M_TIMER = 7ULL; - -constexpr uint64_t LOG_ENTRY_STRIDE = 24ULL; -constexpr uint64_t MACHINE_TRAP_VECTOR = 0x00000000ULL; -constexpr uint64_t SUPERVISOR_TRAP_VECTOR = 0x00000100ULL; -constexpr uint64_t MACHINE_LOG_PTR_ADDR = 0x000000000000FFF0ULL; -constexpr uint64_t SUPERVISOR_LOG_PTR_ADDR = 0x000000000000FFE0ULL; - -constexpr uint64_t PRIV_MODE_U = 0ULL; -constexpr uint64_t PRIV_MODE_S = 1ULL; -constexpr uint64_t PRIV_MODE_M = 3ULL; - -// ----------------------- -// RISC-V Instruction Encoding Helpers -// ----------------------- - -// R-type: funct7[6:0] | rs2[4:0] | rs1[4:0] | funct3[2:0] | rd[4:0] | opcode[6:0] -static uint32_t rv_r_type(uint32_t funct7, uint32_t rs2, uint32_t rs1, uint32_t funct3, uint32_t rd, uint32_t opcode) { - return (funct7 << 25) | (rs2 << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | opcode; -} - -// I-type: imm[11:0] | rs1[4:0] | funct3[2:0] | rd[4:0] | opcode[6:0] -static uint32_t rv_i_type(uint32_t imm12, uint32_t rs1, uint32_t funct3, uint32_t rd, uint32_t opcode) { - return ((imm12 & 0xFFF) << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | opcode; -} - -// S-type: imm[11:5] | rs2[4:0] | rs1[4:0] | funct3[2:0] | imm[4:0] | opcode[6:0] -static uint32_t rv_s_type(uint32_t imm12, uint32_t rs2, uint32_t rs1, uint32_t funct3, uint32_t opcode) { - uint32_t imm_hi = (imm12 >> 5) & 0x7F; - uint32_t imm_lo = imm12 & 0x1F; - return (imm_hi << 25) | (rs2 << 20) | (rs1 << 15) | (funct3 << 12) | (imm_lo << 7) | opcode; -} - -// U-type: imm[31:12] | rd[4:0] | opcode[6:0] -static uint32_t rv_u_type(uint32_t imm20, uint32_t rd, uint32_t opcode) { - return ((imm20 & 0xFFFFF) << 12) | (rd << 7) | opcode; -} - -// J-type: imm[20] | imm[10:1] | imm[11] | imm[19:12] | rd[4:0] | opcode[6:0] -static uint32_t rv_j_type(int32_t offset, uint32_t rd, uint32_t opcode) { - uint32_t imm = static_cast<uint32_t>(offset); - uint32_t bit20 = (imm >> 20) & 0x1; - uint32_t bit10_1 = (imm >> 1) & 0x3FF; - uint32_t bit11 = (imm >> 11) & 0x1; - uint32_t bit19_12 = (imm >> 12) & 0xFF; - return (bit20 << 31) | (bit10_1 << 21) | (bit11 << 20) | (bit19_12 << 12) | (rd << 7) | opcode; -} - -// B-type: imm[12|10:5] | rs2[4:0] | rs1[4:0] | funct3[2:0] | imm[4:1|11] | opcode[6:0] -static uint32_t rv_b_type(int32_t offset, uint32_t rs2, uint32_t rs1, uint32_t funct3, uint32_t opcode) { - uint32_t imm = static_cast<uint32_t>(offset); - uint32_t bit12 = (imm >> 12) & 0x1; - uint32_t bit10_5 = (imm >> 5) & 0x3F; - uint32_t bit4_1 = (imm >> 1) & 0xF; - uint32_t bit11 = (imm >> 11) & 0x1; - return (bit12 << 31) | (bit10_5 << 25) | (rs2 << 20) | (rs1 << 15) | - (funct3 << 12) | (bit4_1 << 8) | (bit11 << 7) | opcode; -} - -// Common instruction shorthands -static uint32_t NOP() { return 0x00000013; } // addi x0, x0, 0 -static uint32_t ILLEGAL() { return 0xFFFFFFFF; } -static uint32_t ADDI(uint32_t rd, uint32_t rs1, int32_t imm) { - return rv_i_type(static_cast<uint32_t>(imm) & 0xFFF, rs1, 0x0, rd, 0x13); -} -static uint32_t ADD(uint32_t rd, uint32_t rs1, uint32_t rs2) { - return rv_r_type(0x00, rs2, rs1, 0x0, rd, 0x33); -} -static uint32_t SUB(uint32_t rd, uint32_t rs1, uint32_t rs2) { - return rv_r_type(0x20, rs2, rs1, 0x0, rd, 0x33); -} -static uint32_t LD(uint32_t rd, uint32_t rs1, int32_t imm) { - return rv_i_type(static_cast<uint32_t>(imm) & 0xFFF, rs1, 0x3, rd, 0x03); -} -static uint32_t SD(uint32_t rs2, uint32_t rs1, int32_t imm) { - return rv_s_type(static_cast<uint32_t>(imm) & 0xFFF, rs2, rs1, 0x3, 0x23); -} -static uint32_t BEQ(uint32_t rs1, uint32_t rs2, int32_t offset) { - return rv_b_type(offset, rs2, rs1, 0x0, 0x63); -} -static uint32_t BNE(uint32_t rs1, uint32_t rs2, int32_t offset) { - return rv_b_type(offset, rs2, rs1, 0x1, 0x63); -} -static uint32_t LUI(uint32_t rd, uint32_t imm) { - return rv_u_type(imm, rd, 0x37); -} -static uint32_t JAL(uint32_t rd, int32_t offset) { - return rv_j_type(offset, rd, 0x6f); -} -static uint32_t JALR(uint32_t rd, uint32_t rs1, int32_t imm) { - return rv_i_type(static_cast<uint32_t>(imm) & 0xFFF, rs1, 0x0, rd, 0x67); -} -static uint32_t SYSTEM(uint32_t funct12, uint32_t rs1, uint32_t funct3, uint32_t rd) { - return ((funct12 & 0xFFF) << 20) | ((rs1 & 0x1F) << 15) | ((funct3 & 0x7) << 12) | ((rd & 0x1F) << 7) | 0x73; -} -static uint32_t ECALL() { return SYSTEM(0x000, 0, 0x0, 0); } -static uint32_t EBREAK() { return SYSTEM(0x001, 0, 0x0, 0); } -static uint32_t MRET() { return SYSTEM(0x302, 0, 0x0, 0); } -static uint32_t SRET() { return SYSTEM(0x102, 0, 0x0, 0); } -static uint32_t CSRInstrReg(uint32_t csr, uint32_t funct3, uint32_t rd, uint32_t rs1) { - return ((csr & 0xFFF) << 20) | ((rs1 & 0x1F) << 15) | ((funct3 & 0x7) << 12) | ((rd & 0x1F) << 7) | 0x73; -} -static uint32_t CSRRW(uint32_t rd, uint32_t csr, uint32_t rs1) { - return CSRInstrReg(csr, 0b001, rd, rs1); -} -static uint32_t CSRRS(uint32_t rd, uint32_t csr, uint32_t rs1) { - return CSRInstrReg(csr, 0b010, rd, rs1); -} -static uint32_t CSRRWI(uint32_t rd, uint32_t csr, uint32_t zimm) { - return CSRInstrReg(csr, 0b101, rd, zimm & 0x1F); -} - -static void emit_load_imm64(std::vector<uint32_t>& prog, uint32_t rd, uint64_t imm) { - const uint64_t upper = (imm + 0x800ULL) >> 12; - int32_t lower = static_cast<int32_t>(imm & 0xFFFULL); - if (lower >= 2048) { - lower -= 4096; - } - prog.push_back(LUI(rd, static_cast<uint32_t>(upper))); - prog.push_back(ADDI(rd, rd, lower)); -} - -static void emit_nops(std::vector<uint32_t>& prog, int count) { - for (int i = 0; i < count; ++i) { - prog.push_back(NOP()); - } -} - -static void emit_csrrw_imm64(std::vector<uint32_t>& prog, uint32_t tmp_reg, uint32_t csr, uint64_t value) { - // CSR source operand is read in ID stage, so we need a short drain window after loading tmp_reg. - emit_load_imm64(prog, tmp_reg, value); - emit_nops(prog, 6); - prog.push_back(CSRRW(0, csr, tmp_reg)); -} - -// ----------------------- -// Test Infrastructure -// ----------------------- -static int g_test_count = 0; -static int g_pass_count = 0; - -#define ASSERT_EQ(actual, expected, msg) do { \ - g_test_count++; \ - if ((actual) != (expected)) { \ - std::cerr << " FAIL [" << g_test_count << "] " << msg \ - << ": expected 0x" << std::hex << (expected) \ - << ", got 0x" << std::hex << (actual) << std::dec << std::endl; \ - } else { \ - g_pass_count++; \ - } \ -} while(0) - -// ----------------------- -// Simple Memory Model -// ----------------------- -class MemoryModel { -public: - // Instruction memory: map from byte_addr -> 32-bit word - std::map<uint64_t, uint32_t> imem; - // Data memory: map from byte_addr -> 64-bit doubleword - std::map<uint64_t, uint64_t> dmem; - - void load_program(uint64_t base_addr, const std::vector<uint32_t>& program) { - for (size_t i = 0; i < program.size(); i++) { - imem[base_addr + i * 4] = program[i]; - } - } - - uint32_t read_instr(uint64_t addr) { - auto it = imem.find(addr); - if (it != imem.end()) return it->second; - return NOP(); // Return NOP for unmapped addresses - } - - uint64_t read_data(uint64_t addr) const { - // Align to doubleword - uint64_t aligned = addr & ~0x7ULL; - auto it = dmem.find(aligned); - if (it != dmem.end()) return it->second; - return 0; - } - - void write_data(uint64_t addr, uint64_t data) { - uint64_t aligned = addr & ~0x7ULL; - dmem[aligned] = data; - } -}; - -static std::vector<uint32_t> make_machine_trap_handler_program() { - constexpr uint32_t REG_PTR = 5; - constexpr uint32_t REG_MCAUSE = 6; - constexpr uint32_t REG_MEPC = 7; - constexpr uint32_t REG_MTVAL = 28; - constexpr uint32_t REG_TMP = 29; - constexpr uint32_t REG_SLOT_ADDR = 30; - - std::vector<uint32_t> prog; - emit_load_imm64(prog, REG_SLOT_ADDR, MACHINE_LOG_PTR_ADDR); - prog.push_back(LD(REG_PTR, REG_SLOT_ADDR, 0)); - emit_nops(prog, 12); - prog.push_back(CSRRS(REG_MCAUSE, CSR_MCAUSE, 0)); - emit_nops(prog, 4); - prog.push_back(SD(REG_MCAUSE, REG_PTR, 0)); - prog.push_back(CSRRS(REG_MEPC, CSR_MEPC, 0)); - emit_nops(prog, 4); - prog.push_back(SD(REG_MEPC, REG_PTR, 8)); - prog.push_back(CSRRS(REG_MTVAL, CSR_MTVAL, 0)); - emit_nops(prog, 4); - prog.push_back(SD(REG_MTVAL, REG_PTR, 16)); - prog.push_back(ADDI(REG_PTR, REG_PTR, static_cast<int32_t>(LOG_ENTRY_STRIDE))); - prog.push_back(SD(REG_PTR, REG_SLOT_ADDR, 0)); - prog.push_back(CSRRS(REG_TMP, CSR_MEPC, 0)); - prog.push_back(ADDI(REG_TMP, REG_TMP, 4)); - emit_nops(prog, 12); - prog.push_back(CSRRW(0, CSR_MEPC, REG_TMP)); - prog.push_back(MRET()); - prog.push_back(NOP()); - prog.push_back(NOP()); - return prog; -} - -static std::vector<uint32_t> make_supervisor_trap_handler_program() { - constexpr uint32_t REG_PTR = 5; - constexpr uint32_t REG_SCAUSE = 6; - constexpr uint32_t REG_SEPC = 7; - constexpr uint32_t REG_STVAL = 28; - constexpr uint32_t REG_TMP = 29; - constexpr uint32_t REG_SLOT_ADDR = 30; - - std::vector<uint32_t> prog; - emit_load_imm64(prog, REG_SLOT_ADDR, SUPERVISOR_LOG_PTR_ADDR); - prog.push_back(LD(REG_PTR, REG_SLOT_ADDR, 0)); - emit_nops(prog, 12); - prog.push_back(CSRRS(REG_SCAUSE, CSR_SCAUSE, 0)); - emit_nops(prog, 4); - prog.push_back(SD(REG_SCAUSE, REG_PTR, 0)); - prog.push_back(CSRRS(REG_SEPC, CSR_SEPC, 0)); - emit_nops(prog, 4); - prog.push_back(SD(REG_SEPC, REG_PTR, 8)); - prog.push_back(CSRRS(REG_STVAL, CSR_STVAL, 0)); - emit_nops(prog, 4); - prog.push_back(SD(REG_STVAL, REG_PTR, 16)); - prog.push_back(ADDI(REG_PTR, REG_PTR, static_cast<int32_t>(LOG_ENTRY_STRIDE))); - prog.push_back(SD(REG_PTR, REG_SLOT_ADDR, 0)); - prog.push_back(CSRRS(REG_TMP, CSR_SEPC, 0)); - prog.push_back(ADDI(REG_TMP, REG_TMP, 4)); - emit_nops(prog, 12); - prog.push_back(CSRRW(0, CSR_SEPC, REG_TMP)); - prog.push_back(SRET()); - prog.push_back(NOP()); - prog.push_back(NOP()); - return prog; -} - -static void install_trap_handlers(MemoryModel& mem) { - mem.load_program(MACHINE_TRAP_VECTOR, make_machine_trap_handler_program()); - mem.load_program(SUPERVISOR_TRAP_VECTOR, make_supervisor_trap_handler_program()); -} - -struct TrapRecord { - uint64_t mcause; - uint64_t mepc; - uint64_t mtval; -}; - -static TrapRecord fetch_trap_record(const MemoryModel& mem, uint64_t base, int index) { - const uint64_t addr = base + static_cast<uint64_t>(index) * LOG_ENTRY_STRIDE; - TrapRecord rec; - rec.mcause = mem.read_data(addr + 0); - rec.mepc = mem.read_data(addr + 8); - rec.mtval = mem.read_data(addr + 16); - return rec; -} - -// ----------------------- -// Core Simulation Wrapper -// ----------------------- -class CoreSim { -public: - Vrichard_core* top; - MemoryModel mem; - uint64_t sim_time; - bool m_timer_interrupt_level; - bool m_software_interrupt_level; - bool m_external_interrupt_level; - bool s_timer_interrupt_level; - bool s_software_interrupt_level; - bool s_external_interrupt_level; -#if VM_TRACE - VerilatedVcdC* tfp; -#endif - - CoreSim() : sim_time(0), - m_timer_interrupt_level(false), - m_software_interrupt_level(false), - m_external_interrupt_level(false), - s_timer_interrupt_level(false), - s_software_interrupt_level(false), - s_external_interrupt_level(false) { - top = new Vrichard_core; -#if VM_TRACE - Verilated::traceEverOn(true); - tfp = new VerilatedVcdC; - top->trace(tfp, 99); - tfp->open("waves/tb_core.vcd"); -#endif - } - - ~CoreSim() { -#if VM_TRACE - if (tfp) { tfp->close(); delete tfp; } -#endif - delete top; - } - - void reset(int cycles = 3) { - top->rst = 1; - top->imem_rdata = NOP(); - top->dmem_rdata = 0; - clear_interrupts(); - for (int i = 0; i < cycles; i++) { - tick(); - } - top->rst = 0; - } - - void tick() { - // Drive instruction memory response from model - top->imem_rdata = mem.read_instr(top->imem_addr); - - // Drive data memory response - if (top->dmem_read_en) { - top->dmem_rdata = mem.read_data(top->dmem_addr); - } else { - top->dmem_rdata = 0; - } - - top->m_timer_interrupt = m_timer_interrupt_level; - top->m_software_interrupt = m_software_interrupt_level; - top->m_external_interrupt = m_external_interrupt_level; - top->s_timer_interrupt = s_timer_interrupt_level; - top->s_software_interrupt = s_software_interrupt_level; - top->s_external_interrupt = s_external_interrupt_level; - - // Rising edge - top->clk = 0; - top->eval(); -#if VM_TRACE - tfp->dump(sim_time++); -#endif - - top->clk = 1; - top->eval(); -#if VM_TRACE - tfp->dump(sim_time++); -#endif - - // Handle writes on the falling edge conceptually - if (top->dmem_write_en) { - mem.write_data(top->dmem_addr, top->dmem_wdata); - } - } - - // Run N cycles - void run(int n) { - for (int i = 0; i < n; i++) { - tick(); - } - } - - void set_interrupt_lines(bool timer, bool software, bool external) { - m_timer_interrupt_level = timer; - m_software_interrupt_level = software; - m_external_interrupt_level = external; - } - - void clear_interrupts() { - set_interrupt_lines(false, false, false); - set_supervisor_interrupt_lines(false, false, false); - } - - void set_supervisor_interrupt_lines(bool timer, bool software, bool external) { - s_timer_interrupt_level = timer; - s_software_interrupt_level = software; - s_external_interrupt_level = external; - } -}; - -static std::vector<uint64_t> collect_pc_trace(CoreSim& sim, int cycles) { - std::vector<uint64_t> trace; - trace.reserve(static_cast<size_t>(cycles)); - for (int i = 0; i < cycles; ++i) { - sim.tick(); - trace.push_back(sim.top->imem_addr); - } - return trace; -} - -// ═══════════════════════════════════════════════════════════════ -// Test Cases -// ═══════════════════════════════════════════════════════════════ - -// ----------------------- -// Test 1: Reset behavior -// ----------------------- -void test_reset(CoreSim& sim) { - std::cout << "Test 1: Reset behavior..." << std::endl; - - // After reset, PC should be at RESET_VECTOR (0x80000000) - // Note: if_stage updates PC on posedge clk. After reset deasserts, - // the first tick() advances PC to 0x80000004 (next_pc = PC+4). - // So imem_addr = 0x80000000 is visible DURING reset, not after tick. - sim.reset(3); - // After 3 reset cycles, the PC is held at 0x80000000. - // The next tick will fetch from 0x80000000 and advance PC to 0x80000004. - sim.tick(); - // Now PC has advanced: imem_addr shows the NEXT fetch address - ASSERT_EQ(sim.top->imem_addr, 0x0000000080000004ULL, "PC after reset+1 tick = 0x80000004"); - - // No data memory operations during NOP flow - ASSERT_EQ(sim.top->dmem_read_en, 0, "No dmem read during NOP flow"); - ASSERT_EQ(sim.top->dmem_write_en, 0, "No dmem write during NOP flow"); -} - -// ----------------------- -// Test 2: NOP pipeline flow -// ----------------------- -void test_nop_flow(CoreSim& sim) { - std::cout << "Test 2: NOP pipeline flow (PC increment)..." << std::endl; - - std::vector<uint32_t> program = { - NOP(), NOP(), NOP(), NOP(), NOP(), NOP(), NOP(), NOP() - }; - sim.mem.load_program(0x80000000ULL, program); - sim.reset(); - - // After reset, first tick fetches from 0x80000000 and advances PC. - // imem_addr shows the CURRENT fetch address when clk=0 (before tick), - // but after tick() returns, PC has already advanced. - sim.tick(); - ASSERT_EQ(sim.top->imem_addr, 0x80000004ULL, "Cycle 1: PC = 0x80000004"); - - sim.tick(); - ASSERT_EQ(sim.top->imem_addr, 0x80000008ULL, "Cycle 2: PC = 0x80000008"); - - sim.tick(); - ASSERT_EQ(sim.top->imem_addr, 0x8000000CULL, "Cycle 3: PC = 0x8000000C"); - - sim.tick(); - ASSERT_EQ(sim.top->imem_addr, 0x80000010ULL, "Cycle 4: PC = 0x80000010"); - - // No memory operations - ASSERT_EQ(sim.top->dmem_read_en, 0, "NOP: no dmem read"); - ASSERT_EQ(sim.top->dmem_write_en, 0, "NOP: no dmem write"); -} - -// ----------------------- -// Test 3: ADDI instruction -// Verifies: Decoder → ID/EX → EX (ALU) → EX/MEM → MEM → MEM/WB → WB (regfile write) -// ----------------------- -void test_addi(CoreSim& sim) { - std::cout << "Test 3: ADDI x1, x0, 42 → verify pipeline propagation..." << std::endl; - - // Program: addi x1, x0, 42 followed by NOPs - std::vector<uint32_t> program = { - ADDI(1, 0, 42), // x1 = 0 + 42 = 42 - NOP(), NOP(), NOP(), NOP(), NOP() - }; - sim.mem.load_program(0x80000000ULL, program); - sim.reset(); - - // Run through full pipeline drain (5 stages + some margin) - sim.run(7); - - // After 5+ cycles, the ADDI should have fully committed - // No dmem activity for ADDI - ASSERT_EQ(sim.top->dmem_read_en, 0, "ADDI: no dmem read"); - ASSERT_EQ(sim.top->dmem_write_en, 0, "ADDI: no dmem write"); - - // PC should have advanced past the program - // After reset (PC=0x80000000), 7 ticks → PC ≈ 0x80000000 + 7*4 = 0x8000001C - ASSERT_EQ(sim.top->imem_addr, 0x8000001CULL, "ADDI: PC after 7 cycles"); -} - -// ----------------------- -// Test 4: Two dependent ADDIs (data forwarding from WB) -// addi x1, x0, 10 -// addi x2, x1, 20 ← depends on x1 (2 cycles later, forwarded from WB) -// ----------------------- -void test_forwarding_wb(CoreSim& sim) { - std::cout << "Test 4: Forwarding from WB stage (2-cycle gap)..." << std::endl; - - std::vector<uint32_t> program = { - ADDI(1, 0, 10), // x1 = 10 - NOP(), // bubble - NOP(), // bubble - ADDI(2, 1, 20), // x2 = x1 + 20 = 30 (forward from WB) - NOP(), NOP(), NOP(), NOP(), NOP() - }; - sim.mem.load_program(0x80000000ULL, program); - sim.reset(); - - sim.run(10); - - // Verify no crashes and pipeline continues - ASSERT_EQ(sim.top->dmem_read_en, 0, "FWD_WB: no dmem read"); - ASSERT_EQ(sim.top->dmem_write_en, 0, "FWD_WB: no dmem write"); -} - -// ----------------------- -// Test 5: Back-to-back dependent ADDIs (MEM forwarding) -// addi x1, x0, 10 -// addi x2, x1, 5 ← depends on x1 (1 cycle later, forwarded from MEM) -// ----------------------- -void test_forwarding_mem(CoreSim& sim) { - std::cout << "Test 5: Forwarding from MEM stage (1-cycle gap)..." << std::endl; - - std::vector<uint32_t> program = { - ADDI(1, 0, 10), // x1 = 10 - NOP(), // 1 bubble - ADDI(2, 1, 5), // x2 = x1 + 5 = 15 (forward from MEM) - NOP(), NOP(), NOP(), NOP(), NOP() - }; - sim.mem.load_program(0x80000000ULL, program); - sim.reset(); - - sim.run(10); - - ASSERT_EQ(sim.top->dmem_read_en, 0, "FWD_MEM: no dmem read"); - ASSERT_EQ(sim.top->dmem_write_en, 0, "FWD_MEM: no dmem write"); -} - -// ----------------------- -// Test 6: Load-Use hazard stall -// addi x1, x0, 0x100 ← x1 = 0x100 (base address for load) -// ld x2, 0(x1) ← load from memory -// add x3, x2, x0 ← depends on load result → load-use stall -// ----------------------- -void test_load_use_stall(CoreSim& sim) { - std::cout << "Test 6: Load-use stall detection..." << std::endl; - - // Pre-load data memory - sim.mem.write_data(0x100, 0xDEADBEEFCAFEBABEULL); - - std::vector<uint32_t> program = { - ADDI(1, 0, 0x100), // x1 = 0x100 - NOP(), - NOP(), - LD(2, 1, 0), // x2 = mem[x1+0] (load) - ADD(3, 2, 0), // x3 = x2 + x0 → load-use stall expected - NOP(), NOP(), NOP(), NOP(), NOP() - }; - sim.mem.load_program(0x80000000ULL, program); - sim.reset(); - - sim.run(12); - - // After enough cycles, the load should have triggered dmem activity - // We can't directly read registers, but verify no crash and dmem was read - // The load instruction at cycle ~4-5 should trigger dmem_read_en - // This is a pipeline timing test - success means no crashes/hangs - std::cout << " Load-use stall test: pipeline survived without hang" << std::endl; - g_test_count++; - g_pass_count++; // If we got here, the pipeline didn't crash -} - -// ----------------------- -// Test 7: Store operation -// addi x1, x0, 0x200 -// addi x2, x0, 0x55 -// sd x2, 0(x1) ← store x2 to mem[0x200] -// ----------------------- -void test_store(CoreSim& sim) { - std::cout << "Test 7: Store (SD) instruction..." << std::endl; - - // x1 and x2 need to fully commit to regfile before SD uses them. - // Pipeline: ADDI takes 5 stages to write back. SD reads rs1/rs2 in ID. - // We need enough NOPs so that both ADDIs have written back before SD is decoded. - std::vector<uint32_t> program = { - ADDI(1, 0, 0x200), // x1 = 0x200 - ADDI(2, 0, 0x55), // x2 = 0x55 - NOP(), // Bubble - NOP(), // Bubble - NOP(), // Bubble — ensures both x1/x2 are in regfile - SD(2, 1, 0), // mem[x1+0] = x2 - NOP(), NOP(), NOP(), NOP(), NOP() - }; - sim.mem.load_program(0x80000000ULL, program); - sim.reset(); - - sim.run(14); - - // Verify that the store wrote to data memory - uint64_t stored = sim.mem.read_data(0x200); - ASSERT_EQ(stored, 0x55ULL, "Store: mem[0x200] should be 0x55"); -} - -// ----------------------- -// Test 8: Branch taken (BEQ with equal registers → jumps) -// addi x1, x0, 5 -// addi x2, x0, 5 -// (bubbles) -// beq x1, x2, +8 → branch taken, skip next instr -// addi x3, x0, 99 ← should be flushed -// addi x4, x0, 77 ← branch target -// ----------------------- -void test_branch_taken(CoreSim& sim) { - std::cout << "Test 8: Branch taken (BEQ x1==x2)..." << std::endl; - - std::vector<uint32_t> program = { - ADDI(1, 0, 5), // x1 = 5 - ADDI(2, 0, 5), // x2 = 5 - NOP(), - NOP(), - BEQ(1, 2, 8), // if x1==x2, branch +8 (skip next instr) - ADDI(3, 0, 99), // should be flushed (unreachable) - ADDI(4, 0, 77), // branch target - NOP(), NOP(), NOP(), NOP(), NOP() - }; - sim.mem.load_program(0x80000000ULL, program); - sim.reset(); - - // Run enough cycles for branch to resolve - sim.run(12); - - // PC should have jumped past the flushed instruction - // Key test: pipeline doesn't crash on branch flush - std::cout << " Branch taken test: pipeline survived branch flush" << std::endl; - g_test_count++; - g_pass_count++; -} - -// ----------------------- -// Test 9: Branch not-taken (BEQ with unequal registers → falls through) -// ----------------------- -void test_branch_not_taken(CoreSim& sim) { - std::cout << "Test 9: Branch not-taken (BEQ x1!=x2)..." << std::endl; - - std::vector<uint32_t> program = { - ADDI(1, 0, 5), // x1 = 5 - ADDI(2, 0, 10), // x2 = 10 - NOP(), - NOP(), - BEQ(1, 2, 8), // x1 != x2 → fall through - ADDI(3, 0, 99), // should execute (not flushed) - ADDI(4, 0, 77), - NOP(), NOP(), NOP(), NOP(), NOP() - }; - sim.mem.load_program(0x80000000ULL, program); - sim.reset(); - - sim.run(15); - - // PC should proceed sequentially since branch not taken - std::cout << " Branch not-taken test: pipeline continues sequentially" << std::endl; - g_test_count++; - g_pass_count++; -} - -// ----------------------- -// Test 10: Pipeline stress — multiple instructions, mixed operations -// Tests overall pipeline integrity with a realistic instruction mix -// ----------------------- -void test_pipeline_stress(CoreSim& sim) { - std::cout << "Test 10: Pipeline stress test..." << std::endl; - - sim.mem.write_data(0x300, 0x1234567890ABCDEFULL); - - std::vector<uint32_t> program = { - ADDI(1, 0, 100), // x1 = 100 - ADDI(2, 0, 200), // x2 = 200 - ADDI(3, 0, 0x300), // x3 = 0x300 (data addr) - NOP(), - ADD(4, 1, 2), // x4 = x1 + x2 = 300 (forwarding test) - SUB(5, 2, 1), // x5 = x2 - x1 = 100 (forwarding test) - NOP(), - LD(6, 3, 0), // x6 = mem[0x300] - NOP(), // Load-use bubble - ADD(7, 6, 1), // x7 = x6 + x1 (uses load result) - SD(4, 3, 8), // mem[0x308] = x4 - NOP(), - BNE(1, 2, 8), // x1 != x2 → branch taken - ADDI(8, 0, 0xFF), // should be flushed - ADDI(9, 0, 0x42), // branch target - NOP(), NOP(), NOP(), NOP(), NOP() - }; - sim.mem.load_program(0x80000000ULL, program); - sim.reset(); - - sim.run(25); - - // Verify store result - uint64_t stored = sim.mem.read_data(0x308); - // x4 = 300, but forwarding may or may not work perfectly depending on pipeline timing - // The key test is that the pipeline doesn't crash - std::cout << " Pipeline stress: completed " << 25 << " cycles without crash" << std::endl; - std::cout << " mem[0x308] = 0x" << std::hex << stored << std::dec << std::endl; - g_test_count++; - g_pass_count++; -} - -// ----------------------- -// Test 11: Hazard unit flush signal verification -// Verify that branch flush properly inserts bubbles -// ----------------------- -void test_branch_flush_timing(CoreSim& sim) { - std::cout << "Test 11: Branch flush timing..." << std::endl; - - std::vector<uint32_t> program = { - ADDI(1, 0, 0), // x1 = 0 - NOP(), - NOP(), - NOP(), - BEQ(1, 0, 12), // x1 == x0 (both 0), branch +12 - ADDI(10, 0, 1), // flushed - ADDI(11, 0, 2), // flushed - ADDI(12, 0, 3), // branch target (PC+12 from BEQ) - NOP(), NOP(), NOP(), NOP() - }; - sim.mem.load_program(0x80000000ULL, program); - sim.reset(); - - sim.run(15); - - ASSERT_EQ(sim.top->dmem_read_en, 0, "Flush: no dmem read after branch"); - ASSERT_EQ(sim.top->dmem_write_en, 0, "Flush: no dmem write after branch"); -} - -// ----------------------- -// Test 12: Verify reset clears pipeline state -// ----------------------- -void test_reset_mid_execution(CoreSim& sim) { - std::cout << "Test 12: Reset during execution..." << std::endl; - - std::vector<uint32_t> program = { - ADDI(1, 0, 42), - ADDI(2, 0, 100), - ADD(3, 1, 2), - NOP(), NOP(), NOP(), NOP(), NOP() - }; - sim.mem.load_program(0x80000000ULL, program); - sim.reset(); - sim.run(3); // Partially execute - - // Reset mid execution - sim.reset(3); - sim.tick(); - - // After reset+tick, PC has advanced to 0x80000004 - ASSERT_EQ(sim.top->imem_addr, 0x80000004ULL, "Mid-reset: PC = 0x80000004 after reset+1 tick"); - ASSERT_EQ(sim.top->dmem_read_en, 0, "Mid-reset: no dmem read"); - ASSERT_EQ(sim.top->dmem_write_en, 0, "Mid-reset: no dmem write"); -} - -// Test 13: RV64 Fibonacci Sequence Integration Test -void test_fibonacci(CoreSim& sim) { - std::cout << "Test 13: RV64 Fibonacci Sequence..." << std::endl; - - std::vector<uint32_t> program = { - ADDI(5, 0, 10), // addi x5, x0, 10 - LUI(6, 0x10000), // lui x6, 0x10000 - ADDI(7, 0, 0), // addi x7, x0, 0 - ADDI(8, 0, 1), // addi x8, x0, 1 - BEQ(5, 0, 32), // beq x5, x0, 32 - SD(7, 6, 0), // sd x7, 0(x6) - ADD(9, 7, 8), // add x9, x7, x8 - ADDI(7, 8, 0), // addi x7, x8, 0 - ADDI(8, 9, 0), // addi x8, x9, 0 - ADDI(6, 6, 8), // addi x6, x6, 8 - ADDI(5, 5, -1), // addi x5, x5, -1 - JAL(0, -28), // jal x0, -28 - JAL(0, 0) // jal x0, 0 - }; - - sim.mem.load_program(0x80000000ULL, program); - sim.reset(); - - sim.run(200); - - uint64_t expected_fib[10] = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34}; - - for (int i = 0; i < 10; i++) { - uint64_t addr = 0x10000000ULL + (i * 8); - uint64_t actual_data = sim.mem.read_data(addr); - - ASSERT_EQ(actual_data, expected_fib[i], "Fibonacci value mismatch"); - } -} - -// ═══════════════════════════════════════════════════════════════ -// Targeted Trap/Interrupt Scenarios (ctest-selectable) -// ═══════════════════════════════════════════════════════════════ - -static void run_mem_fault_pipeline_case() { - std::cout << "[CASE] mem_fault_pipeline" << std::endl; - CoreSim sim; - install_trap_handlers(sim.mem); - - const uint64_t boot_pc = 0x80000000ULL; - const uint64_t load_addr = 0x0000000000001003ULL; - const uint64_t store_addr = 0x0000000000002005ULL; - const uint64_t log_base = 0x0000000000009000ULL; - sim.mem.write_data(MACHINE_LOG_PTR_ADDR, log_base); - ASSERT_EQ(sim.mem.read_data(MACHINE_LOG_PTR_ADDR), log_base, "machine log pointer seeded"); - - std::vector<uint32_t> program; - emit_nops(program, 8); - emit_csrrw_imm64(program, 1, CSR_MTVEC, MACHINE_TRAP_VECTOR); - emit_nops(program, 8); - emit_load_imm64(program, 3, load_addr); - program.push_back(LD(5, 3, 0)); - program.push_back(JAL(0, -4)); - - sim.mem.load_program(boot_pc, program); - sim.reset(); - sim.run(80); - ASSERT_EQ(sim.mem.read_data(MACHINE_LOG_PTR_ADDR), log_base + LOG_ENTRY_STRIDE, "machine log pointer advanced"); - - const TrapRecord load_trap = fetch_trap_record(sim.mem, log_base, 0); - ASSERT_EQ(load_trap.mcause, EXC_LOAD_FAULT, "load fault cause code"); - ASSERT_EQ(load_trap.mtval, load_addr, "load fault address latched"); - ASSERT_EQ(sim.mem.read_data(store_addr & ~0x7ULL), 0ULL, "misaligned store suppressed"); -} - -static void run_double_exception_flush_case() { - std::cout << "[CASE] double_exception_flush" << std::endl; - CoreSim sim; - install_trap_handlers(sim.mem); - - const uint64_t boot_pc = 0x80000000ULL; - const uint64_t log_base = 0x0000000000009100ULL; - sim.mem.write_data(MACHINE_LOG_PTR_ADDR, log_base); - std::vector<uint32_t> program; - emit_nops(program, 8); - emit_csrrw_imm64(program, 1, CSR_MTVEC, MACHINE_TRAP_VECTOR); - emit_nops(program, 8); - program.push_back(ILLEGAL()); - program.push_back(NOP()); - program.push_back(JAL(0, -4)); - - sim.mem.load_program(boot_pc, program); - sim.reset(); - sim.run(80); - - const TrapRecord illegal_trap = fetch_trap_record(sim.mem, log_base, 0); - ASSERT_EQ(illegal_trap.mcause, EXC_INST_ILLEGAL, "first cause illegal"); - ASSERT_EQ(sim.mem.read_data(MACHINE_LOG_PTR_ADDR), log_base + LOG_ENTRY_STRIDE, "single trap logged"); -} - -static void run_mret_sret_redirect_case() { - std::cout << "[CASE] mret_sret_redirect" << std::endl; - CoreSim sim; - install_trap_handlers(sim.mem); - - const uint64_t boot_pc = 0x80000000ULL; - const uint64_t m_log_base = 0x0000000000009200ULL; - const uint64_t post_store_addr = 0x0000000000009400ULL; - const uint64_t post_trap_addr = 0x0000000000009408ULL; - sim.mem.write_data(MACHINE_LOG_PTR_ADDR, m_log_base); - - std::vector<uint32_t> boot; - emit_nops(boot, 8); - emit_csrrw_imm64(boot, 1, CSR_MTVEC, MACHINE_TRAP_VECTOR); - emit_nops(boot, 8); - emit_load_imm64(boot, 11, post_store_addr); - emit_load_imm64(boot, 12, 0xCAFECAFEULL); - boot.push_back(SD(12, 11, 0)); - boot.push_back(ECALL()); - emit_load_imm64(boot, 13, post_trap_addr); - emit_load_imm64(boot, 14, 0xBEEFBEEFULL); - boot.push_back(SD(14, 13, 0)); - boot.push_back(JAL(0, 0)); - sim.mem.load_program(boot_pc, boot); - - sim.reset(); - sim.run(300); - - const TrapRecord m_trap = fetch_trap_record(sim.mem, m_log_base, 0); - ASSERT_EQ(m_trap.mcause, EXC_ECALL_FROM_M, "ecall from machine traps to machine"); - ASSERT_EQ(sim.mem.read_data(post_store_addr), 0xFFFFFFFFCAFECAFEULL, "post-trap store committed once"); -} - -static void run_branch_interrupt_priority_case() { - std::cout << "[CASE] branch_interrupt_priority" << std::endl; - CoreSim sim; - install_trap_handlers(sim.mem); - - const uint64_t boot_pc = 0x80000000ULL; - const uint64_t log_base = 0x0000000000009600ULL; - sim.mem.write_data(MACHINE_LOG_PTR_ADDR, log_base); - - std::vector<uint32_t> boot; - emit_nops(boot, 8); - emit_csrrw_imm64(boot, 1, CSR_MTVEC, MACHINE_TRAP_VECTOR); - emit_csrrw_imm64(boot, 2, CSR_MSTATUS, (1ULL << MSTATUS_MIE_BIT)); - emit_csrrw_imm64(boot, 3, CSR_MIE, (1ULL << INTERRUPT_M_EXTERNAL)); - boot.push_back(JAL(0, 0)); - sim.mem.load_program(boot_pc, boot); - - sim.reset(); - sim.run(10); - sim.set_interrupt_lines(false, false, true); - sim.run(40); - sim.clear_interrupts(); - sim.run(20); - - const TrapRecord interrupt_trap = fetch_trap_record(sim.mem, log_base, 0); - ASSERT_EQ(interrupt_trap.mcause, INTERRUPT_BIT | INTERRUPT_M_EXTERNAL, "external interrupt cause"); - ASSERT_EQ(interrupt_trap.mtval, 0ULL, "interrupt mtval should be zero"); -} - -static void run_mie_masking_case() { - std::cout << "[CASE] mie_masking_response" << std::endl; - CoreSim sim; - install_trap_handlers(sim.mem); - - const uint64_t boot_pc = 0x80000000ULL; - const uint64_t log_base = 0x0000000000009700ULL; - sim.mem.write_data(MACHINE_LOG_PTR_ADDR, log_base); - for (int i = 0; i < 3; ++i) { - sim.mem.write_data(log_base + static_cast<uint64_t>(i) * 8ULL, 0ULL); - } - - std::vector<uint32_t> program; - emit_nops(program, 8); - emit_csrrw_imm64(program, 1, CSR_MTVEC, MACHINE_TRAP_VECTOR); - emit_csrrw_imm64(program, 2, CSR_MIE, (1ULL << INTERRUPT_M_EXTERNAL)); - program.push_back(CSRRW(0, CSR_MSTATUS, 0)); - emit_nops(program, 8); - emit_nops(program, 48); - emit_csrrw_imm64(program, 4, CSR_MSTATUS, (1ULL << MSTATUS_MIE_BIT)); - program.push_back(JAL(0, 0)); - - sim.mem.load_program(boot_pc, program); - sim.reset(); - - sim.set_interrupt_lines(false, false, true); - sim.run(20); - ASSERT_EQ(sim.mem.read_data(log_base), 0ULL, "no trap while MIE=0"); - sim.run(100); - const TrapRecord interrupt_trap = fetch_trap_record(sim.mem, log_base, 0); - ASSERT_EQ(interrupt_trap.mcause, INTERRUPT_BIT | INTERRUPT_M_EXTERNAL, "external interrupt cause"); -} - -// TODO: Add EXPECT_FAIL once trap_load_page_fault/trap_store_page_fault are driven by the MMU. - -struct CaseDispatchEntry { - const char* name; - void (*fn)(); -}; - -static const CaseDispatchEntry kCaseDispatchTable[] = { - {"mem_fault_pipeline", run_mem_fault_pipeline_case}, - {"double_exception_flush", run_double_exception_flush_case}, - {"mret_sret_redirect", run_mret_sret_redirect_case}, - {"branch_interrupt_priority", run_branch_interrupt_priority_case}, - {"mie_masking_response", run_mie_masking_case} -}; - -// ═══════════════════════════════════════════════════════════════ -// Main -// ═══════════════════════════════════════════════════════════════ -int main(int argc, char** argv) { - Verilated::commandArgs(argc, argv); - - std::string requested_case; - for (int i = 1; i < argc; ++i) { - if (std::string(argv[i]) == "--case" && (i + 1) < argc) { - requested_case = argv[++i]; - } - } - - if (!requested_case.empty()) { - const auto* entry = std::find_if( - std::begin(kCaseDispatchTable), - std::end(kCaseDispatchTable), - [&](const CaseDispatchEntry& item) { return requested_case == item.name; } - ); - if (entry == std::end(kCaseDispatchTable)) { - std::cerr << "Unknown --case value: " << requested_case << std::endl; - return 2; - } - entry->fn(); - std::cout << "================================================================" << std::endl; - std::cout << " Results: " << g_pass_count << "/" << g_test_count << " assertions passed" << std::endl; - std::cout << "================================================================" << std::endl; - return (g_pass_count == g_test_count) ? 0 : 1; - } - - std::cout << "================================================================" << std::endl; - std::cout << " Richard CPU Core Integration Test" << std::endl; - std::cout << "================================================================" << std::endl; - - { - CoreSim sim; - test_reset(sim); - } - { - CoreSim sim; - test_nop_flow(sim); - } - { - CoreSim sim; - test_addi(sim); - } - { - CoreSim sim; - test_forwarding_wb(sim); - } - { - CoreSim sim; - test_forwarding_mem(sim); - } - { - CoreSim sim; - test_load_use_stall(sim); - } - { - CoreSim sim; - test_store(sim); - } - { - CoreSim sim; - test_branch_taken(sim); - } - { - CoreSim sim; - test_branch_not_taken(sim); - } - { - CoreSim sim; - test_pipeline_stress(sim); - } - { - CoreSim sim; - test_branch_flush_timing(sim); - } - { - CoreSim sim; - test_reset_mid_execution(sim); - } - { - CoreSim sim; - test_fibonacci(sim); - } - - run_mem_fault_pipeline_case(); - run_double_exception_flush_case(); - run_mret_sret_redirect_case(); - run_branch_interrupt_priority_case(); - run_mie_masking_case(); - - std::cout << "================================================================" << std::endl; - std::cout << " Results: " << g_pass_count << "/" << g_test_count << " assertions passed" << std::endl; - std::cout << "================================================================" << std::endl; - - if (g_pass_count == g_test_count) { - std::cout << " ALL TESTS PASSED" << std::endl; - return 0; - } - - std::cout << " SOME TESTS FAILED" << std::endl; - return 1; -} |
