# CPU 设计文档及思考题

# 设计草稿

# 顶层模块设计

  • 在 P3 的基础上,将其顶层结构翻译映射为 Verilog HDL 的工程文件。
  • 因此,对 P3 文档稍加修订,得到顶层模块的工程设计如下
    • 数据通路
      图 2
    • 控制
      指令NPCMode[1:0]RegWriteALUOp[3:0]MemoryWriteExtOp[1:0]ALUSrc[1:0]GRFSrc[1:0]GRFA3Src[1:0]
      add00100000xx000000
      sub00100010xx000000
      ori0010010000010001
      lw0010000001010101
      sw000000010101xxxx
      beq01000010xx00xxxx
      lui00100110xx010001
      jal101xxxx0xxxx1010
      jr110xxxx0xxxxxxxx
      nop000xxxx0xxxxxxxx

# 子模块

  • 程序计数器 PC

    信号名方向描述
    ClkInput时钟
    ResetInput同步复位到 0x00003000
    InInput[31:0]npc
    OutOutput[31:0]pc
  • 下一指令运算器 NPC

    信号名方向描述
    ModeInput[2:0]运算模式
    ZeroInputALU 结果是否为零
    pcInput[31:0]PC
    ImmInput[25:0]25 位立即数
    raInput[31:0]ra
    pc4Output[31:0]PC+4
    npcOutput[31:0]NPC
  • 指令存储器 IM

    信号名方向描述
    AInput[13:2]指令的字节地址 - 0x00003000
    InstrOutput[31:0]指令
  • 寄存器堆 GRF

    信号名方向描述
    ClkInput时钟
    ResetInput同步复位
    WEInput写使能
    A1[4:0]Input操作数 1 地址
    A2[4:0]Input操作数 2 地址
    A3[4:0]Input操作数 3 地址
    WD[31:0]Input写入数据
    D1[31:0]Output读出数据 1
    D2[31:0]Output读出数据 2
  • 控制器 Controller

    信号名方向描述
    Opcode[5:0]Input
    Funct[5:0]Input
    NPCModeOutput[2:0]NPC 模式使能
    RegWriteOutputGRF 写使能
    ALUSrc[1:0]OutputB 为寄存器 / 立即数
    ALUOp[3:0]OutputALU 模式
    MemoryWriteOutputDM 写使能
    ExtOp[1:0]OutputEXT 使能和模式
    GRFSrc[1:0]OutputALU/DM 回写
  • 算术逻辑运算单元 ALU

    信号名方向描述
    A[31:0]Input操作数 1
    B[31:0]Input操作数 2
    ALUOp[3:0]Input模式
    C[31:0]Output运算结果
    ZeroOutput结果是否为零
  • 数据存储器 DM

    信号名方向描述
    ClkInput时钟
    ResetInput同步复位
    EnInput写使能
    A[31:0]Input读地址
    D[31:0]Input写数据
    Data[31:0]Output读出数据
  • 结合上述模块输入输出和连接关系,做出各个子模块以及顶层架构,下面展示了顶层架构:

