当前位置:文档之家› 32位MIPS处理器说明

32位MIPS处理器说明

32位MIPS处理器说明一、实验目的熟悉现代处理器的基本工作原理;掌握单周期和流水线处理器的设计方法。

二、实验原理及实验内容该32位MIPS处理器主要需要设计ALU、单周期、流水线。

下面分别讲述这三个部分的设计原理及实验内容。

1.ALUALU即算数逻辑单元,要求设计一个32位的ALU,实现基本的算术、逻辑、关系、位与移位运算。

输入32bit的A和B作为操作数,5bit的控制输入ALUFun和一位控制输入Sign,输出32bit的结果Z。

其功能表如下图所示:根据功能表和要求,我们在顶层模块中设计了32bit输入ALUSrc1和ALUSrc2,以及5bit控制ALUFunc,1bit控制Sign,32bit输出ALUOut。

设计了四个主要模块:ALU_ADD_SUB,ALU_CMP,ALU_Logic,ALU_Shift,并根据ALUFunc的值来确定使用的模块。

设计框图思路如下:(1)ALU_ADD_SUB这部分是ALU设计中最重要的一部分。

采用了8位超前进位级联,用与门来实现32位全加器。

减法由加法实现,在运算的开始由ALUFunc判断进行的是加法还是减法,如果是加法则直接将两个操作数输入全加器,最低进位为0,如果是减法则取ALUSrc2的反码之后再做加法运算,最低进位是1。

需要注意的是运算结束之后需要判断结果是否为0(这个在branch指令中尤为重要),是否为负数,还有是否溢出。

是否为溢出还需要检查控制符号,如果控制符号为有效(即有符号计算)则两正数相加得负或两负数相加得正则溢出,溢出则结果符号位反了,再进而判断是否为负。

若为无符号运算,加法结果溢出则最高进位为1,且结果不为负;减法结果溢出等价于结果为负,即最高进位为0。

而结果为0等价于全部位都取0。

(2)ALU_Shift移位运算分别有逻辑左移,逻辑右移,算术右移三种情况。

首先判断是逻辑移位还是算术移位来决定填充0还是填充逻辑位。

之后采取先将操作数根据左移还是右移的判断做出32位扩充,再根据shamt从最高位到最低位判断是否为0来决定取那几位操作数,最后得到移位的结果。

(3)ALU_Logic逻辑运算可以直接按位运算,因此可以直接用A&B、A|B、A^B、~(A|B)计算与、或、异或、或非运算,结果根据ALUFunc的输入用选择器选择即可。

(4)ALU_CMP关系运算需要ALU_ADD_SUB的运算结果作为输入,因为A>B或A<B都可以用A-B的结果是否为负来表示。

通过判断ALU_ADD_SUB的输出是零还是负,即可得知A和B的关系,即:A==B则A-B为0,A!=B则A-B不为0,A<B则A-B 为负,A>=B则A-B不为负,A<=B则A-B为负或为0,A>B则A-B不为负或0。

而A和0的关系也可以同理判断。

只需要知道A-0的正负即可。

2.单周期实验指导书中,模块设计与连接如下(1)CPU_sc时钟clk,reset使能信号rst_n,定时器TL寄存器,Switch,串口接收Uart_Rxd,和三个中断信号IRQ作为输入,定时器TH寄存器,定时器使能信号,七段数码管,串口输出与输出使能,以及LED作为输出。

并在其中调用了所需要的模块:InstructionMemory(指令集),cpucontrol(控制单元),RegFile(寄存器模块),ALU,DataMemory(存储器模块)。

我们在这里实现了PC寻址,首先判断reset是否使能来确定PC取0还是取PC_nxt。

将中断跳转地址分别赋予ILLOP_TIMER,ILLOP_TX和ILLOP_RX,并通过判断三种中断使能标志来确定中断取值。

之后PC_nxt根据PCSrc的控制信号来判断取PC_4(即PC+4)、是否跳转至branch目标地址、jump目标地址、jump Register中的寄存器储存地址、中断目标地址或者异常目标地址,最终完成PC寻址。

这之后我们在设计了寄存器的读写,这里面我们发现了实验指导书中的一处稍微有些争议的地方:实验指导书中的MEMToReg控制信号控制的多路选择器有三种输出选择:计算得出的ALUOut,存储器中读出的ReadData和PC+4,分别对应于大多数指令、lw指令和jal,jalr指令,但是进中断时所存指令选用PC+4是有问题的,因为我们并不能控制走到那条指令的时候进入中断,如果出中断之后从Xp读出的是当前指令的下一条指令,那么当前指令就并未被有效执行。

或者说如果我们选择执行完当前指令再进入中断,那么如果当前是一个branch或者jump指令,那我们出中断之后进入的并不是跳转的对象指令,而是当前指令的下一条指令,branch和jump就等于直接失去了作用,汇编程序就会出不必要的bug。

为了解决这个问题,我们给MEMToReg增加了一条选择,即可以选择PC作为写寄存器的输入。

之后我们设计了DataBusA和DataBusB的输入,根据ALUSrc1和ALUSrc2进行选择,还对立即数的扩展做了处理,判断是有符号扩展还是无符号扩展,计算了branch指令的目标地址,最后判断了允许中断的IRQ信号的取值。

在这里受到我们重视的另一个问题就是对PC[31]的取值。

