CPU设计之二——VerilogHDL 开发流水线处理器(支持42条指令)
CPU设计之三——VerilogHDL 开发流水线处理器(支持50条指令)
所有代码和参考文件已经上传至github:https://github.com/lcy19981225/Single-Cycle-CPU-10
MIPS32指令集的pdf我已经上传到github中,建议大家先好好看看自己要完成的命令的具体含义再开始写!
取指令(IF):根据程序计数器PC中的指令地址,从指令存储器中取出一条指令,同时PC根据指令字长度自动递增产生下一条指令所需要的指令地址,但遇到“地址转移”指令时,则控制器把“转移地址”送入PC。
指令译码(ID):对取指令操作中得到的指令进行分析并译码,确定这条指令需要完成的操作,具体方式根据自己的设定而定
指令执行(EXE):根据指令译码得到的操作控制信号,具体地执行指令动作,然后转移到结果写回状态。
存储器访问(MEM):所有需要访问存储器的操作都将在这个步骤中执行,该步骤给出存储器的数据地址,把数据写入到存储器中数据地址所指定的存储单元或者从存储器中得到数据地址单元中的数据。
结果写回(WB):指令执行的结果或者访问存储器中得到的数据写回相应的目的寄存器中。
Mars可以将你自己写的测试汇编代码转换成机器码,这样就可以生成你自己想要的而测试样例,如果没有使用过可以搜索一下相关博客啥的,Mars.jar我也同样上传到github中了~
PC需要对reset进行判断,如果不需要重置,那么只用把PC置为下一条指令即可
module PC(Clk,Reset,Result,Address);
input Clk;//时钟
input Reset;//是否重置地址。0-初始化PC,否则接受新地址
input[31:0] Result;
output reg[31:0] Address;
initial begin
Address <= 0;
end
always @(posedge Clk or negedge Reset)
begin
if (!Reset) //如果为0则初始化PC,否则接受新地址
begin
Address <= 0;
end
else
begin
Address = Result;
end
end
endmodule
PCAdd4只需要对PC+4就行,按照之前讲的超前进位加法器写了一个cal32用来进行32位加法,代码放在GitHub上了~
module PCAdd4(PC_o,PCadd4);
input [31:0] PC_o;//偏移量
output [31:0] PCadd4;//新指令地址
CLA_32 cla32(PC_o,4,0, PCadd4, Cout);
endmodule
ALU用来进行各种运算,需要设置一个多路选择器判断ALU B端的输入是来自寄存器堆还是来自扩展单元,同时当运算结果为0是,将控制信号ZERO置为1
module ALU( ReadData1, ReadData2,ALUOp,result,zero);
input [31:0] ReadData1;
input [31:0] ReadData2;
input [2:0] ALUOp;
output reg[31:0] result;
output reg zero;
always@(ReadData1 or ReadData2 or ALUOp)
begin
case(ALUOp)
3'b000: result = ReadData1 + ReadData2;
3'b001: result = ReadData1 - ReadData2;
3'b010: result = ReadData2 & ReadData1;
3'b011: result = ReadData1 | ReadData2;
3'b100: result = ReadData2<<16;
endcase
zero = (result == 0) ? 1 : 0;
end
endmodule
控制单元控制各种信号的值,控制信号有
以上控制信号需要一个5位二选一多路选择器,两个个32位二选一多路选择器,一个32位五选一多路选择器,具体实现在MUX.v中介绍
module CONUNIT(Op,Func,Z,Regrt,Se,Wreg,Aluqb,Aluc,Wmem,Pcsrc,Reg2reg);
input[5:0]Op,Func;
input Z;
output Regrt,Se,Wreg,Aluqb,Wmem,Reg2reg;
output[2:0]Pcsrc;
output [2:0]Aluc;
wire R_type=~|Op;
wire I_add=R_type&Func[5]&~Func[4]&~Func[3]&~Func[2]&~Func[1]&~Func[0];
wire I_sub=R_type&Func[5]&~Func[4]&~Func[3]&~Func[2]&Func[1]&~Func[0];
wire I_and=R_type&Func[5]&~Func[4]&~Func[3]&Func[2]&~Func[1]&~Func[0];
wire I_or=R_type&Func[5]&~Func[4]&~Func[3]&Func[2]&~Func[1]&Func[0];
wire I_jr=R_type&~Func[5]&~Func[4]&Func[3]&~Func[2]&~Func[1]&~Func[0];
wire I_addi=~Op[5]&~Op[4]&Op[3]&~Op[2]&~Op[1]&~Op[0];
wire I_andi=~Op[5]&~Op[4]&Op[3]&Op[2]&~Op[1]&~Op[0];
wire I_ori=~Op[5]&~Op[4]&Op[3]&Op[2]&~Op[1]&Op[0];
wire I_lw=Op[5]&~Op[4]&~Op[3]&~Op[2]&Op[1]&Op[0];
wire I_sw=Op[5]&~Op[4]&Op[3]&~Op[2]&Op[1]&Op[0];
wire I_beq=~Op[5]&~Op[4]&~Op[3]&Op[2]&~Op[1]&~Op[0];
wire I_bne=~Op[5]&~Op[4]&~Op[3]&Op[2]&~Op[1]&Op[0];
wire I_J=~Op[5]&~Op[4]&~Op[3]&~Op[2]&Op[1]&~Op[0];
wire I_lui=~Op[5]&~Op[4]&Op[3]&Op[2]&Op[1]&Op[0];
wire I_jal=~Op[5]&~Op[4]&~Op[3]&~Op[2]&Op[1]&Op[0];
assign Regrt=I_addi|I_andi|I_ori|I_lw|I_sw|I_beq|I_bne|I_J|I_lui|I_jal;
assign Se=I_addi|I_lw|I_sw|I_beq|I_bne|I_lui;
assign Wreg=I_add|I_sub|I_and|I_or|I_addi|I_andi|I_ori|I_lw|I_lui|I_jal;
assign Aluqb=I_add|I_sub|I_and|I_or|I_beq|I_bne|I_J|I_jal|I_jr;
assign Aluc[2]=I_lui;
assign Aluc[1]=I_and|I_or|I_andi|I_ori;
assign Aluc[0]=I_sub|I_or|I_ori|I_beq|I_bne;
assign Wmem=I_sw;
assign Pcsrc[2]=I_jal|I_jr;
assign Pcsrc[1]=I_beq&Z|I_bne&~Z|I_J;
assign Pcsrc[0]=I_J|I_jr;
assign Reg2reg=I_add|I_sub|I_and|I_or|I_addi|I_andi|I_ori|I_sw|I_beq|I_bne|I_J|I_lui|I_jal|I_jr;
endmodule
只有lw,sw会用到,分别用来存数据和读数据
module dm_4k(Addr,Din,We,Clk,Dout);
input[31:0]Addr,Din;
input Clk,We;
output[31:0]Dout;
reg[31:0]Ram[1024:0];
assign Dout=Ram[Addr[6:2]];
always@(posedge Clk)begin
if(We)
Ram[Addr[6:2]]<=Din;
end
integer i;
initial begin
for(i=0;i<32;i=i+1)
Ram[i]=0;
end
endmodule
从code.txt读取指令码
module im_4k(Addr,Inst);//指令存储器
input[31:0]Addr;
reg [31:0]Rom[1024:0];
output[31:0]Inst;
initial
begin
$readmemh("C:\\Users\\Y\\Desktop\\code.txt", Rom);
end
integer i;
initial begin
$display("start simulation");
for (i=0;i<20;i=i+1)
$display("%h %h", i,Rom[i]);
end
assign Inst=Rom[Addr[6:2]];
endmodule
分别实现了5位二选一多路选择器,32位二选一多路选择器,32位五选一多路选择器,每一个选择器都有信号S,根据信号S可以决定输出
module MUX2X5(A0,A1,S,Y);
input [4:0] A0,A1;
input S;
output [4:0] Y;
function [4:0] select;
input [4:0] A0,A1;
input S;
case(S)
0:select=A0;
1:select=A1;
endcase
endfunction
assign Y=select(A0,A1,S);
endmodule
module MUX2X32(A0,A1,S,Y);
input [31:0] A0,A1;
input S;
output [31:0] Y;
function [31:0] select;
input [31:0] A0,A1;
input S;
case(S)
0:select=A0;
1:select=A1;
endcase
endfunction
assign Y=select(A0,A1,S);
endmodule
module MUX5X32 (A0, A1, A2, A3, A4, S, Y);
input [31:0] A0, A1, A2, A3, A4;
input [2:0] S;
output [31:0] Y;
function [31:0] select;
input [31:0] A0, A1, A2, A3, A4;
input [2:0] S;
case(S)
3'b000: select = A0;
3'b001: select = A1;
3'b010: select = A2;
3'b011: select = A3;
3'b100: select = A3;
3'b101: select = A4;
endcase
endfunction
assign Y = select (A0, A1, A2, A3, A4, S);
endmodule
寄存器堆需要找到存在寄存器中的值,还需要往寄存器堆中写入值
module RegisterFile(ReadReg1,ReadReg2,WriteData,WriteReg,RegWrite,CLK,Reset,ReadData1,ReadData2,Pcsrc,PcAdd4);
input [4:0] ReadReg1;//rs
input [4:0] ReadReg2;//rt或者立即数
input [31:0] WriteData;//写入的数据
input [4:0] WriteReg;//写入地址
input RegWrite; //写信号
input CLK;
input Reset;
output reg[31:0] ReadData1;
output reg[31:0] ReadData2;
input [2:0] Pcsrc;
input [31:0] PcAdd4;
initial begin
ReadData1 <=0;
ReadData2 <=0;
end
reg [31:0] regFile[0:31];
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
//$display("lala");
if(RegWrite && WriteReg)
begin
regFile[WriteReg] = WriteData;
//$display("%d %d",WriteReg, WriteData);
end
end
always@(negedge CLK)
begin
//$display("lala");
if(Pcsrc == 3'b100 && RegWrite )
begin
//$display("%h",PcAdd4);
regFile[31] = PcAdd4;
end
end
endmodule
进行扩展操作,零扩展或者符号扩展
module SignExtend (X, Se, Y);
input [15:0] X;
input Se;
output [31:0] Y;
wire [31:0] E0, E1;
wire [15:0] e = {
16{
X[15]}};
parameter z = 16'b0;
assign E0 = {
z, X};
assign E1 = {
e, X};
MUX2X32 i(E0, E1, Se, Y);
endmodule
进行移位操作,包括将立即数左移两位,还有取PC+4前4位+address26位+两个0
module SHIFTER_COMBINATION(X,PCADD4,Sh);
input [26:0] X;
input [31:0] PCADD4;
output [31:0] Sh;
parameter z=2'b00;
assign Sh={
PCADD4[31:28],X[26:0],z};
endmodule
module SHIFTER32_L2(X,Sh);
input [31:0] X;
output [31:0] Sh;
parameter z=2'b00;
assign Sh={
X[29:0],z};
endmodule
将所有的模块合起来
module MAIN(Clk,Reset,Addr,Inst,Qa,Qb,ALU_R,NEXTADDR,D);
input Clk,Reset;
output [31:0] Inst,NEXTADDR,ALU_R,Qb,Qa,Addr,D;
wire [31:0]Result,PCadd4,EXTIMM,InstL2,EXTIMML2,D,Y,Dout,mux4x32_2,R;
wire Z,Regrt,Se,Wreg,Aluqb,Reg2reg,Cout,Wmem;
wire [2:0]Pcsrc;
wire [2:0]Aluc;
wire [4:0]Wr;
PC pc(Clk,Reset,Result,Addr);
PCAdd4 pcadd4(Addr,PCadd4);
im_4k instmem(Addr,Inst);
CONUNIT conunit(Inst[31:26],Inst[5:0],Z,Regrt,Se,Wreg,Aluqb,Aluc,Wmem,Pcsrc,Reg2reg);
MUX2X5 mux2x5(Inst[15:11],Inst[20:16],Regrt,Wr);
SHIFTER_COMBINATION shifter1(Inst[26:0],PCadd4,InstL2);//J指令的跳转地址
RegisterFile regfile(Inst[25:21],Inst[20:16],D,Wr,Wreg,Clk,Reset,Qa,Qb,Pcsrc,PCadd4);
ALU alu(Qa,Y,Aluc,R,Z);
dm_4k datamem(R,Qb,Wmem,Clk,Dout);
MUX2X32 mux2x322(Dout,R,Reg2reg,D);//写寄存器堆的来源,是ALU 还是 DM
SignExtend ext16t32(Inst[15:0],Se,EXTIMM);//扩展 0还是符号
MUX2X32 mux2x321(EXTIMM,Qb,Aluqb,Y);//选择ALU B端的来源
SHIFTER32_L2 shifter2(EXTIMM,EXTIMML2);
CLA_32 cla_32(PCadd4,EXTIMML2,0,mux4x32_2, Cout);//beq bne的跳转至零
MUX5X32 mux5x32(PCadd4,0,mux4x32_2,InstL2,Qa,Pcsrc,Result);//选择跳转的地址
assign NEXTADDR=Result;
assign ALU_R=R;
integer i;
initial begin
$display("start simulation");
//for (i=0;i<3;i=i+1)
// $display("%h %h", i,instmem.Rom[i]);
//$display("%h %h",regfile.ReadReg1,regfile.ReadReg2);
//$display("%h",pc.Address);
//$display("%h",instmem.Addr);
end
endmodule
最后的模拟仿真文件,包括clk(不断进行翻转)和reset(最开始进行初始化之后就一直不需要初始化了)
module mips;
wire [31:0] Addr,Inst,Qa,Qb,ALU_R,NEXTADDR;
reg CLK;
reg Reset;
MAIN uut(
.Clk(CLK),
.Reset(Reset),
.Addr(Addr),
.Inst(Inst),
.Qa(Qa),
.Qb(Qb),
.ALU_R(ALU_R),
.NEXTADDR(NEXTADDR),
.D(D)
);
initial begin
CLK = 0;
Reset = 0;
CLK = !CLK; // 下降沿,使PC先清零
Reset = 1; // 清除保持信号
forever #20
begin
CLK = !CLK;
end
end
endmodule
Regrt:所有的l,j型指令会写回到rt
Se:参考mips手册即可
Wreg:除了bne,beq,j,jr都需要写回寄存器堆
Aluqb:对于需要进行立即数扩展的指令就需要从扩展单元输入
Wmem:只有sw会用到
Pcsrc:也是根据自己的想法设计
Reg2reg:除了lw之外都是直接从ALU写回寄存器堆
首先通过addi给$1赋值,之后测试了addu,subu,ori,lw,sw,beq,lui,jal,jr等指令
addi $1,$0,8 #$1=8
ori $2,$0,12 #$2=12
addu $3,$1,$2 #$3=20
subu $4,$2,$1 #$4=4
and $5,$1,$2
or $6,$1,$2
lui $7 100 #$8=100<<64
bne $1,$2,p1
p1:
beq $1,$2,out
j p3
p3:
sw $2 10($8)
lw $4 10($8)
beq $2,$4,p4
p4:
andi $9,$2,9
loop:
jal minus
bne $1 $2 loop
j out
minus:
addi $1 $1 1
jr $ra
out:
bne $1 $2 2
的指令的时候会报错,一定要跳转到一个label才行在所有独立完成的大作业中这个的复杂程度应该算得上数一数二的了,所以想要写篇博客记录一下,之后也会更新支持50条指令的流水线CPU,也算留个纪念hhh,如有疏漏之处,欢迎指正,有问题也欢迎大家一起交流~