实验一 : 单周期CPU设计与实现
一、 实验目的
(1) 掌握单周期CPU数据通路图的构成、原理及其设计方法;
(2) 掌握单周期CPU的实现方法,代码实现方法;
(3) 认识和掌握指令与CPU的关系;
(4) 掌握测试单周期CPU的方法。
二、 实验内容
设计一个单周期CPU,该CPU至少能实现以下指令功能操作。指令与格式如下:
==> 算术运算指令
(1)add rd , rs, rt
000000 rs(5位) rt(5位) rd(5位) 00000 100000
功能:GPR[rd] ← GPR[rs] + GPR[rt]。
(2)sub rd , rs , rt
000000 rs(5位) rt(5位) rd(5位) 00000 100010
功能:GPR[rd] ← GPR[rs] - GPR[rt]。
(3)addiu rt , rs ,immediate
001001 rs(5位) rt(5位) immediate(16位)
功能:GPR[rt] ← GPR[rs] + sign_extend(immediate); immediate做符号扩展再参加“与”运算。
==> 逻辑运算指令
(4)andi rt , rs ,immediate
001100 rs(5位) rt(5位) immediate(16位)
功能:GPR[rt] ← GPR[rs] and zero_extend(immediate);immediate做0扩展再参加“与”运算。
(5)and rd , rs , rt
000000 rs(5位) rt(5位) rd(5位) 00000 100100
功能:GPR[rd] ← GPR[rs] and GPR[rt]。
(6)ori rt , rs ,immediate
001101 rs(5位) rt(5位) immediate(16位)
功能:GPR[rt] ← GPR[rs] or zero_extend(immediate)。
(7)or rd , rs , rt
000000 rs(5位) rt(5位) rd(5位) 00000 100101
功能:GPR[rd] ← GPR[rs] or GPR[rt]。
==>移位指令
(8)sll rd, rt,sa
000000 00000 rt(5位) rd(5位) sa(5位) 000000
功能:GPR[rd] ← GPR[rt] << sa。
==>比较指令
(9) slti rt, rs,immediate 带符号数
001010 rs(5位) rt(5位) immediate(16位)
功能:if GPR[rs] < sign_extend(immediate) GPR[rt] =1 else GPR[rt] = 0。
==> 存储器读/写指令
(10)sw rt , offset (rs) 写存储器
101011 rs(5位) rt(5位) offset(16位)
功能:memory[GPR[base] + sign_extend(offset)] ← GPR[rt]。
(11) lw rt , offset (rs) 读存储器
100011 rs(5位) rt(5位) offset (16位)
功能:GPR[rt] ← memory[GPR[base] + sign_extend(offset)]。
==> 分支指令
(12)beq rs,rt, offset
000100 rs(5位) rt(5位) offset (16位)
功能:if(GPR[rs] = GPR[rt]) pc←pc + 4 + sign_extend(offset)<<2 else pc ←pc + 4
特别说明:offset是从PC+4地址开始和转移到的指令之间指令条数。offset符号扩展之后左移2位再相加。为什么要左移2位?由于跳转到的指令地址肯定是4的倍数(每条指令占4个字节),最低两位是“00”,因此将offset放进指令码中的时候,是右移了2位的,也就是以上说的“指令之间指令条数”。
(13)bne rs,rt, offset
000101 rs(5位) rt(5位) offset (16位)
功能:if(GPR[rs] != GPR[rt]) pc←pc + 4 + sign_extend(offset) <<2 else pc ←pc + 4
(14)bltz rs, offset
000001 rs(5位) 00000 offset (16位)
功能:if(GPR[rs] < 0) pc←pc + 4 + sign_extend (offset) <<2 else pc ←pc + 4。
==>跳转指令
(15)j addr
000010 addr(26位)
功能:PC ← {PC[31:28] , addr , 2’b0},无条件跳转。
说明:由于MIPS32的指令代码长度占4个字节,所以指令地址二进制数最低2位均为0,将指令地址放进指令代码中时,可省掉!这样,除了最高6位操作码外,还有26位可用于存放地址,事实上,可存放28位地址,剩下最高4位由pc+4最高4位拼接上。
==> 停机指令
(16)halt
111111 00000000000000000000000000(26位)
功能:停机;不改变PC的值,PC保持不变。
三、实验原理
单周期CPU指的是一条指令的执行在一个时钟周期内完成,然后开始下一条指令的执行,即一条指令用一个时钟周期完成。电平从低到高变化的瞬间称为时钟上升沿,两个相邻时钟上升沿之间的时间间隔称为一个时钟周期。时钟周期一般也称振荡周期(如果晶振的输出没有经过分频就直接作为CPU的工作时钟,则时钟周期就等于振荡周期。若振荡周期经二分频后形成时钟脉冲信号作为CPU的工作时钟,这样,时钟周期就是振荡周期的两倍。)
CPU在处理指令时,一般需要经过以下几个步骤:
(1) 取指令(IF):根据程序计数器PC中的指令地址,从存储器中取出一条指令,同时,PC根据指令字长度自动递增产生下一条指令所需要的指令地址,但遇到“地址转移”指令时,则控制器把“转移地址”送入PC,当然得到的“地址”需要做些变换才送入PC。
(2) 指令译码(ID):对取指令操作中得到的指令进行分析并译码,确定这条指令需要完成的操作,从而产生相应的操作控制信号,用于驱动执行状态中的各种操作。
(3) 指令执行(EXE):根据指令译码得到的操作控制信号,具体地执行指令动作,然后转移到结果写回状态。
(4) 存储器访问(MEM):所有需要访问存储器的操作都将在这个步骤中执行,该步骤给出存储器的数据地址,把数据写入到存储器中数据地址所指定的存储单元或者从存储器中得到数据地址单元中的数据。
(5) 结果写回(WB):指令执行的结果或者访问存储器中得到的数据写回相应的目的寄存器中。
单周期CPU,是在一个时钟周期内完成这五个阶段的处理。
图1 单周期CPU指令处理过程
MIPS指令的三种格式:
其中,
op:为操作码;
rs:只读。为第1个源操作数寄存器,寄存器地址(编号)是0000011111,001F;
rt:可读可写。为第2个源操作数寄存器,或目的操作数寄存器,寄存器地址(同上);
rd:只写。为目的操作数寄存器,寄存器地址(同上);
sa:为位移量(shift amt),移位指令用于指定移多少位;
funct:为功能码,在寄存器类型指令中(R类型)用来指定指令的功能与操作码配合使用;
immediate:为16位立即数,用作无符号的逻辑操作数、有符号的算术操作数、数据加载(Laod)/数据保存(Store)指令的数据地址字节偏移量和分支指令中相对程序计数器(PC)的有符号偏移量;
address:为地址。
图2 单周期CPU数据通路和控制线路图
图2是一个简单的基本上能够在单周期CPU上完成所要求设计的指令功能的数据通路和必要的控制线路图。其中指令和数据各存储在不同存储器中,即有指令存储器和数据存储器。访问存储器时,先给出内存地址,然后由读或写信号控制操作。对于寄存器组,先给出寄存器地址,读操作时不需要时钟信号,输出端就直接输出相应数据;而在写操作时,在 WE使能信号为1时,在时钟边沿触发将数据写入寄存器。图中控制信号作用如表1所示,表2是ALU运算功能表。
表1 控制信号的作用
控制信号名 | 状态“0” | 状态“1” |
---|---|---|
Reset | 初始化PC为0 | PC接收新地址 |
PCWre | PC不更改,相关指令:halt | PC更改,相关指令:除指令halt外 |
ALUSrcA | 来自寄存器堆data1输出,相关指令:add、sub、addiu、or、and、andi、ori、slti、beq、bne、bltz、sw、lw | 来自移位数sa,同时,进行(zero-extend)sa,即 {{27{1’b0}},sa},相关指令:sll |
ALUSrcB | 来自寄存器堆data2输出,相关指令:add、sub、or、and、beq、bne、bltz | 来自sign或zero扩展的立即数,相关指令:addi、andi、ori、slti、sw、lw |
DBDataSrc | 来自ALU运算结果的输出,相关指令:add、addiu、sub、ori、or、and、andi、slti、sll | 来自数据存储器(Data MEM)的输出,相关指令:lw |
RegWre | 无写寄存器组寄存器,相关指令:beq、bne、bltz、sw、halt | 寄存器组写使能,相关指令:add、addiu、sub、ori、or、and、andi、slti、sll、lw |
InsMemRW | 写指令存储器 | 读指令存储器(Ins. Data) |
mRD | 输出高阻态 | 读数据存储器,相关指令:lw |
mWR | 无操作 | 写数据存储器,相关指令:sw |
RegDst | 写寄存器组寄存器的地址,来自rt字段,相关指令:addiu、andi、ori、slti、lw | 写寄存器组寄存器的地址,来自rd字段,相关指令:add、sub、and、or、sll |
ExtSel | (zero-extend)immediate(0扩展),相关指令:addiu、andi、ori | (sign-extend)immediate(符号扩展),相关指令:slti、sw、lw、beq、bne、bltz |
PCSrc[1…0] | 00:pc<-pc+4 01:pc<-pc+4+(sign-extend)immediate,10:pc<-{(pc+4)[31:28],addr[27:2],2’b00};11:未用 | # |
ALUOp[2…0] | ALU 8种运算功能选择(000-111) | # |
相关部件及引脚说明:
Instruction Memory:指令存储器,
Iaddr,指令存储器地址输入端口
IDataIn,指令存储器数据输入端口(指令代码输入端口)
IDataOut,指令存储器数据输出端口(指令代码输出端口)
RW,指令存储器读写控制信号,为0写,为1读
Data Memory:数据存储器,
Daddr,数据存储器地址输入端口
DataIn,数据存储器数据输入端口
DataOut,数据存储器数据输出端口
/RD,数据存储器读控制信号,为0读
/WR,数据存储器写控制信号,为0写
Register File:寄存器组
Read Reg1,rs寄存器地址输入端口
Read Reg2,rt寄存器地址输入端口
Write Reg,将数据写入的寄存器端口,其地址来源rt或rd字段
Write Data,写入寄存器的数据输入端口
Read Data1,rs寄存器数据输出端口
Read Data2,rt寄存器数据输出端口
WE,写使能信号,为1时,在时钟边沿触发写入
ALU: 算术逻辑单元
result,ALU运算结果
zero,运算结果标志,结果为0,则zero=1;否则zero=0
sign,运算结果标志,结果最高位为0,则sign=0,正数;否则,sign=1,负数
表2 ALU运算功能表
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 | 异或 |
需要说明的是以上数据通路图是根据要实现的指令功能的要求画出来的,同时,还必须确定ALU的运算功能(当然,以上指令没有完全用到提供的ALU所有功能,但至少必须能实现以上指令功能操作)。从数据通路图上可以看出控制单元部分需要产生各种控制信号,当然,也有些信号必须要传送给控制单元。从指令功能要求和数据通路图的关系得出以上表1,这样,从表1可以看出各控制信号与相应指令之间的相互关系,根据这种关系就可以得出控制信号与指令之间的关系表(留给学生完成),再根据关系表可以写出各控制信号的逻辑表达式,这样控制单元部分就可实现了。
指令执行的结果总是在时钟下降沿保存到寄存器和存储器中,PC的改变是在时钟上升沿进行的,这样稳定性较好。另外,值得注意的问题,设计时,用模块化的思想方法设计,关于ALU设计、存储器设计、寄存器组设计等等,也是必须认真考虑的问题。
四、实验设备
PC机一台,BASYS 3 实验板一块,Xilinx Vivado 开发软件一套。
关于测试单周期CPU的简单方法
1、测试程序段
地址 汇编程序 指令代码
op(6) rs(5) rt(5) rd(5)/immediate(16)
0x00000000 addiu $1,$0,8 001001 00000 00001 0000000000001000
0x00000004 ori $2,$0,2 001101 00000 00010 0000000000000010
0x00000008 add $3,$2,$1 000000 00010 00001 0001100000100000
0x0000000C sub $5,$3,$2 000000 00011 00010 0010100000100010
0x00000010 and $4,$5,$2 000000 00101 00010 0010000000100100
0x00000014 or $8,$4,$2 000000 00100 00010 0100000000100101
0x00000018 sll $8,$8,1 000000 00000 01000 0100000001000000
0x0000001C bne $8,$1,-2 000101 01000 00001 1111111111111110
0x00000020 slti $6,$2,4 001010 00010 00110 0000000000000100
0x00000024 slti $7,$6,0 001010 00110 00111 0000000000000000
0x00000028 addiu $7,$7,8 001001 00111 00111 0000000000001000
0x0000002C beq $7,$1,-2 110000 00111 00001 1111111111111110
0x00000030 sw $2,4($1) 101011 00001 00010 0000000000000100
0x00000034 lw $9,4($1) 100011 00001 01001 0000000000000100
0x00000038 addiu $10,$0,-2 001001 00000 01010 1111111111111110
0x0000003C addiu $10,$10,1 001001 01010 01010 0000000000000001
0x00000040 bltz $10,-2 000001 01010 00000 1111111111111110
0x00000044 andi $11,$2,2 001000 00010 01011 0000000000000010
0x00000048 j 0x00000050 000010 00000 00000 0000000000010100
0x0000004C or $8,$4,$2 000000 00100 00010 0000000000001000
0x00000050 halt 111111 00000 00000 0000000000000000
1、将指令代码初始化到指令存储器中,直接写入。
2、初始化PC的值,也就是以上程序段首地址PC=0x00000000,以上程序段从0x00000000地址开始存放。
3、运行Xilinx Vivado进行仿真,看波形。
五.实验过程与结果
在设计单周期CPU之前,需要先熟悉其数据通路和控制线路图,从图中我们可以看到,CPU被分成了七个主模块,分别是:程序计数器(PC)、指令寄存器(InsMem)、数据存储器(DataMem)、算数逻辑单元(ALU)、寄存器组(Register File)、符号和零扩展器(Sign、Zero extend)、控制单元(Control Unit)、数据选择器(MUX),其次,在控制单元产生的各种控制信号需要根据控制信号表(表1)产生相应的信号值。
在设计单周期CPU时,首先需要搭建程序计数器以产生相应的地址,同时,需要搭建指令存储器,将指令存到指令存储器后在控制单元对指令进行译码,主要是提取指令的op码和func码字段,其中R型指令只需要func码字段即可判断该指令执行的操作,以上模块搭建好之后,再搭建算术逻辑单元、寄存器组、数据存储器、符号和零扩展器和数据选择器(MUX),这些模块需要也需要根据控制单元给出的信号进行操作。
以下针对各模块以及控制信号展开详细的说明:
- 程序计数器(PC)
PC的改变是在时钟上升沿进行的,每当时钟上升沿到来的时候,PC根据控制单元发出的信号进行相应的改变,即PCWre与PCSrc,PCWre控制PC是否继续工作,若为1,则继续更新地址,反之则保持原值不变,PCSrc根据指令的不同控制PC进行更新不同的地址,决定以PC+4作为下一个地址,或者以跳转后的地址作为下一地址。当Reset为0的时候PC需要进行复位置零。指令执行的结果总是在时钟下降沿保存到寄存器和存储器中.
module Program_Counter(
input Reset,PCWre,CLK,
input [31:0] new_addr,
output reg [31:0] PC
);
initial begin
PC = 0;
end
always@(posedge CLK or negedge Reset)
begin
if(Reset==0)
begin
PC = 0;
end
else
if(PCWre==1)
begin
PC = new_addr;
end
end
endmodul
- 指令寄存器(InsMem)
指令存储器在初始化阶段将测试指令保存在寄存器数组中,由RW读/写控制信号来控制是否对指令寄存器进行读或者写操作。
module Ins_Memory(
input [31:0] Iaddr,
input RW,
output reg [31:0] IDataOut
);
reg [7:0] Ins_mem[255:0];
initial
begin
$readmemb("C:/Users/maxwell/Desktop/Instructions.txt",Ins_mem);
end
always@(Iaddr or RW)
begin
if(RW) //writeable
begin
IDataOut = {Ins_mem[Iaddr],Ins_mem[Iaddr+1],Ins_mem[Iaddr+2],Ins_mem[Iaddr+3]};
end
end
endmodule
- 寄存器组(Register File)
寄存器组对指令中给出的地址rs、rt、rd字段进行读或者写操作,其中每当时钟下降沿到来的时候,根据给出的地址对寄存器进行写操作。
module Register_File(
input [4:0] rs,
input [4:0] rt,
input [4:0] Write_Reg,
input WE,CLK,
input [31:0] Write_Data,
output [31:0] Read_Data1,
output [31:0] Read_Data2
);
reg [31:0] registers [0:31];
integer i;
initial begin
for (i = 0; i < 32; i = i+1) registers[i] <= 0;
end
assign Read_Data1 = rs?registers[rs]:0;
assign Read_Data2 = rt?registers[rt]:0;
always@(negedge CLK )
begin
if(WE&&Write_Reg) begin
registers[Write_Reg] = Write_Data;
end
end
endmodule
- 数据存储器(DataMem)
存储器采用大端存储方式,存储单元宽度为8位,其中Daddr为取值地址,当读使能mRD为1时,从存储器中读取值,当时钟下降沿到来时,并且写使能mWR为1时,将输入的数据写入存储器中,同样也是以存储单元宽度8位来写。
`timescale 1ns / 1ps
module Data_Memory(
input CLK,
input [31:0]Daddr,
input [31:0]DataIn,
input mRD,mWR,
output reg [31:0]DataOut
);
reg [7:0]mem[255:0];
integer i;
initial begin
for(i=0;i<256;i=i+1)
mem[i]<=0;
end
always@(Daddr or mRD)
begin
if(mRD==1)
begin
DataOut[31:24] = mem[Daddr]; //8bits per unit ,big endian
DataOut[23:16] = mem[Daddr+1];
DataOut[15:8] = mem[Daddr+2];
DataOut[7:0] = mem[Daddr+3];
end
end
always@(negedge CLK)
begin
if(mWR==1)
begin
mem[Daddr] <= DataIn[31:24];
mem[Daddr+1] <= DataIn[23:16];
mem[Daddr+2] <= DataIn[15:8];
mem[Daddr+3] <= DataIn[7:0];
end
end
endmodule
- 符号和零扩展器(Sign、Zero extend)
module Extension(
input ExtSel,
input [15:0]immediate,
output [31:0] res
);
assign res = {ExtSel&&immediate[15]?16'hffff:16'h0000,immediate};
endmodule
- 算数逻辑单元(ALU)
module ALU(
input [31:0] A,B,
input [2:0] ALUOp,
output reg [31:0]result,
output zero,sign
);
always@(*)
begin
case(ALUOp)
3'b000:result = A+B;
3'b001:result = A-B;
3'b010:result = B<<A;
3'b011:result = A|B;
3'b100:result = A&B;
3'b101:result = A<B?1:0;
3'b110:result = ((A<B&&A[31]==B[31])||(A[31]==1&&B[31]==0)?1:0);
3'b111:result = A^B;
default: result = 0;
endcase
end
assign zero = result==0;
assign sign = result[31]==1;
endmodule
- 控制单元(Control Unit)
module Control_Unit(
input zero,sign,
input [5:0]op,
input [5:0]func,
output PCWre,
output RegWre,
output ExtSel,
output InsMemRw,
output DBDataSrc,
output RegDst,
output ALUSrcA,
output ALUSrcB,
output [1:0] PCSrc,
output mRD,
output mWR,
output [2:0] ALUOp
);
parameter halt = 6'b111111;
parameter addiu = 6'b001001;
parameter ori = 6'b001101;
parameter bne =6'b000101;
parameter slti = 6'b001010;
parameter beq = 6'b110000;
parameter sw = 6'b101011;
parameter lw = 6'b100011;
parameter bltz = 6'b000001;
parameter j = 6'b000010;
parameter andi = 6'b001000;
parameter add_func = 6'b100000;
parameter sub_func = 6'b100010;
parameter and_func = 6'b100100;
parameter or_func= 6'b100101;
parameter sll_func = 6'b000000;
parameter add_ = 3'b000;
parameter sub_ = 3'b001;
parameter sll_ = 3'b010;
parameter or_ = 3'b011;
parameter and_ = 3'b100;
parameter slti_ = 3'b110;
wire RegWre_func ;
wire ALUOp_sub;
wire ALUOp_add;
wire ALUOp_and;
wire ALUOp_or;
assign RegWre_func = func==add_func||func==sub_func||func==or_func||func==and_func||func==sll_func;
assign ALUOp_sub = func==sub_func||op==bne||op==beq||op==bltz;
assign ALUOp_add = func==add_func||op==addiu;
assign ALUOp_and = func==and_func||op==andi;
assign ALUOp_or = func==or_func||op==ori;
assign PCWre = op!=halt;
assign ALUSrcA = func==sll_func;
assign ALUSrcB = op==addiu||op==andi||op==ori||op==slti||op==sw||op==lw;
assign DBDataSrc = op==lw;
assign RegWre = RegWre_func||op==addiu||op==ori||op==andi||op==slti||op==lw;
assign InsMemRw = 1;
assign mRD = op==lw;
assign mWR = op==sw;
assign RegDst = func==add_func||func==or_func||func==sub_func||func==sll_func||func==and_func;
assign ExtSel = op!=andi&&op!=ori; //op==slti||op==sw||op==lw||op==beq||op==bne||op==bltz||op==addiu;
assign PCSrc[0] = (op==beq&&zero==1)||(op==bne&&zero==0)||(op==bltz&&sign==1);
assign PCSrc[1] = op==j;
assign ALUOp = ALUOp_add?add_:ALUOp_sub?sub_:ALUOp_or?or_:func==sll_func?sll_:op==slti?slti_:ALUOp_and?and_:111;
endmodule
- 五线二选一数据选择器
module MUX_5b(
input enable, //enable=1:rd enable=0:rt
input [4:0]Data1, //rt [20:16]
input [4:0]Data2, //rd [15:11]
output [4:0] Res
);
assign Res = enable?Data2:Data1;
endmodule
- 三十二线二选一数据选择器
module MUX_32b(
input enable,
input [31:0]Data1,
input [31:0]Data2,
output [31:0] Res
);
assign Res = enable?Data1:Data2;
endmodule
- 单周期CPU顶层模块
module Single_Cycle_CPU(
input CLK,
input Reset,
output [31:0] cur_PC,
output [31:0]new_PCaddr,
output [31:0]Instruction,
output [31:0] Data_rs,
output [31:0] Data_rt,
output [31:0] ALU_Res,
output [31:0] Write_Data
);
wire [4:0] Write_Reg;
wire [31:0] DataOut;
wire [31:0]Ext_Immediate;
wire PCWre;
wire RegWre;
wire ExtSel;
wire InsMemRw;
wire DBDataSrc;
wire RegDst;
wire ALUSrcA;
wire ALUSrcB;
wire mRD;
wire mWR;
wire [2:0] ALUOp;
wire [1:0] PCSrc;
wire zero;
wire sign;
wire [31:0]ALU_inputA;
wire [31:0]ALU_inputB;
wire [31:0] cur_PC_4 = cur_PC + 4;
assign new_PCaddr = (PCSrc==2'b01)?cur_PC_4+(Ext_Immediate<<2):(PCSrc==2'b10)?({cur_PC_4[31:28],Instruction[25:0],2'b00}):cur_PC_4;
Program_Counter pc(.Reset(Reset),.PCWre(PCWre),.CLK(CLK),.new_addr(new_PCaddr),.PC(cur_PC));
Ins_Memory ins_m(.Iaddr(cur_PC),.RW(InsMemRw),.IDataOut(Instruction));
ALU alu(ALU_inputA,ALU_inputB,ALUOp,ALU_Res,zero,sign);
Data_Memory data_m(CLK,ALU_Res,Data_rt,mRD,mWR,DataOut);
Extension extension(ExtSel,Instruction[15:0],Ext_Immediate);
MUX_5b RtorRd_mux_5b(RegDst,Instruction[20:16],Instruction[15:11],Write_Reg);
MUX_32b ALUA_mux_32b(ALUSrcA,{27'b000000000000000000000000000,Instruction[10:6]},Data_rs,ALU_inputA);
MUX_32b ALUB_mux_32b(ALUSrcB,Ext_Immediate,Data_rt,ALU_inputB);
MUX_32b ALUres_mux_32b(DBDataSrc,DataOut,ALU_Res,Write_Data);
Register_File res_file(Instruction[25:21],Instruction[20:16],Write_Reg,RegWre,CLK,Write_Data,Data_rs,Data_rt);
Control_Unit con_unit(zero,sign,Instruction[31:26],Instruction[5:0],PCWre,RegWre,ExtSel,InsMemRw,DBDataSrc,RegDst,ALUSrcA,ALUSrcB,PCSrc,mRD,mWR,ALUOp);
endmodule
六、波形仿真结果
注:Write_Reg为写入的寄存器的地址、Write_Data为写入寄存器的值,ALU_inputA和ALU_inputA分别是输入ALU的两个数;
波形说明:前100ns进行Reset,计数器清零,其周期为100ns。
100ns~200ns为第一条指令 addiu $1,$0,8 ,将寄存器$0与立即数8进行addiu运算,得到的结果为8,写入寄存器$1。
800ns~900ns执行 bne $8,$1,-2 ,将寄存器$8与寄存器$1的值进行比较,不相等则进行分支跳转,此时寄存器$8的值为4,寄存器$1的值为8不相等,进行跳转,转入地址0x00000018。
1400ns~1500ns执行beq $7,$1,-2,将寄存器$7与寄存器$1的值进行比较,相等则进行分支跳转,此时寄存器$7的值为8,寄存器$1的值为8相等,进行跳转,转入地址0x00000028。
- Basys3顶层模块
每个按键周期,4个数码管都必须刷新一次。数码管位控信号AN3-AN0在 1110、1101、1011、0111之间进行跳变,为0时点亮该数码管。
代码中的AN为当前4位数码管的高低电平,例如当AN=1110的时候,即第四位数码管亮,则下一时刻让第三位亮,即AN被赋值为1101。此外,在这里我们把T1MS设为5000,当计数器count达到5000的时候即可执行上述的电平转换,因为人眼有视觉暂留效应,所以,以一定频率进行跳变刷新的数码管在人眼看来是没有在进行刷新的,所以只需要设定好刷新的频率,就可以实现在数码管上显示值。
在CPU内部时钟上升沿到来时,根据SW开关信号与当前点亮的数码管对显示的数值进行选择,以AN=1110,SW=00、以AN=1101,SW=00为例,对于前者来说,此时第四位数码管点亮,SW=00说明显示的是下条指令PC值(我们只考虑PC值的低八位),因此取new_PCaddr的低四位进行显示,对于后者来说,此时第三位数码管点亮,取的应是new_PCaddr的高四位进行显示。
开关SW_in (SW15、SW14)状态情况如下:
SW_in = 00:显示 当前 PC值:下条指令PC值
SW_in = 01:显示 RS寄存器地址:RS寄存器数据
SW_in = 10:显示 RT寄存器地址:RT寄存器数据
SW_in = 11:显示 ALU结果输出 :DB总线数据。
module Basys3(
input button,
input [1:0]SW,
input CLK, //Basys3's Clock
input Reset,
output [7:0]Digit_Out,
output reg[3:0] AN
);
wire [31:0]cur_PC;
wire [31:0] new_PCaddr;
wire [31:0]ALU_Res;
wire [31:0] Write_Data;
wire [31:0]Data_rs;
wire [31:0]Data_rt;
wire[31:0] Instruction;
wire [31:0]DataOut;
wire [4:0]Write_Reg;
wire Single_CLK; //CPU's Clock
parameter T1MS = 5000;
reg [19:0] count ;
reg[3:0] Value;
Single_Cycle_CPU scpu(Single_CLK,Reset,cur_PC,new_PCaddr,ALU_Res,Write_Data,Data_rs, Data_rt,Instruction,DataOut,Write_Reg);
initial begin
count <= 0;
AN <= 4'b0111;
end
Debounce debounce(CLK,button,Single_CLK);
always@(posedge CLK)
begin
if(Reset==0) begin
count<=0;
AN<=4'b0000;
end
else begin
count=count+1;
if(count==T1MS)begin
count<=0;
case(AN)
4'b1110:AN = 4'b1101;
4'b1101:AN = 4'b1011;
4'b1011:AN = 4'b0111;
4'b0111:AN = 4'b1110;
4'b0000:AN = 4'b0111;
endcase
end
end
end
Seg_Led led(Reset,Value,Digit_Out);
always@(posedge Single_CLK)
begin
case(AN)
4'b1110:
begin
case(SW)
2'b00:Value = new_PCaddr[3:0];
2'b01:Value = Data_rs[3:0];
2'b10:Value = Data_rt[3:0];
2'b11:Value = DataOut[3:0];
endcase
end
4'b1101:
begin
case(SW)
2'b00:Value = new_PCaddr[7:4];
2'b01:Value = Data_rs[7:4];
2'b10:Value = Data_rt[7:4];
2'b11:Value = DataOut[7:4];
endcase
end
4'b1011:
begin
case(SW)
2'b00:Value = cur_PC[3:0];
2'b01:Value = Instruction[24:21];
2'b10:Value = Instruction[19:16];
2'b11:Value = ALU_Res[3:0];
endcase
end
4'b0111:
begin
case(SW)
2'b00: Value = cur_PC[7:4];
2'b01: Value = {3'b000,Instruction[25]};
2'b10: Value = {3'b000,Instruction[20]};
2'b11: Value = ALU_Res[7:4];
endcase
end
endcase
end
endmodule
- 消抖模块
由于Basys3采用的是机械按键,CPU工作的单脉冲时钟需要用到按键来操作,而机械按键会产生无法避免的抖动,因此必须进行消抖,以避免错误的信号产生。
具体消抖原理如下:代码中的in_key为按键的电平,在时钟的上升沿记录当前按键的电平信号,当保持为低电平时count_low持续自增,同理,当保持为高电平时count_high持续自增,当达到计数到达5000的时候,将该电平信号取反输出(代码中的KEY为输出信号),这里取反的原因是为了避免第一条指令的运算结果没有被写入寄存器(时钟下降沿时写入)。
modle Debounce(
input CLK,
input in_key,
output KEY
);
parameter T1MS = 5000;
reg [21:0]count_low;
reg [21:0]count_high;
reg out_key;
always@(posedge CLK)
begin
if(in_key==1'b0)
count_low = count_low + 1;
else
count_low = 1'b0;
end
always@(posedge CLK)
begin
if(in_key==1'b1)
count_high = count_high + 1;
else
count_high = 1'b0;
end
always@(posedge CLK)
begin
if(count_low == T1MS) //°´Ï³ÖÐøÊ±¼ä×ã¹»5000ms¼´¿ÉÈ·¶¨Êä³öµçƽ
out_key = 1'b0;
else if(count_high == T1MS)
out_key = 1'b1;
end
assign KEY = !out_key;
endmodule
- 七段数码管译码器
为了将程序计数器的值、ALU运算结果等数值显示在七段数码管上,需要设计一个数码管译码模块,输入为4bit的二进制数值(即十进制的0~15),输出为8bit的二进制数,表示高低电平,决定七段数码管点亮情况,分别译码为0~F。
module Seg_Led(
input Reset,
input [3:0]Digit_in,
output reg[7:0]Out
);
always@(Digit_in or Reset)
begin
if(Reset == 0) Out = 8'b11111110;
else begin
case(Digit_in)
4'b0000: Out= 8'b00000011; //0
4'b0001: Out= 8'b10011111; //1
4'b0010: Out= 8'b00100101; //2
4'b0011: Out= 8'b00001101; //3
4'b0100: Out= 8'b10011001; //4
4'b0101: Out= 8'b01001001; //5
4'b0110: Out= 8'b01000001; //6
4'b0111: Out= 8'b00011111; //7
4'b1000: Out= 8'b00000001; //8
4'b1001: Out= 8'b00001001; //9
4'b1010: Out= 8'b00010001; //A
4'b1011: Out= 8'b11000001; //B
4'b1100: Out= 8'b01100011; //C
4'b1101: Out= 8'b10000101; //D
4'b1110: Out= 8'b01100001; //E
4'b1111: Out= 8'b01110001; //F
default: Out= 8'b00000000;
endcase
end
end
endmodule
Basys3板上运行上运行的结果
操作说明:CPU工作时钟为按键BTNU,SW_in为拨键SW15和SW14,Reset为拨键SW0
SW_in = 00:显示 当前 PC值:下条指令PC值
SW_in = 01:显示 RS寄存器地址:RS寄存器数据
SW_in = 10:显示 RT寄存器地址:RT寄存器数据
SW_in = 11:显示 ALU结果输出 :DB总线数据。
Basys3板运行结果演示如下:
- 指令:bne $8,$1,-2
当前PC的值为1C,PC下地址的值为18
寄存器$8的值为04
寄存器$1的值为08
ALU Result的值为FC,DB总线数据的值为FC