def sin(x)
x2 = x * x
x3 = x2 * x
x5 = x3 * x2
x7 = x5 * x2
x9 = x7 * x2
x - x3 / 6 + x5 / 120 - x7 / 5040 + x9 / 362880
end
xの値が0から離れるほど誤差が大きくなる

sinやcosは同じ波が繰り返されるので、xの値を-3.14から3.14の間に収まるよう調整
うまく実装できなかったので、1.0 を返してお茶を濁した...
let rec atan x = 1.0 in
# レイトレサーバ
# usage: ruby script/server.rb > contest.ppm
require 'rubyserial'
begin
serialport = Serial.new '/dev/cu.usbserial-D00084', 115200
# 不要なデータを読み捨て
while serialport.getbyte
# 何もしない
end
# 自作CPUに送信開始を依頼
serialport.write " "
# UART読み込み
while true
puts serialport.gets
$stderr.puts "receiving..."
end
rescue Interrupt
serialport.close
end


こんにちは。 今日は「自作CPUの上で例のレイトレを動かした話」をします。
まず自己紹介です。 はたけやまたかしと申します。 永和システムマネジメントという会社で、プログラマとして、RubyでWEBアプリケーションを書いて暮らしています。 また、ダジャレが好きなので、思いついたダジャレをTwitterへ放流したりしています。 こちらは「最近のお気に入りツイート」になります。
低レイヤプログラミングが趣味で、 CPUや、シミュレータや、コンパイラを自作して遊んでいます。
今日お話しすることです。 先日、RISC-V CPUを自作して、その上で東大CPU実験で有名な、例のレイトレーサーを動かしました。 今日は、自分もレイトレ動かしたい!と思っている人向けに、CPUの自作からレイトレまでの流れについてお話ししようと思います。 あと、レイトレを動かすまでに苦労したこともお話しできればと思います。
早速ですが、自作CPUの上でレイトレが動く様子をお見せしたいと思います。 レイトレを開始したら、終了を知らせる青いLEDが光るまで待機します。 (動画スタート) (青いLEDが光ったら) 今青いLEDが光ったのでレイトレ終了です。レイトレが出力した画像を確認します。 以上、レイトレでした。
次に、東大CPU実験について知らない方もいらっしゃると思うので、簡単にご説明します。 東大CPU実験とは、東大 理学部 情報科学科 の名物実験で、4人1組のグループに分かれて、CPUやコンパイラやシミュレータを自作し、どれだけ速いレイトレーサーが作れるかを目指す実験です。 CPU実験の体験記が毎年WEBに投稿されるので、いつも楽しみにしていて、 それらを読んでるうちに「自分でもやってみたい!」と思い、ひとりCPU実験を始めました。
CPU実験は、主に4つの係に分かれて作業を行います。 コア係は、FPGAボード上で動くCPUをVerilogなど実装します。 コンパイラ係は、MinCamlという言語のコンパイラを、自分たちのCPU向けに移植します。 シミュレータ係は、アセンブラの作成と、デバッグ用のシミュレータの作成を行います。 FPU係は、CPUに組み込む浮動小数点演算器(FPU)の実装と、浮動小数点計算を行うライブラリ関数の作成を行います。
そのうち、私が実際に行ったのは、 コア係のCPUの実装と、 コンパイラ係のMinCamlコンパイラの移植と、 FPU係のライブラリ関数の実装を行いました。 シミュレータは、既存のRISC-Vシミュレータを利用しましたし、 FPUは、github で見つけた既存のFPUコアを利用しました。
レイトレを動かす手順はこんな感じです。 (見てもらう) 説明の都合上、5つのステップに分けて説明するのですが、実際の手順はこんなに厳密に分かれてはおらず、各手順を行ったり来たりしながら開発を行うことになります。
まずはFPGAボードを用意します。私が使っているFPGAボードは「ULX3S」という、Lattice ECP5 を搭載したボードで、スペックはこんな感じです。 ちなみにこの ULX3S、お気に入りのFPGAボードなのですが、2021年に購入当時は 1万8000円だったお値段が、2025年現在は 3万8000円に値上がりしていて、気軽にオススメしづらくなってしまいました...
みなさんのお手持ちのFPGAボードでレイトレが動くかどうか気になってるかと思いますが、こちらの条件を満たせば、レイトレを動かせると思います。 まず、LUTが12K程度必要です。この12Kという数字は、私が作成したCPUのLUT数なので、CPUをうまいこと作ればもう少し圧縮できるかもしれません。 次にSDRAMが必要です。スタックとヒープ合わせて5MBが必要になるので、SDRAMなどの大容量メモリが必要になります。 最後に、これは必須ではないのですが、ブロックRAMが 250KB 程度あると、ブート領域にレイトレ全体がすっぽりと収まるのでおすすめです。 レイトレがブート領域に収まらない場合、ローダーなどが必要になります。 この条件だと、Tang Nano 9Kがダメで、Tang Primer 20Kだといけるかなあ? というところです。
FPGAボードを用意したら、次はCPUを作成します。
今回作成したCPUのアーキテクチャは、32ビットRISC-Vです。整数演算と単精度浮動小数点演算ができます。また、コンパクト命令には対応しませんでした。 浮動小数点演算に対応しているのは、レイトレを動かすのに必要だからです。浮動小数点の精度は、単精度にするか倍精度にするか悩んだのですが、単精度で大丈夫でした。 また、バスインターフェースは PicoRV32 の Native Memory Interface を採用しました。 周辺機器としてUARTを備えていて、メモリマップトIOでアクセスします。UARTは、レイトレの結果をパソコンへ送ったりするのに利用します。
言語は System Verilog で開発しました。オープンソースな開発ツール、yosys と nextpnr を利用してビルドしています。 これらは、インストールが容易で、macOS ネイティブでも動作するし、ビルドも早いので気に入っています。
最近はCPUの作り方に関しての本がたくさん出ているので、それらの本を読めば、もうCPUは作れちゃいます! なので、お好きな本でチャレンジしてみてください、という気持ちなのですが、 一応私が参考にした書籍も紹介しておきます。 CPU作成の基本に関しては、ディジタル回路設計とコンピュータアーキテクチャ RISC-Vの仕様については、RISC-V原典 System Verilogの書き方については、FPGA Prototyping by SystemVerilog Examples が参考になりました。
自作CPUに実装した命令はこちらになります。ご参考までに。
次はMinCamlコンパイラの移植についてです。
MinCamlは教育用のコンパイラで、東大のコンパイラの授業などで使われています。 MinCamlはOCamlという言語のサブセットです。 また、MinCamlコンパイラ自身もOCamlで書かれています。
CPU実験のレイトレーサーはMinCamlで書かれているので、MinCamlコンパイラを自作CPUへ移植することで、レイトレーサーを動かすことができます。 ちなみに、レイトレーサーのソースコードはMinCamlリポジトリの中に含まれています。
オリジナルのMinCamlが対応しているCPUアーキテクチャは、 UltraSPARC、PowerPC、32ビットのx86の三種類で、 MinCamlコンパイラのビルド時に、出力したいCPUアーキテクチャを指定することで、出力するアセンブリコードを切り替えることができます。
CPUアーキテクチャに依存したコードは、各CPUアーキテクチャ用のディレクトリの下に配置されます。 例えば、「PowerPC/emit.ml」には、PowerPC のアセンブリコード出力ロジックがまとまっています。
MinCamlの、RISC-Vへの移植の流れはこんな感じです。 (1) まず PowerPC ディレクトリを複製して、RV32 ディレクトリを作成します このままだと、PowerPC のアセンブリコードが出力されてしまうので、RISC-V のアセンブリコードを出力できるよう、手を入れていきます。 (2) 次に、asm.ml のレジスタ一覧を、PowerPC のレジスタ一覧から、RISC-Vのレジスタ一覧に修正します (3) 次に、emit.ml の中に埋まっている PowerPC のアセンブリ出力ロジックを、RISC-Vのアセンブリを出力するように修正します (4) 次に、PowerPC アセンブリで書かれたライブラリ関数 libmincaml.S を、RISC-V アセンブリで書き直します。 MinCamlの移植はこんな流れで行います。
次に、MinCamlの開発環境についてです。 MinCamlの開発には、OCamlとRISC-V GNUツールチェインが必要になります。 OCamlを使ってMinCamlコンパイラを改造し、 改造したMinCamlコンパイラが出力したRISC-Vのアセンブリを、GNUツールチェインの「アセンブラ」や「リンカ」を使ってビルドし、 GNU ツールチェイン付属の Spike シミュレータで動作を確認します。
MinCamlコンパイラを移植しようとした時に、一番最初に直面する壁が OCaml でした。 私はMinCamlを移植するためにOCamlをさわり始めたのですが、文法やモジュールシステムが独特で、とっつきにくかったです。また、関数型言語の流儀や、再帰を多用したプログラミングスタイルに慣れていないのもあって、OCamlの習得には苦労しました。 何冊かOCamlの本を読みましたが、一番のオススメは「プログラミングの基礎」、通称「浅井本」です。 この本は、OCamlの基礎を学びつつ、関数型プログラミングの基礎を学ぶことができる本で、この本の練習問題をこなしているうちにMinCamlのソースが読めるようになりました。
また、OCamlはやはりちょっと...という人は、Rust で書かれたMinCamlコンパイラがあって、昨年のCPU実験から使用可能になったらしいので、こちらを試してみるのも良いかもしれません。
次は、ライブラリ関数の実装です。
MinCamlの移植が終わったら、次はレイトレから呼び出されるライブラリ関数の実装を行います。 実装が必要なライブラリ関数は、 入出力系の関数 5つ、 三角関数 3つ、 平方根 1つ、 浮動小数点数変換系の関数 4つの 合計13個の関数になります。
平方根を求める sqrt 関数は「Babylonian method」と呼ばれるアルゴリズムを使うと簡単に実装できて良かったです。 こちらはRubyで書いたサンプルコードです。
三角関数 sin/cos/atan はマクローリン展開を使って計算することができます。
例えば sin 関数はこんな感じです。
マクローリン展開で求めたsinやcosは、xの値が0から離れるほど誤差が大きくなります
atan関数はうまく実装できなかったのですが、実際には呼ばれてなさそうだったので、適当な値を返してお茶を濁しました... レイトレもちゃんと動いていたので、まあ大丈夫かなあ...
ライブラリ関数の実装については、こちらの資料が非常に参考になったので、ぜひご参照ください。
最後に、レイトレの組み込みです。
MinCamlで書かれたプログラムを、自作CPUへ組み込む流れは、こんな感じです。 * MinCamlのプログラムをコンパイルして、アセンブリファイルを生成し、 * アセンブリファイルをアセンブルして、オブジェクトファイルを生成し、 * オブジェクトファイルをリンカにかけて、ELFフォーマットの実行ファイルを生成し、 * objcopy コマンドを使って、実行ファイルから、プログラムとデータを抽出して、HEXファイルとして保存し、 * 最後に、Verilog の $readmemh 関数を使って、FPGAの ブロックRAM を HEX ファイルで初期化します CPUを起動すると、ブロックRAMに書き込まれたプログラムが読み込まれ、実行されます。
次はレイトレサーバです。 レイトレを実行すると、計算結果がUARTから流れてくるので、ちょっとしたスクリプトを使って受信したデータをファイルへ出力しています。
レイトレを組み込み、パソコンからレイトレサーバーを起動して、これでようやくレイトレが動きます
もっと話したいことがあったのですが、資料に入りきらなかったので、より詳細が知りたい人はこちらをご参照ください