SwiftでプログラミングできるハードウェアをFPGAで自作してみた

>100 Views

September 13, 26

スライド概要

iOSDC Japan 2026 LTの登壇資料です。
https://fortee.jp/iosdc-japan-2026/proposal/0688a16a-b469-4f00-9ddd-d8c14d7ff383

コード置き場
https://github.com/bricklife/embedded-swift-tangnano9k-console

profile-image

SwiftとLEGOとBluetooth LEが好きなプログラマ

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

ダウンロード

関連スライド

各ページのテキスト
1.

Swiftでプログラミングできる ハードウェアをFPGAで自作して みた 大庭 慎一郎 Shinichiro Oba @ooba / @bricklife

2.

↑↑↑を見てEmbedded Swiftにハマった結果↓↓↓

3.

https://www.youtube.com/watch?v=0OcOFBe0jbQ https://www.youtube.com/watch?v=mrEC1WIdW-A

4.

マイコン系のMMIOは ボタンを増やしたい 音鳴らすの大変すぎ 汎用的すぎて面倒 画面解像度を大きく 画面モードとか不要 したい CPUをオープンな RISC-Vにしたい なるべくアセンブリ 言語を使いたくない リフレッシュレート を変えたい

5.

沸き起こる衝動…

6.

ハードウェアも自分で 設計したい!

7.

ハードウェアも自分で設計して、 それをSwiftで動かしたい!

8.

これ作るの無理では…? 写真は全て https://www.ifixit.com/ より

9.

FPGA Field Programmable Gate Array

10.

コンピュータのコアはデジタル回路 オーディオ RAM CDデコーダー SPU ROM CDデジタル信号プロセッサ この辺がデジタル回路 D/Aコンバーター ビデオエンコーダー VRAM GPU CPU DRAM https://jp.ifixit.com/Teardown/Sony+PlayStation%E3%81%AE%E5%88%86%E8%A7%A3/128089 より抜粋

12.

FPGA

13.
[beta]
ハードウェア記述言語

Hardware Description Language (HDL)

Verilog
SystemVerilog
VHDL

// Toggle q after every CYCLES clocks of clk.
// Example: CYCLES = 13_500_000 at 27 MHz → q flips every 0.5 s.
module clk_toggle #(
parameter CYCLES = 13_500_000
) (
input wire clk,
input wire rst_n, // active-low
output reg q
);
localparam integer W = $clog2(CYCLES);
reg [W-1:0] cnt;

Chisel
Veryl
…

