跳转至

lab4:CPU(Single Cycle)

参考资料与验收要求

Wintermelon的笔记 \(\quad\) 计算机组成与设计-2024年lab4

验收要求:

4-0 CPU核集成设计

首先建构SCPU.v,并且将DataPath.v,SCPU_ctrl.v,DataPath.edf,SCPU_ctrl.edf文件导入.

SCPU.v如下:

SCPU.v
module SCPU(
    input wire MIO_ready,
    input wire [31:0] Data_in,
    input wire clk,
    input wire [31:0] inst_in,
    input wire rst,
    output wire MemRW,
    output wire CPU_MIO,
    output wire [31:0] Addr_out,
    output wire [31:0] Data_out,
    output wire [31:0] PC_out
);

    wire [1:0] ImmSel;
    wire Jump;
    wire Branch;
    wire RegWrite;
    wire [2:0] ALU_Control;
    wire ALUSrc_B;
    wire [1:0] MemtoReg;

    SCPU_ctrl U_1(
        .OPcode     (inst_in[6:2]),
        .Fun3       (inst_in[14:12]),
        .Fun7       (inst_in[30]),
        .MIO_ready  (MIO_ready),
        .ImmSel     (ImmSel),
        .ALUSrc_B   (ALUSrc_B),
        .MemtoReg   (MemtoReg),
        .Jump       (Jump),
        .Branch     (Branch),
        .RegWrite   (RegWrite),
        .MemRW      (MemRW),
        .ALU_Control(ALU_Control),
        .CPU_MIO    (CPU_MIO)
    );

    DataPath U_2(
        .clk        (clk),
        .rst        (rst),
        .inst_field (inst_in),
        .Data_in    (Data_in),
        .ALU_Control(ALU_Control),
        .ImmSel     (ImmSel),
        .MemtoReg   (MemtoReg),
        .ALUSrc_B   (ALUSrc_B),
        .Jump       (Jump),
        .Branch     (Branch),
        .RegWrite   (RegWrite),
        .PC_out     (PC_out),
        .Data_out   (Data_out),
        .ALU_out    (Addr_out)
    );

endmodule

这样测试平台就搭好了,之后我们需要完成的是:4-1将数据通路搭建完之后替换当前DataPath,4-2将控制单元搭建完之后替换当前的Control Unit,并且在4-3中进行指令扩展,4-4完成中断处理.

4-1 CPU设计之数据通路

本小节实际上是依据给出的DataPath.pdf文件写好PC.v,Data_Path.vImmGen.v,并组合成一个不完全版的数据通路,替换掉SCPU中的DataPath部分.

文件结构:

PC.v
module PC(
    input clk,
    input rst,
    input CE,
    input [31:0] D,
    output reg [31:0] Q
    );
    always @(posedge clk or posedge rst) begin
        if (rst) begin
            // 复位时,PC初始化为0x00000000
            Q <= 32'h00000000;
        end
        else if (CE) begin
            // 使能有效时,更新PC值
            Q <= D;
        end
    end
endmodule

在我们的DataPath中,CE使能恒置1'b1,使得PC信号可以被不断修改.

ImmSel 为 0 时,生成 I-Type 指令的立即数;为 1 时,生成 S-Type 指令的立即数;为 2 时,生成 B-Type 指令的立即数;为 3 时,生成 J-Type 指令的立即数.

ImmGen.v
module ImmGen(
    input [1:0]   ImmSel,
    input [31:0]  inst_field,
    output reg [31:0] Imm_out
);

