【P4】Verilog搭建单周期MIPS-CPU
课下
Bug_Log
1.模块实例化的信号需先定义,且记得定义完备
其实testbench见过多次了,自己写的时候还想不清。
若实例化模块时使用的信号,若事先无声明,则会自动生成1bit此名称信号,自然在多位信号传输中便出错了。使用单位信号的模块到可以省略实现声明。
!由于mips.v的clk
和reset
信号,与自己写的下层模块的Clk
和Rst
信号长得挺像,以致于忘记声明,从而使下层模块接收到的时钟和复位信号均为未知高阻态。
2.assign的对象不为寄存器
细碎的语言基础啊。。从逻辑上也不可能对寄存器使用连续赋值。
语法检查不会报错,会在仿真时报错。
3.PC-IM地址问题
[31:0]] IM | PC -> addr |
---|---|
[0] | 0 (.data : 0x0000_0000 ) |
[1] | 4 |
[2] | 8 |
... | ... |
[3071] | 12284 (.data 最后一个地址) |
[3072] | 12288 (.text : 0x0000_3000 ) |
... | ... |
[4095] | 16380 (此时可存入最大指令1024(=4095-3071 )个) |
... | ... |
Memory使用:
reg[31:0] IM[0:4095]
此声明下的memory从0开始;$readmemh("<数据文件名>", <存储器名> [, <起始地址>, <终止地址>]);
每个被读取的数字都被存放到地址连续的存储器单元中。- 存储器单元的存放地址由系统任务的起始地址和结束地址说明
- 文件中每个数据的存放地址在数据文件中说明,用
@hh...h
标识
有点蠢,还是设置IM[0:1023]
,然后每次PC取指令时减0x00003000
即可。
4.PC地址和IM指令索引的转换遗漏
PC[31:2]
才是IM的索引,写的时候忘记了。
同样在NPC的beq信号中,也忘了在尾部加2'b00
。
5.DM中忘记shData和sbData含义以致位数声明错误
原因在于自己写的lhData
/lbData
与shData
/sbData
的声明不对称。前者在最后输出RD
的assign
里进行位扩展;后者是在计算生成后已扩展,最后写入用的WDReal
里不用扩展。
所以正确的声明应该是:
wire [31:0] lwData, swData,shData,sbData;
wire [15:0] lhData;
wire [7:0] lbData;
wire [31:0] WDReal;
6.不算bug的bug(事关sb
sh
信号输出的协议)
sb、lb输出信号的规范。是以当前A的地址为始还是以规整化addr作为起始地址。
模块与组装
支持指令集{addu, subu, ori, lw, lh, lb, sw, sh, sb, beq, bne, jal, jr, lui, nop, j, sll, slt}
。主要包含IFU(PC+NPC+IM)、GRF、EXT、ALU、DM、CU模块。
各模块组件解构树状图如下:
和P3几乎一样,合并了IFU,并为了测评增加了一些信号
module mips(
input clk,
input reset
);
wire [2:0] ALUOp;
wire [1:0] NPCOp, EXTOp, SSel, LSel, M1Sel, M2Sel;
wire BSel, M3Sel, RFWr, DMWr;
wire [5:0] op, funct;
integer handle;
initial begin
handle = $fopen("../msg_out_cpu.txt","w");
$fclose(handle);
end
datapath dp (
.Clk(clk),
.Rst(reset),
.NPCOp(NPCOp),
.EXTOp(EXTOp),
.ALUOp(ALUOp),
.BSel(BSel),
.SSel(SSel),
.LSel(LSel),
.M1Sel(M1Sel),
.M2Sel(M2Sel),
.M3Sel(M3Sel),
.RFWr(RFWr),
.DMWr(DMWr),
.op(op), //Output
.funct(funct)
);
control ctrl (
.op(op),
.funct(funct),
.NPCOp(NPCOp), //Output
.EXTOp(EXTOp),
.ALUOp(ALUOp),
.BSel(BSel),
.SSel(SSel),
.LSel(LSel),
.M1Sel(M1Sel),
.M2Sel(M2Sel),
.M3Sel(M3Sel),
.RFWr(RFWr),
.DMWr(DMWr)
);
endmodule
datapath=
module datapath(
input Clk,
input Rst,
input [1:0] NPCOp,
input [1:0] EXTOp,
input [2:0] ALUOp,
input BSel,
input [1:0] SSel,
input [1:0] LSel,
input [1:0] M1Sel,
input [1:0] M2Sel,
input M3Sel,
input RFWr,
input DMWr,
output [5:0] op,
output [5:0] funct
);
wire [31:0] IM_D;
wire [31:0] GRF_RD1, GRF_RD2;
wire Zero;
wire ACmp0;
wire [31:0] PC_sgn, PC4;
wire [31:0] EXT_O;
wire [31:0] ALU_Y;
wire [31:0] DM_RD;
wire [4:0] M1_out;
wire [31:0] M2_out;
wire [31:0] M3_out;
assign M1_out = (M1Sel == 2'b00) ? IM_D[20:16] :
(M1Sel == 2'b01) ? IM_D[15:11] :
(M1Sel == 2'b10) ? 5'b11111 : 0 ;
assign M2_out = (M2Sel == 2'b00) ? PC4 :
(M2Sel == 2'b01) ? DM_RD:
(M2Sel == 2'b10) ? ALU_Y:
(M2Sel == 2'b11) ? EXT_O: 0 ;
assign M3_out = (M3Sel == 1'b0) ? GRF_RD2 : EXT_O ;
assign op = IM_D[31:26];
assign funct = IM_D[5:0];
//所以最初设计端口时,输出口名称最好不重名,那样不用改也不容易混;但为了醒目,还是加上模块前缀吧
IFU ifu (
.Clk(Clk), //Input
.Rst(Rst),
.IMM(IM_D[25:0]),
.RA(GRF_RD1),
.NPCOp(NPCOp),
.BSel(BSel),
.ACmp0(ACmp0),
.Zero(Zero),
.PC4(PC4), //Output
.IM_D(IM_D),
.PC_sgn(PC_sgn)
);
GRF grf (
.PC(PC_sgn), //Input
.Clk(Clk),
.Rst(Rst),
.A1(IM_D[25:21]),
.A2(IM_D[20:16]),
.A3(M1_out),
.WD(M2_out),
.RFWr(RFWr),
.RD1(GRF_RD1), //Output
.RD2(GRF_RD2)
);
EXT ext (
.I(IM_D[15:0]), //Input
.EXTOp(EXTOp),
.O(EXT_O) //Output
);
ALU alu (
.A(GRF_RD1), //Input
.B(M3_out),
.Shamt(IM_D[10:6]),
.ALUOp(ALUOp),
.Y(ALU_Y), //Output
.Zero(Zero),
.ACmp0(ACmp0)
);
DM dm (
.PC(PC_sgn), //Input
.Clk(Clk),
.Rst(Rst),
.A(ALU_Y),
.WD(GRF_RD2),
.DMWr(DMWr),
.SSel(SSel),
.LSel(LSel),
.RD(DM_RD) //Output
);
endmodule
1.IFU
module IFU(
input Clk,
input Rst,
input [25:0] IMM,
input [31:0] RA,
input [1:0] NPCOp,
input [1:0] ACmp0, //ALU传出的信号,用于branch类指令
input Zero,
input BSel,
output [31:0] PC4,
output [31:0] IM_D,
output [31:0] PC_sgn //为了打印测评信号传出
);
reg [31:0] PC;
reg [31:0] IM[0:1023];
wire [31:0] NPC;
wire [31:0] PC_b;
wire [31:0] PC_beq, PC_bne;
//wire [31:0] PC_bgez, PC_bgtz, PC_blez, PC_bltz;
integer i;
// 这里耦合度高了,代码也重复得很蠢。是信号传递产生的冗余,在下一p优化
assign PC4 = PC + 32'd4;
assign PC_beq = (Zero == 1'b1) ? PC4 + {{14{IMM[15]}},IMM[15:0],2'b00} : PC4;
assign PC_bne = (Zero == 1'b0) ? PC4 + {{14{IMM[15]}},IMM[15:0],2'b00} : PC4;
//assign PC_bgez = (ACmp0 != 2'b10) ? PC4 + {{14{IMM[15]}},IMM[15:0],2'b00} : PC4;
//assign PC_bgtz = (ACmp0 == 2'b01) ? PC4 + {{14{IMM[15]}},IMM[15:0],2'b00} : PC4;
//assign PC_blez = (ACmp0 != 2'b01) ? PC4 + {{14{IMM[15]}},IMM[15:0],2'b00} : PC4;
//assign PC_blez = (ACmp0 == 2'b10) ? PC4 + {{14{IMM[15]}},IMM[15:0],2'b00} : PC4;
assign PC_b = (BSel == 1'b0) ? PC_beq : PC_bne;
assign NPC = (NPCOp == 2'b00) ? PC4 :
(NPCOp == 2'b01) ? PC_b:
(NPCOp == 2'b10) ? {PC[31:28], IMM, 2'b00} :
(NPCOp == 2'b11) ? RA : -1 ;
assign PC_sgn = PC;
assign IM_D = IM[(PC-32'h00003000)/4];
always @(posedge Clk) begin
if(Rst == 1) begin
PC <= 32'h0000_3000;
end else begin
PC <= NPC;
end
end
initial begin
PC = 32'h0000_3000;
for(i=0;i<1024;i=i+1)
IM[i] = 32'h0000_0000;
$readmemh("code.txt",IM);
end
endmodule
2.GRF
module GRF(
input [31:0] PC, //为了打印信号而传入
input Clk,
input Rst,
input [4:0] A1,
input [4:0] A2,
input [4:0] A3,
input [31:0] WD,
input RFWr,
output [31:0] RD1,
output [31:0] RD2
);
integer handle;
reg [31:0] rf[31:1];
integer i;
assign RD1 = (A1 == 0) ? 0 : rf[A1];
assign RD2 = (A2 == 0) ? 0 : rf[A2];
always @(posedge Clk) begin
if(Rst == 1) begin
for(i=1;i<32;i=i+1)
rf[i] <= 32'h0000_0000;
end else if(RFWr == 1 && A3 != 0) begin
rf[A3] <= WD;
$display("@%h: $%d <= %h", PC, A3, WD);
handle = $fopen("../msg_out_cpu.txt","a");
$fdisplay(handle,"@%h: $%d <= %h", PC, A3, WD);
$fclose(handle);
end
end
initial begin
for(i=1;i<32;i=i+1)
rf[i] <= 32'h0000_0000;
end
endmodule
3.EXT
module EXT(
input [15:0] I,
input [1:0] EXTOp,
output [31:0] O
);
assign O = (EXTOp == 2'b00) ? {16'h0000,I} :
(EXTOp == 2'b01) ? {{16{I[15]}},I}:
(EXTOp == 2'b10) ? {I,16'h0000} : -1 ;
endmodule
4.ALU
module ALU(
input [31:0] A,
input [31:0] B,
input [4:0] Shamt,
input [2:0] ALUOp,
output [31:0] Y,
output Zero,
output [1:0] ACmp0
);
assign Y = (ALUOp == 3'b000) ? A + B :
(ALUOp == 3'b001) ? A - B :
(ALUOp == 3'b010) ? A | B :
(ALUOp == 3'b011) ? B << Shamt :
(ALUOp == 3'b100) ? A < B : -1;
assign Zero = (A == B) ? 1'b1 : 1'b0 ;
assign ACmp0 = (A == 0) ? 2'b00 :
(A > 0) ? 2'b01 :
(A < 0) ? 2'b10 : -1;
endmodule
5.DM
module DM(
input [31:0] PC, //为打印信号而传入
input Clk,
input Rst,
input [31:0] A,
input [31:0] WD,
input DMWr,
input [1:0] SSel,
input [1:0] LSel,
output [31:0] RD
);
integer handle;
reg [31:0] DM[0:1023];
wire [9:0] addr;
wire [31:0] lwData, swData,shData,sbData;
wire [15:0] lhData;
wire [7:0] lbData;
wire [31:0] WDReal_addr, WDReal_A; //WDReal_addr是适配addr的内容,WDReal_A是适配A的内容
integer i;
assign addr = A[11:2]; //在DM处是大小是控制好的,A是完整地址信号(Byte),addr是字地址信号(4Byte),对应DM索引
assign lwData = DM[addr];
assign lhData = (A[1] == 1'b0) ? DM[addr][15:0] : DM[addr][31:16];
assign lbData = (A[1:0] == 2'b00) ? DM[addr][7:0] :
(A[1:0] == 2'b01) ? DM[addr][15:8] :
(A[1:0] == 2'b10) ? DM[addr][23:16]:
(A[1:0] == 2'b11) ? DM[addr][31:24]: -1;
assign RD = (LSel == 2'b00) ? lwData :
(LSel == 2'b01) ? {{16{lhData[15]}},lhData} :
(LSel == 2'b10) ? {{24{lbData[7]}} ,lbData} : -1;
assign swData = WD;
assign shData = (A[1] == 1'b0) ? {DM[addr][31:16],WD[15:0]} : {WD[15:0],DM[addr][15:0]};
assign sbData = (A[1:0] == 2'b00) ? {DM[addr][31:8] ,WD[7:0]} :
(A[1:0] == 2'b01) ? {DM[addr][31:16],WD[7:0], DM[addr][7:0]} :
(A[1:0] == 2'b10) ? {DM[addr][31:24],WD[7:0],DM[addr][15:0]} :
(A[1:0] == 2'b11) ? {WD[7:0],DM[addr][23:0]} : -1;
assign WDReal_addr = (SSel == 2'b00) ? swData :
(SSel == 2'b01) ? shData :
(SSel == 2'b10) ? sbData : -1;
assign WDReal_A = (SSel == 2'b00) ? WD :
(SSel == 2'b01) ? WD[15:0] :
(SSel == 2'b10) ? WD[7:0] : -1;
always @(posedge Clk) begin
if(Rst == 1) begin
for(i=0;i<1024;i=i+1)
DM[i] <= 32'h0000_0000;
end else if(DMWr == 1) begin
DM[addr] <= WDReal_addr;
$display("@%h: *%h <= %h", PC, A, WDReal_A);
handle = $fopen("../msg_out_cpu.txt","a");
$fdisplay(handle,"@%h: *%h <= %h", PC, A, WDReal_A);
$fclose(handle);
end
end
initial begin
for(i=0;i<1024;i=i+1)
DM[i] = 32'h0000_0000;
end
endmodule
control(CU)
module control(
input [5:0] op,
input [5:0] funct,
output [1:0] NPCOp,
output [1:0] EXTOp,
output [2:0] ALUOp,
output BSel,
output [1:0] SSel,
output [1:0] LSel,
output [1:0] M1Sel,
output [1:0] M2Sel,
output M3Sel,
output RFWr,
output DMWr
);
//op
parameter special= 6'b00_0000;
parameter ADDU = 6'b10_0001; //funct
parameter SUBU = 6'b10_0011; //funct
parameter ORI = 6'b00_1101;
parameter SLL = 6'b00_0000; //funct
parameter LW = 6'b10_0011;
parameter LH = 6'b10_0001;
parameter LB = 6'b10_0000;
parameter SW = 6'b10_1011;
parameter SH = 6'b10_1001;
parameter SB = 6'b10_1000;
parameter BEQ = 6'b00_0100;
parameter BNE = 6'b00_0101;
parameter JAL = 6'b00_0011;
parameter JR = 6'b00_1000; //funct
parameter J = 6'b00_0010;
parameter LUI = 6'b00_1111;
parameter SLT = 6'b10_1010; //funct
wire addu,subu,ori,lw,lh,lb,sw,sh,sb,beq,bne,jal,jr,j,lui,sll,slt;
assign addu = (op == special) & (funct == ADDU);
assign subu = (op == special) & (funct == SUBU);
assign jr = (op == special) & (funct == JR );
assign sll = (op == special) & (funct == SLL );
assign slt = (op == special) & (funct == SLT );
assign ori = (op == ORI);
assign lw = (op == LW );
assign lh = (op == LH );
assign lb = (op == LB );
assign sw = (op == SW );
assign sh = (op == SH );
assign sb = (op == SB );
assign beq = (op == BEQ);
assign bne = (op == BNE);
assign jal = (op == JAL);
assign j = (op == J );
assign lui = (op == LUI);
/* =================================================== */
assign NPCOp[1] = jal | jr | j ;
assign NPCOp[0] = beq | jr | bne ;
assign EXTOp[1] = lui ;
assign EXTOp[0] = lw | lh | lb | sw | sh | sb ;
assign ALUOp[2] = slt ;
assign ALUOp[1] = ori | sll ;
assign ALUOp[0] = subu | sll ;
assign BSel = bne ;
assign SSel[1] = sb ;
assign SSel[0] = sh ;
assign LSel[1] = lb ;
assign LSel[0] = lh ;
assign M1Sel[1] = jal ;
assign M1Sel[0] = addu | subu | sll | slt ;
assign M2Sel[1] = addu | subu | ori | lui | sll | slt ;
assign M2Sel[0] = lw | lui | lh | lb ;
assign M3Sel = ori | lw | lh | lb | sw | sh | sb ;
assign RFWr = addu | subu | ori | lw | lh | lb | jal | lui | sll | slt ;
assign DMWr = sw | sh | sb ;
endmodule
课上
这次没上次顺,第二第三个指令均写出bug。但定位的还算快,最终还是解决了
T1 RLB(Reverse Lower Bits)
将GPR[rs]
低immediate位按位取反后存入GPR[rt]
31:26 | 25:21 | 20:16 | 15:0 |
---|---|---|---|
RLB 111111 |
rs | rt | immediate |
6 | 5 | 5 | 16 |
数据通路均已有,修改ALU,再于CU里增修指令和控制信号。核心在于按位取反运算操作。
一开始想直接位拼接{A[31:Imm],{~A[Imm-1:0]}}
。但是报错说Imm
不是Constant,不能用。于是开始考量逻辑位运算。上次P3的slo指令也是类似做法,没文化就自称“类掩码法”。
wire [31:0]rlb_data;
assign rlb_data = A ^ ( ~(32'hffffffff << Imm) );
操作~(32hffffffff << Imm)
可得一串32位数,需要取反的地方为1剩下为0,再异或一下即可
不知道“位拼接”究竟能不能做。
T2 BNEZALC(BNEZ And Link)
如果GPR[rs]
不为0,则跳转到label并将PC+4存入$ra
31:26 | 25:21 | 20:16 | 15:0 |
---|---|---|---|
regimm 000001 |
rs | BNEZALC 10011 |
offset |
6 | 5 | 5 | 16 |
真就经典祖传:万物皆可AL。
课下的时候本打算把branch一类指令全加上的,后来发现bgez/bgtz/blez/bltz全都用了新信号判断指令IM_D[20:16]
,认为把自己的CPU改的太偏离寻常可能不利于课上快速加指令从而作罢,只加了bne。没想到课上真就碰上了。
感觉有些时候确实有狗屎运而没有狗屎命。上学期基物期末突击复习时压中最后大题,结果最后因为忘记毕萨定理尝试倒推公式出错而倒台。\u
修改数据通路信号,设置新信号。从ALU传新信号[1:0] ACmp0
(之所以两位是因为课下已经为bgez等指令写好的这个接口)入IFU,判断获取NPC取值。然后在CU里增修指令和控制信号,别忘了AL的惯常操作。
bug:然而忘记了AL操作并不是必定执行的,若GPR[rs]
为0则不跳转也不link。所以又直接向CU里加信号[1:0]ACmp0
修正控制信号逻辑。
这样打补丁把整个构架搞的挺难看,信号传输没有规矩散兵游勇十分杂乱。或许一开始ALU中类似Zero
ACmp0
等信号就该传入CU统一处理。
T3 LWRR(Load Word Rotate Right)
将从Memory[GPR[base]+sign_extend(offset)]
取出的字循环右移vaddr[1:0]
个字节后载入GPR[rt]
31:26 | 25:21 | 20:16 | 15:0 |
---|---|---|---|
LWRR 110100 |
base | rt | offset |
6 | 5 | 5 | 16 |
数据通路均已有,在DM输出段做相关运算即可。核心在于循环右移运算。
注意题目说的是Byte,其实总共就4种情况,三目运算罗列一下也很快。
一开始看成bit,就用了和第一题相同的方法,之后将A[1:0]
改为A[1:0]*8
。
//这里 A 即为 vAddr,ALU传出的计算结果
wire [31:0] tmp1_lwrr, tmp2_lwrr;
assign tmp1_lwrr = ~(32'hffffffff << A[1:0]*8); //取出右移丢失的内容
assign tmp2_lwrr = tmp1_lwrr << (32 - A[1:0]*8); //将其挪至需要加载的高位处
assign lwwrData = (lwData >> A[1:0]*8) | tmp2_lwrr; //或上即可