0%

111:一生一芯F5(4)

F5 支持数列求和的简单处理器

我们已经学会了基本的数字电路逻辑和状态机的思想,现在我们要着手实现一个简单的处理器。首先我们需要明确接下来要实现的指令集sLSA和处理器的细节:

  • PC位宽为4位, 初值为0
  • GPR有4个, 位宽均为8位
  • 支持如下3条指令:
1
2
3
4
5
6
7
8
 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]则跳转
+----+----------+-----+

接下来完成实验的任务:

F5.1.1 实现取指功能

通过多路选择器实现一个ROM, 并在其中存放数列求和的指令序列, 然后通过PC寄存器取出指令. 你需要根据你的理解来确定ROM的规格.

先把数列求和的指令翻译成机器码,以1+2+3+...+10为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
0: li r0, 10   
1: li r1, 0
2: li r2, 0
3: li r3, 1
4: add r1, r1, r3
5: add r2, r2, r1
6: bner0 r1, 4
7: bner0 r3, 7

10001010
10010000
10100000
10110001
00010111
00101001
11010001
11011111

然后将数据硬编码到我们的ROM中,并通过PC进行取值:

图中左上是PC寄存器,负责提供地址。右边这一堆是编码的ROM存储电路,可以将我们的数列求和程序读取出来

image.png

F5.1.2 实现GPR及其写入功能

在寄存器的基础上搭建一个RAM, 从而实现GPR的写入功能. 你需要根据你的理解来确定RAM的规格.

我们的GPR有4个寄存器,每个寄存器存储八位的数据。可以简单设计如下:

image.png

F5.1.3 实现仅支持li指令的sCPU

根据上文, 用数字电路实现li的指令周期涉及的各个部件, 并将它们连接起来. 实现后, 尝试让sCPU执行数列求和程序中的前几条li指令, 并观察电路中GPR的状态是否与ISA的状态一致.

成功的从ROM中向寄存器写入了值:

image.png

F5.2.1 添加add指令

根据上文, 在sCPU中添加add指令. 实现后, 尝试让sCPU继续执行数列求和程序中的几条add指令, 并观察电路中GPR的状态是否与ISA的状态一致.

经过上一个实验,现在我们的寄存器已经能够成功的接受写入了。但是为了实现进一步的指令支持,我们需要将我们的GPR优化并封装起来,我们需要实现以下端口:

  • 第1个读端口: raddr1(读地址), rdata1(读数据)
  • 第2个读端口: raddr2, rdata2
  • 写端口: waddr(写地址), wdata(写数据), wen(写使能), clk(时钟)
image.png

这样我们就能满足add指令,同时读取两个源操作数和写入一个寄存器的操作了。

但是再次之前我们还需要进一步实现译码功能,从而将我们的addli逻辑分离,不同的指令需要不同的信号:

  • li:
    • 需要waddrimm作为输入,需要写使能信号开启
  • add:
    • 需要raddr1raddr2作为输入
    • 接受rdata1rdata2,做加法输入到wdata
    • 根据wdatawaddr和写使能信号,更新寄存器

综上,我们可以规划出我们的电路图,以完成前6条指令的执行:

image.png

以下是执行一轮前六条指令后的寄存器状态:

image.png

F5.2.2 添加bner0指令

根据上文, 在sCPU中添加bner0指令. 实现后, 尝试让sCPU执行完整的数列求和程序, 如果你的实现正确, 你应该能看到PC最终为7, 且在某GPR中存放求和结果55.

添加bner0我们首先分析他需要哪些输入和信号:

  • 首先需要addr用于改变PC的值,也就是说我们现在需要一个机制能支持我们改变PC的内容
  • 同时需要读取r0r2的值进行比对,如果不同则跳转

这两个问题都可以简单的通过多路复用器的思路来解决:

image.png

我们的求和程序运行的最终情况如下。可以看到r2 = 0x37 = 55符合我们的预期:

image.png

F5.2.3 和数列求和电路进行对比

在学习数字电路时, 有一道必做题要求你通过寄存器和加法器, 计算出1+2+...+10的结果. 现在你用sCPU完成了同样的计算, 尝试对比两个方案各有什么优点和缺点.

先前的数字求和电路,是通过设计电路的模式来实现”程序”的运行,那种行为类似于程序中的硬编码,比较死板,而且不方便改动。我们每想运行一个程序都需要重新设计一个电路。

但是想这种通过操控寄存器的方法,明显为我们带来了更好的可拓展性,我们可以通过现有的三个指令,运行各种加法程序。我们只需要对指令的内容进行改动就好了,这给我们带来了更好的可拓展性。所以我认为这是一种进步。不过相应的,为了满足电路能够运行指令,我们的设计也会更加的复杂,无法像先前的电路一样轻便。

F5.3.1 计算10以内的奇数之和

编写一段指令序列, 计算10以内的奇数之和, 即1+3+5+7+9. 然后尝试用你设计的sCPU指令这段指令序列, 检查运行结果是否符合预期

首先我们需要为其编写相应的指令:

1
2
3
4
5
6
7
8
0: li r0, 11   
1: li r1, 1
2: li r2, 0
3: li r3, 2
4: add r2, r2, r1
5: add r1, r1, r3
6: bner0 r1, 4
7: bner0 r3, 7

然后将程序编写进我们的ROM中,执行得到结果如下(R2 = 0x19 = 25):

image.png

F5.3.2. 添加新指令

尝试为sISA添加一条新指令out rs, 执行该指令后, 会将R[rs]以十六进制的形式输出到七段数码管. 你可以自行决定这条指令的编码.

然后, 在sCPU中实现out指令, 并修改数列求和程序, 使得在计算出结果后, 能在七段数码管中显示计算结果.

首先向我们的ISA中添加out的格式

1
2
3
4
5
6
7
8
9
10
 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]则跳转
+----+----------+-----+
| 01 | none | rs2 | R[rs2] -> 七段数码管 out指令, 将R[rs2]的值输出到七段数码管
+----+----------+-----+

这个指令只需要读取rs2作为输入就可以了,所以我们添加一个显示内容的电路,就可以完成啦:

image.png