计算机组成与设计实验三:多周期CPU设计

这篇博客介绍了中山大学计算机组成与设计实验,重点是多周期CPU的设计,包括算术、逻辑、移位、比较、存储器读写等指令的实现。实验内容涵盖取指令、指令译码、执行、存储器访问和结果写回等步骤,以及MIPS指令的三种格式。博主分享了实验过程中的代码实现、仿真检验和烧写到Basys3实验板的过程,总结了实验心得。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

此为中山大学17级计算机组成与设计实验课题。为不影响老师的教学,本文已经删去所有实现代码,完整含代码版本已在本学期结束时发布在我的个人博客 https://wu-kan.cn/2018/12/23/多周期CPU设计/

实验目的

  1. 认识和掌握多周期数据通路图的构成、原理及其设计方法;
  2. 掌握多周期CPU的实现方法,代码实现方法;
  3. 编写一个编译器,将MIPS汇编程序编译为二进制机器码;
  4. 掌握多周期CPU的测试方法;
  5. 掌握多周期CPU的实现方法。

实验内容

设计一个多周期CPU,该CPU至少能实现以下指令功能操作。需设计的指令与格式如下:

算术运算指令

add rd rs rt

000000rs(5位)rt(5位)rd(5位)reserved
-----

功能:rd←rs + rt;reserved为预留部分,即未用,一般填“0”。

sub rd rs rt

000001rs(5位)rt(5位)rd(5位)reserved
-----

功能:rd←rs - rt

addiu rt rs immediate

000010rs(5位)rt(5位)immediate(16位)
----

功能:rt←rs + (sign-extend)immediate;immediate符号扩展再参加“加”运算。

逻辑运算指令

and rd rs rt

010000rs(5位)rt(5位)rd(5位)reserved
-----

功能:rd←rs & rt;逻辑与运算。

andi rt rs immediate

|010001|rs(5位)|rt(5位)|immediate(16位)|

-----

功能:rt←rs & (zero-extend)immediate;immediate做“0”扩展再参加“与”运算。

ori rt rs immediate

010010rs(5位)rt(5位)immediate(16位)
----

功能:rt←rs | (zero-extend)immediate;immediate做“0”扩展再参加“或”运算。

xori rd rs rt

010011rs(5位)rt(5位)rd(5位)reserved
-----

功能:rt←rs ⊕ \oplus (zero-extend)immediate;immediate做“0”扩展再参加“异或”运算。

移位指令

sll rd rt sa

011000未用rt(5位)rd(5位)sa(5位)reserved
------

功能:rd<-rt<<(zero-extend)sa,左移sa位 ,(zero-extend)sa。

比较指令

slti rt rs immediate

100110rs(5位)rt(5位)immediate(16位)
----

功能:if (rs< (sign-extend)immediate) rt =1 else rt=0, 带符号比较,详见ALU运算功能表。

slt rd rs rt

100111rs(5位)rt(5位)rd(5位)sa(5位)reserved
------

功能:if (rs<rt) rd =1 else rd=0, 具体请看ALU运算功能表,带符号。

存储器读/写指令

sw rt immediate(rs)

110000rs(5位)rt(5位)immediate(16位)
----

功能:memory[rs+ (sign-extend)immediate]←rt;immediate符号扩展再相加。即将rt寄存器的内容保存到rs寄存器内容和立即数符号扩展后的数相加作为地址的内存单元中。

lw rt immediate(rs)

110001rs(5位)rt(5位)immediate(16位)
----

功能:rt ← memory[rs + (sign-extend)immediate];immediate符号扩展再相加。即读取rs寄存器内容和立即数符号扩展后的数相加作为地址的内存单元中的数,然后保存到rt寄存器中。

分支指令

beq rs rt immediate

