つくったもの

mrubyのバイトコードをネイティブ実行するCPUをFPGAで作っていてLチカまできた

FPGAの上に、mrubyのバイトコード(.mrb)を機械語としてそのまま実行するCPUをつくっています。そのCPUが、実機でLEDを点滅させました。

目指しているのは、R2P2と同じ使い心地の、mrubyネイティブのマイコンボードです。電源を入れるとshellが上がり、mrubyソースコードでピンを操る、というものです。

R2P2-dev-harnessのPR #24で進めています。

手探りもいいところで理解自体も機能もこれからではありますが、まずは一区切りとして記事にしています。

つかったもの

  • PERIDOT-Air(Cyclone IV E EP4CE6E22C8N、6,272 LE)
  • USB-Blaster互換のダウンロードケーブルと、ジャンパー線6本
  • Quartus Prime Lite 23.1std.1(Apple containerのx86-64 Ubuntu 22.04とRosetta)
  • openFPGALoader 1.1.1
  • Verilator 5.052
  • PicoRubyのmrbc、mrubyのmrblib、picoruby-gpio
  • MacBook Pro(M1 Max)
  • Claude Code

いまの状態

Lチカ1本ぶんの範囲が、実機で動いています。

led = GPIO.new(28, GPIO::OUT)
loop do
  led.write(1)
  sleep_ms 500
  led.write(0)
  sleep_ms 500
end

PicoRubyのLチカそのままです。GPIOのピン28を、板のUSER_LED0に割り当てています。

hostのmrbcにかけると、217バイトの.mrbになります。中身はirepが2つです。

irep 0 (トップレベル)
  000 GETCONST  R2  GPIO
  003 LOADI8    R3  28
  006 GETCONST  R4  GPIO
  009 GETMCNST  R4  (R4)::OUT
  012 SEND      R2  :new  n=2
  016 MOVE      R1  R2          ; R1:led
  019 BLOCK     R3  I[0]
  022 SSENDB    R2  :loop  n=0
  026 RETURN    R2
  028 STOP

irep 1 (loopに渡すブロック)
  000 ENTER     0:0:0:0:0:0:0:0
  004 GETUPVAR  R2  1  0
  008 LOADI_1   R3  (1)
  010 SEND      R2  :write  n=1
  014 LOADI16   R3  500
  018 SSEND     R2  :sleep_ms  n=1
  022 GETUPVAR  R2  1  0
  026 LOADI_0   R3  (0)
  028 SEND      R2  :write  n=1
  032 LOADI16   R3  500
  036 SSEND     R2  :sleep_ms  n=1
  040 RETURN    R2

CPUはこの命令の並びをROMから読んで、そのまま実行します。

ROMに入れたもの

mrubyソースコード 出どころ .mrbの大きさ
blink.rb 自分で書いたもの 217バイト
kernel.rb mrubyのmrblib 599バイト
gpio.rb picoruby-gpioのmrblib 2,663バイト

kernel.rbとgpio.rbには手を入れていません。

loopは、kernel.rbにあるKernel#loopの定義をCPUが起動のときに実行して、メソッドとして登録したものです。GPIO.new(28, GPIO::OUT)は、gpio.rbのinitializeからsetmode、set_dirへと、バイトコードのまま辿ります。

シミュレーションで合わせる

実機の前に、Verilatorで回路を走らせて、ピンの変化の列を取りました。

同じblink.rbをhostのPicoRubyでも走らせて、2,000msまでの6回の変化が、同じ時刻と同じ値になることを確かめています。

FPGAに書き込む

書いた回路をFPGAに書き込めるデータへ変換する作業を、合成といいます。PERIDOT-AirのFPGAでは、メーカーのAlteraが配っているQuartus Prime Liteというソフトで合成します。

QuartusはWindows用とLinux用が配られていて、Mac用はありません。
わたしの手元はMacBook Pro(M1 Max)なので、Macの中でLinuxを動かし、その中でQuartusを動かしました。

Macの中でLinuxを動かすのには、Appleが出しているcontainerというツールを使っています。QuartusはIntel系のCPU向けのソフトで、M1 MaxはCPUの種類が違うので、Rosettaを通しています。

項目 値
ロジックエレメント 4,792 / 6,272(76%)
レジスタ 1,959
メモリ 49,088 bit
50MHzのsetup slack 3.539 ns

yosysでの事前の見積もりは3,994 LEでした。Quartusの結果は、それより798 LE多く出ました。

JTAGがつながらない

USB-Blaster互換のケーブルを、同梱のJTAG-CONN変換基板と10ピンのリボンケーブルでつなぐと、openFPGALoader --detectが返すIDCODEが全部0でした。Macを替えても同じでした。

切り分けは2段でやりました。

まず、USB-Blasterの10ピンの口でTDOとTDIをジャンパー線でつなぎ、0以外が読み戻せることを見ました。USB-Blasterの口は生きています。

次に、変換基板とリボンケーブルを通さずに、USB-Blasterから板へジャンパー線6本で直結しました。

USB-Blasterの10ピン      PERIDOT-Air
  3番 TDO  ───────────  JTAGの6ピンの1番 TDO
  1番 TCK  ───────────  JTAGの6ピンの3番 TCK
  5番 TMS  ───────────  JTAGの6ピンの5番 TMS
  9番 TDI  ───────────  JTAGの6ピンの4番 TDI
  4番 VCC  ───────────  電源のピンソケットの3.3V
  2番 GND  ───────────  電源のピンソケットのGND

これで応答を得ることができました。変換基板なのかリボンケーブルなのか、挿し方の問題かは切り分けできていません。

点滅

rake fpga:flashで書き込むと、USER_LED0が0.5秒ごとに点いたり消えたりしました。

参考にしたもの