鬱猫のつぶやき

鬱猫が日々思っていることをつづります

ChiselのハードウェアデザインからVerilogのRTLを生成し、Treadleでシミュレーションを実行する

ものすごくタイトルが長くなってしまった。調べても意外と出てこなかったのでメモしておく。

VerilogのRTLを生成する方法

この記事を参考に以下のように書いてみた。

package GenVerilogTest

import chisel3._

class Top(in0Bits: Int, in1Bits: Int) extends Module {
  val io = IO(new Bundle {
    val in0 = Input(UInt(in0Bits.W))
    val in1 = Input(UInt(in0Bits.W))
    val out = Output(UInt((in0Bits + 1).W))
  })

  io.out := io.in0 + io.in1
}

object Elabolate extends App {
  chisel3.Driver.execute(args, () => new Top(32, 32)) match {
    case ChiselExecutionSuccess(Some(_), emittedStr, Some(firrtlExecutionResult)) => {
      firrtlExecutionResult match {
        case firrtl.FirrtlExecutionSuccess(_, compiledFirrtl) => {
          println(compiledFirrtl) // FirrtlからVerilogに変換した結果が表示される
          println("")
          println(emittedStr) // Firrtlが表示される
        }
        case firrtl.FirrtlExecutionFailure(message) =>
          throw new Exception(s"FirrtlBackend: Compile failed.")
        case _ =>
          throw new Exception("Problem with compilation")
      }
    }
  }
}

上記コードにおいてchisel3.Driver.execute(args, () => new Top(32, 32))においてVerilogとFirrtlの2つを生成している。生成されたファイルはbuild.sbtが置かれているディレクトリと同じ箇所に置かれる。

生成されたファイルは以下の通り。この程度ぐらいであればなんとか読めるレベルではある。

;buildInfoPackage: chisel3, version: 3.1.8, scalaVersion: 2.11.12, sbtVersion: 1.1.1, builtAtString: 2019-07-08 17:44:42.884, builtAtMillis: 1562607882884
circuit Top : 
  module Top : 
    input clock : Clock
    input reset : UInt<1>
    output io : {flip in0 : UInt<32>, flip in1 : UInt<32>, out : UInt<33>}
    
    node _T_11 = add(io.in0, io.in1) @[Top.scala 12:20]
    node _T_12 = tail(_T_11, 1) @[Top.scala 12:20]
    io.out <= _T_12 @[Top.scala 12:10]
module Top( // @[:@3.2]
  input         clock, // @[:@4.4]
  input         reset, // @[:@5.4]
  input  [31:0] io_in0, // @[:@6.4]
  input  [31:0] io_in1, // @[:@6.4]
  output [32:0] io_out // @[:@6.4]
);
  wire [32:0] _T_11; // @[Top.scala 12:20:@8.4]
  wire [31:0] _T_12; // @[Top.scala 12:20:@9.4]
  assign _T_11 = io_in0 + io_in1; // @[Top.scala 12:20:@8.4]
  assign _T_12 = io_in0 + io_in1; // @[Top.scala 12:20:@9.4]
  assign io_out = {{1'd0}, _T_12}; // @[Top.scala 12:10:@10.4]
endmodule

また、FirrtlとVerilogへの変換結果は文字列としても受け取ることができる。

object Simulator {
  // buildした結果のFirrtlとVerilogの2つをタプルとして返す
  def build(args: Array[String]): (String, String)= {
    chisel3.Driver.execute(args, () => new Top(32, 32)) match {
      case ChiselExecutionSuccess(Some(_), firrtlSource, Some(firrtlExecutionResult)) => {
        firrtlExecutionResult match {
          case firrtl.FirrtlExecutionSuccess(_, verilogSource) => {
            (verilogSource, firrtlSource)
          }
          case firrtl.FirrtlExecutionFailure(message) =>
            throw new Exception(s"FirrtlBackend: Compile failed.")
          case _ =>
            throw new Exception("Problem with compilation")
        }
      }
    }
  }
 
  def main(args: Array[String]): Unit = {
    // .. Simulatorの定義
  }
}

上記コードのbuild関数では、戻り値の1つめがVerilogのコード、2つめがFirrtlのコードに対応する。

次に、Firrtlのコードを使ってシミュレーターを起動してみる。Treadleを使うと以下のように書くことができる。

class SimulatorOptionsManager extends TreadleOptionsManager

object Simulator {
  // buildした結果のFirrtlとVerilogの2つをタプルとして返す
  def build(args: Array[String]): (String, String)= {
    // .. 上のコードを参考
  }

  def main(args: Array[String]): Unit = {
    val (_, firrtlSource) = build(args)
    val optionsManager = new SimulatorOptionsManager // simulatorを作成する際のオプションを指定できるみたいなのだが、正直良くわかっていない

    optionsManager.setTargetDirName("simulator_run_dir")

    val simulator = TreadleTester(firrtlSource, optionsManager) // 生成されたFirrtl自体を渡す

    val maxCycles = 100
    var cycles = 0
    println("Running...")

    simulator.poke("io_in0", 1) // io.in0 ではなく、 io_in0 のようにアンダースコアでつないで表現する必要があるので注意
    simulator.poke("io_in1", 2)
    while (simulator.peek("io_out") != 12 && cycles < maxCycles) {
      simulator.step(1)
      cycles += 1
      simulator.poke("io_in0", 10)
      simulator.poke("io_in1", 2)
      println(s"Simulated $cycles cycles")
      val out = simulator.peek("io_out")
      println(s"output is $out")
    }
  }
}

以下のような出力が出てくるはず。

[info] Compiling 1 Scala source to /mnt/src/GenVerilogTest/target/scala-2.11/classes ...
[warn] there were three feature warnings; re-run with -feature for details
[warn] one warning found
[info] Done compiling.
[info] Packaging /mnt/src/GenVerilogTest/target/scala-2.11/chisel-module-template_2.11-3.1.1.jar ...
[info] Done packaging.
[info] Running GenVerilogTest.Sumulator 
[info] [0.001] Elaborating design...
[info] [0.689] Done elaborating.
Total FIRRTL Compile Time: 240.4 ms
Total FIRRTL Compile Time: 12.9 ms
file loaded in 0.051272257 seconds, 7 symbols, 3 statements
Running...
Simulated 1 cycles
output is 12
[success] Total time: 3 s, completed 2019/08/03 15:52:56
[IJ]sbt:chisel-module-template>