FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages

FIRRTLに入門する (5. ANTLR4 用のFIRRTL.g4を使ってC++の簡易パーサを作ってみる)

https://raw.githubusercontent.com/freechipsproject/firrtl/master/doc/images/firrtl_logo.svg?sanitize=true

FIRRTLのパーサがANTLRで作られていることが分かったので、これを使えば遊べるような気がしてきた。C++向けにFIRRTLの文法ファイルを変換して、C++で簡易パーサを作って遊んでみる。

まず、ANTLR4のランタイムをダウンロードした。C++用のANTLRランタイムをダウンロードしてコンパイルしておく。s

https://www.antlr.org/download.html

mkdir runtime
cd runtime
wget https://www.antlr.org/download/antlr4-cpp-runtime-4.7.2-source.zip
unzip antlr4-cpp-runtime-4.7.2-source.zip
cd runtime
cmake .
make -j8

FIRRTLのFIRRTL.g4の配置してあるディレクトリに移動し、ANTRLでC++用のクラスファイルを生成した。

antlr4 -Dlanguage=Cpp FIRRTL.g4

生成したFIRRTLParser.cpp, FIRRTLLexer.cppを呼び出すためのラッパーであるmain.cppを作っておく。

  • main.cpp
#include <iostream>

#include "antlr4-runtime.h"
#include "FIRRTLLexer.h"
#include "FIRRTLParser.h"
#include "FIRRTLBaseListener.h"

using namespace antlr4;

int main(int argc, const char* argv[]) {
  std::ifstream stream;
  stream.open(argv[1]);
  ANTLRInputStream input(stream);
  FIRRTLLexer lexer(&input);
  CommonTokenStream tokens(&lexer);
  FIRRTLParser parser(&tokens);

  FIRRTLParser::CircuitContext* tree = parser.circuit();

  std::cout << tree->toStringTree(&parser);
  return 0;
}

ランタイムライブラリと、main.cppと、FIRRTLParser.cpp, FIRRTLLexer.cppをまとめてコンパイルして実行ファイルを作成する。

g++ -o firrtl_cpp *.cpp -I./runtime/runtime/src runtime/dist/libantlr4-runtime.a

完成したので、以下の簡単なFIRファイルを読ませて解析させる。

  • simple_circuit.fir
circuit SimpleCircuit :
  module SimpleCircuit :
    output io : {flip in : UInt<32>, out : UInt<32>}

    io.out <= io.in
./firrtl_cpp simple_test.fir
line 1:23 mismatched input '\n  ' expecting {FileInfo, INDENT}
(circuit circuit (id SimpleCircuit) : \n   module SimpleCircuit : \n     output io : { flip in : UInt < 32 > out : UInt < 32 > } \n \n     io . out <= io . in \n)

何となく構文解析した結果がダンプできているような気がする。でも、最初のmismatched inputが良く分からないし、もう少し改造して綺麗に表示できないものかね。とりあえずFIRファイルを構文解析できたので良しとする。

FIRRTLに入門する (4. FIRRTLの.firファイルの読み取り方法)

https://raw.githubusercontent.com/freechipsproject/firrtl/master/doc/images/firrtl_logo.svg?sanitize=true

FIRRTLは入力ファイルとして.firファイルを指定することができる。ということは、FIRファイルのファイルを読み込んで、Parseして、構文解析木を生成する場所があるはずである。それを調査した。

どうやらFIRRTLのコマンドラインを立ち上げたときは、FirrtlCli.scalaFirrtlCliトレイトが含まれるようで、こちらにFIRファイルをオープンする記述が含まれているようだ。

  • firrtl/stage/FirrtlCli.scala
trait FirrtlCli { this: Shell =>
  parser.note("FIRRTL Compiler Options")
  Seq( FirrtlFileAnnotation,
       OutputFileAnnotation,
       InfoModeAnnotation,
       FirrtlSourceAnnotation,  // FIRファイルを読み込むためのアノテーション
       CompilerAnnotation,
       RunFirrtlTransformAnnotation,
       firrtl.EmitCircuitAnnotation,
       firrtl.EmitAllModulesAnnotation )
    .map(_.addOptions(parser))