always @(*) begin
    case(ImmSel)
        2'b00: begin
                // I-Type: imm[11:0] = inst[31:20]
                // 符号扩展到32位
                Imm_out = {{20{inst_field[31]}}, inst_field[31:20]};
            end
        2'b01: begin
                // S-Type: imm[11:5] = inst[31:25], imm[4:0] = inst[11:7]
                // 符号扩展到32位
                Imm_out = {{20{inst_field[31]}}, inst_field[31:25], inst_field[11:7]};
            end
        2'b10: begin
                // B-Type: imm[12|10:5|4:1|11] = inst[31|30:25|11:8|7]
                // imm[0] = 0 (最低位始终为0,因为分支地址是2字节对齐)
                // 符号扩展到32位
                Imm_out = {{19{inst_field[31]}}, inst_field[31], inst_field[7], 
                        inst_field[30:25], inst_field[11:8], 1'b0};
            end
        2'b11: begin
                // J-Type (JAL): imm[20|10:1|11|19:12] = inst[31|30:21|20|19:12]
                // imm[0] = 0 (最低位始终为0)
                // 符号扩展到32位
                Imm_out = {{11{inst_field[31]}}, inst_field[31], inst_field[19:12], 
                        inst_field[20], inst_field[30:21], 1'b0};
            end
        default: begin
                Imm_out = 32'b0;
            end
        endcase
    end
endmodule
// ImmSel 为 0 时,生成 I-Type 指令的立即数;
// 为 1 时,生成 S-Type 指令的立即数;
// 为 2 时,生成 B-Type 指令的立即数;
// 为 3 时,生成 J-Type 指令的立即数
Data_Path.v
module DataPath(
    input clk,
    input rst,
    input [31:0] inst_field,
    input ALUSrc_B,
    input [1:0] MemtoReg,
    input Jump,
    input Branch,
    input RegWrite,
    input [31:0] Data_in,
    input [2:0] ALU_Control,
    input [1:0] ImmSel,
    output [31:0] ALU_out,
    output [31:0] Data_out,
    output [31:0] PC_out
);
    wire [31:0] next_PC = PC_out + 32'd4;
    wire [31:0] Imm_out;
    wire [4:0] Rs1_addr = inst_field[19:15];
    wire [4:0] Rs2_addr = inst_field[24:20];
    wire [4:0] Wt_addr = inst_field[11:7];
    wire [31:0] Wt_data;
    wire [31:0] Rs1_data;
    wire [31:0] Rs2_data;
    wire Op2;
    wire [31:0] B;
    wire Res;
    wire [31:0] I0;
    wire [31:0] I1 = PC_out + Imm_out;
    wire [31:0] o;

    mux4to1 mux4to1(
        .s(MemtoReg),
        .I0(ALU_out),
        .I1(Data_in),
        .I2(next_PC),
        .I3(next_PC),
        .o(Wt_data)
    );

    Regs Regs(
        .clk(clk),
        .rst(rst),
        .Rs1_addr(Rs1_addr),
        .Rs2_addr(Rs2_addr),
        .Wt_addr(Wt_addr),
        .Wt_data(Wt_data),
        .RegWrite(RegWrite),
        .Rs1_data(Rs1_data),
        .Rs2_data(Rs2_data)
    );

    ImmGen ImmGen(
        .ImmSel(ImmSel),
        .inst_field(inst_field),
        .Imm_out(Imm_out)
    );

    mux2to1 mux2to1_0(
        .I0(Rs2_data),            
        .I1(Imm_out),
        .sel(ALUSrc_B),
        .o(B)
    );

    ALU ALU(
        .A(Rs1_data),
        .B(B),
        .ALU_operation(ALU_Control),
        .res(ALU_out),
        .zero(Op2)
    );

    and32 and32(
        .A(Branch),
        .B(Op2),
        .res(Res)
    );

    mux2to1 mux2to1_1(
        .I0(next_PC),
        .I1(I1),
        .sel(Res),
        .o(I0)
    );

    mux2to1 mux2to1_3(
        .I0(I0),
        .I1(I1),
        .sel(Jump),
        .o(o)
    );

    PC PC(
        .clk(clk),
        .rst(rst),
        .CE(1'b1),
        .D(o),
        .Q(PC_out)
    );
    assign Data_out = Rs2_data;
endmodule

拼接之后综合并上板验证即可.

4-2 CPU设计之控制器

SCPU_ctrl.v部分的接口定义使用了瓜豪文档的定义,在此引用列举的功能:

SCPU_ctrl.v信号功能

ImmSel 用于选择生成立即数的方式,0 为 I-Type,1 为 S-Type,2 为 B-Type,3 为 J-Type.
ALUSrc_B 用于选择 ALU 的 B 输入口,0 为寄存器值,1 为立即数.
MemtoReg 用于选择写回寄存器的数据来源,0 为 ALU 输出,1 为存储器读出的值,2 为 PC+4.
Jump 用于选择 PC 的下一个值,0 为 PC+4,1 为 J-Type 指令的目标地址.
Branch 用于选择是否进行分支跳转,0 为不跳转,1 为跳转.
RegWrite 用于选择是否写回寄存器,0 为不写回,1 为写回.
MemRW 用于选择存储器的读写方式,0 为读,1 为写.
ALU_Control 用于选择 ALU 的运算方式,接口定义与 Lab2 中 ALU 的实现相同.
CPU_MIO 用于选择是否进行存储器的读写,0 为不进行,1 为进行. (实际上这个控制信号可有可无)

控制信号真值表(根据lab 4-2 21-25页得到):

Opcode[7:2] Funct3 Funct7 ALUOp ALUControl Branch Jump MemRW ALUSrc RegWrite ImmSel MemtoReg
R-type add 01100 000 0000000 10 010 0 0 0 0 1 - 00
sub 同上 000 0100000 同上 110 0 0 0 0 1 - 00
slt 同上 010 0000000 同上 111 0 0 0 0 1 - 00
xor 同上 100 0000000 同上 011 0 0 0 0 1 - 00
srl 同上 101 0000000 同上 101 0 0 0 0 1 - 00
or 同上 110 0000000 同上 001 0 0 0 0 1 - 00
and 同上 111 0000000 同上 000 0 0 0 0 1 - 00
S-type sw 01000 010 - 00 010 0 0 1 1 0 01 -
B-type beq 11000 000 - 01 110 1 0 0 0 0 10 -
J-type jal 11011 - - - - 0 1 0 1 1 11 10
I-type lw 00000 010 - 00 010 0 0 0 1 1 00 01
addi 00100 000 - 11 010 0 0 0 1 1 00 00
slti 同上 010 - 同上 111 0 0 0 1 1 00 00
xori 同上 100 - 同上 011 0 0 0 1 1 00 00
ori 同上 110 - 同上 001 0 0 0 1 1 00 00
andi 同上 111 - 同上 000 0 0 0 1 1 00 00
srli 同上 101 0000000 同上 101 0 0 0 1 1 00 00

以下是连线写出来的代码:

SCPU_ctrl.v

我本来以为到这里4-2就要收尾了,但是后面的事情复杂度又超乎了我的想象.

首先我需要根据给出的仿真平台示例进行功能仿真,先书写顶层代码进行搭建:

SCPU_top.v

此处需要将lab0完成的ROM也导入进来,构成一个完整的SCPU测试平台.

assembly.py运行后得出的文件program.coe输入到Vivado中

4-3 CPU设计之指令扩展

4-4 CPU设计之中断(选)