
现在是时候用数字电路实现一个CPU了!
先明确指令集sISA的细节:
0 7 6 5 4 3 2 1 0
+----+----+-----+-----+
| 00 | rd | rs1 | rs2 | R[rd]=R[rs1]+R[rs2] add指令, 寄存器相加
+----+----+-----+-----+
| 10 | rd | imm | R[rd]=imm li指令, 装入立即数, 高位补0
+----+----+-----+-----+
| 11 | addr | rs2 | if (R[0]!=R[rs2]) PC=addr bner0指令, 若不等于R[0]则跳转
+----+----------+-----+和之前相比, 还约定了一些寄存器的位宽
将这个用数字电路实现的sISA指令集的CPU称为sCPU
li指令的sCPU回顾在ISA层次执行指令的过程: 无论是执行什么指令, 骤都是类似的
指令周期(instruction cycle): 执行一条指令的步骤
li为例, 操作数需要看立即数是多少,
需要写入哪个目的寄存器
实现CPU = 用数字电路实现上述的每个步骤
和寄存器相比, 存储器还支持寻址
可将存储器看成一个由比特构成的矩阵
通常用深度x宽度表示存储器的规格
| 地址 | 存储字 |
|---|---|
| 0 | \(b_{(0, 2)}b_{(0, 1)}b_{(0, 0)}\) |
| 1 | \(b_{(1, 2)}b_{(1, 1)}b_{(1, 0)}\) |
存储器又分只读存储器(ROM)和随机访问存储器(RAM), 后者可读可写

地址译码器字线(word line)
位线(bit line)
硬连线方式连接到逻辑0或逻辑1
给定地址addr, 读出ROM中相应存储字的工作过程如下:
addr对应的字线有效,
使得该行中存放的信息可以通过与门
0
地址译码器, 与门和或门, 在功能上共同构成了一个多路选择器
ROM的读操作也可以看作是从多个存储字中选择一个
addr相当于多路选择器的选择端
ROM从功能上也可看作是数据端为常数的多路选择器

译码: 根据指令的编码识别指令的功能
li r0, 1010001010,
查阅指令表了解其功能
译码的工作进一步分为操作码译码和操作数译码
li指令, 无需进行li指令中的rd和imm字段
li指令的功能:
将立即数imm写入rd寄存器中
GPR通常包含多个寄存器, 一次访问通常只访问其中的几个寄存器

addr, 读出存储器中的相应内容

addr和数据D,
将数据D写入存储器中的相应位置EN,
指示当前是否需要写入
bner0指令时, 就不需要写入GPR
D通过位线将每一位分别连接到每一个存储字中相应位的D端EN端
EN有效时, 只有地址addr对应的字线有效,
其余行无效D, 从而完成写入操作
单端口RAM(single port RAM):
同一时刻只用一个地址访问一个存储字多端口RAM(multi-port RAM):
同一时刻可用多个地址访问多个存储字
对PC寄存器加1即可
取指 - 和li一样
操作码译码
opcode来确定是什么指令opcode有2位, 用2-4译码器
指令译码器操作数译码
add指令除了写入rd寄存器,
还需要读出rs1和rs2作为源操作数raddr1(读地址),
rdata1(读数据)raddr2, rdata2waddr(写地址), wdata(写数据),
wen(写使能), clk(时钟)执行
wdata信号已经被li指令占用
opcode编码的唯一性,
一条指令不可能既是li, 又是addadd指令, 就选择加法器的结果作为写入数据li指令,
就选择li指令的立即数作为写入数据
更新PC - 和li一样
取指 - 和li一样
操作码译码 - 可复用指令译码器
操作数译码
rs2和addr,
还有一个隐含的R[0]
0作为GPR的raddr1的输入add指令的rs1占用
执行 - 用比较器实现比较两数是否相等的操作
更新PC - 若比较结果不相等, 则跳转
bner0, 且比较结果不相等,
则将PC更新为addrPC+1添加指令的过程:
| 指令 | wdata的选择 |
wen |
raddr1的选择 |
更新PC的选择 |
|---|---|---|---|---|
add |
加法器的输出 | 1 |
指令的rs1 |
PC + 1 |
li |
立即数 | 1 |
X |
PC + 1 |
bner0 |
X |
0 |
0 |
指令的addr |
X表示无关项(don’t care), 选择什么都可以
bner0指令不会写入GPR
wdata选择什么, 都不影响sCPU的正确性
这些控制信号的取值与指令的类型相关
CPU执行程序 = 用程序编译出的指令序列控制CPU电路进行状态转移
虽然sCPU可以计算1+2+...+10, 但无法运行更复杂的程序
因为sISA过于简单
要运行更多程序, 就需要换一个ISA
我们提出一个迷你RISC-V指令集minirv
0add, addi,
lui, lw, lbu, sw,
sb, jalr
我们在编译层次研发了一些黑科技
取指
操作码译码
操作数译码
R[0]和sISA不同, 需要考虑执行 - 加法器
更新PC - 指令编码的位宽与sISA不同, 需要考虑如何更新
假设有\(k\)位二进制数\(A=\mathrm{0b}a_{k-1}a_{k-2}\dots a_1a_0\)
将其符号扩展至\(k+1\)位, 得到\(A^\prime=\mathrm{0b}a_{k-1}a_{k-1}a_{k-2}\dots a_1a_0\)
\[\begin{array}{ll} A^\prime&=\displaystyle -2^{k}+\sum_{i=0}^{k-1}2^ia_i=-2^k+2^{k-1}a_{k-1}+\sum_{i=0}^{k-2}2^ia_i \\ &=\displaystyle -2^k+2^{k-1}+\sum_{i=0}^{k-2}2^ia_i=-2^{k-1}+\sum_{i=0}^{k-2}2^ia_i=A \\ \end{array}\]
jalr
bner0更简单, 跳转目标作为新PC的数据选择端addi类似add
add类似lui
li类似, 只是立即数的格式不同
四条访存指令中有两条store指令, 需要额外添加RAM
lw和lbu和取指类似
lbu还需要对读出数据进行零扩展sw和sb需要写入内存
RGB Video组件
内存映射I/O方式来访问外设RGB Video组件的屏幕大小是256x256
256x256x4B=256KB[0x20000000, 0x20040000)
RGB Video
内存映射I/O的关键 - 添加一个地址译码器
isVGA和isMem课程的讲解只能让你相信这个处理器能设计出来
目前通过执行程序在RGB Video组件中显示一张256x256的图片,
都要花费1~2小时
只要在设计过程中不小心连错了一根线, 处理器运行程序的结果就可能不符合预期
1+2+...+100的求和程序,
就已经要执行6000多条指令这些缺陷说明, 使用Logisim这种手工连线的方式设计处理器, 并不是一种扩展性良好的方案
事实上, 现代的处理器设计流程主要采用代码开发的方式

在现代处理器设计流程中需要解决的一部分问题:
HDL编码只是逻辑设计一个环节的工作
事实上, 处理器设计的过程中的很多环节都与软件相关, 因为:
需要理解程序如何在处理器上运行:
要设计出一个好的处理器, 就需要重视软件在其中发挥的作用
CPU执行程序 = 用程序编译出的指令序列控制CPU电路进行状态转移
纸上得来终觉浅, 绝知此事要躬行