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秒ごとに点いたり消えたりしました。
