单周期CPU

懵懂时期作品之计算机组成原理

Posted by Birdie on May 20, 2022

一、实验准备

本次实验开始整合之前的实验内容,搭建完整的单周期处理器 datapath。在理论课上,学习了 MIPS 架构 CPU 的传统流程可分为取指、译码、执行、访存、回写 (Instruction Fetch (IF), Register Access and Decode, Execution, Memory Request, Write Back),五阶段。在前面几次实验中,实验 5 完成了 ALU 设计和寄存器器堆的设计;实验 6 掌握了存储器 IP 的使用;实验 7 实现了单周期 CPU 的取指、译码阶段,完成了 PC、控制器的设计。通过前面的几次实验,单周期 CPU 的设计的各模块已经具备,再引入数字逻辑课程中所实现的多路选择器、加法器等门级组件,通过对原理图的理解,分析单条(单类型)指令在数据通路中的执行路径,依次连接对应端口,将各个模块通过搭积木一样组合起来即可完成单周期 CPU。在进行本次实验前,需要具备以下基础能力:

1.1 熟悉 Vivado 的仿真功能 (行为仿真)

1.2 理解数据通路、控制器的信号

二、实验目的:

2.1 掌握单周期 CPU 数据通路图的构成、原理及其设计方法;

2.2 掌握单周期 CPU 的实现方法,代码实现方法;

2.3 认识和掌握指令与 CPU 的关系;

2.4 掌握测试单周期 CPU 的方法。

三、实验设备

3.1 ThinkPad E575 (操作系统:windows10)

3.2 Basys3开发板

3.3 Xilinx Vivado 2015.4

四、实验任务

4.1 实现Datapath,其中主要包含alu和寄存器堆、存储器、PC和controller(其中 Controller包含两部分,分别为main_decoder,alu_decoder),adder、mux2、signext、sl2。

4.2 实现指令存储器inst_mem(Single Port Rom),数据存储器data_mem(Single Port Ram),使用 BlockMemory Generator IP 构造指令,注意考虑 PC 地址位数统一。

4.3 将上述模块依指令执行顺序连接。实验给出top文件,需兼容top文件端口设定。

4.4 实验给出仿真程序,最终以仿真输出结果判断是否成功实现要求指令。

五、实验原理

5.1 实验总体框架及单周期处理器连线图

5.1.1 单周期CPU框架图

img

5.1.2 除j指令以外完整的cpu连线图

img

5.2 控制器译码信号规范

5.2.1 ALU控制码译码

img

5.2.2 信号控制码译码信号

1606911305(1)

5.3 数据通路连接

5.3.1 LW和SW指令

以LW指令为例构建基本的单周期 CPU 数据通路,需要的基本器件有 PC、regfile、存储器,见图 3,其他小的组合逻辑部件根据需求进行添加。

img

第一步为取值,将PC(即指令地址)输出到Instruction Memory(指令存储器)的address端口。LW指令的汇编格式为:LW rt, offset(base),其中base(基地址) 为指令[25:21]所指向的寄存器值,offset(地址偏移)为指令[15:0]。将base寄存器的值加上符号扩展后的立即数offset 得到访存的地址,根据地址从存储器中读取1 个字 (连续4个字节) 的值写入到 rt 寄存器中。根据 LW 指令的定义,将指令存储器读出的指令([31:0])中[25:21]连接至regfile寄存器堆的第一个输入地址,即 Address1(A1)。过程如图4所示。

img

除此之外需要将offset进行扩展,因而将指令[15:0]传至有符号扩展模块,输出32位的符号扩展信号 (SignImm)。得到基地址 base 和 offset 后,需进行加法计算,其操作可以采用如下描述:base+ sign_extend(offset)。加法计算使用ALU 中设计实现的加法运算,因而图 5 中,RD1读出base和经过有符号扩展后的 sign_extend(offset)分别作为ALU的两个输入 (SrcA、SrcB),经过ALU进行加法运算后,得到 ALUResult 作为地址,输入到数据存储器Data Memory 的 Address 端口。这里需要注意的是,由于计算地址与进行加法运算相同,所以用于控制 ALU 运算类型的信号ALUControl 与加法运算应该相同,如图 5 中蓝色部分所示。AlUControl信号由Controller译码得到,在此不再赘述。

img

