Featured image of post 北航CO P3 单周期CPU的logisim搭建

北航CO P3 单周期CPU的logisim搭建

P3课下搭建任务


设计文档

Key Point: 数据通路,控制器及其连接

要实现的核心指令介绍

add,sub,lui,ori,lw,sw,beq,nop

add

sub

lui

ori

lw

sw

beq


数据通路的建立

名称 功能 简写
程序计数器 输出当前指令所在地址 PC
下指令地址 计算下一指令所在地址并传递给PC NPC
寄存器堆 32 个 32 位寄存器 GRF
指令存储器 存储所有的指令 IM
数据存储器 充当内存存储数据 DM
扩展单元 进行位扩展运算 EXT
算术逻辑单元 进行各项基本运算 ALU

下面先对各数据通路进行分析,并在此过程中得到对控制器的需求

(在每个数据通路中都只考虑自己需要什么

输出的东西如何被使用是其他数据通路考虑的事情!!)

PC

一个 32 位寄存器即可

输出引脚:输出当前指令所在地址,提供给IM用于取指令

输入引脚:由NPC算出的下一条指令地址

作为整个设备中最核心的时序逻辑,在PC层面其本质应为Moore机

下一周期上升沿才可输出下一PC

在输出PC以后,其他数据通路都基本呈现组合逻辑的形态,即时性进行输出内容的改变,即Mealy机

(ROM和GRF的写入操作还是要等下一上升沿的,但是关于其写入的各项信息都是即时性得到的)

PC

当然,最后输出PC值的时候要再加上 3000

若用PC初始化3000的方法,在复位后PC还是会输出 0

PC最终输出

NPC

下一指令的地址可能是PC+4

对于beq跳转 也可能是PC+4+sign_ext(imm_16 || 00)

对于j, jal跳转 还可能是PC[31:28] || imm_26 || 00

输入引脚:输入当前PC值,imm_16, imm_26

选择信号:br, zero, j

这里区分br和zero是有原因的。

详见zero产出地:ALU

输出引脚:下一条指令PC值

!!经后续分析,对于XXXAndLink指令,PC+4是被需要的,那就直接从NPC输出

NPC内部

NPC外部的连接也比较直白

NPC外部

具体分析:

控制信号的使用:

  • 只有br和zero同时为1时才是成功beq跳转
  • 鉴别出j,jal等才使用imm_26直接地址跳转

Tips: 由于jal指令为本人后续添加,因此选择了新增了MUX而不是改变原MUX

计算过程实现:简单的加法,移位,位扩展,位拼接运算

要注意位扩展一定是sign_ext,因此完全可以直接使用bit_sign_extender

IM

采用了4096*32bit的ROM 地址长度选项只需12位

!!ROM的地址以字为单位,因此PC要右移2位才能传给ROM

右移之后还需bit_extender到12位

(一定要先右移再extend,因为实质上就是取PC[13:2])

其余的没什么内容了

IM

Splitter

把这个东西单独拿出来写只是图个方便()

Splitter外部

Splitter内部的构造就只是一些小splitter

Splitter内部

EXT

EXT也是一个相对好说的部件

根据EXTOp进行不同形式的位扩展

(缺点(?)是只能用于16bit -> 32bit)

EXTOp 含义
0 无符号扩展
1 符号扩展

EXTOp具体怎么得到就是Ctrl要考虑的了

外部构造:

EXT外部

内部结构:

EXT内部

好的我刚刚意识到bit_extender本来就可以输入EXTOp

含input的bit extender

但是我的EXT更好看(?)那就不改了

GRF

GRF相对来说就复杂多了

内部结构在P0课下已经搭好了,但其正确性仍需后续测试

在此仅略加展示:

GRF内部结构

而其外部结构也大有文章

指令 使用数据
add,sub 读rs,rt写ALU到rd
ori 读rs写ALU到rt
lui 写ALU到rt
lw 读rs(即base)求ALU写MemReadData到rt
sw 读rs(即base)求ALU但不写Grf
beq 读rs,rd求ALU(做减法判zero)但不写Grf
jal 不读但写PC+4到$31

