设计一个多周期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
000010 | rs(5λ) | rt(5λ) | immediate(16λ) |
---|
功能:rt←rs + (sign-extend)immediate;immediate符号扩展再参加“加”运算。
3. sub rd , rs , rt
000001 | rs(5λ) | rt(5λ) | rd(5λ) | reserved |
---|
功能:rd←rs - rt
==> 逻辑运算指令
4. ori rt , rs ,immediate
010010 | 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
010000 | 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. sltiu rt, rs,immediate 不带符号
100011 | rs(5λ) | rt(5λ) | immediate(16λ) |
---|
功能:if (rs <(sign-extend)immediate) rt =1 else rt=0, 具体请看表2 ALU运算功能表,不带符号
==> 存储器读/写指令
9. sw rt ,immediate(rs) 写存储器
110000 | rs(5λ) | rt(5λ) | immediate(16λ) |
---|
功能:memory[rs+ (sign-extend)immediate]←rt;immediate符号扩展再相加。即将rt寄存器的内容保存到rs寄存器内容和立即数符号扩展后的数相加作为地址的内存单元中。
10. lw rt , immediate(rs) 读存储器
110001 | rs(5λ) | rt(5λ) | immediate(16λ) |
---|
功能:rt ← memory[rs + (sign-extend)immediate];immediate符号扩展再相加。
即读取rs寄存器内容和立即数符号扩展后的数相加作为地址的内存单元中的数,然后保存到rt寄存器中。
==> 分支指令
11. beq rs,rt,immediate
110100 | 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. bltz rs,immediate
110110 | rs(5λ) | 00000 | immediate(16λ) |
---|
功能:if(rs<0) pc←pc + 4 + (sign-extend)immediate <<2 else pc ←pc + 4
==>跳转指令
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. jr rs
111001 | rs(5λ) | 未用 | 未用 | reserved |
---|
功能:调用子程序,pc <- {(pc+4)[31:28],addr[27:2],2’b00};31。跳转地址的形成同 j addr 指令。
15. j al addr
111000 | addr[27…2] |
---|
功能:调用子程序,pc <- {(pc+4)[31:28],addr[27:2],2’b00};31。跳转地址的形成同 j addr 指令。
==> 停机指令
16. halt
111111 | 00000000000000000000000000(26λ) |
---|
功能:停机;不改变PC的值,PC保持不变。
多周期CPU指的是将整个CPU的执行过程分成几个阶段,每个阶段用一个时钟去完成,然后开始下一条指令的执行,而每种指令执行时所用的时钟数不尽相同,这就是所谓的多周期CPU。
- 取指令(IF):根据程序计数器pc中的指令地址,从存储器中取出一条指令,同时,pc根据指令字长度自动递增产生下一条指令所需要的指令地址,但遇到“地址转移”指令时,则控制器把“转移地址”送入pc,当然得到的“地址”需要做些变换才送入pc。
- 指令译码(ID):对取指令操作中得到的指令进行分析并译码,确定这条指令需要完成的操作,从而产生相应的操作控制信号,用于驱动执行状态中的各种操作。
- 指令执行(EXE):根据指令译码得到的操作控制信号,具体地执行指令动作,然后转移到结果写回状态。
- 存储器访问(MEM):所有需要访问存储器的操作都将在这个步骤中执行,该步骤给出存储器的数据地址,把数据写入到存储器中数据地址所指定的存储单元或者从存储器中得到数据地址单元中的数据。
- 结果写回(WB):指令执行的结果或者访问存储器中得到的数据写回相应的目的寄存器中。
实验中就按照这五个阶段进行设计,这样一条指令的执行最长需要五个(小)时钟周期才能完成,但具体情况怎样?要根据该条指令的情况而定,有些指令不需要五个时钟周期的,这就是多周期的CPU。
- op:为操作码;
- rs:为第1个源操作数寄存器,寄存器地址(编号)是00000~11111,00~1F;
- rt:为第2个源操作数寄存器,或目的操作数寄存器,寄存器地址(同上);
- rd:为目的操作数寄存器,寄存器地址(同上);
- sa:为位移量(shift amt),移位指令用于指定移多少位;
- funct:为功能码,在寄存器类型指令中(R类型)用来指定指令的功能;
- immediate:为16位立即数,用作无符号的逻辑操作数、有符号的算术操作数、数据加载(Load)/数据保存(Store)指令的数据地址字节偏移量和分支指令中相对程序计数器(PC)的有符号偏移量;
- address:为地址。
状态的转移有的是无条件的,例如从sIF状态转移到sID就是无条件的;有些是有条件的,例如sEXE状态之后不止一个状态,到底转向哪个状态由该指令功能,即指令操作码决定。每个状态代表一个时钟周期。
图3是多周期CPU控制部件的电路结构,三个D触发器用于保存当前状态,是时序逻辑电路,RST用于初始化状态“000“,另外两个部分都是组合逻辑电路,一个用于产生下一个阶段的状态,另一个用于产生每个阶段的控制信号。从图上可看出,下个状态取决于指令操作码和当前状态;而每个阶段的控制信号取决于指令操作码、当前状态和反映运算结果的状态zero标志和符号sign标志。
图4是一个简单的基本上能够在多周期CPU上完成所要求设计的指令功能的数据通路和必要的控制线路图。其中指令和数据各存储在不同存储器中,即有指令存储器和数据存储器。访问存储器时,先给出内存地址,然后由读或写信号控制操作。对于寄存器组,给出寄存器地址(编号),读操作时不需要时钟信号,输出端就直接输出相应数据;而在写操作时,在 WE使能信号为1时,在时钟边沿触发将数据写入寄存器。图中控制信号功能如表1所示,表2是ALU运算功能表。
特别提示,图上增加IR指令寄存器,目的是使指令代码保持稳定,pc写使能控制信号PCWre,是确保pc适时修改,原因都是和多周期工作的CPU有关。ADR、BDR、ALUoutDR、DBDR四个寄存器不需要写使能信号,其作用是切分数据通路,将大组合逻辑切分为若干个小组合逻辑,大延迟变为多个分段小延迟。
控制信号名 | 状态“0” | 状态“1” |
---|---|---|
RST | 初始化PC为0 | PC接收新地址 |
PCWre | PC不更改,相关指令:halt,另外,除‘000’状态之外,其余状态慎改PC的值 | PC更改,相关指令:除指令halt外,另外,在‘000’状态时,修改PC的值合适 |
ALUSrcA | 来自寄存器堆data1输出,相关指令:add、sub、addi、or、and、ori、beq、bltz、slt、sltiu、sw、lw | 来自移位数sa,同时,进行(zero-extend)sa,即 {{27{1’b0}},sa},相关指令:sll |
ALUSrcB | 来自寄存器堆data2输出,相关指令:add、sub、or、and、beq、bltz、slt、sll | 来自sign或zero扩展的立即数,相关指令:addi、ori、sltiu、lw、sw |
DBDataSrc | 来自ALU运算结果的输出,相关指令:add、addi、sub、ori、or、and、sltiu、slt、sll | 来自数据存储器(Data MEM)的输出,相关指令:lw |
RegWre | 无写寄存器组寄存器,相关指令:beq、bltz、j、sw、jr、halt | 寄存器组寄存器写使能,相关指令:add、sub、addi、or、and、ori、slt、sltiu、sll、lw、jal |
InsMemRW | 写指令存储器 | 读指令存储器(Ins. Data) |
mRD | 输出高阻态 | 读数据存储器,相关指令:lw |
mWR | 无操作 | 写数据存储器,相关指令:sw |
RegDst | 写寄存器组寄存器的地址,来自:00:0x1F(31<-pc+4);01:rt字段,相关指令:addi、ori、sltiu、lw;10:rd字段,相关指令:add、sub、or、and、slt、sll;11:未用; | |
ExtSel | (zero-extend)immediate(0扩展),相关指令:ori、sltiu | (sign-extend)immediate(符号扩展),相关指令:addi、sw、lw、beq、bltz |
PCSrc[1..0] | 00:pc<-pc+4,相关指令:add、addi、sub、or、ori、and、slt、sltiu、sll、sw、lw、beq(zero=0)、bltz(sign=0,或zero=1);01:pc<-pc+4+(sign-extend)immediate,相关指令:beq(zero=1)、bltz(sign=1,zero=0);10:pc<-rs,相关指令:jr; 11:pc<-{(pc+4)[31:28],addr[27:2],2’b00},相关指令:j、jal; | |
ALUOp[2..0] | ALU 8种运算功能选择(000-111),看功能表 |
- Instruction Memory:指令存储器
- Iaddr,指令地址输入端口
- DataIn,存储器数据输入端口
- DataOut,存储器数据输出端口
- 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时,在时钟边沿触发写入
- IR: 指令寄存器,用于存放正在执行的指令代码
- ALU: 算术逻辑单元
- result,ALU运算结果
- zero,运算结果标志,结果为0,则zero=1;否则zero=0
- sign,运算结果标志,结果最高位为0,则sign=0,正数;否则,sign=1,负数
值得注意的问题,设计时,用模块化、层次化的思想方法设计,关于如何划分模块、如何整合成一个系统等等,是必须认真考虑的问题。
设计思路以及流程:
控制信号、指令以及执行状态相互关系的过程
理清控制信号、指令以及执行状态三者之间的关联是设计多周期CPU的第一个步骤,也是主要难点之一。在多周期CPU中,部分控制信号遇到在特定的状态下控制单元才对该控制信号进行相应的改变,而部分信号则与当前执行状态无关,可以在取指令阶段时就赋予需要的值。例如,对于任何一条指令,信号量RegWre的状态时比较需要注意的,假如再对于需要写寄存器组的指令一开始就对所有信号量赋予相应的值,那么寄存器组的写使能端信号量RegWre为使能状态,此时,在指令执行处理过程中,可能不止在结果写回的时候写入数据,在指其余的执行过程中都可能发生寄存器组写入数据,这时候可能导致原来寄存器组一些数据被修改替换了,再次使用的时候得到的结果与预期的就不一样了,存在比较大的危害性。同时指令的执行不一定需要所有的模块,比如跳转指令,其无需对数据寄存器进行读写操作,则数据寄存器的控制信号mRD、mWR都无需使用到,因此为了防止出现一些不必要的错误,统一将指令相对应的无关的使能控制信号(x)默认为低电平(0),无需ALU运算的(例如跳转指令)默认将其操作变为加操作(000).
完成控制信号、指令以及执行状态的关系表以后 ,对于如何实现多周期CPU依旧很困惑,主要不清楚如何将一条分多个时钟周期执行并且能够保持正确,以及如何保证中间过程不会使用到非对应的数据或在寄存器中写入了错误数据。此时,思考实验原理中的图2 多周期CPU状态转移图,总结多周期CPU状态情况,五个执行状态并非所有的指令都有,则我们需要创建一个状态机,具体如实验原理中的图3 多周期CPU控制部件的原理结构图,依据指令的操作码以及当前的CPU状态,在时钟的上升沿触发得到下一个CPU状态,同时控制信号亦依据当前状态进行修改。例如,在指令写回寄存器组阶段--sWB阶段的时候,控制信号RegWre为1,即寄存器组可以写此时,至于其他的执行阶段,则为0,即寄存器组使能端失效,不能写,依据当前CPU的状态设置控制信号可以有效的避免写入错误数据入寄存器组。结合实验原理中的图4 多周期CPU数据通路和控制线路图,首先将其模块化,分成多个模块相连起来,同时与之前设计的单周期CPU不同的是,需要添加一些临时数据的寄存器,其中寄存器ADR和BDR用来保留寄存器组中rs和rt中相对应的内容,寄存器ALUoutDR用来保留ALU的运算结果,寄存器DBDR用来保留写回数据,这些寄存器的主要作用是切分数据通路,将大组合逻辑切分为若干个小组合逻辑,大延迟变成多个分段小延迟。同时,为了使指令代码保持稳定,需要增加指令寄存器IR。
依据图2 多周期CPU数据通路和控制线路图,将CPU划分为14个模块。其中没有具体细分每个功能模块,将一些数据选择的模块并入到需要的功能模块中,没有完全依据多周期CPU数据通路图进行划分,否则需要过多模块,划分的太过冗余。模块划分结果如图三所示。
pcAdd
- 模块功能:根据控制信号PCSrc,计算获得下一个pc以及控制信号Reset重置。
- 实现思路:首先决定何时引起触发,将里面涉及的信号量作为敏感变量,主要是为了确保下一条pc能够正确得到。
实现代码:
module pcAdd( input RST, input [1:0] PCSrc, //数据选择器输入 input [31:0] immediate, //偏移量 input [25:0] addr, input [31:0] curPC, input [31:0] rs, output reg[31:0] nextPC //新指令地址 ); initial begin nextPC = 0; end reg [31:0] pc; always@(*) begin if(!RST) begin nextPC = 0; end else begin pc = curPC + 4; case(PCSrc) 2'b00: nextPC = curPC + 4; 2'b01: nextPC = curPC + 4 + immediate * 4; 2'b10: nextPC = rs; 2'b11: nextPC = {pc[31:28],addr,2'b00}; endcase end end endmodule
PC
- 模块功能:根据控制信号PCWre,判断pc是否改变以及根据Reset信号判断是否重置。
- 实现思路:将时钟信号的上升沿和控制信号Reset作为敏感变量,使得pc在上升沿的时候发生改变或被重置。
实现代码:
module PC( input CLK, //时钟 input RST, //是否重置地址。0-初始化PC,否则接受新地址 input PCWre, //是否接受新的地址。0-不更改;1-可以更改 input [31:0] nextPC, //新指令地址 output reg[31:0] curPC //当前指令的地址 ); initial begin curPC <= 0; end always@(posedge CLK or negedge RST) begin if(!RST) // Reset == 0, PC = 0 begin curPC <= 0; end else begin if(PCWre) // PCWre == 1 begin curPC <= nextPC; end else // PCWre == 0, halt begin curPC <= curPC; end end end endmodule
InsMEM
- 模块功能:依据当前pc和信号量InsMemRW,读取指令寄存器中,相对应地址的指令。
- 实现思路:将pc的输入作为敏感变量,当pc发生改变的时候,则进行指令的读取,根据相关的地址,输出指令寄存器中相对应的指令。
实现代码:
module InsMEM( input [31:0] IAddr, input InsMemRW, //状态为'0',写指令寄存器,否则为读指令寄存器 output reg[31:0] IDataOut ); reg [7:0] rom[128:0]; // 存储器定义必须用reg类型,存储器存储单元8位长度,共128个存储单元,可以存32条指令 // 加载数据到存储器rom。注意:必须使用绝对路径 initial begin //绝对路径 $readmemh("F:\\Vivado\\MultiCycleCPU\\romData.txt", rom); end //大端模式 always@(IAddr or InsMemRW) begin //取指令 if(InsMemRW) begin IDataOut[7:0] = rom[IAddr + 3]; IDataOut[15:8] = rom[IAddr + 2]; IDataOut[23:16] = rom[IAddr + 1]; IDataOut[31:24] = rom[IAddr]; end end endmodule
IR
- 模块功能:为了使指令代码保持稳定。
- 实现思路:将时钟上升沿作为敏感信号,同时依据信号量IRWre,对IR寄存器进行写入。
实现代码:
module IR( input [31:0] instruction, input CLK, input IRWre, output reg[31:0] IRInstruction ); initial begin IRInstruction = 0; end always@(posedge CLK) begin if(IRWre) begin IRInstruction <= instruction; end end endmodule
InstructionCut
- 模块功能:对指令进行分割,获得相对应的指令信息。
- 实现思路:根据各种类型的指令结构,将指令分割,得到相对应的信息。
实现代码:
module InstructionCut( input [31:0] instruction, output reg[5:0] op, output reg[4:0] rs, output reg[4:0] rt, output reg[4:0] rd, output reg[4:0] sa, output reg[15:0] immediate, output reg[25:0] addr ); initial begin op = 5'b00000; rs = 5'b00000; rt = 5'b00000; rd = 5'b00000; end always@(instruction) begin op = instruction[31:26]; rs = instruction[25:21]; rt = instruction[20:16]; rd = instruction[15:11]; sa = instruction[10:6]; immediate = instruction[15:0]; addr = instruction[25:0]; end endmodule
ControlUnit
- 模块功能:控制单元,依据指令的操作码(op)、标记符(ZERO)以及当前CPU状态,依据表三 控制信号、指令以及执行状态之间的相互关系,输出相匹配控制信号量。
- 实验思路:设计一个CPU的状态机,依据指令的操作码(op)、当前状态以及重置信号(RST),其状态在上升沿的时候发生,同时依据当前的状态、操作码(op)以及标记符(ZERO)修改并且输出控制信号。本模块为多周期CPU中最重要的模块,需要注意部分信号在特定的CPU状态才能输出相应的使能,否则将出现错误。
实现代码:
module ControlUnit( input CLK, input RST, input zero, //ALU运算结果是否为0,为0时候为1 input [5:0] op, //指令的操作码 output reg IRWre, //IR的写使能信号 output reg PCWre, //PC是否更改的信号量,为0时候不更改,否则可以更改 output reg ExtSel, //立即数扩展的信号量,为0时候为0扩展,否则为符号扩展 output reg InsMemRW, //指令寄存器的状态操作符,为0的时候写指令寄存器,否则为读指令寄存器 output reg WrRegDSrc, //写入寄存器的数据选择信号 output reg [1:0] RegDst,//写寄存器组寄存器的地址,为0的时候地址来自rt,为1的时候地址来自rd output reg RegWre, //寄存器组写使能,为1的时候可写 output reg ALUSrcA, //控制ALU数据A的选择端的输入,为0的时候,来自寄存器堆data1输出,为1的时候来自移位数sa output reg ALUSrcB, //控制ALU数据B的选择端的输入,为0的时候,来自寄存器堆data2输出,为1时候来自扩展过的立即数 output reg [1:0]PCSrc, //获取下一个pc的地址的数据选择器的选择端输入 output reg [2:0]ALUOp, //ALU 8种运算功能选择(000-111) output reg mRD, //数据存储器读控制信号,为0读 output reg mWR, //数据存储器写控制信号,为0写 output reg DBDataSrc //数据保存的选择端,为0来自ALU运算结果的输出,为1来自数据寄存器(Data MEM)的输出 ); reg [2:0] state, nextState; //记录状态 parameter [2:0] iniState = 3'b111, sIF = 3'b000, sID = 3'b001, sEXE = 3'b010, sMEM = 3'b100, sWB = 3'b011; initial begin state = iniState; PCWre = 0; InsMemRW = 0; IRWre = 0; RegWre = 0; ; ExtSel = 0; PCSrc = 2'b00; RegDst = 2'b11; ALUOp = 0; ExtSel = 0; WrRegDSrc = 0; ALUSrcA = 0; ALUSrcB = 0; DBDataSrc = 0; mRD = 0; mWR = 0; end //状态机 always@(posedge CLK) begin if(!RST) begin state <= sIF; end else begin state <= nextState; end end always@(state or op or zero) begin // 状态更新 case(state) iniState : nextState = sIF; sIF: nextState = sID; sID: begin case(op[5:3]) 3'b111: nextState = sIF; //指令j,jal,jr,halt default: nextState = sEXE; endcase end sEXE: begin if((op == 6'b110100) || (op == 6'b110110)) begin //beq,bltz nextState = sIF; end else if(op == 6'b110000 || op == 6'b110001) begin //sw,lw nextState = sMEM; end else begin nextState = sWB; end end sMEM: begin if(op == 6'b110000) begin //sw nextState = sIF; end else begin //lw nextState = sWB; end end sWB: nextState = sIF; endcase // 信号量 // PCWre and InsMemRW if(nextState == sIF && op != 6'b111111 && state != iniState) begin // halt PCWre = 1; InsMemRW = 1; end else begin PCWre = 0; InsMemRW = 0; end // IRWre if(state == sIF || nextState == sID) begin IRWre = 1; end else begin IRWre = 0; end // ALUSrcA if(op == 6'b011000) begin // sll ALUSrcA = 1; end else begin ALUSrcA = 0; end // ALUSrcB if(op == 6'b000010 || op == 6'b010010 || op == 6'b110000 || op == 6'b110001 || op == 6'b100111) begin // addi,ori,sw,lw,sltiu ALUSrcB = 1; end else begin ALUSrcB = 0; end // DBDataSrc if(op == 6'b110001) begin // lw DBDataSrc = 1; end else begin DBDataSrc = 0; end // RegWre and WrRegDSrc and RegDst if((state == sWB && op != 6'b110100 && op != 6'b110000 && op != 6'b110110) || (op == 6'b111010 && state == sID)) begin // 非beq,sw,bltz RegWre = 1; if(op == 6'b111010) begin // jal WrRegDSrc = 0; RegDst = 2'b00; end else begin WrRegDSrc = 1; if(op == 6'b000010 || op == 6'b010010 || op == 6'b100111 || op == 6'b110001) begin // addi, ori, sltiu, lw RegDst = 2'b01; end else begin // add, sub, or, and, slt, sll RegDst = 2'b10; end end end else begin RegWre = 0; end // InsMemRW if(op != 6'b111111) InsMemRW = 1; // mRD mRD = (op == 6'b110001) ? 1 : 0; // lw // mWR mWR = (state == sMEM && op == 6'b110000) ? 1 : 0; // sw // ExtSel ExtSel = (op == 6'b000010 || op == 6'b110001 || op == 6'b110000 || op == 6'b110100 || op == 6'b110110) ? 1 : 0; // addi、lw、sw、beq、bltz // PCSrc if(op == 6'b111001) begin // jr PCSrc = 2'b10; end else if((op == 6'b110100 && zero) || (op == 6'b110110 && !zero)) begin // beq 和 bltz跳转 PCSrc = 2'b01; end else if(op == 6'b111010 || op == 6'b111000) begin // j,jal PCSrc = 2'b11; end else begin PCSrc = 2'b00; end // ALUOp case(op) 6'b000010: ALUOp = 3'b000; // addi 6'b010010: ALUOp = 3'b101; // ori 6'b010000: ALUOp = 3'b101; // or 6'b000001: ALUOp = 3'b001; // sub 6'b010001: ALUOp = 3'b110; // and 6'b011000: ALUOp = 3'b100; // sll 6'b110100: ALUOp = 3'b001; // beq 6'b100110: ALUOp = 3'b011; // slt 6'b100111: ALUOp = 3'b010; // sltiu 6'b110110: ALUOp = 3'b001; // bltz 6'b110001: ALUOp = 3'b000; //sw 6'b110000: ALUOp = 3'b000; //lw endcase end endmodule
RegisterFile
- 模块功能:寄存器组,通过控制单元输出的控制信号,进行相对应的读或写操作。
- 实现思路:当ReadReg1或者ReadReg2发生改变的时候,即对寄存器组进行数据读取。至于数据写入,则选择在时钟的下降沿时候进行操作,同时写入信号(RegWre)必须为1且写入寄存器的地址不能为$0。至于数据的写入地址则在任意信号发生改变则依据写入地址的控制信号(RegDst)进行修改,使得其在数据写回阶段确保为正确的写入地址。
实现代码:
module RegisterFile( input CLK, //时钟 input [4:0] ReadReg1, //rs寄存器地址输入端口 input [4:0] ReadReg2, //rt寄存器地址输入端口 input [4:0] rd, //rd寄存器 input [31:0] WriteData, //写入寄存器的数据输入端口 input [1:0] RegDst, //写寄存器组地址选择信号 input RegWre, //WE,写使能信号,为1时,在时钟边沿触发写入 output reg[31:0] ReadData1, //rs寄存器数据输出端口 output reg[31:0] ReadData2, //rt寄存器数据输出端口 output reg[31:0] WriteReg //写回数据寄存器 ); initial begin ReadData1 <= 0; ReadData2 <= 0; WriteReg <= 0; end reg [31:0] regFile[0:31]; // 寄存器定义必须用reg类型 integer i; initial begin for (i = 0; i < 32; i = i+ 1) regFile[i] <= 0; end always@(ReadReg1 or ReadReg2) begin ReadData1 = regFile[ReadReg1]; ReadData2 = regFile[ReadReg2]; //$display("regfile %d %d\n", ReadReg1, ReadReg2); end always@(negedge CLK) begin //$0恒为0,所以写入寄存器的地址不能为0 if(RegWre && WriteReg) begin regFile[WriteReg] <= WriteData; end end always@(*) begin case(RegDst) 2'b00: WriteReg = 31; 2'b01: WriteReg = ReadReg2; 2'b10: WriteReg = rd; endcase; end endmodule
SignZeroExtend
- 模块功能:根据指令相关的控制信号ExtSel,对立即数进行扩展。
- 实现思路:根据控制信号ExtSel判断是0扩展还是符号扩展,然后进行相对应的扩展
实现代码:
module SignZeroExtend( input wire [15:0] immediate, //立即数 input ExtSel, //状态'0',0扩展,否则符号位扩展 output [31:0] extendImmediate ); assign extendImmediate[15:0] = immediate; assign extendImmediate[31:16] = ExtSel ? (immediate[15] ? 16'hffff : 16'h0000) : 16'h0000; endmodule
ALU
- 模块功能:算术逻辑单元,对两个输入依据ALUOp进行相对应的运算。
- 实现思路:依据实验原理中的ALU运算功能表(表2)完成操作码对应的操作,当ReadDatat1、ReadDatat2、ALUSrcA、ALUSrcB、ALUOp任意发生改变的时候,即进行运算。
实现代码:
module ALU( input ALUSrcA, input ALUSrcB, input [31:0] ReadData1, input [31:0] ReadData2, input [4:0] sa, input [31:0] extend, input [2:0] ALUOp, output reg zero, output reg[31:0] result ); reg [31:0] A; reg [31:0] B; initial begin result = 0; zero = 0; end always@(ReadData1 or ReadData2 or ALUSrcA or ALUSrcB or ALUOp) begin //定义两个输入端口 A = (ALUSrcA == 0) ? ReadData1 : sa; B = (ALUSrcB == 0) ? ReadData2 : extend; case(ALUOp) 3'b000: result = A + B; 3'b001: result = A - B; 3'b010: result = (A < B) ? 1 : 0; 3'b011: result = (((ReadData1 < ReadData2) && (ReadData1[31] == ReadData2[31] )) ||( ( ReadData1[31] ==1 && ReadData2[31] == 0))) ? 1:0; 3'b100: result = B << A; 3'b101: result = A | B; 3'b110: result = A & B; 3'b111: result = A ^ B; endcase zero = (result == 0) ? 1 : 0; end endmodule
DataMEM
- 模块功能:数据存储器,通过控制信号,对数据寄存器进行读或者写操作,并且此处模块额外合并了输出DB的数据选择器,此模块同时输出写回寄存器组的数据DB。
- 实现思路:将相关信号量作为敏感变量,假如读数据寄存器的信号量(mRD)为1的时候,则对数据寄存器进行读操作,假如写数据寄存器的信号量(mWR)为1的时候,则对数据寄存器进行写操作。
实现代码:
module DataMEM( /* Daddr,数据存储器地址输入端口 DataIn,数据存储器数据输入端口 DataOut,数据存储器数据输出端口 mRD,数据存储器读控制信号,为0读 mWR,数据存储器写控制信号,为0写 */ input mRD, input mWR, input DBDataSrc, input [31:0] DAddr, input [31:0] DataIn, output reg[31:0] DataOut, output reg[31:0] DB ); initial begin DB <= 16'b0; end reg [7:0] ram [0:31]; // 存储器定义必须用reg类型 always@(mRD or DAddr or DBDataSrc) begin //读 DataOut[7:0] = mRD ? ram[DAddr + 3] : 8'bz; // z 为高阻态 DataOut[15:8] = mRD ? ram[DAddr + 2] : 8'bz; DataOut[23:16] = mRD ? ram[DAddr + 1] : 8'bz; DataOut[31:24] = mRD ? ram[DAddr] : 8'bz; DB = (DBDataSrc == 0) ? DAddr : DataOut; end always@(mWR or DAddr) begin //写 if(mWR) begin ram[DAddr] = DataIn[31:24]; ram[DAddr + 1] = DataIn[23:16]; ram[DAddr + 2] = DataIn[15:8]; ram[DAddr + 3] = DataIn[7:0]; end //$display("mwr: %d $12 %d %d %d %d", mWR, ram[12], ram[13], ram[14], ram[15]); end endmodule
- 模块功能:切分数据通路,将大组合逻辑切分为若干个小组合逻辑,大延迟变为多个分段小延迟。
- 实现思路:在时钟上升沿将相应的数据写入寄存器中。四个寄存器功能相同,可以使用用一个模块实例得到。
实现代码:
module TempReg( input CLK, input [31:0] IData, output reg[31:0] OData ); initial begin OData = 0; end always@(posedge CLK) begin OData <= IData; end endmodule