Small changes

This commit is contained in:
2025-06-18 20:18:42 +08:00
parent 105a5c3b06
commit 949396519e
7 changed files with 320 additions and 261 deletions

View File

@@ -2,8 +2,8 @@
# Webtalk v2018.1 (64-bit) # Webtalk v2018.1 (64-bit)
# SW Build 2188600 on Wed Apr 4 18:40:38 MDT 2018 # SW Build 2188600 on Wed Apr 4 18:40:38 MDT 2018
# IP Build 2185939 on Wed Apr 4 20:55:05 MDT 2018 # IP Build 2185939 on Wed Apr 4 20:55:05 MDT 2018
# Start of session at: Wed Jun 18 18:21:59 2025 # Start of session at: Wed Jun 18 20:13:54 2025
# Process ID: 8708 # Process ID: 21412
# Current directory: D:/Schoolwork/ComputerComposition/LA32R/Hardware/LA32R.sim/sim_1/behav/xsim # Current directory: D:/Schoolwork/ComputerComposition/LA32R/Hardware/LA32R.sim/sim_1/behav/xsim
# Command line: wbtcv.exe -mode batch -source D:/Schoolwork/ComputerComposition/LA32R/Hardware/LA32R.sim/sim_1/behav/xsim/xsim.dir/cpu_tb_snapshot/webtalk/xsim_webtalk.tcl -notrace # Command line: wbtcv.exe -mode batch -source D:/Schoolwork/ComputerComposition/LA32R/Hardware/LA32R.sim/sim_1/behav/xsim/xsim.dir/cpu_tb_snapshot/webtalk/xsim_webtalk.tcl -notrace
# Log file: D:/Schoolwork/ComputerComposition/LA32R/Hardware/LA32R.sim/sim_1/behav/xsim/webtalk.log # Log file: D:/Schoolwork/ComputerComposition/LA32R/Hardware/LA32R.sim/sim_1/behav/xsim/webtalk.log

View File

@@ -2,8 +2,8 @@
# Webtalk v2018.1 (64-bit) # Webtalk v2018.1 (64-bit)
# SW Build 2188600 on Wed Apr 4 18:40:38 MDT 2018 # SW Build 2188600 on Wed Apr 4 18:40:38 MDT 2018
# IP Build 2185939 on Wed Apr 4 20:55:05 MDT 2018 # IP Build 2185939 on Wed Apr 4 20:55:05 MDT 2018
# Start of session at: Wed Jun 18 18:21:55 2025 # Start of session at: Wed Jun 18 20:13:51 2025
# Process ID: 35000 # Process ID: 30320
# Current directory: D:/Schoolwork/ComputerComposition/LA32R/Hardware/LA32R.sim/sim_1/behav/xsim # Current directory: D:/Schoolwork/ComputerComposition/LA32R/Hardware/LA32R.sim/sim_1/behav/xsim
# Command line: wbtcv.exe -mode batch -source D:/Schoolwork/ComputerComposition/LA32R/Hardware/LA32R.sim/sim_1/behav/xsim/xsim.dir/cpu_tb_snapshot/webtalk/xsim_webtalk.tcl -notrace # Command line: wbtcv.exe -mode batch -source D:/Schoolwork/ComputerComposition/LA32R/Hardware/LA32R.sim/sim_1/behav/xsim/xsim.dir/cpu_tb_snapshot/webtalk/xsim_webtalk.tcl -notrace
# Log file: D:/Schoolwork/ComputerComposition/LA32R/Hardware/LA32R.sim/sim_1/behav/xsim/webtalk.log # Log file: D:/Schoolwork/ComputerComposition/LA32R/Hardware/LA32R.sim/sim_1/behav/xsim/webtalk.log

View File

@@ -2,8 +2,8 @@
# xsim v2018.1 (64-bit) # xsim v2018.1 (64-bit)
# SW Build 2188600 on Wed Apr 4 18:40:38 MDT 2018 # SW Build 2188600 on Wed Apr 4 18:40:38 MDT 2018
# IP Build 2185939 on Wed Apr 4 20:55:05 MDT 2018 # IP Build 2185939 on Wed Apr 4 20:55:05 MDT 2018
# Start of session at: Wed Jun 18 18:21:56 2025 # Start of session at: Wed Jun 18 20:13:51 2025
# Process ID: 43956 # Process ID: 20256
# Current directory: D:/Schoolwork/ComputerComposition/LA32R/Hardware/LA32R.sim/sim_1/behav/xsim # Current directory: D:/Schoolwork/ComputerComposition/LA32R/Hardware/LA32R.sim/sim_1/behav/xsim
# Command line: xsim.exe -log ..\..\..\..\..\simulation.log -mode tcl -source {xsim.dir/cpu_tb_snapshot/xsim_script.tcl} # Command line: xsim.exe -log ..\..\..\..\..\simulation.log -mode tcl -source {xsim.dir/cpu_tb_snapshot/xsim_script.tcl}
# Log file: D:/Schoolwork/ComputerComposition/LA32R/Hardware/LA32R.sim/sim_1/behav/xsim/../../../../../simulation.log # Log file: D:/Schoolwork/ComputerComposition/LA32R/Hardware/LA32R.sim/sim_1/behav/xsim/../../../../../simulation.log

View File

