实验四、单周期 CPU 设计与实现——单指令 CPU
一、实验目的:
通过设计并实现支持一条指令的 CPU,理解和掌握 CPU 设计的基本原理和过程。
二、实验内容:
设计和实现一个支持加法指令的单周期 CPU。要求该加法指令(表示为 add r1,r2,r3)格式约定如下:
采用寄存器寻址,r1,r2,r3 为寄存器编号,r1 和 r2 存放两个源操作数,r3为目标寄存器,其功能为[r1] + [r2] -> r3;
指令字长 16 位,操作码和地址码字段分配如下所示:
15 9 8 6 5 3 2 0
OpCode r1 r2 r3
三、实验原理
单周期 CPU 是指所有指令均在一个时钟周期内完成的 CPU。CPU 由数据通路及其控 制部件两部分构成,因而要完成一个支持若干条指令 CPU 的设计,需要依次完成以下两件 事:
1) 根据指令功能和格式设计 CPU 的数据通路;
2) 根据指令功能和数据通路设计控制部件。
3.1 根据功能和格式完成 CPU 的数据通路设计
本实验需要设计的 CPU 只需要支持一条加法指令,而该指令的功能是在一个时钟周期 内从寄存器组中 r1 和 r2 中取出两个操作数,然后送到 ALU 进行加法运算,最后把计算结 果保存到 r1 寄存器中。下图给出了改加法指令的数据通路图。
OpCode |
r1 |
r2 |
r3 |
图 3.1. 加法指令 add r1,r2,r3 数据通路
此外,还需要确定各个部件的位数,为了简单起见,我们假设目标 CPU 的机器字长、 存储字长和指令字长相等均为 16 位,存储单元个数假设为 256,按字寻址,并取 PC 位数 为 8。
3.2 根据指令功能、数据通路完成控制单元的设计
控制单元的功能是为当前要执行的指令产生微操作命令从而完成该指令的执行。为了能 够完成加法指令的执行,结合图 1,控制单元需要在取出指令后根据指令操作码(本例中是 加法指令),控制 ALU(参考实验二)做加法(通过给 alu_op 信号线相应赋值),并把结果 写回寄存器组(参考实验三)中(通过给 wr_en 赋值为 true)。图 2 给出了整合控制单元后 目标 CPU 的原理图,系统时钟信号也已标注。
图 3.2. 单指令 CPU 原理图
四、实验步骤
在第三部分通过对该 CPU 实现细节的分析、设计,并得到该 CPU 的原理图后,就可 以依次实现各个模块,并进行仿真验证了。
4.1 CPU 各模块 Verilog 实现
在前面实验中,已经分别设计和实现了 PC、指令存储器、寄存器组和 ALU,这里只给 出各个模块的功能描述及其接口定义,具体实现可以直接使用或者调整前面试验的实现代码。
1) PC 模块
表 4.1 PC 模块功能描述
输入 |
时钟信号 clk、重置信号 rst |
输出 |
指令地址 pc(8 位) |
功能 |
每个时钟上升沿 PC 的值自动加 1,并输出 |
Verilog 关键代码:
module pc(
input wire clk, rst,
output reg [7:0] pc
);
always@(posedge clk) begin
if(rst == 1)
pc = 0;
else
pc = pc + 1;
end
endmodule
2) 指令存储器模块
表 4.2 指令存储器模块功能描述
输入 |
8 位指令地址 Addr |
输出 |
16 位指令 Ins |
功能 |
存放待执行的指令(初始化),并根据地址输出指令 |
Verilog 关键代码:
module insmemory(
input wire [7:0] Addr,
output reg [15:0] Ins
);
integer i;
integer j;
reg [15:0] unit[8'b11111111:0];
initial begin
for(i = 0; i < 256; i = i + 1) begin
j = i % 5;
unit[i][2:0] = j;
unit[i][5:3] = j + 1;
unit[i][8:6] = j + 2;
unit[i][15:9] = 0;
end
end
always@* begin
Ins = unit[Addr];
end
endmodule
3) 寄存器堆
表 4.3 寄存器堆模块功能描述
输入 |
时钟信号 clk、读写控制线 wr_en、读寄存器编号 read_reg1 和read_reg2、写寄存器编号 write_reg、写入数据 write_data |
输出 |
对应两个读寄存器编号的寄存器值 reg1 和 reg2 |
功能 |
根据读寄存器编号给出对应寄存器的值;在写允许情况下,把写入 端的数据在 clk 下降沿写到写寄存器编号对应的寄存器 |
Verilog 关键代码:
module registerfile(
input wire clk, wr_en,
input wire [2:0] read_reg1, read_reg2, write_reg,
input wire [15:0] write_data,
output reg [15:0] reg1, reg2
);
integer i;
reg [15:0] regfile[7:0];
initial begin
for(i = 0; i < 8; i = i + 1) begin
regfile[i] = i;
end
end
always@* begin
reg1 = regfile[read_reg1];
reg2 = regfile[read_reg2];
end
always@(negedge clk) begin
if(wr_en == 1)
regfile[write_reg] = write_data;
end
endmodule
4) ALU
表 4.4 ALU 模块功能描述
输入 |
操作数 in1 和 in2、操作选择信号 alu_op |
输出 |
ALU 运算结果 Z |
功能 |
根据操作选择信号计算 in1 和 in2 的运算结果 Z |
Verilog 关键代码:
module alu(
input wire [15:0] in1, in2,
input wire [2:0] alu_op,
output reg [15:0] Z
);
always@* begin
case(alu_op)
3'b000: Z = in1+in2;
3'b001: Z = in1-in2;
3'b010: Z = in1&&in2;
3'b011: Z = in1||in2;
3'b100: Z = in1<>in2;
endcase
end
endmodule
5) 控制单元
表 4.5 控制单元模块功能描述
输入 |
指令(操作码) |
输出 |
寄存器堆的读写控制线 wr_en、ALU 的操作选择信号 alu_op |
功能 |
根据当前指令功能对 wr_en 和 alu_op 赋值 |
Verilog 关键代码:
module cu(
input wire [6:0] Ins_op,
output reg wr_en,
output reg [2:0] alu_op
);
always@* begin
if(Ins_op == 0)
wr_en = 1;
alu_op = 3'b000;
end
endmodule
4.2 CPU 顶层文件封装实现
通过根据图 2 将以上定义的模块进行连接、封装就得到了目标 CPU,该 CPU 的输入为 系统时钟信号 clk 和重置信号 reset。
Verilog 关键代码:
module cpu(
input wire clk, rst
);
wire wr;
wire [2:0] op;
wire [7:0] addr;
wire [15:0] z, ins, r1, r2;
pc pc(
.clk(clk), .rst(rst), .pc(addr)
);
insmemory insmemory(
.Addr(addr), .Ins(ins)
);
cu cu(
.Ins_op(ins[15:9]),
.wr_en(wr), .alu_op(op)
);
registerfile registerfile(
.clk(clk), .wr_en(wr),
.read_reg1(ins[8:6]), .read_reg2(ins[5:3]), .write_reg(ins[2:0]),
.write_data(z), .reg1(r1), .reg2(r2)
);
alu alu(
.in1(r1), .in2(r2),
.alu_op(op), .Z(z)
);
endmodule
4.3 CPU 模拟仿真
为了仿真验证所实现的 CPU,需要定义测试文件并在测试文件中对指令存储器和寄存 器堆中的相应寄存器的值进行初始化,并通过仿真波形图查看是否指令得到了正确执行。
1)TestBench 关键代码
module cpu_tb;
reg clk, rst;
always #1 clk = ~clk;
initial begin
clk = 1;
rst = 1;
#1 rst = 0;
#10 $stop;
end
cpu uut(
.clk(clk), .rst(rst)
);
endmodule
2、ModelSim 仿真及分析
五、总结
通过实验,请思考你认为完成一个 CPU 的设计与实现主要由哪几个步骤完成?主要注 意事项有哪些?
六、进一步实验
1)在本节实现的单指令 CPU 基础上,添加存数指令 st r1,addr,实现一个可以支持加17 / 27
法和存数指令的 CPU,并使用 ModelSim 进行仿真验证。 指令 st r1, addr: [r1] -> mem[addr]的格式如下:
159865 0
实验分析提示
首先根据新增加的访存指令功能设计其数据通路,如图 6.1。为使 CPU 能够支持前面 的加法指令和该访存指令,只需要将两个数据通路进行合并(即图 3.1 和图 6.1),并最终得 到该 CPU 的原理图,如图 6.2 所示。
图 6.1 访存指令数据通路
如果要设计支持更多条指令的 CPU,只需根据每条指令设计其数据通路,并采用合并 的方式构建支持所有指令的 CPU 数据通路,然后在进行控制单元的设计即可。在合并过程 中需要注意的是:当某个部件的输入端口有多个输入来源的时候需在此端口前添加一个多路 选择器从而允许控制部件根据执行的需要选择所需的数据来源。
st |
r1 |
addr |
18 / 27
图 6.2 支持加法和访存指令的 CPU 原理图
2)设计和实现一个支持加法 add、立即数加 addi、存数 st、取数 ld 四条指令的 CPU(机器字长=指令字长=存储字长),要求指令格式如下:
加法 add: [r1] + [r2] -> r1
15 1211 98 65 0
立即数加 addi r1,Imm: [r1]+SignExt[Imm] -> r1
15 12 11 9 8 0
add |
r1 |
r2 |
addi |
r1 |
imm |
存数 st r1, addr: [r1] -> mem[addr] 15 12 11
9 8 0
19 / 27
st |
r1 |
addr |
取数 ld r1, addr: mem[addr] -> r1
15 12 11 9 8 0
ld |
r1 |
addr |