CPU写到这里,流水线已经基本可以跑起来了,但是目前在写的过程中也渐渐感觉到一些问题,具体是:
- 流水线缓冲级的写法过于繁琐,虽然之前稍微了修改了一下,使显式连接没那么多,但问题还在
- 增加指令较为麻烦,如果说是增加普通的I、R型指令,因此之前已经写好模式,直接增加即可。但在增加一些指令,比如MULT,MULTU,就发现了问题。因为这两个指令并不是向寄存器组的寄存器中写入,而是向寄存器HI,LO写入。那怎么办呢,修改前面的范式,让他除了能像寄存器组的寄存器写入,还能写入HI,LO吗?可想而知,最后那个范式会越来越复杂。
重构计划:
- 目前流水线是由Component的形式组装起来的,但实际上,在spinalHDL,它建议不必要的时候,采用Area而不是另外建立Component,因为建立Component就意味着要重复定义输入输出端口。之前我虽然采用将端口抽象成类的方式,但仍有些累赘。
- 为什么一开始要采用Component呢,因为这是用Verilog写CPU的正常方法。以Verilog的表现力,如果将所有的东西挤在一个Module中,可想而知最后只能变成一坨屎山。但是
- 因此,现在考虑使用Area来重构流水线
- 指令系统参考了VexRiscv的方法,打算用另外一个思路来写。
- 以前的思路是按照流水线为主的思路,在流水线的每一级,针对不同的指令做不同的操作。
- VexRiscv的思路是以指令为主,每个指令在每个阶段做什么事,直接Plug到该阶段。当然了,VexRiscv的实现比较复杂,我的scala功底还不够能完全理解他的实现方式,只能说先借鉴一下思想。
2020年7月5日 update:
重构计划1:使用Area来重构流水线 失败
- 丢弃的分支在此:https://github.com/Ncerzzk/SimpleCPU/tree/AreaInsteadOfModule
- 具体原因挺多的
- 使用Area来写整个流水线的话,生成的Verilog非常难以阅读。如果是用Component 来写的话,至少各个模块还能分开,但如果是用Area来写的话,整个CPU所有的东西都挤在一起,如果想通过verilog来看某些语句的效果的话,简直是噩梦。
- 流水线之间只能通过一些中间信号的来连接,VexRiscv中使用了一些自建的数据结构来维护,如input(signal) output(signal) insert(signal),等等。我也照猫画虎自己实现了一套,但是由于scala水平不够,写得略显臃肿,完全没有VexRiscv中那种轻便的感觉。越写越恶心
- 为了更高程度的抽象,经常迷失在scala的语法中。虽然随着重构指令系统中,scala的水平又长进了一些,但即使这样,还是不太能驾驭高程度的抽象写法。
重构计划2:重构指令系统
本来想按照VexRiscv,直接将指令的行为plug到流水线中。但由于Area的重构搁浅,因此这方面没有实现。
目前的重构完的指令系统,使用SpinalHDL内置的MaskedLiteral来进行指令的匹配。
指令定义
def
MaskedLiteral可直接调用===与Bits类型进行匹配。
指令译码行为的抽象
将指令译码的行为抽象出来,译码阶段的行为其实不多,抽象完变成这些:
object
有些行为的参数直接使用Bool类型的True或者False,有些行为的参数是特定的:
object
然后将某一类的指令的共有行为抽出来,作为一个普通的Actions列表,如I型指令的共有行为:
def
当然了,每个指令还有一些独特的东西,比如OP码,和OPsel码,这些每个指令再单独添加:
def
J型指令由于行为差别都比较大,对每个指令添加的东西比较多:
def
然后在ID模块中,遍历匹配指令,匹配到之后,根据指令的行为列表,操作具体端口:
def
这样一来,译码模块就显得清爽很多了:
val
到目前为止的源码:
https://github.com/Ncerzzk/SimpleCPU/tree/c8142ba1f5ed55081fd439eaad77b876317bfa05github.com到目前为止的源码