  phases.DriverCompatibility.TopNameAnnotation.addOptions(parser)
  phases.DriverCompatibility.EmitOneFilePerModuleAnnotation.addOptions(parser)
}

FirrtlSourceAnnotationを開いてみると、ファイルをオープンして、ANTLR構文解析木に渡すようだった。

  • firrtl/stage/FirrtlAnnotations.scala
object FirrtlSourceAnnotation extends HasShellOptions {

  val options = Seq(
    new ShellOption[String](
      longOption      = "firrtl-source",
      toAnnotationSeq = a => Seq(FirrtlSourceAnnotation(a)),
      helpText        = "An input FIRRTL circuit string",
      shortOption     = Some("<string>") ) )

}
/** Holds a [[scala.Predef.String String]] containing FIRRTL source to read as input
  *  - set with `--firrtl-source`
  * @param value FIRRTL source as a [[scala.Predef.String String]]
  */
case class FirrtlSourceAnnotation(source: String) extends NoTargetAnnotation with CircuitOption {

  def toCircuit(info: Parser.InfoMode): FirrtlCircuitAnnotation =
    FirrtlCircuitAnnotation(Parser.parseString(source, info))

}
  • firrtl/Parser.scala
  /** Parses a org.antlr.v4.runtime.CharStream and returns a parsed [[firrtl.ir.Circuit Circuit]] */
  def parseCharStream(charStream: CharStream, infoMode: InfoMode): Circuit = {
    val (parseTimeMillis, cst) = time {
      val parser = {
        val lexer = new FIRRTLLexer(charStream)
        val parser = new FIRRTLParser(new CommonTokenStream(lexer))
        parser
      }

オープンソースRISC-VコアHummingBirdについて調査(7. FPGAでのビルドを試行する)

RISC-Vの実装であるHummingBirdを調査していくことにした。

https://camo.githubusercontent.com/f393ca23a6df9f640c1e4a43b78f2a503fdc47e8/68747470733a2f2f66647661643032316173666438712e6f73732d636e2d68616e677a686f752e616c6979756e63732e636f6d2f4c696368656554616e672f626f6f6b7069632e6a7067

HummingBirdがRTLシミュレーションで何となく動作するようになってきたので、FPGAでどれくらいの回路面積になるのか見てみたいと思う。HummingBirdの合成環境はXilinxFPGAボードをサポートしており、

  • artydevkit
  • hbirdkit
  • nucleikit

というコンフィグレーションがサポートされていた。それぞれ、使用するFPGA

  • artydevkit : xc7a35ticsg324-1L
  • hbirdkit : xc7a75tfgg484-2
  • nucleikit : xc7a100tfgg484-2

が指定されていた。ArtyKitを使う関係もあり、下記のDigilentのサイトからボードファイルをダウンロードして、Vivadoのインストールディレクトリに配置しておいた。

reference.digilentinc.com

まずはArtyDevKitで試してみた。fpgaディレクトリで、以下のように入力してVivadoを起動する。

make install CORE=e203 FPGA_NAME=artydevkit
make mcs CORE=e203 FPGA_NAME=artydevkit

しかし、合成の途中でRAMの数が足りないというエラーメッセージが出て落ちてしまった。えええ、そんなことある?

---------------------------------------------------------------------------------
Finished Cross Boundary and Area Optimization : Time (s): cpu = 00:05:01 ; elapsed = 00:05:46 . Memory (MB): peak = 0.000 ; gain = 0.000 ; free physical = 6608 ; free virtual = 55889
---------------------------------------------------------------------------------
ERROR: [Synth 8-5834] Design needs 1056 RAMB18 which is more than device capacity of 100

仕方がないのでhbirdkitでの合成を試行した。同様に以下のようにしてVivadoを立ち上げる。

make install CORE=e203 FPGA_NAME=hbirdkit
make mcs CORE=e203 FPGA_NAME=hbirdkit

こんどはTIMING Optimizationの所でVivadoが固まってしまった。固まったというか、どうも猛烈に時間がかかっているらしい。2ステージパイプラインのHummingBirdでFPGAの合成が数時間もかかるだろうか...?何かがおかしい気が。design checkpointを見てみたかったが吐き出されていなかった。

---------------------------------------------------------------------------------
Finished Applying XDC Timing Constraints : Time (s): cpu = 00:07:25 ; elapsed = 00:08:09 . Memory (MB): peak = 0.000 ; gain = 0.000 ; free physical = 8745 ; free virtual = 56416
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
Start Timing Optimization
---------------------------------------------------------------------------------

一晩流してみて、様子を見た方が良い気がしている。

2019/11/11追記。落ちたー。なんで?リソース足りない?

Phase 1.2 IO Placement/ Clock Placement/ Build Placer Device
ERROR: [Place 30-640] Place Check : This design requires more F7 Muxes cells than are available in the target device. This design requires 288747 of such cell types but only 31700 compatible sites are available in the target device. Please analyze your synthesis results and constraints to ensure the design is mapped to Xilinx primitives as expected. If so, please consider targeting a larger device.
ERROR: [Place 30-640] Place Check : This design requires more Slice LUTs cells than are available in the target device. This design requires 799988 of such cell types but only 47200 compatible sites are available in the target device. Please analyze your synthesis results and constraints to ensure the design is mapped to Xilinx primitives as expected. If so, please consider targeting a larger device. Please set tcl parameter "drc.disableLUTOverUtilError" to 1 to change this error to warning.
ERROR: [Place 30-640] Place Check : This design requires more LUT as Logic cells than are available in the target device. This design requires 259236 of such cell types but only 47200 compatible sites are available in the target device. Please analyze your synthesis results and constraints to ensure the design is mapped to Xilinx primitives as expected. If so, please
consider targeting a larger device. Please set tcl parameter "drc.disableLUTOverUtilError" to 1 to change this error to warning.
ERROR: [Place 30-640] Place Check : This design requires more LUT as Memory cells than are available in the target device. This design requires 540752 of such cell types but only 19000
compatible sites are available in the target device. Please analyze your synthesis results and constraints to ensure the design is mapped to Xilinx primitives as expected. If so, please consider targeting a larger device. Please set tcl parameter "drc.disableLUTOverUtilError" to 1 to change this error to warning.

オープンソースRISC-VコアHummingBirdについて調査(6. すべてのテストケースで確認)

RISC-Vの実装であるHummingBirdを調査していくことにした。

https://camo.githubusercontent.com/f393ca23a6df9f640c1e4a43b78f2a503fdc47e8/68747470733a2f2f66647661643032316173666438712e6f73732d636e2d68616e677a686f752e616c6979756e63732e636f6d2f4c696368656554616e672f626f6f6b7069632e6a7067

前回何となくコードがPassできるようになったので、用意されているすべてのパタンを流してみることにした。

for bin in `ls -1 /home/msyksphinz/work/riscv/e200_opensource/vsim/run/../../riscv-tools/riscv-tests/isa/generated/rv32?i-p-* | grep -v .dump | grep -v .verilog`
do
./run/obj_dir/Vtb_top +DUMPWAVE=1 +TESTCASE=${bin} 2>&1 | tee `basename ${bin}`.log
done

各テストケースでログファイルを出力するので、これを確認していく。

grep Finish *.log

すべてPassしたようだ。本当かなあ?

rv32mi-p-breakpoint.log:207:Finish. Result =          1
rv32mi-p-csr.log:208:Finish. Result =          1
rv32mi-p-i2c.log:208:Finish. Result =          1
rv32mi-p-illegal.log:208:Finish. Result =          1
rv32mi-p-ma_addr.log:212:Finish. Result =          1
rv32mi-p-ma_fetch.log:208:Finish. Result =          1
rv32mi-p-mcsr.log:208:Finish. Result =          1
rv32mi-p-sbreak.log:208:Finish. Result =          1
rv32mi-p-scall.log:207:Finish. Result =          1
rv32mi-p-shamt.log:208:Finish. Result =          1
rv32ui-p-add.log:212:Finish. Result =          1
rv32ui-p-addi.log:209:Finish. Result =          1
rv32ui-p-and.log:212:Finish. Result =          1
rv32ui-p-andi.log:209:Finish. Result =          1
rv32ui-p-auipc.log:208:Finish. Result =          1
rv32ui-p-beq.log:210:Finish. Result =          1
rv32ui-p-bge.log:211:Finish. Result =          1
rv32ui-p-bgeu.log:211:Finish. Result =          1
rv32ui-p-blt.log:210:Finish. Result =          1
rv32ui-p-bltu.log:210:Finish. Result =          1
rv32ui-p-bne.log:210:Finish. Result =          1
rv32ui-p-fence_i.log:210:Finish. Result =          1
rv32ui-p-jal.log:207:Finish. Result =          1
rv32ui-p-jalr.log:208:Finish. Result =          1
rv32ui-p-lb.log:210:Finish. Result =          1
rv32ui-p-lbu.log:210:Finish. Result =          1
rv32ui-p-lh.log:210:Finish. Result =          1
rv32ui-p-lhu.log:210:Finish. Result =          1
rv32ui-p-lui.log:208:Finish. Result =          1
rv32ui-p-lw.log:210:Finish. Result =          1
rv32ui-p-or.log:212:Finish. Result =          1
rv32ui-p-ori.log:209:Finish. Result =          1
rv32ui-p-sb.log:212:Finish. Result =          1
rv32ui-p-sh.log:213:Finish. Result =          1
rv32ui-p-simple.log:207:Finish. Result =          1
rv32ui-p-sll.log:212:Finish. Result =          1
rv32ui-p-slli.log:209:Finish. Result =          1
rv32ui-p-slt.log:212:Finish. Result =          1
rv32ui-p-slti.log:209:Finish. Result =          1
rv32ui-p-sltiu.log:209:Finish. Result =          1
rv32ui-p-sltu.log:212:Finish. Result =          1
rv32ui-p-sra.log:212:Finish. Result =          1
rv32ui-p-srai.log:210:Finish. Result =          1
rv32ui-p-srl.log:212:Finish. Result =          1
rv32ui-p-srli.log:210:Finish. Result =          1
rv32ui-p-sub.log:212:Finish. Result =          1
rv32ui-p-sw.log:213:Finish. Result =          1
rv32ui-p-xor.log:212:Finish. Result =          1
rv32ui-p-xori.log:209:Finish. Result =          1

FIRRTLに入門する (3. FIRRTLのざっくりとしたフローを追いかける)

https://raw.githubusercontent.com/freechipsproject/firrtl/master/doc/images/firrtl_logo.svg?sanitize=true

まずはfirrtlに取り込まれる引数を確認した。以下のようにprintlnを挿入した。

  • firrtl/options/Stage.scala
  final def execute(args: Array[String], annotations: AnnotationSeq): AnnotationSeq = {
    println("execute : args")
    for (i <- 0 until args.size) {
      println(args(i))
    }
    transform(shell.parse(args, annotations))
  }

実行すると以下のようになる。まあこれは想定通り。

execute : args
-td
regress
-i
regress/simple_test.fir
-o
regress/simple_test.v
-X
sverilog

つぎに、以下のようにしてすべての処理にprintlnを挟んでみる。

diff --git a/src/main/scala/firrtl/options/Stage.scala b/src/main/scala/firrtl/options/Stage.scala
index 3752b846..ee3bf80b 100644
--- a/src/main/scala/firrtl/options/Stage.scala
+++ b/src/main/scala/firrtl/options/Stage.scala
@@ -35,7 +35,7 @@ abstract class Stage extends Phase {
       Seq( new phases.GetIncludes,
            new phases.ConvertLegacyAnnotations )
         .map(phases.DeletedWrapper(_))
-        .foldLeft(annotations)((a, p) => p.transform(a))
+        .foldLeft(annotations)((a, p) => { println(s"Annotation = ${p}"); p.transform(a)})

     Logger.makeScope(annotationsx) {
       Seq( new phases.AddDefaults,
@@ -43,7 +43,7 @@ abstract class Stage extends Phase {
            new Phase { def transform(a: AnnotationSeq) = run(a) },
            new phases.WriteOutputAnnotations )
         .map(phases.DeletedWrapper(_))
-        .foldLeft(annotationsx)((a, p) => p.transform(a))
+        .foldLeft(annotationsx)((a, p) => { println(s"Annotation2 = ${p}"); p.transform(a)})
     }
   }

以下のようになった。とりあえずPassが進んでいるようだ。

execute = DeletedAnnotation(firrtl.options.Stage$$anon$1,FirrtlFileAnnotation(regress/simple_test.fir))
execute = DeletedAnnotation(firrtl.options.Stage$$anon$1,CompilerAnnotation(firrtl.SystemVerilogCompiler@78aab498))
execute = DeletedAnnotation(firrtl.stage.phases.WriteEmitted,EmittedVerilogCircuitAnnotation(EmittedVerilogCircuit(SimpleCircuit,module SimpleCircuit(
  input  [31:0] io_in,
  output [31:0] io_out
);
  assign io_out = io_in;
endmodule
,.sv)))
execute = DeletedAnnotation(firrtl.stage.phases.Compiler,FirrtlCircuitAnnotation(Circuit(,ArrayBuffer(Module(,SimpleCircuit,ArrayBuffer(Port(,io,Output,BundleType(ArrayBuffer(Field(in,Flip,UIntType(IntWidth(32))), Field(out,Default,UIntType(IntWidth(32))))))),Block(ArrayBuffer(Connect(,SubField(Reference(io,UnknownType),out,UnknownType),SubField(Reference(io,UnknownType),in,UnknownType)))))),SimpleCircuit)))
execute = DeletedAnnotation(firrtl.stage.phases.Compiler,CompilerAnnotation(firrtl.SystemVerilogCompiler@78aab498))
execute = DeletedAnnotation(firrtl.stage.phases.Compiler,RunFirrtlTransformAnnotation(firrtl.SystemVerilogEmitter@7e0b85f9))
execute = FirrtlCircuitAnnotation(Circuit(,ArrayBuffer(Module(,SimpleCircuit,ArrayBuffer(Port(,io_in,Input,UIntType(IntWidth(32))), Port(,io_out,Output,UIntType(IntWidth(32)))),Block(ArrayBuffer(Block(ArrayBuffer(Connect(,WRef(io_out,UIntType(IntWidth(32)),PortKind,SinkFlow),WRef(io_in,UIntType(IntWidth(32)),PortKind,SourceFlow)))))))),SimpleCircuit))
execute = CombinationalPath(ReferenceTarget(SimpleCircuit,SimpleCircuit,List(),io_out,List()),ArrayBuffer(ReferenceTarget(SimpleCircuit,SimpleCircuit,List(),io_in,List())))
execute = EmitCircuitAnnotation(class firrtl.SystemVerilogEmitter)
execute = OutputFileAnnotation(regress/simple_test.v)
execute = TargetDirAnnotation(regress)
execute = InfoModeAnnotation(use)
execute = BlackBoxTargetDirAnno(regress)
execute = DeletedAnnotation(firrtl.stage.phases.AddCircuit,FirrtlFileAnnotation(regress/simple_test.fir))

例えば、EmitCircuitAnnotationを見てみる。ここでは、firrtl.SystemVerilogEmitterを引数として渡しているようだが、実体はVerilogEmitterと同一である。

  • firrtl/Emitter.scala
object EmitCircuitAnnotation extends HasShellOptions {

  val options = Seq(
    new ShellOption[String](
      longOption = "emit-circuit",
      toAnnotationSeq = (a: String) => a match {
        case "chirrtl"              => Seq(RunFirrtlTransformAnnotation(new ChirrtlEmitter),
                                           EmitCircuitAnnotation(classOf[ChirrtlEmitter]))
        case "high"                 => Seq(RunFirrtlTransformAnnotation(new HighFirrtlEmitter),
                                           EmitCircuitAnnotation(classOf[HighFirrtlEmitter]))
        case "middle"               => Seq(RunFirrtlTransformAnnotation(new MiddleFirrtlEmitter),
                                           EmitCircuitAnnotation(classOf[MiddleFirrtlEmitter]))
        case "low"                  => Seq(RunFirrtlTransformAnnotation(new LowFirrtlEmitter),
                                           EmitCircuitAnnotation(classOf[LowFirrtlEmitter]))
        case "verilog" | "mverilog" => Seq(RunFirrtlTransformAnnotation(new VerilogEmitter),
                                           EmitCircuitAnnotation(classOf[VerilogEmitter]))
        case "sverilog"             => Seq(RunFirrtlTransformAnnotation(new SystemVerilogEmitter),
                                           EmitCircuitAnnotation(classOf[SystemVerilogEmitter]))
        case _                      => throw new PhaseException(s"Unknown emitter '$a'! (Did you misspell it?)") },
...
  • firrtl/Emitter.scala
class SystemVerilogEmitter extends VerilogEmitter {
  StageUtils.dramaticWarning("SystemVerilog Emitter is the same as the Verilog Emitter!")
...

やはりEmitterのあたりがみそな気がするなあ。ここですべての構文に対するVerilogの対応が行われてる様子。

そもそも、Verilogに一対一対応で変換する前に、ファイルとして取り込んだFIRファイルをどこかで構文木に経関していると思うのだけれど、それはどこで行われているのだろう?

オープンソースRISC-VコアHummingBirdについて調査(5. パイプライントレーサを追加して内部情報を取得)

RISC-Vの実装であるHummingBirdを調査していくことにした。

https://camo.githubusercontent.com/f393ca23a6df9f640c1e4a43b78f2a503fdc47e8/68747470733a2f2f66647661643032316173666438712e6f73732d636e2d68616e677a686f752e616c6979756e63732e636f6d2f4c696368656554616e672f626f6f6b7069632e6a7067

HummingBirdが何となく動くようになってきたのだが、ログファイルが生成されないので何が起きているのかよく分からない。 そこで、内部のパイプライン動作を把握するためにパイプライントレーサを追加して追いかけてみることにした。

パイプライントレーサは、HummingBirdのテストベンチtb_top.vの中に突っ込むことにする。ファイルに書き出して、あとで参照する。

  • e200_opensource/tb/tb_top.v
`define E203_CORE u_e203_soc_top.u_e203_subsys_top.u_e203_subsys_main.u_e203_cpu_top.u_e203_cpu.u_e203_core
`define E203_CORE_EXU `E203_CORE.u_e203_exu

integer fp_log;
initial begin
  fp_log = $fopen("core_trace.log", "w");
end

always_ff @ (negedge clk) begin
  if (rst_n) begin
    $fwrite(fp_log, "%t   | ", $time);
    if (`E203_CORE.ifu_o_pc_vld) begin
      $fwrite(fp_log, "PC=%08x |", `E203_CORE.ifu_o_pc);
    end else begin
      $fwrite(fp_log, "            |");
    end

    if (`E203_CORE.ifu_o_valid & `E203_CORE.ifu_o_ready) begin
      $fwrite(fp_log, "%08x |", `E203_CORE.ifu_o_ir);
    end else begin
      $fwrite(fp_log, "         |");
    end

    if (`E203_CORE_EXU.rf_wbck_ena) begin
      $fwrite(fp_log, "R%02x<=%08x |", `E203_CORE_EXU.rf_wbck_rdidx, `E203_CORE_EXU.rf_wbck_wdat);
    end else begin
      $fwrite(fp_log, "              |");
    end

    if (`E203_CORE.ifu_o_valid & `E203_CORE.ifu_o_ready) begin
      $fwrite(fp_log, "DASM(%08x)", `E203_CORE.ifu_o_ir);
    end

    $fwrite(fp_log, "\n");
  end // if (rst_n)
end

パイプライントレーサでは、命令のフェッチ、デコード結果、そしてレジスタへのライトバックの情報を出力している。 これでシミュレーションを行うと、core_trace.logにトレースファイルが書き出されて以下のようになる。

make install && make compile && ./run/obj_dir/Vtb_top +DUMPWAVE=1 +TESTCASE=/home/msyksphinz/work/riscv/e200_opensource/vsim/run/../../riscv-tools/riscv-tests/isa/generated/rv32ui-p-add 2>&1 | tee log
              197681   | PC=80000546 |fe520001 |              |DASM(fe520001)
              197691   | PC=80000548 |fe5240b5 |R01<=0000000d |DASM(fe5240b5)
              197701   | PC=8000054a |00208f33 |R1e<=00000018 |DASM(00208f33)
              197711   | PC=8000054e |00200205 |R04<=00000002 |DASM(00200205)
              197721   | PC=80000550 |00204289 |R05<=00000002 |DASM(00204289)
              197731   | PC=80000552 |fe5219e3 |              |DASM(fe5219e3)
              197741   |             |         |              |
              197751   | PC=80000556 |         |              |
              197761   | PC=80000556 |fe524ee1 |R1d<=00000018 |DASM(fe524ee1)
              197771   | PC=80000558 |02000193 |R03<=00000020 |DASM(02000193)
              197781   | PC=8000055c |09df1663 |              |DASM(09df1663)

spike-dasmに突っ込むと何となくなにが起きているのかわかる。

              197681   | PC=80000546 |fe520001 |              |c.nop
              197691   | PC=80000548 |fe5240b5 |R01<=0000000d |c.li    ra, 13
              197701   | PC=8000054a |00208f33 |R1e<=00000018 |add     t5, ra, sp
              197711   | PC=8000054e |00200205 |R04<=00000002 |c.addi  tp, 1
              197721   | PC=80000550 |00204289 |R05<=00000002 |c.li    t0, 2
              197731   | PC=80000552 |fe5219e3 |              |bne     tp, t0, pc - 14
              197741   |             |         |              |
              197751   | PC=80000556 |         |              |
              197761   | PC=80000556 |fe524ee1 |R1d<=00000018 |c.li    t4, 24
              197771   | PC=80000558 |02000193 |R03<=00000020 |li      gp, 32
              197781   | PC=8000055c |09df1663 |              |bne     t5, t4, pc + 140
              197791   | PC=80000560 |09df4201 |R04<=00000000 |c.li    tp, 0
              197801   | PC=80000562 |09df412d |R02<=0000000b |c.li    sp, 11

波形をログを取得すると、テストベンチ自体は正しく動いていることが分かった。ただし現在の状態では全く停止しないので、停止条件を入れてみる。

always_ff @ (negedge clk) begin
  if (`E203_CORE.lsu2itcm_icb_cmd_valid & `E203_CORE.lsu2itcm_icb_cmd_ready) begin
    if (`E203_CORE.lsu2itcm_icb_cmd_addr == 32'h00001000) begin
      $display("Finish. Result = %d\n", `E203_CORE.lsu2itcm_icb_cmd_wdata);
      $finish;
    end
  end
end
f:id:msyksphinz:20191107232341p:plain
最後のテストベンチの実行結果をストアする様子。

0x80001000に値をストアすると停止するので、この条件を取得して書き出す。その時のストア値がベンチの実行結果なので、ついでにそれも表示する。 これで、最終的なテストベンチの結果を取得することができる。

Count = 198000
Finish. Result =          1

- /home/msyksphinz/work/riscv/e200_opensource/vsim/run/../install/tb/tb_top.v:601: Verilog $finish

FIRRTLに入門する (2. FIRRTL解析のためのIntelliJ IDEAの立ち上げと最初の解析)

https://raw.githubusercontent.com/freechipsproject/firrtl/master/doc/images/firrtl_logo.svg?sanitize=true

FIRRTLの動作を解析するためには、Scalaの総合開発環境としてIntelliJ IDEAをインストールしておくのが良い。

これまでインストールしていなかったので、これを機にインストールしておこう。

www.jetbrains.com

無料版のCommunity EditionのLinux版をダウンロードして、WSL上にインストールしておく。tar.gzファイルを展開するとインストールは不要なので、ダウンロードした場所にPATHを通して起動する。

プロジェクトの設定は、firrtlリポジトリの先頭ディレクトリを指定してプロジェクトを開くと自動的に行われるようだ。CTRLキーを押しながら変数や関数にマウスポインタを合わせると、自動的にジャンプできるモードに入る。これは便利。

f:id:msyksphinz:20191106005135p:plain
IntelliJ IDEAを起動してFIRRTLプロジェクトを開いたところ。

前回のリグレッション実行で立ち上げたスクリプトutil/bin/firrtlは実体は単なるシェルスクリプトで、中身は以下のようになっている。

#!/bin/bash

# This may be a brittle way to find $(root_dir)/utils/bin, is there a better way?
path=`dirname "$0"`
cmd="java -cp ${path}/firrtl.jar firrtl.stage.FirrtlMain ${@:1}"
eval $cmd

firrtl.stage.FirrtlMainが引数を含めて呼び出されているらしい。これを見てみる。

とりあえず、以下のサンプルコードを使ってみる。自力でFIRRTLのコードを書いてみた。

  • simple_circuit.fir
circuit SimpleCircuit :
  module SimpleCircuit :
    output io : {flip in : UInt<32>, out : UInt<32>}

    io.out <= io.in

以下のようにしてFIRRTLを起動すると、しばらくしてsimple_circuit.vが生成された。それにしても1秒以下とはいえちょっと時間がかかるな。

$ ./utils/bin/firrtl -td regress -i regress/simple_test.fir -o regress/simple_test.v -X verilog
Total FIRRTL Compile Time: 565.4 ms
  • simple_circuit.v
module SimpleCircuit(
  input  [31:0] io_in,
  output [31:0] io_out
);
  assign io_out = io_in;
endmodule

具体的にどのように実行されているかという話だが、まずは、FirrtlStage.scalaから始まっているように見える。

object FirrtlMain extends StageMain(new FirrtlStage)

StageMain内でmain()が呼ばれ、stage.execute()つまり、FirtlStage.execute()が実行される。

  • firrtl/options/Stage.scala
class StageMain(val stage: Stage) {
...
  final def main(args: Array[String]): Unit = try {
    stage.execute(args, Seq.empty)
...

execute()では、各ステージでのtransformが呼ばれるらしい。実行されるステージは、FirrtlStageで定義されている。

  • firrtl/stage/FirrtlStage.scala
  private val phases: Seq[Phase] =
    Seq( new firrtl.stage.phases.AddDefaults,
         new firrtl.stage.phases.AddImplicitEmitter,
         new firrtl.stage.phases.Checks,
         new firrtl.stage.phases.AddCircuit,
         new firrtl.stage.phases.AddImplicitOutputFile,
         new firrtl.stage.phases.Compiler,
         new firrtl.stage.phases.WriteEmitted )
      .map(DeletedWrapper(_))

まずAddDefaultsだが、以下の3つのオプションを操作しているように見える。

  • firrtl/stage/phases/AddDefaults.scala
  /** Append any missing default annotations to an annotation sequence */
  def transform(annotations: AnnotationSeq): AnnotationSeq = {
...
    annotations.foreach {
      case _: BlackBoxTargetDirAnno => bb = false
      case _: CompilerAnnotation => c  = false
      case _: InfoModeAnnotation => im = false
      case a =>
    }
...
    (if (bb) Seq(BlackBoxTargetDirAnno(targetDir)) else Seq() ) ++
      (if (c) Seq(CompilerAnnotation(default.compiler)) else Seq() ) ++
      (if (im) Seq(InfoModeAnnotation()) else Seq() ) ++
      annotations

BlackBoxTargetDirAnnoは、ブラックボックスVerilogファイルを見つけるためのターゲットディレクトリの指定か?

CompilerAnnotationは、コンパイラに指示するオプションのようにみえる。

InfoModeAnnotationは、良く分からない。

このアノテーションというのは何かというのを調べたのだが、どうやらコンパイラに伝えるべき情報をオプションの中から蓄えるために使用しているもので、最初はEmptyだった。

  • firrtl/options/Stage.scala
class StageMain(val stage: Stage) {

  /** The main function that serves as this stage's command line interface.
    * @param args command line arguments
    */
  final def main(args: Array[String]): Unit = try {
    stage.execute(args, Seq.empty)
  • firrtl/options/Stage.scala
  /** Run this stage on on a mix of arguments and annotations
    * @param args command line arguments
    * @param initialAnnotations annotation
    * @return output annotations
    * @throws OptionsException if command line or annotation validation fails
    */
  final def execute(args: Array[String], annotations: AnnotationSeq): AnnotationSeq =
    transform(shell.parse(args, annotations))
...
  • firrtl/options/Stage.scala
  /** Execute this stage on some input annotations. Annotations will be read from any input annotation files.
    * @param annotations input annotations
    * @return output annotations
    * @throws OptionsException if command line or annotation validation fails
    */
  final def transform(annotations: AnnotationSeq): AnnotationSeq = {
    val annotationsx =
      Seq( new phases.GetIncludes,
           new phases.ConvertLegacyAnnotations )
        .map(phases.DeletedWrapper(_))
        .foldLeft(annotations)((a, p) => p.transform(a))
...