Skip to main content

支持RV32E的单周期NPC

一个多月没有写verilog了,数电知识算是忘光了,难受,这个时候就体现了写博客记录的好处了hhh,于是我打算复习一下顺便做个笔记

粗略复习数字电路

复习主要跟着余老师的视频和ddca

1.晶体管

nMOS的特性是:当VG-VS较大(一般VG为1,S接地为0), 则开关合上,S→D

pMOS的特性是:当VG-VS较小(一般VG为0,S接高电压1),则开关合上,S→D

非门(Y = ~A):

与非门(Y = ~(A&B))

或非门(Y =~(A|B)):

异或门(Y = A ^B):

2.组合逻辑

译码器

编码器

优先编码器

多路选择器(MUX)

解释,假如我需要第4个分支,首先decode成独热码,第四个为1,所有加上and门,只有第四个and门的输出是输入,其他的都是0,再通过或门,结果就是第四个的结果

比较器

同或门(一位的比较器)加与门

加法器

半加器只是A+B=cout(产生cin);全加器是A+B+cin=cout(产生cin)

多为加法器的RCA实现比较简单但是慢,我记得有一种预测进位的方法,可以提高速度,详情见ddca第五章,之后看了一下,叫做先行进位加法器

3.时序逻辑

锁存器的进化路

交叉配对反相器

特性:可以存贮一个bit的状态
缺点:无法改变其中的值

SR锁存器

特性:S(Set)为1,置1;
R(Reset)为1,置0;
缺点:存在一个禁止的设置,即SR都为1

D锁存器

特性:写使能WE开启时,写入D(data)的值。关闭时,数据保存不变
缺点:无法在posedge clk时写入数据,不能很好配合时序逻辑

D触发器(DFF)

图中的D触发器使用三个SR锁存器搭建的,还可以用两个D锁存器搭建,不过最重要的还是理解D触发器的原理:在时钟上升沿将数据D传过去,其他时候保持Q的数据不变,也可以形象理解,时钟上升沿它是透明的,其他时候它是阻塞的,保存上一次的数据

寄存器

学习chisel

为什么我从verilog转至学习chisel

最主要的原因在于,我即将要写支持RV32E的NPC了,如果写过nemu的人都知道,让nemu支持多条指令需要人为的添加指令译码和指令行为,这是一个费时费力并且容易出错的工程问题,好在在nemu中,余老师用了c语言的一些好用的语言特性创造出了一个框架,叫做模式匹配(我现在都有点没看懂hhh),这个框架使得加指令的行为变得特别简单,根本原因在于余老师写了一份易维护易扩展的代码,现在面对没有框架白手起家的NPC时,在遇到越来越复杂的情况,当代码数量越来越多,模块越来越多,我也应该写一份易维护易扩展的代码,如何写呢?

可以看到下面两条,想要编写可复用的代码,就需要语言本身提供相应的语言特性,可是verilog是一个连电路的语言,灵活性不够,没有好的语言特性,想一想,当芯片到了上亿个门的程度,用verilog搭建就需要一是做很多相同的工作,二是写出的代码不是一个好代码(难道你指望一个assgin有超长或者说有几百个case组成的译码器能不言自明或者不言自证吗),当然对于初学者来说,verilog这种连电路的思想本身是很好的,但是规模扩大之后,就应该选择合适的工具来代替我们连电路,而让我们则关注于更高层的事情上面,所以就有了这样一个工具——chesel语言。

不要陷入的误区:可以看出chisel是一个生成器,从算法层面描述然后转化为verilog的门级描述,也就是让语言帮助你连线,但千万要记住的是你在设计的是电路,而不是软件,你仅仅是在使用chisel给你提供的好用的语言特性,so,在用chisel写下每一个代码时,都必须要先画出架构图!才不会陷入误区。

至于chisel的学习请移步至https://mybinder.org/v2/gh/freechipsproject/chisel-bootcamp/master,我将略微记录一下chisel的语法

chisel基本语法(学之前得要会scala)

1,模块

语法说明,首先定义一个类继承于内置的Module类,并且还有参数,用类来写一个模块很有意思,我们都知道类是一个蓝图,而实例化则是将一个类通过传入不同参数来获得不同的模块,所以,chisel中写一个模块实际上是在写一个模块生成器,用的是scala得语法,这其实verilog中也有参数化的想法,所以也没什么

相关input,output参数是IO的实例化,名字叫io(好像必须得叫这名字),我觉得这样写好丑阿巴阿巴

2,Testbench

和verilog一样,chisel也有相应的testbench

放弃chisel

放弃了,我觉得chisel我可能驾驭不了,之后再说吧,先用verilog写一个简单的流水线之后再考虑他吧