将地址输入后,将数据存储器读出的数据写回到regfile中,其地址为rt,即指令的[20:16]。如图 6,连接时需要将指令[20:16]连接至寄存器堆的 Address3(A3)端口,对应的数据信号ReadDat连接至WriteData3(WD3)端口。

img

注意:LW 指令回写目标寄存器是使用的rt, 而这里R type指令使用的rd, 所以这里后面会添加一个mux。

完成上述连接后,一条能够满足LW指令执行的数据通路即完成。LW 指令执行结束后,需开始下一条指令的执行,重复同样的执行过程。唯一的不同在于PC 地址需要变为下一条指令所在地址。由于实现的是 32 位MIPS指令集,并且采用字节读写的方式,因为需要读取后续4个字节的数据,即PC+4。将得到的PC+4 信号写入PC(D触发器) 的输入端,即可实现每周期地址+4的操作。

img

5.3.2 R-type指令

R-type指令和LW指令不同的地方在于,R type指令使用目标寄存器是rd,而不是LW使用的rt, 写回寄存器堆的数据是从ALU过来,而不是LW操作中是从存储器过来。因此,通路改造方法为添加多路选择器。lw 指令写入regfile的地址为rt,而R-Type为rd,在此处加入一个多路选择器,输入分别为指令的 [20:16]和[15:11],使用RegDst(register destination) 信号控制,多路选择其输出信号命名为 WriteReg(write register)。ALU输入为rs,rt,分别对应srcA,srcB,因此需要在srcB处加入多路选择器,选择来源为RD2或立即数SignImm,控制信号为ALUSrc(ALU source)。最后写回到regfile 的值应该为ALU计算得到的值,为ALUResult,加入多路选择器控制Result来源为ALU或数据存储器,控制信号为MemtoReg(Memory to regfile)。

img

5.3.3 branch指令

beq需要三步操作,分别为条件判断,偏移计算,PC 转移,下面分别实现每步操作。条件判断需要判断rs、rt 所在的寄存器值是否相等,可以使用 ALU 的减法进行计算,输出zero信号,并与译码得到的Branch 信号 (判断是否为分支指令) 进行and操作,作为PCSrc信号。第二步偏移计算公式为:PC+4+ sign_extend(offset)*4。先将offset进行有符号扩展,再左移2位后,再最后与PC+4 相加。最后PC转移根据PCSrc信号进行控制,满足条件时,PCSrc为1,选择 PCBranch作为下一条指令的地址。通路图修改见图9:

img

5.3.4 j指令

跳转指令实际为无条件跳转,不需要做任何判断,因此更加简单。只需要计算地址,更改PC即可。其跳转目标由该指令对应的延迟槽指令的PC(PC+4)的最高4位与立即数instr_index(instr[25:0])左移2位后的值拼接得到。如图10,instr[25:0]左移2位,拼接pc[31:28],而后通过多路选择器。多路选择器直接由jump进行控制。

img

图10. j数据通路

六、实验步骤

6.1 导入alu模块和寄存器堆模块

6.2 导入 PC、Controller 模块;

6.3 从提供的基础模块中导入多路选择器、加法器模块;

6.4 使用IP核中的Block Memory,其中inst_mem导入coe文件;

6.5 编写数据通路,连接各模块;

6.6 导入顶层文件及仿真文件,运行仿真;

实验模块结构如下:

img

七、实验代码

7.1 datapath(数据通路)模块

