第11章 ソフトの足場 ― 「使えるボード」にする土台(Arduino / MicroPython)
ここまでで、私たちのSoCは五感と運動のハードの骨格を備えました。けれど、それだけでは「使えるボード」とは言えません。毎回バイナリを手で焼き、レジスタ番地を覚えて叩くのでは、作った本人しか動かせないからです。本章は、その最後の一歩――誰もが、慣れた書き方で、このボードをプログラムできるようにするソフトの土台を据えます。具体的には、RISC-V GCCツールチェーン、Arduinoコア(おなじみのAPIを我々のMMIOに結ぶ薄い層)、UARTブートローダ、そしてMicroPython/CircuitPythonへの道筋です。
11.1 なぜ「ソフトの足場」が要るのか ― 民主化の最後の一歩
本講座の旗印は「半導体設計の民主化」でした。自分でCPUと周辺を設計できるようになったのは大きな一歩ですが、それを"普通の人が普通に使える"ところまで運ぶのがソフトの役目です。Arduinoが世界中で使われたのは、性能ではなく「5行でLチカできる」敷居の低さでした。私たちの自作SoCにも、その敷居の低さを与えます。ハードの自由(自分で決めた周辺)×ソフトの親しみ(標準的な書き方)――この掛け算が、民主化を実用に変えます。
11.2 三層で考える ― ユーザのコードから、ハードまで
ソフトの足場は、層で捉えると見通しが良くなります。上から順に、こうです。
ソフトの足場(上=親しみやすい/下=ハードに近い)
───────────────────────────────────────────────────────────────
① ユーザのコード digitalWrite(13, HIGH); Serial.print("hi"); (Arduino)
machine.Pin(13).on() (MicroPython)
↓
② 言語コア/ランタイム Arduinoコア or MicroPython =「APIを番地アクセスに翻訳する薄い層」
↓
③ MMIOレジスタ 0x1 LED / 0x2 GPIO / 0x4 PWM / 0x6 UART …(第3〜6章で設計)
↓
④ Wishbone → 周辺RTL 自作のWishboneスレーブ(第3章の型)
↓
⑤ FPGA(Basys 3)
───────────────────────────────────────────────────────────────
※ ①②はソフト、③④⑤はこれまで作ってきたハード。橋は「番地への読み書き」。
注目すべきは、②と③の境目がとても薄いことです。私たちは自分でMMIOマップを設計したので(第3章)、「APIを番地アクセスに翻訳する」だけで層がつながります。ここが、市販マイコンの分厚いペリフェラル・ライブラリと違う、自作SoCならではの透明さです。
11.3 土台その1 ― RISC-V GCCツールチェーン(調達して使う)
最下層の言語処理系は、自作しません。RISC-V用のGCCを調達して使います。専用ビルド工程(M1〜M5)で、すでに私たちはこれを使っていました。C言語のソースを、自作CPU(RV32I)が実行できる形へ変換する流れは、こうです。
C → 自作CPUで動く形 への変換(M1〜M5で実証済みの流れ)
───────────────────────────────────────────────────────────────
main.c ─[ riscv-gcc -march=rv32i -mabi=ilp32 ]→ prog.elf
prog.elf ─[ objcopy -O binary ]→ prog.bin ─[ 変換 ]→ Flashイメージ(hex)
↑ link.ld:コードを 0x5000_0000(外部Flash・XIP)に配置
↑ start.S:リセット後にスタックを設定し main へ跳ぶ
───────────────────────────────────────────────────────────────
※ ツールチェーンの導入手順・版は環境で変わる。最新は公式で確認(断定回避)。
ここで効くのが、第4章で固めたXIP(外部Flashからその場実行)と、リセットベクタ 0x5000_0000 です。リンカスクリプト(link.ld)がコードをFlash領域に置き、起動コード(start.S)がスタックを整えて main へ跳ぶ。この小さな"足場の足場"があって初めて、CのプログラムがCPUの上で走ります。
11.4 土台その2 ― Arduinoコア(APIをMMIOに結ぶ"薄い層")
次に、Arduinoの書き方を載せます。やることは「Arduinoのよく使うAPIを、我々のMMIOレジスタへの読み書きに翻訳する」こと。たとえば digitalWrite は、GPIOレジスタ(0x2)の1ビットを立てる/落とすだけです。
// Arduinoコアの芯:digitalWrite を、我々のGPIOレジスタ(0x2)への書き込みに翻訳する(概念・骨格)
#define GPIO_OUT (*(volatile unsigned int *)0x20000000)
void digitalWrite(unsigned pin, unsigned value) {
unsigned bit = 1u << pin; // Arduinoのピン番号 → GPIOのビット位置
if (value) GPIO_OUT = GPIO_OUT | bit; // HIGH:そのビットを立てる
else GPIO_OUT = GPIO_OUT & ~bit; // LOW :そのビットを落とす
}
// 同様に:analogWrite→PWM(0x4)、Serial.print→UART(0x6)、digitalRead→GPIO入力 …
あとは、Arduinoのピン番号と我々のGPIOビットの対応表(pins_arduino.h 相当)と、Arduino IDE/CLIに「このボードは何者か」を伝えるboards.txt(マイコン種別・クロック・書き込み方法など)を用意します。
boards.txt が伝えること(概念・キーは一例) ─────────────────────────────────────────────────────────────── ボード名 / アーキ=riscv32 / クロック=100MHz / 書き込み=UARTブートローダ コンパイルフラグ= -march=rv32i -mabi=ilp32 …(M1〜M5と同じ系統) ─────────────────────────────────────────────────────────────── ※ これで Arduino 環境が「我々のボード」を選べるようになる。
※"薄い"のは偶然ではない(著者の整理)。 このコアが薄く書けるのは、周辺の番地と作法を自分で決めたから(第3章)。市販マイコンの分厚いHALと違い、API1つ=レジスタ1アクセスに落ちる。これは第3章「番地への読み書き=周辺の操作(MMIO)」の、ソフト側からの回収です。
11.5 土台その3 ― UARTブートローダ(毎回のJTAGを卒業する)
ここまでだと、新しいプログラムを試すたびに書き込み機(JTAG等)でFlashを焼き直す必要があります。ArduinoのようにUSB(UART)経由でサッと書き込みたい――それを叶えるのがブートローダです。考え方はこうです。
UARTブートローダの流れ(概念)
───────────────────────────────────────────────────────────────
リセット → 小さな常駐プログラムが起動 → UART(0x6)を一定時間待つ
├─ 新プログラムが来た:受信して 置き場に書く → そこへジャンプ
└─ 来なければ:既存のユーザプログラムへジャンプ(通常起動)
───────────────────────────────────────────────────────────────
置き場の選択(設計判断):
・Flashに書く(不揮発・本格)→ SPI Flashの“書き込み”経路が要る(XIPは読み専)
・RAMにロードして実行(簡単・揮発)→ まず動かすのに向く。ただし外部RAMが要る規模も
※ブートローダは"鶏と卵"に注意(断定回避)。 ブートローダ自身は、ユーザプログラムより先に確実に存在していなければなりません(保護したFlash領域や、起動時ROMに置くなど)。またFlashへ書き込むには、第4章で使った spimemio の読み(XIP)だけでなく書き込みコマンドの経路が要ります。まずRAMロード型で仕組みを確かめ、Flash書き込み型は段階的に――というのが安全な順序、と著者は考えます([改訂マーカー:ブートローダ方式・Flash書込経路])。
11.6 高みへ ― MicroPython / CircuitPython(外部RAM前提)
最後に、もっと親しみやすいPythonでボードを動かす道を見据えます。MicroPython/CircuitPythonは、組込み向けに作られたPythonの実装で、REPL(対話実行)や machine.Pin のような分かりやすいAPIを持ちます。ただし、ここには第4章で見送った外部RAMという前提が、はっきり立ちはだかります。
移植の道筋そのものは、これまでの積み重ねで見通せます。RISC-V向けのベアメタル移植を土台に、我々の周辺ドライバ(machine.Pin→GPIO(0x2)、PWM→0x4、UART(0x6)でREPL …)を差し込み、外部RAMをヒープに割り当てる――という構成です。本章ではこれを「道筋」として正直に示し、完成を約束はしません(規模が大きく、外部RAM・検証が前提のため)。
発展:Arduino と MicroPython、どちらを先に?
敷居と前提が違います。Arduinoコアは内蔵RAMの小ささでも成立しやすく(コンパイル済みのCが走るだけ)、まず取りに行く価値があります。一方MicroPythonは外部RAMが実質前提で、ハード(外部RAM)が整ってからの大仕事。だから本講座は「Arduinoコア+UARTブートローダで"使えるボード"を先に成立させ、MicroPythonは外部RAMの段で」という順序を基本線にします。順番も設計判断であり、用途で変わります。
11.7 なぜ"薄い層"で済むのか ― 第3章MMIOの回収
本章のコアもブートローダも、驚くほど薄く書けました。その理由を一言で言えば、「周辺の番地と作法を、自分で設計したから」です。第3章で「番地への読み書き=周辺の操作(MMIO)」という型を作り、第5〜6章で通信を、第8〜10章で音声・モータ・映像をその型に乗せてきました。だから上のソフトは、どの機能も"ある番地を読み書きするだけ"に帰着します。ハードの設計が、そのままソフトの設計図になっている――自作SoCの一番おいしいところです。
11.8 自作/調達の判断 ― どこを作り、どこを借りるか
ソフトの足場:自作 / 調達 の分担
───────────────────────────────────────────────────────────────
調達(既存を使う) ・RISC-V GCCツールチェーン
・MicroPython/CircuitPython 本体(移植のベース)
自作(我々固有) ・Arduinoコアの薄い層(API→我々のMMIO)
・pins_arduino / boards.txt(我々のピン・ボード定義)
・UARTブートローダ(我々のFlash/UARTに合わせた小プログラム)
移植(借りて合わせる)・MicroPythonの周辺ドライバ(machine.* → 我々のMMIO)
───────────────────────────────────────────────────────────────
原則:言語処理系・ランタイムは調達、"我々固有の対応づけ"は自作。
11.9 【確認】この章の最小確認
下から上へ、足場を一段ずつ確かめます。
- ベアメタル(実証済み)。 riscv-gcc でCをビルドし、XIPで起動してLチカ(M1〜M5で達成済みの土台)。
- Arduinoコア(FPGA)。
digitalWrite/Serial相当を我々のMMIOへ翻訳する薄い層を入れ、Arduino流のスケッチでLチカ+シリアル出力。 - UART書き込み(FPGA)。 ブートローダ経由で、JTAGなしにUARTから新プログラムを書き込んで実行(まずRAMロード型から)。
- Python(外部RAM後)。 外部RAMを実装した段で、MicroPythonのREPLをUARTに出し、
machine.PinでLチカ。ここは外部RAMが前提。
11.10 この章のまとめ
- ソフトは民主化の最後の一歩: RTLがあるだけでは"使えるボード"でない。慣れた書き方で動かせて初めて、誰もが使える。
- 三層で見通す: ユーザのコード → 言語コア(薄い層) → MMIO → 周辺RTL → FPGA。橋は「番地への読み書き」。
- ツールチェーンは調達: RISC-V GCCでC→ELF→Flashイメージ。XIPとリセットベクタ(第4章)が足場の足場。
- Arduinoコアは薄い自作層: API1つ=レジスタ1アクセス。pins_arduino/boards.txtで環境に"我々のボード"を教える。
- UARTブートローダで卒業: 毎回のJTAGをやめ、UARTで書き込む。まずRAMロード型から(断定回避)。
- MicroPythonは外部RAM前提: Pythonはメモリを食う。第4章で見送った外部RAMの回収とセット。道筋は示すが完成は約束しない。
これで、設計した自作SoCを"普通に使える"土台ができました。残るは、テープアウト(実チップ製造)に向けた最大の山――徹底検証です。一発勝負の製造の前に、シミュレーションで異常系まで網羅し、AIを検証に活かす。次章は本講座の集大成のひとつ、第12章:徹底検証 ― シミュレーションとAI活用です。