Archive:

搭建自己的 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 原型。

  1. 主要参考书籍 Digital Design with Chisel
  2. FPGA 板卡 DE10 Lite
  3. FPGA 核心 MAX10M50DAF484C7G
  4. 仓库地址 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,值得注意的是,这里的 UInt2.W 均非 Scala 自带的类型,而是用于表示硬件的类型,前者 代表一个可综合的值,后者则是专用于表示宽度的 Width 类型,在这里由字面量 1 通过 .W 后缀转换而来。
  • io.xorLed := io.xorSw(0) ^ io.xorSw(1),这里的 := 为重载后的运算符,被 Chisel 定义为线路间的 连接,整个语句代表将 io.xorLedio.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 ProjectAdd File in Quartus 在将 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 灯上看到关于开关状态的反馈了。