前言:本章内容主要是演示在vivado下利用Verilog语言进行单周期简易CPU的设计。一步一步自己实现模型机的设计。本章先介绍单周期简易CPU中基本时序逻辑部件设计。
💻环境:一台内存4GB以上,装有64位Windows操作系统和Vivado 2017.4以上版本软件的PC机。
💎本章所采用的指令为LoongArch之LA32R版
目录
Ⅰ前置知识
0x00 32位寄存器DR
0x01 32位的程序计数器PC
0x02 通用寄存器堆Registers
0x03 32位RAM存储器
Ⅱ Verilog实现
0x00 32位寄存器DR
0x01 32位的程序计数器PC
0x02 通用寄存器堆Registers
0x03 32位RAM存储器
Ⅰ前置知识
0x00 32位寄存器DR
32位寄存器DR,全称为数据寄存器(Data Register),是计算机中常见的寄存器之一。它用于存储数据,是CPU中的一个重要组成部分。
本章介绍的32位寄存器DR模块的参考电路框图如下:
该模块的功能及引脚信号说明如下:
信号名称 | 功能说明 |
DataIn | 32位数据输入信号 |
DataOut | 32位数据输出信号 |
clk | 时钟信号 |
WE | 数据存入信号。当WE为1且在clk上升沿到来时,数据存入寄存器;当WE为0时,寄存器输出维持不变。 |
0x01 32位的程序计数器PC
32位程序计数器(Program Counter,PC)是计算机中一种重要的寄存器,它通常存储下一条指令的内存地址,是CPU能够顺利地执行指令的关键。
PC寄存器在CPU中被设计为自加的寄存器,它会在一个时钟周期中自动递增,从而指向下一条指令的地址,使得CPU可以连续地执行一系列指令。在程序执行过程中,PC寄存器会不断地指向下一条要执行的指令,直到程序结束或出现异常情况。
在现代计算机中,PC寄存器的位数一般为32位。当程序执行中遇到条件分支、循环等需要改变执行流程的指令时,CPU会根据条件跳转到不同的地址,PC寄存器也就被设置为相应的地址,从而改变了程序的执行路径。
本章介绍的32位的程序计数器PC模块的参考电路框图如下:
该模块的功能及引脚信号说明如下:
信号名称 | 功能说明 |
rst | PC异步清零信号,高电平有效,即:rst为1时,PCdata =0 |
clk | 时钟信号 |
offset | 32位偏移量 |
pc_inc | 自增控制信号,与clk上升沿配合工作。 在clk上升沿时刻,当pc_inc=1时,PCdata =原PCdata +1;当pc_inc=0时,PCdata =原PCdata +offset。 |
PCdata | 32位数据输出信号 |
0x02 通用寄存器堆Registers
通用寄存器堆 (Registers) 是计算机体系结构中的一种硬件组件,用于存储和操作数据。它通常由多个独立的寄存器组成,每个寄存器都有固定的位宽。寄存器堆用于执行各种计算机指令和数据传输操作。
本章介绍的通用寄存器堆Registers的参考电路框图如下:
该模块的功能及引脚信号说明如下:
信号名称 | 功能说明 |
busA和busB | 两路32位数据输出信号 |
Ra(5位) | 读寄存器编号输入信号,该编号指定的寄存器的值经过“取数延迟”后,输出到busA |
Rb(5位) | 读寄存器编号输入信号,该编号指定的寄存器的值经过“取数延迟”后,输出到busB |
Rw(5位) | 写寄存器编号输入信号,该编号指定的数据要写入哪个寄存器 |
busW | 32位数据输入信号 |
clk | 写操作时钟控制信号,上升沿有效 |
RegWr | 写使能控制信号,clk上升沿时刻,若RegWr为1,则busW上的数据被存入Rw指定的寄存器中 |
🚩注:
(1)寄存器堆的读操作不受clk控制;
(2)0号寄存器的值恒为0,不受写操作的影响。
0x03 32位RAM存储器
32位RAM存储器是一种具有32位数据线的随机存取存储器。RAM (Random Access Memory) 指的是随机存取存储器,它允许随机访问存储器中的任何位置,而无需按照顺序访问。32位RAM存储器的数据线有32条,可以同时传送32位二进制数据,因此它能够以非常快的速度读取和写入数据。
本章介绍的32位RAM存储器参考电路框图如下:
该模块的功能及引脚信号说明如下:
信号名称 | 功能说明 |
addr | 32位地址总线,用于传送地址,以便按地址访问存储单元。 |
data_in | 32位数据输入总线 |
data_out | 32位数据输出总线 |
clk | 时钟信号,上升沿有效 |
MemWrEn | 写使能信号。 当MemWrEn为0时,数据从addr地址端口指定的内存单元读出。 当MemWrEn为1时,配合clk时钟信号工作,在clk上升沿,数据存入由addr地址端口指定的内存单元。 |
Ⅱ Verilog实现
0x00 32位寄存器DR
设计一个32位的寄存器DR,利用Verilog HDL完成建模设计
设计代码:
module Register32 (
input [31:0] DataIn,
output reg [31:0] DataOut,
input clk,
input WE
);
always @(posedge clk) begin
if (WE) begin
DataOut <= DataIn;
end
end
endmodule
在Vivado中点击”RTL ANALYSIS->Open Elaborated Design”,可以查看综合得到的 32位寄存器DR的逻辑电路,如图所示:
仿真代码:
module Register32_Test;
reg [31:0] DataIn;
wire [31:0] DataOut;
reg clk;
reg WE;
Register32 dut (
.DataIn(DataIn),
.DataOut(DataOut),
.clk(clk),
.WE(WE)
);
always begin
#5 clk = ~clk; // 时钟周期为10个时间单位
end
initial begin
clk = 0;
WE = 0;
DataIn = 32'h11111111; // 设置输入数据
#20 WE = 1; // 在20个时间单位后,使WE为1,触发数据存入
#20 WE = 0; // 在另外20个时间单位后,使WE为0,保持输出稳定
#20 DataIn = 32'h11101011; // 修改输入数据
#20 WE = 1; // 再次触发数据存入
#50 $finish; // 结束仿真
end
endmodule
仿真结果:
❓思考:
读者可以思考一个问题,刚开始时为什么DataOut为xxx态?
0x01 32位的程序计数器PC
设计代码:
module ProgramCounter(
input rst,
input clk,
input [31:0] offset,
input pc_inc,
output reg [31:0] PCdata
);
always @(posedge clk or posedge rst) begin
if (rst) begin
PCdata <= 32'd0;
end else begin
if (pc_inc) begin
PCdata <= PCdata + 32'd1;
end else begin
PCdata <= PCdata + offset;
end
end
end
endmodule
在Vivado中点击”RTL ANALYSIS->Open Elaborated Design”,可以查看综合得到的32位的程序计数器PC的逻辑电路,如图所示:
仿真代码:
module ProgramCounter_tb;
reg rst;
reg clk;
reg [31:0] offset;
reg pc_inc;
wire [31:0] PCdata;
ProgramCounter dut (
.rst(rst),
.clk(clk),
.offset(offset),
.pc_inc(pc_inc),
.PCdata(PCdata)
);
initial begin
rst = 1;
clk = 0;
offset = 32'h00000001;
pc_inc = 0;
#10;
rst = 0;
#10;
offset = 32'h00000002;
pc_inc = 1;
#10;
pc_inc = 0;
#10;
$display("PCdata = %h", PCdata);
#10;
$finish;
end
always #5 clk = ~clk;
endmodule
仿真结果:
调整进制后可观察到下列波形
🚩注: 若不知道如何调整进制,可翻看本专栏的第一篇博客:
【单周期CPU】LoongArch | 立即数扩展模块Ext | 32位算术逻辑运算单元(ALU)_流继承的博客-CSDN博客
0x02 通用寄存器堆Registers
设计代码:
module Registers(
input [4:0] Ra,
input [4:0] Rb,
input [4:0] Rw,
input [1:0] busW,
input clk,
input RegWr,
output reg [31:0] busA,
output reg [31:0] busB
);
reg [31:0] regfile [0:31];
always @(*) begin
busA = regfile[Ra];
busB = regfile[Rb];
end
always @(posedge clk) begin
if (RegWr) begin
if (Rw != 0)
regfile[Rw] <= busW;
end
end
initial begin
for (integer i = 0; i <= 31; i = i + 1)
regfile[i] = 32'h0;
end
endmodule
在Vivado中点击”RTL ANALYSIS->Open Elaborated Design”,可以查看综合得到的通用寄存器堆Registers的逻辑电路,如图所示:
仿真代码:
module Registers_Test;
reg [4:0] Ra;
reg [4:0] Rb;
reg [4:0] Rw;
reg [1:0] busW;
reg clk;
reg RegWr;
wire [31:0] busA;
wire [31:0] busB;
Registers dut (
.Ra(Ra),
.Rb(Rb),
.Rw(Rw),
.busW(busW),
.clk(clk),
.RegWr(RegWr),
.busA(busA),
.busB(busB)
);
initial begin
clk = 0;
RegWr = 0;
Ra = 0;
Rb = 0;
Rw = 0;
busW = 2'b01;
#25;
Ra = 2;
Rb = 4;
#25;
$display("busA = %h, busB = %h", busA, busB);
Rw = 6;
busW = 2'b11;
RegWr = 1;
#50;
$display("busA = %h, busB = %h", busA, busB);
Ra = 6;
Rb = 0;
#25;
$display("busA = %h, busB = %h", busA, busB);
#10;
$display("busA = %h, busB = %h", busA, busB);
end
always begin
#50;
clk = ~clk;
end
endmodule
仿真结果:
调整进制后可观察到下列波形
0x03 32位RAM存储器
设计代码:
module RAM(
input MemWrEn, input clk,
input [31:0] addr,
input [31:0] data_in, output reg [31:0] data_out
);
reg [31:0] ram [0:31];
always@(posedge clk) begin
if (MemWrEn) ram[addr] <= data_in; end
always@(*) begin
if(MemWrEn==0) data_out <= ram[addr]; end
endmodule
在Vivado中点击”RTL ANALYSIS->Open Elaborated Design”,可以查看综合得到的32位RAM存储器的逻辑电路,如图所示:
仿真代码:
module RAM_sim; reg MemWrEn; reg clk;
reg [31:0] addr;
reg [31:0] data_in;
wire [31:0] data_out;
RAM
RAM1(.MemWrEn(MemWrEn),.clk(clk),
.addr(addr),.data_in(data_in),.data_out(data_out));
initial begin MemWrEn=1; addr=20; data_in=60; #200;
addr=15; data_in=30; #200;
addr=5; data_in=120; #200;
MemWrEn=0; addr=20; #100;
addr=15; #100;
addr=5; #200;
end
initial clk = 0;
always # 50 clk = ~clk; endmodule
仿真结果:
调整进制后可观察到下列波形
❓思考
在完成上述四个模块之后,读者可以思考一下:
1.“如何将数据存入暂存器”、“如何读取暂存器的值”等问题。
2.“如何控制PC的值自增或加偏移量”、“如何给PC清0”等问题。
3.“如何指定要写入数据的寄存器编号”、“如何保证0号寄存器的值不受写入操作的影响而始终保持为0”,“如何指定从哪个寄存器读出数据”和“是否具有将所有寄存器一次性全部清0的功能”等问题。
4.“如何指定要写入数据的内存单元地址” “如何指定从哪个内存单元读出数据”和“是否具有将所有内存单元一次性全部清0的功能”等问题。
END
📝因为作者的能力有限,所以文章可能会存在一些错误和不准确之处,恳请大家指出!