实验指导书中要求:PC的最高位PC[31]为监督位。

当该位为‘1’时,处理器处于内核态,此时异常和中断被禁止;当该位为‘0’时,处理器处于普通态,此时允许发生中断和异常。

只有RESET、异常、中断等有可能将PC[31]设置为‘1’,其他指令不能设置该位为‘1’,JR和JALR指令可以使监督位清零。

并且PC+4逻辑电路实现时应该保证PC[31]不变,分支语句和J、JAL语句不应该改变PC[31],当执行JR、JALR指令时,PC[31]的值由跳转地址($Ra)中的第31位(最高位)决定。

所以我们在每一次赋值中对PC[31]做了特殊处理,很好地满足了上述要求。

(2)InstructionMemory输入address,输出指令,其内容为我们的汇编代码,这段代码将在之后进行阐述。

(3)cpucontrol为了更好地看清每一条指令对控制信号的需求我们做了一个真值表来记录各指令功能、用法及对应的控制信号。

其中我们在汇编中用了ori指令,所以在指令集里多增加了这条指令以方便我们更好地完成汇编。

其中我们特别注意了对PC[31]的判断,以确定什么时候是中断或异常指令,什么时候不能有中断异常指令。

(4)RegFile我们并没有用老师提供的regfile文件而是自己又重新写了一个。

输入时钟、reset使能信号、read address A、read address B、write address C、需要写入寄存器的值和寄存器写控制信号,输出从A中读出的data和从中读出的data。

先找出需要写入的寄存器,再找出需要读出的寄存器。

整体来看对于我们而言要更加清晰一些,并且特别设计了0寄存器始终为0。

(5)ALU之前已经有过阐述。

(6)DataMemory在DataMemory中,我们把RAM分成了两部分,一部分为dataRAM,256字,一部分为PeripheralRAM即外设RAM。

之后分别实现写RAM和读RAM。

写RAM中,通过判断地址前24位的取值来决定是写data还是写外设,因为写外设只发生在中断部分,而中断部分的最明显特征即为地址开始位为4。

同时对flag标志信号和使能信号进行更改。

读地址过程中我们同样是通过判断地址前24位的取值来决定是读data还是读外设。

这样就综合了原本的DataMemory和Peripheral,让代码更为拥有整体性。

(7)MCU_sc这部分调用了单周期的CPU_sc,串口UART,定时器Timer,和七段数码管扫描digitube_scan,可以输出硬件显示,是严格意义上的顶层模块,然而并不是cpu整体构造的重点。

在其中为了能够让我们设计的单周期cpu正常工作,对时钟进行了二分频。

(8)UART使用的是朱宸卓当时实验的串口程序,并按照需求进行了简单的修改。

我们对串口选择中断访问。

(9)TIMER定时器TIMER按照实验指导书上的说明设置了一个从hffffffff计时到全1的定时器。

以满足我们对扫描频率的要求。

(10)digitube_scan使用的是老师提供的digitube_scan文件。

3.流水流水线的设计中,需要将整个过程分为取指令(IF)、指令译码(ID)、执行(EX)、数据访问(ME)、写回(WB)五个部分,我们设计的流水基本采用此模块设计连接思想:其中InstructionMemory,DataMemory、RegFile、cpucontrol和ALU与单周期没有任何区别。

(1)CPU_ppl与单周期类似,在这里我们同样要完成PC寻址过程,同时还要完成从IF/ID到ID/EX,再到EX/MEM,再到MEM/WB的逐级保存,在时钟的上升沿都使它们等于对应的nxt信号。

之后根据控制信号完成每一个nxt指令的赋值,并在其中解决forwarding,stall和flush对四个寄存器赋值的影响。

这一点我们会在forwarding和hazard部分进行阐述。

我们还做了一个小的ALU比较器,因为我们的程序中把所有的branch都做了提前,所以需要提前知道branch的比较结果,这个ALU实现十分简单,所以并不会造成太大延时。

下面需要比较详细阐述的是我们对forwarding和hazard的解决方案。

(2)Forwarding我们首先解决的便是书上的那两条转发情况:如果要读的寄存器的值还未被写入,则提前对计算得出的值进行转发,其中受到影响的分别是EXMEM_RegisterRd和IDEX_RegisterRs、Rt以及MEMWB_RegisterRd和IDEX_RegisterRs、Rt,分别对应于ForwardA和ForwardB。

但我们发现书上的这两种情况并不够用。

如果下一条指令需要存入RAM的数据是上一条指令才写入寄存器的,则需要转发。

对应于ForwardC.如果是branch或者jr指令,因为我们在流水中对这两类指令做了提前处理,使它们在ID阶段就能够知道自己是否跳转或者跳转到哪里,这样会遇到上一条指令刚刚计算得出的数据还没有存入寄存器就需要被branch或jr在ID阶段使用,则需要一次转发。

对应于ForwardD和ForwardE。

这样转发单元才趋于完整。

(3)Hazard遇到hazard我们一共采取了两种解决方案:stall和flush。

并且也尽可能全面地考虑了hazard的情况。

首先是书上提到的lw冒险,当lw与下一条指令产生关联,即lw取出的指令在下一条寄存器中需要使用时,需要阻塞一个周期以完成从内存中取data,并将ID寄存器flush以清空指令和控制信号。

相关主题