「BUAA-CO」 P4课下


简介

从P4开始,我们将开始搭建单周期CPU,使用的编程语言仅仅是verilog,并且对于verilog的要求仅限于语法,没有烦人的状态机。所以被P1搞红温的同学可以长舒一口气了😋。

设计方案综述

本次实验实现的指令包括add,sub,ori,lw,sw,beq,lui,sll,j,jal,jr,jalr,通过将各个元件模块化封装,再用wire连接,进而封装成完整的CPU。

整体架构图如下:
整体架构图

下面将分模块详细介绍一下设计思路和实现方法。

关键模块实现

datapath

这一部分是除了CTRL之外的元件的集合,相当于顶层模块MIPS的两个子分支之一。其实理论上可以将DM,ALU,CTRL等元件直接揉到一起写成mips.v,但是笔者认为像这样分开来写思路更加清晰条理。

datapath模块的顶层是将ifu,am,alu等组件封装起来,形成一个对CTRL的接口。代码如下:

module DATAPATH (
input clk,
input reset,
input [2:0] NPCOp,
input [2:0] WDSel,
input WESel,
input [2:0] WRA3Sel,
input [2:0] ALUOp,
input BSel,
input EXTOp,
input DMWr,
output [5:0] opcode,
output [5:0] func
);

// 首先对模块实例化,其次要定义连线,
// 这里定义的wire是真的wire
wire [31:0] ifu_pc;
wire ALU_zero;
wire [31:0] instr;
wire [31:0] IFU_npc;
wire [31:0] NPC_pc_4;
wire [4:0] grf_A3;
wire [31:0] grf_WD;
wire [31:0] grf_RD1;
wire [31:0] grf_RD2;
wire [31:0] ALU_C;
wire [31:0] ext_imm;
wire [31:0] ALU_B;
wire [31:0] dm_rd;

这里以IFU模块为例说明模块实例化如何实现,请看代码:

IFU ifu (
.NPC(IFU_npc),
.PC(ifu_pc),
.instruct(instr),
.clk(clk), //out
.reset(reset) //out
);

其中IFU_npc代表NPC模块的输出NPC,也是IFU的输入。理论上讲这种格式应该是括号外是子模块的端口而括号内是父模块的信号。为了方便理解可以把他直观看成一根线,一根连接两个端口的线。

其他子模块

剩余子模块相对独立,正常翻译logisim代码即可。

特别说明,为了方便表示电路中的所有连线,新增MUX模块,端口定义如下:

module MUX (
input [4:0] WRA3MUX1,
input [4:0] WRA3MUX2,
input [4:0] WRA3MUX3,
input [2:0] WRA3Sel,
output [4:0] A3,

input [31:0] WDMUX1,
input [31:0] WDMUX2,
input [31:0] WDMUX3,
input [ 2:0] WDSel,
output [31:0] WD,

input [31:0] BMUX1,
input [31:0] BMUX2,
input BSel,
output [31:0] ALU_B
);

CTRL

ctrl 要实现和datapath对接的接口,内部按照logisim设计时的与或关系来实现即可。

mips

mips作为顶层电路,实现的是CTRL模块和DATAPATH模块的的拼接

Some Tips

  • 关于连线:在datapath模块的线路拼接中,由于一根线的两端连接的元件一般不同,所以建议在给wire命名时加上相连的某个元件,在写代码时便于识别信号通路。例如wire ALU_zero;

但是在mips中的线路拼接时,由于只有两个元件,因此所有接口都是一一对应关系。子模块端口和父模块信号可以一样,不会混淆。例如

wire [5:0] func; 

DATAPATH datapath(
//.....
.func(func) //out
);

CTRL ctrl(
//.....
.func(func) //in
);

  • 关于命名:个人习惯模块名称大写,实例化时的名字小写,例如 DATAPATH datapath

Some Bugs

  1. IFU模块中的指令文档命名 code.txt
  2. NPC模块中传入的zero一定仔细考虑好BEQ指令下zero==0zero!=0的输出分别是什么,千万别搞混
  3. CTRL模块中的JRJALR指令有opcode==SPECIAL (一定要记得),并且是opcode不是func。😭
  4. 注意一下在对指令切片时放大logisim仔细瞅瞅,要不然就要硬搓Isim仿真了

Verilog仿真指南

verilog的testbench还是较为友好的,只需要开始时reset=1,#100后reset = 0。

想要监测指令名称时看波形图
想要监测寄存器中的数据时,在 Isim 界面左侧选择 Memory 选项,如图:

register查看示例

思考题

  • Q:这个 addr 信号又是从哪里来的?地址信号 addr 位数为什么是 [11:2] 而不是 [9:0] ?

A:

addr由ALU计算得到。一条地址占32位2进制,也就是4个字节,截取 [11:2] 相当于将地址右移四位,将地址转换位第n条指令,从而便于在RAM读写。
  • Q:
    思考上述两种控制器设计的译码方式,给出代码示例,并尝试对比各方式的优劣。

A:

指令对应的控制信号如何取值:

always @(*) begin
if (add == 1) begin
WESel = 1;
WRA3Sel[0] = 1;
end
end

控制信号每种取值对应的指令:

assign NPCOp[0] = beq | jalr | jr;
assign NPCOp[1] = j | jal | jalr | jr;
assign NPCOp[2] = 0;

优劣对比:

  • Q:复位信号的设计都是同步复位,这与 P3 中的设计要求不同。请对比同步复位与异步复位这两种方式的 reset 信号与 clk 信号优先级的关系。

A :

异步复位:reset信号优先级大于clk

同步复位:reset信号优先级小于clk

  • Q:C 语言是一种弱类型程序设计语言。C 语言中不对计算结果溢出进行处理,这意味着 C 语言要求程序员必须很清楚计算结果是否会导致溢出。因此,如果仅仅支持 C 语言,MIPS 指令的所有计算指令均可以忽略溢出。 请说明为什么在忽略溢出的前提下,addi 与 addiu 是等价的,add 与 addu 是等价的。提示:阅读《MIPS32® Architecture For Programmers Volume II: The MIPS32® Instruction Set》中相关指令的 Operation 部分(详见文档 page 34、page 35)。

A:

在MIPS英文指令集中,对于add指令的定义是 ADDU performs the same arithmetic operation but does not trap on overflow.因此addu和add在忽略溢出的前提下是等价的。

Lyrics Sharing

等不到天黑
烟火不会太完美
回忆烧成灰
还是等不到结尾
他层说的无所谓
我怕一天一天被摧毁
等不到天黑
不敢凋谢的花蕾
雨也在跟随
放开刺痛的滋味
今后不再怕天明
我想只是害怕清醒

文章作者: Cordial-Kid
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Cordial-Kid !
  目录