因此构造相应的控制信号与多路选择器即可

Wrsel 含义
0 写到rt
1 写到rd
Wdsel 含义
0 写ALU的结果
1 写MemReadData

后续添加的jal指令我又使用了控制信号Link

Link 含义
0 按上述两个选择器行事
1 写PC+4到$31

当然,除此之外还有最重要的写使能信号

整体外部结构如下图所示:

GRF外部结构

GRF外部结构之选择器

ALU

首先明确ALU要解决的问题:

加法,减法,或,左移16位共4种运算

显然ALUOp需要4种,即需要两位信号

ALUOp 使用指令
00 add,加法
01 sub,beq,减法
10 ori,或运算
11 lui,左移16位

不过,我并没有直接分四种器件进行操作

采用了gxp老师课上提到的方法:

加法和减法可使用同一加法器!!

a - b = a + ~b + 1

而两个数相加再加1正是加法器已经提供的功能!

加法器

最上方的接口即为进位接口

!但是复用器件的代价就是更多的选择信号

减法不仅仅要选择进位信号,还要选择参与运算的数

对于整个ALU还需要区分进行了什么运算

我们分为如下三个

使用指令 M1 M1含义 Cin Cin含义 M2 M2含义
加法 0 使用b 0 进位为0 00 加减
减法 1 使用~b 1 进位为1 00 加减
或运算 X X X X 01 或运算
左移16位 X X X X 10 左移

如何根据ALUOp写出这些控制信号也一目了然:

只需把上面两个表合起来,让ALUOp直接对应控制信号

用Combinational Analysis给我们写出来就行了

最终内部结构如下:

ALU内部

在此解释前文NPC中zero与br都需存在的原因:

beq也是对rs,rt的值做减法,但关键在于结果值为0时输出zero信号为1

因此zero信号也是会被sub甚至add干扰的

所以在zero==1时必须有br==1才有效

另一方向显然,br==1时判跳转有效当然离不开zero==1的判定

关于其外部连接,关键只是两个控制信号:

我们把GRF处使用的表格稍有侧重点地修改

指令 使用数据
add,sub rs,rt算ALU(加减)
ori rs,imm算ALU(或运算)
lui imm算ALU(移位)
lw,sw rs,imm算ALU(加法)
beq rs,rd算ALU(做减法判zero)

将rs替换成RD1,将rt,rd按照对应法则替换到RD2

可得到更清晰的表格

指令 使用数据
add,sub RD1,RD2算ALU(加减)
ori RD1,imm算ALU(或运算)
lui imm算ALU(移位)
lw,sw RD1,imm算ALU(加法)
beq RD1,RD2算ALU(做减法判zero)

即可发现控制信号除了ALUOp之外,还有操作数2的选择

操作数2可选择RD2或imm

因此构造相应的控制信号与多路选择器即可

Bsel 含义
0 RD2
1 imm

这里的imm都是imm_16经过EXT的结果

事已至此,可以顺便EXTOp也考虑清楚

