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