srlv
000000
rs
rt
rd
00000
000110
srlv$1,$2,$3
$1=$2>>$3
rd<-rt>>rs;(logical)其中rs=$3,rt=$2,rd=$1
srav
000000
rs
rt
rd
00000
000111
srav$1,$2,$3
$1=$2>>$3
rd<-rt>>rs;(arithmetic)注意符号位保留
其中rs=$3,rt=$2,rd=$1
jr
000000
rs
00000
00000
00000
001000
jr$31
goto$31
PC<-rs
I-type
op
rs
rt
immediate
addi
001000
rs
rt
immediate
addi$1,$2,100
$1=$2+100
rt<-rs+(sign-extend)immediate;其中rt=$1,rs=$2
addiu
001001
rs
rt
immediate
addiu$1,$2,100
$1=$2+100
rt<-rs+(zero-extend)immediate;其中rt=$1,rs=$2
andi
001100
rs
rt
immediate
andi$1,$2,10
$1=$2&10
rt<-rs&(zero-extend)immediate;其中rt=$1,rs=$2
ori
001101
rs
rt
immediate
andi$1,$2,10
$1=$2|10
rt<-rs|(zero-extend)immediate;其中rt=$1,rs=$2
xori
001110
rs
rt
immediate
andi$1,$2,10
$1=$2^10
rt<-rsxor(zero-extend)immediate;其中rt=$1,rs=$2
lui
001111
00000
rt
immediate
lui$1,100
$1=100*65536
rt<-immediate*65536;将16位立即数放到目标寄存器高16
位,目标寄存器的低16位填0
lw
100011
rs
rt
immediate
lw$1,10($2)
$1=memory[$2
+10]
rt<-memory[rs+(sign-extend)immediate];rt=$1,rs=$2
sw
101011
rs
rt
immediate
sw$1,10($2)
memory[$2+10]
=$1
memory[rs+(sign-extend)immediate]<-rt;rt=$1,rs=$2
beq
000100
rs
rt
immediate
beq$1,$2,10
if($1==$2)?
gotoPC+4+40
if(rs==rt)PC<-PC+4+(sign-extend)immediate<<2
bne
000101
rs
rt
immediate
bne$1,$2,10
if($1!
=$2)
gotoPC+4+40
if(rs!
=rt)PC<-PC+4+(sign-extend)immediate<<2
slti
001010
rs
rt
immediate
slti$1,$2,10
if($2<10)?
$1=1else
$1=0
if(rs<(sign-extend)immediate)rt=1elsert=0;
其中rs=$2,rt=$1
sltiu
001011
rs
rt
immediate
sltiu$1,$2,10
if($2<10)?
$1=1else
$1=0
if(rs<(zero-extend)immediate)rt=1elsert=0;
其中rs=$2,rt=$1
J-type
op
address
j
000010
address
j10000
goto10000
PC<-(PC+4)[31..28],address,0,0;address=10000/4
jal
000011
address
jal10000
$31<-PC+4;
goto10000
$31<-PC+4;PC<-(PC+4)[31..28],address,0,0
;address=10000/4
经典的MIPS指令有三种格式:
R-TYPE、I-TYPE、J-TYPE。
其编码格式各不相同,分别代表寄存器操作指令、立即数操作指令、跳转指令。
寄存器操作主要是将寄存器中的树取出进行运算并存回寄存器;立即数操作为一个寄存器数与指令中的扩展后的立即数进行运算得到结果再存回寄存器;跳转
我们后面的设计都是对这31条指令进行具体实现,所有的内容紧密围绕这31条指令。
、画数据通路图
在设计完指令后,要做的就是根据指令描述的功能确定CPU有哪些部件,并且确定各部件之间的连线方式。
首先,一个CPU最重要的部件一定是控制器。
控制器就像人的大脑,控制其他各个部件的工作。
其次,由于是基于MIPS指令集的CPU,大多数操作基于寄存器,因此必须设计一个寄存器堆。
此外,ALU运算部件对于CPU来说也是必不可少的。
由于有六条指令涉及比较操作,并且给有效位置位。
考虑到本CPU设计初衷是四级流水控制,若一条指令多个节拍使用ALU部件会造成部件冲突,后期控制会比较复杂。
因此,在本CPU中额外设计了一个比较部件。
此外,还有其他部件,如PC部件、立即数扩展部件、数据寄存器、地址寄存器等。
再根据31条指令,画出数据通路图。
这只是一个初步的数据通路图,却奠定了CPU的基本构架。
可能在后续的详细设计过程中,会对这个数据通路有所改动,但一定是以现有的图为基础。
可以说,这就是我们将要设计的CPU的一个初步蓝图。
二、详细设计
、控制信号的提取与分析
这一部分可以说是整个CPU设计过程里最重要、最复杂的过程之一了。
在这一部分,我们将依据数据通路图,分析数据通路中所有控制信号的组成与赋值。
第一步,我们将31条指令都划分为四个节拍执行完成:
取指与译码、取数与ALU操作、主存取数、写回寄存器。
我们需要对每一条指令的每一个控制信号进行分析,判断它应该在第几节拍给出。
经分析,我们在CPU工作过程中共需要控制信号38个(其中包括4位ALUCtr),分别是:
COM0,COM1,ALU_AR,PC_1,RT_DR,PC_AB,DB_IR,DB_DR,RS_ALU,RT_ALU,DE_ALU,PC_ALU,ZERO_ALU,HEX_ALU,SHAMT_ALU,DR_REG,REG_W,REG_R,ALUCtr0,ALUCtr1,ALUCtr2,ALUCtr3,ADDR_PC,DR_DB,ALU_DR,AR_AB,DR_PC,DR_REG31,PC_W,PC_R,J_PC,IMM_DE,DE_U,RD_REG,RS_REG,RT_REG,RS_COM,RT_COM,DE_COM,COM_NVST,ZERO_REG,PC_JIE。
具体的信号表在附件的Excel中。
列出所有控制信号后,对每一条指令需要哪些控制信号,在第几节拍需要这些控制信号都要标识清楚。
如对于一号指令有符号数加:
在第一节拍(取指与译码),要给出控制信号PC_1,PC_AB,DB_IR,将PC地址传入地址总线并取出相应的指令送入指令寄存器,PC再完成自加一的操作;第二节拍(取数与ALU操作)需要的控制信号是RS_ALU,RT_ALU,REG_R,ALU_DR,RS_REG,RT_REG(ALUCtr为0,四个控制信号都不给出),将指令中rs,rt地址传给寄存器,寄存器将对应寄存器中的数取出送到ALU,ALU根据给出的ALUCtr进行不同的操作。
第三节拍是写回寄存器,给出控制信号DR_REG,REG_W,RD_REG,将DR中的数、rd地址送至寄存器,并打开寄存器写开关。
用这种方式分析所有指令。
在分析完控制信号后,我们就可以对控制器进行具体设计了。
、各部件具体设计
、ALU部件
ALU是基于指令集来设计的,可完成二进制信息的算术运算、逻辑运算和移位操作,共包含31条MIPS指令。
我们将其总结为13种ALU操作类型,用四位二进制数进行编码,如下图所示。
这些二进制编码作为ALU控制信号,即aluctr。
编号
操作类型
二进制编码
1
有符号加
0000
2
无符号加
0001
3
有符号减
0010
4
无符号减
0011
5
与
0100
6
或
0101
7
异或
0110
8
非或
0111
9
有符号比较
1000
10
无符号比较
1001
11
逻辑左移
1010
12
逻辑右移
1011
13
数字右移
1100
在ALU中,为符合它的双操作数功能,我们设计了两个寄存器r1和r2,作为ALU的两个输入。
经过对指令集的分析,将进入ALU数据来源分为rt、rs,shamt,imm,PC几种类型,为了避免数据操作时发生冲突,将其分别放在两个寄存器中,其中rt、imm放在r1中,rs、shamt和PC放在r2中。
除了两个寄存器输入外,还有ALU控制信号输入端口aluctr。
一个输出端口out。
在ALU运行过程中,从r1、r2中取出数据,根据aluctr给出的控制信号进行相应的运算,从out端口输出。
ALU部件的代码见附录。
、PC部件
PC部件是用来计算下一条指令的,在一般情况下,指令都是顺序执行下去的,即PC+1,而在遇到跳转指令时,则需要进行其他复杂的操作,所以我们制作的PC部件主要分了这两种操作方法。
在编写的PC部件代码中,共有四个输入端口,分别是从当前执行的指令中取来的地址address,用于下一条PC的拼接,既然有PC拼接和PC+1的两种操作,我们就分别给出了两个输入的PC操作信号:
pc_ctr和pc_pj,另外还有从ALU来的运算结果alu_pc作为第四个输入端口。
输出端口有pc_out和pc_alu。
当接收到pc_ctr信号时,进行PC+1的操作,而当接收到pc_pj信号时,进行PC的拼接操作。
PC部件的代码见附录。
、寄存器组
寄存器是计算机的一个重要部件,用于暂存数据和指令等。
由于考虑到存储空间的大小,我们将原有的32个32位的寄存器组修改为4个32位的寄存器组。
在寄存器组中,存在两个控制信号,分别为读和写,当收到读信号时,我们接收到的地址所对应的寄存器的数据取出,若接收到的是写信号,我们就将接收到的数据写回之前发过来的地址所指示的寄存器中。
其中,地址的来源有指令中的rs_add,rrt_addr和rd_addr,数据来源为rd_data,共有两个输出端口,分别为rt_out,rs_out。
寄存器组的代码见附录。
、立即数扩展部件
立即数扩展部件的主要工作是将指令中的16位的立即数扩展成为32位的数,并且有有符号扩展和无符号扩展两种。
因此,对于一个立即数扩展部件来说,有两个输入一个输出。
输入分别是16位的立即数和控制扩展方式的控制信号,输出是扩展之后的32位的数。
立即数扩展部件具体代码见附录。
、比较部件
比较部件是我们在本次设计中的一个创新点。
考虑到每一次比较操作都要经过ALU,而涉及到比较操作的指令除了比较操作本身还会用一次ALU操作,这在流水设计中无疑会带来部件冲突。
尽管在发生冲突时,我们可以采用检测冲突、加入阻塞的方法进行排除,但这无疑给控制的设计带来了很大的困难。
我们考虑:
能否为比较操作专门设置一个比较部件,用来判断两个操作数大小并依据指令类型为标志位赋值。
为后面的操作提供依据。
我们设计的比较部件,可以完成如下四种操作:
判等置1,判不等置1,判大于置1,判小于置1。
因此比较部件有四个输入:
比较控制信号1,比较控制信号2(两个信号组合起来表示现在在做哪一种判断),比较数1以及比较数2,一个输出对标志位置位。
具体代码见附录。
、信号发生器
信号发生器是根据脉冲信号循环给出节拍控制信号的部件。
学过熟悉逻辑的都应该就得不难。
首先将clk进行分频,将分频后的信号与clk组合起来,表示四个节拍,从一到四分别为:
00,01,10,11。
在仿真后出现了下面的情况(图):
图仔细观察可以发现,途中输出信号w1,w2,w3,w4出现了毛刺(如4600ns处w1信号)。
在查阅相关资料并向教员请教后我们意识到,这就是数电中说到的“毛刺”。
由于从“01”到“10”,两个输入信号同时发生了变化,所以在变化的一瞬间出现了不想要的“毛刺”。
我们又查阅数电书,发现解决毛刺的方法就是采用“格雷码”编码方式,即每次信号变化时只有一个信号发生变化。
因此我们又添加了一个分频信号在clk的时钟下降沿翻转,与原先始终上升沿翻转的信号一起,组成了新的控制信号,从一到四分别为:
00,01,11,10。
这样产生的信号就符合“格雷码”编码方式,也不会产生“毛刺”。
(如图)
图信号发生器源码见附录。
、硬布线控制器
在设计CPU之初,我们就决定了使用硬布线方式设计控制器。
在IDE工具的帮助下设计硬布线控制器比真实的大规模集成电路设计硬布线要简单的多。
运用一些固定的格式,IDE会自动生成硬布线电路。
之前在的部分,我们就已经完成了控制信号的提取与分析,这时候只需要细心地将Excel表格中的内容转换成代码。
控制器的输入有六个:
6位的OP,6位的FUNC,节拍信号w1,w2,w3,w4,输出为所有控制信号。
如在某条指令中,需要在第几节拍给出哪个信号,就将该信号等于对应的节拍输入即可,IDE会自动生成硬布线电路。
源码见附录。
实验感悟
唐玲芳的实验感悟
经过几个星期的努力,虽然没能把CPU制作出来,却收获了不少。
在这次实验中,我最大的收获是对计算机组成原理这门课程的理解更加深刻了。
在制作的过程中,我们不懂就问,自己动脑去理解,因为我们知道,如果不理解,实验是无法进行下去的,在教员的悉心帮助下,我们也取得了一些成果。
在实验的前期,我们一步一个脚印:
确定CPU的功能、能执行指令集,制定通路路线图,分析出所有的控制信号等等。
在这个过程中,我们也充分理解到“结构决定功能”这句话,在没能全面考虑到所有的功能时,在通路图上总会少几个部件,只有将所有功能走通了,都能实现了,我们的通路才真的制定完整了。
在实验的过程中,我主要参与了ALU部件、寄存器组、PC部件的制作。
在编写程序的过程中,也遇到了不少麻烦。
比如说部件的中心思想、主要结构没有弄清楚,导致程序出错,而无法进行下去。
在之前我们一直以为控制器所给的控制信号是在ALU内部的,控制信号一来,才知道数据是从哪来ALU的,然后再从ALU中的寄存器中取出来进行相应的运算,但是后来才发现,控制器的控制信号并不归ALU管,它是各部件连接的通路上的,在ALU中我们只需要从寄存器中取数,根据ALU控制信号进行相应的运算就可以了。
明白了这一点后,我们的ALU才基本完成了正确的实现。
另外就是我对所用的Verilog语言不够熟悉,很多语法都没有完全掌握,在写程序时,断断续续,影响进度。
总的来说,在这次实验中,我的收获还是很大的。
无论是对课程的理解还是对编程、动手能力的提高,都有一定程度的帮助。
戚洪源的实验感悟
在回到信大之前,心里是非常向往这次实验的。
在科大学习了一学期计算机原理,第一次如此深入地接触计算机,当时教员说会在暑期学期用整整两周时间来做这个实验。
原本以为自己没有这样的机会,但很幸运还是解除了这个实验。
整体感觉是:
很难。
设计CPU不是纸上谈兵,是要实实在在写代码烧板子的,一个小小的错误可能就导致了最后的失败。
我在试验中最大的感受有以下几点:
一、万事开头难
做这个实验摆在面前的第一个问题就是:
现在干嘛这是一个很实际的问题,如果没有教员指导,应该怎么开始在实验开始前我就着手资料准备,和许多研究生学长交流之后确定了从指令集到通路图的方案。
浴室我着手开始搜关于设计指令集的资料,并且确定以31条MIPS指令集为基础。
事后回想起来,真是万事开头难。
当一个复杂的任务来临不知道从何入手时,一定要注意,千万不能自己搞自己那一套,很可能一开始就走到了一个错误的道路上。
既然不知道从何入手,就去查资料,请教别人,这是本次实验的收获之一。
二、模块化设计思想和黑盒设计思想
CPU这几工作量很大,而且各部分之间紧密联系。
首先时间紧,一个人去完成不太可能实现,而合作的话,如何将一个联系非常紧密的东西分开去做呢这就要用到模块化设计思想和黑盒设计思想。
将这个数据通路图中的元器件按照功能进行最小化划分。
一个元器件有什么输入什么输出,实现什么样的功能,这和总体连线是没有太大关联的。
确定出来大致有哪几个元器件,分别是什么输入输出,比如ALU部件,就两个操作数输入,一个控制信号输入,一个结果输出,至于怎么和寄存器堆相连,输出到哪里之类的问题,不需要AlU设计者去考虑。
最终,在所有元器件确认没有问题之后进行顶层连线。
讲一个个元件看成黑盒,元件设计者不需要知道外界怎么连接它,顶层设计者不需要知道内部如何运行。
这样,就可以把CPU这个有机的整体合理划分成几小块,交给不同人去分别实现。
这种思想还是令我感触十分深的。
在以后的大项目、大问题出现的时候,也可以考虑这种方式进行分工合作。
三、对于中央处理器有了更深一步的认识
实践是检验真理的唯一标准。
经过这一次的实验,我对于CPU的理解又深了一层。
在科大的时候做过一个简单的,七条指令的单周期CPU的元器件连线图,连控制信号都是教员给出来的,也可以跑指令,当时觉得非常激动。
这一次,我自己动手设计31条指领的流水CPU。
从设计指令集开始,一步一步地提取控制信号,这个过程的收获非常大。
这一次实验让我彻底理解了设计CPU的控制器应该遵循一个什么样的流程,绝不是简单复制某本书上的真值表。
在走遍31条指令后,整个CPU的数据通路图已经深深刻在我的脑海里。
还有太多太多,整个过程的确让我受益匪浅。
四、对于硬件设计有了更深的了解
原先也学习过数字设计课程,不过几乎没有接触实验。
以至于学完之后许多逻辑上的问题根本无法理解。
这一次做了实验,再回过头来想想,许多东西就可以理解了。
特别是一些问题的处理,必须用到数电的知识。
如:
设计信号发生器的时候遇到的“毛刺”问题,一开始一头雾水,根本不懂为什么会出现这个东西。
后来经教员点拨,一下子想起来这是数电中学习过的“格雷码”编码