0 回顾
上一次实践笔记(0)我们实现了一个最简单的,能够每个上升沿+4
的PC。
我们最需要关注的就是器件功能的独立性,避免内外功能混杂,同时一定要注意脑中有电路(RTL级描述的抽象电路而不是实际的门级电路,内个交给EDA工具就行)。
1 针对的指令
现在,我们分析一下R类指令,我们需要构建数据通路,而很多的R类指令是相似的,因此可以直接分析一类指令,就像所有指令都需要取指,我们就分析所有指令一样。
那么,我们要分析哪一类指令?这取决于我们准备实现的指令有什么,我们先看看。
add addu sub subu and or xor nor slt sltu sll srl sra sllv srlv srav jr
哦是的,我们需要实现以上R类指令,那么我们来分分类吧:
- 指令格式
op rs rt rd 00000 func
:- 算数运算指令:
add addu sub subu and or xor nor
- 置位指令:
slt sltu
- 移位指令:
sllv srlv srav
- 算数运算指令:
- 指令格式
op 00000 rt rd shamt func
:移位指令:sll srl sra
- 指令格式
op rs 00000 00000 00000 func
:跳转指令:jr
我们需要注意的是,我们是按照指令格式的形式进行分类的,而不是功能,因为同样指令格式的指令,(各阶段的)执行过程是类似的,可以有类似的器件和信号。
下面,我们来分析一下第一类指令格式:
指令格式op rs rt rd 00000 func
的分析如下:
2 功能分析
我们一直强调的是类似,而不是相同,因此我们先完成通用器件的分析,再针对个别特殊情况处理。
2.1 指令格式
指令 | op | rs | rt | rd | shamt | func | e.g. | explain | operation |
---|---|---|---|---|---|---|---|---|---|
add | 000000 | rs | rt | rd | 00000 | 100000 | add $1,$2,$3 | $1=$2+S3 | (rd)←(rs)+(rt); rs=$2,rt=$3,rd=$1 |
我们就先看这一条add
指令,它具备通用的特征,首先声明,我们不实现add指令的异常处理机制。
我们依次分析一下各个字段。
2.2 指令识别
CPU设计最重要的点,就是需要识别出当前执行的指令,对于该格式来说,需要识别是编码是 op
和 func
。
op = 6'b000_000
,几乎所有R型指令都是这样,在我们要实现的R型指令中,全部都是op = 6'b000_000
- 对于
func
,是R型指令识别的关键,不同的R型指令(op = 000000),func字段一定不一样,每个func
编号,对应了一个指令,对应了一种操作。
2.3 指令操作
这一类指令,shamt = 00000
都是一样的,并且并不使用该字段,可以直接忽略。
而对于rs rt rd
字段,这类指令都执行rd = rs 操作 rt
。
我们使用一种通用的表述方式:op $reg1,$reg2,$reg3
,那么对应的是
- 字段:
rs = reg2
,rt = reg3
,rd = reg1
- 操作:
rd = rs op rt
我们目前分析的这一类,都是这样的!
它的完整功能(RTL)描述是:
- 取指:M[PC]; PC <- PC + 4
- 执行:R[rd] <- R[rs] op R[rd]
3 需要的器件
之前我们知道了指令的识别与操作,现在,我们看看需要什么器件来完成操作。
首先,毫无疑问是从ROM中取指(IF阶段),这一点,我们上一篇完成了PC,但是还没有设计ROM,后面再说。
现在,假设我们取到了指令,接下来该如何处理呢?
3.1 ID(译码)阶段
我们看看这个操作:R[rd] <- R[rs] op R[rd]
毫无疑问,我们需要寄存器,是的,就是MIPS的32个内部寄存器,然后我们需要控制器,因为寄存器需要控制的,至少写入操作需要控制,不是任何时候每条指令都能够写入寄存器的。
现在,我们知道我们需要2个器件了
- 控制器
- 寄存器
问题:为什么这两个器件在译码阶段?
- 经典的流水线就是这么设计的(好吧这不是什么分析…)
- 你可以想想,我们刚刚拿到了一串二进制信息,总得知道它是什么吧,控制器就是解析编码用的,根据
op
和func
的信息,来确定这是什么指令,需要什么控制信号。 - 我们又知道,译码是需要时间的,不过组合逻辑执行会非常快。我们知道硬件是并行工作的,控制器执行的同时,也可以干点别的事情,我们知道MIPS大部分指令都需要寄存器组的内容,所以,我们就用指令的其他字段完成**寄存器访问(读)**的工作。
- 另外,我们再探析一下,什么叫做译码,译码就是解析指令,它完成了2项内容
- 控制器解析
op
和func
字段,获取控制信号 - 寄存器使用
rs
、rt
和rd
字段,完成数据的访问,当然,第一阶段实际上只是读取数据,没有使用rd
shamt
字段并没有使用,我们当前分析的指令不需要管它,至于后面的,以后再说。- 剧透(可以先不看):后面我们会看见,指令的解析不仅仅在译码阶段,也可以在其他阶段,
shamt
可以在EX
执行阶段被使用,而func
也可以在该阶段被解析,这完全取决于设计者的意志,我们最终能够达到正确执行指令的目标就好了。
- 控制器解析
3.1.1 寄存器堆(Register Files)的设计
我们看看这个寄存器堆的信号
- 指令字段相关:
rs rt rd
- 通用功能:
clk
时钟上升沿\下降沿(取决于设计)触发写入操作,rst
高电平复位 - 数据:
R[rd]
是待写入的数据,是计算结果,R[rs] R[rd]
是根据指令字段读取的数据 - 控制相关:
RegWrite
代表是否允许写入数据,是来自控制器的控制信号,高电平有效
图中有一个错误:右边输出的下面的
R[rd]
应该是R[rt]
3.1.2 控制器的设计
我们的输入信号是op
和func
字段,输出是什么?是控制信号。需要什么控制信号?我们之后再说。
我们能够得到输入和输出,一个纯组合逻辑的电路不难设计,是的,控制器的硬件实现非常简单,困难的是如何设计控制信号,我们后面一步步展开。
我们先建立一个空架子。
3.2 EX(执行)阶段
再这个操作:R[rd] <- R[rs] op R[rd]
,我们需要执行操作是op
,那么
- 这个操作是什么?
- 如何识别这个操作?
- 识别操作后,如何执行?
我们依次解答一下。
首先,这个操作是什么,取决于op
和func
字段,我们需要使用控制器识别操作,并且输出相应的控制信号。
给出了相应的控制信号,我们就知道要执行什么操作(加法,减法,还是乘法……),然后我们就需要ALU运算器完成运算。
因此,我们需要的新器件是ALU运算器。
这就是我们的ALU了
- 两个输入的数据来自于刚才从寄存器堆读取的值
- ALU的操作
op
来自于控制器输出的**运算操作ALUop
**信号 - 输出的值
R[rd]
会被写回到寄存器堆中去
现在,我们的数据通路是这样了,其中红色字是指令的字段。
4 控制信号
4.1 控制器设计
- 输入信号:
op func
- 输出信号:
RegWrite ALUop
instruction | op | func | ALUop | RegWrite |
---|---|---|---|---|
add | 000000 | 100000 | 0000 | 1 |
addu | 000000 | 100001 | 0001 | 1 |
sub | 000000 | 100010 | 0010 | 1 |
subu | 000000 | 100011 | 0011 | 1 |
and | 000000 | 100100 | 0100 | 1 |
or | 000000 | 100101 | 0101 | 1 |
xor | 000000 | 100110 | 0110 | 1 |
nor | 000000 | 100111 | 0111 | 1 |
slt | 000000 | 101010 | 1000 | 1 |
sltu | 000000 | 101011 | 1001 | 1 |
sllv | 000000 | 101010 | 1010 | 1 |
srlv | 000000 | 000110 | 1011 | 1 |
srav | 000000 | 000111 | 1100 | 1 |
其中,RegWrite
高电平有效,代表能够写入到寄存器,再配合时钟触发(暂定上升沿触发)即可向寄存器堆写入数据。
现在有13种操作,后面还有一些操作,因此暂时设置ALUop
为4位信号(最多识别16种操作),后续如果需要再更改。
4.2 ALU设计
- 数据输入:
R[rs] R[rd]
- 控制输入:
ALUop
- 数据输出:
R[rd]
ALUop | 操作 |
---|
- 操作:对应4.1中的instruction
- ALUop:对应4.1中的ALUop
这里就不写了。
5 数据线
输入的指令就是最重要的数据。
6 地址线
无
7 控制线
clk时钟信号和rst复位信号。
8 实现
8.1 控制器
control_1.v
`timescale 1ns / 1psmodule control_1(input [5:0] op,input [5:0] func,output reg RegWrite,output reg [3:0] ALUop);always @(*)
beginif(op == 0)begincase (func)6'b100000: // addbeginRegWrite <= 1;ALUop <= 4'b0000;end6'b100001: // addubeginRegWrite <= 1;ALUop <= 4'b0001;end6'b100010: // subbeginRegWrite <= 1;ALUop <= 4'b0010;end6'b100011: // sububeginRegWrite <= 1;ALUop <= 4'b0011;end6'b100100: // andbeginRegWrite <= 1;ALUop <= 4'b0100;end6'b100101: // orbeginRegWrite <= 1;ALUop <= 4'b0101;end6'b100110: // xorbeginRegWrite <= 1;ALUop <= 4'b0110;end6'b100111: // norbeginRegWrite <= 1;ALUop <= 4'b0111;end6'b101010: // sltbeginRegWrite <= 1;ALUop <= 4'b1000;end6'b101011: // sltubeginRegWrite <= 1;ALUop <= 4'b1001;end6'b000100: // sllvbeginRegWrite <= 1;ALUop <= 4'b1010;end6'b000110: // srlvbeginRegWrite <= 1;ALUop <= 4'b1011;end6'b000111: // sravbeginRegWrite <= 1;ALUop <= 4'b1100;enddefault:beginRegWrite <= 0;ALUop <= 4'b1111;endendcaseendelsebeginRegWrite <= 0;ALUop <= 4'b1111;end
endendmodule
注意默认情况下的值。
RTL优化
功能仿真测试
tb_control_1.v
`timescale 1ns / 1psmodule tb_control_1();
// control_1 Parameters
parameter PERIOD = 10;// control_1 Inputs
reg [5:0] op = 0 ;
reg [5:0] func = 0 ;// control_1 Outputs
wire RegWrite ;
wire [3:0] ALUop ;initial
beginop = 1 ;func = 0 ;#10op = 0 ;func = 6'b100000;#10op = 0 ;func = 6'b100001;#10op = 0 ;func = 6'b100010;#10op = 0 ;func = 6'b000111;#10op = 0 ;func = 6'b111111;
endcontrol_1 u_control_1 (.op ( op [5:0] ),.func ( func [5:0] ),.RegWrite ( RegWrite ),.ALUop ( ALUop [3:0] ));endmodule
是的,控制器非常简单,2个输入,2个输出,真值表都有了,非常容易不是吗?
8.2以及之后的内容,在下一篇文章。
【计算机系统设计】实践笔记(2)数据通路构建:第一类R型指令分析(2)