BIO:Bao I/Oコプロセッサ (bunniestudioブログ翻訳)
BIOはBaochip-1xに搭載されているI/Oコプロセッサだ。Baochip-1xは僕が設計をした、ほとんどがオープンソースの22nm SoCである。Baochip-1xの背景についてはこちらの記事で詳しく説明しているし、評価ボードはCrowd Supplyで入手できる。 この投稿では、BIOの成り立ちについて話そう。まず参考としてRaspberry Pi PIOを詳細に研究したところから始め、その後BIOのアーキテクチャに深く掘り下げていく。さらに、BIOのプログラミング例を3つ紹介する。うち2つはアセンブリ、1つはC言語だ。BIOの使い方だけに興味があるなら、背景の詳細は飛ばして、記事の後半にある「Design of the BIO」というセクションまでスキップしてもいいし、コード例に直接進んでも構わない。背景I/Oコプロセッサは、I/OタスクをメインCPUコアからオフロードする。メインCPUは何らかのマルチタスク方式を使って複数の優先度を処理しなければならず、それが応答時間の予測不能さにつながる。この予測不能な応答は、クリティカルな応答における望ましくないジッタや遅延として現れる。I/Oタスクにコプロセッサを専任させることで、汎用CPUの柔軟性を維持しながら、専用ハードウェアのステートマシンに迫る決定性を実現できる。 I/Oコプロセッサのよく知られた例が、Raspberry PiのPIOだ。PIOは4つの「プロセッサ」から構成され、それぞれが9命令を持ち、32ワードの命令メモリを備えている。GPIOのサイクル単位での正確な操作を容易にしながら、高い柔軟性を発揮するよう高度に調整されている。例えば、クロック、イン、アウトを備えたSPIの実装は、コンフィギュレーション修飾子と、わずか2つの命令で実現できる。これらはPIOのコンフィギュレーションで利用可能な副作用(コードの自動ラップアラウンドやFIFO管理など)によって「実質的なループ」として実行される: “.side_set 1”, “out pins, 1 side 0 [1]”, “in pins, 1 side 1 [1]",僕はBaochipに何らかの形のI/Oコプロセッサを欲しがっていたので、PIOを自分が知る最善の方法 — — つまりコピーすること — — で研究することにした。Lawrie Griffithのfpga_pioをフォークして出発点とし、欠けていたコーナーケースをすべて洗い出すために、大量の回帰テストと詳細なシミュレーションを行った。その結果、RP2040世代のPIOコアにかなり近い、仕様に完全に準拠したものができたと思う。このgithubリポジトリで公開している。PIOから学んだ教訓PIOクローンを構築してFPGAにコンパイルしてみて、僕は驚いた。PIOが驚くほど多くのリソースを消費するのだ。FPGAで使うことを考えているなら、PIOはスキップして、やりたいペリフェラルをRTLで直接実装したほうがいいだろう。 上記はXC7A100 FPGAをターゲットに配置配線したPIOコアの階層的リソースマップだ。PIOが占める部分をマゼンタでハイライトしている。FPGAの半分以上を消費しており、RISC-V CPUコア(右側の「VexRiscAxi4」ブロック)よりも多い!わずか9命令しか実行できないにもかかわらず、各PIOコアは約5,000のロジックセルで構成されている。比較すると、VexRiscv CPUは、IキャッシュとDキャッシュを除けばわずか4,600ロジックセルしか消費しない。 さらに、PIOコアのクリティカルパスはVexRiscvの少なくとも2倍悪い。FPGA設計ではVexRiscvだけであれば100MHzのタイミングクローズは容易だが、PIOコアが入ると50MHzでのタイミングクローズすら困難になる。 Vivadoのタイミング解析結果をざっと見てみると、何が起きているかの手がかりが得られる。 上記は設計中の最も長い組み合わせパスのひとつとして特定されたロジックパスであり、下記はそのセルの詳細レポートである。 問題は、コンピュータアーキテクチャそのものと同じくらい古い論争、すなわちCISC対RISCの議論に行き着く。PIOは「たった」9命令しか持たないが、各命令は信じられないほど複雑だ。単一の命令で、以下のすべてを1サイクル内に実行できるように調整されている:何らかの通常操作(JMP、WAIT、IN、OUT、PUSH、PULL MOV、IRQ、SET)プログラムカウンタのインクリメント……さらに、特定の条件に達したらプリセットされた場所にラップバックする32ビットバレルシフタを通じてデータを回転させ、潜在的な宛先/ソースとの間でやり取りするしきい値をチェックし、入力/出力FIFOを補充するかどうかを決定する(結合されている場合とされていない場合がある)別のピンをサイドセットする可能性割り込みフラグを計算し、結果に基づいてプログラムカウンタを変更する可能性複数のマシンが共有リソースにアクセスしようとした場合の優先度の競合を解決するロジック面積の多くは、ピンマッピングオプションの柔軟性を処理するために必要なシフタによって消費されていることがわかる。PINCTRLレジスタを見ると、4つの「ベース」セレクタがあり、これは4つの32ビットバレルシフタと、シフタの末端に取り付けられた構成可能なラン長を意味する。基本的に、PIOの「回転+マスク」部分は、ステートマシン自体よりも多くのロジック面積を消費しており、これら一連の回転マスクとクロック分周、FIFOしきい値計算を1サイクルに押し込むことは、時間的にも非常にコストが高い。PIOのオプションの柔軟性は、基本的にFPGAの上でFPGAのような配線ネットワークをエミュレートしていることを意味し、それが非効率性の原因だ。 おそらく僕のPIOの実装には、もっと効率的にするための最適化が欠けているのかもしれない。しかし、僕はサイクル精度を維持することにかなり注意を払っており、その過程で、たとえタイミングクローズを改善できたとしても、忠実性に影響を与える最適化は避けなければならなかった。 FPGA研究から得た教訓は、ASICフローにも引き継がれた。Baochip-1xの生成に使用したのと同じツールチェーンにコードベースを通したところ、ゲート数と遅延も同様に大きく、そして「遅い」ことがわかった。「遅い」と引用符を付けたのは、GPIOのビットバンギングという本来の用途には十分な速度だからだ。ASICで可能なことに比べれば遅い、というだけの話だ。PIOユーザへの注意事項どうやらPIOには少なくとも1つの特許が関わっているようだ。方針として、僕は特許を読まないことにしている。したがって、この再実装が特許を侵害しているかどうかについて意見を述べることはできない。しかし、これはRaspberry Pi財団が自社ブロックのオープンソースによる再実装を歓迎していないというシグナルだ。彼らは僕に対してこのブロックのソースコードを削除するよう強制していないが、この共有したリファレンスコードを使用しようとする人は誰でもこの問題を認識し、製品に組み込むリスクを考慮すべきだ。別のアプローチ僕の専門的な訓練とキャリアの影響は、コンピュータアーキテクチャにおけるRISC陣営にしっかりと位置づけられている。博士課程の指導教官だったTom Knightは、ハードウェアアーキテクチャについて考えるとき「重要なのは配線だ、バカ」とよく言っていた。複雑さは将来の負債になること(別の言い方をすれば「シンプルな設計は新しいプロセスへの移植が容易だ」)、ハードウェアの目新しさは優れたソフトウェアツールなしでは無価値であること、を彼は教えてくれた。 その結果、PIOは抽象的な思考概念としてはなかなか面白いが、実装者としては本当に気になる存在だった。バレルシフタはハードウェア的に高コストだ。バレルシフタにはたくさんの配線があり、僕は配線を慎重に使うように訓練されてきた。さらに、カスタム命令セットはコーディングが難しく、特に命令実行に影響を与える帯域外の設定があるとなおさらだ。数ヶ月を費やして大量のPIOコードを書いた後でも、僕はまだ最初のトライで動かすことに苦労し、カスタムPIOコードをデバッグするためにVerilatorシミュレーションに大きく依存していた(他のPIOプログラマーがどのようにデバッグしているのか、僕には見当もつかない。しかし、もし何かあるとすれば、PIOの再実装の最大の利点のひとつは、Verilatorを使ったシミュレーションで実際にPIOコードをデバッグできることかもしれない)。 結論として、この作業をすべて終えた後、僕は力を得たというよりも疲弊していると感じた。PIOは、僕が期待していたほど楽しくなかったのだ。 そこで、ふと思いついた。PIOのオールRISCバージョンがあったらどうだろう?こうして「BIO」が生まれた。ひねりのあるRISC実のところ、RISC-V 32ビットコアは非常にコンパクトにできる。Claire Xenia WolfのPicoRV32はその好例だ。このコアは、Xilinx 7シリーズFPGA上で761スライスLUTまで縮小し、200MHz以上の速度を達成できる。それにもかかわらず、完全なRV32I命令セットを実行できる。つまり、RISC-Vエコシステムで利用可能な優れたソフトウェアツール群を活用できるのだ。 もちろん、欠点もある。ひとつは、I/Oを決められたタイミングで切り替えたい場合、サイクルカウント地獄に陥ることだ。もうひとつは、単にコアをロード/ストア命令でI/Oレジスタに配線すると、4つのコアがGPIOレジスタのバンクを競合することになり、多くの非決定性やウェイトステート、その他の複雑さが生じる。つまり、4つのPicoRV32コアをAXIバスに接続してGPIOをビットバンギングすればPIOのような結果が得られる、という単純な話にはならない。 幸い、僕には秘策がある。数十年前、博士論文のために「ADAM」と呼ばれるCPUアーキテクチャを設計した。詳細のほとんどはここでは関係ないが、ひとつのトリックだけは重要だ。レジスタファイルに通常のレジスタだけを置くのではなく、その一部をキューにマッピングし、アーキテクチャレベルでfull/emptyのブロッキングセマンティクスを持たせるのだ。このトリックにより、軽量な命令レベルの並列性から、プロセッサとI/Oリソース間の高速で低レイテンシな通信まで、多くのことが可能になる。ここで使うのは後者の特性だ。BIOの設計BIOの設計は、RV32Eとして構成されたPicoRV32から始まる。このモードでは、完全な32レジスタ(ゼロレジスタを含む)の代わりに、16レジスタ(r0~r15のみ)がRV32E仕様の正式な部分となる。そして僕は、r16~r31を悪用して、一連の「レジスタキュー」とGPIOアクセス、同期プリミティブをマッピングする。下図は、4つのRV32Eコアそれぞれで公開される最終的なレジスタセットを示したものだ。 (上図で、ベージュ色の長方形は通常の読み書きセマンティクスを持つレジスタ、ラベンダー色の長方形は様々な条件に基づいてCPU実行をブロックできるレジスタである。) テキストでの「上位バンク」レジスタの説明:FIFO - 8段FIFOの先頭/末尾アクセス。コアはオーバーフロー/アンダーフロー時に停止する. x16 r/w fifo[0] x17 r/w fifo[1] x18 r/w fifo[2] x19 r/w fifo[3] Quantum - ホストが設定したクロック分周パルス、またはホストが指定したGPIOピンからの外部イベントが発生するまでコアは停止する . ...