从零开始写流水线——模块设计+指令定义
Posted aagnosticengineer
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从零开始写流水线——模块设计+指令定义相关的知识,希望对你有一定的参考价值。
此前在UCSB交换旁听了那边的ECE154b,课设是写MIPS+cache+branch prediction,个人感觉很有意思。现在就按照教授给的project guideline自己动手走一遍流程(见https://www.ece.ucsb.edu/~strukov/ece154bSpring2017/project1.pdf)。参考的书籍是Harris编写的 "Digital Design and Computer Architecture" 。
我写的MIPS处理器为5级流水线,分为取指->译码->执行->存储->回写,有冲突监测。
1.指令集编码
首先我们需要确定我们希望MIPS所能实现的指令,并确定指令的编码。对于该32bit微处理器,我们希望实现至少以下27种指令,我们分为三种:
R型指令:add, addu, sub, subu, and, or, xor, xnor, slt, sltu, lw, sw, mult, multu. 指令格式:OP, rd, rs, rt. 对于R型指令,将32位的指令各部分分配为opcode(6), rs(5), rt(5), rd(5), shamt(5), funct(6),其中opcode=6‘b0,对于不同指令的区分取决于funct部分。
I型指令:addi,addiu,andi,ori,xori,slti,sltiu,lw,sw,lui,bne,beq.指令格式:OP, rt, rs, imm. 对于I型指令,将32位的指令各部分分配为opcode(6), rs(5), rt (5), immediate(16)。
J型指令:j. 指令格式:OP, LABEL. 对于J型指令,将32位的指令各部分分配为opcode(6), address(26)。
我们将上述15种指令编码得到表1.
表1 MIPS指令编码
R-type instr |
operation | OP | rs | rt | rd | shamt | funct |
add | 000000 |
sssss (reg addr) |
ttttt (reg addr) |
ddddd (reg addr) |
00000 (no use) |
100000 | |
addu | 100001 | ||||||
sub | 100010 | ||||||
subu | 100011 | ||||||
and | 100100 | ||||||
or | 100101 | ||||||
xor | 100110 | ||||||
xnor | 100111 | ||||||
slt | 101010 | ||||||
sltu | 101011 | ||||||
mult | 000111 | ||||||
multu | 000101 |
I-type instr |
operation | op | rs | rt | imm |
addi | 001000 |
sssss (reg addr) |
ttttt (reg addr) |
imm |
|
addiu | 001001 | ||||
andi | 001100 | ||||
ori | 001101 | ||||
xori | 001110 | ||||
slti | 001011 | ||||
sltiu | 001100 | ||||
lw | 100011 | ||||
sw | 101001 | ||||
lui | 001111 | ||||
bne | 000101 | ||||
beq | 000100 |
J-type instr |
operation | OP | LABEL |
J | 000010 | address |
2.ALU设计
对于ALU,我们将模块设定为如图1所示(实际上我在此基础上加入了一个输出信号判断此时输出是否为0),是一个有3个输入、2个输出的组合逻辑电路。对于该ALU,我们希望实现除乘法外所有的运算环节(乘法耗时较长,我们另加一个独立单元计算,避免让流水线频率过低)。
图1 ALU模块图(来自project guideline)
图1中Func信号来自control模块读取opcode与funct等作出的判断,输入ALU中以判断执行何种操作。Func[2:0]用于实现+,-,&,|,^等运算,Func[3]用于区分符号数运算与非符号数运算,代码如下:
module ALU( input [31:0] In1E, input [31:0] In2E, input [3:0] FuncE, output reg[31:0] AluoutE, output ZeroE);//if ZeroE == 1, then AluoutE == 0 wire [31:0] In1_sign, In2_sign; wire [31:0] ScrA, ScrB; wire [31:0] minus; //calculate 2‘s complemental # assign In1_sign = (In1E[31])? ({In1E[31],(~(In1E[30:0]))}+32‘b00000000000000000000000000000001) : In1E; assign In2_sign = (In2E[31])? ({In2E[31],(~(In2E[30:0]))}+32‘b00000000000000000000000000000001) : In2E; //assign In1_sign = assign ScrA = (FuncE[3])? In1E : In1_sign; assign ScrB = (FuncE[3])? In2E : In2_sign; assign minus = ScrA - ScrB; assign ZeroE = AluoutE? 0 : 1; always @(*) begin case (FuncE[2:0]) 3‘b000 : AluoutE = ScrA+ScrB; 3‘b001 : AluoutE = minus;//ScrA-ScrB 3‘b010 : AluoutE = ScrA|ScrB; 3‘b011 : AluoutE = ScrA&ScrB; 3‘b100 : AluoutE = ScrA^ScrB; 3‘b101 : AluoutE = ~(ScrA^ScrB); 3‘b110 : AluoutE = {31‘b0,minus[31]};//slt 3‘b111 : AluoutE = {ScrB[15:0],16‘b0};//lui default : AluoutE = 32‘b0; endcase end endmodule
给出testbench,主要用来判断符号数与非符号数运算(我在这纠结了很久,被绕进去了,感觉ALU中的算法可能很蠢lol),其他就无所谓了因为很简单。
module AlU_tb(); reg [31:0] In1E; reg [31:0] In2E; reg [3:0] FuncE; wire [31:0] AluoutE; wire ZeroE; ALU ALU(.In1E(In1E), .In2E(In2E), .FuncE(FuncE), .AluoutE(AluoutE), .ZeroE(ZeroE)); initial begin In1E = 32‘b00000000000000000000000000000101; In2E = 32‘b10000000000000000000000000000101; FuncE = 4‘b1000; #200 FuncE = 4‘b0000; #200 FuncE = 4‘b1001; #200 FuncE = 4‘b0001; end endmodule
3.数据内存设计
数据内存为模拟cache功能,因为MIPS为RISC机,即寄存器与外部存储只能通过load/store指令交换数据,我们在开始一个新的程序时,必须要通过lw将操作数导入寄存器文件(Register File)。
module data_memory( input [31:0] address, input [31:0] write_data, input clk, input write,//enable signal output [31:0] read_data); reg [31:0] data_ram [63:0]; assign read_data = data_ram[address[31:2]];//word aligned always@(posedge clk) begin if (write) data_ram[address[31:2]] <= write_data; end endmodule
这一模块为时序模块,处于五级流水线的第四级(存储部分)。
4.指令存储
该模块为流水线第一级,取指部分,只有一个输入一个输出,在初始的时候我们将机器码写入指令存储代码内部的寄存器组中,每过一个时钟上升沿,将地址+4得到下一条指令,但遇到j,bne,beq时需要通过特殊的运算跳转到特定的地址取指,这一部分我将在后面的文章中给出(重度拖延症患者)。
module inst_memory( input [31:0] address, output [31:0] read_data); reg [31:0] inst_mem [63:0]; assign read_data = inst_mem[address[31:2]]; initial begin $readmemh("filename.dat",inst_mem); //initialize instruction memory end endmodule
5.寄存器组
这一模块为译码部分,我们通过译码选中不同的寄存器导出相应数据输送到ALU与乘法器中进行计算,此外也可以通过lw与sw指令与cache中的数据进行交换。其中两个读输出为rs与rt,为了避免在同一时刻进行读和写,我规定了在时钟上升沿读取寄存器数据,下降沿写出寄存器数据。
module reg_file( input [31:0] wd,//data written in input [4:0] wr,//write addr input clk,reg_wr,rst_n, input [4:0] pr1,pr2,//read addr output[31:0]rd1,rd2);//output data reg [31:0] rf [63:0]; integer i; always@(posedge clk or negedge rst_n) begin if (!rst_n) begin for (i=0;i<64;i=i+1) begin rf[i] <= 0; end end if (reg_wr) begin rf[wr] <= wd; end end assign rd1 = pr1?rf[pr1]:0; assign rd2 = pr2?rf[pr2]:0; endmodule
6.乘法器设计
因为乘法器耗时较长,所以我们将乘法功能从ALU中抽出来,可以直接用*号硬刚。
写好各个模块后我们就可以连接各个模块得到数据通路了。
以上是关于从零开始写流水线——模块设计+指令定义的主要内容,如果未能解决你的问题,请参考以下文章