关于单周期CPU的设计思路和相关原理分析可以参看个人的另一篇博客,博客链接如下:
单周期CPU设计与实现原理分析
设计一个单周期CPU,该CPU至少能实现以下指令功能操作。指令与格式如下:
(1)add rd , rs, rt (说明:以助记符表示,是汇编指令;以代码表示,是机器指令)
000000 | rs(5位) | rt(5位) | rd(5位) | reserved |
功能:rd←rs + rt。 reserved为预留部分,即未用,一般填“0”。
(2)addi rt , rs ,immediate
000001 | rs(5位) | rt(5位) | immediate(16位) |
功能:rt←rs + (sign-extend)immediate; immediate符号扩展再参加“加”运算。
(3)sub rd , rs , rt
000010 | rs(5位) | rt(5位) | rd(5位) | reserved |
功能:rd←rs - rt
(4)ori rt , rs ,immediate
010000 | rs(5位) | rt(5位) | immediate(16位) |
功能:rt←rs | (zero-extend)immediate; immediate做“0”扩展再参加“或”运算。
(5)and rd , rs , rt
010001 | rs(5位) | rt(5位) | rd(5位) | reserved |
功能:rd←rs & rt; 逻辑与运算。
(6)or rd , rs , rt
010010 | rs(5位) | rt(5位) | rd(5位) | reserved |
功能:rd←rs | rt; 逻辑或运算。
(7)sll rd, rt,sa
011000 | 未用 | rt(5位) | rd(5位) | sa | reserved |
功能:rd<-rt<<(zero-extend)sa, 左移sa位 ,(zero-extend)sa
(8)slti rt, rs,immediate 带符号
011011 | rs(5位) | rt(5位) | immediate(16位) |
功能:if (rs <(sign-extend)immediate) rt =1 else rt=0, 具体请看表2 ALU运算功能表,带符号
(9)sw rt ,immediate(rs) 写存储器
100110 | rs(5位) | rt(5位) | immediate(16位) |
功能:memory[rs+ (sign-extend)immediate]←rt; immediate符号扩展再相加。即将rt寄存器的内容保存到rs寄存器内容和立即数符号扩展后的数相加作为地址的内存单元中。
(10) lw rt , immediate(rs) 读存储器
100111rs(5位) | rt(5位) | immediate(16位) |
功能:rt ← memory[rs + (sign-extend)immediate]; immediate符号扩展再相加。即读取rs寄存器内容和立即数符号扩展后的数相加作为地址的内存单元中的数,然后保存到rt寄存器中。
(11)beq rs,rt,immediate
110000 | rs(5位) | rt(5位) | immediate(16位) |
功能:if(rs=rt) pc←pc + 4 + (sign-extend)immediate <<2 else pc ←pc + 4
特别说明:immediate是从PC+4地址开始和转移到的指令之间指令条数。immediate符号扩展之后左移2位再相加。为什么要左移2位?由于跳转到的指令地址肯定是4的倍数(每条指令占4个字节),最低两位是“00”,因此将immediate放进指令码中的时候,是右移了2位的,也就是以上说的“指令之间指令条数”。
(12)bne rs,rt,immediate
110001 | rs(5位) | rt(5位) | immediate |
功能:if(rs!=rt) pc←pc + 4 + (sign-extend)immediate <<2 else pc ←pc + 4
特别说明:与beq不同点是,不等时转移,相等时顺序执行。
(13)j addr
111000 | addr[27..2] |
功能:pc <-{(pc+4)[31…28],addr[27…2],2{0}}, 无条件跳转。
特别说明:由于MIPS32的指令代码长度占4个字节,所以指令地址二进制数最低2位均为0,将指令地址放进指令代码中时,可省掉!这样,除了最高6位操作码外,还有26位可用于存放地址,事实上,可存放28位地址了,剩下最高4位由pc+4最高4位拼接上。
(14)halt
111111 | 00000000000000000000000000(26位) |
功能:停机; 不改变PC的值,PC保持不变。
单周期CPU指的是一条指令的执行在一个时钟周期内完成,然后开始下一条指令的执行,即一条指令用一个时钟周期完成。电平从低到高变化的瞬间称为时钟上升沿,两个相邻时钟上升沿之间的时间间隔称为一个时钟周期。时钟周期一般也称振荡周期(如果晶振的输出没有经过分频就直接作为CPU的工作时钟,则时钟周期就等于振荡周期。若振荡周期经二分频后形成时钟脉冲信号作为CPU的工作时钟,这样,时钟周期就是振荡周期的两倍。
)
其中,
op:为操作码;
rs:只读。为第1个源操作数寄存器,寄存器地址(编号)是0000011111,001F;
rt:可读可写。为第2个源操作数寄存器,或目的操作数寄存器,寄存器地址(同上);
rd:只写。为目的操作数寄存器,寄存器地址(同上);
sa:为位移量(shift amt),移位指令用于指定移多少位;
funct:为功能码,在寄存器类型指令中(R类型)用来指定指令的功能与操作码配合使用;
immediate:为16位立即数,用作无符号的逻辑操作数、有符号的算术操作数、数据加载(Load)/数据保存(Store)指令的数据地址字节偏移量和分支指令中相对程序计数器(PC)的有符号偏移量;
address:为地址。
:
控制信号名 | 状态“0” | 状态“1” |
Reset | 初始化PC为0 | PC接收新地址 |
PCWre | PC不更改,相关指令:halt | PC更改,相关指令:除指令halt外 |
ALUSrcA | 来自寄存器堆data1输出,相关指令:add、sub、addi、or、and、ori、beq、bne、slti、sw、lw | 来自移位数sa,同时,进行(zero-extend)sa,即 {{27{0}},sa},相关指令:sll |
ALUSrcB | 来自寄存器堆data2输出,相关指令:add、sub、or、and、sll、beq、bne | 来自sign或zero扩展的立即数,相关指令:addi、ori、slti、sw、lw |
DBDataSrc | 来自ALU运算结果的输出,相关指令:add、addi、sub、ori、or、and、slti、sll | 来自数据存储器(Data MEM)的输出,相关指令:lw |
RegWre | 无写寄存器组寄存器,相关指令:beq、bne、sw、halt、j | 寄存器组写使能,相关指令:add、addi、sub、ori、or、and、slti、sll、lw |
InsMemRW | 写指令存储器 | 读指令存储器(Ins. Data) |
mRD | 输出高阻态 | 读数据存储器,相关指令:lw |
mWR | 无操作 | 写数据存储器,相关指令:sw |
RegDst | 写寄存器组寄存器的地址,来自rt字段,相关指令:addi、ori、lw、slti | 写寄存器组寄存器的地址,来自rd字段,相关指令:add、sub、and、or、sll |
ExtSel | (zero-extend)immediate(0扩展),相关指令:ori | (sign-extend)immediate(符号扩展),相关指令:addi、slti、sw、lw、beq、bne |
PCSrc[1..0] | 00:pc←pc+4,相关指令:add、addi、sub、or、ori、and、slti、sll、sw、lw、beq(zero=0)、bne(zero=1); 01:pc←pc+4+(sign-extend)immediate,相关指令:beq(zero=1)、bne(zero=0); 10:pc←{(pc+4)[31:28],addr[27:2],2{0}},相关指令:j; 11:未用 |
|
ALUOp[2..0] | ALU 8种运算功能选择(000-111),看功能表 |
根据个人设计有所修改
):Iaddr:指令存储器地址输入端口
IDataOut:指令存储器数据输出端口(指令代码输出端口)
RW:指令存储器读写控制信号,为0写,为1读
①PC地址计数模块:
CLK: CPU时钟,当CLK上升沿到来时PC值可以发生变化,为输入端口
Reset:重置信号输入端口,Reset为0时重置当前PC地址为0,为1时正常运行
PCWre:判定PC地址是否需要发生变化,0时不更改,1时更改,为输入端口
newAddress:下一个指令地址,为输入端口
PCAddr:PC当前的指令地址,为输出端口
②指令跳转控制模块:
PCSrc:用于判断下一条指令地址的跳转方式(具体功能见上表),为输入端口
CurPC:PC模块输出的当前地址,为输入端口
Immediat:从I型指令中读取的16-bit位宽立即数,为输入端口
addr:从J型指令中读取的26-bit位宽地址偏移量,为输入端口
newAddress:通过PCSrc确定的跳转方式得到的下一个指令地址,为输出端口
DAddr:数据存储器地址输入端口
CLK:CPU时钟,当CLK的下降沿到来时才会将数据写入数据存储器中,为输入端口
mRD:数据存储器读控制信号,为0读
mWR:数据存储器写控制信号,为0写
DataIn:数据存储器数据输入端口
DataOut:数据存储器数据输出端口
①rt,rd寄存器地址选择输入模块:
Select:选择信号输入端口,R、I类型指令时用以选择传入rt或rd寄存器地址
DataIn1:rt寄存器地址,为输入端口
DataIn2:rd寄存器地址,为输入端口
DataOut:选择要写入数据的寄存器地址,为输出端口
②寄存器值输入模块:
Select:选择信号输入端口,调用lw指令时选择从数据存储器读取对应数据
DataIn1:ALU模块输出值,为输入端口
DataIn2:数据存储器内存储的对应位置的值,为输入端口
DataOut:选择要写入寄存器的值,为输出端口
③寄存器管理模块:
WE:写使能信号,为1时,在时钟边沿触发写入,为输入端口
CLK:CPU时钟,当CLK的下降沿到来时才会将数据写入寄存器组中,为输入端口
ReadReg1:rs寄存器地址输入端口
ReadReg2:rt寄存器地址输入端口
WriteReg:将数据写入的寄存器端口,其地址来源rt或rd字段,为输入端口 WriteData:寄存器的数据输入端口,数据来源ALU模块输出或者数据存储器取值
ReadData1:rs寄存器数据输出端口
ReadData2:rt寄存器数据输出端口
①A操作值输入选择模块:
Select:选择信号输入端口,来自ControlUnit模块,用以确定A操作值
DataIn1:来自寄存器组输出的32-bit位宽的rs寄存器值,为输入端口
sa:来自指令存储器输出的sa值,用作ALU功能中左移的左移量,为输入端口
DataOut:选择输入ALU模块的A操作数的值,为输出端口
②B操作值输入选择模块:
Select:选择信号输入端口,来自ControlUnit模块,用以确定B操作值
DataIn1:来自寄存器组输出的32-bit位宽的rt寄存器值,为输入端口
DataIn2:来自位宽扩展单元输出的一个32-bit位宽的立即数扩展值,为输入端口
DataOut:选择输入ALU模块的B操作数的值,为输出端口
③ALU逻辑操作模块:
A:A操作数,来源A操作值输入选择模块输出,为输入端口
B:B操作数,来源B操作值输入选择模块输出,为输入端口
ALUOp:ALU算术逻辑单元的操作选择信号输入端,来自控制单元ControlUnit
result:ALU模块运算结果,为输出端口
zero:运算结果标志,结果为0,则zero=1;否则zero=0,为输出端口
Imm_Number:用作扩展操作的16-bit位宽立即数,为输入端口
ExtSel:扩展选择信号输入端,用作判断扩展方式为带符号扩展或者无符号扩展
Result:32-bit位宽的扩展结果,为输出端口
opCode:指令前六位的6-bit位宽的操作数,来自指令存储器,为输入端口
zero:ALU算术逻辑单元计算结果的零标志位,来自ALU,为输入端口
ExtSel,PCWre,InsMemRW,RegDst,RegWre,ALUOp,PCSrc,ALUSrcA,ALUSrcB,RD,WR,DBDataSrc:根据执行指令得到的CPU各模块的控制信号,具体功能见上表,为输出端口
ALUOp[2…0] | 功能 | 描述 |
---|---|---|
000 | Y = A + B | 加 |
001 | Y = A – B | 减 |
010 | Y = B << A | B左移A位 |
011 | Y = A ∨ B | 或 |
100 | Y = A ∧ B | 与 |
101 | Y=(A < B)?1: 0 | 比较A与B 不带符号 |
110 | Y=(((rega < regb) && (rega[31] == regb[31] )) || ( ( rega[31] ==1 && regb[31] == 0))) ? 1:0 | 比较A与B 带符号 |
111 | Y = A⊕B | 异或 |
从指令功能要求和数据通路图的关系得出以上表3,这样,从表3可以看出各控制信号与相应指令之间的相互关系,根据关系表可以写出各控制信号的逻辑表达式,这样控制单元部分就可实现了。
在此次试验中Basys3板用作单周期CPU结果、PC值、rs,rt寄存器地址和数据的输出。输出内容是通过7端数码管实现。数码管通过使能端的高低电平控制亮和灭(Basys3为共阳,所以低电平有效)。通过控制4个7端数码管相对应使能端按顺序快速地高低变化,实现肉眼可见的同时输出结果。
其中,Basys板设置四个开关,SW15,SW14,T17与V17,其中SW15与SW14作为显示内容的选择,T17用作CPU按键输入,V17用作Reset重置信号输入。指令执行采用单步(按键控制)执行方式,由T17按键提供单个的CPU时钟周期,开关(SW15、SW14)控制选择查看数码管上的相关信息,地址和数据。
地址或数据的输出经以下模块代码转换后接到数码管上。其中:
SW_in = 00:显示 当前 PC值:下条指令PC值
SW_in = 01:显示 RS寄存器地址:RS寄存器数据
SW_in = 10:显示 RT寄存器地址:RT寄存器数据
SW_in = 11:显示 ALU结果输出 :DB总线数据。
电脑一台,Xilinx Vivado 软件一套,Basys3板一块。
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2018/05/25 01:43:15
// Design Name:
// Module Name: PC
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module PC(
input CLK,
input Reset,
input PCWre,
input [31:0] newAddress,
output reg [31:0] PCAddr
);
initial begin
PCAddr = 0;
end
always @(posedge CLK or negedge Reset) begin
if (Reset == 0) begin
PCAddr = 0;
end
else if (PCWre) begin
PCAddr = newAddress;
end
end
endmodule
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2018/05/25 01:28:02
// Design Name:
// Module Name: InstructionMemory
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module InstructionMemory(
input [31:0] IAddr,
input RW,
output reg [31:0] IDataOut
);
reg [7:0] InstMemory [255:0];
initial begin
$readmemb("F:/test.txt", InstMemory);
end
always@(IAddr or RW) begin
if (RW == 1) begin
IDataOut = { InstMemory[IAddr], InstMemory[IAddr + 1], InstMemory[IAddr + 2], InstMemory[IAddr + 3] };
end
$display("InstMem PC", IAddr, " INST: ", IDataOut);
end
endmodule
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2018/05/25 01:35:43
// Design Name:
// Module Name: RegisterFile
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module RegisterFile(
input WE,
input CLK,
input [4:0] ReadReg1, ReadReg2,
input [4:0] WriteReg,
input [31:0] WriteData,
output [31:0] ReadData1, ReadData2
);
reg [31:0] registers[1:31];
assign ReadData1 = ( ReadReg1 == 0 ) ? 0 : registers[ReadReg1];
assign ReadData2 = ( ReadReg2 == 0 ) ? 0 : registers[ReadReg2];
always@( negedge CLK ) begin // д²Ù×÷
if (( WriteReg != 0 ) && ( WE == 1 )) begin
$display("WriteData: ", WriteData, " WriteReg: ", WriteReg);
registers[WriteReg] <= WriteData;
end
end
endmodule
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2018/05/25 01:34:37
// Design Name:
// Module Name: Sign_Zero_Extend
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module Sign_Zero_Extend(
input [15 :0] Imm_Number,
input ExtSel,
output reg [31:0] Result
);
always@( Imm_Number or ExtSel) begin
if (ExtSel == 0 || Imm_Number[15] == 0)
Result = { 16'b0000000000000000, Imm_Number };
else
Result = { 16'b1111111111111111, Imm_Number };
end
endmodule
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2018/05/25 01:51:09
// Design Name:
// Module Name: ALU
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module ALU(
input [31:0] A,
input [31:0] B,
input [2:0] ALUOp,
output reg [31:0] result,
output zero
);
assign zero = ( result == 0 ) ? 1 : 0;
always@( ALUOp or A or B ) begin
case (ALUOp)
3'b000 : result = A + B; // Y = A + B
3'b001 : result = A - B; // Y = A - B
3'b010 : result = B << A; // Y = B << A
3'b011 : result = A | B; // Y = A | B
3'b100 : result = A & B; // Y = A & B
3'b101 : result = (A < B) ? 1 : 0; // Y=£¨A < B£©? 1 : 0
3'b110 : result = (((A < B) && (A[31] == B[31])) || ((A[31] ==1 && B[31] == 0))) ? 1:0; // Y=(((A
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2018/05/25 01:40:45
// Design Name:
// Module Name: DataMemory
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module DataMemory(
input [31:0] DAddr,
input CLK,
input mRD,
input mWR,
input [31:0] DataIn,
output reg [31:0] DataOut
);
reg [7:0] dataMemory [255:0];
always@( mRD or DAddr ) begin
if (mRD == 1) begin
DataOut[7:0] = dataMemory[DAddr + 3];
DataOut[15:8] = dataMemory[DAddr + 2];
DataOut[23:16] = dataMemory[DAddr + 1];
DataOut[31:24] = dataMemory[DAddr];
end
end
always@( negedge CLK ) begin //总是在时钟下降沿到来时触发
if (mWR == 1) begin
dataMemory[DAddr + 3] <= DataIn[7:0];
dataMemory[DAddr + 2] <= DataIn[15:8];
dataMemory[DAddr + 1] <= DataIn[23:16];
dataMemory[DAddr] <= DataIn[31:24];
end
end
endmodule
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2018/05/25 02:13:34
// Design Name:
// Module Name: ControlUnit
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module ControlUnit(
output ExtSel,
output PCWre,
output InsMemRW,
output RegDst,
output RegWre,
output [2:0] ALUOp,
output [1:0] PCSrc,
output ALUSrcA,
output ALUSrcB,
output RD,
output WR,
output DBDataSrc,
input [5:0] opCode,
input zero
);
assign ExtSel = (opCode == 6'b010000 || opCode == 6'b011000) ? 0 : 1;
assign PCWre = (opCode == 6'b111111) ? 0 : 1;
assign InsMemRW = 0;
assign RegDst = (opCode == 6'b000001 || opCode == 6'b010000 || opCode == 6'b100111 || opCode == 6'b011011) ? 0 : 1;
assign RegWre = (opCode == 6'b100110 || opCode == 6'b110000 || opCode == 6'b110001 || opCode == 6'b111000) ? 0 : 1;
assign PCSrc[0] = ((opCode == 6'b110000 && zero == 1) || (opCode == 6'b110001 && zero == 0)) ? 1 : 0;
assign PCSrc[1] = (opCode == 6'b111000) ? 1 : 0;
assign ALUSrcA = (opCode == 6'b011000) ? 1 : 0;
assign ALUSrcB = (opCode == 6'b000001 || opCode == 6'b010000 || opCode == 6'b011011 || opCode == 6'b100110 || opCode == 6'b100111) ? 1 : 0;
assign RD = (opCode == 6'b100111) ? 1 : 0;
assign WR = (opCode == 6'b100110) ? 1 : 0;
assign DBDataSrc = (opCode == 6'b100111) ? 1 : 0;
assign ALUOp[0] = (opCode == 6'b000010 || opCode == 6'b010000 || opCode == 6'b010010 || opCode == 6'b110000 || opCode == 6'b110001) ? 1: 0;
assign ALUOp[1] = (opCode == 6'b010000 || opCode == 6'b010010 || opCode == 6'b011000 || opCode == 6'b011011) ? 1 : 0;
assign ALUOp[2] = (opCode == 6'b010001 || opCode == 6'b011011) ? 1 : 0;
endmodule
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2018/05/26 10:18:34
// Design Name:
// Module Name: Mux_ThreeToOne
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module Mux_ThreeToOne(
input [1:0] PCSrc,
input [31:0] CurPC,
input [31:0] immediate,
input [27:2] addr,
output reg [31:0] newAddress
);
wire [31:0] PCAddr4 = CurPC + 4;
always@( PCSrc or CurPC or addr or immediate ) begin
if ( PCSrc == 2'b00 )
newAddress = PCAddr4;
else if ( PCSrc == 2'b01 )
newAddress = PCAddr4 + immediate * 4;
else if ( PCSrc == 2'b10 )
newAddress = {PCAddr4[31:28], addr[27:2],2'b00};
end
endmodule
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2018/05/25 02:11:51
// Design Name:
// Module Name: Mux_TwoToOne
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module Mux_TwoToOne(
input Select,
input [31:0] DataIn1,
input [31:0] DataIn2,
output reg [31:0] DataOut
);
always@( Select or DataIn1 or DataIn2 ) begin
if ( Select == 0 )
DataOut = DataIn1;
else
DataOut = DataIn2;
end
endmodule
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2018/05/25 02:08:52
// Design Name:
// Module Name: Mux_TwoToOne_A
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module Mux_TwoToOne_A(
input Select,
input [31:0] DataIn1,
input [4:0] sa,
output reg [31:0] DataOut
);
always@( Select or DataIn1 or sa ) begin
if ( Select == 0 )
DataOut = DataIn1;
else
DataOut = { 27'b000000000000000000000000000, sa }; // 对sa进行扩展
end
endmodule
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2018/05/25 02:10:46
// Design Name:
// Module Name: Mux_TwoToOne_Reg
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module Mux_TwoToOne_Reg(
input Select,
input [4:0] DataIn1,
input [4:0] DataIn2,
output reg [4:0] DataOut
);
always@( Select or DataIn1 or DataIn2 ) begin
if ( Select == 0 )
DataOut = DataIn1;
else
DataOut = DataIn2;
end
endmodule
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2018/05/25 02:44:32
// Design Name:
// Module Name: SCPU
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module SCPU(
input CLK, // 时钟信号
input Reset, // 置零信号
output [31:0] CurPC, // 当前指令地址
output [31:0] newaddress, // 下一个指令地址
output [31:0] instcode, // rs,rt寄存器所在指令
output [31:0] Reg1Out, // 寄存器组rs寄存器的值
output [31:0] Reg2Out, // 寄存器组rt寄存器的值
output [31:0] ALU_Out, // ALU的result输出值
output [31:0] WriteData // DB总线值
);
wire ExtSel; // 位扩展信号,1为符号扩展,0为0扩展
wire PCWre; // PC工作信号,0不更改,1更改
wire InsMemRW; // 指令寄存器信号,0为写,1为读
wire RegDst; // 指令读取时判断是rt还是rd进入寄存器组的写数据端,0为rt,1为rd
wire RegWre; // 寄存器组是否需要写功能,0为无写功能,1为些功能
wire [2:0] ALUOp; // ALU8种运算功能选择
wire [1:0] PCSrc; // PC正常+4还是要跳转,0为正常+4,1为跳转
wire ALUSrcA; // 寄存器组Data1的输出,0为寄存器本身输出,1为指令码的最后16位立即数
wire ALUSrcB; // 寄存器组Data2的输出,0位本身的输出,1为扩展后的立即数
wire RD; // 读数据存储器功能,0时读取
wire WR; // 写数据存储器功能,1时写
wire DBDataSrc; // 决定将什么数据传入寄存器组Write Data端,0为ALU结果,1为存储器
wire [4:0] WriteRegAddr; // 寄存器组Write Reg输入端
wire [31:0] ALU_Input_A; // ALU的A输入端
wire [31:0] ALU_Input_B; // ALU的B输入端
wire zero; // ALU的zero输出
wire [31:0] MemOut; // 存储器的输出
wire [31:0] Ext_Imm; // 位扩展后的立即数
// Mux_ThreeToOne( PCSrc, CurPC, immediate, addr, newAddress)
Mux_ThreeToOne my_Mux_ThreeToOne( PCSrc, CurPC, Ext_Imm, instcode[25:0], newaddress );
// PC( CLK, Reset, PCWre, newAddress, PCAddr )
PC my_PC( CLK, Reset, PCWre, newaddress, CurPC );
// ALU( Reg1, Reg2, ALUOp, result, zero )
ALU my_ALU( ALU_Input_A, ALU_Input_B, ALUOp, ALU_Out, zero );
// DataMemory( DAddr, CLK, RD, WR, DataIn, DataOut)
DataMemory my_DataMemory( ALU_Out, CLK, RD, WR, Reg2Out, MemOut );
// Sign_Zero_Extend( Imm_Number, ExtSel, Result )
Sign_Zero_Extend my_Sign_Zero_Extend( instcode[15:0], ExtSel, Ext_Imm );
// Mux_TwoToOneReg( Select, DataIn1, DataIn2, DataOut )
Mux_TwoToOne_Reg my_Mux_TwoToOneReg( RegDst, instcode[20:16], instcode[15:11], WriteRegAddr );
// Mux_TwoToOne( Select, DataIn1, DataIn2, DataOut )
Mux_TwoToOne_A my_Mux_TwoToOne_For_ALU_InputA( ALUSrcA, Reg1Out, instcode[10:6], ALU_Input_A );
Mux_TwoToOne my_Mux_TwoToOne_For_ALU_InputB( ALUSrcB, Reg2Out, Ext_Imm, ALU_Input_B );
Mux_TwoToOne my_Mux_TwoToOne_For_RegisterFile_WriteData( DBDataSrc, ALU_Out, MemOut, WriteData );
// RegisterFile( RegWre, CLK, Reg1, Reg2, WriteReg, WriteData, DataOut1, DataOut2 )
RegisterFile my_RegisterFile( RegWre, CLK, instcode[25:21], instcode[20:16], WriteRegAddr, WriteData, Reg1Out, Reg2Out );
// ControlUnit( ExtSel, PCWre, InsMemRW, RegDst, RegWre, ALUOp, PCSrc, ALUSrcB, RD, WR, DBDataSrc, opCode, zero )
ControlUnit my_ControlUnit( ExtSel, PCWre, InsMemRW, RegDst, RegWre, ALUOp, PCSrc, ALUSrcA, ALUSrcB, RD, WR, DBDataSrc, instcode[31:26], zero );
// InstructionMemory( CurPC, instMemRW, instcode )
InstructionMemory my_InstructionMemory( CurPC, InsMemRW, instcode );
endmodule
至此,单周期CPU的所有模块完成,其中SCPU.v是顶层文件,我们最后写一个仿真代码对单周期CPU进行仿真测试,经检查已知结果正确,仿真代码如下:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2018/05/25 02:53:19
// Design Name:
// Module Name: SCPU_sim
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module SCPU_sim;
reg CLK; // 时钟信号
reg Reset; // 置零信号
SCPU my_SCPU( .CLK(CLK),
.Reset(Reset)
);
always #30 CLK = !CLK; // 60ns为一周期
initial begin
CLK = 0;
Reset = 0;
#90;
Reset = 1;
end
endmodule
本部分是实现在Basys3实验板上运行的相关代码,因为时钟设置原因(具体原因已在另一篇博客中进行讲解说明
),在按键控制操作上存在着半个周期的延迟,这一点不影响实验板实现结果的正确性,但是主观上会觉得有些别扭,所以建议大家以参考为主,只要抓住该部分代码设计的核心是确定合适的数码管扫描频率这一点,相信实现过程并不困难。
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2018/05/27 11:21:21
// Design Name:
// Module Name: SegLED
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module SegLED(
input [3:0] Store,
input Reset,
output reg [7:0] Out
);
always @( Store or Reset) begin
if(Reset == 0) begin
Out = 8'b11111110;
end
else begin
case (Store)
4'b0000 : Out = 8'b10000001; //0£»'0'-ÁÁµÆ£¬'1'-ϨµÆ
4'b0001 : Out = 8'b11001111; //1
4'b0010 : Out = 8'b10010010; //2
4'b0011 : Out = 8'b10000110; //3
4'b0100 : Out = 8'b11001100; //4
4'b0101 : Out = 8'b10100100; //5
4'b0110 : Out = 8'b10100000; //6
4'b0111 : Out = 8'b10001111; //7
4'b1000 : Out = 8'b10000000; //8
4'b1001 : Out = 8'b10000100; //9
4'b1010 : Out = 8'b10001000; //A
4'b1011 : Out = 8'b11100000; //b
4'b1100 : Out = 8'b10110001; //C
4'b1101 : Out = 8'b11000010; //d
4'b1110 : Out = 8'b10110000; //E
4'b1111 : Out = 8'b10111000; //F
default : Out = 8'b0000_0000; //no light
endcase
end
end
endmodule
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2018/05/27 23:27:49
// Design Name:
// Module Name: key_fangdou
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module key_fangdou(clk,key_in,key_out);
parameter SAMPLE_TIME = 5000;
input clk;
input key_in;
output key_out;
reg [21:0] count_low;
reg [21:0] count_high;
reg key_out_reg;
always @(posedge clk)
if(key_in ==1'b0)
count_low <= count_low + 1;
else
count_low <= 0;
always @(posedge clk)
if(key_in ==1'b1)
count_high <= count_high + 1;
else
count_high <= 0;
always @(posedge clk)
if(count_high == SAMPLE_TIME)
key_out_reg <= 1;
else if(count_low == SAMPLE_TIME)
key_out_reg <= 0;
assign key_out = !key_out_reg;
endmodule
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2018/05/26 13:21:45
// Design Name:
// Module Name: Basys3_Out
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module Basys3_Out(
input CLK,
input [1:0] SW, // 选择输出信号
input Reset, // 重置按钮
input Button, // 单脉冲
output reg[3:0] AN, // 数码管位选择信号
output [7:0] Out // 数码管输入信号
);
reg [16:0] showCounter;
parameter T1MS = 100000;
wire [31:0] ALU_Out; // ALU的result输出值
wire [31:0] CurPC;
wire [31:0] WriteData; // DB总线值
wire [31:0] Reg1Out; // 寄存器组rs寄存器的值
wire [31:0] Reg2Out; // 寄存器组rt寄存器的值
wire [31:0] instcode;
wire myCLK;
reg [3:0] store; // 记录当前要显示位的值
wire [31:0] newAddress;
SCPU my_SCPU(myCLK, Reset, CurPC, newAddress, instcode, Reg1Out, Reg2Out, ALU_Out, WriteData);
key_fangdou my_key_fangdou(CLK, Button, myCLK);
initial begin
showCounter <= 0;
AN <= 4'b0111;
end
always@ (posedge CLK)
begin
if(Reset == 0) begin
showCounter <= 0;
AN <= 4'b0000;
end else begin
showCounter <= showCounter + 1;
if(showCounter == T1MS)
begin
showCounter <= 0;
case(AN)
4'b1110 : begin
AN <= 4'b1101;
end
4'b1101 : begin
AN <= 4'b1011;
end
4'b1011 : begin
AN <= 4'b0111;
end
4'b0111 : begin
AN <= 4'b1110;
end
4'b0000 : begin
AN <= 4'b0111;
end
endcase
end
end
end
SegLED led(store, Reset, Out);
always@ (myCLK) begin
case(AN)
4'b1110 : begin
case(SW)
2'b00: store <= newAddress[3:0];
2'b01: store <= Reg1Out[3:0];
2'b10: store <= Reg2Out[3:0];
2'b11: store <= WriteData[3:0];
endcase
end
4'b1101 : begin
case(SW)
2'b00: store <= newAddress[7:4];
2'b01: store <= Reg1Out[7:4];
2'b10: store <= Reg2Out[7:4];
2'b11: store <= WriteData[7:4];
endcase
end
4'b1011 : begin
case(SW)
2'b00: store <= CurPC[3:0];
2'b01: store <= instcode[24:21];
2'b10: store <= instcode[19:16];
2'b11: store <= ALU_Out[3:0];
endcase
end
4'b0111 : begin
case(SW)
2'b00: store <= CurPC[7:4];
2'b01: store <= { 3'b000,instcode[25]};
2'b10: store <= { 3'b000,instcode[20]};
2'b11: store <= ALU_Out[7:4];
endcase
end
endcase
end
endmodule