这次任务可以说是一个比较难的任务的,需要仔细阅读RISCV手册,理解其中的指令才能做到。

这边我是一共做了大概20多个小时吧。

RV32E的寄存器数量应该是16个寄存器,但是我为了后面的拓展,这里使用了32位的寄存器堆。

关于RISCV指令的阅读

这次实现的一共有8条指令: add, addi, lui, lw, lbu, sw, sb, jalr

R 型格式

包含助记符:add

1
2
31-25 | 24-20 | 19-15 | 14-12 | 11-7  | 6-0
funct7| rs2 | rs1 | funct3| rd |opcode

I型格式

包含助记符:addi,lw,lb,jalr

1
2
31-20 | 19-15 | 14-12 | 11-7  | 6-0
imm12 | rs1 | funct3| rd |opcode

S型格式

包含助记符:sw,sb

1
2
31-25 | 24-20 | 19-15 | 14-12 | 11-7  | 6-0
imm[11:5]|rs2 | rs1 | funct3|imm[4:0]|opcode

U型格式

包含助记符:lui

1
2
31-12 | 11-7  | 6-0
imm20 | rd |opcode

具体的机器码请自行阅读手册。

搭建过程

准备元件

  1. 16个寄存器组成的寄存器堆(这里我采用了32个寄存器的),需要注意的是寄存器x0是在硬件上锁死为零的。

image-20260206005819148

  1. 一个ROM和一个RAM

image-20260205231452199

剩下的元件都要自己手搓更改,所以我就放在下面讲了,其中也有我遇见的一些坑。

手搓的元件

  1. PC计数器是不可少的。

这里我采用了已经实例化的conter来作为我的PC寄存器,当然我加了一些修改。

因为这个CPU是RISVE的指令,所以他是四对齐的,而PC寄存器和RAM,ROM在logisim的增加都是一位一位增加的。(字计数和字节计数区别)

所以这些地址应该是经过处理的,在进入RAM,ROM,conter的时候要除以4,在从conter里面出来的时候要乘以4(这里要注意,不要把RAM读出的数据乘了,他该咋还是咋)。

**重点:**千万不要用乘法器和除法器,就是这logisim里面已经实例化的也不要使用,这两个元件很复杂,会拖信号时间很久,导致clk早早进入了,他还在运算。

而对于这个乘四除四的操作,我们完全可以通过位移器来实现。

  1. PC计数器的改造是需要的。

为了适配我们的jalr命令,我们需要对PC计数器进行一定的改造。

image-20260205232252811

我们需要把这个M1M2接口恒定设置为1,这里是让PC寄存器进入到load状态,让他的下接口可以读入数据。

然后我们需要区分情况,当出现jalr命令时,PC计数器要跳到指定地址,正常指令时,PC计数器要正常加1,所以我这里通过多来两个寄存器来实现。(为什么是两个后面就明白了)

image-20260205232833859

image-20260205232721587

我来讲解一下这个搭线的方法,下面寄存器的作用,是读取我们拆解jarl命令整来的地址,而上面的寄存器,则是记录当前的conter的地址值加一,以便于正常的运行程序的,他们通过一个分路选择器来选择,当jarl信号存在的时候,输入信号选择下面的寄存器进入conter,没有的时候,就是选上面的正常加一执行。

而jarl命令还有一个特性,就是会将返回地址存在指定寄存器里面,这就又反应出了上边的寄存器的需要了,将这个寄存器进行乘四输入存储,就可以了。

ps:有人可能会问这个clockk是什么,其实这里面全是非门,是为了拖慢clk的执行的,至于为什么要拖慢,全是那个除法器的锅,所以我不推荐用除法器。

  1. 解码器亦是不可或缺的。

现在开始讲解码器的搭建,这里需要你对RISCV指令集的这八条指令有一个详细的了解,不了解的请返工看手册。

我们先采用分线器进行初步的分离。

image-20260205233947200

这是将机器码分解成了几个核心部分。

3.1.不得不品的立即数处理。

这里关于立即数的处理需要考虑到不同形式命令的编码情况。

image-20260205234458999

这是我搭建的立即数处理器,根据不同的指令情况来输出立即数,当然,还得考虑负数的情况,这里采用了扩展器的sign模式来实现。

3.2.控制核心的搭建是简单但是重要的

image-20260205234832141

