CPU的一种特殊设计模式-Microcode
在ISA中, 尤其是CISC, 一条指令可能会完成非常复杂的任务, 这样就不好实现.
这时, 我们可以将这一条复杂的指令, 分解成若干的简单指令, 然后直接执行简单指令即可.
这种简单指令就叫做Microcode.
对于CPI而言, 一条复杂指令的CPI就等于若干简单指令的CPI之和.
一种简单的Microcode Machine
Spec
这中Microcode Machine是一种单总线的结构, 每一次只能有一个结构写入总线, 但是可以有很多结构读取总线.
这种Machine的spec如下:
- IR: Instruction Register, 里面存储着将要被执行的Microcode.
- 其中的控制信号是
IRLd
, 当IRLd
为1时, 在当前周期, 总线上的数据会被读取到IR中.
- 其中的控制信号是
- Decode: 用来翻译Microcode, 看看这个微码需要用哪些寄存器.
ImmEn
: 当ImmEn
为1时, 在当前周期, Decode会把立即数写入总线.ImmSel
: 这个信号是控制输入哪一种立即数, RISC-V中立即数可以分为I, J, U, S, B型.
- Register File: 寄存器文件中包括
PC
,RA
和一些通用寄存器.、RegEn
: 和RegSel
一起使用, 如果这个信号是1, 在当前周期, 对应寄存器的数据会写入总线.RegWr
: 和RegSel
一起使用, 如果这个信号是1, 在当前周期, 总线上的数据会写入对应寄存器.
- ALU:
ALd
,BLd
: 当这两个信号是1时, 总线上的数据会被读取到A, B
中, 这两个寄存器存储ALU的源操作数.ALUOp
: ALU的计算类型.ALUEn
: 在当前周期将ALU计算得到的数据写入总线.
其中ALUOp一共有如下几种:
ALUOp | ALU输出 |
---|---|
COPY_A | A |
COPY_B | B |
INC_A_1 | A+1 |
DEC_A_1 | A-1 |
INC_A_4 | A+4 |
DEC_A_4 | A-4 |
ADD | A+B |
SUB | A-B |
SLT | Signed(A) < Signed(B) |
SLTU | A < B |
- MA: 当一个地址写入MA之后, 下一个周期如果设置
MemEn
为1, 会把从内存中读出的数据写到总线上. 如果下一个周期MemWr
为1, 会把总线的数据读入内存.MALd
: 如果设置为1, 当前周期总线的数据会被写到MA
中.
工作过程
这个Microcode Machine的工作过程可以用一个表格来描述:
State | Pseudocode | IRLd | RegSel | RegWr | RegEn | ALd | BLd | ALUOp | ALUEn | MALd | MemWr | MemEn | ImmSel | ImmEn | μBR | Next State |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
其中, State
表示当前执行的宏观指令, Pseudocode
是宏观指令对应的Microcode.
μBR和Next State共同控制要转移到的下一个状态.
这个μBR有六个值, 分别是N
, J
, EZ
, NZ
, D
和S
, 他们的意思分别是:
N
: 下一个状态是Current State + 1
.J
: 无条件跳转到Next State
.EZ
: 下一个状态取决于ALU的输出, 如果ALU输出是0, 那么下一个状态就是Next State
, 否则就是Current State + 1
.NZ
: 同上, 这个判断的是ALU的输出不是0.D
: 对于下一个状态, 整个计算机会先看IR
中的指令, 根据IR
中的指令转移到对应的状态.S
: 如果是S
, 会先判断当前的微状态μPC是不是正在busy(例如读取内存, 需要好多个cycles), 如果正忙就spin, 如果不忙就转移到下一个状态, microcode涉及内存操作会用这个.
一些例子
FETCH指令
例如取指令这个大状态, 把这个状态叫做FETCH0
, 那么FETCH0
这个大状态可以分为如下几个microcode来完成:
- 首先, 我需要把寄存器的读信号
RegEn
设置成1, 然后把寄存器的RegSel
设置成PC
, 表示我要读PC
这个寄存器, 这样总线上就有PC
的值.- 之后, 我把
ALd
设置成1, 下个周期总线上的PC
值会被读到ALU的缓冲区寄存器A
中, 用来下一步计算PC+4
. - 然后, 再把
MALd
设置成1, 下个周期总线上的PC
值会被读到MA
寄存器中, 如果下个周期内存读信号MemEn
为1, 那么总线上就能拿到当前PC
对应的指令.
- 之后, 我把
- 然后, 现在到了下一个周期, 我把
MemEn
设置成1, 总线上就是当前PC
对应的指令.- 之后, 我把
IRLd
设置成1, 那么下一个周期总线上的PC
值就会被读如IR
寄存器中.
- 之后, 我把
- 最后, 我把
ALUEn
设置成1, 然后ALUOp
设置成INC_A_4
, 那么总线上就会是PC+4
的值.- 之后, 我把
RegWr
设置成1, 把RegSel
设置成PC
, 那么下一个周期后, 寄存器中原来的PC
就会是PC+4
.
- 之后, 我把
- 就完成了这个状态机.
如果用表格描述的话可以这样描述:
State | Pseudocode | IRLd | RegSel | RegWr | RegEn | ALd | BLd | ALUOp | ALUEn | MALd | MemWr | MemEn | ImmSel | ImmEn | μBR | Next State |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
FETCH0 | MA, A := PC | 0 | PC | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | N | * |
IR := Mem | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | S | * | |
PC := PC + 4 | 0 | PC | 1 | 0 | 0 | 0 | INC_A_4 | 1 | 0 | 0 | 0 | 0 | 0 | D | * |
CMOVM指令
CMOVM指令的格式为: cmovm rd, rs1, rs2
, 语义是如果rs2
不是0, 那么Mem[R[rd]] := Mem[R[rs1]]
.
这个指令可以分为如下的microcode:
A := R[rs2]
MA := R[rs1]
+ ALU判断状态转移.A := Mem
MA := R[rd]
Mem := A
因此, 这个题目是有sketch的:
- 如果你想读取一个寄存器:
做判断: 读到
A
中作为内存地址: 读到
MA
中.如果读内存: 下个周期就把
Mem
读到A
中.如果写内存: 下个周期就把
A
写入Mem
中.
对应的状态转移表格如下:
State | Pseudocode | IRLd | RegSel | RegWr | RegEn | ALd | BLd | ALUOp | ALUEn | MALd | MemWr | MemEn | ImmSel | ImmEn | μBR | Next State |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
CMOVM0 | A := R[rs2] | 0 | rs2 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | N | * |
MA := R[rs1] + branch | 0 | rs1 | 0 | 1 | 0 | 0 | COPY_A | 0 | 1 | 0 | 0 | 0 | 0 | EZ | FETCH0 | |
A := Mem | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | S | * | |
MA := R[rd] | 0 | rd | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | N | * | |
Mem := A | 0 | 0 | 0 | 0 | 0 | 0 | COPY_A | 1 | 0 | 1 | 0 | 0 | 0 | S | * | |
branch to FETCH0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | J | FETCH0 |
MODULOM指令
MODULOM
指令的格式是: modulom rd, rs1, rs2
, 语义是Mem[rd] := Mem[rs1] % Mem[rs2]
其中, modulom
算法的实现可以参考:
unsigned int modulom(unsigned int x, unsigned int y) {
while (x >= y) x -= y;
return x;
}
首先, 这个题目涉及循环, 可以把Microcode分成三个Label.
MODULOM0:
MA := R[rs1]
A := Mem
MA := R[rs2]
B := Mem
LOOP:
if A < B goto DONE, MA := R[rd]
A := A - B, goto LOOP
DONE:
Mem := A
goto FETCH0
状态转移表格如下:
State | Pseudocode | IRLd | RegSel | RegWr | RegEn | ALd | BLd | ALUOp | ALUEn | MALd | MemWr | MemEn | ImmSel | ImmEn | μBR | Next State |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
MODULOM0 | MA := R[rs1] | 0 | rs1 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | N | * |
A := Mem | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | S | ||
MA := R[rs2] | 0 | rs2 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | N | * | |
B := Mem | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | S | ||
LOOP | if A < B goto DONE, MA := R[rd] | 0 | rd | 0 | 1 | 0 | 0 | SLTU | 0 | 1 | 0 | 0 | 0 | 0 | NZ | DONE |
A := A - B, goto LOOP | 0 | 0 | 0 | 0 | 1 | 0 | SUB | 1 | 0 | 0 | 0 | 0 | 0 | J | LOOP | |
DONE | Mem := A | 0 | 0 | 0 | 0 | 0 | 0 | COPY_A | 1 | 0 | 1 | 0 | 0 | 0 | S | |
goto FETCH0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | J | FETCH0 |
注意最后不要忘记goto FETCH0
.
注意, 如果μBR是S
的话, 那么Next State
应该是空白, 而不是*
.
MINV指令
MINV
指令的格式是: minv rd, rs1, rs2
, 它的语义是: 找到一个向量中元素的最小值.
rs1
指向的是向量起始元素的地址.rs2
指向的是向量终止元素的地址.rd
存储返回的最小值.
注意, 向量中的元素全部是32位的无符号数.
这个指令的伪代码可以表示为:
int ans = -1;
for (int i = start; i < end; i ++) {
if (vector[i] < ans) ans = vector[i];
}
return ans;
Microcode可以是:
MINV0:
A := B
A := A - B
(这两步是在初始化A
为0)
R[rd] := A - 1
(这一步是在初始化最大值为-1, 按照无符号数就是最大值)
LOOP:
A,MA := R[rs1]
B := R[rs2]
if not (A < B) goto FETCH0, B := R[rd]
R[rs1] := A + 4
A := Mem
if not (A < B) goto LOOP
R[rd] := A, goto LOOP