搭建自己的 CPU / Chisel 介绍
Chisel 介绍
Chisel 是一个通过 Scala 语言生成 Verilog 代码,然后再通过 FPGA 工具写入板子。 Chisel 是 Scala 的开源项目,而 FPGA 工具 Vivado 与 Quartus 则一般是由 Xilinx 和 Intel 等 FPGA 板卡厂商提供,并非开源。 有一个被称为 F4PGA 的开源项目意图提供开源工具链替代 Vivado 与 Quartus,目前支持的板卡非常有限。
graph LR;
Chisel-->Verilog-->Quartus/Vivado-->FPGABoard;
本文介绍
本文旨在记录自己学习 FPGA 编程的过程,我之前只有软件设计的基础,对 RISC-V 指令集较为了解。 对硬件知识例如数字电路,时序逻辑等则是一无所知。之前了解到有一本基于 Chisel 搭建 CPU 的书籍,可惜只有日文版。 这只是爱好性质的练习,但会尽力在最后做到一个能够 Work 的 RISC-V CPU 原型。
- 主要参考书籍 Digital Design with Chisel
- FPGA 板卡 DE10 Lite
- FPGA 核心 MAX10M50DAF484C7G
- 仓库地址 jwnhy/ddchisel
FPGA 板卡不一定非要是同款,只要有类似器件即可,需要在 Pin Assignment 阶段根据手册重新设置针脚映射。
Chisel 构建系统
Scala 使用 sbt
(Simple Build Tool) 作为其构建系统,一个使用 Chisel 的 sbt
构建文件如下所示。
// build.sbt
scalaVersion := "2.12.13"
scalacOptions ++= Seq(
"-feature",
"-language:reflectiveCalls",
)
// Chisel 3.5
addCompilerPlugin("edu.berkeley.cs" % "chisel3-plugin" % "3.5.4" cross CrossVersion.full)
libraryDependencies += "edu.berkeley.cs" %% "chiseltest" % "0.5.3"
libraryDependencies += "edu.berkeley.cs" %% "chisel3" % "3.5.4"
目录结构
由于 Scala 也是基于 JVM 的语言,继承了 Java 系列复杂的目录结构,通常一个 Chisel 项目的目录结构如下。
project
| Makefile
| build.sbt
|---src
|---main
|---scala
| Top.scala
| Module.scala
|---test
|---scala
| TopTest.scala
| ModuleTest.scala
Hello Chisel
接下来我们来看一个例子。
// Hello.scala
package hello
import chisel3._
class Hello extends Module {
val io = IO(new Bundle {
val andSw = Input(UInt(2.W))
val andLed = Output(UInt(1.W))
val orSw = Input(UInt(2.W))
val orLed = Output(UInt(1.W))
val xorSw = Input(UInt(2.W))
val xorLed = Output(UInt(1.W))
})
io.xorLed := io.xorSw(0) ^ io.xorSw(1)
io.andLed := io.andSw(0) & io.andSw(1)
io.orLed := io.orSw(0) | io.orSw(1)
}
object Hello extends App {
(new chisel3.stage.ChiselStage).emitVerilog(new Hello(), Array("--target-dir", "generated"))
}
val io
代表声明一个常量,相对应的,在 Scala 中,可以用var
声明一个可以反复赋值的变量, 这两者分别对应 Value 与 Variable。IO(new Bundle {})
,IO
代表声明端口,Bundle
与 C 语言中的结构体类似,用于将一组值组合在一起。val andSw = Input(UInt(2.W))
,Input
指明端口类型,UInt
表示该端口处理的为无符号整形,长度为2.W
bits,值得注意的是,这里的UInt
与2.W
均非 Scala 自带的类型,而是用于表示硬件的类型,前者 代表一个可综合的值,后者则是专用于表示宽度的Width
类型,在这里由字面量1
通过.W
后缀转换而来。io.xorLed := io.xorSw(0) ^ io.xorSw(1)
,这里的:=
为重载后的运算符,被 Chisel 定义为线路间的 连接,整个语句代表将io.xorLed
与io.xorSw
的两个位通过与非门连接起来。
创建好目录结构后,我们只需要运行下面的指令即可令 Chisel
生成对应的 Verilog
代码。
sbt run
接下来,在 generated/Hello.v
中,即可看到 Chisel 生成的 Verilog 代码如下,
module Hello(
input clock,
input reset,
input [1:0] io_andSw,
output io_andLed,
input [1:0] io_orSw,
output io_orLed,
input [1:0] io_xorSw,
output io_xorLed
);
assign io_andLed = io_andSw[0] & io_andSw[1]; // @[Hello.scala 14:28]
assign io_orLed = io_orSw[0] | io_orSw[1]; // @[Hello.scala 15:26]
assign io_xorLed = io_xorSw[0] ^ io_xorSw[1]; // @[Hello.scala 13:28]
endmodule
Hello Quartus
创建项目
有了 Verilog 代码之后,下一步就是用 Quartus 将 Verilog 代码烧录进 FPGA 开发板中。
开启 Quartus,按照下列步骤创建新项目(也可以直接使用仓库中的 .qpf
。
File->New->New Quartus Prime Project->...
注意在创建过程中,选择正确的板卡类型 MAX10M50DAF484C7G。
创建项目结束后,在左侧 Project Navigator
下拉框中,选择 Files
,右键点击最顶层的 Files
并选择 Add/Remove Files in Project
。
在将 generated/Hello.v
添加到项目中之后,右键点击刚添加的文件,选择 Set as a Top-Level Entity
,表明其为顶层实体。
针脚分配
值得注意的是,到目前为止,我们的 Chisel & Verilog 程序都只定义了软件层面上的端口, 并没有将硬件针脚和软件端口进行对应,接下来我们就要使用 Quartus Prime 进行针脚分配。
首先输入部分,我们有 io_{and,or,xor}Sw
三个输入,每个输入 2 bits,我们将它们
绑定到板子上的 6 个开关上。
打开 DE-10 Lite 开发板的 User Manual
翻到关于 Push-button、Switches、LEDs
一节,找到对应的名称。
接着我们在最上方选择到 Pin Planner
,分配对应的引脚。
Assigments->Pin Planner
为了避免重复劳动,Quartus 也提供了将引脚分配导出成 .csv
文件的方式,具体大家可以
自己探索下,这里就不多讨论了。
综合与烧写
做完这些后,对 Verilog 代码进行综合(类似于编译),点击蓝色三角形即可,或者
Processing->Start Compliation
最后插入板卡,对 FPGA 进行编程。
Tools->Programmer
烧写结束后,应该就能够在 LED 灯上看到关于开关状态的反馈了。