FireSimについてある程度動かしたので、今度はカスタマイズをしてみたい。
というわけで、FireSimの実際のデザイン部分であるFireChipを使ってみることにした。
オリジナルのデザインを追加してみることにした。
メモリマップのレジスタを作る
参考にしたのは以下のチュートリアル。
Memory-mapped Registers — FireSim documentation
ここで作るのは、外部からの情報を取ってきてメモリに書き込むデバイスだ(まどろっこしい書き方をしたが、つまりDMAだ)。 メモリマップされたレジスタを持っており、CPUはこのレジスタを使ってデバイスをコントロールすることができる。
まずはTLRegisterNode
オブジェクトを作って、RTLのレジスタを作成する。
src/main/scala/example/InputStream.scala
class InputStream( address: BigInt, val beatBytes: Int = 8) (implicit p: Parameters) extends LazyModule { val device = new SimpleDevice("input-stream", Seq("example,input-stream")) val regnode = TLRegisterNode( address = Seq(AddressSet(address, 0x3f)), device = device, beatBytes = beatBytes) lazy val module = new InputStreamModuleImp(this) }
ここでTLRegisterNode
に対してパラメータを設定しているのだが、
- address
: レジスタのアドレスとアドレスマスク。ここでは0x3fをマスクとして設定することで、64バイトのレジスタを宣言している。
- device
: SimpleDevice型として作られたdevice
というオブジェクトを接続しているのだが、これはLinuxのカーネルドライバで使用するためのもの。ここでは無視。
- beatBytes
: TileLinkで規定されているデータ幅。この長さのレジスタが作られる。
ここではDMAを作るので、以下のレジスタがあれば良さそうだ。
addr
: アドレスlen
: 転送長running
: デバイスが実行中かどうかcomplete
: 転送が完了したか
実際のモジュールとしては、以下のようなInputStreamModuleImp
を作成した。
src/main/scala/example/InputStream.scala
class InputStreamModuleImp(outer: InputStream) extends LazyModuleImp(outer) { val addrBits = 64 val w = 64 val io = IO(new Bundle { // Not used yet val in = Flipped(Decoupled(UInt(w.W))) } val addr = Reg(UInt(addrBits.W)) val len = Reg(UInt(addrBits.W)) val running = RegInit(false.B) val complete = RegInit(false.B) outer.regnode.regmap( 0x00 -> Seq(RegField(addrBits, addr)), 0x08 -> Seq(RegField(addrBits, len)), 0x10 -> Seq(RegField(1, running)), 0x18 -> Seq(RegField(1, complete))) }
TileLinkのポートを作る
参考にしたのは以下。
DMA and Interrupts — FireSim documentation
つくるのはDMAなので、まずはTileLinkのポートを作らないと始まらない。これは先ほどのInputStream
に追加する。
TLClientNode
をモジュールとして追加した。
src/main/scala/example/InputStream.scala
class InputStream( address: BigInt, val beatBytes: Int = 8, val maxInflight: Int = 4) (implicit p: Parameters) extends LazyModule { val device = new SimpleDevice("input-stream", Seq("example,input-stream")) val regnode = TLRegisterNode( address = Seq(AddressSet(address, 0x3f)), device = device, beatBytes = beatBytes) val dmanode = TLClientNode(Seq(TLClientPortParameters( Seq(TLClientParameters( name = "input-stream", sourceId = IdRange(0, maxInflight)))))) lazy val module = new InputStreamModuleImp(this) }
TLClientNode
のパラメータとしてはとりあえず何となくわかりそうなものだが、ソースIDはTileLinkの識別IDを設定している。
これはどんな自由なモジュールでもこの調子で名前を付けていいのかしら?
次にDMAのステートマシンを作る。これも上記のチュートリアルの写経だが...
src/main/scala/example/InputStream.scala
class InputStreamModuleImp(outer: InputStream) extends LazyModuleImp(outer) { val (tl, edge) = outer.dmanode.out(0) val addrBits = edge.bundle.addressBits val w = edge.bundle.dataBits val beatBytes = (w / 8) val io = IO(new Bundle { val in = Flipped(Decoupled(UInt(w.W))) }) val addr = Reg(UInt(addrBits.W)) val len = Reg(UInt(addrBits.W)) val running = RegInit(false.B) ...
やっていることとしては、IO
ポートから入ってくるデータを、tlポート(TileLinkポート)に出力している。
以下のコードが肝になるらしい?TileLinkのAポートでValidを出している。
src/main/scala/example/InputStream.scala
... io.in.ready := canIssue && tl.a.ready tl.a.valid := canIssue && io.in.valid tl.a.bits := edge.Put( fromSource = OHToUInt(xactOnehot), toAddress = addr, lgSize = log2Ceil(beatBytes).U, data = io.in.bits)._2 tl.d.ready := running && xactBusy.orR ...
SoCへの組み込み
次に、SoC環境へのDMAコントローラの追加を行う。
参考にしたのは以下のチュートリアル。
Connecting Devices to Bus — FireSim documentation
以下のようなtraitを作る。BaseSubsystemに対してデバイスを追加する。アドレスは 0x10017000 だ。
BaseSubsystemはシステムバスのsbus
とペリフェラルバスのpbus
、さらに割り込み用のバスとしてibus
を持っている。
pbus
をレジスタノードに接続し、sbus
をDMAノード、割り込みノードをibus
に追加する。
src/main/scala/example/InputStream.scala
trait HasPeripheryInputStream { this: BaseSubsystem => private val portName = "input-stream" val streamWidth = pbus.beatBytes * 8 val inputstream = LazyModule(new InputStream(0x10017000, pbus.beatBytes)) pbus.toVariableWidthSlave(Some(portName)) { inputstream.regnode } sbus.fromPort(Some(portName))() := inputstream.dmanode ibus.fromSync := inputstream.intnode } trait HasPeripheryInputStreamModuleImp extends LazyModuleImp { val outer: HasPeripheryInputStream val stream_in = IO(Flipped(Decoupled(UInt(outer.streamWidth.W)))) outer.inputstream.module.io.in <> stream_in def connectFixedInput(data: Seq[BigInt]) { val fixed = Module(new FixedInputStream(data, outer.streamWidth)) stream_in <> fixed.io.out } }
そしてTopへの接続だ。さらにシミュレーション環境を接続して完了となる。
src/main/scala/example/Top.scala
class ExampleTopWithInputStream(implicit p: Parameters) extends ExampleTop with HasPeripheryInputStream { override lazy val module = new ExampleTopWithInputStreamModule(this) } class ExampleTopWithInputStreamModule(outer: ExampleTopWithInputStream) extends ExampleTopModuleImp(outer) with HasPeripheryInputStreamModuleImp
src/main/scala/example/Configs.scala
class WithFixedInputStream extends Config((site, here, up) => { case BuildTop => (clock: Clock, reset: Bool, p: Parameters) => { val top = Module(LazyModule(new ExampleTopWithInputStream()(p)).module) top.connectFixedInput(Seq( BigInt("1002abcd", 16), BigInt("34510204", 16), BigInt("10329999", 16), BigInt("92101222", 16))) top } }) class FixedInputStreamConfig extends Config( new WithFixedInputStream ++ new BaseExampleConfig)
最後にVerilogを出力してみたのだが、FixedInputStream
がないと怒られる。たしかに、資料を探してもFixedInputStream
が見当たらない。自分で作らないといけないのかなあ?
$ make CONFIG=FixedInputStreamConfig mkdir -p /home/msyksphinz/work/firechip/verisim/generated-src cd /home/msyksphinz/work/firechip && java -Xmx2G -Xss8M -XX:MaxPermSize=256M -jar /home/msyksphinz/work/firechip/rocket-chip/sbt-launch.jar "runMain example.Generator /home/msyksphinz/work/firechip/verisim/generated-src example TestHarness example FixedInputStreamConfig" OpenJDK 64-Bit Server VM warning: ignoring option MaxPermSize=256M; support was removed in 8.0 [info] Loading project definition from /home/msyksphinz/work/firechip/project [info] Loading settings from build.sbt ... [info] Loading settings from build.sbt ... [info] Loading settings from build.sbt ... [info] Loading settings from build.sbt ... [info] Loading settings from plugins.sbt ... [info] Loading project definition from /home/msyksphinz/work/firechip/rocket-chip/project [info] Loading settings from build.sbt ... [info] Loading settings from build.sbt ... [info] Loading settings from build.sbt ... Using addons: [info] Set current project to example (in build file:/home/msyksphinz/work/firechip/) [warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list [info] Compiling 3 Scala sources to /home/msyksphinz/work/firechip/target/scala-2.11/classes ... [error] /home/msyksphinz/work/firechip/src/main/scala/example/InputStream.scala:30:28: not found: type FixedInputStream [error] val fixed = Module(new FixedInputStream(data, outer.streamWidth)) [error] ^ [error] one error found [error] (Compile / compileIncremental) Compilation failed [error] Total time: 6 s, completed Jun 26, 2018 11:18:11 PM /home/msyksphinz/work/firechip/Makefrag:28: recipe for target '/home/msyksphinz/work/firechip/verisim/generated-src/example.TestHarness.FixedInputStreamConfig.fir' failed make: *** [/home/msyksphinz/work/firechip/verisim/generated-src/example.TestHarness.FixedInputStreamConfig.fir] Error 1
2018/06/28追加。MLで聞いてみるとFixedInputStream
の実装を教えてくれた。以下を追加した。
src/main/scala/example/InputStream.scala
class FixedInputStream(data: Seq[BigInt], w: Int) extends Module { val io = IO(new Bundle { val out = Decoupled(UInt(w.W)) }) val s_start :: s_send :: s_done :: Nil = Enum(3) val state = RegInit(s_start) val dataVec = VecInit(data.map(_.U(w.W))) val (sendIdx, sendDone) = Counter(io.out.fire(), data.size) io.out.valid := (state === s_send) io.out.bits := dataVec(sendIdx) when (state === s_start) { state := s_send } when (sendDone) { state := s_done } }
コンパイルが完了すると、チュートリアルの通りにテストプログラムを作成し、コンパイルする。 実行すると、以下のようになった。正しく動いている。
$ ./simulator-example-FixedInputStreamConfig ../tests/input-stream.riscv 000000001002abcd 0000000034510204 0000000010329999 0000000092101222
何だこの出力は?と思うかもしれないが、これは最初にChiselでFixした入力信号なのでそれが出力されているだけだ。
src/main/scala/example/Config.scala
class WithFixedInputStream extends Config((site, here, up) => { case BuildTop => (clock: Clock, reset: Bool, p: Parameters) => { val top = Module(LazyModule(new ExampleTopWithInputStream()(p)).module) top.connectFixedInput(Seq( BigInt("1002abcd", 16), BigInt("34510204", 16), BigInt("10329999", 16), BigInt("92101222", 16))) top } })
これだけでは単純にテストベクタが固まっているので、もっとカスタマイズして実行したい。