准确来说,只有ori需要无符号(EXTOp == 0

EXTOp 指令
0 ori
1 lw,sw
X 其他

beq的sign_ext已经在NPC里面确认了

EXTOp对于beq指令没有意义

外部连接如下图所示:

ALU外部

DM

我们采用了3072*32bit的RAM 地址长度选项只需9位

DM和GRF逻辑类似

但是由于我们直接使用RAM

在一些内容的表达上需要按照RAM行事

  • RAM 应使用双端口模式,即设置 RAM 的 Data Interface 属性为 Separate load and store ports
  • RAM的ld也需要特别激活,因此需要一个lw信号来控制它
  • str信号就是普通的写使能信号
  • clr信号为异步复位信号
  • 和ROM类似,RAM的地址也以字为单位,因此PC要右移2位才能传入
  • 右移之后还需bit_extender到9位

(一定要先右移再extend,因为实质上就是取PC[10:2])

DM的A接口是地址,由ALU算出

要存入的信息则是GRF从rt读出来的RD2

连接比较简单

DM

控制器的搭建

在数据通路的苦苦搭建之中,其实控制信号已经都有了自己的定义

我们发现,这些定义一般都依赖于指令

那么控制器就可以建起这个桥梁:

通过opcode和funct得到指令

再由指令得出控制信号

由外部信号到指令

得到指令的过程我使用了比较器,因此构建过程非常简单

CTRL之指令

由指令到控制信号

由指令到控制信号那可就更简单了

除了 ALUOp()

我们使用OR阵列

哪条指令需要这个信号,我们就给它到这个控制信号上

这种拉线式的写法还是很方便的

CTRL之控制信号

ALUOp就只能拜托我们亲爱的Combinational Analysis啦

CTRL在整个main电路中的书写还是比较简单的

CTRL外部

我把他设计成了一个很富有信息量的样子()

搭电路的过程中一些小惬意的时光就是在修改电路外观~


测试文档

那么至此,我们的基础版CPU算是实现了基本自洽

接下来,就需要有大量的边界与随机指令去测试它了!

我首先测试了课程组给出的附件,并翻译为MIPS

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
.text
ori		$28, $0, 0
ori		$29, $0, 0
ori		 $1, $0, 0x3456
add		 $1, $1, $1
lw		 $1, 4($0)
sw	 	 $1, 4($0)
lui		 $2, 0x7878
sub		 $3, $2, $1
lui		 $5, 0x1234
ori	 	 $4, $0, 5
nop
sw	 	 $5, -1($4)
lw	 	 $3, -1($4)
beq		 $3, $5, _1
beq		 $0, $0, _13
_1:
ori		 $7, $3, 0x404
beq		 $7, $3, _11
nop
lui		 $8, 0x7777
ori		 $8, $8, -1
sub		 $0, $0, $8
ori		  0, $0, 0x1100
add		$10, $7, $6
ori		 $8, $0, 0
ori		 $9, $0, 1
ori		$10, $0, 1
_minus2:
add		 $8, $8, $10
beq		 $8, $9, _minus2
_13:
_11:
_minus1:
beq		 $0, $0, _minus1  # dead continue

寄存器数据及跳转测试

// 本测试的不足之处在于支持了MARS不支持的错误运算 //如最大正数加1,最小负数减1,-1减最小负数等

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//记得写v2.0 raw   
3c00ffff // 测试`$0`  
//下面是对低16位赋值  
3c01ffff  
3c028000  
00000000  //nop测试
3c047fff  
3c058000  
//下面是对低16位赋值  
3421ffff  // `$1` = -1   
34420000  // `$2` = 最小负数  
34630001  // `$3` = 1  
3484ffff  // `$4` = 最大正数  
34a50001  // `$5` = 最小负数 + 1   
//sw与lw的简单测试   
ac64ffff  //在0处存 `$4`  测试offset为负  
bc200001  //把0处的值给`$0` 测试`$0`    
8c040000  //把0处的值给`$4` 测试offset为0   
00253020  // `$6` = `$5` + `$1`    
10460002  //等于最小负数 跳2句   
//跳-8句跳回形成死循环  
00643820  //最大正数加一   
10e20000  //等于最小负数 跳0句  
00a34022  //最小负数+1减一   
11020000  //等于最小负数 跳0句  
01034822  //最小负数减一    
11240000  //等于最大正数 跳0句   
00225022  //-1减最小负数  
1144fff8  //等于最大正数 跳-8句  

检验方法

在保证sw无问题的前提下
将所有的敏感变量都及时存储
be like:

1
2
3
4
5
6
.macro save(%data) #不保存$t0,$t1
sw %data, 0($t0)
# 不能用addi
ori $t1, $0, 4
add $t0, $t0, $t1
.end_macro

之后只需要直接比对MARS和DM的存储区即可

comments powered by Disqus
Easy Life and Easy Learning
使用 Hugo 构建
主题 StackJimmy 设计