always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= {W{1'b0}};
q
<= 1'b0;
end else if (cnt == CYCLES - 1) begin
cnt <= {W{1'b0}};
q
<= ~q;
end else begin
cnt <= cnt + 1'b1;
end
end
endmodule

14.
[beta]
VerilogによるLED点滅回路
// Two-LED blinker for Tang Nano 9K.
// LED0 and LED1 are opposite (on-board LEDs are active-low).

// Toggle q after every CYCLES clocks of clk.
// Example: CYCLES = 13_500_000 at 27 MHz → q flips every 0.5 s.

module blink_leds (
input wire
clk_osc, // 27 MHz
input wire
btn,
// S2, active-low reset
output wire [5:0] led
);

module clk_toggle #(
parameter CYCLES = 13_500_000
) (
input wire clk,
input wire rst_n, // active-low
output reg q
);

wire q;
// 27 MHz × 13_500_000 = 0.5 s per edge → 1 Hz blink.
clk_toggle #(
.CYCLES(13_500_000)
) u_toggle (
.clk (clk_osc),
.rst_n(btn),
.q
(q)
);
assign led[0] = ~q;
// on when q = 1
assign led[1] = q;
// on when q = 0
assign led[5:2] = 4'b1111; // off
endmodule

localparam integer W = $clog2(CYCLES);
reg [W-1:0] cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= {W{1'b0}};
q
<= 1'b0;
end else if (cnt == CYCLES - 1) begin
cnt <= {W{1'b0}};
q
<= ~q;
end else begin
cnt <= cnt + 1'b1;
end
end
endmodule

15.

AIコーディングエージェントが 使える!

16.

自作携帯ゲーム機 CPU:32-bit RISC-V 15MHz(RV32IMAC) 画面:480x272@50Hz(RGB565) ボタン:10個 スプライト:最大16枚 1ライン4枚 音声:1ch 矩形波音階 デバッグ機能:JTAG、UART、LED×6 バッテリー未実装

17.

中身 LCDパネル Tang Nano 9K FPGA: GW1NR-9 LUT4: 8640 値段: 秋月で3,780円(税込) ボタン×10 圧電スピーカー

18.

全体構成 Tang Nano 9K LCDコントローラ LCD SRAM プログラム スタック / ヒープ スプライト/ 音 CPU Hazard3 LED×6 ボタン×10 PSRAM ビットマップ用VRAM SDカードス ロット スピーカー APB USB-UART

19.

MMIOの設計 Tang Nano 9K アドレス 用途 0x0000_0000 ブートローダー 0x0000_2000 アプリ SRAM 0x4000_0000 タイマ プログラム LCDコントローラ LCD スタック / ヒープ 0x4000_1000 CPU Hazard3 LCD 制御(vcount / enable) スプライト/ 音 LED×6 0x4000_2000 スプライト CTRL(ENABLE / STAT / INFO) 0x4000_4000 UART 0x4000_8000 ボタン×10 PSRAM GPIO 出力(LED[5:0]、SDカードSPI) SDカードス ビットマップ用VRAM 0x4000_c000 GPIO 入力(btn_gpio[11:0]、SDカードSPI) ロット 0x5000_0000 OBJ パレット(RGB565×16×16) 0x5000_1000 OBJ タイル(64×32B @4bpp) APB スピーカー USB-UART 0x5000_4000 OAM(N=16 / 1ライン最大4) 0x6000_0000 VRAM ビットマップ(480x272 RGB565)

20.

Embedded Swiftのサポートターゲット % cd ~/Library/Developer/Toolchains/swift-6.3-RELEASE.xctoolchain/usr/lib/swift/embedded/Swift.swiftmodule/ % ls *.swiftmodule aarch64-none-none-elf.swiftmodule armv6-none-none-eabi.swiftmodule i686-unknown-none-elf.swiftmodule arm64-apple-ios.swiftmodule armv6m-apple-none-macho.swiftmodule riscv32-none-none-eabi.swiftmodule arm64-apple-macos.swiftmodule armv6m-none-none-eabi.swiftmodule riscv64-none-none-eabi.swiftmodule arm64-apple-none-macho.swiftmodule armv7-apple-none-macho.swiftmodule wasm32-unknown-none-wasm.swiftmodule arm64e-apple-ios.swiftmodule armv7-none-none-eabi.swiftmodule wasm64-unknown-none-wasm.swiftmodule arm64e-apple-macos.swiftmodule armv7em-apple-none-macho.swiftmodule x86_64-apple-macos.swiftmodule arm64e-apple-none-macho.swiftmodule armv7em-none-none-eabi.swiftmodule x86_64-unknown-none-elf.swiftmodule armv4t-none-none-eabi.swiftmodule armv7m-apple-none-macho.swiftmodule armv6-apple-none-macho.swiftmodule avr-none-none-elf.swiftmodule armv4t-none-none-eabi: GBA (ARM7TDMI) armv6m-none-none-eabi: Raspberry Pi Pico (RP2040) armv7em-none-none-eabi: Playdate (STM32F746) riscv32-none-none-eabi: ESP32-C6 (RISC-V RV32IMAC)

21.
[beta]
Embedded Swiftによるプログラミング例
import _Volatile

let LCD_W = 480
let LCD_H = 272

let VCOUNT = VolatileMappedRegister<UInt32>(unsafeBitPattern: 0x4000_1000)
let LED
= VolatileMappedRegister<UInt32>(unsafeBitPattern: 0x4000_8000)
let KEY
= VolatileMappedRegister<UInt32>(unsafeBitPattern: 0x4000_c000)

@main
struct GameMain {
static func waitForVsync() {
while VCOUNT.load() >= UInt32(LCD_H) {}
while VCOUNT.load() < UInt32(LCD_H) {}
}

struct Key: OptionSet {
let rawValue: UInt32
static let left
static let up
static let down
static let right
static let select
static let start
static let y
static let x
static let b
static let a

= Self(rawValue: 1 << 0)
= Self(rawValue: 1 << 1)
= Self(rawValue: 1 << 2)
= Self(rawValue: 1 << 3)
= Self(rawValue: 1 << 4)
= Self(rawValue: 1 << 5)
= Self(rawValue: 1 << 6)
= Self(rawValue: 1 << 7)
= Self(rawValue: 1 << 8)
= Self(rawValue: 1 << 9)

static func poll() -> Self {
return Self(rawValue: KEY.load() & 0x03ff)
}
var isPressingAny: Bool {
rawValue != 0
}
}

static func main() {
while true {
waitForVsync()
let key = Key.poll()
if key.contains(.a) {
LED.store(0x07)
} else if key.contains(.b) {
LED.store(0x28)
} else {
LED.store(0x00)
}
}
}
}

22.

AIに少しずつ機能を実装してもらう 1. VRAM上のビットマップ(RGB565) をラインバッファを介してLCDに表示 7. プログラムをSDカードやシリアル ポートから読み込み(H/W実装) マップをLCDに表示 8. 回路規模縮小のためにプログラム読み 込みをSwift製ブートローダに変更 3. GPIO出力(LED点滅) 9. パレット(16バンク×16色 RGB565) 4. GPIO入力(ボタン入力) 10.スプライト(最大16枚 1ライン4枚) 5. タイマー 11.音声(1ch 矩形波音階のみ) 2. CPUからVRAMに書き込んだビット 6. LCDのオンオフや水平同期

23.

画面描画のデバッグが大変すぎた…

24.

ソフトウェアのデバッグはJTAGをAIが操作

25.

画面描画のデバッグもAIにお願いできたかも LCDのタイミングチャートとFPGA内部ロジックアナライザ

27.

また少しコンピュータと 仲良くなれた気がする

28.

ありがとうございました プロジェクトのリポジトリ https://github.com/bricklife/embedded-swift-tangnano9k-console リポジトリ 気軽に話し かけてね