写在前面:今天去见了导师,他强烈要求我把设计中的decoder删去,去掉宏定义引入局部变量,使用封装的函数来取而代之。并在其他运算模块调用函数的返回值,提高代码简洁度和清晰度,避免全局变量污染环境,下面是根据导师的主页总结的设计笔记。-----2025/7/1
opcodes
包是为一个简单的处理器设计的辅助模块,作用是封装指令解析相关的功能,供 CPU 的其他模块调用,比如alu。可以把它当作“指令解释器”:外部模块不需要知道一条 16 位指令的结构细节,只需要调用这里提供的函数即可获取操作码、操作数、ALU 操作类型等信息。// This package knows about the instruction coding
// the rest of the processor accesses this information by calling functions
package opcodes;
// Define ALU Operations Type:
//
typedef
enum logic [3:0] { aluACC, aluMem, aluADD, aluSUB, aluAND, aluOR, aluNOT, aluLSL, aluLSR }
alu_operation_t;
// Define Opcodes:
localparam NOP = 4'd0;
localparam JMP = 4'd1;
localparam JMPZ = 4'd2;
localparam JMPNZ = 4'd3;
localparam LDA = 4'd4;
localparam ADD = 4'd5;
localparam SUB = 4'd6;
localparam AND = 4'd7;
localparam OR = 4'd8;
localparam NOT = 4'd9;
localparam LSL = 4'd10;
localparam LSR = 4'd11;
localparam STA = 4'd15;
//
// Extract opcode and operand from instruction
//
function [11:0] get_operand(input [15:0] instruction);
return instruction[11:0];
endfunction
function [3:0] get_opcode(input [15:0] instruction);
return instruction[15:12];
endfunction
function is_store(input [15:0] instruction);
return (get_opcode(instruction) == STA);
endfunction
function is_unconditional_jump(input [15:0] instruction);
return (get_opcode(instruction) == JMP);
endfunction
function is_conditional_jump(input [15:0] instruction);
return ((get_opcode(instruction) == JMPZ) || (get_opcode(instruction) == JMPNZ));
endfunction
function expected_flag(input [15:0] instruction);
return (get_opcode(instruction) == JMPZ);
endfunction
//
// Decode Opcode to create ALU Function code
//
function alu_operation_t get_alu_operation(input [15:0] instruction);
case (get_opcode(instruction))
LDA : return aluMem;
ADD : return aluADD;
SUB : return aluSUB;
AND : return aluAND;
OR : return aluOR;
NOT : return aluNOT;
LSL : return aluLSL;
LSR : return aluLSR;
default : return aluACC;
endcase
endfunction
endpackage
步骤一:引入包
import opcodes::*;
步骤二:使用函数
例如,在你的 CPU 控制单元中:
always_comb begin
logic [15:0] instr = instruction_register;
if (is_unconditional_jump(instr)) begin
pc = get_operand(instr);
end
alu_op = get_alu_operation(instr);
end
这样你就不需要手动位提取指令中的字段,而是使用包内封装好的接口函数,代码更清晰,复用性强。
each control signal is a function !!!
目的是为运算表达式提供返回值,便于简化代码和维护大型代码。
import opcodes::*;
从 opcodes.svh 中导入全部定义(枚举类型、函数、局部参数等)。
表示导入所有内容,相当于 using namespace 的意思。
什么时候使用typedef enum? 什么时候使用localparam 什么时候使用package ?什么时候使用宏定义呢?(下面是一些使用场景)
场景 | 推荐用法 | 原因 |
---|---|---|
枚举、状态码、类型分类(立即数类型、状态机) | typedef enum |
更清晰、类型安全、IDE 支持好 |
单个有名称的常数(立即数宽度、总线宽度) | localparam |
模块内部常量,稳定安全 |
跨文件复用的常量值(硬件标准中定义的) | localparam + package |
替代宏定义更安全 |
条件编译(例如仿真开关、版本切换) | \ define` 宏定义 |
宏能控制编译行为 |
\
define`) 来定义常量?虽然可以写:
`define IMM_NONE_R 3'd0
但这样做有这些问题:
enum
(推荐,适合类型分类):typedef enum logic [2:0] {
IMM_I = 3'd1, IMM_S = 3'd2
} imm_type_e;
localparam
(推荐,适合常量):localparam int XLEN = 32;
localparam logic [3:0] ALU_ADD = 4'd0;
\
define`(比如编译开关):`define ENABLE_DEBUG
`ifdef ENABLE_DEBUG
$display("Debug mode");
`endif
分类枚举 ➜ 用 typedef enum
(像 imm_type_e
, alu_op_t
)
模块内部常量 ➜ 用 localparam
跨模块常量 ➜ 用 localparam
+ package
:把多个 localparam
常量集中写到一个 package
(包)里,然后在其他模块中 import
这个包,就能像使用本地变量一样使用这些常量。
传统写法 (\ define`) |
localparam + package 好处 |
---|---|
\ define ALU_ADD 4’d0` |
有作用域(包),不污染全局命名空间 |
所有模块共享一个全局命名 | 每个包管理自己的命名,更安全 |
没有类型检查 | localparam logic [3:0] 有类型 |
调试中只看到数字不知含义 | 名称保留,有利调试和 IDE 提示 |
更改示例,下面是根据导师的修改建议我修改decoder内部控制信号的过程展示。以下是原始代码:
// Immediate type enum for decoding purposes
typedef enum logic [2:0] {
IMM_NONE_R = 3'd0, // R-type: no immediate field
IMM_I = 3'd1, // I-type: arithmetic immediate, load, JALR
IMM_S = 3'd2, // S-type: store instructions
IMM_B = 3'd3, // B-type: branch instructions
IMM_U = 3'd4, // U-type: upper immediate (LUI, AUIPC)
IMM_J = 3'd5 // J-type: jump and link (JAL)
} imm_type_e;
imm_type_e imm_type_sel;
assign imm_type = imm_type_sel; // output to datapath or control unit
// Decode opcode to determine the immediate format
always_comb begin
unique case (opcode)
7'b0110011: imm_type_sel = IMM_NONE_R; // R-type: register-register (e.g. ADD, SUB)
7'b0010011, // I-type: immediate ALU ops (e.g. ADDI)
7'b0000011, // I-type: load (e.g. LW)
7'b1100111: imm_type_sel = IMM_I; // I-type: JALR (jump and link register)
7'b0100011: imm_type_sel = IMM_S; // S-type: store (e.g. SW, SH, SB)
7'b1100011: imm_type_sel = IMM_B; // B-type: branch (e.g. BEQ, BNE)
7'b0110111, // U-type: LUI (load upper immediate)
7'b0010111: imm_type_sel = IMM_U; // U-type: AUIPC (add upper immediate to PC)
7'b1101111: imm_type_sel = IMM_J; // J-type: JAL (jump and link)
default: imm_type_sel = IMM_NONE_R; // Default: treat as no immediate (safe fallback)
endcase
end
修改后的代码:
//Tmmediate type decoder
function automatic imm_type_e get_imm_type (input logic [6:0] opcode);
case (opcode)
7'b0110011: return IMM_NONE_R; // R-type: register-register (e.g. ADD, SUB)
7'b0010011, // I-type: immediate ALU ops (e.g. ADDI)
7'b0000011, // I-type: load (e.g. LW)
7'b1100111: return IMM_I; // I-type: JALR (jump and link register)
7'b0100011: return IMM_S; // S-type: store (e.g. SW, SH, SB)
7'b1100011: return IMM_B; // B-type: branch (e.g. BEQ, BNE)
7'b0110111, // U-type: LUI (load upper immediate)
7'b0010111: return IMM_U; // U-type: AUIPC (add upper immediate to PC)
7'b1101111: return IMM_J; // J-type: JAL (jump and link)
default: return IMM_NONE_R; // Default: treat as no immediate (safe fallback)
endcase
endfunction
类型 | 示例 |
---|---|
位宽信号 | logic [31:0] , logic [3:0] |
布尔 | logic //return (opcode == 7’b1100011); // BEQ, BNE, etc. |
整型 | int , integer |
枚举类型 | imm_type_e |
结构体 | reg_fields_t |
数组 | logic [3:0][7:0] |
那么啥是结构体呢?
----结构体知识补充:
typedef struct
就是把一组相关的***数据“打包”***成一个结构体,用起来更方便、更安全,是 SystemVerilog 面向系统设计的关键特性之一。(不是输出一堆线,是把多个变量打包成一个结构体处理)
struct reg_fields_t {
uint5_t rs1, rs2, rd;
};
然后函数返回这个结构体:
function automatic reg_fields_t decode_regs(input logic [31:0] instr);
decode_regs.rs1 = instr[19:15];
decode_regs.rs2 = instr[24:20];
decode_regs.rd = instr[11:7];
endfunction
//调用
assign rs1 = decode_regs(instr).rs1
function [return_type] function_name ([input arguments]);
function_name
是函数本身的名字;
函数返回值直接通过 return
语句返回;
返回值的名字只能是函数名本身(它在函数体内部也当作一个隐式变量使用);
不能给返回值再起一个单独名字。
或也可以写成这样,不用 return
,而是给函数名赋值(函数名是隐式的返回变量):
function automatic logic [31:0] get_imm(input logic [31:0] instr, input imm_type_e imm_type);
case (imm_type)
IMM_I: get_imm = {{20{instr[31]}}, instr[31:20]};
...
endcase
endfunction
如果你希望函数调用更清晰,也可以用中间变量来接收返回值,例如:
logic [31:0] imm;
imm = get_imm(instr, imm_type);
ctrl+F2 批量重命名 或者右键(vscode)
使用返回值为结构体的函数来定义pc_control 的信号(多个变量打包放在一起)
typedef struct packed {
} struct_name;
systemverilog复制编辑function automatic pc_ctrl_t get_pc_ctrl(input logic [6:0] opcode);
pc_ctrl_t ctrl;
ctrl.pc_relbranch = ...;
...
return ctrl; // 推荐做法,明确返回值
endfunction
SystemVerilog 允许这么写:
systemverilog复制编辑function automatic pc_ctrl_t get_pc_ctrl(input logic [6:0] opcode);
get_pc_ctrl.pc_relbranch = (opcode == 7'b1101111 || opcode == 7'b1100011);
get_pc_ctrl.pc_absbranch = (opcode == 7'b1100111);
get_pc_ctrl.pc_incr = ~(get_pc_ctrl.pc_relbranch || get_pc_ctrl.pc_absbranch);
endfunction
get_pc_ctrl
在函数内部相当于一个自动创建的结构体变量,表示返回值;return
。以下代码片段展示了我对pc_control信号的两种写法展示:
//Control signal struct for PC
typedef struct packed {
logic pc_incr,
logic pc_relbranch,
logic pc_absbranch
} pc_control_fields_t;
function automatic pc_control_fields_t get_pc_control (input [6:0] opcode);
// 1.pc_control_fields_t ctrl;
// ctrl.pc_incr = ...;
//return ctrl
//2.
get_pc_control.pc_incr = ~ (get_pc_control.pc_absbranch || get_pc_control.pc_relbranch);
get_pc_control.pc_absbranch = (opcode == 7'b1101111);
get_pc_control.pc_relbranch = (opcode == 7'b1101111 || opcode == 7'b1100011)
endfunction