module mips(
    input clk,
    input reset
    );
    NPC npc(.mode(controller.NPCMode),
	         .Zero(alu.Zero),
	         .pc(pc.out),
				.imm(im.Instr[25:0]),
				.ra(grf.RD1));
				
    PC pc(.clk(clk),
	       .reset(reset),
			 .in(npc.npc));
	 
    wire [31:0] IM_Addr;
    assign IM_Addr = pc.out - 32'h0000_3000;	 
	 IM im(.A(IM_Addr[13:2]));		 

    GRF grf(.clk(clk),
            .reset(reset),
            .WE(controller.RegWrite),
				.A1(im.Instr[25:21]),
				.A2(im.Instr[20:16]),
				.A3(mux_grf_A3.B),
				.WD(mux_grf_D.B),
				.pc(pc.out));				

    ALU alu(.A(grf.RD1),
	         .B(mux_alu_B.B),
				.ALUOp(controller.ALUOp));

    DM dm(.clk(clk),
	       .reset(reset),
			 .WE(controller.MemoryWrite),
			 .A(alu.C[13:0]),
			 .D(grf.RD2),
			 .pc(pc.out));
			 
	 EXT ext(.imm(im.Instr[15:0]),
	         .EXTOp(controller.EXTOp));		 

    MUX_5_4 mux_grf_A3(.A0(im.Instr[15:11]),
	                    .A1(im.Instr[20:16]),
							  .A2(5'h1f),
							  .chose(controller.GRFA3Src));
    
	 MUX_32_4 mux_grf_D(.A0(alu.C),
	                    .A1(dm.Data),
							  .A2(npc.pc4),
							  .chose(controller.GRFSrc));

    MUX_32_4 mux_alu_B(.A0(grf.RD2),
	                    .A1(ext.ext),
							  .chose(controller.ALUSrc));

    Controller controller(.Opcode(im.Instr[31:26]),
	                       .Funct(im.Instr[5:0]));

endmodule

# 测试方案

  • 思路
    • 沿用 P3 测试的思路和方法,重用其代码。可以完成 add,sub,ori,lw,sw,lui,nop 等运算和访存指令的测试。
    • 下面是 P3 的测试方案:
      图 0
      例如:
    #include<stdio.h>
    #include<stdlib.h>
    #include<time.h>
    #define BATCH_SIZE 50
    
    FILE *pCode;
    
    int main() {
        srand((unsigned int)time(0));
        pCode = fopen("mips_code.txt","w");
        char s[120];
        int i;
        for (i = 0; i< 32; i++) {
                sprintf(s,"ori $%d,$%d,%d\n",i,i + 1,0);
                fprintf(pCode,s);
        }
        fclose(pCode);
        pCode = fopen("mips_code_ori1.txt","w");
        for (i = 0; i< 32; i++) {
                sprintf(s,"ori $%d,$%d,%d\n",i,i + 1,0xffff);
                fprintf(pCode,s);
        }
        fclose(pCode);
        pCode = fopen("mips_code_ori2.txt","w");
        for (i = 0 ; i < BATCH_SIZE; i++) {
            sprintf(s,"ori $%d,$%d,%d\n",rand()% 32,rand()%32,rand()%0x10000);
            fprintf(pCode,s);
        }
        fclose(pCode);
        return 0;
    }
    
    
    • 对于 beq,jal,jr 等命令,手动编写了若干条指令进行测试。
      例如:
    .data
    
    .text
    jal loop
    beq $t1,$t2,end
    ori $t1,$0,114
    loop:
    ori $t1,$0,100
    ori $t2,$t1,0
    jr $ra
    beq $0,$0,loop
    end:
    
    

# 思考题

  • 思考题 1:
    • 在课下实验中该 addr 信号从 ALU 的输出端口 C 来,这是因为访存时候的寻址通常是基址寻址,地址由 ALU 计算得到;但若有其他寻址方式,则还有可能从 EXT,GRF 等地方读取而来。
    • 由 ALU 计算得到的地址是字节地址,但 DM 中的数据索引是字地址,因此应该将字节地址除以四得到字地址,即 addr [11:2]。
  • 思考题 2:
    • 对于记录 “指令对应的控制信号如何取值”, 以其中一条指令 add 为例:
     if(add) begin
     NPCMode = 3'b000;
     RegWrite = 1'b1;
     ALUOp = 4'b0000;
     MemoryWrite = 0;
     EXTOp = 2'b00;
     ALUSrc = 2'b00;
     GRFA3Src = 2'b00;
     GRFSrc = 2'b00;
     end
    
    记录每种指令对应信号的方式便于以指令为单位进行代码逻辑的查看,不同指令之间不进行耦合,有利于消除不同指令之间的逻辑冲突,能够减少 bug,也有利于直接添加指令。缺点则是代码数量偏大,每增加一个指令就需要重写全部控制信号,相当于不进行代码的复用,且检查时工作量较大。
    • 对于记录 “控制信号每种取值所对应的指令” 的编码方式,与搭建电路时采用或门阵列一致,示例代码如下:
        NPCMode[0] = beq | jr;
     	NPCMode[1] = jal | jr; 
     	NPCMode[2] = 0;
        RegWrite = add | sub | ori | lw | lui | jal;
     	ALUOp[0] = sub | beq | lui;
     	ALUOp[1] = ori | lui;
     	ALUOp[2] = 0;
     	ALUOp[3] = 0;
     	MemoryWrite = sw;
     	EXTOp[0] = lw | sw;
     	EXTOp[1] = 0;
     	ALUSrc[0] = ori | lw | sw | lui;
     	ALUSrc[1] = 0;
     	GRFA3Src[0] = ori | lw | lui;
     	GRFA3Src[1] = jal;
     	GRFSrc[0] = lw;
     	GRFSrc[1] = jal;
    
    这种方式与实际实现的电路有良好的对应关系。代码量明显较少,编写和修改时工作量较小,无需列出控制信号取 0 时的指令。但是不同指令耦合在一起,修复错误和增加指令时更容易出现逻辑问题;并且为了使得代码清晰,将控制信号不同位拆开表示。考虑到已经写清楚控制逻辑真值表理清逻辑,故采用此方式。
  • 思考题 3:
    • (以上升沿 + 高电平有效为例)异步复位当中复位信号的优先级比时钟信号要高,只要复位信号为高电平,不论时钟信号是什么,立即复位。即
    always @(posedge clk,posedge reset) begin
      if(reset) begin
        /*reset*/
      end
      /*code*/
    end
    
    • 同步复位当中时钟信号的优先级比复位信号要高,当且仅当时钟上升沿时复位信号为高电平才进行复位,将有一个周期无其他操作。即:
    always @(posedge clk) begin
      if(reset) begin
        /*reset*/
      end
      /*code*/
    end
    
  • 思考题 4:
    addu 相比于 add 指令,其操作多了
    if temp32 ≠ temp31 then
    SignalException(IntegerOverflow)
    
    部分,即溢出检查,若忽略溢出,不进行检查,则操作为 else 部分
    else
    GPR[rd] ← temp
    endif
    
    和 add 完全相同。因此,忽略溢出时,add 与 addu 是等价的。addi 和 addiu 同理。
更新于

请我喝[茶]~( ̄▽ ̄)~*

Zeng Huaxu 微信支付

微信支付

Zeng Huaxu 支付宝

支付宝