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