110100rs(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位的,也就是以上说的“指令之间指令条数”。

bne rs rt immediate

110101rs(5位)rt(5位)immediate(16位)
----

功能:if(rs!=rt) pc←pc + 4 + (sign-extend)immediate <<2 else pc ←pc + 4
特别说明:与beq不同点是,不等时转移,相等时顺序执行。

bltz rs immediate

110110rs(5位)00000immediate(16位)
----

功能:if(rs<$zero) pc←pc + 4 + (sign-extend)immediate <<2 else pc ←pc + 4

跳转指令

j addr

111000addr[27:2]
--

功能:pc <-{(pc+4)[31:28],addr[27:2],2'b00},无条件跳转。
说明:由于MIPS32的指令代码长度占4个字节,所以指令地址二进制数最低2位均为0,将指令地址放进指令代码中时,可省掉!这样,除了最高6位操作码外,还有26位可用于存放地址,事实上,可存放28位地址,剩下最高4位由pc+4最高4位拼接上。

jr rs

111001rs(5位)未用未用reserved
-----

功能:pc <- rs,跳转。

调用子程序指令

jal addr

111010addr[27:2]
--

功能:调用子程序,pc <- {(pc+4)[31:28],addr[27:2],2'b00}$31<-pc+4,返回地址设置;子程序返回,需用指令 jr $31。跳转地址的形成同 j addr 指令。

停机指令

halt

11111100000000000000000000000000(26位)
--

功能:停机;不改变PC的值,PC保持不变。

实验原理

多周期CPU指的是将整个CPU的执行过程分成几个阶段,每个阶段用一个时钟去完成,然后开始下一条指令的执行,而每种指令执行时所用的时钟数不尽相同,这就是所谓的多周期CPU。

CPU在处理指令的几个步骤

取指令IF
指令译码ID
指令执行EXE
存储器访问MEM
结果写回WB

图1 CPU指令处理过程
实验中就按照这五个阶段进行设计,这样一条指令的执行最长需要五个(小)时钟周期才能完成,但具体情况怎样?要根据该条指令的情况而定,有些指令不需要五个时钟周期的,这就是多周期的CPU。

取指令(IF)

根据程序计数器pc中的指令地址,从存储器中取出一条指令,同时,pc根据指令字长度自动递增产生下一条指令所需要的指令地址,但遇到“地址转移”指令时,则控制器把“转移地址”送入pc,当然得到的“地址”需要做些变换才送入pc。

指令译码(ID)

对取指令操作中得到的指令进行分析并译码,确定这条指令需要完成的操作,从而产生相应的操作控制信号,用于驱动执行状态中的各种操作。

指令执行(EXE)

根据指令译码得到的操作控制信号,具体地执行指令动作,然后转移到结果写回状态。

存储器访问(MEM)

所有需要访问存储器的操作都将在这个步骤中执行,该步骤给出存储器的数据地址,把数据写入到存储器中数据地址所指定的存储单元或者从存储器中得到数据地址单元中的数据。

结果写回(WB)

指令执行的结果或者访问存储器中得到的数据写回相应的目的寄存器中。

MIPS指令的三种格式

缩写说明
op操作码
rs只读,为第1个源操作数寄存器,寄存器地址(编号)是00000~11111,00~1F
rt可读可写,为第2个源操作数寄存器,或目的操作数寄存器,寄存器地址(同上)
rd只写,为目的操作数寄存器,寄存器地址(同上)
sa位移量(shift amt),移位指令用于指定移多少位
funct功能码,在寄存器类型指令中(R类型)用来指定指令的功能与操作码配合使用
immediate16位立即数,用作无符号的逻辑操作数、有符号的算术操作数、数据加载(Laod)/数据保存(Store)指令的数据地址字节偏移量和分支指令中相对程序计数器(PC)的有符号偏移量;
address地址

R类型

31-2625-2120-1615-1110-65-0
oprsrtrdsafunc
6位5位5位5位5位6位

I类型

31-2625-2120-1615-0
oprsrtimmediate
6位5位5位16位

J类型:

31-2625-0
opaddress
6位26位

多周期CPU状态转移图

在这里插入图片描述
状态的转移有的是无条件的,例如从sIF状态转移到sID就是无条件的;有些是有条件的,例如sEXE状态之后不止一个状态,到底转向哪个状态由该指令功能,即指令操作码决定。每个状态代表一个时钟周期。

多周期CPU控制部件的原理结构图

在这里插入图片描述

多周期CPU数据通路和控制线路图

在这里插入图片描述
上图是一个简单的基本上能够在多周期CPU上完成所要求设计的指令功能的数据通路和必要的控制线路图。其中指令和数据各存储在不同存储器中,即有指令存储器和数据存储器。访问存储器时,先给出内存地址,然后由读或写信号控制操作。对于寄存器组,给出寄存器地址(编号),读操作时不需要时钟信号,输出端就直接输出相应数据;而在写操作时,在 WE使能信号为1时,在时钟边沿触发将数据写入寄存器。图中控制信号功能如表1所示,表2是ALU运算功能表。

特别提示,图上增加IR指令寄存器,目的是使指令代码保持稳定,pc写使能控制信号PCWre,是确保pc适时修改,原因都是和多周期工作的CPU有关。ADR、BDR、ALUoutDR、DBDR四个寄存器不需要写使能信号,其作用是切分数据通路,将大组合逻辑切分为若干个小组合逻辑,大延迟变为多个分段小延迟。

指令执行的结果总是在时钟下降沿保存到寄存器和存储器中,PC的改变是在时钟上升沿进行的,这样稳定性较好。另外,值得注意的问题,设计时,用模块化的思想方法设计,关于ALU设计、存储器设计、寄存器组设计等等,也是必须认真考虑的问题。

其中指令和数据各存储在不同存储器中,即有指令存储器和数据存储器。访问存储器时,先给出内存地址,然后由读或写信号控制操作。对于寄存器组,先给出寄存器地址,读操作时不需要时钟信号,输出端就直接输出相应数据;而在写操作时,在 WE使能信号为1时,在时钟边沿触发将数据写入寄存器。

控制信号的作用表

以上数据通路图是根据要实现的指令功能的要求画出来的,同时,还必须确定ALU的运算功能(当然,以上指令没有完全用到提供的ALU所有功能,但至少必须能实现以上指令功能操作)。从数据通路图上可以看出控制单元部分需要产生各种控制信号,当然,也有些信号必须要传送给控制单元。从指令功能要求和数据通路图的关系得出以上表1,这样,从表1可以看出各控制信号与相应指令之间的相互关系,根据这种关系就可以得出控制信号与指令之间的关系(见下面表中的“相关指令”),从而写出各控制信号的逻辑表达式,这样控制单元部分就可实现了。

控制信号名状态“0”状态“1”
RST对于PC,初始化PC为程序首地址对于PC,PC接收下一条指令地址
PCWrePC不更改,相关指令:halt,另外,除‘000’状态之外,其余状态慎改PC的值。PC更改,相关指令:除指令halt外,另外,在‘000’状态时,修改PC的值合适。
ALUSrcA来自寄存器堆data1输出,相关指令:add、sub、addiu、and、andi、ori、xori、slt、slti、sw、lw、beq、bne、bltz来自移位数sa,同时,进行(zero-extend)sa,即 { {27{1'b0},sa},相关指令:sll
ALUSrcB来自寄存器堆data2输出,相关指令:add、sub、and、slt、beq、bne、bltz来自sign或zero扩展的立即数,相关指令:addiu、andi、ori、xori、slti、lw、sw、sll
DBDataSrc来自ALU运算结果的输出,相关指令:add、sub、addiu、and、andi、ori、xori、sll、slt、slti来自数据存储器(Data MEM)的输出,相关指令:lw
RegWre无无写寄存器组寄存器,相关指令:beq、bne、bltz、j、sw、jr、halt寄存器组寄存器写使能,相关指令:add、sub、addiu、and、andi、ori、xori、sll、slt、slti、lw、jal
WrRegDSrc写入寄存器组寄存器的数据来自pc+4(pc4),相关指令:jal,写$31写入寄存器组寄存器的数据来自ALU运算结果或存储器读出的数据,相关指令:add、addiu、sub、and、andi、ori、xori、sll、slt、slti、lw
InsMemRW写指令存储器读指令存储器(Ins. Data)
mRD输出高阻态读数据存储器,相关指令:lw
mWR无操作写数据存储器,相关指令:sw
IRWreIR(指令寄存器)不更改IR寄存器写使能。向指令存储器发出读指令代码后,这个信号也接着发出,在时钟上升沿,IR接收从指令存储器送来的指令代码。与每条指令都相关。

ALUOp的功能表

ALUOp[2…0]功能描述相关指令
000Y=A+Badd、addiu、sw、lw
001Y=A–Bsub、beq、bne、bltz
010Y=B<<AB左移A位sll
011Y=A∨Bori
100Y=A∧Bandi、and
101Y=A<B不带符号比较A<B
110Y=A[31]!=B[31]?A[31]>B[31]:A<B带符号比较A<Bslti、slt
111Y=A^B异或xori

ExtSel的功能表

ExtSel[1…0]功能相关指令
00(zero-extend)sasll
01(zero-extend)immediateandi、xori、ori
10(sign-extend)immediateaddiu、slti、lw、sw、beq、bne、bltz
11未用

PCSrc的功能表

|PCSrc[1…0]|功能|相关指令|
|-|-|-|-|
|00|pc<-pc+4|add、addiu、sub、and、andi、ori、xori、slt、slti、sll、sw、lw、beq(zero=0)、bne(zero=1)、bltz(sign=0)|
|01|pc<-pc+4+(sign-extend)immediate ×4|beq(zero=1)、 bne(zero=0)、bltz(sign=1)|
|10|pc<-rs|jr|
|11|pc<-{pc[31:28],addr[27:2],2’b00}|j、jal|

RegDs的功能表

|RegDs[1…0]|写寄存器组寄存器的地址|相关指令|
|-|-|-|-|
|00|0x1F($31)|jal(用于保存返回地址$31<-pc+4)|
|01|rt|addiu、andi、ori、xori、slti、lw|
|10|rd|add、sub、and、slt、sll|
|11|未用|

相关部件及引脚说明:

Instruction Memory

指令存储器。

Iaddr指令存储器地址输入端口
IDataIn指令存储器数据输入端口(指令代码输入端口)
IDataOut指令存储器数据输出端口(指令代码输出端口)
RW指令存储器读写控制信号,为0写,为1读

Data Memory

数据存储器。

Daddr数据存储器地址输入端口
DataIn数据存储器数据输入端口
DataOut数据存储器数据输出端口
RD数据存储器读控制信号,为0读
WR数据存储器写控制信号,为0写

Register File

寄存器组。

Read Reg1rs寄存器地址输入端口
Read Reg2rt寄存器地址输入端口
Write Reg将数据写入的寄存器端口,其地址来源rt或rd字段
Write Data写入寄存器的数据输入端口
Read Data1rs寄存器数据输出端口
Read Data2rt寄存器数据输出端口
WE写使能信号,为1时,在时钟边沿触发写入

IR

指令寄存器,用于存放正在执行的指令代码。由于RW模块所需要的寄存器IR太多,因此合在了一起。

ALU:算术逻辑单元

resultALU运算结果
zero运算结果标志,结果为0,则zero=1;否则zero=0
sign运算结果标志,结果最高位为0,则sign=0,正数;否则,sign=1,负数

实验器材

电脑一台,Xilinx Vivado 2017.4 软件一套,Basys3实验板一块。

实验过程与结果

代码实现

MultipleCPU.v

多周期CPU的顶层连接文件,主要是调用下层模块并将它们输入输出连在一起。


ControlUnit.v

控制信号模块,通过解析op得到该指令的各种控制信号。定义了很多用到的常量,可读性还是比较高的。
控制单元通过输入的zero零标志位与当前指令中对应的指令部分来确定当前整个CPU程序中各模块的工作和协作情况,根据CPU运行逻辑,事先对整个CPU中控制信号的控制,以此来达到指挥各个模块协同工作的目的。


ALU.v

该部分为算术逻辑单元,用于逻辑指令计算和跳转指令比较。ALUOp用于控制算数的类型,A、B为输入数,result为运算结果,zero、sign主要用于beq、bne、bltz等指令的判断。
ALU算术逻辑单元的功能是根据控制信号从输入的数据中选取对应的操作数,根据操作码进行运算并输出结果与零标志位。


DataMemory.v

该部分控制内存存储,用于内存存储、读写。用256大小的8位寄存器数组模拟内存,采用小端模式。DataMenRW控制内存读写。由于指令为真实地址,所以不需要<<2


InstructionMemory.v

该部分为指令寄存器,通过一个256大小的8位寄存器数组来保存从文件输入的全部指令。然后通过输入的地址,找到相应的指令,输出到IDataOut。
指令存储器的功能是存储读入的所有32-bit位宽的指令,根据程序计数器PC中的指令地址进行取指令操作并对指令类型进行分析,通过指令类型对所取指令的各字段进行区分识别,最后将对应部分传递给其他模块进行后续处理。
指令存储器中每个单元的位宽为8-bit,也就是存储每条32-bit位宽的指令都需要占据4个单元,所以第n(n大于或等于0)条指令所对应的起始地址为4n,且占据第4n,4n+1,4n+2,4n+3这四个单元。取出指令就是将这四个单元分别取出,因为指令的存储服从高位指令存储在低位地址的规则,所以4n单元中的字段是该条指令的最高8位,后面以此类推,并通过左移操作将指令的四个单元部分移动到相对应的位置,以此来得到所存指令。


MUX2L_32.v

三十二线双路选择器。


MUX4L_32.v

三十二线四路选择器。


MUX4L_5.v

五线四路选择器。


PC.v

CLK上升沿触发,更改指令地址。由于指令地址存储在寄存器里,一开始需要赋currentAddress为0。Reset是重置信号,当为1时,指令寄存器地址重置。PCWre的作用为保留现场,如果PCWre为0,指令地址不变。
PC程序计数器用于存放当前指令的地址,当PC的值发生改变的时候,CPU会根据程序计数器PC中新得到的指令地址,从指令存储器中取出对应地址的指令,根据PCSrc控制信号的值选择指令地址是要进行PC+4或者跳转等操作。若PC程序计数器检测到Reset输入信号为1时,则对程序计数器存储的当前指令地址进行清零处理。


RegisterFile.v

该部分为寄存器读写单元,储存寄存器组,并根据地址对寄存器组进行读写。WE的作用是控制寄存器是否写入。同上,通过一个32大小的32位寄存器数组来模拟寄存器,开始时全部置0。通过访问寄存器的地址,来获取寄存器里面的值,并进行操作。(由于$0恒为0,所以写入寄存器的地址不能为0)
寄存器组中的每个寄存器位宽32-bit,是存放ALU计算所需要的临时数据的,与数据存储器不同,可能会在程序执行的过程中被多次覆盖,而数据存储器内的数据一般只有sw指令才能进行修改覆盖。寄存器组会根据操作码opCode与rs,rt字段相应的地址读取数据,同时将rs,rt寄存器的地址和其中的数据输出,在CLK的下降沿到来时将数据存放到rd或者rt字段的相应地址的寄存器内。


SignZeroExtend.v

比较简单的一个模块,用于立即数的扩展。ExtSel为控制补位信号。判断后,将extendImmediate的前16位全补1或0即可。


仿真检验

编写一个编译器,将MIPS汇编程序编译为二进制机器码

使用支持c++11以上标准的编译器编译下述代码,得到编译器wkmips

#include<bits/stdc++.h>
#define TRANS(s,len) (bitset<len>(stoi(s,0,0)).to_string())
using namespace std;
string compiler(string s)
{
	static unordered_map<string,string> mp
	{
		{"add","000000"},
		{"sub","000001"},
		{"addiu","000010"},
		{"and","010000"},
		{"andi","010001"},
		{"ori","010010"},
		{"xori","010011"},
		{"sll","011000"},
		{"slti","100110"},
		{"slt","100111"},
		{"sw","110000"},
		{"lw","110001"},
		{"beq","110100"},
		{"bne","110101"},
		{"bltz","110110"},
		{"j","111000"},
		{"jr","111001"},
		{"jal","111010"},
		{"halt","111111"}
	};
	vector<string> v;
	for(char &c:s)if(!isgraph(c)||c=='$'||c=='('||c==')')c=',';
	for(istringstream sin(s); getline(sin,s,',');)
		if(!s.empty())v.push_back(s);
	if(v.empty())return "";
	s=mp[v[0]];
	if(v.size()==1)s+=string(26,'0');
	else if(v.size()==2)
	{
		if(v[0]=="jr")s+=TRANS(v[1],5)+string(21,'0');
		else s+=bitset<26>(stoi(v[1],0,0)>>2).to_string();
	}
	else if(v.size()==3)
	{
		if(v[0]=="bltz")s+=TRANS(v[1],5)+string(5,'0')+TRANS(v[2],16);
		else s+=TRANS(v[1],5)+TRANS(v[2],21);
	}
	else
	{
		if(v[0]=="sw"||v[0]=="lw")swap(v[2],v[3]);
		if(v[0]=="sll")
		{
			s+=string(5,'0');
			s+=TRANS(v[2],5)+TRANS(v[1],5);
			s+=TRANS(v[3],5)+string(6,'0');
		}
		else if(v[0].find('i')!=v[0].npos
		        ||v[0]=="sw"||v[0]=="lw"
		        ||v[0]=="beq"||v[0]=="bne")
		{
			s+=TRANS(v[2],5)+TRANS(v[1],5);
			s+=TRANS(v[3],16);
		}
		else
		{
			s+=TRANS(v[2],5)+TRANS(v[3],5);
			s+=TRANS(v[1],5)+string(11,'0');
		}
	}
	return s;
}
int main(int argc,char **argv)
{
	if(argc<2)return 0;
	ifstream fin(argv[1]);
	ofstream fout(argv[argc-1]);
	for(string s; getline(fin,s); fout<<'\n')
	{
		s=compiler(s);
		for(int i=0; i<s.size(); ++i)
		{
			fout<<s[i];
			if(i%8==7)fout<<' ';
		}
	}
}

待转换的MIPS代码test.txt内容如下:

addiu  $1,$0,8
ori  $2,$0,2
xori  $3,$2,8
sub  $4,$3,$1
and  $5,$4,$2
sll   $5,$5,2
beq  $5,$1,-2
jal  0x0000050
slt  $8,$13,$1
addiu  $14,$0,-2
slt  $9,$8,$14
slti  $10,$9,2
slti  $11,$10,0
add  $11,$11,$10
bne  $11,$2,-2
addiu  $12,$0,-2
addiu  $12,$12,1
bltz  $12,-2
andi  $12,$2,2
j  0x000005C
sw  $2,4($1)
lw  $13,4($1)
jr  $31
halt

在命令行中键入wkmips test.txt input.txt,得到编译后文件input.txt

00001000 00000001 00000000 00001000
01001000 00000010 00000000 00000010
01001100 01000011 00000000 00001000
00000100 01100001 00100000 00000000
01000000 10000010 00101000 00000000
01100000 00000101 00101000 10000000
11010000 00100101 11111111 11111110
11101000 00000000 00000000 00010100
10011101 10100001 01000000 00000000
00001000 00001110 11111111 11111110
10011101 00001110 01001000 00000000
10011001 00101010 00000000 00000010
10011001 01001011 00000000 00000000
00000001 01101010 01011000 00000000
11010100 01001011 11111111 11111110
00001000 00001100 11111111 11111110
00001001 10001100 00000000 00000001
11011001 10000000 11111111 11111110
01000100 01001100 00000000 00000010
11100000 00000000 00000000 00010111
11000000 00100010 00000000 00000100
11000100 00101101 00000000 00000100
11100111 11100000 00000000 00000000
11111100 00000000 00000000 00000000

检验:手动将指令转换成二进制代码如下表,可对比检验上述编译器转换结果无误。

地址汇编程序op(6)rs(5)rt(5)rd(5)/immediate(16)16进制数代码
0x00000000addiu $1,$0,8000010000000000100000000 0000100008010008
0x00000004ori $2,$0,2010010000000001000000000 0000001048020002
0x00000008xori $3,$2,8010011000100001100000000 000010004c430008
0x0000000csub $4,$3,$1000001000110000100100000 0000000004612000
0x00000010and $5,$4,$2010000001000001000101000 0000000040822800
0x00000014sll $5,$5,2011000000000010100101000 1000000060052880
0x00000018beq $5,$1,-2110100000010010111111111 11111110d025fffe
0x0000001cjal 0x0000050111010000000000000000000 00010100e8000014
0x00000020slt $8,$13,$1100111011010000101000000 000000009da14000
0x00000024addiu $14,$0,-2000010000000111011111111 11111110080efffe
0x00000028slt $9,$8,$14100111010000111001001000 000000009d0e4800
0x0000002cslti $10,$9,2100110010010101000000000 00000010992a0002
0x00000030slti $11,$10,0100110010100101100000000 00000000994b0000
0x00000034add $11,$11,$10000000010110101001011000 00000000016a5800
0x00000038bne $11,$2,-2110101000100101111111111 11111110d44bfffe
0x0000003caddiu $12,$0,-2000010000000110011111111 11111110080cfffe
0x00000040addiu $12,$12,1000010011000110000000000 00000001098c0001
0x00000044bltz $12,-2110110011000000011111111 11111110d980fffe
0x00000048andi $12,$2,2010001000100110000000000 00000010444c0002
0x0000004cj 0x000005C111000000000000000000000 00010111e0000017
0x00000050sw $2,4($1)110000000010001000000000 00000100c0220004
0x00000054lw $13,4($1)110001000010110100000000 00000100c42d0004
0x00000058jr $31111001111110000000000000 00000000e7e00000
0x0000005chalt111111000000000000000000 00000000fc000000

Sim.v

仿真模块。


仿真波形

波形比较长,分成三部分逐一分析。
在这里插入图片描述
第一次执行到地址0x00000018的时候,执行了beq $5,$1,-2,此时$5$1都是8,回退到0x00000014;第二次执行到地址0x00000018的时候,$5是32,$1都是8,不等,继续执行下一条指令jal 0x0000050。于是跳到0x00000050,并在0x00000058地址上跳回0x00000020
在这里插入图片描述
第一次执行到地址0x00000038的时候,执行了bne $11,$2,-2,此时$11是1,$2是2,不等,回退到0x00000034;第二次执行到地址0x00000038的时候,$11$2都是2,继续执行下一条指令。
在这里插入图片描述
第一次执行到地址0x00000044的时候,执行了bltz $12,-2,此时$12是-1,小于0,回退到0x00000040;第二次执行到地址0x00000044的时候,$12是0,继续执行下一条指令。
执行到地址0x0000004C的时候,执行了j 0x000005C,直接跳转到0x000005C,于是执行该地址上的Halt,波形不再变化。

烧写到Basys3实验板

Basys3.v

顶层模块。和单周期没有太大区别。(Reset的取值)改了。


Debounce.v

和单周期一样的按键消抖模块。Basys3板采用的是机械按键,在按下按键时按键会出现人眼无法观测但是系统会检测到的抖动变化,这可能会使短时间内电平频繁变化,导致程序接收到许多错误的触发信号而出现许多不可知的错误。消抖操作是每当检测到CLK上升沿到来时检测一次当前电平信号并记录,同计数器开始计数,若在计数器达到5000之前电平发生变化,则将计数器清零,若达到5000,则将该记录电平取反输出。
因为程序开始时已经运行第一条指令,为避免跳过第一条指令计算值的写入,我们的输入需要从下降沿开始,因此我们给按键信号取反后再输入。


SegLED.v

数码管译码模块。译码模块将CPU运算的结果转换成7段数码管中各个数码管显示所需的高低电平信号,该单元的输入为4-bit位宽的二进制数。其中,七段数码管的八个电平控制输出中最低位是小数点的显示信号,但小数点在CPU运行时没有用到,恰好用于标记Reset状态。

`timescale 1ns / 1ps
module SegLED(
	input[3:0] Store,
	input Reset,
	output reg[7:0] Out
);
	always@(Store or Reset)begin
		begin
			case(Store)
				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;	//all light
			endcase
		end
	end
endmodule

运行结果

端口映射

在这里插入图片描述

初始化

在这里插入图片描述
所有寄存器被初始化为0。

第1条指令addiu $1,$0,8

在这里插入图片描述
当前地址00,下一地址04。
在这里插入图片描述
0号寄存器,值为0。
在这里插入图片描述
1号寄存器。立即数8。
在这里插入图片描述
运算结果8。

第2条指令ori $2,$0,2

在这里插入图片描述
当前地址04,下一地址08。
在这里插入图片描述
0号寄存器,值为0。
在这里插入图片描述
2号寄存器,立即数2。
在这里插入图片描述
ALU结果为2。

第3条指令xori $3,$2,8

在这里插入图片描述
当前地址08,下一地址0c。
在这里插入图片描述
2号寄存器,值为02。
在这里插入图片描述
3号寄存器,值为0a(这里是已经写入后的结果)。
在这里插入图片描述
ALU结果为0a。

第4条指令sub $4,$3,$1

在这里插入图片描述
当前地址0c,下一地址10。
在这里插入图片描述
3号寄存器,值为0a。
在这里插入图片描述
1号寄存器,值为08。
在这里插入图片描述
ALU结果为02。

第5条指令and $5,$4,$2

在这里插入图片描述
当前地址10,下一地址14。
在这里插入图片描述
4号寄存器,值为2。
在这里插入图片描述
2号寄存器,值为2。
在这里插入图片描述
ALU结果为2。

实验心得

多周期CPU相对于单周期主要是要多思考一下自动状态机转换的问题。此外,还需要单独增加几个寄存器用于接收中间的结果,并且需要连上时钟。
两个学期的实验课终于到此告一段落。作为公认较难的一门课,即使学分较为少,我觉得对人的锻炼却是非常大的。每一年计组的课题都会略有区别,这让我们很难找到参考的地方;甚至很多东西往年的资料和今年的完全相反,如果不加思考完全照抄可能反而会带来误导。因此,虽然难度较大,但是却更加符合我们日后工作科研中可能会遇到的情况。不管怎样,自己总是算是做掉了多周期CPU,几天来的心血没有白费。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值