0
点赞
收藏
分享

微信扫一扫

实验项目2 简单功能型处理器设计 —— 基于MIPS 32位指令集

单周期CPU实验:
完成本次实验最重要的策略是参考CPU的流程图:
在这里插入图片描述

通过流程图,我们可以发现一共要实现五个部分,并且流程图给出了关键电线的定义。
电线的定义:
 与寄存器接口有关的:
在这里插入图片描述

在原本提供的三个接口中,添加四根交互的电线。
 与译码阶段有关的:
在这里插入图片描述

按照mips手册提供的指令,将instruction拆分,方便后续获得所需要的部分。
Control Unit:
对于单周期CPU,Load指令和MemtoReg始终相当,所以其电平高低始终和MemRead指令一样,因此可以舍去着一根电线。

电线含义:

  1. RegDst信号用来区分RF_waddr是rt还是rd。
  2. Branch信号用来指示是否存在一个跳转信号。
  3. Condition信号代表在所有跳转类指令中总有成立条件,Branch指令高电平代表条件成立。
  4. MemRead信号在Load指令高电平。
  5. MemWrite信号在Store指令高电平。
  6. ALU_conrol信号用来操控ALU。
  7. ALUsrc信号代表那些计算过程中需要利用扩展数的指令。
     与加法器、移位器有关的:
    在这里插入图片描述

 与取址阶段有关的:
在这里插入图片描述

 与Load和Store阶段有关的:
在这里插入图片描述

  1. Vaddr代表在Load过程中运算得到的Address值的末两位,其用来决定最后读入寄存器堆的值是哪一块。
  2. l_mask代表掩码,其用来选数。
     用来判断opcode的一些电线:

 一些用于符号拓展的电线:
在这里插入图片描述

前两条电线设计的分别是无符号和有符号数拓展,第三条是针对lui指令的特别设计,最后一条是为了ITYPE的branch指令设计的扩展。
取址阶段:
在这里插入图片描述

