Chiselを使って、非常にシンプルなCPUを作ってみるプロジェクト、ある程度演算命令は動き始めたが、ちゃんと動作しているのかどうかを確かめたい。
普通CPUを作るときは、波形をわざわざ観測するなんてそんなダサいことはしなくて、トレースファイルを出力してそれをISSと一致比較検証するか、そもそもDPIか何かを使ってCPUの内部情報を動的に抽出してISSを比較したりする。
そのために、CPUの内部にシミュレーション時限定のトレースユニットを組み込み、そこから情報を回収してテキストファイルなりなんなりに出力するわけだ。
Chiselも、一応printfなどの方法が存在している。ただし、非常に不便だ。
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)) }
そして外部の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] );