您的位置:首页 > 其它

制作单周期CPU(分析)

2017-04-21 16:49 253 查看
用的是Vivado软件,代码部分将在下一期展现出来(内容太多我也很绝望),这一期来讲讲思路

要求







原理

1.单周期:

单周期CPU指一条指令在一个时钟周期内完成并开始下一条指令的执行。由时钟上升、下降沿控制相关操作。两个相邻时钟上升沿或下降沿之间的时间间隔为一个时钟周期

2.CPU如何处理指令:

CPU处理指令的步骤如下:

a.取指令:根据PC中的指令地址,在指令存储器中获取相应指令。然后PC值会自动改变移动到下一条指令的地址

b.指令译码:对获取的指令进行分析,确定这个指令要完成什么操作,改变相应的控制信号

c.指令执行:相关组件获取控制信号,执行相应操作,并将结果反馈

d.存储器访问:如果指令设计读取和存储内存,则需要对存储器中相应地址进行读取或者写入

e.结果写回:将得到的数据(访问存储器或者修改其它寄存器的值获得)写回相应的寄存器

CPU的工作就是无限循环上诉步骤。

3.要实现的指令格式:

实验要求的操作满足MIPS指令的三种格式



op为操作码,是解析指令要完成什么操作的重要参考,不同操作的操作码独一无二

rs,rt,rd为寄存器编号,由于一共有32个,所以五位二进制编码足够表示每个寄存器(0号寄存器不能修改,值永远为0)

funct为拓展码,有时候op不足以完全表示所要完成的操作,拓展码用于辅助描述这类操作

4.数据传递

根据数据通路图



整体是一个CPU,而Control Unit(控制单元)作为CPU的大脑,向各个组件发送控制信号指导它们完成任务,相应控制信号及功能如图



关于ALUOp,因为很多操作要用到ALU,所以ALUOp用于区分ALU到底要做什么,以下是我设计的功能表



5.掌握一个周期的时间:

一个周期时间很短,必须合理运用,如果写寄存器或存储器和PC地址转移都在时钟上升沿或都在时钟下降沿执行,则有可能数据还没来得及写就执行下一条指令了,为了提高稳定性,可以在时钟下降沿开始保存指令执行的结果到寄存器和存储器中,上升沿则用来改变PC。

分析

1.CPU架构建立:

由数据通路图可得,一共包含11个组件:

1.1.PC:

给出当前要执行的指令地址,同时获取下一条指令的地址。我将跳转器直接加入到了PC组件中,如果需要跳转,PC根据传入的值获取下一条指令地址,否则执行+4获取下一条相邻指令

输入:CLK,Reset,PCWre,PCSrc,immediate(立即数,跳转时有用)

输出:Address(当前指令的地址)



可以看出,PC是在上升沿获取下一条指令,下降沿重置。立即数反映的是逻辑的跳转数,然而真实的是每跳过一条指令相应PC地址需要+/-4

1.2.InstructionMemory(指令存储器):

通过PC地址获取相应指令的操作码

输入:CurPC(当前PC所指地址),instMemRW

输出:instcode(找到的指令的操作码)

指令寄存器我规定存储单元宽度位8位,采用大端规则存储,即指令高位存到寄存器低地址,低位存到高地址,这是$readmemb方法默认实现的,那么获取操作码就可以通过

instcode = { InstMemory[CurPC], InstMemory[CurPC + 1], InstMemory[CurPc + 2], InstMemory[CurPC + 3] };

获取



1.3.Mux_TwoToOneReg(两路寄存器选择器):

由于MIPS指令格式的不同,进入寄存器组(RegisterFile)的Write Reg端的可能是rt,也可能是rd,因此在这里要依据控制信号来选择寄存器

输入:RegDst,RegIn1(rt), RegIn2(rd)

输出:RegOut(寄存器组的Write Reg输入)

根据传入的RegDst来选择寄存器,如图



1.4.RegisterFile(寄存器组):

大多数指令涉及寄存器,寄存器组管理32个寄存器,同时可以获取不同寄存器的值,可以修改允许修改的寄存器的值

输入:RegWre,CLK,RegIn1(寄存器的编号),RegIn2(寄存器的编号),Write_Reg,WriteData

输出:RegOut1(RegIn1号寄存器的值),RegOut2(RegIn2号寄存器的值)

由于0号寄存器默认值为0,不能被写入,只能读取,我通过



来屏蔽0号寄存器,实际上这个寄存器并不物理存在,为确保0号寄存器不被写入,在时钟下降沿时



If语句规定只有当写寄存器编号不为0时才会执行写操作

1.5.Sign_Zero_Extend(位扩展器):

指令中的立即数是16位的,要进入ALU的输入端则必须变为32位,逻辑操作(与、或)使用的是无符号扩展;加、减等操作使用的是有符号扩展

输入:Imm_Number(指令中的立即数,16位),ExtSel

输出:Result