在单周期CPU,每一次上升沿都应当将PC寄存器变化为下一拍的值,其中有四个可能的情况:
  1. Rst高电平,对PC复位
  2. 一般情况下,PC下一拍就是PC+4
  3. 对于当前拍是一个Branch指令,而且这个Branch指令符合了其所被需求的条件,则下一拍跳转至PC_4+shift_sign_extend。
  4. 对于当前拍是一个跳转指令,分两种可能:
    对于R_Type的,直接跳转到寄存器堆读出的指定位置;
    对于J_Type的,跳转到
{PC_4[31:28],Instruction[25:0],2'b00};

译码阶段:
Control Unit
在这里插入图片描述

  1. RegDst信号在R_Type指令中高电平,写位置为rd否则为rt,当jal类指令时,写在第31号寄存器处。
  2. RF_wen信号代表write enable信号,在所有计算类信号、移位信号、满足条件的移 动信号、Store信号和需要向31号位写的情况下全部都是高电平。
    执行阶段:
    增加了更多功能的ALU:
    在这里插入图片描述

本ALU修改时比较简单,添加了sltu/xor/nor,其中 sltu和Carryout的电平高低是一样的,因为定义是相同的,xor和nor则直接算好了选数就行了。
自行设计的shifter:
在这里插入图片描述

由于直接使用了移位指令,因此shifter直接算出结果再进行选数即可,唯一要注意的是算术右移最好做到先把A转为有符号数,免得出现一些因为被误认为是无符号数而显得吊诡的波形。
ALU的端口信号设计:
在这里插入图片描述

 ALUcontrol较为好设计:以下是电路示意图
在这里插入图片描述

对于ITYPE和RTYPE的计算信号,除开lui需要单独设计之外,全部调用对应的ALUcontrol模块即可。
对于Load和Store信号,需要利用加法计算得到Address,因此ALUcontrol为ADD。
对于movz/movn/beq/bne指令,前者是【rt】和0比较是否相等,后者是【rs】/【rt】比较是否相等,因此利用减法,利用Zero这根电线判断即可。
对于bltz/bgez/blez/bgtz我选择使用slt操作判断【rs】和零的关系。这里有一个思考:
对于bltz和bgez,需要把A设置为【rs】B设置为0,对于blez和bgtz,需要把A设置为0B设置【rs】,这样利用slt的定义,就可以直接判断结果是否满足了。

 Extend选数设计:
基本上除了ITYPE的andi/ori/xori,其余全是有符号扩展,MUX选数即可。
 ALU的A/B赋值:
对于A,大部分操作它应当被复制为【rs】但是在movz/movn操作中,其应当为0,对于blez/bgtz前文已解释其为0。
对于B,大部分操作它应当被复制为【rt】,但对与Load/Store操作其应该被赋值为扩展后的立即数,对于ITYPE也是如此。对于blez和bgtz前文已经提及,应当被赋值为【rs】。
Shift端口信号设计:
在这里插入图片描述
Shifter端口非常方便设计,对于因为func的末两位和shiftop是一致的,对于shiftB按照不同指令选数即可。
Store指令:

在这里插入图片描述

按照mips指令要求,Address的返回值末两位为0,末两位的信息利用掩码反馈。
我选择的设计是算出所有的MemWrite指令的掩码和返回值,然后选数。
 Write_strb掩码设计:
最好设计的掩码是sw指令的掩码,因为它一定是1111。
对于sb指令的掩码,根据3-4’b1000,2-4’b0100……直接设计。
对于sh指令的掩码,如果高位为1,为4’b1100,否则为低位,为4’b0011。
对于swl和swr指令,必须注意到这是一个小根端CPU,按照mips指令参考手册的指示,即可写出对应的掩码。
随后进行按照指令选数即可。
 Write_data返回值设计:
根据掩码的定义,掩码为1所在的位,代表这一字节需要被存入内存,因此按照掩码指示设计拼接然后补零即可。
对于sw数据,由于直接是RF_data2,可以不用单独设计选出这个数。
Load指令设计:
在这里插入图片描述

虽然未作要求,但是同样设计一条掩码会比较方便,因为在原本的电路中2bit才能确定是哪一位,但先求出掩码,就只需要1bit找到是哪一位了。
在求得mask后,可以直接利用mask选数,从而得到每一种指令需要的值,最后在寄存器取值时选数即可。
RF_Wdata设计:
在这里插入图片描述

除开lui指令和shift指令需要单独设计之外,所有计算指令的结果就存在ALU_result。
对于其他指令:

  1. 对于jal/jalr指令,需要将下一拍PC值存在【31】即PC+8.
  2. 对于shift指令,结果存在shift_result。
  3. 对于lui指令,直接选取预先算好的lui即可。
  4. 对于load指令,需要扩展的按照指令要求,相应的符号或者无符号扩展即可。
    多周期CPU实验:
    多周期CPU状态转移图:
    在这里插入图片描述

多周期CPU自动机代码:

always @(*)begin
    case(current_state)
        `state_IF : begin
            next_state = `state_ID;
        end
        `state_ID : begin
            if(op_EX == 1'b1)
                next_state = `state_EX;
            else 
                next_state = `state_IF;
        end
        `state_EX : begin
            if(op_REGIMM|op_I_Type_branch|op_j)
                next_state = `state_IF;
            else 
                if(op_R_Type|op_jal|op_I_Type_cal)
                    next_state = `state_WB;
                else if(Load|Store)
                        next_state = `state_MEM;
                    else
                        next_state = `state_EX;
        end
        `state_MEM: begin
            if(Load)
                next_state = `state_WB;
            else 
                if(Store)
                    next_state = `state_IF;
                else 
                    next_state = `state_MEM; 
        end
        `state_WB : begin
            next_state = `state_IF;
        end 
    default:
        next_state = `state_IF;
    endcase
end

多周期CPU的核心是添加一个自动机模块,按照CPU设计的五个阶段,定义相应的状态,在这里我使用one-hot编码:

`define state_NOP 9'b000000001
`define state_IF  9'b000000010
`define state_ID  9'b000000100
`define state_EX  9'b000001000
`define state_MEM 9'b000010000
`define state_WB  9'b000100000

对于状态转移过程,总的来讲,按照转移图设计代码,即可,但是值得注意的的是,为避免形成锁存器,这个组合逻辑所有的if结构必须要保有else,因此如果本状态所有状态选择完毕后,要将不合逻辑的输入状态保留在原本的状态位置,也就是自己的下个状态仍然是自己。
除此之外PC的时序逻辑也需要修改:

    always@(posedge clk)begin
        if(rst)begin
            PC<=32'b0;
        end
        else begin
            if(current_state == `state_IF )
                PC<=PC+4;
            else 
                if(current_state == `state_EX && (op_jump|(Branch&condition)))
                    if(op_jump)
                        PC<=PC_jump;
                    else 
                        PC<=PC_branch;
        end
    end

对于PC来讲,在取址阶段其一定会正常加4,对于branch指令在执行阶段跳转。由于mips指令集存在一种被称为延迟槽的技术,因此对于branch指令的PC+4得到的那条多余的指令其实是一个nop指令,因此不会执行。
寄存器:

    always@(posedge clk)begin
        if(current_state == `state_IF)begin
            PC_temp<=PC;
        end
    end

    always@(posedge clk)begin
        if(current_state == `state_IF)begin
            Instruction_temp<= Instruction;     
        end
    end

    always@(posedge clk)begin
        if(MemRead == 1'b1)begin
            Read_data_temp<=Read_data;
        end
    end

多周期CPU设计了三种寄存器,分别是PC/Instruction/Read_data,对于前两者,由于在译码阶段PC会改变,所以需要存储上一拍PC和Instruction的值,对于后一个,则只有在MemRead拉高时才有值,因此需要用寄存器存储。

举报

相关推荐

0 条评论