今回は作ってたRISC-V(dirv)とUARTを接続した簡単なシステムがFPGAで動いたので、その結果について簡単にまとめておきたいと思う。
要約
オレオレRISC-V(dirv)と自作のバスインターコネクト&UartをArty 35Tに入れて動くのが確認できたーーーー!!
データは以下のリポジトリにあるので、ご興味あれば是非!
という記事(2019/1/5修正:リンク先をUART込み込み版のリリースタグに変更しました。)
ここまでの経緯
以前に自分で簡単なRISC-Vを作った!という記事をまとめていたが、その記事の時点では以下のような状況だった。
- Verilatorを使ったシミュレーションでriscv-testsのRV32Iのパターンが全てPASSする
- Vivadoでインプリしてエラー無く合成できる
その後Arty 35Tを購入したので、その上にdirvとメモリを直接接続したデザインを構築し、そのデザインをFPGA上で動かして見るところまでは確認していた。
Chiselで作ったオレオレRISC-V、とりあえず動作確認用にいれたaddiのテストが通った!!
— だいにんぎょー@技書博2に向けて活動中 (@diningyo) September 2, 2019
finish信号をLEDに繋いで光るのも確認出来た☺️
左:シミュレーション波形
右:FPGAの動作波形#ChiselLang #RISCV pic.twitter.com/4MKCWTl48i
でこれだけだと、なんだかなぁ、、、というのもあったので、その後の目標として
- dirv + UARTで文字を出したい!!
というものを考えていた。
SysUart
作ろうとしていたシステム(と言うにはシンプルだけど)は以下のようなもの。
最初はdirvの命令フェッチとデータアクセスをそれぞれMbusICに接続して、メモリにつなぐつもりだったけど、そもそもメモリをデュアルポートで作っていたので、データバスだけメモリとUARTにアクセスする形にした。
この中で既に作っているのはdirvとメモリだけなので、バスインターコネクトとUARTは新規で設計することになる。 各モジュールの詳細な説明はまた別の記事にするつもりなので、ここではブロック図ベースでどんなものを作った!というのを載せておく。
MbusIC
先に書いたようにデータアクセスの経路をアドレスでメモリ or UARTに分岐すればいいのでこのモジュールは実際には1-in/2-outのセレクタとして動作すればOKということにはなる。ただ単に1-in/2-outのセレクタをベタ書きで作ってもいいのだが、それだと「Chiselを使う意味!!」となるのでもう少し真面目に設計しておく。
「真面目に」の意味だが、仕様として以下のようなことが出来るように設計することを指している。
- インターコネクトらしくN-in/M-outのパラメタライズが可能
- スレーブのメモリマップをパラメータで指定可能
ブロック図的には以前にChiselで作るNICという記事で書いたものと同様で以下のようになる。
違いは以下の2点。
- 各Decoder/Arbiterのバスプロトコルがdirvで設計したMbus
- Decoderの出力がモジュールに入力したメモリマップに従って制御される
以下の感じでメモリマップのSeq
を渡すと、その情報を元にしてスレーブの数とアドレスの範囲のチェックを行う。
Seq( (0x0, 32 * 1024), // MemTop (32KBytes) (32 * 1024, 0x100) // Uart ),
このMbusIC
のパラメータクラスは以下のような定義なのだが、現状ではmasterInfos
がいらない子になってる。。。
/** * parameter class for MbusIC * @param ioAttr IO port access attribute. * @param slaveInfos Slave module information. It contains base address and size. * @param dataBits Data bus width. */ case class MbusICParams ( ioAttr: MbusIOAttr, masterInfos: Seq[(Int, Int)], // マスタの情報も(Int, Int)にしたが今の状態ではマスタの個数だけでよかった。。 slaveInfos: Seq[(Int, Int)], dataBits: Int ) { val numOfMasters = masterInfos.length val numOfSlaves = slaveInfos.length val addrBits = 32 val decParams = Seq.fill(numOfMasters)(MbusDecoderParams(MbusRW, slaveInfos, dataBits)) val arbParams = Seq.fill(numOfSlaves)(MbusArbiterParams(MbusRW, masterInfos, dataBits)) }
きちっと拡張すると、Masterからアクセス可能なスレーブを限定したり出来るようになる、、、、はず。。。
Uart
お次はUart、なのだがこれはXilinxがVivadoで使えるIPとして公開してくれているUartLiteというものの仕様書をベースにして、とりあえず送受信を行うために必要な部分だけを実装することにした。
https://japan.xilinx.com/products/intellectual-property/axi_uartlite.html
仕様書はこちら。
ブロック図は以下のようなもの。
図中の枠線はそれぞれ以下のような意味を持つ。
- 青色:実装するもの
- 赤色:バスプロトコルを変更
- 灰色:今回は実装しないもの
要はモジュールのインターフェースをMbusに変更して、Uartの制御レジスタと送受信制御のみを作る形。
作ってみた結果
冒頭に載せたとおりArty 35T上で正常に動作してUartで接続したLinux上のターミナルに"Hello, World!!"が表示されました。嬉しい。
ここでは各種合成結果についてをまとめておきます。
リソースの使用率
以下の画像のようになりました。
RISC-Vは最小になるように作ると大体1000LUTとか聞いた気もするので、ざっくり倍くらいのサイズ。この辺はバスプロトコルが多少複雑なこととかが影響してるか?という感じ。IFUが結構膨らんでるし。
m_mbus_ic(MbusIC)の構成がm_dec_0/1になってて、何故かマスターポートが2つあるのに気づく人もいるかもですが、これはMbusICの作りにまずいところがあって、マスターを1ポートにするとRTLの生成に失敗するからです。。ここは要修正な部分。
タイミング
こちらも画像をペタリとします。まずはクロックから。
ご覧の通りでシステムのクロックは50MHz。
タイミングレポートのサマリは以下。
ワーストでスラックが0.246nsなので、もう少しなら余裕がある。長いパスはフェッチした命令をレジスタで受けた後のパスなので、パイプライン化を真面目にすればまだ上げられるかな、、という感じ。
今後の予定
これはリポジトリのREADMEのTODOにも書いてるのだが、3-stage/5-stageに変更してどうなるか、というのと、割り込みの実装をまずはやりたいかな。 ツイッターでやり取りをしていてMMUとかも実装したいなーとか思い始めてますが、このあたりはまずは勉強から、、という感じなのでいつになることやら。