こんにちは。WhatACotton です。書きたい書きたいと言って全然書いてこなかった課題晒しをしていきたいと思います。(少し酔いながら書いているので多少内容がブレるかもしれませんがご愛嬌で)
私がセキュリティキャンプ 2024 で受講したゼミは、開発ゼミ S02 FPGA を用いた RISC-V CPU 自作ゼミ という CPU を自作し、それを FPGA ボードに実装して動かしてみるというゼミでした。その内容についても後日書こうと思います。
あと、確認しろと言われればそうなのですが、特に他の方の応募課題晒しは見ていないので一風違ったものになっている可能性があります。ご了承ください()
# 問 1
# 問題
RISC-V の特徴の一つとして、命令セットアーキテクチャ (ISA) がロイヤリティフリーなため、RISC-V の命令セットに対して独自の命令を追加した CPU を設計しても、ISA のライセンスとして問題がないという点があります。このため、RISC-V を推進している様々な組織が命令セットを提案し、有用なものは拡張命令セットとして規格化されています。 例えば最近、規格化された拡張命令の一覧は以下のページに記載されています。 https://wiki.riscv.org/display/HOME/RISC-V+Specification+Status これらの中で興味深いと思うものがあるならば、その理由とともに説明してください。
これらの中に特に興味深いと思うものが無いのであれば、あなたが RISC-V に命令を追加する場合、どのような命令(群)を追加してみたいかを、その命令の仕様 (ニーモニック、フォーマット、動作) とともに説明してください。 既に規格化・提案されているものと重複していても構いません 逆に命令が多すぎるのでサブセットでも良いのでは?と思う場合は、RV32I をベースとしてどの命令を減らすのかとともに、減らすことによる利点・欠点を説明してください。
# 回答
https://jira.riscv.org/browse/RVS-653 この仕様は ACPI のハードウェアの操作をソフトウェアに変換する規格です。この規格は UEFI や SBI にも関連する規格で、かなり複雑なことをしていたので面白いと感じました。具体的にはシャットダウンや、スリープモードをソフトウェア的に実装できるのはすごいと思います。
ACPIFFH の FFH とは Functional Fixed Hardware の略のことで、arm の FFH に関する仕様書を見ると、 “This refers to software(SW) operations that replace a hardware(HW) finction.” とあり、ハードウェアで今まで行われていたことをソフトウェアで行うということで批准されたものです。
ドキュメントがあるので、当然 arm には実装されているらしく、更に、x86 で調べてみると、Chromium で使われているサードパーティー製のライブラリの中に、x86 の命令セットを見つけることができ、acpi.h を見てみると、FFH について定義されている行を見つけました。 以上から、他の CPU でも実装されている仕様が RISC-V でも取り入れられたということがわかりました。
UEFI でも FFH に関する記述を見つけることができました。ここでは ACPI の説明の中に FFH についての言及を見つけることができます。 “ACPI defines the fixed hardware low-level interfaces as a means to convey to the system OEM the minimum interfaces necessary to achieve a level of capability and quality for motherboard configuration and system power management.” OEM に対して電源管理の最小限のインターフェースを提供するものであるということがわかります。
ここまで調べてきて、まずハードウェアで通常電源管理をしているところに、ソフトウェアでそのインターフェースを用意しているということが面白いと思いました。実際に x86 や arm が実装している部分を、RISC-V でも実装が進んできているので、RISC-V もどんどん汎用 CPU に近づいていると思いました。
UEFI のドキュメント: https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#functional-fixed-hardware arm のドキュメント: https://developer.arm.com/documentation/den0048/latest/ chromium での記述: https://chromium.googlesource.com/chromiumos/third_party/coreboot/+/firmware-parrot-2685.B/src/arch/x86/include/arch/acpi.h#72
# 感想
この回答は今の自分が改めて見てみると、すこし面白くはあるが、ISA としての規格や、その実装についての説明がなかったので、今ひとつの回答だったなと思います。というのも、この回答では具体的にどのような命令セットでどのような命令列でどのように実行されるかについては今ひとつ説明がないからです。
ただ、自分としてはこの回答に至った経緯としては、初めて RISC-V というアーキテクチャの開発におけるコミュニティに触れたということもあり、そもそも自分自身が RISC-V についての理解がそこまで足りていなかったというところがあると思います。この頃は RISC-V がモジュール化されており、その中で批准されているものとされていないものがあるということについてそこまで理解していなかったため、しょうがなかったといえばそうかもしれません。
私がこの回答を行う上で気をつけたところは、自分の知識がそこまで深くないことを自覚して、できるだけ多くの資料に基づいた回答を行ったところです。何も資料を参考にせずにタラタラとし持論を述べただけではなんの説得力もないので、できるだけ色々な資料を漁ってテーマに基づいた言及を探しに行くことに焦点を当てて回答を作成していきました。
# 問 2
# 問題
今までに使ったことがある CPU の命令セット (x86_64, Arm, PIC, AVR, RL78, RX, RISC-V, etc… なんでも OK) について自由に記述してください。
# 回答
「アルカディアでゲームを作ろう」というニコニコ動画の動画を見ながら、バイナリエディタで機械語を打ったことがあります。アルカディアはバンダイから 1983 年に発売された家庭用ゲーム機で、その中には Signetics2650 という CPU が入っており、アルカディアのエミュレータを動かしながら、ゲームのカセットとしてバイナリを読ませることで動作させます。私は動画を参考にしながら、文字の色を変えたり少しずつ元のコードから変更を加えることで遊んでいました。
また、x86_64 のアセンブリを授業で少し触りました。例えば、x86_64 では rax の中の下位 32bit が eax という別の名前として名付けられているなどの特徴があり、そのような Signetics2650 には存在していなかったハードウェア依存の仕様があり、面白いと感じました。また、x86 でできることが Signetics2650 よりもあまりにも多かったので、技術の進歩をとても強く感じました。
その他、Cra2yPierr0t さんの教育用 CPU である jacaranda-8 を読んで Verilog で CPU がどのように作られていくのかということを知り、jacaranda-8 はとても分かりやすい命令セットだったので勉強になりました。 CPU 命令セットとは言い難いかもしれませんが、Turing Complete という NAND ゲートから CPU を自作していくというゲームがあり、CPU を自作するステージまでクリアし、自作アセンブリを作るところまで到達しました。 最近この 2 つを通して、実際にどうやって CPU を実装していくかについて大まかな流れを掴むことができました。
# 感想
この回答は自分自身の状況からしては大分書けている回答だったと思います。CPU 自作ゼミに応募しようとしていたのに、そもそものアセンブラや機械語について全然触れてきていませんでした。第一段落の Signetics2650 のプロセッサについての話はもともとニコニコ動画で遭遇した動画から派生した知識なので、本当にたまたまで、第2段落の話は大学の講義、「コンピュータとプログラミング」において、x86_64 プロセッサのアセンブリを触ったところから Signetics2650 の話につなげていったという感じです。
また、jacaranda-8 については課題を解いていく上で色々なものを調べていくうちにたどり着いたものでして、これに関しては、課題の意図である、そもそも今までどのようなプロセッサに触れてきたことがあるのかということについては本題からそれてしまうような回答にはなっていたとは思いますが、色々なことを調べていくうちに、CPU についての理解を大分深めることができたのでとても良い経験だったと思います。また、キャンプ中には jacaranda-8 の開発者の一人であるHeppoko yuki氏とも会うことができ、実際にテープアウトした jacaranda-8 を見せていただきました。本当に実物を見て感動しました。
# 問 3
# 問題
本講義では、基本的な RISC-V CPU コアを FPGA に実装して動作を確認したのち、余裕があれば受講者の実装したい機能を実装する時間を設ける予定です。 RISC-V CPU の内部、もしくは外部に実装してみたい機能・回路について説明してください。
# 回答
base64 エンコード・デコードをしてくれる命令セットを実装してみたいです。ハードウェアで実装する意義の一つとして、高速に動作させるというものがあると思うので大量に base64 でエンコード・デコードしたくなったときに使えたら面白いなと思います。 固定長のエンコーダは以下の様に実装できましたが、可変長のエンコーダは作れるのか気になります。
|
|
# 感想
この回答は自分としてもそこまで悪くない回答なのではないかと思います。ちなみに途中まで実装してみたところは大学の先輩であるところのlaplaさんから、具体的に課題の段階からある程度実装してみたら良いのではないかとアドバイスを受けたので、実際に実装してみたという経緯があります。また先輩からは、自分の回答の日本語の不適切なところや、ぎこちないところの添削をしてもらいました。本当に感謝しています。
# 問 4
# 問 4-a
# 問題
以下のポート定義を持つ、0 から 65535 の 範囲 ( 0 および 65535 を含む) の値を順に、下位ビット側からバイト単位で出力するモジュール mem_read
を作成してください。
値は 0 から 65535 の 16 ビットの範囲の値なので、出力するには 2 サイクル必要になります。例えば、 1022
は 16 進数で 0x03fe
なので、下位バイト 0xfe
を先に出力し、次のサイクルで上位バイト 0x03
を出力します。値を出力する 2 サイクルの間は out_valid
に 1
を出力して、値が有効であることを表してください。
また、値一つ分、つまり 2 バイト分を出力し終えた次のサイクルは 1 サイクルの間 out_valid
に 0
を出力して、値が無効とし、値の出力を行わないでください。
記述内容としては、付属のテストを使って Icarus Verilog でテストをパスすることを目標とします。 (どういう考え方をしたか評価しますので、テストをパスしない内容でもご応募いただければと思います) 逆にテストが足りないと思ったり、付属のテストベンチが使いにくいと思ったのであれば、追加していただいても構いません。
使用可能な SystemVerilog の言語機能は Icarus Verilog でシミュレーションが可能な範囲 (-g2012
オプションを指定して使える SystemVerilog 2012 のサブセット) とします。
また、記述したコードの動作について日本語で説明してください。
信号のタイミング例はテストベンチに同梱の mem_read/waveform.png を参考にしてください。
# 回答
まずは仕様をまとめました。
- 4 つのステートがある
- カウンタに値を加算するステート
- 無条件で
- out_valid を 0 にする
- カウンタを加算
- 下位ビットステートへ
- 無条件で
- 下位ビットを出力するステート
- カウンタが 65536 だったら
- out_valid を 0 にする
- 終了ステートへ
- カウンタが 65536 ではなかったら
- out_valid を 1 にする
- 下位ビットを出力する
- 上位ビットステートへ
- カウンタが 65536 だったら
- 上位ビットを出力するステート
- 無条件で
- out_valid を 1 にする
- 上位ビットを出力する
- 加算ステートへ
- 無条件で
- 終了ステート
- 無条件で
- out_valid を 0 にする
- 値を出力しない
- 無条件で
- カウンタに値を加算するステート
- リセットするとき下位ビット出力から実行されるように指定する
実装は以下のようになりました。
|
|
# 問 4-b
# 問題
4-A で作成したモジュールに、モジュールの出力を受け取れるかどうかを表す 1 ビットの信号 out_ready
を追加してください。
out_valid
かつ out_ready
が 1
のサイクルのみ、 out_data
の出力が更新されます。
ただし、4-A と違い out_valid
を 1
にするサイクルは 4 サイクルに 1 度にする必要はありません。
信号のタイミング例はテストベンチに同梱の mem_read_ready/waveform.png を参考にしてください。
# 回答
4-B の仕様は以下のようになりました。
- 4 つのステート
- カウンタに加算するステート
- 無条件
- 下位ビットへ
- out_valid 0
- カウンタを加算
- 無条件
- 下位ビットを出力するステート
- カウンタが 65536 だったら
- out_valid 0
- 終了ステートへ
- out_valid かつ out_ready が 1 のとき
- out_valid 1
- 下位ビットを出力
- 上位ビットへ
- カウンタが 65536 だったら
- 上位ビットを出力するステート
- out_valid かつ out_ready が 1 のとき
- 上位ビットを出力
- out_valid 1
- 加算ステートへ
- out_valid かつ out_ready が 1 のとき
- 終了するステート
- out_valid 0
- out_data 0
- カウンタに加算するステート
- リセットするとき下位ビット出力から実行されるように指定する
実装は以下のようになりました。
|
|
# 感想
このコードに至るまでは、なかなかの時間を要しました。サークルの OB の先輩から verilog の書き方のセオリーを教えていただき、その後自分で仕様を決めて実装していったところ最終的にこのコードになりました。適当に変化を加えてテストベンチを1から通していくよりも、一つ一つ丁寧に仕様を決めて実装したほうがコードもきれいになるし、時間もかからないということを身を持って実感しました。
この課題を解いたことで自分のコンピュータにおける設計についての考え方が随分変わったと思います。
ちなみにこの問題はキャンプ中での CPU からの入出力と、それと接続するためのプロトコルにおいてとても重要な役割を果たしていることがわかり、感動しました。
# まとめ
いかがだったでしょうか。独学なりにかなり頑張ったつもりです。また verilog のセオリーなどに関してはまた気が向いたらまとめてみようかと思います。(自分にそこまでの知識があるかはわからないですが w) このブログを読んで来年度以降の受講生に参考になればと思います。また、軽くなら相談も乗れると思いますので、気軽に連絡をくださると幸いです。
# 補遺
ちなみに、問 4-a で、確かテストベンチも通っていたと思うのですが、完全にセオリーとかを知らなかった状態での自分のコードを以下においておきます。verilog の仕様的に自動で回路が吐かれるところまでゲートレベルで実装しようとしていて、自分の中でもかなり初期の実装であることがわかります。wire の宣言なども一つ一つされており、かなり初期段階のコードだと思います。ただ、自分なりにかなり考えてやっていたのであえてこのコードも公開することにしました。
|
|