FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

Chiselを使ってCPUを作ろう(5. トレース記述をどう作る?)

Chiselを使って、非常にシンプルなCPUを作ってみるプロジェクト、ある程度演算命令は動き始めたが、ちゃんと動作しているのかどうかを確かめたい。

普通CPUを作るときは、波形をわざわざ観測するなんてそんなダサいことはしなくて、トレースファイルを出力してそれをISSと一致比較検証するか、そもそもDPIか何かを使ってCPUの内部情報を動的に抽出してISSを比較したりする。

そのために、CPUの内部にシミュレーション時限定のトレースユニットを組み込み、そこから情報を回収してテキストファイルなりなんなりに出力するわけだ。

Chiselも、一応printfなどの方法が存在している。ただし、非常に不便だ。

github.com

Chiselのシミュレーションモードで実行しているとScalaとしてこのprintfが実行される(Verilogに直すとVerilogとして実行される)が、そのフォーマットが非常にしょぼい。 まず表示幅をきちんと調整してくれない。つまり%08xとか書いても、まったく無視してくる。っていうかコンパイルが通らない。 Verilogに直してシミュレーションすると、Verilogシミュレータがちゃんと調整してくれるが、いちいちVerilogに直してられないのでScalaだけで済ませたい。

仕方が無いので、Debug情報専用のBundleを定義してそこから外部に引っ張ってくることにした。 とりあえず、

  • 命令が実行されたか (inst_valid)
  • 実行された命令アドレス(inst_addr)
  • 実行された命令(inst_addr)
  • レジスタに書き込みが発生したか(reg_wren)
  • 書き込みレジスタアドレス(reg_wraddr)
  • 書き込みレジスタデータ(reg_wrdata)

を外部に引っ張り出した。

class CpuDebugMonitor extends Bundle {
  val inst_valid = Output(Bool())
  val inst_addr  = Output(UInt(32.W))
  val inst_hex   = Output(UInt(32.W))
  val reg_wren   = Output(Bool())
  val reg_wraddr = Output(UInt(5.W))
  val reg_wrdata = Output(SInt(64.W))
}
f:id:msyksphinz:20181120011505p:plain

そして外部のTester上でScalaの記述を使ってトレースファイルを書き出す。

  for (i <- 0 to 100) {
    val inst_valid = peek(cpu_tb.io.dbg_monitor.inst_valid)
    if (inst_valid == 1) {
      val hexbus_width = 8
      writer.printf("%d : ".format(cycle))
      val reg_wren   = peek(cpu_tb.io.dbg_monitor.reg_wren)
      val reg_wraddr : Long = peek(cpu_tb.io.dbg_monitor.reg_wraddr).toLong
      val reg_wrdata : Long = peek(cpu_tb.io.dbg_monitor.reg_wrdata).toLong
      if (reg_wren == 1) {
        writer.printf("x%d<=0x%016x".format(reg_wraddr, reg_wrdata))
      } else {
        writer.printf("                     ")
      }
      val inst_addr = peek(cpu_tb.io.dbg_monitor.inst_addr)
      val inst_hex  = peek(cpu_tb.io.dbg_monitor.inst_hex)
      writer.printf(" : 0x%08x : INST(0x%08x) : DASM(%08x)\n"format(inst_addr, inst_hex, inst_hex))
    }
    step(1)
  }

printfのフォーマットもちゃんと使えるので便利だ。Chiselで記述していると、信号線は全てUIntかSIntになってしまい通常のprintfで制御できなくなる。 これをScalaのIntに変換するためにはChisel Testerのpeekを使わなければならないのだが、そのためには一度Scalaの世界に持ってこなくてはならないわけだ。

あと、64-bitの値を"%016x"で表示すると負数の時におかしな表示(マイナス付きの値)になるのでLongに変換しておくのもポイント。

これで、所望のログをファイルに出力できるようになる。

0 : x14<=0x00000000048a2b41 : 0x00000084 : INST(0xb417071b) : addiw   a4, a4, -1215
0 : x14<=0x00000048a2b41000 : 0x00000088 : INST(0x00c71713) : slli    a4, a4, 12
0 : x14<=0x00000048a2b40b11 : 0x0000008c : INST(0xb1170713) : addi    a4, a4, -1263
0 : x14<=0x00048a2b40b11000 : 0x00000090 : INST(0x00c71713) : slli    a4, a4, 12
0 : x14<=0x00048a2b40b10a21 : 0x00000094 : INST(0xa2170713) : addi    a4, a4, -1503
0 : x14<=0x48a2b40b10a21000 : 0x00000098 : INST(0x00c71713) : slli    a4, a4, 12
0 : x14<=0x48a2b40b10a2099a : 0x0000009c : INST(0x99a70713) : addi    a4, a4, -1638
0 : x15<=0xffffffffffd51000 : 0x000000a0 : INST(0xffd517b7) : lui     a5, 0xffd51
0 : x15<=0xffffffffffd50b45 : 0x000000a4 : INST(0xb457879b) : addiw   a5, a5, -1211
0 : x15<=0xfffffffaa168a000 : 0x000000a8 : INST(0x00d79793) : slli    a5, a5, 13
0 : x15<=0xfffffffaa168a219 : 0x000000ac : INST(0x21978793) : addi    a5, a5, 537
0 : x15<=0xfffea85a28864000 : 0x000000b0 : INST(0x00e79793) : slli    a5, a5, 14
0 : x15<=0xfffea85a28863d37 : 0x000000b4 : INST(0xd3778793) : addi    a5, a5, -713
0 : x15<=0xea85a28863d37000 : 0x000000b8 : INST(0x00c79793) : slli    a5, a5, 12

でも、この方式だとVerilogに変換した際に、CPUのインタフェースにデバッグ信号がそのまま出てくる。これは最終的に取り除きたいのだけれども、どうすればいいかなあ。。。

module Cpu( // @[:@569.2]
  input         clock, // @[:@570.4]
  input         reset, // @[:@571.4]
  input         io_run, // @[:@572.4]
  output [15:0] io_inst_addr, // @[:@572.4]
  output        io_inst_req, // @[:@572.4]
  input         io_inst_ack, // @[:@572.4]
  input  [31:0] io_inst_data, // @[:@572.4]
  output        io_dbg_monitor_inst_valid, // @[:@572.4]
  output [31:0] io_dbg_monitor_inst_addr, // @[:@572.4]
  output [31:0] io_dbg_monitor_inst_hex, // @[:@572.4]
  output        io_dbg_monitor_reg_wren, // @[:@572.4]
  output [4:0]  io_dbg_monitor_reg_wraddr, // @[:@572.4]
  output [63:0] io_dbg_monitor_reg_wrdata // @[:@572.4]
);