1.6.Mux_TwoToOne_For_ALU_InputA(ALU输入A端数据选择器):

通常ALU的A输入端口是寄存器组1号输出端口输出的rs寄存器的值,但是当操作为sll时,应该是指令中的sa段,但是sa只有5位,所以这个选择器的功能有两个:1.将sa通过无符号扩展变为32位;2.根据控制信号ALUSrcA判断输入到ALU的值应该是寄存器组传入的值还是sa的值

输入:ALUSrcA,Reg1Out,sa

输出:DataOut(ALU_Input_A)

数据选择通过如图实现



1.7.Mux_TwoToOne_For_ALU_InputB(ALU输入B端数据选择器):

ALU的B输入端可能来自于寄存器组2号输出端口输出的rt寄存器的值,也可能是指令中立即数的值,所以需要这个选择器来选择ALU的B的输入

输入:ALUSrcB,Reg2Out,immediate(来自位扩展器)

输出:DataOut(ALU_Input_B)

模块与Mux_TwoToOne_For_ALU_InputA类似,不需要位扩展



1.8.Mux_TwoToOne_For_RegisterFile_WriteData(寄存器输入WriteData端数据选择器):

操作入lw,add需要对寄存器进行写操作,目标数据可能来自于ALU的结果(如add)也可能来自存储器中(如lw),所以需要这个选择器对进入寄存器输入WriteData端的数据进行选择

输入:DBDataSrc,ALU_Out,MemDataOut(来自数据存储器)

输出:WriteData

和Mux_TwoToOne_For_ALU_InputB所用模块相同,只需要修改对应参数即可

1.9.ALU(运算单元):

大多数操作都可能用到ALU,包括add,sll,sub等,ALU负责的是对数据进行基本的算术操作,根据实验内容部分ALU运算表可以定义相关方法,zero输出端是判断运算结果是否为0的,主要用处是在beq中,判断两个值是否相等就是将其相减并判断结果是否与0相等,ALU输出的值可能是一个具体的数据(如add),也可能是某个地址(如sw、lw)

输入:ALU_Input_A,ALU_Input_B,ALUOp

输出:ALU_Out,zero

ALUOp是具体的操作指示码,根据它的值ALU_Out的会有相应的结果



1.10.DataMemory(数据存储器):

lw操作和sw操作关系到了数据的存储,那么我就制作了一个数据存储器用于存放数据。为了保证该模拟CPU的稳定性,我设计数据存储器的读操作是随时的,存储单元宽度为8位,同样采用大端规则存储



而写操作是时钟下降沿触发



1.11.ControlUnit(控制单元):

作为CPU的大脑,获取指令操作码进行解析,发出相应的控制信号,指挥所有组件的工作。根据数据通路图,每种指令操作码的控制信号真值表如图



x表示可以为任意值并不影响结果,InsMemRW一直为0,但我没有删掉这个控制信号,因为以后的功能扩展可能会用到

输入:opCode(指令码的高6位),zero(用于判断beq是否需要跳转)

输出:每种操作信号

根据真值表可以很好确定对应控制信号的值



值得注意:ALUOp的最高位在最左边

SCPU(单周期CPU):

作为顶层模块,包含上述所有组件

输入:CLK,Reset

输出:CurPC,instcode(指令码)

作为整体的设计,要弄清楚不同组件的数据传递关系,根据数据通路图以及设计的组件可绘制出下图



所以很明确顶层模块需要实例化每个组件,并将相关数据、控制信号联系起来

2.仿真测试:

在InstructionMemory模块中采取了$readmemb方法,所以通过文件写入,指令真值表为



在txt文档中写



因为$readmemb是一个字节一个字节的读,所以字节与字节之间要用制表符或者空格之类的分隔符分开,之前因为这个原因总是读不了指令

在完成所有组件的定义后,新建一个仿真组件SCPU_sim



并初始化



就可以启动仿真了

这是执行左移位操作时的仿真波形



要验证仿真是否正确,可以在每个周期查看对应组件中的I/O,虽然看起来很复杂,但是如果已经理解了数据通路图,则能够快速准确的验证。比如查看RegisterFile



此时已经验证384被成功写入,384 = 12 * 2^5

总结

创建一个单周期CPU,数据的流动必须要搞清楚,建议自上而下设计,根据总体需要什么来设计相应组件,其次,数据通路图很重要,它概括了一个项目所要攻克的所有问题,建议头脑比较混乱的时候多写写,多画画,认真思考,一定可以做出来(我一开始也觉得根本做不出来)

关于vivado我也有了一点认识,input关键字表明这个参数是外界传入的,仿真时必须赋值;output则表示是传出外界的,在仿真时会在示波器中显示相应波形。而reg、wire关键字都可以作为该组件的内部属性,这些属性对于其它组件来说是透明的

但是还是有bug,我的对数据存储器读的操作是随时的,但仿真结果是下降沿才开始读,还在尝试解决中

如果有什么不足,请提出我好改进,感谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: