2023/01/07

RISC-V PicoRV32 (1) シミュレーション

はじめに

クリスマスも過ぎた年末、インターフェース2022年12月号の別冊付録で Tang Nano 9K に RISC-V を載せる記事があると知って早速お取り寄せ。うちに積み FPGA してる Tang Nano 9K があるので、いつかは触ってみたいと思っていた RISC-V も併せてちょうど良いなと。ちなみに Tang Nano 4K も持ってるので、MCU 使いたければ Cortex-M3 搭載しているこちらでいいんですけど、FPGA に縛られずに MCU 使えるのはいいですよね。というか RISC-V 触ってみたいだけ。

さて、とりあえずシミュレーション流して動作確認したいですよね?ファームを開発する環境と、Verilog のシミュレーションを流す両方の準備が必要です。そこで Windows に WSL を入れて Ubuntu 環境を用意してファームのビルトとシミュレーション実行環境を整えました。別冊付録ではファームを開発する環境 (RISC-V ツールチェーン) の導入手順が書かれています。素直に記事通りのバージョンを入手して環境を整えましょう。最新版を引っ張ってきたらビルドが通らず悩んで、RISC-V ツールチェーンをゼロからビルドしようとしたらそれも失敗し、結局記事の通りに。。随分と無駄足を踏みました。

OS