这里就是识别指令的核心部分了,并不难理解,就是简单的信号拆解。

  1. 控制单元的集成

image-20260205235030823

这里都是实现一些各种情况的处理,比如内存的写入写出,还有加法器的实现。

4.1.简单的ALU

image-20260205235147234

没什么特色。

  1. 真正的重头戏——内存的实现

image-20260205235406330

令人望而生畏,不是吗。

这个内存模块的核心是RAM,其他的东西都是围绕RAM进行的条件判断。

image-20260205235515763

这里对于输入进行了一系列判断,具体看名字也能知道引脚的作用。

关于这边的接线可以说是十分混乱,因为这是我在debug的过程中不断改进的结果。

首先还是四位对齐的问题,用位移器去实现就可以。

关于sb和lbu这类字节写入的问题,RAM有一个开启字节的选项,记得打开。

然后对于他们进行判断,其实通过分线器拿出来这些数值在除四后被抛弃的那两位就可以实现,通过分路选择器就可以达到了。

image-20260206000046213

image-20260206000058762

这两部分各种负责的分线判断,其实就是为了实现字节写的功能,这里还是希望自己思考。

在完成了这一部分的时候,我们的工作其实才刚刚完成了一半,甚至是一半都不到,因为后面的Debug阶段才是重点。还有一部分jarl的一点没有展示,但是不重要,属于很简单的部分。

欢迎来到地狱般的Debug阶段

Debug才是这里的重点。

推荐一个网站:https://rvdis.openatom.club/

可以实现汇编与十六进制码的转换。

1
2
3
4
5
lui ra, 305418240
addi ra, ra, 1656
addi sp, zero, 10
sw ra, 10(zero)
lw gp, 10(zero)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
lui ra, 305418240
addi ra, ra, 1656
addi sp, zero, 12
sw ra, 12(zero)
lw gp, 12(zero)
addi tp, zero, 144
addi t0, zero, 171
addi t1, zero, 205
addi t2, zero, 239
sb t2, 12(zero)
sb t1, 13(zero)
sb t0, 14(zero)
sb tp, 15(zero)
lw s0, 12(zero)
lbu s1, 12(zero)
lbu a0, 13(zero)
lbu a1, 14(zero)
lbu a2, 13(zero)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
nop(注,这里nop是一种很不规范的用法,RISCV命令集并不包含nop指令,自行替换成addi x0,x0,0之类的命令)
addi ra, zero, 5
addi sp, zero, 3
add gp, ra, sp
lui tp, 305418240
addi t0, zero, 256
sw gp, 0(t0)
lw t1, 0(t0)
addi t2, zero, 255
sb t2, 4(t0)
lbu s0, 4(t0)
addi s1, zero, 14
jalr a0, 0(s1)
addi a1, zero, 186
addi t6, zero, 1
jal zero, 14

这里的测试集是我自己写的,除了缺少大负数的运算外基本都满足了,至于大负数,你猜猜为什么我前面要设置成sign的扩展器:)

这些测试集是比较精简的,如果能跑通可以试一下大的测试集的测试,这里我就是通过大测试集一步步调试找见我的测试集没有覆盖到大负数的运算的。

差分测试

https://github.com/meiniKi/RV32I_SC_Logisim/tree/main

这是一个搭建好的cpu,可以和他比较自己的指令执行差在哪里了来判断自己的错误,我这边是为了练汇编来一步步看那个大指令集的,如果想轻松点可以用这个来比较。

附加关

对于这个cpu实现图像化

这里关于RGB的手册很有可能内置手册没有,建议访问网站的官方手册,英文版:https://www.baillifard.com/logisim/en/html/libs/index.html。

顺便一提,RGB的中文手册连接是坏的。

方便理解,你也可以将这个RAM理解为一个内存来处理。

如果你查看了手册,那么你应该能明白RGB在logisim里是如何工作的。

相应的,这个的地址也是要除以四的,就像RAM一样。

而且我们不能将内存写入的和RGB写入的混淆,所以要专门画出来一片地址来给RGB,通过比较器和简单的门电路来实现这一段RGB可写或内存可写。

image-20260206002804095

image-20260206002819362

这里我跑了一个程序,是一个一生一芯的logo。

运行中:

image-20260206003054405

成品:

image-20260206003638116

大概跑了四五分钟左右。