【P4】Verilog搭建单周期MIPS-CPU

课下

Bug_Log

1.模块实例化的信号需先定义,且记得定义完备

其实testbench见过多次了,自己写的时候还想不清。

若实例化模块时使用的信号,若事先无声明,则会自动生成1bit此名称信号,自然在多位信号传输中便出错了。使用单位信号的模块到可以省略实现声明。

由于mips.v的clkreset信号,与自己写的下层模块的ClkRst信号长得挺像,以致于忘记声明,从而使下层模块接收到的时钟和复位信号均为未知高阻态。

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/lbDatashData/sbData的声明不对称。前者在最后输出RDassign里进行位扩展;后者是在计算生成后已扩展,最后写入用的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模块。

各模块组件解构树状图如下:
image

和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里增修指令和控制信号。核心在于按位取反运算操作。

\[GPR[rt] \leftarrow GPR[rs]_{31...imm}||inverse(GPR[rs]_{imm-1...0}) \]

一开始想直接位拼接{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,再异或一下即可

不知道“位拼接”究竟能不能做。

如果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输出段做相关运算即可。核心在于循环右移运算。

\[\begin{align*} vAddr & \leftarrow GPR[base + sign\_extend(offset)] \\ memword & \leftarrow Memory[vAddr] \\ byte & \leftarrow vAddr_{1...0} \\ GPR[rt] & \leftarrow Memory[vAddr]_{8*(Byte-1)...00}||Memory[vAddr]_{31...8*Byte} \end{align*} \]

注意题目说的是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; //或上即可
posted @ 2021-11-14 23:33  Xlucidator  阅读(981)  评论(0编辑  收藏  举报