Open5
RISC-V CPU自作本を読む
自作CPUがやりたくなったので、「RISC-VとChiselで学ぶ はじめてのCPU自作」をやっている
とりあえず、「第Ⅲ部 パイプラインの実装」まで一通り実装しつつ読んだ
理解が不十分なままの部分もあるので、メモしつつ振り返る
Scalaの文法
var
とval
object
)
シングルトンオブジェクト (- Javaの場合、クラス自体に属する(インスタンスを生成せずに呼び出せる)メンバ(staticフィールドやstaticメソッド)を定義可能
- Scalaでは、staticフィールドやstaticメソッドの定義はできない
- その代わりに、同一名前空間に1つだけ同名のオブジェクトを定義でき、それをシングルトンオブジェクトという
- シングルトンオブジェクト固有のフィールドやメソッドを定義できる
Seq
とかList
とかの関係がよく分からん
- これ
scala.collection.immutable
の全コレクション (Scalaの公式ドキュメントより引用) - 単に
Seq
と書いた場合、immutableのSeq
のサブクラスであるList
として宣言される - なお、mutableなコレクションもある(
scala.collection.mutable
)
trait
- 複数のクラス間でメンバを共有するために使う
- クラスからコンストラクタを定義する機能を抜いたようなもの
- 単体ではインスタンス化できない
Chiselの文法
UInt
/SInt
オブジェクト
val a = UInt(32.W) // 符号なし整数型"信号"
val b = SInt(32.W) // 符号あり整数型"信号"
-
UInt()
という記法は、UInt.apply()
と等価- (Scalaにおいて
apply
という名前のメソッドは省略可能)
- (Scalaにおいて
-
自然数.W
はbit幅を表すWidth
型を返すメソッド
// 定数のUInt変換
val a = 2.U(32.W) // ScalaのInt型定数をChiselのUInt型に変換するメソッド
// 変数のUInt変換
val b = 2
val c = b.asUInt(32.W) // ScalaのInt型変数をChiselのUInt型に変換するメソッド
- Scalaの
Int
→ ChiselのUInt
に変換するメソッド-
.U
定数に対し用いるのが推奨されている -
.asUInt
変数に対し用いるのが推奨されている
-
Module
// 回路を定義するSampleクラスの宣言
class Sample extends Module {...}
// Scala上でインスタンス化
val instance = new Sample()
// Chiselのハードウェア化 (Moduleオブジェクトに対しapplyメソッドを適用)
val hardware1 = Module.apply(instance)
val hardware2 = Moduke(instance) // 上記の省略形
clock
とreset
-
Module
を継承したクラスであるIO
クラスでは、暗黙的にclock
とreset
信号が定義されている - レジスタに対して自動的に接続されている
- 立ち上がりエッジトリガで動作
Wire
/WireDefault
- 組み合わせ回路を記述するのに使用
- 接続先が未定の状態で、Chiselのハードウェアを確保したい場合に使用
val a = Wire(UInt(32.W)) // 32bit幅の配線
val b = WireDefault(0.U(32.W)) // 0.Uがはじめから接続されている32bit幅の配線
...
a := ... // aの接続先が確定
RegInit
- 順序回路(レジスタ)を記述するのに使用
- 引数で初期値を設定
val reg = RegInit(0.U(32.W))
reg := 1.U(32.W) // 次クロックの立ち上がりエッジでレジスタの値が1になる
reg : = reg + 1.U(32.W) // クロックの立ち上がりごとにregがインクリメント
Mem
オブジェクト
- レジスタファイルを定義するために使用
- レジスタの配列
val regfile = Mem(32, UInt(32.W)) // 32bit幅のレジスタを32本定義
// 1番レジスタのデータを読み出し
val read_data = regfile(1.U)
// 1番レジスタにデータを書き込み
regfile(1.U) := <書き込みデータ>