普段は Intel Mac (Macbook Pro 15" 2015 Mid) を使っているので、電子工作系や天文系の機材を動かすために Bootcamp を入れています。M1/M2 Mac に乗り換えたいところなのですが、これらの趣味のために乗り換えられません。。😩

Windows10 Pro 64bit + Ubuntu 20.04.5 LTS
上に環境を構築していきました。WSL は入れていたのですがあまり使ってはおらず、当初 GUI を持つアプリは動作しなかったのですが、ごちょごちょいじってたら WSL のアップデートを促され、そしたら GUI が開くようになりました。

ツールチェーン

マイコンを触るのですから、まずはファームをコンパイルする環境を整える必要があります。記事に従い、riscv64-elf-ubuntu-20.04-nightly-2022.09.21-nightly.tar.gz をダウンロードしました。新しいバージョンだとうまくいきません。なおツールチェーンは展開するだけ。展開方法などインターフェースの記事を参考にしてください。あとはどこでもツールを実行できるように記事中で実行を試している export の行を ~/.bashrc に追加しておきます。

シミュレーター

最新の機能を使いたいので、Icarus verilog を git から引っ張ってきて make します。以下ブログが参考になります。
WSL上でIcarus Verilogをソースからインストール

なお Icarus verilog を make する前に波形データの圧縮フォーマット (FSD, LXT2) が使えるようにするために zlib を入れて置きます。

% sudo apt install zlib1g

% sudo apt install zlib1g-dev  

zlib を導入してから上記ブログに従って Icarus Verilog を make すると FST, LXT2 フォーマットを出力できるようになります。デフォルトでは波形データは VCD というテキストフォーマットで出力されるのですが、ちょっと長めのシミュレーションを実行するとあっという間にすごいディスク量を食うので波形圧縮することをお勧めします。LXT フォーマットを使うにはほかのライブラリの導入が必要みたいですが、FST フォーマットで良いと思ったので導入していません。

Icarus Verilog は /usr/local/bin/iverilog としてインストールされます。

波形表示ツール

波形表示ツールは gtkwave を使います。

% sudo apt install gtkwave

でインストールしました。圧縮フォーマットにも対応しています。

シミュレーション

インターフェースの記事のコードを試すには、RISC-V そのもの (PicoRV32 picorv32.v)、RISC-V にペリフェラルを加えた回路(記事にならって top.sv)、RISC-V のメモリにロードできるフォーマットでのプログラムコードの準備 (top.sv では bootrom.hex を想定しています)、そして top.sv をシミュレーションするためのテストベンチ tb_top.sv (無いので用意します)の 4つのファイルが必要です。

picorv32.v

Git で公開されています。このファイル一つに RISC-V がすべて詰め込まれています。

top.sv

CQ 出版のインターフェースのサイトにコードが公開されています。最初コードの内容を理解するために手入力しましたが、、ダウンロードしたほうがいいですね。

bootrom.hex

こちらも自前でコード入力しましたが、同じく CQ 出版のサイトにコードが公開されていますし、makefile もあるので、ダウンロードして、 make を実行すると bootrom.hex が出来上がります。

tb_top.sv

これは無いので頑張って用意します。top.sv には OSC が無いのでそれをテストベンチでつないであげる必要があります。あとはシミュレーション波形をダンプする指示、適当なシミュレーション時間を待って終了する指示を加えます。あとは bootrom.hex のコードと top.sv に含まれるペリフェラルが動いてくれるので特になにもせずに LED マトリクス及び LED を駆動する波形を見ることができます。以下が tb_top.sv のサンプルコードです。

`timescale 1ns/1ps

module tb_top;

  parameter CLK_PERIOD2 = 37 / 2;

  logic       clk = 0;
  logic [7:0] anode;
  logic [7:0] cathode;
  logic [5:0] led;

  always begin
    #(CLK_PERIOD2);
    clk = ~clk;
  end

  top u_top(.*);

  initial begin
    $dumpfile("tb_top.fst");
    $dumpvars(0, tb_top);
  end

  initial begin
    #16ms;
    $display("Finish!");
    $finish;
  end

endmodule

テストベンチの解説

まずはシミュレーション対象 (DUT: Design Under Test) top.sv をインスタンスして配線をつけます。
  logic       clk = 0;
  logic [7:0] anode;
  logic [7:0] cathode;
  logic [5:0] led;

これが配線(みたいなもの)。

 top u_top(.*);

これがインスタンスしてるところ (top.sv の回路を実体化しておいて配線もつなぎます) 配線のつなぎ方はほかにもあるのですが、top モジュールのポート名と同じ信号名でよければ上記のように省略して配線することができます。Icarus Verilog もぼちぼちと SystemVerilog の構文をサポートしつつあるので便利になってきました。

外付け部品のクロックを用意します。水晶発振器のビヘイビアモデル!(しょぼいけど)になります。

  always begin
    #(CLK_PERIOD2);
    clk = ~clk;
  end

TangNano 9K を意識して、27MHz のクロックのつもりです。27MHz の半周期が来たら clk を反転させてクロック信号を生成します。割り算結果は切り捨てられて、36ns 周期のクロックが生成されるので 27.78MHz のクロックになってしまいますが。

波形出力の設定は以下のようになります。

  initial begin
    $dumpfile("tb_top.fst");
    $dumpvars(0, tb_top);
  end

\$dumpfile は出力ファイル名、fst フォーマットを出力するつもりなので、.fst にしてます。\$dumpvars でどれだけの階層をどのモジュールから出力するか指示しています。階層 0 なので全階層。テストベンチのモジュールを指定しているのでトップから全部信号を出力することになります。

最後にシミュレーション時間を決めます。16ms 経過したら \$finish が呼ばれるようにします。

  initial begin
    #16ms;
    $display("Finish!");
    $finish;
  end

以上でテストベンチの解説は終わりです。

しかしながらこれでシミュレーションを流してみると LED マトリクスをドライブしている anode, cathode が駆動している様子は見れるのですが、ぐるぐると点灯を繰り返しているはずの led ポートがピクリとも動きません。bootrom.c をよく読むと、led は 1秒で点灯を切り替えます。そんなのシミュレーションでやったら 8秒で一周なので、、、シミュレーション時間も波形出力も膨大になります。なので bootrom.c をシミュレーション用にちょっと変更します。

    const uint32_t clock_hz = *REG_CLOCK_HZ >> 8;    // クロックの周波数取得
周期を 1/256秒に変更したので、16ms のシミュレーション時間でもそれなりにトグルしている様子をみることができます。

シミュレーションの実行

まず上記の 4つの必要ファイルを同じディレクトリに置いて、iverilog コマンドを実行してコンパイルします。コンパイルされたファイルをシミュレーションのランタイムエンジンである vvp の引数に与えてシミュレーションを実行します。iverilog の出力ファイルは省略すると a.out なのですが、-o オプションで明示的に与えています tb_top。また SystemVerilog の構文を含むので -g 2012 のオプションを忘れないように。vvp を実行する際には -fst または -lxt2 のオプションをつけたほうが良いと思います。つけないと vcd フォーマットで波形ファイルが出力され、ディスクの消費量がとんでもないことになると思います。とりあえず以下のようにコマンドラインで実行すれば波形ファイル tb_top.fst が得られると思います。
  % iverilog -g 2012 -o tb_top tb_top.sv top.sv picorv32.v
  % vvp tb_top -fst
波形ファイルは gtkwave を起動して読み込ませます。"&" をつけておくと gtkwave を立ち上げた後にシェルが占有されません。
  % gtkwave tb_top.fst&
読み込んだ後は見たいノードを選択して、Append すると波形が表示されると思います。回路やファームを変更してシミュレーションを流しなおした場合、gtkwave は閉じずに [File]->[Reload Waveform] をクリックすると波形を読み込みなおします。
gtkwave の使い方は、ググるといろいろ出てくるのでそちらを参考にしてください。


シミュレーション波形はこんな感じです。anode, cathode, led が期待通りにトグルしている様子を見ることができます。

ようやくこれでスタートラインに立てました。PicoRV32 をコアにペリフェラルを改造してみたオレオレ SoC の続き (2) は。。あるのか?