Lab 4: 单周期 CPU
注意
- 官方 ppt 部分内容存在错误,请注意辨别
- 具体的 verilog 代码请先根据官方 ppt 自行完成,本文档不提供全部的 verilog 代码,仅作参考和提示作用
- 本文档为个人经验,具体情况具体分析
实验内容
- 利用课件给出的 DataPath 和 Controller,构建 CPU 核
- 设计 DataPath
- 设计 Controller
- 实现指令扩展
- 实现 CPU 中断处理
4.1 构建 CPU 核
将课件中的 DataPath.edf DataPath.v SCPU_ctrl.edf SCPU_ctrl.v 保存到 comp_organ/IP/lab4 目录下
在目录 comp_organ/project/ 下创建工程文件,命名为 lab4_CPU
新建 SCPU.v 源文件,导入文件 DataPath.edf DataPath.v SCPU_ctrl.edf SCPU_ctrl.v,根据连接图编写 verilog 代码
4.2 设计 DataPath
原理
根据 ppt 的内容,了解 DataPath 的原理
PC 模块
新建 PC.v 源文件,根据 ppt 内容,编写 verilog 代码
ImmGen 模块
新建 ImmGen.v 源文件,根据 ppt 内容和不同格式立即数扩展规则,编写 verilog 代码
| Instr_type |
ImmSel |
| I-type |
00 |
| S-type |
01 |
| B-type |
10 |
| J-type |
11 |
DataPath 模块
新建 my_DataPath.v 源文件,根据连接图调用相关文件,编写 verilog 代码
4.3 设计 Controller
原理
根据 ppt 的内容,了解 Controller 的原理
Controller 模块
新建 my_SCPU_ctrl.v 源文件,根据 ppt 内容和下表,编写 verilog 代码
| instr |
opcode |
fun3 |
fun7 |
| add |
0110011 |
000 |
0000000 |
| sub |
0110011 |
000 |
0100000 |
| slt |
0110011 |
010 |
0000000 |
| xor |
0110011 |
100 |
0000000 |
| srl |
0110011 |
101 |
0000000 |
| or |
0110011 |
110 |
0000000 |
| and |
0110011 |
111 |
0000000 |
| sw |
0100011 |
010 |
- |
| beq |
1100011 |
000 |
- |
| jal |
1101111 |
- |
- |
| lw |
0000011 |
010 |
- |
| addi |
0010011 |
000 |
- |
| slti |
0010011 |
010 |
- |
| xori |
0010011 |
100 |
- |
| ori |
0010011 |
110 |
- |
| andi |
0010011 |
111 |
- |
| srli |
0010011 |
101 |
0000000 |
| Instr |
Branch |
Jump |
ImmSel |
ALUSrc_B |
ALU_Control |
MemRW |
RegWrite |
MemtoReg |
| add |
0 |
0 |
* |
0 |
010 |
0 |
1 |
00 |
| sub |
0 |
0 |
* |
0 |
110 |
0 |
1 |
00 |
| and |
0 |
0 |
* |
0 |
000 |
0 |
1 |
00 |
| or |
0 |
0 |
* |
0 |
001 |
0 |
1 |
00 |
| slt |
0 |
0 |
* |
0 |
111 |
0 |
1 |
00 |
| xor |
0 |
0 |
* |
0 |
011 |
0 |
1 |
00 |
| srl |
0 |
0 |
* |
0 |
101 |
0 |
1 |
00 |
| addi |
0 |
0 |
00 |
1 |
010 |
0 |
1 |
00 |
| slti |
0 |
0 |
00 |
1 |
111 |
0 |
1 |
00 |
| xori |
0 |
0 |
00 |
1 |
011 |
0 |
1 |
00 |
| ori |
0 |
0 |
00 |
1 |
001 |
0 |
1 |
00 |
| andi |
0 |
0 |
00 |
1 |
000 |
0 |
1 |
00 |
| srli |
0 |
0 |
00 |
1 |
101 |
0 |
1 |
00 |
| lw |
0 |
0 |
00 |
1 |
010 |
0 |
1 |
01 |
| sw |
0 |
0 |
01 |
1 |
010 |
1 |
0 |
* |
| beq |
1 |
0 |
10 |
0 |
110 |
0 |
0 |
* |
| jal |
0 |
1 |
11 |
1 |
* |
0 |
1 |
10 |
修改寄存器相关模块
修改这些模块,以实现在 VGA 上显示 32 个寄存器的值的效果
思路就是将 32 个寄存器的值从 regs 模块中一路引出来
| regs.v |
|---|
| module regs (
-- snip --
output [31:0] x0,
output [31:0] ra,
output [31:0] sp,
output [31:0] gp,
output [31:0] tp,
output [31:0] t0,
output [31:0] t1,
output [31:0] t2,
output [31:0] s0,
output [31:0] s1,
output [31:0] a0,
output [31:0] a1,
output [31:0] a2,
output [31:0] a3,
output [31:0] a4,
output [31:0] a5,
output [31:0] a6,
output [31:0] a7,
output [31:0] s2,
output [31:0] s3,
output [31:0] s4,
output [31:0] s5,
output [31:0] s6,
output [31:0] s7,
output [31:0] s8,
output [31:0] s9,
output [31:0] s10,
output [31:0] s11,
output [31:0] t3,
output [31:0] t4,
output [31:0] t5,
output [31:0] t6
);
reg [31:0] registers [1:31];
-- snip --
assign x0 = 32'b0;
assign ra = registers[1];
assign sp = registers[2];
assign gp = registers[3];
assign tp = registers[4];
assign t0 = registers[5];
assign t1 = registers[6];
assign t2 = registers[7];
assign s0 = registers[8];
assign s1 = registers[9];
assign a0 = registers[10];
assign a1 = registers[11];
assign a2 = registers[12];
assign a3 = registers[13];
assign a4 = registers[14];
assign a5 = registers[15];
assign a6 = registers[16];
assign a7 = registers[17];
assign s2 = registers[18];
assign s3 = registers[19];
assign s4 = registers[20];
assign s5 = registers[21];
assign s6 = registers[22];
assign s7 = registers[23];
assign s8 = registers[24];
assign s9 = registers[25];
assign s10 = registers[26];
assign s11 = registers[27];
assign t3 = registers[28];
assign t4 = registers[29];
assign t5 = registers[30];
assign t6 = registers[31];
-- snip --
endmodule
|
| my_DataPath.v |
|---|
| module my_DataPath (
-- snip --
output [31:0] x0,
output [31:0] ra,
output [31:0] sp,
output [31:0] gp,
output [31:0] tp,
output [31:0] t0,
output [31:0] t1,
output [31:0] t2,
output [31:0] s0,
output [31:0] s1,
output [31:0] a0,
output [31:0] a1,
output [31:0] a2,
output [31:0] a3,
output [31:0] a4,
output [31:0] a5,
output [31:0] a6,
output [31:0] a7,
output [31:0] s2,
output [31:0] s3,
output [31:0] s4,
output [31:0] s5,
output [31:0] s6,
output [31:0] s7,
output [31:0] s8,
output [31:0] s9,
output [31:0] s10,
output [31:0] s11,
output [31:0] t3,
output [31:0] t4,
output [31:0] t5,
output [31:0] t6
);
-- snip --
regs regs_0 (
-- snip --
.x0(x0),
.ra(ra),
.sp(sp),
.gp(gp),
.tp(tp),
.t0(t0),
.t1(t1),
.t2(t2),
.s0(s0),
.s1(s1),
.a0(a0),
.a1(a1),
.a2(a2),
.a3(a3),
.a4(a4),
.a5(a5),
.a6(a6),
.a7(a7),
.s2(s2),
.s3(s3),
.s4(s4),
.s5(s5),
.s6(s6),
.s7(s7),
.s8(s8),
.s9(s9),
.s10(s10),
.s11(s11),
.t3(t3),
.t4(t4),
.t5(t5),
.t6(t6)
);
-- snip --
endmodule
|
| SCPU.v |
|---|
| module SCPU (
-- snip --
output [31:0] x0,
output [31:0] ra,
output [31:0] sp,
output [31:0] gp,
output [31:0] tp,
output [31:0] t0,
output [31:0] t1,
output [31:0] t2,
output [31:0] s0,
output [31:0] s1,
output [31:0] a0,
output [31:0] a1,
output [31:0] a2,
output [31:0] a3,
output [31:0] a4,
output [31:0] a5,
output [31:0] a6,
output [31:0] a7,
output [31:0] s2,
output [31:0] s3,
output [31:0] s4,
output [31:0] s5,
output [31:0] s6,
output [31:0] s7,
output [31:0] s8,
output [31:0] s9,
output [31:0] s10,
output [31:0] s11,
output [31:0] t3,
output [31:0] t4,
output [31:0] t5,
output [31:0] t6
);
-- snip --
my_DataPath my_DataPath_0(
-- snip --
.x0(x0),
.ra(ra),
.sp(sp),
.gp(gp),
.tp(tp),
.t0(t0),
.t1(t1),
.t2(t2),
.s0(s0),
.s1(s1),
.a0(a0),
.a1(a1),
.a2(a2),
.a3(a3),
.a4(a4),
.a5(a5),
.a6(a6),
.a7(a7),
.s2(s2),
.s3(s3),
.s4(s4),
.s5(s5),
.s6(s6),
.s7(s7),
.s8(s8),
.s9(s9),
.s10(s10),
.s11(s11),
.t3(t3),
.t4(t4),
.t5(t5),
.t6(t6)
);
-- snip --
endmodule
|
搭建仿真平台
替换 4.1 中的 DataPath 和 Controller 模块为 4.2 和 4.3 自己设计的
原来的几个文件可以右键点击 Disable File 停用,之后若要启用右键点击 Enable File 即可
新建 SCPU_top.v 模块,根据连接图生成相应的 IP 核,编写 verilog 代码
再次说明:
- ROM 核中存放机器码,即指令
- RAM 核相当于内存
仿真验证
新建 SCPU_top_tb.v testbench 文件
| SCPU_top_tb.v |
|---|
| module SCPU_top_tb();
reg clk;
reg rst;
SCPU_top my_SCPU_top (
.clk(clk),
.rst(rst)
);
always begin
#5 clk = ~clk;
end
initial begin
clk = 1'b0;
rst = 1'b1;
#10;
rst = 1'b0;
end
endmodule
|
根据 ppt,仿真后,添加需要的变量到窗口,点击 Relaunch Simulate 按钮
仿真确认无误后,将 SCPU_top.v 文件,ROM 和 RAM 右键停用,封装此工程文件为 IP 核到 comp_organ/IP/lab4,但在封装前,建议先综合一下,并检查报错信息(包括黄色的报错),确认没有什么语法错误,端口位数不一样,变量名打错了等等的错误(其实仿真前最好也综合一下,把一些问题先排除掉)
即使仿真结果正确,也可能存在一些问题导致后续综合、实现报错
如果需要多次封装 IP 核(即有多个版本做调试),封装时注意命名,避免调用时冲突
例如可命名为 SCPU_v1 SCPU_v2 等等
上板验证
注意
由于未知原因,上板验证时,凡是涉及 RAM 的指令(如 lw sw 等)均运行不正确。如果遇到此问题无法解决,可以尝试将 RAM 替换为一个模块,此模块通过创建一个大数组以达到类似 RAM 的效果
我们验收可以仿真验收,也可以上板验收,我也遇到了这个问题,懒得弄了,就仿真验收了
打开 lab 2 工程文件,生成调用 lab 4.3 SCPU 的 IP 核,替换掉原来的 SCPU 文件,检查 ROM 和 RAM 初始化文件是否是自己需要的
如果觉得封装为 IP 核再调用太麻烦,你可以直接在 lab 2 工程文件里导入 SCPU 的源文件进行编辑
修改 VGA 相关模块,以实现在 VGA 上显示 32 个寄存器的值的效果
| CSSTE.v |
|---|
| -- snip --
wire [31:0] x0;
wire [31:0] ra;
wire [31:0] sp;
wire [31:0] gp;
wire [31:0] tp;
wire [31:0] t0;
wire [31:0] t1;
wire [31:0] t2;
wire [31:0] s0;
wire [31:0] s1;
wire [31:0] a0;
wire [31:0] a1;
wire [31:0] a2;
wire [31:0] a3;
wire [31:0] a4;
wire [31:0] a5;
wire [31:0] a6;
wire [31:0] a7;
wire [31:0] s2;
wire [31:0] s3;
wire [31:0] s4;
wire [31:0] s5;
wire [31:0] s6;
wire [31:0] s7;
wire [31:0] s8;
wire [31:0] s9;
wire [31:0] s10;
wire [31:0] s11;
wire [31:0] t3;
wire [31:0] t4;
wire [31:0] t5;
wire [31:0] t6;
-- snip --
SCPU SCPU_0(
-- snip --
.x0(x0),
.ra(ra),
.sp(sp),
.gp(gp),
.tp(tp),
.t0(t0),
.t1(t1),
.t2(t2),
.s0(s0),
.s1(s1),
.a0(a0),
.a1(a1),
.a2(a2),
.a3(a3),
.a4(a4),
.a5(a5),
.a6(a6),
.a7(a7),
.s2(s2),
.s3(s3),
.s4(s4),
.s5(s5),
.s6(s6),
.s7(s7),
.s8(s8),
.s9(s9),
.s10(s10),
.s11(s11),
.t3(t3),
.t4(t4),
.t5(t5),
.t6(t6)
);
-- snip --
VGA VGA_0(
-- snip --
.x0(x0),
.ra(ra),
.sp(sp),
.gp(gp),
.tp(tp),
.t0(t0),
.t1(t1),
.t2(t2),
.s0(s0),
.s1(s1),
.a0(a0),
.a1(a1),
.a2(a2),
.a3(a3),
.a4(a4),
.a5(a5),
.a6(a6),
.a7(a7),
.s2(s2),
.s3(s3),
.s4(s4),
.s5(s5),
.s6(s6),
.s7(s7),
.s8(s8),
.s9(s9),
.s10(s10),
.s11(s11),
.t3(t3),
.t4(t4),
.t5(t5),
.t6(t6)
);
-- snip --
|
| VGA.v |
|---|
| module VGA (
-- snip --
input [31:0] x0,
input [31:0] ra,
input [31:0] sp,
input [31:0] gp,
input [31:0] tp,
input [31:0] t0,
input [31:0] t1,
input [31:0] t2,
input [31:0] s0,
input [31:0] s1,
input [31:0] a0,
input [31:0] a1,
input [31:0] a2,
input [31:0] a3,
input [31:0] a4,
input [31:0] a5,
input [31:0] a6,
input [31:0] a7,
input [31:0] s2,
input [31:0] s3,
input [31:0] s4,
input [31:0] s5,
input [31:0] s6,
input [31:0] s7,
input [31:0] s8,
input [31:0] s9,
input [31:0] s10,
input [31:0] s11,
input [31:0] t3,
input [31:0] t4,
input [31:0] t5,
input [31:0] t6,
-- snip --
);
-- snip --
VgaDebugger vga_debugger(
-- snip --
.x0 (x0 ),
.ra (ra ),
.sp (sp ),
.gp (gp ),
.tp (tp ),
.t0 (t0 ),
.t1 (t1 ),
.t2 (t2 ),
.s0 (s0 ),
.s1 (s1 ),
.a0 (a0 ),
.a1 (a1 ),
.a2 (a2 ),
.a3 (a3 ),
.a4 (a4 ),
.a5 (a5 ),
.a6 (a6 ),
.a7 (a7 ),
.s2 (s2 ),
.s3 (s3 ),
.s4 (s4 ),
.s5 (s5 ),
.s6 (s6 ),
.s7 (s7 ),
.s8 (s8 ),
.s9 (s9 ),
.s10 (s10 ),
.s11 (s11 ),
.t3 (t3 ),
.t4 (t4 ),
.t5 (t5 ),
.t6 (t6 ),
-- snip --
);
-- snip --
endmodule
|
综合,实现,生成比特流文件,上板验证
4.4 实现指令扩展
保存文件 comp_organ/lab4_instr_more_mem.coe
| lab4_instr_more_mem.coe |
|---|
| memory_initialization_radix=16;
memory_initialization_vector=
00007293,00007313,88888137,00832183,0032A223,00402083,
01C02383,00338863,555550B7,0070A0B3,FE0098E3,007282B3,
00230333,00531463,40000033,40530433,405304B3,0080006F,
00007033,0072F533,00157593,00B51463,00006033,00A5E5B3,
0015E513,00558463,00004033,00A5C633,00164613,00B61463,
00000013,0012D293,00060463,40000033,00129293,00B28463,
00000013,001026B3,00503733,F65FF06F;
|
之后 4.5 中断以及 lab 5 的部分模块,ppt 上用的是未进行指令扩展的版本,所以这里可以复制一份工程文件出来(可以新建一个工程文件,导入 4.1 4.2 4.3 的模块),做个备份
但 4.5 以及 lab 5 使用指令扩展之后的模块,也是能够实现的,只不过要根据具体情况改一些东西
DataPath 扩展
ALU
修改 ALU.v,按照 ppt 内容和下表,编写 verilog 代码
| ALU 控制信号 |
功能 |
| 0010 |
add |
| 0110 |
sub |
| 1110 |
sll(逻辑左移) |
| 0111 |
slt |
| 1001 |
sltu |
| 1100 |
xor |
| 1101 |
srl(逻辑右移) |
| 1111 |
sra(算术右移) |
| 0001 |
or |
| 0000 |
and |
原来的 ALU 采用的是结构描述,增加的功能可以采用行为描述的方法,例如(具体情况具体分析):
| ALU.v |
|---|
| module ALU(
-- snip --
input [3:0] ALU_operation,
output reg [31:0] res,
-- snip --
);
wire [31:0] MUX8T1_32_0_0_out;
-- snip --
always @(*) begin
if (ALU_operation[3] == 0) begin // 兼容原 ALU
res = MUX8T1_32_0_0_out;
end else if (ALU_operation[3] == 1) begin // 扩展指令
case (ALU_operation[2:0])
3'b110: begin // sll 逻辑左移
res = A << B[4:0];
-- snip --
endcase
end
end
-- snip --
MUX8T1_32_0 MUX8T1_32_0_0 (
-- snip --
.s(ALU_operation[2:0]), // input wire [2 : 0] s
.o(MUX8T1_32_0_0_out) // output wire [31 : 0] o
);
endmodule
|
ImmGen
修改 ImmGen.v,根据 ppt 内容和下表,编写 verilog 代码
| Instr_type |
ImmSel |
| I-type |
001 |
| S-type |
010 |
| B-type |
011 |
| J-type |
100 |
| U-type |
000 |
DataPath
修改 my_DataPath.v,根据连接图编写 verilog 代码
Controller 扩展
修改 my_SCPU_ctrl.v,根据 ppt 内容和下表,编写 verilog 代码
| instr |
opcode |
fun3 |
fun7 |
| add |
0110011 |
000 |
0000000 |
| sub |
0110011 |
000 |
0100000 |
| sll |
0110011 |
001 |
0000000 |
| slt |
0110011 |
010 |
0000000 |
| sltu |
0110011 |
011 |
0000000 |
| xor |
0110011 |
100 |
0000000 |
| srl |
0110011 |
101 |
0000000 |
| sra |
0110011 |
101 |
0100000 |
| or |
0110011 |
110 |
0000000 |
| and |
0110011 |
111 |
0000000 |
| sw |
0100011 |
010 |
- |
| beq |
1100011 |
000 |
- |
| bne |
1100011 |
001 |
- |
| lui |
0110111 |
- |
- |
| jal |
1101111 |
- |
- |
| lw |
0000011 |
- |
- |
| jalr |
1100111 |
- |
- |
| addi |
0010011 |
000 |
- |
| slti |
0010011 |
010 |
- |
| sltiu |
0010011 |
011 |
- |
| xori |
0010011 |
100 |
- |
| ori |
0010011 |
110 |
- |
| andi |
0010011 |
111 |
- |
| slli |
0010011 |
001 |
0000000 |
| srli |
0010011 |
101 |
0000000 |
| srai |
0010011 |
101 |
0100000 |
| Instr |
Branch |
BranchN |
Jump |
ImmSel |
ALUSrc_B |
ALU_Control |
MemRW |
RegWrite |
MemtoReg |
| add |
0 |
0 |
00 |
* |
0 |
0010 |
0 |
1 |
00 |
| sub |
0 |
0 |
00 |
* |
0 |
0110 |
0 |
1 |
00 |
| and |
0 |
0 |
00 |
* |
0 |
0000 |
0 |
1 |
00 |
| or |
0 |
0 |
00 |
* |
0 |
0001 |
0 |
1 |
00 |
| sra |
0 |
0 |
00 |
* |
0 |
1111 |
0 |
1 |
00 |
| slt |
0 |
0 |
00 |
* |
0 |
0111 |
0 |
1 |
00 |
| sll |
0 |
0 |
00 |
* |
0 |
1110 |
0 |
1 |
00 |
| sltu |
0 |
0 |
00 |
* |
0 |
1001 |
0 |
1 |
00 |
| xor |
0 |
0 |
00 |
* |
0 |
1100 |
0 |
1 |
00 |
| srl |
0 |
0 |
00 |
* |
0 |
1101 |
0 |
1 |
00 |
| addi |
0 |
0 |
00 |
001 |
1 |
0010 |
0 |
1 |
00 |
| slti |
0 |
0 |
00 |
001 |
1 |
0111 |
0 |
1 |
00 |
| sltiu |
0 |
0 |
00 |
001 |
1 |
1001 |
0 |
1 |
00 |
| xori |
0 |
0 |
00 |
001 |
1 |
1100 |
0 |
1 |
00 |
| ori |
0 |
0 |
00 |
001 |
1 |
0001 |
0 |
1 |
00 |
| andi |
0 |
0 |
00 |
001 |
1 |
0000 |
0 |
1 |
00 |
| slli |
0 |
0 |
00 |
001 |
1 |
1110 |
0 |
1 |
00 |
| srli |
0 |
0 |
00 |
001 |
1 |
1101 |
0 |
1 |
00 |
| srai |
0 |
0 |
00 |
001 |
1 |
1111 |
0 |
1 |
00 |
| lw |
0 |
0 |
00 |
001 |
1 |
0010 |
0 |
1 |
01 |
| sw |
0 |
0 |
00 |
010 |
1 |
0010 |
1 |
0 |
* |
| beq |
1 |
* |
00 |
011 |
0 |
0110 |
* |
0 |
* |
| bne |
* |
1 |
00 |
011 |
0 |
0110 |
* |
0 |
* |
| jalr |
* |
* |
10 |
001 |
1 |
0010 |
* |
1 |
10 |
| jal |
* |
* |
01 |
100 |
1 |
* |
* |
1 |
10 |
| lui |
0 |
0 |
00 |
000 |
* |
* |
* |
1 |
11 |
SCPU 模块
修改 SCPU.v,根据连接图编写 verilog 代码
仿真验证
启用 SCPU_top.v,ROM 和 RAM 模块,其中 ROM 的初始化文件为 comp_organ/lab4_instr_more_mem.coe,进行仿真验证
上板验证
停用 SCPU_top.v,ROM 和 RAM 模块,封装 IP 核,封装时注意命名,避免调用时出现冲突
4.5 实现中断
保存文件 comp_organ/lab4_instr_int_mem.coe
| lab4_instr_int_mem.coe |
|---|
| memory_initialization_radix=16;
memory_initialization_vector=
0200006F,0C40006F,0D80006F,0C80006F,00000033,
00000033,00000033,00000033,00007293,00007313,
88888137,FE62DAE3,00832183,0032A223,00402083,
01C02383,00338863,555550B7,0070A0B3,FE0098E3,
007282B3,00230333,00531463,40000033,40530433,
405304B3,0080006F,00007033,0072F533,00000073,
00157593,00B51463,00006033,00A5E5B3,0015E513,
00558463,00004033,00A5C633,00164613,00B61463,
00000013,0012D293,00060463,40000033,00129293,
00B28463,00000013,001026B3,00503733,F5DFF06F,
00168693,00168693,30200073,40C70733,40C70733,
30200073,00128793,00178793,30200073;
|
建议复制一份工程文件
原理
根据 ppt 内容和课件中的 I_int.pdf 文件了解中断处理的原理
虽然 ppt 里介绍了很多中断需要用到的寄存器,但我自己在实现时只用了 mepc 这一个
RV_int
新建 RV_int.v 源文件,根据 ppt 内容和下文提示(具体情况具体分析),编写 verilog 代码
我自己实现的时候,仿真时发现 CPU 变成了双周期 CPU,因为第一个周期 PC 码停留在 RV_int 模块,处理中断,第二个周期 PC 码才到 PC 模块。还有一些因双周期带来的小 bug。我当时想不明白怎么改了,就改了这些 bug 按照双周期做了
不过我现在想到,可以把中断处理的功能也放到 PC 模块里,不需要额外增加这个 RV_int 模块了,应该能解决这个双周期问题吧(没试过,可行性不清楚)
| RV_int.v |
|---|
| module RV_int(
input clk,
input rst,
input INT, // 外部中断信号
input ecall, // 内部中断信号
input mret, // 异常状态返回信号
input ill_instr, // 非法指令中断信号
input [31:0] pc_next,
output reg [31:0] pc,
);
reg [31:0] mepc; // 存储异常的返回地址
// mepc
// 遇到 ecall 和 ill_instr 情况,mepc 应保存 pc_next 的值
// 遇到 INT 情况,mepc 应保存 pc_next - 4 的值
// pc
// 正常情况,pc 即为 pc_next
// 遇到 INT,pc 应为 0xc
// 遇到 ecall。pc 应为 0x8
// 遇到 ill_instr,pc 应为 0x4
// 遇到 mret,pc 应为 mepc(返回原来的地址)
endmodule
|
DataPath
修改 DataPath.v,根据连接图,编写 verilog 代码
Controller
修改 my_SCPU_ctrl.v,根据 ppt 内容、下表和下文提示(具体情况具体分析),编写 verilog 代码
| instr |
opcode |
Fun_mret(instr[29:28]) |
Fun_ecall(instr[22:20]) |
| mret |
1110011 |
11 |
- |
| ecall |
1110011 |
- |
000 |
| my_SCPU_ctrl.v |
|---|
| module my_SCPU_ctrl(
input [6:0] OPcode,
input [2:0] Fun3,
input Fun7,
-- snip --
input [2:0] Fun_ecall, // inst[22:20]
input [1:0] Fun_mret, // inst[29:28]
-- snip --
output reg ecall,
output reg mret,
output reg ill_instr
);
-- snip --
assign Fun = {Fun3, Fun7};
always @(*) begin
case (OPcode)
7'b0110011: begin // R-type
mret = 1'b0; // 正常的指令,这些信号均为 0
ecall = 1'b0;
ill_instr = 1'b0;
-- snip --
case (Fun)
4'b0000: begin // add
ALU_Control = 4'b0010;
end
-- snip --
default: begin // default ill_instr
ill_instr = 1'b1; // 其余的指令均为未定义/错误指令
RegWrite = 1'b0; // 保险起见,为避免寄存器被修改,将这个置为 0
end
endcase
end
-- snip --
7'b1110011: begin // 中断处理
mret = 1'b0;
ecall = 1'b0;
ill_instr = 1'b0;
RegWrite = 1'b0;
if (Fun_mret == 2'b11) begin // mret
mret = 1'b1;
end else if (Fun_ecall == 3'b000) begin // ecall
ecall = 1'b1;
end else begin
ill_instr = 1'b1;
end
end
default: begin // default ill_instr
ill_instr = 1'b1; // 其余的指令均为未定义/错误指令
RegWrite = 1'b0;
end
endcase
end
endmodule
|
SCPU
修改 SCPU.v,根据连接图,编写 verilog 代码
仿真验证
修改 SCPU_top.v,增加 INT 信号
| SCPU_top_tb.v |
|---|
| module SCPU_top(
input clk,
input rst,
input INT
);
-- snip --
SCPU my_SCPU (
-- snip --
.INT0(INT),
-- snip --
);
-- snip --
endmodule
|
ROM 初始化文件使用 comp_organ/lab4_instr_int_mem.coe
修改 SCPU_top_tb.v,增加 INT 信号
| SCPU_top_tb.v |
|---|
| module SCPU_top_tb();
reg clk;
reg rst;
reg INT;
SCPU_top my_SCPU_top (
.clk(clk),
.rst(rst),
.INT(INT)
);
always begin
#5 clk = ~clk;
end
initial begin
clk = 0;
rst = 1;
INT = 0;
#10;
rst = 0;
#615; // 具体数值根据仿真波形图来定
INT = 1;
#20;
INT = 0;
end
endmodule
|
上板验证
建议复制一份 lab 2 的工程文件
修改 CSSTE.v,根据连接图,编写 verilog 代码