CPU-使用Microcode实现复杂的硬件指令

  1. CPU的一种特殊设计模式-Microcode
  2. 一种简单的Microcode Machine
    1. Spec
    2. 工作过程
    3. 一些例子
      1. FETCH指令
      2. CMOVM指令
      3. MODULOM指令
      4. MINV指令

CPU的一种特殊设计模式-Microcode

在ISA中, 尤其是CISC, 一条指令可能会完成非常复杂的任务, 这样就不好实现.

这时, 我们可以将这一条复杂的指令, 分解成若干的简单指令, 然后直接执行简单指令即可.

这种简单指令就叫做Microcode.

对于CPI而言, 一条复杂指令的CPI就等于若干简单指令的CPI之和.

一种简单的Microcode Machine

Spec

singlebus

这中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, DS, 他们的意思分别是:

  • 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