`timescale 1ns / 1ps

module datapath(
	input wire clk,rst,
	input wire memtoreg,pcsrc,
	input wire alusrc,regdst,
	input wire regwrite,jump,
	input wire[2:0] alucontrol,
	output wire overflow,zero,
	output wire[31:0] pc,
	input wire[31:0] instr,
	output wire[31:0] aluout,writedata,
	input wire[31:0] readdata
    );
    wire zero;
    wire [31:0] aluresult;
    wire [31:0] result;
    wire [31:0] srcB;
    wire [4:0] writereg;
    wire [31:0] signimm;
    wire [31:0] pcbranch;
    wire [31:0] pc1;
    wire [31:0] pcplus4;
    wire [31:0] srcA;
    wire memwrite;
    wire [31:0] signimm2;
    wire [31:0] PCjump;
    wire [31:0] pc2;
    
    assign aluout=aluresult;
	
	//add your code here
    inst_mem imem(clk,pc[9:2],instr);
    
    adder add(.a(pc),.b(4),.y(pcplus4));
    
    regfile regfile(
        .clk(clk),
        .we3(regwrite),
        .ra1(instr[25:21]),.ra2(instr[20:16]),.wa3(writereg),
        .wd3(result),
        .rd1(srcA),.rd2(writedata)
        );
    
    controller ctl(.op(instr[31:26]),.funct(instr[5:0]),
        .zero(zero),
        .memtoreg(memtoreg),.memwrite(memwrite),
        .pcsrc(pcsrc),.alusrc(alusrc),
        .regdst(regdst),.regwrite(regwrite),
        .jump(jump),
        .alucontrol(alucontrol)
    );
    
    data_mem dmem(~clk,memwrite,aluresult[9:2],writedata,readdata);
    
    alu alu(
        .a(srcA),.b(srcB),
        .op(alucontrol),
        .y(aluresult),
        .overflow(overflow),
        .zero(zero)
    );
    
    mux2 #(32) mux(
        .d0(aluresult),
        .d1(readdata),
        .s(memtoreg),
        .y(result)
    );
    
    mux2 #(32) mux1(
        .d0(writedata),
        .d1(signimm),
        .s(alusrc),
        .y(srcB)
    );
    
    mux2 #(5) mux2(
        .d0(instr[20:16]),
        .d1(instr[15:11]),
        .s(regdst),
        .y(writereg)
    );
    
    signext signext(.a(instr[15:0]),.y(signimm));
    
    sl2 sl2(.a(signimm),.y(signimm2));
    
    sl2 sl3(.a(instr),.y(PCjump));
    
    //assign PCjump[31:28]=pcplus4[31:28];
    
    adder add1(.a(signimm2),.b(pcplus4),.y(pcbranch));
    
    mux2 #(32) mux3(
        .d0(pcplus4),
        .d1(pcbranch),
        .s(pcsrc),
        .y(pc1)
    );
    
    mux2 #(32) mux4(
        .d0(pc1),
        .d1({pcplus4[31:28],PCjump[27:0]}),
        .s(jump),
        .y(pc2)
    );
    
    flopr #(32) flopr(
        .clk(clk),
        .rst(rst),
        .d(pc2),
        .q(pc)
    );
    
endmodule

7.2 多路选择器模块

module mux2 #(parameter WIDTH = 8)(
	input wire[WIDTH-1:0] d0,d1,
	input wire s,
	output wire[WIDTH-1:0] y
    );
	
    assign y=s ? d1 : d0;
endmodule

八、实验结果

coe文件:

memory_initialization_radix = 16;
memory_initialization_vector =
20020005,
2003000c,
2067fff7,
00e22025,
00642824,
00a42820,
10a7000a,
0064202a,
10800001,
20050000,
00e2202a,
00853820,
00e23822,
ac670044,
8c020050,
08000011,
20020001,
ac020054

执行指令总览:

img

8.1 指令1:20020005

该机器码为addi $2,$0,5指令,为R-type指令中的立即数加法。

img

instr正确读取指令20020005;

pc从0变化到4;

srcA读取$0中的数值0;

srcB获取立即数5;

aluresult计算出结果:srcA与srcB的和5;

成功将计算结果存入writereg即$2寄存器中。

8.2 指令2:2003000c

该机器码为addi $3,$0,12指令,为R-type指令中的立即数加法。

img

instr正确读取指令2003000c;

pc从4变化到8;

srcA读取$0中的数值0;

srcB获取立即数c;

aluresult计算出结果:srcA与srcB的和c;

成功将计算结果存入writereg即$3寄存器中。

8.3 指令3:2067fff7

该机器码为addi $7,$3,-9指令,为R-type指令中的立即数加法。

img

instr正确读取指令2067fff7;

pc从8变化到c;

srcA读取$3中的数值c;

srcB获取立即数-12(fffffff7);

aluresult计算出结果:srcA与srcB的和3;

成功将计算结果存入writereg即$7寄存器中。

8.4 指令4:00e22025

该机器码为or $4,$7,$2指令,为R-type指令中的或运算。

img

instr正确读取指令00e22025;

pc从c变化到10;

srcA读取$7中的数值3;

srcB读取$2中的数值5;

aluresult计算出结果:3 or 5=7;

成功将计算结果存入writereg即$4寄存器中。

8.5 指令5:00642824

该机器码为and $5,$3,$4指令,为R-type指令中的与运算。

img

instr正确读取指令00642824;

pc从10变化到14;

srcA读取$3中的数值c;

srcB读取$4中的数值7;

aluresult计算出结果:c and 7=4;

成功将计算结果存入writereg即$5寄存器中。

8.6 指令6:00a42820

该机器码为add $5,$5,$4指令,为R-type指令中的加法运算。

img

instr正确读取指令00a42820;

pc从14变化到18;

srcA读取$5中的数值4;

srcB读取$4中的数值7;

aluresult计算出结果:4+7=b;

成功将计算结果存入writereg即$5寄存器中。

8.7 指令7:10a7000a

该机器码为beq $5,$7,end指令,为branch指令。

1608117712(1)

instr正确读取指令10a7000a;

srcA读取$5中的数值b;

srcB读取$7中的数值3;

branch信号为1;

b不等于3,故pcsrc信号为0;

pc正常从14变化到1c。

8.8 指令8:0064202a

该机器码为slt $4,$3,$4指令,为R-type指令中的比较指令。

img

instr正确读取指令0064202a;

pc从1c变化到20;

srcA读取$3中的数值c;

srcB读取$4中的数值7;

aluresult计算出结果:c<7=0;

成功将计算结果存入writereg即$4寄存器中。

8.9 指令9:10800001

该机器码为beq $4,$0,around指令,为branch指令。

img

instr正确读取指令10800001;

srcA读取$4中的数值0;

srcB读取$0中的数值0;

branch信号为1;

0等于0,故pcsrc信号为1;

pcbranch为around的位置28;

pc从20跳转到28。

8.10 指令10:20050000

被跳过,未执行

8.11 指令11:00e2202a

该机器码为slt $4,$7,$2指令,为R-type指令中的比较指令。

img

instr正确读取指令00e2202a;

pc从28变化到2c;

srcA读取$7中的数值3;

srcB读取$2中的数值5;

aluresult计算出结果:3<5=1;

成功将计算结果存入writereg即$4寄存器中。

8.12 指令12:00853820

该机器码为add $7,$4,$5指令,为R-type指令中的加法运算。

img

instr正确读取指令00853820;

pc从2c变化到30;

srcA读取$4中的数值1;

srcB读取$5中的数值b;

aluresult计算出结果:1+b=c;

成功将计算结果存入writereg即$7寄存器中。

8.13 指令13:00e23822

该机器码为sub $7,$7,$2指令,为R-type指令中的减法运算。

img

instr正确读取指令00e23822;

pc从30变化到34;

srcA读取$7中的数值c;

srcB读取$2中的数值5;

aluresult计算出结果:c-5=7;

成功将计算结果存入writereg即$7寄存器中。

8.14 指令14:ac670044

该机器码为sw $7,68($3)指令,为sw指令。

img

instr正确读取指令ac670044;

pc从34变化到38;

srcA读取到$3中的数值c;

srcB读取立即数68(16进制下是44);

aluresult计算出sw的地址68+12=[80](16进制下是50)。

8.15 指令15:8c020050

该机器码为lw $2,80($0)指令,为lw指令。

img

instr正确读取指令8c020050;

pc从38变化到3c;

srcA读取到$0中的数值0;

srcB读取立即数80(16进制下是50);

aluresult计算出lw取的地址80+0=[80](16进制下是50)

readdata读取到储存在[80]位置中的7,验证得sw与lw指令正常运作。

8.16 指令16:08000011

该机器码为j end指令,为跳转指令。

img

instr正确读取指令08000011;

pc从38正确跳转到44;

jump信号响应;

PCjump的低38为正确取得end的位置;

下一条instr指令正确读取ac020054。

8.17 指令17:20020001

被跳过,不执行

8.18 指令18:ac020054

该机器码为sw $2,84($0)指令,为sw指令。

1608122621(1)

instr正确读取指令ac020054;

pc为44;

srcA读取$7中的数值c;

srcB读取立即数84(16进制下是54);

aluresult计算出sw的地址84(16进制下是54)。

8.19 指令完成

4fb81de5e021798d7b89301eecb3e3c

展示“simulation succeeded”;

仿真顺利结束。