From 7c671e752160174965b8af8b66b9df15e00a4387 Mon Sep 17 00:00:00 2001 From: Richard Qin Date: Tue, 17 Mar 2026 01:02:51 +0800 Subject: Instruction Decoder Stage unit and tests --- rv64cpu/rtl/core/id_stage.sv | 142 ++++++++++++++++++++++++++++++ rv64cpu/tb/CMakeLists.txt | 11 +++ rv64cpu/tb/unit/tb_id_stage.cc | 192 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 345 insertions(+) create mode 100644 rv64cpu/rtl/core/id_stage.sv create mode 100644 rv64cpu/tb/unit/tb_id_stage.cc (limited to 'rv64cpu') diff --git a/rv64cpu/rtl/core/id_stage.sv b/rv64cpu/rtl/core/id_stage.sv new file mode 100644 index 0000000..5e22662 --- /dev/null +++ b/rv64cpu/rtl/core/id_stage.sv @@ -0,0 +1,142 @@ +// 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: rtl/core/id_stage.sv +// Description: Instruction Decode Stage, instantiate the Decoder and RegFile, extract control signals and operands + +module id_stage + import rv64_pkg::*; +( + input logic clk, + input logic rst, + + // Inputs from IF Stage (via IF/ID Pipeline Register) + input logic [XLEN-1:0] if_pc, // PC of the current instruction + input logic [INST_WIDTH-1:0] if_instr, // The 32-bit instruction fetched + + // Write-back Inputs from WB Stage (to RegFile) + input logic wb_reg_write_en, // Register write enable from WB stage + input logic [4:0] wb_rd_addr, // Destination register address from WB + input logic [XLEN-1:0] wb_rd_wdata, // Data to write back to register + + // Outputs to EX Stage + // (Notice the 'id_' prefix to signify signals leaving the ID stage) + // Data/Operands + output logic [XLEN-1:0] id_pc, // Pass-through PC for branch/jump calculation + output logic [XLEN-1:0] id_rs1_rdata, // Source register 1 data + output logic [XLEN-1:0] id_rs2_rdata, // Source register 2 data + output logic [XLEN-1:0] id_imm, // Sign-extended generic immediate + + // Execution Control (ALU & Branch) + output alu_op_t id_alu_op, // ALU operation code + output logic id_alu_src1_sel,// 0: rs1_rdata, 1: PC + output logic id_alu_src2_sel,// 0: rs2_rdata, 1: imm + output logic id_is_word_op, // 1: RV64 32-bit operation (e.g. ADDW) + output logic id_is_branch, // 1: B-type branch instruction + output branch_op_t id_branch_op, // Specific condition for branch (BEQ, BNE...) + output logic id_is_jump, // 1: JAL or JALR + + // Memory & Writeback Control (Passed down to MEM/WB stages eventually) + output logic id_is_load, // 1: Load instruction + output logic id_is_store, // 1: Store instruction + output logic [2:0] id_mem_size, // Memory access size (Byte/Half/Word/Double) + output logic id_reg_write_en,// 1: Instruction will write to a register + output logic [4:0] id_rd_addr, // Destination register index + output logic [1:0] id_wb_sel, // Writeback source (0: ALU, 1: MEM, 2: PC+4) + + // System / Exception + output logic id_is_csr, + output logic [2:0] id_csr_op, + output logic id_illegal_instr, + output logic id_is_ecall, + output logic id_is_ebreak, + output logic id_is_mret, + output logic id_is_sret, + output logic id_is_wfi, + output logic id_is_sfence_vma, + output logic id_is_fence, + output logic id_is_fence_i +); + // Internal Wires for connecting sub-modules + logic [4:0] rs1_addr; + logic [4:0] rs2_addr; + + // Pass-through PC directly to next stage + assign id_pc = if_pc; + + // Instruction Decoder Instantiation + + decoder u_decoder ( + .instr (if_instr), + + // Outputs to RegFile read ports + .rs1_addr (rs1_addr), + .rs2_addr (rs2_addr), + .rd_addr (id_rd_addr), + + // Outputs to EX stage (ALU & execution control) + .alu_op (id_alu_op), + .alu_src1_sel (id_alu_src1_sel), + .alu_src2_sel (id_alu_src2_sel), + .is_word_op (id_is_word_op), + .imm (id_imm), + + // Branch & Jump control + .is_branch (id_is_branch), + .branch_op (id_branch_op), + .is_jump (id_is_jump), + + // Memory control + .is_load (id_is_load), + .is_store (id_is_store), + .mem_size (id_mem_size), + + // Writeback control + .reg_write_en (id_reg_write_en), + .wb_sel (id_wb_sel), + + // System Control + .is_csr (id_is_csr), + .csr_op (id_csr_op), + .is_ecall (id_is_ecall), + .is_ebreak (id_is_ebreak), + .is_mret (id_is_mret), + .is_sret (id_is_sret), + .is_wfi (id_is_wfi), + .is_sfence_vma (id_is_sfence_vma), + .is_fence (id_is_fence), + .is_fence_i (id_is_fence_i), + .illegal_instr (id_illegal_instr) + ); + + // Register File Instantiation + regfile u_regfile ( + .clk (clk), + .rst (rst), + + // Read ports (from Decoder) + .rs1_addr (rs1_addr), + .rs2_addr (rs2_addr), + .rs1_rdata (id_rs1_rdata), + .rs2_rdata (id_rs2_rdata), + + // Write port (from WB stage) + .reg_write_en (wb_reg_write_en), + .rd_addr (wb_rd_addr), + .rd_wdata (wb_rd_wdata) + ); + +endmodule diff --git a/rv64cpu/tb/CMakeLists.txt b/rv64cpu/tb/CMakeLists.txt index 8fc3ccf..78ab903 100644 --- a/rv64cpu/tb/CMakeLists.txt +++ b/rv64cpu/tb/CMakeLists.txt @@ -74,6 +74,17 @@ add_verilator_target( 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}/id_stage.sv + INCLUDE_DIRS ${RTL_COMMON_INCLUDES} + TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_id_stage.cc +) # # add_verilator_target( # NAME tb_csr diff --git a/rv64cpu/tb/unit/tb_id_stage.cc b/rv64cpu/tb/unit/tb_id_stage.cc new file mode 100644 index 0000000..0d3aa71 --- /dev/null +++ b/rv64cpu/tb/unit/tb_id_stage.cc @@ -0,0 +1,192 @@ +// 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_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; +} -- cgit v1.2.3