从命令行中读入NPC需要执行的程序

这个和nemu一样即可,直接照搬hhh

实现几条简单指令

为了运行dummy程序,我需要实现auipc,lui,jal,jalr四条指令

auipc

对于我架构只需要修改 1,IMM模块增加对U type的支持;2,增加一个alua的mux和相应的信号;3,IDU支持auipc

lui

将imm的值直接写入GPR即可,需要改动的地方 1,添加一个WD3的mux和相应信号;2,IDU支持lui

(写的时候出了点bug,只能说verilog确实不好整,我在这里记录一下,加一个信号需要改1,idu相关;2,top加一个wire;)

jal

1,加一个j type立即数编码;2,给wd加一个nextpc的选项;3,加一个pcsrc的mux和相应信号

jalr

1,在alu后面添加一个与非门,进行地址地位清零操作

可运行dummy.c的cpu架构如上

搭建基础设施

不得不说自己写这么大一个工程最大的难题莫过于依赖问题了,哪怕是copy- paste也不是那么容易的事情啊

Diff Test

上面的包括sdb,trace等基础设施如果说是copy-paste就能解决的话,diff test由于需要让nemu作为ref,我需要自己写nemu相关的ref api,我将我的任务分步:1,在nemu中实现这三个API,并且让npc成功链接到产生的so文件 2,在npc中通过调用API实现dut相关。(说实话,让自己写的nemu和我自己写的npc交互真是太酷啦)

改进Makefile

我根据中科大的实验来改进我的makefile,他的mk文件写的正好是我想达到的目的,我就参考了一下CECS-Lab/simulator/script at main · USTC-System-Courses/CECS-Lab (github.com),不过我粗略浏览了一下他添加基础设施的部分,个人认为没有我写的优雅(我基本是按照nemu写的),他写的好杂好乱的感觉,makefile的改进主要体现在美观,这个我是看的中科大的makefile,第二是不能存在平台依赖,要写出一个通用的,一般是每一个工具都会有他的命令来告诉你他安装在哪里,这样就可以摆脱依赖问题,并且我还学到了哪些flag究竟是啥意思

CXXFLAGS和CFLAGS主要是给c/c++编译器看的(后者就只有c),CPPFLAGS是给c/c++预编译器看的(一般放-D -I),即C PreProcess,然后LDFLAGS是给ld看的,一般是放-L(指定搜索库的路径)之类的,LIBS一般放-l(指定要链接的库)

实现RV32E指令集

sw

这是一条访存指令,我打算先将他实现了,这里我很疑惑讲义里面的代码,为嘛内存写是组合逻辑,不懂

bne

这个指令让我把我的架构改了比较多的地方

结束

异常处理机制

又有近一个月没有写verilog了,又要忘了hhhh

我打算首先实现对csr寄存器的读写操作,最后实现异常机制

CSR相关指令

我看在nemu中我只实现了两条这个指令,那我也只实现两条好了

csrrw

感觉在rtl中实现指令真的是开头特别难,然后越来越简单,这个架构应该可以实现csrrw,首先clk=0的时候根据指令的31到20取出相关csr的值丢到RD1处,RD1连接GPR的wdmux准备写入,与此同时,读出RD1(GPR)的值练到CSR的wd上面,之后在posedge时GPR和CSR同时写入。控制信号需要两个写使能都打开,并且wdmux选出csr。

之后的csrrs和csrrw差不多,不记录了

最难的部分来了,硬件需要提供的异常处理机制,现在只需要实现ecall这一个特殊的异常,但是异常处理机制都是通用的,只有异常号不一样(这就是RISC架构的简洁之处了),并且ysyx简化实现,只需要实现M mode的ecall异常,也就是异常号是11的异常

我打算画两根线出来,一根线指示是否发生了异常,一根线传递异常号,现在这两根线暂时都只从我的IDU中生成,所以很类似control signal,只是这个control signal的优先级必须比其他的更高,这就是我现在的思路(我闭门造车,完全不知道其他架构是怎么实现的,因为我想少参考别人的,最后如果有流片机会才更有成就感)

说干就干

粗略架构图,irq连接到的所有mux都要以irq为准,irq和irqnumber直接连给csr,由csr内部进行mcause设置

最后一步就是mret指令,这个指令就是将nextpc设置为mepc

麻了,我启动rtt和yield-os时debug了好久,一直无法切换上下文,但是mepc的值却正确设置了,但是看波形,mepc却只是加了4,特别诡异,这里debug花了我超级长的时间,一个下午,我除了实现一个mret就是在debug这个问题,最后发现忘记在trap.S加mv指令了,服气🤬🤬

由于没有实现某些设备,导致无法运行nanos-lite

完结撒花