@@ -40,7 +40,7 @@ module control_unit (
output reg mem_to_reg, // 数据选择信号选择写入寄存器的数据来源 (0: ALU结果, 1: 存储器数据) output reg mem_to_reg, // 数据选择信号选择写入寄存器的数据来源 (0: ALU结果, 1: 存储器数据)
output reg mem_write_en, // 数据存储器写使能信号 output reg mem_write_en, // 数据存储器写使能信号
output reg alu_src, // 数据选择信号选择ALU的B操作数来源 (0: 寄存器, 1: 立即数) output reg alu_src, // 数据选择信号选择ALU的B操作数来源 (0: 寄存器, 1: 立即数)
output reg src_reg, // 数据选择信号选择寄存器堆的第二个读地址来源 (0: instr[19:15], 1: instr[24:20]) output reg src_reg, // 数据选择信号选择寄存器堆的第二个读地址来源 (0: instr[14:10], 1: instr[4:0])
output reg [2:0] ext_op, // 立即数扩展单元操作控制信号 output reg [2:0] ext_op, // 立即数扩展单元操作控制信号
output reg [3:0] alu_op, // ALU操作类型控制信号 output reg [3:0] alu_op, // ALU操作类型控制信号
output reg alu_asrc, // 数据选择信号选择ALU的A操作数来源 (0: 寄存器, 1: PC / 0 for LUI12I) output reg alu_asrc, // 数据选择信号选择ALU的A操作数来源 (0: 寄存器, 1: PC / 0 for LUI12I)
@@ -78,7 +78,7 @@ module control_unit (
mem_to_reg = 1'b0; // 默认ALU结果写入寄存器 mem_to_reg = 1'b0; // 默认ALU结果写入寄存器
mem_write_en = 1'b0; // 默认不写入存储器 mem_write_en = 1'b0; // 默认不写入存储器
alu_src = 1'b0; // 默认ALU第二操作数来自寄存器 alu_src = 1'b0; // 默认ALU第二操作数来自寄存器
src_reg = 1'b0; // 默认寄存器堆第二读地址来自instr[19:15] (rd) src_reg = 1'b0; // 默认寄存器堆第二读地址来自instr[14:10] (rk)
alu_asrc = 1'b0; // 默认ALU第一操作数来自寄存器 alu_asrc = 1'b0; // 默认ALU第一操作数来自寄存器
ext_op = 3'bxxx; // 默认立即数扩展操作无效 ext_op = 3'bxxx; // 默认立即数扩展操作无效
alu_op = 4'bxxxx; // 默认ALU操作无效 alu_op = 4'bxxxx; // 默认ALU操作无效
@@ -97,7 +97,7 @@ module control_unit (
else if (instr[25:22] == 4'b0000 && func_3r_f2 == 2'b01) begin // 3R类型指令 else if (instr[25:22] == 4'b0000 && func_3r_f2 == 2'b01) begin // 3R类型指令
reg_write_en = 1'b1; // 需要写回寄存器 reg_write_en = 1'b1; // 需要写回寄存器
alu_src = 1'b0; // ALU第二操作数来自寄存器 alu_src = 1'b0; // ALU第二操作数来自寄存器
src_reg = 1'b0; // 寄存器堆第二读地址来自instr[19:15] (源操作数2) src_reg = 1'b0; // 寄存器堆第二读地址来自instr[14:10] (rk, 源操作数2)
case(func_3r_f5) // 根据 instr[19:15] (func_3r_f5) 决定具体ALU操作 case(func_3r_f5) // 根据 instr[19:15] (func_3r_f5) 决定具体ALU操作
5'b00000: alu_op = ALU_ADD; // ADD 5'b00000: alu_op = ALU_ADD; // ADD
5'b00010: alu_op = ALU_SUB; // SUB 5'b00010: alu_op = ALU_SUB; // SUB
@@ -128,21 +128,23 @@ module control_unit (
else if (func_2ri12 == 4'b0110) begin // ST.W 指令 (存储字) else if (func_2ri12 == 4'b0110) begin // ST.W 指令 (存储字)
mem_write_en = 1'b1; // 需要写入存储器 mem_write_en = 1'b1; // 需要写入存储器
alu_src = 1'b1; // ALU第二操作数为立即数 (地址偏移) alu_src = 1'b1; // ALU第二操作数为立即数 (地址偏移)
src_reg = 1'b1; // 寄存器堆第二读地址来自instr[24:20] (源数据寄存器) src_reg = 1'b1; // 寄存器堆第二读地址来自instr[4:0] (rd, 源数据寄存器)
ext_op = EXT_SI12; // 12位有符号立即数扩展 (地址偏移) ext_op = EXT_SI12; // 12位有符号立即数扩展 (地址偏移)
alu_op = ALU_ADD; // ALU计算基地址+偏移 alu_op = ALU_ADD; // ALU计算基地址+偏移
end end
end end
OP_B: ext_op = EXT_SI26; // 无条件分支指令设置26位有符号立即数扩展 OP_B: begin // 无条件分支指令
ext_op = EXT_SI26; // 26位有符号立即数扩展
end
OP_BEQ: begin // 相等则分支指令 OP_BEQ: begin // 相等则分支指令
alu_src = 1'b0; // ALU比较两个寄存器的值 alu_src = 1'b0; // ALU比较两个寄存器的值
src_reg = 1'b1; // 寄存器堆第二读地址来自instr[24:20] src_reg = 1'b1; // 寄存器堆第二读地址来自instr[4:0] (rd)
ext_op = EXT_SI16; // 16位有符号立即数扩展 (分支偏移) ext_op = EXT_SI16; // 16位有符号立即数扩展 (分支偏移)
alu_op = ALU_SUB; // ALU执行减法以判断是否相等 (结果送zero_flag) alu_op = ALU_SUB; // ALU执行减法以判断是否相等 (结果送zero_flag)
end end
OP_BLT: begin // 小于则分支指令 OP_BLT: begin // 小于则分支指令
alu_src = 1'b0; // ALU比较两个寄存器的值 alu_src = 1'b0; // ALU比较两个寄存器的值
src_reg = 1'b1; // 寄存器堆第二读地址来自instr[24:20] src_reg = 1'b1; // 寄存器堆第二读地址来自instr[4:0] (rd)
ext_op = EXT_SI16; // 16位有符号立即数扩展 (分支偏移) ext_op = EXT_SI16; // 16位有符号立即数扩展 (分支偏移)
alu_op = ALU_SUB; // ALU执行减法以判断是否小于 (结果送lt_flag) alu_op = ALU_SUB; // ALU执行减法以判断是否小于 (结果送lt_flag)
end end

View File

@@ -79,7 +79,7 @@ module cpu_top (
imm_extender u_imm_ext (.instr(instr), .ext_op(ext_op), .imm_ext(imm_ext)); imm_extender u_imm_ext (.instr(instr), .ext_op(ext_op), .imm_ext(imm_ext));
// 决定寄存器堆的第二个读取地址 (用于某些指令格式如ST.W其中rt是源数据) // 决定寄存器堆的第二个读取地址 (用于某些指令格式如ST.W其中rt是源数据)
wire [4:0] reg_read_addr2_final = src_reg ? instr[4:0] : instr[14:10]; // src_reg=0: rd=instr[19:15] rt=instr[14:10]; src_reg=1: rd=instr[24:20] rt=instr[4:0] wire [4:0] reg_read_addr2_final = src_reg ? instr[4:0] : instr[14:10]; // src_reg=0: rt=instr[14:10] (rk); src_reg=1: rt=instr[4:0] (rd)
// 寄存器堆 // 寄存器堆
register_file u_reg_file ( register_file u_reg_file (

View File

@@ -32,13 +32,14 @@
这种设计并非偶然它极大地简化了寄存器堆Register File的连接。这意味着 这种设计并非偶然它极大地简化了寄存器堆Register File的连接。这意味着
- 指令的`[9:5]`位可以直接连接到寄存器堆的第一个读地址端口(`ReadRegister1`)。 * 指令的`[9:5]`位可以直接连接到寄存器堆的第一个读地址端口(`ReadRegister1`)。
- 指令的`[14:10]`位可以直接连接到寄存器堆的第二个读地址端口(`ReadRegister2`)。 * 指令的`[4:0]`位可以直接连接到寄存器堆的地址端口(`WriteRegister`)。
- 指令的`[4:0]`位可以直接连接到寄存器堆的写地址端口(`WriteRegister`)。
这样的设计避免了在寄存器堆的地址输入端使用复杂的选择器MUX将选择的复杂性转移到了数据通路的其他部分例如选择哪个数据写回寄存器堆。然而对于 ST、BEQ、BLT 等指令,它们的第二个源操作数来自`rd`字段因此在寄存器堆的第二个读地址端口前仍需一个 MUX 进行选择 然而对于第二个源操作数情况有所不同。对于3R型指令它来自`rk`字段(`instr[14:10]`),但对于`ST.W``BEQ``BLT`等指令,它们的第二个源操作数来自`rd`字段`instr[4:0]`)。因此在寄存器堆的第二个读地址端口前需要一个多路选择器MUX根据控制信号`srcReg`进行选择,以确保读取正确的源寄存器
与此相对, **立即数字段的位置和长度则呈现多样性**`LUI12I.W`使用 20 位立即数`si20``ADDI.W`等使用 12 `si12``B`使用 26 `offs26`,而`BEQ`等使用 16 `offs16`。这预示着 **立即数扩展单元Sign/Immediate Extension Unit** 将是一个不可忽视的关键组合逻辑模块。该模块需要根据指令的`opcode`从指令寄存器IR的不同位置提取出正确长度的立即数字段并根据指令功能说明例如`ADDI.W`需要符号扩展,而`LUI12I.W`是逻辑拼接)进行相应的处理。这个单元是初学者设计中常见的错误来源,必须给予足够重视。 与此相对, **立即数字段的位置和长度则呈现多样性**`LUI12I.W`使用20位立即数`si20``ADDI.W`等使用12位`si12``B`使用26位`offs26`,而`BEQ`等使用16位`offs16`。这预示着**立即数扩展单元Sign/Immediate Extension Unit**将是一个不可忽视的关键组合逻辑模块。该模块需要根据指令的
`opcode`从指令寄存器IR的不同位置提取出正确长度的立即数字段并根据指令功能说明例如`ADDI.W`需要符号扩展,而`LUI12I.W`是逻辑拼接)进行相应的处理。这个单元是初学者设计中常见的错误来源,必须给予足够重视。
### 1.2 设计单周期数据通路 ### 1.2 设计单周期数据通路
@@ -48,33 +49,33 @@
数据通路主要由以下功能模块通过导线和多路选择器连接而成: 数据通路主要由以下功能模块通过导线和多路选择器连接而成:
- **程序计数器 (Program Counter, PC):** 32 位寄存器,存放当前待取指令的地址。 * **程序计数器 (Program Counter, PC):** 32位寄存器存放当前待取指令的地址。
- **指令存储器 (Instruction Memory):** 根据 PC 提供的地址,输出对应的 32 位指令。 * **指令存储器 (Instruction Memory):** 根据PC提供的地址输出对应的32位指令。
- **寄存器堆 (Register File):** 包含 3232 位通用寄存器GR。它具有两个异步读端口和一个同步写端口能够在一个周期内同时读取两个操作数并写入一个结果。 * **寄存器堆 (Register File):** 包含3232位通用寄存器GR。它具有两个异步读端口和一个同步写端口能够在一个周期内同时读取两个操作数并写入一个结果。
- **算术逻辑单元 (Arithmetic Logic Unit, ALU):** 执行算术(加、减)和逻辑(与、或、或非)运算,以及比较操作。 * **算术逻辑单元 (Arithmetic Logic Unit, ALU):** 执行算术(加、减)和逻辑(与、或、或非)运算,以及比较操作。
- **数据存储器 (Data Memory):** 用于`LD.W`(加载)和`ST.W`(存储)指令,与处理器进行数据交换。 * **数据存储器 (Data Memory):** 用于`LD.W`(加载)和`ST.W`(存储)指令,与处理器进行数据交换。
- **立即数扩展单元 (Immediate Extender):** 根据指令类型,对指令中的立即数字段进行提取、拼接和符号扩展。 * **立即数扩展单元 (Immediate Extender):** 根据指令类型,对指令中的立即数字段进行提取、拼接和符号扩展。
- **控制单元 (Control Unit):** 核心的译码部件,根据指令的`opcode`生成控制数据通路中所有选择器和功能单元的控制信号。 * **控制单元 (Control Unit):** 核心的译码部件,根据指令的`opcode`生成控制数据通路中所有选择器和功能单元的控制信号。
- **多路选择器 (Multiplexers, MUX):** 在数据通路的关键节点,根据控制信号选择正确的数据来源。 * **多路选择器 (Multiplexers, MUX):** 在数据通路的关键节点,根据控制信号选择正确的数据来源。
#### 1.2.2 关键指令的数据流追踪 #### 1.2.2 关键指令的数据流追踪
通过追踪几条代表性指令的数据流,可以清晰地展示数据通路各组件如何协同工作。 通过追踪几条代表性指令的数据流,可以清晰地展示数据通路各组件如何协同工作。
1. **R型指令 (以`ADD.W rd, rj, rk`为例):** 1. **R型指令 (以`ADD.W rd, rj, rk`为例):**
- **取指 (IF):** PC 的值送入指令存储器地址端口,指令存储器输出`ADD.W`指令。PC 更新为`PC+4` * **取指 (IF):** PC的值送入指令存储器地址端口指令存储器输出`ADD.W`指令。PC更新为`PC+4`
- **译码/读寄存器 (ID):** 指令被送入控制单元和寄存器堆。控制单元进行译码。寄存器堆根据指令中的`rj``instr[9:5]`)和`rk``instr[14:10]`)地址,读出两个 32 位操作数。 * **译码/读寄存器 (ID):** 指令被送入控制单元和寄存器堆。控制单元进行译码。寄存器堆根据指令中的`rj``instr[9:5]`)和`rk``instr[14:10]`地址读出两个32位操作数。
- **执行 (EX):** 两个操作数被送入 ALU。控制单元生成的`ALUOp`信号指示 ALU 执行加法操作。 * **执行 (EX):** 两个操作数被送入ALU。控制单元生成的`ALUOp`信号指示ALU执行加法操作。
- **写回 (WB):** ALU 的计算结果通过一个选择器(`MemtoReg` MUX此时选择 ALU 结果)送回寄存器堆的写数据端口。控制单元使能`RegWrite`信号,将结果写入由`rd``instr[4:0]`)指定的寄存器。 * **写回 (WB):** ALU的计算结果通过一个选择器`MemtoReg` MUX此时选择ALU结果送回寄存器堆的写数据端口。控制单元使能`RegWrite`信号,将结果写入由`rd``instr[4:0]`)指定的寄存器。
2. **加载指令 (`LD.W rd, rj, si12`):** 2. **加载指令 (`LD.W rd, rj, si12`):**
- **IF & ID:** 与 R 型指令类似,但此时寄存器堆只读取`rj`的值。同时,指令中的`si12`字段被送入立即数扩展单元进行符号扩展。 * **IF & ID:** 与R型指令类似,但此时寄存器堆只读取`rj`的值。同时,指令中的`si12`字段被送入立即数扩展单元进行符号扩展。
- **EX:** `rj`寄存器的值和符号扩展后的`si12`被送入 ALU进行加法运算以计算内存地址`Addr` * **EX:** `rj`寄存器的值和符号扩展后的`si12`被送入ALU进行加法运算以计算内存地址`Addr`
- **访存 (MEM):** ALU 计算出的地址`Addr`被送入数据存储器的地址端口。控制单元使能`MemRead`信号,数据存储器根据地址读出数据。 * **访存 (MEM):** ALU计算出的地址`Addr`被送入数据存储器的地址端口。控制单元使能`MemRead`信号,数据存储器根据地址读出数据。
- **WB:** 从数据存储器读出的数据通过`MemtoReg` MUX此时选择内存数据送回寄存器堆并写入`rd`寄存器。 * **WB:** 从数据存储器读出的数据通过`MemtoReg` MUX此时选择内存数据送回寄存器堆并写入`rd`寄存器。
3. **条件分支指令 (`BEQ rj, rd, offs16`):** 3. **条件分支指令 (`BEQ rj, rd, offs16`):**
- **IF & ID:** 与 R 型指令类似,读取`rj``rd`寄存器的值。同时,`offs16`字段被立即数扩展单元进行符号扩展并左移两位(因为指令地址是字节对齐的,而偏移量通常以字为单位)。 * **IF & ID:** 与R型指令类似,读取`rj``rd`寄存器的值。同时,`offs16`字段被立即数扩展单元进行符号扩展并左移两位(因为指令地址是字节对齐的,而偏移量通常以字为单位)。
- **EX:** `rj``rd`的值被送入 ALU 进行减法操作。如果两者相等ALU `Zero`标志位输出为 1。 * **EX:** `rj``rd`的值被送入ALU进行减法操作。如果两者相等ALU的`Zero`标志位输出为1。
- **PC 更新:** 控制单元生成`Branch`信号。该信号与 ALU `Zero`标志位进行逻辑与运算。如果结果为 1则表示分支条件成立一个专门用于 PC 更新的选择器会选择由`PC+4`和扩展后的偏移量相加得到的跳转目标地址来更新 PC否则PC 正常更新为`PC+4` * **PC更新:** 控制单元生成`Branch`信号。该信号与ALU的`Zero`标志位进行逻辑与运算。如果结果为1则表示分支条件成立一个专门用于PC更新的选择器会选择由`PC+4`和扩展后的偏移量相加得到的跳转目标地址来更新PC否则PC正常更新为`PC+4`
#### 1.2.3 单周期设计的核心权衡:性能与简洁性 #### 1.2.3 单周期设计的核心权衡:性能与简洁性
@@ -96,21 +97,20 @@ $$T_{cycle}=T_{PC_{read}}+T_{IMem_{access}}+T_{RegFile_{read}}
### 2.1 程序计数器(PC)及PC更新模块 ### 2.1 程序计数器(PC)及PC更新模块
- **功能说明:** PC 模块用于存储当前指令的地址并在每个时钟周期更新以获取下一条指令。在顺序执行时PC 递增 4在遇到跳转或分支指令时PC 根据偏移量更新。 * **功能说明:** PC模块用于存储当前指令的地址并在每个时钟周期更新以获取下一条指令。在顺序执行时PC递增4在遇到跳转或分支指令时PC根据偏移量更新。
- **接口信号:** * **接口信号:**
- 输入: `clk` (时钟), `rst` (复位), `PCSrc` (下一 PC 来源控制信号), `branch_addr` (分支目标地址)。 * 输入: `clk` (时钟), `rst` (复位), `PCSrc` (下一PC来源控制信号), `branch_addr` (分支目标地址)。
- 输出: `PC` (当前指令地址)。 * 输出: `PC` (当前指令地址)。
- **内部实现:** PC 本质是一个 32 位寄存器,在时钟上升沿根据`PCSrc`信号选择更新。`PCSrc=0`时,`PC_next = PC + 4``PCSrc=1`时,`PC_next = branch_addr``branch_addr`由一个加法器计算得出,通常为`PC + SignExt(offset << 2)` * **内部实现:** PC本质是一个32位寄存器在时钟上升沿根据`PCSrc`信号选择更新。`PCSrc=0`时,`PC_next = PC + 4``PCSrc=1`时,`PC_next = branch_addr``branch_addr`由一个加法器计算得出,通常为`PC + SignExt(offset << 2)`
### 2.2 指令与数据存储器 ### 2.2 指令与数据存储器
- **指令存储器 (Instruction Memory):** * **指令存储器 (Instruction Memory):**
- **功能:** 只读存储器,根据 PC 提供的地址输出 32 位指令。在 FPGA 上可利用分布式 ROM 或块 RAMBlock RAM实现。 * **功能:** 只读存储器根据PC提供的地址输出32位指令。在FPGA上可利用分布式ROM或块RAMBlock RAM实现。
- **实现:** 在仿真中,可使用 Verilog `reg`数组和`$readmemh`系统任务从外部`.hex`文件加载机器码,以方便测试程序的更换与管理。 * **实现:** 在仿真中可使用Verilog的`reg`数组和`$readmemh`系统任务从外部`.hex`文件加载机器码,以方便测试程序的更换与管理。
- **数据存储器 (Data Memory):** * **数据存储器 (Data Memory):**
- **功能:** 支持字32 读写的随机存取存储器RAM用于`LD.W``ST.W`指令。 * **功能:** 支持字32位读写的随机存取存储器RAM用于`LD.W``ST.W`指令。
- **实现:** 可用同步双端口 RAM 实现。写操作在时钟上升沿根据`MemWrite`信号执行;读操作可设计为异步(组合逻辑读),即根据当前地址立即输出数据。地址索引需注意字节地址到字地址的转换(如 * **实现:** 可用同步双端口RAM实现。写操作在时钟上升沿根据`MemWrite`信号执行;读操作可设计为异步(组合逻辑读),即根据当前地址立即输出数据。地址索引需注意字节地址到字地址的转换(如`addr[31:2]`)。
`addr[31:2]`)。
### 2.3 寄存器堆 (Register File) ### 2.3 寄存器堆 (Register File)
@@ -118,33 +118,31 @@ $$T_{cycle}=T_{PC_{read}}+T_{IMem_{access}}+T_{RegFile_{read}}
#### 2.3.1 寄存器堆接口与行为 #### 2.3.1 寄存器堆接口与行为
- **接口:** * **接口:**
- `input clk, rst;` // 时钟和复位信号 * `input clk, rst;` // 时钟和复位信号
- `input RegWrite;` // 写使能信号 * `input RegWrite;` // 写使能信号
- `input [4:0] ReadRegister1, ReadRegister2;` // 两个 5 位读地址 * `input [4:0] ReadRegister1, ReadRegister2;` // 两个5位读地址
- `input [4:0] WriteRegister;` // 一个 5 位写地址 * `input [4:0] WriteRegister;` // 一个5位写地址
- `input [31:0] WriteData;` // 32 位写数据 * `input [31:0] WriteData;` // 32位写数据
- `output [31:0] ReadData1, ReadData2;` // 两个 32 位读数据 * `output [31:0] ReadData1, ReadData2;` // 两个32位读数据
- **行为:** * **行为:**
- **写操作:** 同步的。当`RegWrite`为高电平,在时钟的上升沿,`WriteData`被写入`WriteRegister`指定的寄存器中。 * **写操作:** 同步的。当`RegWrite`为高电平,在时钟的上升沿,`WriteData`被写入`WriteRegister`指定的寄存器中。
- **读操作:** 异步(组合逻辑)的。`ReadData1``ReadData2`的输出实时反映由`ReadRegister1``ReadRegister2`地址指定的寄存器的内容。 * **读操作:** 异步(组合逻辑)的。`ReadData1``ReadData2`的输出实时反映由`ReadRegister1``ReadRegister2`地址指定的寄存器的内容。
#### 2.3.2 零号寄存器 (R0) 的防御性设计 #### 2.3.2 零号寄存器 (R0) 的防御性设计
在许多RISC架构中零号寄存器R0被硬性规定为常数0。它不可被写入读取时永远返回0。实现这一特性需要“防御性”的设计策略。 在许多RISC架构中零号寄存器R0被硬性规定为常数0。它不可被写入读取时永远返回0。实现这一特性需要“防御性”的设计策略。
一个简单的实现是在写操作时增加一个判断条件: 一个简单的实现是在写操作时增加一个判断条件:
`if (RegWrite && (WriteRegister!= 5'd0)) registers <= WriteData;` `if (RegWrite && (WriteRegister!= 5'd0)) registers <= WriteData;`
这可以阻止对R0的写入。然而这种方法并非万无一失。例如如果系统复位不彻底或者存在某些设计缺陷
这可以阻止对 R0 的写入 11。然而这种方法并非万无一失。例如如果系统复位不彻底或者存在某些设计缺陷
`registers`的物理存储单元可能并非为0。此时如果读逻辑仅仅是`assign ReadData1 = registers;`,那么当`ReadRegister1`为0时可能会读出一个非零值导致灾难性的错误。 `registers`的物理存储单元可能并非为0。此时如果读逻辑仅仅是`assign ReadData1 = registers;`,那么当`ReadRegister1`为0时可能会读出一个非零值导致灾难性的错误。
一个更**稳健、更具防御性**的设计方法是**在读写两端同时施加约束**。 一个更**稳健、更具防御性**的设计方法是**在读写两端同时施加约束**。
1. **写端防护:** 如上所述阻止对地址0的任何写操作。 1. **写端防护:** 如上所述阻止对地址0的任何写操作。
2. 读端强制: 在读端口的输出逻辑中,明确地处理地址 0 的情况。 2. **读端强制:** 在读端口的输出逻辑中,明确地处理地址0的情况。
`assign ReadData1 = (ReadRegister1 == 5'd0)? 32'b0 : registers;` `assign ReadData1 = (ReadRegister1 == 5'd0)? 32'b0 : registers;`
`assign ReadData2 = (ReadRegister2 == 5'd0)? 32'b0 : registers;` `assign ReadData2 = (ReadRegister2 == 5'd0)? 32'b0 : registers;`
@@ -201,23 +199,27 @@ ALU 是 CPU 的计算核心,负责执行指令指定的算术和逻辑运算
#### 2.4.1 ALU模块接口与功能 #### 2.4.1 ALU模块接口与功能
- **接口:** * **接口:**
- `input [31:0] A, B;` // 两个 32 位操作数输入
- `input [3:0] ALUOp;` // 4 位操作控制信号 * `input [31:0] A, B;` // 两个32位操作数输入
- `output reg [31:0] Result;` // 32 位运算结果输出 * `input [3:0] ALUOp;` // 4位操作控制信号
- `output Zero;` // 零标志位输出,用于分支指令 * `output reg [31:0] Result;` // 32位运算结果输出
- **功能:** 根据`ALUOp`信号,执行`ADD.W`, `SUB.W`, `SLT`, `SLTU`, `NOR`, `AND`, `OR`等操作。此外ALU 也用于计算内存地址和分支比较。 * `output Zero;` // 零标志位输出,用于分支指令
- **实现:** 使用`always @(*)`块和`case`语句是实现 ALU 组合逻辑最清晰、最直接的方式。 * `output Negative;` // 负标志位用于BLT指令
* **功能:** 根据`ALUOp`信号,执行`ADD.W`, `SUB.W`, `SLT`, `SLTU`, `NOR`, `AND`, `OR`等操作。此外ALU也用于计算内存地址和分支比较。
* **实现:** 使用`always @(*)`块和`case`语句是实现ALU组合逻辑最清晰、最直接的方式。
`Zero`标志位可以通过比较`Result`是否为全0来生成`assign Zero = (Result == 32'h00000000);` `Zero`标志位可以通过比较`Result`是否为全0来生成`assign Zero = (Result == 32'h00000000);`
`Negative`标志位可由结果的最高位获得:`assign negative = result[1];`
#### 2.4.2 `SLT`与`SLTU`的稳健实现 #### 2.4.2 `SLT`与`SLTU`的稳健实现
ISA 要求 ALU 同时支持有符号比较(`SLT`)和无符号比较(`SLTU`^3^。这为 ALU 的设计带来了一个有趣的挑战。 ISA要求ALU同时支持有符号比较`SLT`)和无符号比较(`SLTU`^^。这为ALU的设计带来了一个有趣的挑战。
一种直接的方法是利用Verilog的语言特性。在比较时将输入操作数强制转换为`signed`类型: 一种直接的方法是利用Verilog的语言特性。在比较时将输入操作数强制转换为`signed`类型:
`Result = ($signed(A) < $signed(B))? 32'd1 : 32'd0;` `Result = ($signed(A) < $signed(B))? 32'd1 : 32'd0;`
对于无符号比较则使用默认的无符号行为。这种方法虽然简单但高度依赖于Verilog的类型系统可能会在不同仿真或综合工具间存在细微差异且未能体现底层硬件的实现原理。 对于无符号比较则使用默认的无符号行为。这种方法虽然简单但高度依赖于Verilog的类型系统可能会在不同仿真或综合工具间存在细微差异且未能体现底层硬件的实现原理。
一种更为稳健和体现硬件本质的方法,是基于减法运算的标志位来实现比较。 一种更为稳健和体现硬件本质的方法,是基于减法运算的标志位来实现比较。
@@ -269,20 +271,21 @@ endmodule
### 2.5 立即数扩展单元 (Immediate Extender) ### 2.5 立即数扩展单元 (Immediate Extender)
- **功能说明:** 根据指令类型,将指令中的立即数字段(如 12 位、16 位、20 位、26 位)转换为 32 位值,供 ALUPC 更新模块使用。处理方式包括符号扩展、零扩展和拼接。 * **功能说明:** 根据指令类型将指令中的立即数字段如12位、16位、20位、26位转换为32位值供ALUPC更新模块使用。处理方式包括符号扩展、零扩展和拼接。
- **接口信号:** * **接口信号:**
- 输入: `instr` (32 位指令), `ImmType` (扩展类型控制信号)。
-: `imm_ext` (32 位扩展后立即数)。 *: `instr` (32位指令), `ImmType` (扩展类型控制信号)。
- **内部实现:** 这是一个纯组合逻辑模块。使用`case`语句根据`ImmType`选择对应的处理逻辑。例如,对 12 位有符号立即数`si12`,可使用位拼接实现符号扩展:`imm_ext = {{20{si12}}, si12}`。对于分支偏移量,还需在扩展后左移两位(即末尾补两个 0 * 输出: `imm_ext` (32位扩展后立即数)
* **内部实现:** 这是一个纯组合逻辑模块。使用`case`语句根据`ImmType`选择对应的处理逻辑。例如对12位有符号立即数`si12`,可使用位拼接实现符号扩展:`imm_ext = {{20{si12}}, si12}`。对于分支偏移量还需在扩展后左移两位即末尾补两个0
### 2.6 多路选择器 (Multiplexers) ### 2.6 多路选择器 (Multiplexers)
多路选择器MUX在数据通路中扮演着关键的“交通枢纽”角色根据控制信号选择正确的数据来源。 多路选择器MUX在数据通路中扮演着关键的“交通枢纽”角色根据控制信号选择正确的数据来源。
- **ALU 第二操作数选择 (ALUSrc MUX):** 在 ALU 的 B 输入端,根据`ALUSrc`信号选择数据来源:是来自寄存器堆的第二个读端口,还是来自立即数扩展单元的输出。 * **ALU第二操作数选择 (ALUSrc MUX):** 在ALU的B输入端,根据`ALUSrc`信号选择数据来源:是来自寄存器堆的第二个读端口,还是来自立即数扩展单元的输出。
- **写回数据选择 (MemtoReg MUX):** 决定写入寄存器堆的数据来源。根据`MemtoReg`信号选择:是来自 ALU 的计算结果,还是来自数据存储器的读出值。 * **写回数据选择 (MemtoReg MUX):** 决定写入寄存器堆的数据来源。根据`MemtoReg`信号选择是来自ALU的计算结果还是来自数据存储器的读出值。
- **PC 更新选择 (PCSrc MUX):** 决定下一个 PC 的值。根据`PCSrc`信号选择:是顺序执行的`PC+4`,还是分支/跳转的目标地址。 * **PC更新选择 (PCSrc MUX):** 决定下一个PC的值。根据`PCSrc`信号选择:是顺序执行的`PC+4`,还是分支/跳转的目标地址。
- **寄存器堆第二读地址选择 (srcReg MUX):** 对于`ST.W`和条件分支指令,其第二个源操作数寄存器地址位于`rd`字段(`instr[4:0]`),而非`rk`字段。因此需要一个 MUX 根据`srcReg`信号,为寄存器堆的第二个读地址端口选择`instr[14:10]`rk`instr[4:0]`rd * **寄存器堆第二读地址选择 (srcReg MUX):** 对于`ST.W`和条件分支指令,其第二个源操作数寄存器地址位于`rd`字段(`instr[4:0]`),而非`rk`字段。因此需要一个MUX根据`srcReg`信号,为寄存器堆的第二个读地址端口选择`instr[14:10]`rk`instr[4:0]`rd
### 2.7 顶层CPU模块集成 ### 2.7 顶层CPU模块集成
@@ -302,12 +305,11 @@ endmodule
### 3.1 1RI20型指令 (LUI12I.W) ### 3.1 1RI20型指令 (LUI12I.W)
- **功能:** `GR[rd] ← si20 | | 12b0`,将 20 位立即数加载到目标寄存器高位,低 12 位补零。 * **功能:** `GR[rd] ← si20 | | 12b0`将20位立即数加载到目标寄存器高位低12位补零。
- **数据通路:** 立即数扩展单元提取`si20`并拼接 120生成 32 位常量。该常量通过`ALUSrc` MUX 送入 ALU另一输入可为 0ALU 执行加法(等效于传递)后,结果通过`MemtoReg` MUX 写回`rd`寄存器。 * **数据通路:** 立即数扩展单元提取`si20`并拼接120生成32位常量。该常量通过`ALUSrc` MUX送入ALU另一输入可为0ALU执行加法等效于传递结果通过`MemtoReg` MUX写回`rd`寄存器。
- 控制信号: * **控制信号:**
| 控制信号 | LU12I.W (1RI20) | | 控制信号 | LU12I.W (1RI20) |
| -------- | -------------------------- | |---|---|
| RegWrite | 1 (写使能) | | RegWrite | 1 (写使能) |
| ALUSrc | 1 (ALU第二操作数选立即数) | | ALUSrc | 1 (ALU第二操作数选立即数) |
| MemtoReg | 0 (ALU结果写回) | | MemtoReg | 0 (ALU结果写回) |
@@ -317,12 +319,11 @@ endmodule
### 3.2 3R型指令 (ADD.W, SUB.W, SLT, SLTU, NOR, AND, OR) ### 3.2 3R型指令 (ADD.W, SUB.W, SLT, SLTU, NOR, AND, OR)
- **功能:** `GR[rd] ← GR[rj] op GR[rk]`,执行两个寄存器间的算术或逻辑运算。 * **功能:** `GR[rd] ← GR[rj] op GR[rk]`,执行两个寄存器间的算术或逻辑运算。
- **数据通路:** 寄存器堆读出`rj``rk`的值,送入 ALU。ALU 根据`AluCtrl`信号执行相应运算,结果通过`MemtoReg` MUX 写回`rd`寄存器。 * **数据通路:** 寄存器堆读出`rj``rk`的值送入ALU。ALU根据`AluCtrl`信号执行相应运算,结果通过`MemtoReg` MUX写回`rd`寄存器。
- 控制信号: * **控制信号:**
| 控制信号 | 3R类型指令 | | 控制信号 | 3R类型指令 |
| -------- | ---------------------------- | |---|---|
| RegWrite | 1 | | RegWrite | 1 |
| ALUSrc | 0 (ALU第二操作数来自寄存器) | | ALUSrc | 0 (ALU第二操作数来自寄存器) |
| MemtoReg | 0 | | MemtoReg | 0 |
@@ -336,16 +337,19 @@ endmodule
这类指令格式相同,但数据通路和控制信号有显著差异。 这类指令格式相同,但数据通路和控制信号有显著差异。
- **ADDI.W rd, rj, si12:** `GR[rd] ← GR[rj] + SignExtend(si12)`。数据通路类似 3R 型,但 ALU 的第二操作数来自符号扩展后的 * **ADDI.W rd, rj, si12:** `GR[rd] ← GR[rj] + SignExtend(si12)`。数据通路类似3R型但ALU的第二操作数来自符号扩展后的
`si12` `si12`
- **LD.W rd, rj, si12:** `GR[rd] ← M + SignExtend(si12)]`。ALU 计算地址,数据存储器根据该地址读出数据,该数据通过 * **LD.W rd, rj, si12:** `GR[rd] ← M + SignExtend(si12)]`。ALU计算地址数据存储器根据该地址读出数据该数据通过
`MemtoReg` MUX写回`rd`寄存器。 `MemtoReg` MUX写回`rd`寄存器。
- **ST.W rd, rj, si12:** `M + SignExtend(si12)] ← GR[rd]`。ALU 计算地址,寄存器堆读出 * **ST.W rd, rj, si12:** `M + SignExtend(si12)] ← GR[rd]`。ALU计算地址寄存器堆读出
`rd`的值作为待写数据,写入数据存储器。此指令不写回寄存器。 `rd`的值作为待写数据,写入数据存储器。此指令不写回寄存器。
- 控制信号: * **控制信号:**
| 控制信号 | ADDI.W | LD.W | ST.W | | 控制信号 | ADDI.W | LD.W | ST.W |
| -------- | ------ | ---- | ---- | |---|---|---|---|
| RegWrite | 1 | 1 | 0 | | RegWrite | 1 | 1 | 0 |
| ALUSrc | 1 | 1 | 1 | | ALUSrc | 1 | 1 | 1 |
| MemtoReg | 0 | 1 | X | | MemtoReg | 0 | 1 | X |
@@ -357,18 +361,17 @@ endmodule
### 3.4 I26型指令 (B) ### 3.4 I26型指令 (B)
- **功能:** `PC ← PC + SignExtend(offs26 | | 2b0)`,无条件跳转。 * **功能:** `PC ← PC + SignExtend(offs26 | | 2b0)`,无条件跳转。
- **数据通路:** 立即数扩展单元处理`offs26`得到跳转偏移,与 PC 相加后得到目标地址。该地址通过`PCSrc` MUX 更新到 PC 寄存器。数据通路的主要部分寄存器堆、ALU、数据存储器均不参与有效运算。 * **数据通路:** 立即数扩展单元处理`offs26`得到跳转偏移与PC相加后得到目标地址。该地址通过`PCSrc` MUX更新到PC寄存器。数据通路的主要部分寄存器堆、ALU、数据存储器均不参与有效运算。
- **控制信号:** `PCSrc`置为 1强制选择跳转地址。其他数据通路控制信号`RegWrite`, `MemWrite`等)均为 0 或无关X * **控制信号:** `PCSrc`置为1强制选择跳转地址。其他数据通路控制信号`RegWrite`, `MemWrite`等)均为0或无关X
### 3.5 2RI16型指令 (BEQ, BLT) ### 3.5 2RI16型指令 (BEQ, BLT)
- **功能:** `if (condition) PC ← PC + SignExtend(offs16 | | 2b0)`,条件分支。 * **功能:** `if (condition) PC ← PC + SignExtend(offs16 | | 2b0)`,条件分支。
- **数据通路:** 寄存器堆读出`rj``rd`的值送入 ALU 进行减法比较。ALU `Zero`(用于 BEQ`Negative`(用于 BLT标志位与控制单元生成的`Branch`信号共同决定`PCSrc`的取值。若条件成立,`PCSrc` 1PC 更新为跳转目标地址;否则为 0PC 更新为`PC+4` * **数据通路:** 寄存器堆读出`rj``rd`的值送入ALU进行减法比较。ALU的`Zero`用于BEQ`Negative`用于BLT标志位与控制单元生成的`Branch`信号共同决定`PCSrc`的取值。若条件成立,`PCSrc`为1PC更新为跳转目标地址否则为0PC更新为`PC+4`
- 控制信号: * **控制信号:**
| 控制信号 | BEQ / BLT | | 控制信号 | BEQ / BLT |
| -------- | ------------------------ | |---|---|
| RegWrite | 0 | | RegWrite | 0 |
| ALUSrc | 0 | | ALUSrc | 0 |
| MemRead | 0 | | MemRead | 0 |
@@ -392,35 +395,65 @@ endmodule
为了使设计更加模块化,控制逻辑通常被分为两部分: 为了使设计更加模块化,控制逻辑通常被分为两部分:
1. **主控制单元 (Main Control Unit):** 1. **主控制单元 (Main Control Unit):**
- **输入:** 指令的`opcode`字段(`instr[31:26]`)及其他功能位。 * **输入:** 指令的`opcode`字段(`instr[31:26]`)及其他功能位。
- **输出:** 数据通路中除 ALU 操作外的所有控制信号,如`RegWrite``ALUSrc``MemRead``MemWrite``MemtoReg``Branch`等。此外,它还会生成一个 2 位的`ALUOp`信号,用于粗略地指示 ALU 需要执行的操作类型。 * **输出:** 数据通路中除ALU操作外的所有控制信号`RegWrite``ALUSrc``MemRead``MemWrite``MemtoReg``Branch`等。此外,它还会生成一个2位的`ALUOp`信号用于粗略地指示ALU需要执行的操作类型。
- **实现:** 使用一个大的`case`语句或硬布线逻辑,根据`opcode`为每种指令类型生成一组固定的控制信号。 * **实现:** 使用一个大的`case`语句或硬布线逻辑,根据`opcode`为每种指令类型生成一组固定的控制信号。
2. **ALU控制单元 (ALU Control Unit):** 2. **ALU控制单元 (ALU Control Unit):**
- **输入:** 主控制单元生成的`ALUOp`信号,以及指令的`func`字段(对于 R 型指令)。 * **输入:** 主控制单元生成的`ALUOp`信号,以及指令的`func`字段(对于R型指令)。
- **输出:** 最终驱动 ALU 的 4 `ALUOperation`信号。 * **输出:** 最终驱动ALU的4`ALUOperation`信号。
- **实现:** 这种两级控制结构使得主控制单元不必关心 R 型指令的具体运算,只需告诉 ALU 控制单元“这是一条 R 型指令”。ALU 控制单元再根据`func`字段来确定具体的运算。这遵循了“关注点分离”的设计原则。 * **实现:** 这种两级控制结构使得主控制单元不必关心R型指令的具体运算只需告诉ALU控制单元“这是一条R型指令”。ALU控制单元再根据`func`字段来确定具体的运算。这遵循了“关注点分离”的设计原则。
#### 4.1.2 控制信号真值表:设计的核心文档 #### 4.1.2 控制信号真值表与硬布线逻辑实现
在编写控制单元的 Verilog 代码之前,强烈建议先创建一个 **控制信号真值表** 。这张表是连接 ISA 规范和硬件实现的桥梁,其重要性不可低估。第三部分的指令分析表即是此真值表的详细体现。 在编写控制单元的Verilog代码之前创建**控制信号真值表**是连接ISA规范和硬件实现的桥梁。第三部分的指令分析表即是此真值表的详细体现。基于此表,可采用组合逻辑(硬布线)高效实现控制单元。
- **价值:** 可以定义代表指令类型的中间变量,然后直接用逻辑表达式生成控制信号:
1. **强制性分析:** 迫使设计者在编码前就理清所有逻辑细节。
2. **设计规范:** 作为控制单元最精确、无歧义的设计规范。 ```verilog
3. **调试黄金标准:** 在后续仿真调试阶段,可将实际控制信号与表中期望值比对,快速定位问题。 // 根据指令编码定义指令类型 (注:位字段需根据最终编码确认)
wire opR = (opcode == 6'b000000 &&...); // 3R运算
wire opADDI = (opcode == 6'b000000 &&...); // ADDI.W
wire opLD = (opcode == 6'b001010 &&...); // LD.W
wire opST = (opcode == 6'b001010 &&...); // ST.W
wire opLUI = (opcode == 6'b000101 &&...); // LUI12I.W
wire opB = (opcode == 6'b010100); // B
wire opBEQ = (opcode == 6'b010110); // BEQ
wire opBLT = (opcode == 6'b011000); // BLT
// 根据指令类型生成主控制信号
assign RegWrite = opR |
| opADDI |
| opLD |
| opLUI;
assign MemWrite = opST;
assign MemRead = opLD;
assign ALUSrc = opADDI |
| opLD |
| opST |
| opLUI;
assign MemToReg = opLD;
assign srcReg = opST |
| opBEQ |
| opBLT; // ST/分支用rd字段作第二源
```
对于`AluCtrl`,可进一步用`case`语句根据`func`字段细分。这种方法逻辑清晰,易于实现和调试。
### 4.2 验证策略与测试程序开发 ### 4.2 验证策略与测试程序开发
验证是硬件设计中耗时最长、也最重要的环节。一个结构化的验证策略和精心设计的测试程序是必不可少的。 验证是硬件设计中耗时最长、也最重要的环节。一个结构化的验证策略和精心设计的测试程序是必不可少的。
- **策略:** 遵循“增量测试”和“覆盖关键路径”的原则。测试程序不应是随机指令的堆砌,而应是一个逻辑清晰、能逐步验证 CPU 功能的序列。 * **策略:** 遵循“增量测试”和“覆盖关键路径”的原则。测试程序不应是随机指令的堆砌而应是一个逻辑清晰、能逐步验证CPU功能的序列。
- **测试程序序列示例:** * **测试程序序列示例:**
1. **寄存器加载:** 首先使用`LUI12I.W``ADDI.W`向多个寄存器加载已知初始值。 1. **寄存器加载:** 首先使用`LUI12I.W``ADDI.W`向多个寄存器如R1, R2, R3加载已知初始值。这是后续测试的基础。
2. **算术/逻辑运算:** 依次执行所有 3R 型指令,操作数来自已加载的寄存器结果存入新寄存器。 2. **算术/逻辑运算:** 依次执行所有3R型指令`ADD.W`, `SUB.W`, `AND`, `OR`, `NOR`, `SLT`, `SLTU`,操作数来自已加载的寄存器。将结果存入新寄存器如R4, R5等
3. **内存写/读验证:** 使用`ST.W`已知值存入内存,再用`LD.W`读回并比较 3. **内存写/读验证:** 使用`ST.W`某个寄存器如R4中的已知值存入数据存储器的某个地址。然后立即使用`LD.W`从同一地址将数据读回到一个不同的寄存器如R6。最后比较R4和R6的值是否相等
4. **分支逻辑测试:** 分别测试条件成立(跳转)和不成立(不跳转)两种情况。 4. **分支逻辑测试:**
5. **无条件跳转:** 使用`B`指令跳转到特定位置 * **分支不跳转:** 设计一条`BEQ`指令其比较的两个寄存器值不相等。验证PC是否正常更新为`PC+4`
6. **测试结束标志:** 程序最后应有明确的结束标志,如无限循环或向特定寄存器/内存地址写入“魔法数字”,供测试平台判断 * **分支跳转:** 设计一条`BEQ`指令其比较的两个寄存器值相等。验证PC是否正确跳转到目标地址
*`BLT`进行类似的正反测试,并特别注意使用正负数作为测试用例。
5. **无条件跳转:** 使用`B`指令跳转到一个特定的循环或结束位置。
6. **测试结束标志:** 程序最后应有一个明确的结束标志。例如,可以是一个无限循环(`B.`),或者向某个约定的寄存器或内存地址写入一个特殊的“魔法数字”(如`0xCAFEBABE`),以供测试平台判断程序是否已成功执行完毕。
### 4.3 实现自检查测试平台 (Self-Checking Testbench) ### 4.3 实现自检查测试平台 (Self-Checking Testbench)
@@ -430,22 +463,26 @@ endmodule
一个强大的自检查测试平台应包含以下组件: 一个强大的自检查测试平台应包含以下组件:
- **DUT 实例化:** `cpu uut (...)` * **DUT实例化:** `cpu uut (...)`
- **时钟与复位生成器:** `always #5 clk = ~clk;` 和一个`initial`块来控制复位信号。 * **时钟与复位生成器:** `always #5 clk = ~clk;` 和一个`initial`块来控制复位信号。
- **指令存储器模型:** 一个`reg`数组,使用`$readmemh("test_program.hex", instruction_memory);`从外部文件加载测试程序的机器码。 * **指令存储器模型:** 一个`reg`数组,使用`$readmemh("test_program.hex", instruction_memory);`从外部文件加载测试程序的机器码。
- **数据存储器模型:** 另一个`reg`数组,用于模拟数据存储器。 * **数据存储器模型:** 另一个`reg`数组,用于模拟数据存储器。
- **结果验证逻辑:** 这是自检查的核心。 * **结果验证逻辑:** 这是自检查的核心。
#### 4.3.2 基于“黄金参考模型”的自动化验证 #### 4.3.2 基于“黄金参考模型”的自动化验证
自检查的关键在于“与谁比较?”。最强大的方法之一是使用一个“黄金参考模型”。对于本课程设计,这个模型可以简化为对最终寄存器状态的期望值。 自检查的关键在于“与谁比较?”。最强大的方法之一是使用一个“黄金参考模型”。对于本课程设计,这个模型可以简化为对最终寄存器状态的期望值。
1. **生成黄金参考:** 手动或通过脚本模拟执行测试程序,计算出程序结束时所有寄存器的 **最终期望值** 1. **生成黄金参考:** 在编写测试程序后手动或通过一个简单的高级语言脚本如Python模拟执行程序,计算出程序执行完毕时所有32个通用寄存器的 **最终期望值**
2. **存储黄金参考:** 将期望值存储在测试平台的`golden_reg_file`数组中 2. **存储黄金参考:**这些期望值存储在测试平台的一个“黄金”寄存器数组中:`reg [31:0] golden_reg_file[0:31];`。这些值可以硬编码在测试平台中,或从另一个文件中读入
3. **执行与比对:** 仿真结束时,测试平台用`for`循环逐一比较 DUT 内部寄存器值和`golden_reg_file`中的值。 3. **执行与比对:**
4. **报告结果:** 如果匹配,打印`"TEST PASSED"`;否则,打印详细错误信息和`"TEST FAILED"` * 测试平台启动仿真让CPU运行足够长的时间以确保测试程序执行完毕
* 在仿真结束时(例如,通过`$finish`前的`final`块),测试平台执行一个`for`循环逐一比较DUT内部的寄存器堆需要通过层次化引用访问`uut.reg_file_inst.registers[i]`)和`golden_reg_file`中的值。
* **报告结果:**
* 如果所有寄存器的值都匹配,测试平台打印出`"****** TEST PASSED ******"`
* 一旦发现不匹配,立即打印出详细的错误信息,如`"ERROR: Register R%d mismatch! Expected: 0x%h, Got: 0x%h"`, `i, golden_reg_file[i], uut.reg_file_inst.registers[i]`,并报告`"****** TEST FAILED ******"`
这种方法将验证过程从主观的波形观察,转变为客观、自动化的**PASS/FAIL**判断,是工业界验证方法学的入门实践 这种方法将验证过程从主观的波形观察,转变为客观、自动化的**PASS/FAIL**判断。这不仅极大地提高了验证效率和可靠性,也是工业界验证方法学的入门实践,能显著提升课程设计的专业水准
--- ---
@@ -457,38 +494,48 @@ endmodule
在Vivado中运行仿真后波形查看器是主要的调试工具。为了高效地追踪问题应重点观察以下关键信号 在Vivado中运行仿真后波形查看器是主要的调试工具。为了高效地追踪问题应重点观察以下关键信号
- **时钟与复位:** `clk`, `rst` * **时钟与复位:** `clk`, `rst` - 确保它们行为正常。
- **PC 与指令:** `pc`, `instruction` * **PC与指令:** `pc`, `instruction` - 验证取指是否正确PC是否按预期更新。
- **寄存器写操作:** `reg_write_en`, `write_addr`, `write_data` * **寄存器写操作:** `reg_write_en`, `write_addr`, `write_data` - 检查写回阶段是否在正确的时机将正确的数据写入了正确的寄存器。
- **ALU 操作:** `alu_input_a`, `alu_input_b`, `alu_op`, `alu_result` * **ALU操作:** `alu_input_a`, `alu_input_b`, `alu_op`, `alu_result` - 验证ALU的操作数和操作类型是否正确结果是否符合预期。
- **内存访问:** `mem_address`, `mem_write_data`, `mem_read_data`, `mem_write_en`, `mem_read_en` * **内存访问:** `mem_address`, `mem_write_data`, `mem_read_data`, `mem_write_en`, `mem_read_en` - 调试`LD.W``ST.W`指令的关键。
通过将这些信号组织在一起,可以清晰地看到在每个时钟周期,数据如何在数据通路中流动,从而验证每条指令的执行过程。
### 5.2 主动调试与常见陷阱规避 ### 5.2 主动调试与常见陷阱规避
对于初学者而言Verilog的某些特性可能会导致与直觉相悖的结果。了解并规避这些常见陷阱是成功的关键。 对于初学者而言Verilog的某些特性可能会导致与直觉相悖的结果。了解并规避这些常见陷阱是成功的关键。
1. **思维模型错位 (Mental Model Mismatch):** 最根本的错误是把Verilog当作一种顺序执行的编程语言。必须牢记 1. **思维模型错位 (Mental Model Mismatch):** 最根本的错误是把Verilog当作一种顺序执行的编程语言。必须牢记
**Verilog 是硬件描述语言** 。正确的思维方式是:“ **先画出电路图,再用 Verilog 描述它** ”。
**Verilog是硬件描述语言** 。正确的思维方式是:“ **先画出电路图再用Verilog描述它** ”。你写的每一行代码都应对应一个具体的硬件结构(寄存器、选择器、逻辑门等)。
2. **阻塞赋值 (`=`) vs. 非阻塞赋值 (`<=`):** 这是Verilog中最常见也最致命的错误之一。 2. **阻塞赋值 (`=`) vs. 非阻塞赋值 (`<=`):** 这是Verilog中最常见也最致命的错误之一。
- **铁律:**
-**时序逻辑** `always @(posedge clk)`)中,**永远**使用 **非阻塞赋值 (`<=`)** * **铁律:**
-**组合逻辑** `always @(*)`)中,**永远**使用 **阻塞赋值 (`=`)** ***时序逻辑** `always @(posedge clk)`)中,**永远**使用 **阻塞赋值 (`<=`)**
3. **意外推断出锁存器 (Inferred Latches):** 在组合逻辑块中,如果某些条件下输出没有被赋值(如`if`缺少`else``case`缺少`default`),综合器会推断出锁存器 ***组合逻辑** `always @(*)`)中,**永远**使用 **阻塞赋值 (`=`)**
- **解决方法:** 确保在组合逻辑的`always`块中,所有输出信号在所有代码路径中都被赋值,或在块开头赋默认值 * **原因:** 违反此规则会导致仿真结果与综合后的硬件行为不匹配的风险,即所谓的“仿真-综合不匹配”这是非常难以调试的。非阻塞赋值模拟了时序电路中所有D触发器在时钟沿同时更新的并行行为
4. **不完整的敏感列表 (Incomplete Sensitivity List):** 在 Verilog-1995 标准中,遗漏敏感列表中的输入会推断出锁存器。 3. **意外推断出锁存器 (Inferred Latches):** 在组合逻辑块中,如果某些条件下输出没有被赋值,综合器会认为你需要“记住”上一个值,从而推断出一个锁存器。锁存器会引入意外的时序路径,是设计中的大忌。
- **解决方法:** **始终使用`always @(*)`** Verilog-2001 标准),它能自动推断敏感列表。
* **两大元凶:**
* `if`语句没有`else`分支。
* `case`语句没有覆盖所有可能情况,且没有`default`分支。
* **解决方法:** 确保在任何组合逻辑的`always`块中,所有输出信号在所有可能的代码路径中都被赋值。一个最佳实践是在`always @(*)`块的开头为所有输出赋一个默认值。
4. **不完整的敏感列表 (Incomplete Sensitivity List):** 在Verilog-1995标准中组合逻辑的`always`块需要手动列出所有输入信号。如果遗漏了某个输入,当该输入变化时,逻辑块不会重新计算,同样会推断出锁存器。
* **解决方法:** **始终使用`always @(*)`** Verilog-2001标准。它能自动将块内所有读取的信号都包含进敏感列表从根本上杜绝此类错误。
### 5.3 进阶调试Vivado ILA硬件在线调试 ### 5.3 进阶调试Vivado ILA硬件在线调试
仿真环境是理想的而实际硬件中可能会出现仿真无法复现的问题。Vivado**集成逻辑分析仪 (Integrated Logic Analyzer, ILA)** 核是一个可以植入到设计中的“片上示波器”,允许你在 FPGA 运行时实时捕获内部信号的状态。 仿真环境是理想的,而实际硬件中可能会出现仿真无法复现的问题(如时序问题)。Vivado**集成逻辑分析仪 (Integrated Logic Analyzer, ILA)**核是一个可以植入到设计中的“片上示波器”允许你在FPGA运行时实时捕获内部信号的状态。
- **基本流程:** * **基本流程:**
1. **标记信号:** 在综合后的网表视图中右键点击关心的内部信号选择“Mark Debug”。 1. **标记信号:** 在综合后的网表视图中,右键点击关心的内部信号(如`pc`, `instruction`等)选择“Mark Debug”。
2. **设置 ILA:** 运行“Set Up Debug”向导Vivado 会自动创建 ILA 核并连接探针 2. **设置ILA:** 运行“Set Up Debug”向导Vivado会自动创建一个ILA并将标记的信号连接到ILA的探针上。你可以设置采样深度和触发条件
3. **重新实现与生成比特流:** 包含 ILA 核的新设计需要重新实现并生成`.bit`文件。 3. **重新实现与生成比特流:** 包含ILA核的新设计需要重新进行实现(布局布线)并生成`.bit`文件。
4. **硬件调试:** Hardware Manager 中连接开发板下载比特流,然后像使用逻辑分析仪一样设置触发条件并捕获波形。 4. **硬件调试:**Vivado的Hardware Manager中连接开发板下载比特流。之后,你可以像使用逻辑分析仪一样设置复杂的触发条件例如当PC等于某个特定值时触发然后捕获并查看信号在硬件中运行时的真实波形。
向学生介绍 ILA 的使用,是将其从纯粹的模拟仿真引向了专业的硬件调试领域,极大地提升了其实践技能。 向学生介绍ILA的使用哪怕只是基础层面,也是将其从纯粹的模拟仿真引向了专业的硬件调试领域,极大地提升了其实践技能和解决复杂问题的能力
### 5.4 命名规范与报告撰写 ### 5.4 命名规范与报告撰写
@@ -496,30 +543,40 @@ endmodule
良好的命名规范可以提高代码的可读性和维护性。建议统一使用简洁的英文缩写或单词来命名,并遵循一致的风格(如驼峰式或下划线式)。 良好的命名规范可以提高代码的可读性和维护性。建议统一使用简洁的英文缩写或单词来命名,并遵循一致的风格(如驼峰式或下划线式)。
- **模块命名:** `CpuTop``cpu_top` * **模块命名:** `CpuTop``cpu_top`
- **信号命名:** `pc_next`, `reg_write`, `mem_to_reg` * **信号命名:** `pc_next`, `reg_write`, `mem_to_reg`
- **常量:** 使用全大写,如 `parameter ALU_ADD = 4'b0000;` * **常量:** 使用全大写,如 `parameter ALU_ADD = 4'b0000;`
- **注释:** 在模块定义和关键逻辑处添加注释,说明信号作用和设计意图。 * **注释:** 在模块定义和关键逻辑处添加注释,说明信号作用和设计意图。
#### 5.4.2 最终课程设计报告结构 #### 5.4.2 最终课程设计报告结构
一份结构清晰、内容详实的报告是展示项目成果的关键。建议报告遵循以下结构: 一份结构清晰、内容详实的报告是展示项目成果的关键。建议报告遵循以下结构:
1. **引言:** 项目背景、目标、LA32R 架构简介、报告结构概述。 1. **引言:**
2. **总体设计描述:** CPU 总体架构、模块划分、数据通路概览图及说明 * 项目背景、目标与意义
* LA32R架构简介。
* 报告结构概述。
2. **总体设计描述:**
* CPU总体架构、模块划分、数据通路概览。
* 附上CPU总体结构框图或数据通路图并引用说明。
3. **模块设计说明:** 3. **模块设计说明:**
- 分小节详细描述每个模块PC、寄存器堆、ALU 等)。 * 分小节详细描述每个核心Verilog模块PC、寄存器堆、ALU、控制单元等)。
- 为每个模块提供接口表(信号名、位宽、方向、含义)。 * 为每个模块提供接口表(信号名、位宽、方向、含义)。
- 描述内部实现原理,可附上关键 Verilog 代码片段和注释。 * 描述内部实现原理可附上关键Verilog代码片段和注释。
4. **数据通路与控制分析:** 4. **数据通路与控制分析:**
- 为五类指令分别绘制简化的数据通路图(可在总图上高亮)。 * 为五类指令分别绘制简化的数据通路图(可在总图上高亮)。
- 提供一张完整的控制信号总表,对比所有指令的控制信号取值,并进行分析。 * 提供一张完整的控制信号总表,对比所有指令的控制信号取值,并进行分析。
5. **实现细节和仿真结果:** 5. **实现细节和仿真结果:**
- 附上关键模块的核心 Verilog 代码。 * 附上关键模块的核心Verilog代码。
- 展示关键指令执行时的仿真波形图,并结合数据通路进行分析,证明其正确性。尤其要展示分支跳转成功和失败的对比波形。 * 展示关键指令执行时的仿真波形图,并结合数据通路进行分析,证明其正确性。尤其要展示分支跳转成功和失败的对比波形。
- 附上自检查测试平台的最终 PASS/FAIL 测试结果截图和日志。 * 附上自检查测试平台的最终PASS/FAIL测试结果截图和日志。
6. **结论与展望:** 总结项目完成情况,分析单周期设计的优缺点,并对可能的改进方向(如多周期、流水线)进行展望。 6. **结论与展望:**
7. **附录:** 完整的 Verilog 源代码、测试程序的汇编代码和机器码 * 总结项目完成情况和达成的设计目标
* 再次审视单周期设计的优缺点,并对可能的改进方向(如多周期、流水线)进行展望。
* 分享设计过程中的心得与体会。
7. **附录:**
* 所有Verilog源代码。
* 测试程序的汇编代码和机器码。
--- ---