前言:本章内容主要是演示在vivado下利用Verilog语言进行单周期简易CPU的设计。一步一步自己实现模型机的设计。本章先介绍单周期简易CPU中数据通路的设计。
💻环境:一台内存4GB以上,装有64位Windows操作系统和Vivado 2017.4以上版本软件的PC机。
💎本章所采用的指令为LoongArch之LA32R版
目录
Ⅰ前置知识
0x00 二选一控制器
0x01 数据通路
Ⅱ Verilog实现
0x00 二选一控制器
0x01 构建数据通路
Ⅲ 结果分析
0x00 思路一结果分析
0x01 思路二结果分析
Ⅰ前置知识
0x00 二选一控制器
数据选择器是一种多路输入单路输出的组合逻辑电路,MUX可以实现在两个输入信号中选择一个作为输出信号的功能。它通常用于选择数据通路中的输入信号或控制信号。在数字电路设计中,MUX是一个非常基本的元件,也是其他复杂电路的基础。
“2 选 1”数据选择器电路的输入/输出信号设计说明如图:
信号名称 | 信号用途说明 | |
输入信号 | a | 通道1数据输入,位宽为32位 |
b | 通道2数据输入,位宽为32位 | |
s | 通道选择,位宽是1位 | |
输出信号 | y | 输出所选通道的数值,位宽为32位 |
0x01 数据通路
把前两篇博客中所设计的基本部件:ALU、寄存器堆和存储器进行连接,搭建数据通路。
该模块的电路参考结构图如下:
图中模块和信号说明如下:
- Registers模块:寄存器堆模块,信号及读写功能说明参见实验2。
- ALU模块:算术逻辑运算单元模块,信号及读写功能说明参见实验1。
- MUX模块:2选1模块。
- DataRAM模块:RAM存储器,信号及读写功能说明参见实验2。
- Ext模块:立即数扩展模块,信号及读写功能说明参见实验1。
- Instr信号:32位输入信号,其值位指令的32位机器码。
- clk信号:时钟信号,输入。
- srcReg信号:MUX数据输入选择信号。根据不同指令,选择指令提供的rk或rd值作为寄存器堆Rb的输入。
- ALUBsrc信号:MUX数据输入选择信号。根据不同指令,选择寄存器或立即数作为ALU的源操作数b。
- MemToReg信号:MUX数据输入选择信号。根据不同指令,选择将ALU的运算结果或是DataRAM取出的值作为要存入寄存器堆的值。
Ⅱ Verilog实现
0x00 二选一控制器
设计代码:
module mux(a,b,s,y);
input[31:0]a,b; input s; output[31:0]y; assign y = (!s)?a:b;
endmodule
由于该模块比较简单,且本篇文章重点在于构建数据通路,故不再给出仿真代码和仿真波形,感兴趣的读者可自行验证。
0x01 构建数据通路
利用Verilog HDL设计顶层电路模型,把前面实验设计的ALU、寄存器堆和存储器进行连接,搭建支持下表所示6条LA32R指令功能的数据通路。
指令 | 功能 | 说明 |
add.w rd,rj,rk | GR[rd]⟵GR[rj]+GR[rk] | 加法 |
slt rd,rj,rk | if (GR[rj]<GR[rk]) GR[rd]⟵1 else GR[rd]⟵0 | 带符号数的大小比较 |
sltu rd,rj,rk | if (GR[rj]<GR[rk]) GR[rd]⟵1 else GR[rd]⟵0 | 无符号数的大小比较 |
lu12i.w rd,si20 | GR[rd] ⟵si20 || 12’b0 | GR[rd]的高20位为si20,低12位为0 |
st.w rd,rj,si12 | Addr⟵GR[rj] + Signextend(si12) , M[Addr]⟵GR[rd] | 把GR[rd]的值存入内存Addr单元, |
ld.w rd,rj,si12 | Addr⟵GR[rj] + Signextend(si12) , GR[rd] ⟵M[Addr] | 从内存Addr单元取数,存入R[rd] |
所用到的六条指令的龙芯架构32位精简版指令参考如下图:
本文提供两种思路:
1.在书写仿真文件时,将信号量给出。
2.在书写设计文件时,将信号量封装在设计文件中。
❓思考:
读者可以先思考这两种思路有什么各存在什么优缺点
🚩注:
数据通路所用到的模块(ALU,寄存器等)在本专栏的前两篇文章中均以给出,故此处不再赘述,但是构建数据通路时要注意将这几个模块写入设计文件中。
下列设计文件中只给出顶层设计代码。
需要的模块:
【单周期CPU】LoongArch | 32位寄存器DR | 32位的程序计数器PC | 通用寄存器堆Registers | 32位RAM存储器_流继承的博客-CSDN博客
【单周期CPU】LoongArch | 立即数扩展模块Ext | 32位算术逻辑运算单元(ALU)_流继承的博客-CSDN博客
首先根据指令,选择合适的控制信号:
思路一:
设计代码:
module cpu (
input [31:0]Instr,
input clk, srcReg, ALUBSrc,MemWrEn, MemToReg,RegWr,
input [1:0]ExtOp,
input [2:0]AluCtrl,
output [31:0]Result,
output zero) ;
wire [4:0]rb;
wire [31:0]aluA, aluB, regBusB, imm, addrDram, datainDram, dataoutDram, aluResult, MemToRegMux1;
MUX muxRb(.mux_out(rb),.mux_in1(Instr[14:10]),.mux_in2(Instr[4:0]),.sel(srcReg));
Registers registers(.busW(Result),.clk(clk),.RegWr(RegWr),
.Ra(Instr[9:5]),.Rb(rb),.Rw(Instr[4:0]),
.busA(aluA),.busB(regBusB));
li_ji_shu ext(.DataIn(Instr),.ExtOp(ExtOp),.DataOut(imm));
MUX muxAluB(.mux_out(aluB),.mux_in1(regBusB),.mux_in2(imm),.sel(ALUBSrc));
ALU alu(.a(aluA),.b(aluB),.op(AluCtrl),.AddResult(aluResult),.Zero(zero));
RAM dataRam(.clk(clk),.MemWrEn(MemWrEn),.addr(aluResult),.data_in(regBusB),.data_out(MemToRegMux1));
MUX muxResult(.mux_out(Result),.mux_in1(aluResult),.mux_in2(MemToRegMux1),.sel(MemToReg));
endmodule
仿真代码:
module sim_cpu();
reg [31:0]Instr;
reg clk, srcReg, ALUBSrc,MemWrEn, MemToReg,RegWr;
reg [1:0]ExtOp;
reg [2:0]AluCtrl;
wire [31:0]Result;
wire zero ;
Cpuuu1(Instr,clk, srcReg, ALUBSrc,MemWrEn, MemToReg,RegWr,ExtOp,AluCtrl,Result,zero);
initial clk = 0;
always begin
#20 clk = ~clk;
end
initial begin
Instr = 32'b000101_0_0000_0000_0000_0000_0001_00001;
srcReg=0; ExtOp=2'b10;ALUBSrc=1; AluCtrl=3'b111;MemWrEn=0;MemToReg=0; RegWr=1 ; #40;
// $stop;
Instr = 32'b000101_0_0000_0000_0000_0000_0010_00010;
srcReg=0; ExtOp=2'b10;ALUBSrc=1; AluCtrl=3'b111;MemWrEn=0;MemToReg=0; RegWr=1 ; #40;
// $stop;
Instr = 32'b000101_0_1111_1111_1111_1111_1111_00011;
srcReg=0; ExtOp=2'b10;ALUBSrc=1; AluCtrl=3'b111;MemWrEn=0;MemToReg=0; RegWr=1 ; #40;
// $stop;
Instr = 32'b000101_0_1111_1111_1111_1111_1110_00100;
srcReg=0; ExtOp=2'b10;ALUBSrc=1; AluCtrl=3'b111;MemWrEn=0;MemToReg=0; RegWr=1 ; #40;
$stop;
Instr = 32'b000000_0000_01_00000_00010_00001_00101;
srcReg=0; ExtOp=2'b00;ALUBSrc=0; AluCtrl=3'b000;MemWrEn=0;MemToReg=0; RegWr=1 ; #40;
$stop;
end
endmodule
思路二:
设计代码:
module cpu(Zero,busA,data_out,Instr,clk);
input clk;
input [31:0] Instr;
output Zero;
output [31:0] busA,data_out;
wire [31:0] imm1,imm2,imm3,imm4,imm5,imm6,imm7,imm8;
reg [1:0] ExtOp;
reg SrcReg,RegWr,ALUBSrc,MemWrEn,MemToReg;
reg [2:0] ALUCtrl;
always @ (*) begin
if(Instr[29:27]==3'b000)
case(Instr[17:15])
3'b000:{SrcReg,RegWr,ALUBSrc,ALUCtrl,MemWrEn,MemToReg,ExtOp} = 10'b0100000000;
3'b100:{SrcReg,RegWr,ALUBSrc,ALUCtrl,MemWrEn,MemToReg,ExtOp} = 10'b0101010000;
3'b101:{SrcReg,RegWr,ALUBSrc,ALUCtrl,MemWrEn,MemToReg,ExtOp} = 10'b0101100000;
endcase
if(Instr[29:27]==3'b010)
{SrcReg,RegWr,ALUBSrc,ALUCtrl,MemWrEn,MemToReg,ExtOp} = 10'b0111110010;
if(Instr[29:27]==3'b101) begin
if(Instr[24:22]==3'b110)
{SrcReg,RegWr,ALUBSrc,ALUCtrl,MemWrEn,MemToReg,ExtOp} = 10'b1010001000;
if(Instr[24:22]==3'b010)
{SrcReg,RegWr,ALUBSrc,ALUCtrl,MemWrEn,MemToReg,ExtOp} = 10'b0110000100;
end
end
assign busA = imm3;
assign data_out = imm7;
Ext M1(.Dataout(imm1),.DataIn(Instr),.Extop(ExtOp));
MUX M2(.mux_out(imm2),.sel(SrcReg),.mux_in1(Instr[14:10]),.mux_in2(Instr[4:0]));
Registers M3(.busA(imm3),.busB(imm4),.clk(clk),.RegWr(RegWr),.busW(imm8),.Rw(Instr[4:0]),.Ra(Instr[9:5]),.Rb(imm2));
MUX M4(.mux_out(imm5),.sel(ALUBSrc),.mux_in1(imm4),.mux_in2(imm1));
ALU M5(.Zero(Zero),.AddResult(imm6),.a(imm3),.b(imm5),.op(ALUCtrl));
RAM M6(.data_out(imm7),.clk(clk),.MemWrEn(MemWrEn),.addr(imm6),.data_in(imm4));
MUX M7(.mux_out(imm8),.sel(MemToReg),.mux_in1(imm6),.mux_in2(imm7));
endmodule
仿真代码:
module sim_cpu();
reg clk;
reg [31:0] Instr;
wire Zero;
wire [31:0] busA,data_out;
cpu uu1(Zero,busA,data_out,Instr,clk);
initial clk = 0;
always begin
Instr = 32'b0001010_00000000000000000001_00001;#100;//lu12i.w 存4'h1000入1号寄存器
$stop;
Instr = 32'b0001010_00000000000000000010_00010;#100;//lu12i.w 存4'h2000入2号寄存器
$stop;
Instr = 32'b00000000000100000_00001_00010_00011;#100;//add.w 1+2寄存器->3寄存器
$stop;
Instr = 32'b0001010_10000000000000000001_00100;#100;//lu12i.w 存4'h-1000入4号寄存器
$stop;
Instr = 32'b00000000000100100_00001_00100_00101;#100;//slt 带符号比较结果存入寄存器5
$stop;
Instr = 32'b00000000000100101_00001_00100_00110;#100;//sltu 无符号数比较,结果存入寄存器6
$stop;
Instr = 32'b0010100110_000000000001_00001_00010;#100;//st.w 2寄存器存入1寄存器
$stop;
Instr = 32'b0010100010_000000000001_00001_00111;#100;//ld.w
$stop;
end
always #50 clk = ~clk;
endmodule
Ⅲ 结果分析
0x00 思路一结果分析
仿真结果:
阅读仿真波形,可得到如下的仿真结果数据。
数据通路电路仿真测试输入信号初值仿真测试结果
序号 | 输入 | 输出 | ||||||||
Instr | SrcReg | ExtOp | ALUBSrc | AluCtrl | MemWrEn | MemToReg | RegWr | Result | Zero | |
1 | 32’h14000021 | 0 | 2’b10 | 1 | 3’b111 | 0 | 0 | 1 | 32’h00001000 | 0 |
2 | 32’h14000042 | 0 | 2’b10 | 1 | 3’b111 | 0 | 0 | 1 | 32’h00002000 | 0 |
3 | 32’h15ffffe3 | 0 | 2’b10 | 1 | 3’b111 | 0 | 0 | 1 | 32’hfffff000 | 0 |
4 | 32’h15ffffc4 | 0 | 2’b10 | 1 | 3’b111 | 0 | 0 | 1 | 32’hffffe000 | 0 |
0x01 思路二结果分析
结果展示:
根据寄存器中是否存入了指令里给出的值判断是否成功
阅读实验结果,可得到如下的数据。
数据通路电路仿真测试输入信号初值测试结果
序号 | 输入 | 结果 |
Instr | ||
1 | 0001010_00000000000000000001_00001 | 把值存入寄存器1,寄存器1内的值为00001000 |
2 | 0001010_00000000000000000010_00010 | 把值存入寄存器2,寄存器2内的值为00002000 |
3 | 00000000000100000_00001_00010_00011 | 寄存器1和2的结果相加,存入寄存器3,寄存器3内的值为00003000 |
4 | 0001010_10000000000000000001_00100 | 把值存入寄存器4,寄存器4内的值为80001000 |
5 | 00000000000100100_00001_00100_00101 | 带符号比较结果存入寄存器5,值为00000001 |
6 | 00000000000100101_00001_00100_00110 | 无符号数比较,结果存入寄存器6,值为00000000 |
7 | 0010100110_000000000001_00001_00010 | 将值存入内存 |
8 | 0010100010_000000000001_00001_00111 | 将值从内存取出 |
分析:
首先把值存入寄存器1,并检查寄存器1内的值是否为00001000;
再把值存入寄存器2,检查寄存器2内的值是否为00002000;
把寄存器1和2的结果相加,存入寄存器3,检查寄存器3内的值是否为00003000。
把值存入寄存器4,寄存器4内的值为80001000,对寄存器4和寄存器1的值分别进行无符号比较和有符号比较;
带符号比较结果存入寄存器5,值为00000001。
无符号数比较,结果存入寄存器6,值为00000000。
最后再实现将值存入内存和将值从内存取出。均成功实现。
🚩注:
使用ld.w rd,rj,si12指令的时,若ram中定义的单元数太小,则无法在寄存器中存入数据
根据本篇所书写的指令,可以改为:reg [31:0] mem [0:10000]
得到结果:
📢PS:读者可自行在网上查阅 LoongArch之LA32R版 的相关资料,以便于对LA32R版的指令有进一步的了解。
END
📝因为作者的能力有限,所以文章可能会存在一些错误和不准确之处,恳请大家指出!