------更新一下bug(测试代码有毒)-------
和单周期CPU的设计相同,都是为了实现一系列的指令功能,但需要指出的是何为多周期(注意与前面写道的单周期的区别,这也是设计的关键之处)
多周期CPU指的是将整个CPU的执行过程分成几个阶段,每个阶段用一个时钟去完成,然后开始下一条指令的执行,而每种指令执行时所用的时钟数不尽相同,这就是所谓的多周期CPU。
理解完多周期与单周期的区别后,开始我们的多周期CPU设计之路(可以随时对应单周期的设计,注意联系与区别)。
需要设计的指令及格式如下:
==>算术运算指令
(1)add rd, rs, rt
000000 |
rs(5位) |
rt(5位) |
rd(5位) |
reserved |
功能:rd<-rs + rt
(2)sub rd, rs, rt
000001 |
rs(5位) |
rt(5位) |
rd(5位) |
reserved |
完成功能:rd<-rs - rt
(3)addi rt, rs, immediate
000010 |
rs(5位) |
rt(5位) |
immediate(16位) |
功能:rt<-rs + (sign-extend)immediate
==>逻辑运算指令
(4)or rd, rs, rt
010000 |
rs(5位) |
rt(5位) |
rd(5位) |
reserved |
功能:rd<-rs | rt
(5)and rd, rs, rt
010001 |
rs(5位) |
rt(5位) |
rd(5位) |
reserved |
功能:rd<-rs & rt
(6)ori rt, rs, immediate
010010 |
rs(5位) |
rt(5位) |
immediate |
功能:rt<-rs | (zero-extend)immediate
==>移位指令
(7)sll rd, rs,sa
011000 |
rs(5位) |
未用 |
rd(5位) |
sa |
reserved |
功能:rd<-rs<<(zero-extend)sa,左移sa位 ,(zero-extend)sa
==>传送指令
(8)move rd, rs
100000 |
rs(5位) |
00000 |
rd(5位) |
reserved |
功能:rd<-rs + $0
==>比较指令
(9) slt rd, rs, rt
100111 |
rs(5位) |
rt(5位) |
rd(5位) |
reserved |
功能:如果(rs<rt),则rd=1; 否则 rd=0
==>存储器读写指令
(10)sw rt, immediate(rs)
110000 |
rs(5位) |
rt(5位) |
immediate(16位) |
功能:memory[rs+ (sign-extend)immediate]<-rt
(11)lw rt, immediate(rs)
110001 |
rs(5位) |
rt(5位) |
immediate(16位) |
功能:rt <- memory[rs + (sign-extend)immediate]
==>分支指令
(12)beq rs,rt, immediate (说明:immediate是从pc+4开始和转移到的指令之间间隔条数)
110100 |
rs(5位) |
rt(5位) |
immediate(16位) |
功能:if(rs=rt) pc <-pc+ 4 + (sign-extend)immediate <<2
==>跳转指令
(13)j addr
111000 |
addr[27..2] |
功能:pc <{pc[31..28],addr[27..2],0,0},转移
(14)jr rs
111001 |
rs(5位) |
未用 |
未用 |
reserved |
功能:pc<-rs,转移
==>调用子程序指令
(15)jal addr
111010 |
addr[27..2] |
功能:调用子程序,pc <- {pc[31..28],addr[27..2],0,0};$31<-pc+4,返回地址设置;子程序返回,需用指令 jr $31。
==>停机指令
(16)halt (停机指令)
111111 |
00000000000000000000000000(26位) |
不改变pc的值,pc保持不变。
设计原理
(1) 取指令(IF):根据程序计数器pc中的指令地址,从存储器中取出一条指令,同时,pc根据指令字长度自动递增产生下一条指令所需要的指令地址,但遇到“地址转移”指令时,则控制器把“转移地址”送入pc,当然得到的“地址”需要做些变换才送入pc。
(2) 指令译码(ID):对取指令操作中得到的指令进行分析并译码,确定这条指令需要完成的操作,从而产生相应的操作控制信号,用于驱动执行状态中的各种操作。
(3) 指令执行(EXE):根据指令译码得到的操作控制信号,具体地执行指令动作,然后转移到结果写回状态。
(4) 存储器访问(MEM):所有需要访问存储器的操作都将在这个步骤中执行,该步骤给出存储器的数据地址,把数据写入到存储器中数据地址所指定的存储单元或者从存储器中得到数据地址单元中的数据。
(5) 结果写回(WB):指令执行的结果或者访问存储器中得到的数据写回相应的目的寄存器中。
实验中就按照这五个阶段进行设计,这样一条指令的执行最长需要五个(小)时钟周期才能完成,但具体情况怎样?要根据该条指令的情况而定,有些指令不需要五个时钟周期的,这就是多周期的CPU。
MIPS32的指令的三种格式:
R类型:
31 26 25 21 20 16 15 11 10 6 5 0
op |
rs |
rt |
rd |
sa |
func |
6位 5位 5位 5位 5位 6位
I类型:
31 26 25 21 20 16 15 0
op |
rs |
rt |
immediate |
6位 5位 5位 16位
J类型:
31 26 25 0
op |
address |
6位 26位
其中,
op:为操作码;
rs:为第1个源操作数寄存器,寄存器地址(编号)是00000~11111,00~1F;
rt:为第2个源操作数寄存器,或目的操作数寄存器,寄存器地址(同上);
rd:为目的操作数寄存器,寄存器地址(同上);
sa:为位移量(shift amt),移位指令用于指定移多少位;
func:为功能码,在寄存器类型指令中(R类型)用来指定指令的功能;
immediate:为16位立即数,用作无符号的逻辑操作数、有符号的算术操作数、数据加载(Laod)/数据保存(Store)指令的数据地址字节偏移量和分支指令中相对程序计数器(PC)的有符号偏移量;
address:为地址。
状态的转移有的是无条件的,例如从IF状态转移到ID 和 EXE状态就是无条件的;有些是有条件的,例如ID 或 EXE状态之后不止一个状态,到底转向哪个状态由该指令功能,即指令操作码决定。每个状态代表一个时钟周期。
图3是多周期CPU控制部件的电路结构,三个D触发器用于保存当前状态,是时序逻辑电路,RST用于初始化状态“000“,另外两个部分都是组合逻辑电路,一个用于产生下一个阶段的状态,另一个用于产生每个阶段的控制信号。从图上可看出,下个状态取决于指令操作码和当前状态;而每个阶段的控制信号取决于指令操作码、当前状态和反映运算结果的状态zero标志等。
图4是一个简单的基本上能够在单周期上完成所要求设计的指令功能的数据通路和必要的控制线路图。其中指令和数据各存储在不同存储器中,即有指令存储器和数据存储器。访问存储器时,先给出地址,然后由读/写信号控制(1-写,0-读。当然,也可以由时钟信号控制,但必须在图上画出来)。对于寄存器组,读操作时,给出寄存器地址(编号),输出端就直接输出相应数据;而在写操作时,在 WE使能信号为1时,在时钟边沿触发写入。图中控制信号功能如表1所示,表2是ALU运算功能表。
特别提示,图上增加IR指令寄存器,目的是使指令代码保持稳定,还有pc增加写使能控制信号pcWre,也是确保pc适时修改,原因都是和多周期工作的CPU有关。ADR、BDR、ALUout、ALUM2DR四个寄存器不需要写使能信号,其作用是切分数据通路,将大组合逻辑切分为若干个小组合逻辑,大延时变为多个分段小延时。
表1 控制信号作用
控制信号名 |
状态“0” |
状态“1” |
PCWre |
PC不更改,相关指令:halt |
PC更改,相关指令:除指令halt外 |
ALUSrcB |
来自寄存器堆data2输出,相关指令:add、sub、addi、or、and、ori、move、beq、slt |
来自sign或zero扩展的立即数,相关指令:addi、ori、lw、sw、sll |
ALUM2Reg |
来自ALU运算结果的输出,相关指令:add、sub、addi、or、and、ori、slt、sll、move |
来自数据存储器(Data MEM)的输出,相关指令:lw |
RegWre |
无写寄存器组寄存器,相关指令: beq、j、sw、jr、halt |
寄存器组寄存器写使能,相关指令:add、sub、addi、or、and、ori、move、slt、sll、lw、jal |
WrRegData |
写入寄存器组寄存器的数据来自pc+4(pc4),相关指令:jal,写$31 |
写入寄存器组寄存器的数据来自存储器、寄存器组寄存器和ALU运算结果,相关指令:add、addi、sub、or、and、ori、slt、sll、move、lw |
InsMemRW |
读指令存储器(Ins. Data),初始化为0 |
写指令存储器 |
DataMemRW |
读数据存储器(Data MEM),相关指令:lw |
写数据存储器,相关指令:sw |
IRWre |
IR(指令寄存器)不更改 |
IR寄存器写使能。向指令存储器发出读指令代码后,这个信号也接着发出,在时钟上升沿,IR接收从指令存储器送来的指令代码。与每条指令都相关。 |
|
||
ALUOp[2..0] |
ALU 8种运算功能选择(000-111),看功能表 |
|
PCSrc[1..0] |
00:pc<-pc+4,相关指令:add、addi、sub、or、ori、and、move、 slt、sll、sw、lw、beq(zero=0) 01:pc<-pc+4+(sign-extend)immediate,同时zero=1,相关指令:beq 10:pc<-rs,相关指令:jr 11:pc<-pc(31..28],addr,0,0 ,相关指令:j、jal |
|
RegOut[1..0] |
写寄存器组寄存器的地址,来自: 00:0x1F($31),相关指令:jal,用于保存返回地址($31<-pc+4) 01:rt字段,相关指令:addi、ori、lw 10:rd字段,相关指令:add、sub、or、and、move、slt、sll 11:未用 |
|
ExtSel[1..0] |
00:(zero-extend)sa,相关指令:sll 01:(zero-extend)immediate,相关指令:ori 10:(sign-extend)immediate,相关指令:addi、lw、sw、beq 11:未用 |
相关部件及引脚说明:
InstructionMemory:指令存储器,
Iaddr,指令地址输入端口
DataIn,存储器数据输入端口
DataOut,存储器数据输出端口
RW,指令存储器读写控制信号,为1写,为0读
DataMemory:数据存储器,
Daddr,数据地址输入端口
DataIn,存储器数据输入端口
DataOut,存储器数据输出端口
RW,数据存储器读写控制信号,为1写,为0读
RegisterFile:(寄存器组)
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输出1,否则输出0
表2 ALU运算功能表
ALUOp[2..0] |
功能 |
描述 |
000 |
Y = A + B |
加 |
001 |
Y = A – B |
减 |
010 |
if (A<B) Y = 1; else Y = 0; |
比较A与B |
011 |
Y = A>>B |
A右移B位 |
100 |
Y = A<<B |
A左移B位 |
101 |
Y = A ∨ B |
或 |
110 |
Y = A ∧ B |
与 |
111 |
Y = A ⊕ B |
异或 |
分析与设计
此次实验是在上次单周期CPU基础上的改进,基本框架是相同的,但是相比单周期CPU的设计最大的不同就是“多周期”,何为多周期,在实现上与单周期又有何区别?简单的来说就是每个时钟周期内只执行一个阶段,而不是像单周期那样一个时钟周期就执行完整个指令,这就是单周期与多周期的主要区别。当然,由此也衍生出了其他的几个区别,比如,数据传输的延迟问题,增加的跳转指令等使得数据通路图变得复杂了很多。
根据这些区别,就可以开始在单周期CPU基础进行改进了。具体如下:
首先,确定每个指令的状态转化关系,具体转化图见上面原理分析,例如指令add的指令状态转化是IF(000)->ID(001)->EXE(110)->WB(111)->IF;所以,需要设置两个3位的状态变量(stage)和(next_stage)来表示状态的转变。由于指令是用来控制指令执行的,所以需要把指令状态的转变实现发在控制单元(controlUnit)中。
其次,就是数据传输延迟的问题,从数据通路图中可以看出,寄存器(RegisterFile)输出处存在两个延迟(ADR)和(BDR),计算单元(ALU)的输出处存在一个延迟,数据存储器(DataMemory)输出存在一个延迟,指令寄存器(InsMemory)输出处存在一个延迟,当然这里延迟需要控制信号IRWre的额外控制。综上来看,前四个延迟可以设计一个叫DataLate的简单模板模块(因为它们的输入、输出完全相同),具体实现如下。最后一个延迟可以放在INSMemory模块中。
module DataLate(input [31:0] i_data,
input clk,
output reg [31:0] o_data);
always @(negedge clk) begin
o_data = i_data;
end
endmodule
最后就是融入增加的跳转指令,比如根据数据通路图增添了一个如下的地址模块
另外,其他增加的数据线的增加就具体加入到相应模块中作为输入、输出。
所以,现在在单周期CPU的基础上,可以画出整个多周期CPU的逻辑图。
一、 控制单元(controlUnit.v)
相比单周期的CU,多周期的CU在输入输出上大致相同,但具体控制内容、存在比较大的差别。
1、指令状态转化的实现,前面已经提到。
2、控制信号的赋值。由于多周期指令信号控制状态的不同而可能改变,所以这里实现各控制信号的时候不再像单周期那样单纯利用操作码来实现。
类似的,写出控制信号与指令、指令状态的关系表,如下:
Stage |
Ins |
Zero |
PCWre |
ALUSrcB |
ALUM2Reg |
RegWre |
WrRegData |
InsMemRW |
DataMemRW |
IRWre |
ExtSel[1..0] |
PCSrc [1..0] |
RegOut [1..0] |
ALUOp[2..0] |
sif (000) |
x |
x |
1 |
x |
x |
0 |
x |
1 |
0 |
1 |
xx |
xx |
xx |
xxx |
sid (001) |
j |
x |
0 |
x |
x |
0 |
x |
x |
0 |
0 |
xx |
11 |
xx |
xxx |
jal |
x |
0 |
x |
x |
1 |
0 |
x |
0 |
0 |
xx |
11 |
00 |
xxx |
|
jr |
x |
0 |
x |
x |
0 |
x |
x |
0 |
0 |
xx |
10 |
xx |
xxx |
|
halt |
x |
0 |
x |
x |
0 |
x |
x |
0 |
0 |
xx |
xx |
xx |
xxx |
|
exe1 (110) |
add |
x |
0 |
0 |
x |
0 |
x |
x |
0 |
0 |
xx |
xx |
xx |
000 |
sub |
x |
0 |
0 |
x |
0 |
x |
x |
0 |
0 |
xx |
xx |
xx |
001 |
|
addi |
x |
0 |
1 |
x |
0 |
x |
x |
0 |
0 |
10 |
xx |
xx |
000 |
|
or |
x |
0 |
0 |
x |
0 |
x |
x |
0 |
0 |
xx |
xx |
xx |
101 |
|
and |
x |
0 |
0 |
x |
0 |
x |
x |
0 |
0 |
xx |
xx |
xx |
110 |
|
ori |
x |
0 |
1 |
x |
0 |
x |
x |
0 |
0 |
01 |
xx |
xx |
101 |
|
move |
x |
0 |
0 |
x |
0 |
x |
x |
0 |
0 |
xx |
xx |
xx |
000 |
|
slt |
x |
0 |
0 |
x |
0 |
x |
x |
0 |
0 |
xx |
xx |
xx |
010 |
|
sll |
x |
0 |
1 |
x |
0 |
x |
x |
0 |
0 |
00 |
xx |
xx |
100 |
|
exe2 (101) |
beq |
0 |
0 |
0 |
x |
0 |
x |
x |
0 |
0 |
10 |
00 |
xx |
001 |
beq |
1 |
0 |
0 |
x |
0 |
x |
x |
0 |
0 |
10 |
01 |
xx |
001 |
|
exe3 (010) |
sw |
x |
0 |
1 |
x |
0 |
x |
x |
0 |
0 |
10 |
xx |
xx |
000 |
lw |
x |
0 |
1 |
x |
0 |
x |
x |
0 |
0 |
10 |
xx |
xx |
000 |
|
smem (011) |
sw |
x |
0 |
x |
x |
0 |
x |
x |
1 |
0 |
10 |
00 |
xx |
xxx |
lw |
x |
0 |
x |
x |
0 |
x |
x |
0 |
0 |
10 |
xx |
xx |
xxx |
|
wb1 (111) |
add |
x |
0 |
x |
0 |
1 |
1 |
x |
0 |
0 |
xx |
00 |
10 |
xxx |
sub |
x |
0 |
x |
0 |
1 |
1 |
x |
0 |
0 |
xx |
00 |
10 |
xxx |
|
addi |
x |
0 |
x |
0 |
1 |
1 |
x |
0 |
0 |
xx |
00 |
01 |
xxx |
|
or |
x |
0 |
x |
0 |
1 |
1 |
x |
0 |
0 |
xx |
00 |
10 |
xxx |
|
and |
x |
0 |
x |
0 |
1 |
1 |
x |
0 |
0 |
xx |
00 |
10 |
xxx |
|
ori |
x |
0 |
x |
0 |
1 |
1 |
x |
0 |
0 |
xx |
00 |
01 |
xxx |
|
move |
x |
0 |
x |
0 |
1 |
1 |
x |
0 |
0 |
xx |
00 |
10 |
xxx |
|
slt |
x |
0 |
x |
0 |
1 |
1 |
x |
0 |
0 |
xx |
00 |
10 |
xxx |
|
sll |
x |
0 |
x |
0 |
1 |
1 |
x |
0 |
0 |
xx |
00 |
10 |
xxx |
|
wb2 (100) |
lw |
x |
0 |
x |
1 |
1 |
1 |
x |
0 |
0 |
xx |
00 |
01 |
xxx |
根据以上关系表,写出对应控制信号的实现。
`timescale 1ns / 1ps module controlUnit(input [5:0] opcode, input zero, clk, Reset, output reg PCWre, InsMemRW, IRWre, WrRegData, RegWre, ALUSrcB, ALUM2Reg, DataMemRW, output reg [1:0] ExtSel, RegOut, PCSrc, output reg [2:0] ALUOp, state_out); parameter [2:0] sif = 3'b000, // IF state sid = 3'b001, // ID state exe1 = 3'b110, // add、sub、addl、or、and、ori、move、slt、sll exe2 = 3'b101, // beq exe3 = 3'b010, // sw、lw smem = 3'b011, // MEM state wb1 = 3'b111, // add、sub、addl、or、and、ori、move、slt、sll wb2 = 3'b100; // lw parameter [5:0] addi = 6'b000010, ori = 6'b010010, sll = 6'b011000, add = 6'b000000, sub = 6'b000001, move = 6'b100000, slt = 6'b100111, sw = 6'b110000, lw = 6'b110001, beq = 6'b110100, j = 6'b111000, jr = 6'b111001, Or = 6'b010000, And = 6'b010001, jal = 6'b111010, halt = 6'b111111; reg [2:0] state, next_state; initial begin PCWre = 0; InsMemRW = 0; IRWre = 0; WrRegData = 0; RegWre = 0; ALUSrcB = 0; ALUM2Reg = 0; DataMemRW = 0; ExtSel = 2'b11; RegOut = 2'b11; PCSrc = 2'b00; ALUOp = 0; state = sif; state_out = state; end always @(posedge clk) begin if (Reset == 0) begin state <= sif; end else begin state <= next_state; end state_out = state; end always @(state or opcode) begin case(state) sif: next_state = sid; sid: begin case (opcode[5:3]) 3'b111: next_state = sif; // j, jal, jr, halt等指令 3'b110: begin if (opcode == 6'b110100) next_state = exe2; // beq指令 else next_state = exe3; // sw, lw指令 end default: next_state = exe1; // add, sub, slt, sll等指令 endcase end exe1: next_state = wb1; exe2: next_state = sif; exe3: next_state = smem; smem: begin if (opcode == 6'b110001) next_state = wb2; // lw指令 else next_state = sif; // sw指令 end wb1: next_state = sif; wb2: next_state = sif; default: next_state = sif; endcase end always @(state) begin // 确定PCWre的值 if (state == sif && opcode != halt) PCWre = 1; else PCWre = 0; // 确定InsMemRW的值 InsMemRW = 1; // 确定IRWre的值 if (state == sif) IRWre = 1; else IRWre = 0; // 确定WrRegData的值 if (state == wb1 || state == wb2) WrRegData = 1; else WrRegData = 0; // 确定RegWre的值 if (state == wb1 || state == wb2 || opcode == jal) RegWre = 1; else RegWre = 0; // 确定ALUSrcB的值 if (opcode == addi || opcode == ori || opcode == sll || opcode == sw || opcode == lw) ALUSrcB = 1; else ALUSrcB = 0; // 确定DataMemRW的值 if (state == smem && opcode == sw) DataMemRW = 1; else DataMemRW = 0; // 确定ALUM2Reg的值 if (state == wb2) ALUM2Reg = 1; else ALUM2Reg = 0; // 确定ExtSel的值 if (opcode == ori) ExtSel = 2'b01; else if (opcode == sll) ExtSel = 2'b00; else ExtSel = 2'b10; // 确定RegOut的值 if (opcode == jal) RegOut = 2'b00; else if (opcode == addi || opcode == ori || opcode == lw) RegOut = 2'b01; else RegOut = 2'b10; // 确定PCSrc的值 case(opcode) j: PCSrc = 2'b11; jal: PCSrc = 2'b11; jr: PCSrc = 2'b10; beq: begin if (zero) PCSrc = 2'b01; else PCSrc = 2'b00; end default: PCSrc = 2'b00; endcase // 确定ALUOp的值 case(opcode) sub: ALUOp = 3'b001; Or: ALUOp = 3'b101; And: ALUOp = 3'b110; ori: ALUOp = 3'b101; slt: ALUOp = 3'b010; sll: ALUOp = 3'b100; beq: ALUOp = 3'b001; default: ALUOp = 3'b000; endcase // 防止在IF阶段写数据 if (state == sif) begin RegWre = 0; DataMemRW = 0; end end endmodule
二、 算术运算单元(ALU)
模块ALU接收寄存器的数据和控制信号作为输入,将结果输出,具体设计如下:
`timescale 1ns / 1ps module ALU(input [31:0] ReadData1, ReadData2, inExt, input ALUSrcB, input [2:0] ALUOp, output wire zero, output reg [31:0] result); initial begin result = 0; end wire [31:0] B; assign B = ALUSrcB? inExt : ReadData2; assign zero = (result? 0 : 1); always @(ReadData1 or ReadData2 or B or ALUOp) begin case(ALUOp) 3'b000: result = ReadData1 + B; // A + B 3'b001: result = ReadData1 - B; // A - B 3'b010: result = (ReadData1 < B ? 1 : 0); // 比较A与B 3'b011: result = ReadData1 >> B; // A右移B位 3'b100: result = ReadData1 << B; // A左移B位 3'b101: result = ReadData1 | B; // 或 3'b110: result = ReadData1 & B; // 与 3'b111: result = (~ReadData1 & B) | (ReadData1 & ~B); // 异或 default: result = 0; endcase end endmodule
三、 PC模块(PC)
相比单周期的PC单元,这里的PC模块中多了一个四选一的的地址数据选择器,目的在于根据控制信号正确匹配pc地址,同样输出当前PC地址,具体设计如下:
`timescale 1ns / 1ps module PC(input clk, Reset, PCWre, input [1:0] PCSrc, input wire [31:0] imm, addr, RDout1, output reg [31:0] Address); always @(PCWre or negedge Reset) begin // 这里和单周期不太一样,存在延迟的问题,只有当pcWre改变的时候或者Reset改变的时候再检测 if (Reset == 0) begin Address = 0; end else if (PCWre) begin if (PCSrc == 2'b00) begin Address = Address+4; end else if (PCSrc == 2'b01) begin Address = imm*4+Address+4; end else if (PCSrc == 2'b10) begin Address = RDout1; end else if (PCSrc == 2'b11) begin Address = addr; end end end endmodule
四、 PCAddr模块(补充address)
用于跳转指令的地址补充,输出32位的地址,模块实现如下:
`timescale 1ns / 1ps module PCAddr(input [25:0] in_addr, input [31:0] PC0, output reg [31:0] addr); wire [27:0] mid; assign mid = in_addr << 2; always @(in_addr) begin addr <= {PC0[31:28], mid[27:0]}; end endmodule
五、 扩展单元(Extend)
相比单周期的扩展,此处的扩展内容多了一些,包括sa扩展、立即数扩展等,扩展选择由控制信号ExtSel控制,最后输出完整32位数据。
`timescale 1ns / 1ps module Extend(input [15:0] in_num, input [1:0] ExtSel, output reg [31:0] out); always @(in_num or ExtSel) begin case(ExtSel) 2'b00: out <= {{27{0}}, in_num[10:6]}; // 扩充 sa 2'b01: out <= {{16{0}}, in_num[15:0]}; // 扩充立即数, 如 ori指令 2'b10: out <= {{16{in_num[15]}}, in_num[15:0]}; // 符号扩充立即数,如addi、lw、sw、beq指令 default: out <= {{16{in_num[15]}}, in_num[15:0]}; // 默认符号扩展 endcase end endmodule
六、 数据存储单元(DataMemory)
数据存储单元的功能是读取数据,根据数据通路图可以有如下模块设计:
`timescale 1ns / 1ps module DataMemory(input [31:0] addr, Data2, input DataMemRW, output reg [31:0] DataOut); reg [7:0] memory [0:63]; integer i; initial begin for (i = 0; i < 64; i = i+1) memory[i] <= 0; end always @(addr or Data2 or DataMemRW) begin if (DataMemRW) begin // write data memory[addr] = Data2[31:24]; memory[addr+1] = Data2[23:16]; memory[addr+2] = Data2[15:8]; memory[addr+3] = Data2[7:0]; end else begin // read data DataOut[31:24] = memory[addr]; DataOut[23:16] = memory[addr+1]; DataOut[15:8] = memory[addr+2]; DataOut[7:0] = memory[addr+3]; end end endmodule
七、 指令存储单元(InsMemory)
将指令集以二进制的文件(my_store.txt)存入当前目录,然后通过读取文件的方式将指令存储到内存中,最后实现指令的读取。其中,
内部指令实现:
将需要测试的汇编指令程序转化为指令代码,如下:
地址 |
汇编程序 |
指令代码 |
|||||
op(6) |
rs(5) |
rt(5) |
rd(5)/immediate (16) |
16进制数代码 |
|||
0x00000000 |
j 0x00000008 |
111000 |
00 00000000 00000000 00000010 |
|
|||
0x00000004 |
jr $31 |
111001 |
11111 |
00000 |
|||
0x00000008 |
addi $1,$0,8 |
000010 |
00000 |
00001 |
0000 0000 0000 1000 |
= |
08010008 |
0x0000000C |
ori $2,$0,2 |
010010 |
00000 |
00010 |
0000 0000 0000 0010 |
= |
48020002 |
0x00000010 |
add $3,$1,$2 |
000000 |
00001 |
00010 |
00011 00000000000 |
= |
00221800 |
0x00000014 |
sub $4,$1,$2 |
000001 |
00001 |
00010 |
00100 00000000000 |
= |
04222000 |
0x00000018 |
and $5,$3,$2 |
010001 |
00011 |
00010 |
00101 00000000000 |
= |
44622800 |
0x0000001C |
or $6,$1,$2 |
010000 |
00001 |
00010 |
00110 00000000000 |
= |
40223000 |
0x00000020 |
move $11,$1 |
100000 |
00001 |
00000 |
01011 00000000000 |
= |
80205800 |
0x00000024 |
slt $7,$1,$2 |
100111 |
00001 |
00010 |
00111 00000000000 |
= |
9C223800 |
0x00000028 |
slt $8,$2,$1 |
100111 |
00010 |
00001 |
01000 00000000000 |
= |
9C414000 |
0x0000002C |
sll $2,$2,2 |
011000 |
00010 |
00000 |
00010 00010 000000 |
= |
60401080 |
0x00000030 |
beq $1,$2,-2 转02C |
110100 |
00001 |
00010 |
1111 1111 1111 1110 |
= |
D022FFFE |
0x00000034 |
sw $9,0($3) |
110000 |
00011 |
01001 |
0000 0000 0000 0000 |
= |
C0690000 |
0x00000038 |
jal 0x00000004 |
111010 |
00 00000000 00000000 0000001 |
= |
|||
0x0000003C |
lw $10,2($1) |
110001 |
00001 |
01010 |
0000 0000 0000 0010 |
= |
C42A0002 |
0x00000040 |
halt |
111111 |
00000 |
00000 |
0000000000000000 |
= |
FC000000 |
根据上表,可以创建my_store.txt的二进制指令文件,从而进行存取,二进制文件如下:
最终模块设计如下:
`timescale 1ns / 1ps module InsMemory(input [31:0] addr, input InsMemRW, IRWre, clk, output reg [31:0] ins); reg [31:0] ins_out; reg [7:0] mem [0:127]; initial begin $readmemb("my_store.txt", mem); //ins_out = 0; end always @( addr or InsMemRW) begin if (InsMemRW) begin ins_out[31:24] = mem[addr]; ins_out[23:16] = mem[addr+1]; ins_out[15:8] = mem[addr+2]; ins_out[7:0] = mem[addr+3]; end end always @(posedge clk) begin if (IRWre) ins <= ins_out; end endmodule
八、 寄存器单元(RegFile)
寄存器文件单元的功能是接收instructionMemory中的rs,rt,rd作为输入,输出对应寄存器的数据,从而达到取寄存器里的数据的目的,具体设计如下:
需要注意的是,在其内部实现的过程中,为了防止0号寄存器写入数据需要在writeReg的时候多加入一个判断条件,即writeReg不等于0时写入数据。
`timescale 1ns / 1ps module RegFile(input [4:0] rs, rt, rd, input clk, RegWre, WrRegData, input [1:0] RegOut, input [31:0] PC4, memData, output reg [31:0] data1, data2); reg [31:0] i_data; reg [4:0] temp; reg [31:0] register [0:31]; integer i; initial begin for (i = 0 ; i < 32; i = i+1) register[i] = 0; end always @(negedge clk) begin case(RegOut) 2'b00: temp = 5'b11111; 2'b01: temp = rt; 2'b10: temp = rd; default temp = 0; endcase assign i_data = WrRegData? memData : PC4; assign data1 = register[rs]; assign data2 = register[rt]; if ((temp != 0) && (RegWre == 1)) begin // temp != 0 确保零号寄存器不会改变 register[temp] <= i_data; end end endmodule
九、 二选一数据模块(DataSelect_2)
简单的数据二选一,用于数据存储单元后面的数据选择,可见数据通路图,实现如下:
`timescale 1ns / 1ps module DataSelect_2(input [31:0] A, B, input Sign, output wire [31:0] Get); assign Get = Sign ? B : A; endmodule
十、 数据延迟模板模块(DataLate)
用于数据延迟,目的是使得数据正常输入输出,从数据通路图中可知此模板可在四处地方有用,已分析,所以具体模板实现如下:
`timescale 1ns / 1ps module DataLate(input [31:0] i_data, input clk, output reg [31:0] o_data); always @(negedge clk) begin o_data = i_data; end endmodule
十一、顶层模块(Top)
顶层模块(Top)是整个CPU的控制模块,通过连接各个子模块来达到运行CPU的目的,整个模块设计可以如下:
`include "ALU.v" `include "DataLate.v" `include "DataMemory.v" `include "DataSelect_2.v" `include "Extend.v" `include "InsMemory.v" `include "PC.v" `include "PCAddr.v" `include "RegFile.v" `include "ControlUnit.v" `timescale 1ns / 1ps module Top(input clk, reset, output wire [2:0] state_out, output wire [5:0] opcode, output wire [4:0] rs, rt, rd, // output ins[31:26], ins[25:21], ins[20:16], ins[15:11], output wire [31:0] ins, ReadData1, ReadData2, pc0, result); assign opcode = ins[31:26]; assign rs = ins[25:21]; assign rt = ins[20:16]; assign rd = ins[15:11]; // 数据通路 wire [31:0] j_addr, out1, out2, result1, i_IR, extendData, LateOut1, LateOut2, DataOut; wire zero; // 控制信号 wire [2:0] ALUOp; wire [1:0] ExtSel, RegOut, PCSrc; wire PCWre, IRWre, InsMemRW, WrRegData, RegWre, ALUSrcB, DataMemRW, ALUM2Reg; PC pc(clk, reset, PCWre, PCSrc, extendData, j_addr, ReadData1, pc0); InsMemory insmemory(pc0, InsMemRW, IRWre, clk, ins); PCAddr pcaddr(ins[25:0], pc0, j_addr); RegFile regfile(ins[25:21], ins[20:16], ins[15:11], clk, RegWre, WrRegData, RegOut, (pc0+4), LateOut2, ReadData1, ReadData2); DataLate ADR(ReadData1, clk, out1); DataLate BDR(ReadData2, clk, out2); Extend extend(ins[15:0], ExtSel, extendData); ALU alu(out1, out2, extendData, ALUSrcB, ALUOp, zero, result); DataLate ALUout(result, clk, result1); DataMemory datamemory(result1, out2, DataMemRW, DataOut); DataSelect_2 dataselect_2(result, DataOut, ALUM2Reg, LateOut1); DataLate ALUM2DR(LateOut1, clk, LateOut2); controlUnit control(ins[31:26], zero, clk, reset,PCWre, InsMemRW, IRWre, WrRegData, RegWre, ALUSrcB, ALUM2Reg, DataMemRW, ExtSel, RegOut, PCSrc, ALUOp, state_out); endmodule
十二、测试程序(test)
从顶层模块中可以看出整个CPU的输入只有时钟信号clk和重置信号Reset,所以测试程序代码比较简单。(参照单周期CPU)
clk = 0;
reset= 0;
clk= ~clk;
//Wait 100 ns for global reset to finish
#100;
reset = 1;
forever #100 clk = ~clk;
最后,套路贴结果(部分):
至此,完工!!!