XLS32: AIエージェントだけで 作ったFPGAシンセサイザー
Google XLS(DSLX)で書かれた32音ポリフォニックシンセ。設計・実装・実機検証まで、
ループエンジニアリングによってAIコーディングエージェントが一貫して行いました。
アジェンダ
00 自己紹介 2分
01 XLS32とは 6分
02 技術スタック 16分
03 AIエージェントとループエンジニアリング 9分
04 アーキテクチャ詳解 8分
05 つまずきと学び 5分
06 まとめ + Q&A 4分
A 付録 — ECP5への移植 質問があれば
00
自己紹介
まずは30秒だけ自己紹介を。そして2012年に手作りしたFPGAシンセの話 —
この発表で作り直すのは、実はそれと同じ楽器です。
はじめまして
Kazunori Sato
Developer Advocate, Cloud AI — Google
AIを中心に、登壇・デモ・サンプルコードを書く仕事をしています
XLS32 は個人のサイドプロジェクト。Apache-2.0で公開しています
github.com/kazunori279/xls32-fpga-synth
ここでの発言は私個人のものであり、所属組織の見解ではありません。
私とFPGA: 2012年の試作
14年前、同じ楽器を手作りしていました。Altera DE0、手書きのVerilog、2週間で3本のブログ記事。
MIDIを手で解く 2012年12月15日
SparkFunのMIDIブレイクアウトをDE0へ。31.25kbpsの調歩同期シリアルを4倍オーバーサンプルする
ステートマシンをVerilogで書いて、まず 90h 3Ch 40h がLEDに出たのが最初の一歩。
8音ポリフォニー 2012年12月19日
サイン波のルックアップテーブルとノート番号→ステップ幅の変換表。8個のWaveGenがMIDIを
次段へ受け流してボイスを割り当てます。8bitで足すとクリップしたので、ミックスはDACの12bit側へ。
ブラウザのツマミ 2012年12月29日
ADSRはHDLのカウンタで、VCAはVerilog1行。FTDIケーブル経由921.6kbpsで、Chrome Packaged Appの
jQuery Knobのツマミが chrome.serial 越しに音を動かしていました。
やりたいことは14年前と同じ。2012年: サイン波8音、手書きVerilog、1機能におよそ週末1回 —
そこで止まりました。2026年: 32音・4パート、フィルタもエフェクトもあり、実機テスト130本以上 —
しかも私は1行も書いていません。
01
XLS32とは
まずは楽器そのものの紹介です。USB越しに実機を鳴らすブラウザパネル —
オシレーター、フィルター、エンベロープ、エフェクト、そして4パートのデモ曲。
XLS32とは?
32音・4パートマルチティンバーの減算合成シンセサイザー 。
100 MHzで動く文字どおりの回路が、1クロックごとに1オーディオサンプルを計算します。
手書きVerilogではなくGoogle XLS(DSLX) で記述
Basys 3 ボード上で動作(Xilinx Artix-7 xc7a35t)
USB経由でブラウザのアナログ風パネル からライブ演奏
MIDI入力、16ビットステレオ音声出力 — I2S DACとDIN MIDIにも対応
オープンソース: GitHubでApache-2.0
ボードをリモートで開発したため、あらゆる機能はUSB UART越しに確認 —
音声はホスト側でFFTとスペクトログラムによって検証しています。
USB越しにFPGAシンセをライブで鳴らすWeb UI
デモ: Web UIから実機を鳴らす
ブラウザUI 静的ページ — サーバー不要
アナログ風パネル — つまみはMIDI CCを送信。画面上の鍵盤、PCのキーボード、
Web MIDIコントローラーのいずれからでも演奏可能。シリアルポートの占有と音声ストリームの
バイト境界合わせもタブ自身が行う
▼ MIDIバイト列 Web Serial · USB UART · 2 Mbaud 16ビットステレオ ▲
Basys 3ボード シンセ回路そのもの
ビルド済みのfirmware/top.bitを書き込むだけ — FPGAが全サンプルを計算。
デモにツールチェーンは不要
一行たりとも手書きしていない 。
そしてすべての機能を機械が検証している
設計全体を作ったのはClaude Code(Opus 4.8 )。ビルド → 計測 → 修正という
自己検証ループを、ネットワーク越しに、誰もボードを見ていない状態で回しました。
しかもほぼすべての機能は1週間の旅行中にスマートフォンから開発 されています。
02
技術スタック
なぜシンセをFPGAファブリックに載せるのか — そしてそれを可能にする3つの技術:
ボード、言語、ツールチェーン。
減算合成シンセ入門
倍音の豊かな波形から始めて、削っていく。このスライドの各ブロックはすべてXLS32内に回路として存在します。
オシレーター
倍音の多い生の波形 を作る — ノコギリ波、矩形波、三角波、ノイズ。
デチューンとユニゾンで音を太くします。
フィルター
ここが「減算」 の部分。レゾナンス付きフィルターがスペクトルを削り、
カットオフを動かすと音色が変化します。
アンプ
ADSRエンベロープ (アタック・ディケイ・サステイン・リリース)が音量を形作る —
プラック、パッド、オルガンの違いはほぼこの曲線です。
エフェクト
コーラス・ディレイ・リバーブ が広がりと空間を足す —
単なる音と楽器を分ける差です。
動きを生むのはモジュレーション: エンベロープとLFOがピッチ、カットオフ、音量を揺らします。
パッチ とは各ブロックの設定値のこと — XLS32ではそのひとつひとつがMIDI CCです。
他の合成方式: サンプリング · ウェーブテーブル · FM ·
加算合成 · 物理モデリング · アナログモデリング(バーチャルアナログ)。XLS32は根っこは減算合成で、
クロスオシレーターモジュレーションによるFM風味も持っています。
数字で見るシンセ
スペック 備考
同時発音数32音の時分割多重 — procティック1回につき1ボイス(約24エンジンサイクル) 4パートマルチティンバー(MIDI ch 1–4)、動的に共有されるボイスプール
オシレーター1ボイスあたり2基 + サブオシレーター → 32ボイスで最大64基 5波形、PWM、クロスオシレーターのリング / FM / FM+(8種の比率)
フィルターボイスごとのレゾナンス付きステートバリアブル型 — LP / HP / BP / ノッチ キートラッキング + フィルターエンベロープ + LFOによるカットオフ変調
モジュレーションボイスごとにADSR×2、パートごとのLFO、ベンド、ポルタメント、ユニゾン すべてMIDI CCで制御 — Web UIの正体はCCの送信
エフェクトステレオコーラス、ピンポンディレイ、8コムのFreeverbリバーブ Verilogシェル側のブロックRAMディレイライン
サンプルレート32 kHz · 16ビットステレオPCMをUSB UARTで出力 Vivado / DSP48バックエンド時。オープンなバックエンドでは28 kHz
検証USB経由の実機E2Eテスト130本超、すべて採点付き FFT / スペクトログラムを0–100点で評価し、レポート動画を自動生成
シンセの作り方
オシレーターを実現する5つの方法 — 作りやすい順に並べています。
Web Audio
ブラウザの実験・教育用途 — Chrome Music Lab、オンラインデモ
+ インストール不要、どこでも動く、JS数行で書ける
− タイミングはベストエフォート: 10–40 msのバッファ、高負荷時に音切れ
ソフトシンセ
音楽制作の主流 — Serum、Vital、DAW付属音源
+ 機能が豊富、反復が速い、巨大なエコシステム
− OSとCPUを共有。レイテンシ=オーディオバッファ長
ディスクリートアナログ
MoogやEurorack — オペアンプ、VCO、本物の電圧
+ アナログの音、手触り、直接操作できる面白さ
− 電子回路を手組み。ドリフト、調律、1ボイスあたりのコストが高い
本プロジェクト
FPGA
ハイブリッド型ハードシンセ — Novation Peak、UDO Super 6
+ ハードウェアの決定性、ジッターなし、再構成可能
− HDLとタイミングクロージャ: 最も過酷な開発ループ(本セッションの標的)
ASIC
量産された名機チップ — ヤマハDX7のFM音源、C64のSID
+ 単価が最安、消費電力も最小、完全カスタム
− 数億円規模のNRE、1回転に数か月、テープアウト後はやり直し不可
XLS32が選んだのはFPGA — ASICのコストを払わずにハードウェアの保証を得る道 —
そしてその弱点である「開発ループ」に正面から取り組みます。
なぜWeb Audioではなく FPGA なのか
決定的で極小のレイテンシ
固定長パイプラインが毎サンプルを同じスケジュールで作ります。データパスの遅延は常に数マイクロ秒で一定 —
1ミリ秒未満、ジッターなし です。
Web Audioは10–40 msのOSバッファの上で128サンプル単位にレンダリングし、
UIやガベージコレクタとCPUを共有します。
ハードリアルタイムのデータパス
32ボイスが1本のパイプラインを時分割で通り、すべてが1サンプル周期内に完了。
1サンプルあたりの処理は必ず終わる固定サイクル予算 で、パッチの内容に左右されません。
CPUのタイミングはベストエフォートで、同時発音数と負荷が増えるほど悪化します。
ビット単位でカスタマイズできる
任意ビット幅の固定小数点、専用設計のオシレーター/フィルター構成、サンプル単位で正確な
モジュレーション経路。同じ設計がそのままDACやハードウェアMIDIを
OSやドライバを経由せずに 駆動します。
ブラウザのタブではなく、本物の楽器です。
FPGA入門
Field-Programmable Gate Array — 任意のデジタル回路に配線できる論理のグリッド。書き換えれば何度でも配線し直せます。
I/Oリング — UART · JTAG · LED · Pmodピン
1011
LUTファブリック
ブロックRAM
DSPスライス
1011→
ビットストリーム
数千個の
ルックアップテーブルとフリップフロップ 。プログラマブルな配線がそれらを任意の論理に結びます。
DSPスライス
縦のストライプ状に並ぶ
積和演算 専用ブロック — ファブリックを使わない高速な演算器です。
ブロックRAM
同じくストライプ状に並ぶ
オンチップ専用メモリ — バッファやテーブル用。読み出しはクロック同期です。
いわば「プログラム」。
電源投入時に読み込まれる回路の記述 で、設計そのものがチップになります 。
XLS32が使うArtix-7 xc7a35t: LUT 20,800個 · DSPスライス90個 ·
36 Kb × 50個のブロックRAM · 100 MHzのクロック1本 —
このシンセは1クロックごとに1オーディオサンプルを計算する、文字どおりの回路です。
実際に使っているチップ:
基板上のArtix-7 XC7A35T — 写真: Pedant01, Wikimedia Commons, CC BY-SA 3.0
FPGAは実世界のどこで動いているか
締め切りを決めるのが物理法則で、レイテンシをマイクロ秒・ナノ秒で測るような場所すべてです。
自動車
スバル・レヴォーグの運転支援システムアイサイト 。
Zynq UltraScale+上のステレオカメラ画像処理 — 1台に1個のFPGA-SoC が数百万台に搭載。
スバルがXilinxを採用 —
プレスリリース ↗
写真: Tokumeigakarinoaoshima, Wikimedia Commons, CC BY-SA 4.0
音響・映像
DiGiCoのSDシリーズ卓はStealth 「Super FPGA」
エンジンでミックス — 1台あたり最大256系統 を1–2 msのレイテンシで処理。
DiGiCo Stealth Core 2:
FPGA処理について ↗
写真: The Blackbird Academy, Wikimedia Commons, CC BY-SA 2.0
航空宇宙・防衛
F-35のアビオニクス。ロッキード・マーティンはXilinx FPGAを
機体全体で数万個規模 で調達 — レーダーとセンサーの決定的な処理に使われています。
数万個のFPGA —
Military Aerospace ↗
写真: U.S. Air Force / SSgt. K. Slivinske, パブリックドメイン
高頻度取引(HFT)
マーケットメイカーや自己勘定トレーダーは
ティック・トゥ・トレードの経路をFPGA論理で 実装 — 相場データを解析し発注するまで、
ワイヤーからワイヤーまで1マイクロ秒未満 。
AMDの取引専用
FPGA — プレスリリース ↗
画像 © Advanced Micro Devices, Inc., amd.com
業界はさまざまでも理由はひとつ: 締め切りを決めるのが物理法則なら、論理はハードウェアに入る。
XLS32はその同じ武器を音楽に借りてきました。
Verilogと標準的な開発フロー
ハードウェア記述言語。レジスタ単位、配線単位、クロック単位で回路を記述していきます。
RTLを書く
always @(posedge clk)
q <= d & en;
レジスタ、配線、alwaysブロック — さらにパイプライン段数、ハンドシェイク、
ビット幅まですべて手作業で管理 します。
シミュレーション
テストベンチ で設計を動かす(iverilog、Verilator)。サイクル精度ですが、
書いたテストベンチ以上のことは分かりません。
論理合成
yosysやVivadoがRTLをLUT・DSP・BRAM に対応づける —
実チップのプリミティブによるネットリストになります。
配置配線
ネットリストをダイ上に収め、タイミングを検証 —
ここが遅い工程で、1回に数分から数時間かかります。
書き込み・実機確認
JTAG 経由でビットストリームを書き込み、実機で検証します。
タイミングが通らなければRTLの編集に逆戻り。このループこそが反復コスト であり、
XLS32の方法論全体 — HLS + クラウドビルド + 機械採点による検証 — はそれを縮めるために組み立てられています。
Google XLS入門: ハードウェアをソフトウェアとして書く
高位合成(HLS) : 振る舞い をソフトウェア的に記述したものをハードウェア回路にコンパイルする技術。
RTLはコンパイラが書いてくれます
(Wikipedia ↗)
XLSはGoogleのオープンソースHLSツールキット 。書くのはDSLX —
純粋関数と小さな状態付きproc からなるRustライクな言語です
スケジューリング、パイプラインレジスタの挿入 、ビット幅の削減はすべて自動
ユニットテストはインタプリタ上でミリ秒 で完了 — テストベンチもシミュレータもビルドも不要
大半の反復ではFPGAツールに一切触れません。エージェントのループが速いのはこのためです。
作りたいもの
シンセの1ボイス: オシレーター → フィルター → エンベロープ、を数式として
▼
ƒ
DSLX — 振る舞いを記述する
純粋関数とproc。ユニットテストはミリ秒
▼コード生成
D
Q
Verilog — コンパイラがRTLを出力
スケジューリング、パイプライン化、ビット幅削減まで自動で
▼合成 + 配置配線
チップ上のゲートへ
合成と配置配線がLUT・DSP・BRAMに割り付ける
synth.x から engine.v へ
同じオシレーターの論理を、コンパイラ通過の前後で比較(実際のコードから抜粋・簡略化)。
synth.x — 人が書くコード(DSLX)
// オシレーター1サンプル分: 位相を波形に変換する
// ビット幅は型そのもの: u3/u32は符号なし、s16は符号付き
fn voice_wave (wave: u3 , phase: u32 ,
noise: s16 ) -> s16 {
let t = phase[24:32]; // 周期内の位置 0..255
match wave {
u3 :0 => SINE[t], // サイン波
u3 :1 => (t as s16 ) * s16 :16 - s16 :2048, // ノコギリ波
u3 :4 => noise, // ノイズ
_ => SINE[t],
}
}
+ いつもどおり
純粋関数を書くだけ — 上から下へ読め、組み合わせられ、ソフトウェアと同じようにユニットテストできます。
+ 状態管理と
スケジューリングはツールの仕事 — どの値がどのサイクルにどのレジスタへ入るか(ここでは48段)は
コンパイラが引き受けます。
− サイクル単位の厳密な制御が
必要な場所(BRAMポートやI/O)は、依然としてVerilogシェルに降りる必要があります。
→
engine.v — コンパイラが出力するコード
// パイプラインレジスタ p0/p1/p2 — 自動で挿入される
always @(posedge clk) begin
p0_t <= phase[31:24];
p1_sin <= SINE[p0_t]; // 256要素のROM
p1_saw <= {p0_t, 4'h0}
- 16'd2048; // シフトとオフセット
p2_out <= (wave == 3'd0) ? p1_sin
: (wave == 3'd1) ? p1_saw
: p1_noise; // 波形選択マルチプレクサ
end
+ すべてのレジスタ、
ポート、クロックサイクルを完全に制御 できる — 本当に必要なときに。
− すべてが並列に
起きる : どのalwaysブロックも毎クロック一斉に発火し、
上から下へ読める箇所はどこにもありません。
− 状態管理と
スケジューリングがすべて手作業 : どの値がどのサイクルにどのレジスタにあるかは自分の 責任 —
ソフトウェアエンジニアが必ずぶつかる壁です。
DSLXからビットストリームまで — ビルドパイプライン
DSLX
fn voice_wave(wave: u3,
phase: u32) -> s16 {
… }
synth.x — エンジン全体が378行のproc 1つ。
ユニットテストはインタプリタでミリ秒。
XLSコード生成
ƒ ƒ
パイプラインレジスタ間の論理
ir_converter → opt → codegen を--generator=pipeline付きで実行。
48段のengine.v が出力されます。
後処理
- end else begin
+ end else if (ce) begin
fix_verilog.py がグローバルクロックイネーブルを挿入し、
オープンなフローが受け付けないgenerateループを展開します。
合成 + 配置配線
yosys → VPR (F4PGA)、nextpnr (openXC7)、Vivado のいずれか —
BACKEND=で選択します。
書き込み
JTAG経由のopenFPGALoader — 反復時はSRAMへ、単体起動用はSPIフラッシュへ。
端から端までコマンド1つ: STAGES=48 WCT=48 scripts/remote_build.sh —
ソースをGCE VMに送ってビルドし、top.bitとタイミングレポート を持ち帰ります。
計測していないビルドを信用してはいけません。
03
AIエージェントによる ループエンジニアリング
このボードの立ち上げはすべてリモート・ヘッドレスで行いました —
LEDを見る人も、スピーカーを聴く人も、ボタンを押す人もいません。
「編集 → ビルド → 実行 → 観測」の自己検証サイクルを密に設計し、
エージェントにはその中で 反復させる。
ループエンジニアリング
一手ずつ人がプロンプトを与えるのではなく
ループ
客観的な合否判定を与えれば、あとはループが無人で回ります。
編集
エージェントがsynth.x(DSLX)を変更 — 新機能、修正、タイミング調整。
ビルド
ネイティブx86のGCE VMでビットストリーム生成、約6分。タイミングレポートも一緒に返ります。
書き込み・駆動
JTAG経由のopenFPGALoader。同じUSBケーブルでMIDIを送り、音声を録ります。
検証
FFT / スペクトログラムを0–100点で採点。合格ならマイルストーン達成、劣化なら編集へ戻ります。
要となる材料: 自律的な検証
すべての機能が人間の感覚を介さずに機械が採点できる 信号を出します:
音声はUSB経由でサンプル列としてそのまま取り出せる
音程はFFT で検証 — 和音とはN個の同時ピークのこと
音色と安定性はスペクトログラム で検証 — 濁り、クリッピング、途切れが一目瞭然
採点付きE2Eテスト130本超 : 基本機能・組み合わせ・ストレス。各0–100点で、劣化すれば不合格
教訓: FFTのピーク1点だけを見るチェックは、音声が壊れていても通ってしまったことがあります。
きれいな1スライスではなく、録音全体をスペクトログラムとして描くこと。
スペクトログラムとして検証したカットオフスイープ — これを読むのは人ではなく機械
ループの質はサイクルタイムで決まる
FPGAの配置配線が律速 — だからビルドをクラウドへ移しました。
Apple Silicon Macx86エミュレーション上のF4PGA(Docker)
約10分
ネイティブx86のGCE VMscripts/remote_build.sh — ソースを送り、ビットストリームとタイミングを回収
約6分
DSLXユニットテスト内側のループ — 大半の反復はビルド不要
ミリ秒
エージェントは判定を早く受け取り、1時間あたりの反復回数が増えます。しかもノートPCは空いたまま。
まずソフトウェアモデルで試作し、ビルドは確認のためだけに使いましょう。
検証可能な単位で積み上げる — M1からM19まで
M1 M3 M6a M6b M13–14 WEB UI M19
最初の音 DDSサイン波1本 + ADSR。UART越しに遠隔で検証。
本物のMIDI入力 UART RX、パーサ、ボイスアサイン。音程はFFTで確認。
パイプライン化 32ボイスを時分割するprocへ全面書き直し。procの1ティックで1ボイス。
ボイス毎フィルター 全ボイスに独立のレゾナントSVFとキートラック。
BRAMエフェクト コーラス、ピンポンディレイ、Freeverbリバーブをブロック RAM に。
ブラウザパネル Serum風UI。プリセットは逆合成(CMA-ES)で生成。
オシレーター間FM リング/FMで非調和なベル音。ラッチ解除の修正でSVFを堅牢化。
各マイルストーンは、実際どう鳴っていたか
すべてBasys 3の実機から録った音です。画面はいま聴こえている音の
スペクトログラム。クリックで再生します。
M1 · 最初の音0:06
8ビット/4 kHzのDDSサイン波1本と線形ADSR。音は貧弱ですが、
編集→ビルド→書き込み→測定のループが閉じたことの証明でした。
M6b · ボイス毎フィルター0:10
パイプライン化の書き直し後、全ボイスが独立したレゾナントSVFを持ちます。
カットオフが開き、レゾナンスが鳴き始めるのが聴こえます。
M9 · ノイズ + サブオシ0:13
アナログの定番3点を一度に: LFSRノイズ、マルチモードフィルター、
1オクターブ下のサブオシレーター。
M13 · コーラス + ディレイ0:13
初めてブロックRAMを使用。16K×16のディレイラインが補間コーラスタップと
ピンポンエコーを駆動し、モノラルが一気に広がります。
M14 · リバーブ0:11
BRAM上のSchroeder/Freeverb系の残響。帰還コムとオールパスで、
小部屋から大聖堂まで。この乗算のせいでクロックは÷4になりました。
M15 · ユニゾン0:13
1音に2/3/4ボイスを重ね、デチューンと位相のばらつきを与えます。
分厚いスーパーソウの代償は、最大同時発音数が32÷Nになること。
…そして機械が採点した後の音
同じ録音環境を、完成した楽器に向けたものです。
e2eテストレポート4:41
エージェント自身の回帰テストを字幕付きスペクトログラムに。USB経由で52項目
(基本・統合・厳格なストレス)を毎回書き込み直して録り直し、FFTの基準値と照合して
PASS / WARN / FAIL を判定。最後に総合スコア。
Web UI · バッハ「前奏曲」0:43
ブラウザのパネルからライブで駆動する4パートのマルチティンバー演奏。
バッハの前奏曲ハ長調を、32ボイスをパート間で分け合いながらArtix-7 1枚で鳴らしています。
19のマイルストーンのうち、耳だけで合格にしたものは一つもありません。どのクリップの裏にも、
エージェントが超えるべき数値がありました。
04
アーキテクチャ詳解
クロックは1本、サンプルレートも1つ。すべては純粋関数か小さなprocのどちらかで、
シンセはティックごとにオーディオサンプルを1つ吐き出します。
1本のクロック、3つのリズム
リズム 周波数 周期 何が起きるか
マスタークロック100 MHz 10 ns ボード全体の土台 — 発振器1つのみ。MMCM/PLLなし
ce — エンジンのクロック 33.3 MHz(÷3) 30 ns engine.v の全パイプラインレジスタがこの1本のイネーブルで動く。ボイス1つに約24発。
ce8(エフェクト)16.7 MHz(÷6) 60 ns 28状態のエフェクトFSMを1ステップ。BRAMを1回読み書き
サンプル周期32 kHz 31.25 µs 32 kHzステレオのサンプル1つ分 — マスタークロック3,125発
clk
ce
ce8
30 ns · エンジン1サイクル — engine.v はこのエッジ以外では一切動かない
32ボイスのスキャンに要するのは1サンプルあたり3,125クロック中の約2,304クロック 、周期の74%です。エフェクト(168クロック)とUARTのフレーム(約2,000クロック) はこれと並行して走るので、先に効いてくる上限はUARTではなく演算のほう。残る余裕は約26%です。
パッチとは28個の数字 — その数字が音色そのもの
回路は一切変わらない。28個の数字を差し替えるだけで、同じシリコンがベースにもパッドにもベルにもなる。
素のオシレータ — まだ無個性
パッチ = 28個の数字
立ち上がり・閉じるフィルタ・減衰
1つの数字が1つのつまみ。
単体では音にならない。
28個が組み合わさって音色になる。
下は、その28個が分担する7つの役割。
オシレータ
CC70·75·73·78·80
音の出発点になる素の波形
5種類から1つ選ぶ 1本 → デチューン・重ね
wave
pw
subsel
detsel
unison
クロス変調
CC85·86·87
片方のOSCがもう片方を歪ませる
osc 1 × osc 2
xmode
xdepth
xratio
フィルタ
CC74·71·72·79
どの倍音を残すか
ざらつく入力 cutoff まろやか
cutoff
reso
fmode
fdepth
LFO
CC76·77·1·92
ゆっくり揺らして生気を与える
ピッチ — ビブラート パルス幅 フィルタカットオフ 音量 — トレモロ
lfo_rate
lfo_depth
lfo_ph
vibsel
trdep
音量ADSR
CC20-23
音量が時間とともにどう動くか
A D S R 膨らんで消える
a_att
a_dec
a_sus
a_rel
フィルタADSR
CC24-27
明るさが時間とともにどう動くか
A D S R カットオフが動く
f_att
f_dec
f_sus
f_rel
演奏系
CC7 · bend · CC5
手で加える表情
vol bend portsel
vol
bend
portsel
1サンプル = パッチ × ボイスの189ビット
橙のピクトグラムは前スライドの28個の数字そのもの。ここが、その1つ1つが効く場所。青はボイス自身の状態で、この音がいまどこまで進んだかを覚えている。
青 = ボイスの189ビット(この音のいまの位置)
橙 = パッチ(前スライドの28個の数字)
cinc
26 b · 位相増分
phase
32 b · OSC1位相
ph2
32 b · OSC2位相
subhi
1 b · サブ矩形
env
19 b · 音量ENV+段
fenv
19 b
flo
19 b
fbnd
19 b
1
位相増分
inc = cinc « 6
± ベンド・ビブラート
± ユニゾンデチューン
→ inc
2
位相を積む
phase += inc
桁あふれ = 1周期
→ phase′
3
OSC2・変調
ph2 += inc × 比率
mod = sin(ph2)
→ mod
4
波形を選ぶ
5:1
3:1
saw sq tri sin nz
dry / リング / FM
→ o12
5
サブOSC
1オクターブ下の
±1800矩形波を加算
→ w
6
VCA
g = env · vel · trem
amp = w × g
→ amp
7
ステートバリアブルフィルタ
flo
fbnd
4:1
f = cutoff + キートラック
+ fenv·fdepth + LFO
flo・fbnd を3段の積分で更新
→ filt
8
1サンプル
t
このボイスの1点
→ ミキサーへ
portsel
bend
vibsel
unison
—
detsel
xratio
xmode
xdepth
wave
pw
subsel
a_att…a_rel
vol
trdep
cutoff
reso
fmode
fdepth
f_att…f_rel
—
このうち167ビットは毎サイクル書き換わる。残る22ビット(note・vel・uni・part)はノートオン時にラッチされ、そのまま運ばれるだけ。
32ボイスすべてが同じ回路を通る。違うのは189ビットの状態と、どのパッチを指しているかだけ。
つまみを回せば橙側が変わり、鍵盤を押せば青側が動きだす。出力サンプルは、その2つがこのサイクルで交わった点にすぎない。
48段の中身
この区切りは誰も引いていません。XLSがデータフローグラフ全体を48枚に切った結果です。1つの箱が1段で、大きさはその段が抱える論理の量。箱と箱の間の縦棒が、データを次段へ渡すレジスタの壁です。
箱の面積 = その段の組合せ論理の量/箱の間の縦棒 = 次段へ渡すレジスタ
0
4
8
12
16
20
24
28
32
36
40
44
47
データは1段につきクロックイネーブル1回ぶん進みます。100MHzを3分周した30 nsで、48段を端から端まで通るのに1.44 µs。
新しいボイスは24 ceごとに入る(実測)ので、48段の中を常に2本が走っている。● = ハードウェア乗算で、26個のDSP48にマップされLUTを使わないため箱の面積には含まれない。
0–1
MIDI入力
UARTの1バイト、そしてランニングステータス対応のMIDIパース(ノートオン/オフ、コントロールチェンジ)。
2–20
空きボイス走査
32スロットを順に見て空きを探す直列フォールド。カウントが連鎖するので1段に約2スロットしか入りません。
21–22
ボイスとパートの読み出し
ワンホットの書き込みマスク、続いてスロット0のボイスとそのパートのパラメータ。22段だけで配列読み出し93回。
23–27
エンベロープと変調
ADSR2基、ポルタメント、LFO、トレモロゲイン、ノート→増分テーブル、ユニゾンのデチューン種。
28–33
オシレータ
位相加算、デチューンした第2オシレータ、サブオシレータ、ノイズLFSR、PWM比較、リング/FM乗算、32ボイスリングへの書き戻し。
34–38
フィルタ
ステートバリアブルフィルタ:クランプ付き積分3ステップ(low → high → band)とディラッチ用のリーク。
39–41
ミックスと出力
パートごとのゲインとボリューム、ミックスへの加算、16ビットへのクランプ、スロット31で各パートのLFOを更新。
42–47
何もなし
validビットだけが流れる6段。48段という指定に合わせるためスケジューラが足したレイテンシです。
これは誰のブロック図でもありません。偏っていて、ノート処理とDSPが混ざり、最後は空の6段で終わります。
こちらが渡したのは数字ひとつ、--pipeline_stages=48 だけ。どこで切るかは30nsの制約に対するスケジューラの答えです。
1本のパイプラインを、32ボイスが順番に使う
上は、いま各ボイスがハードのどこにいるか。下は同じ動きを時間軸で — 再生位置は両方共通です。深さは48段ですが、新しいボイスは24サイクルに1つしか入らないので、走っているのは常に2本 — 残り46段はバブルです。
なぜリングなのか — 32:1マルチプレクサの壁
配列の動的インデックスはタダではありません。チップ上で最も幅の広いマルチプレクサになってしまいます。
✗ 配列をインデックスする
voices[vidx]
voice 0
voice 1
voice 2
voice 30
voice 31
⋮
32:1
mux
vidx
データパスへ
入力6,048ビットのマルチプレクサ(189b×32)
約21ns — 30nsの予算を超過。タイミング違反。
✓ 配列を回転させる
voices[0]
slot 0
slot 31
サイクル N
v0
v1
v2
v3
v4
v5
v6
v7
v8
v9
v10
v11
v12
v13
v14
v15
v16
v17
v18
v19
v20
v21
v22
v23
v24
v25
v26
v27
v28
v29
v30
v31
サイクル N+1
v1
v2
v3
v4
v5
v6
v7
v8
v9
v10
v11
v12
v13
v14
v15
v16
v17
v18
v19
v20
v21
v22
v23
v24
v25
v26
v27
v28
v29
v30
v31
v0′
process_voice()
共有データパスは1本だけ
rotate_in(v0′)
全スロットが1つ左へシフト
インデックスがリテラルの 0 になり、読み書きがただの配線になります。
インデックスがすべてループ定数なので、XLSは固定シフトに展開します。マルチプレクサは1つも生成されません。
3つのエフェクト、部品は1つ — ブロックRAMのディレイライン
コーラスも、エコーも、リバーブのコム/オールパスも、すべて同じリングバッファです。違うのは、書き込みポインタの何サンプル後ろを読むか — そして何を書き戻すかだけ。
BRAM
16K × 16
waddr
最新サンプルを書く
waddr − D
Dサンプル後ろのタップ
D = 遅延量
y[n] = x[n − D]
中身はシフトしない。
動くのは2つのポインタだけ —
1サンプルにつき1スロット。
16K×16 が4本 = BRAM 32個
起動時に全アドレスをゼロ埋め
コーラス
CC94 depth
LFO タップが動く Q3で補間
遅延 D
短く、動く — 300〜556サンプル。
三角LFOでスイープ、L/Rは逆位相
書き戻すもの
なし。読むだけ —
エコーが書いているバッファに相乗り。
エコー
CC82 time · CC95 depth
L R L R L × ½ 繰り返しが交互に 反対側へ跳ぶ
遅延 D
長く、固定 — 128〜16,256サンプル。
約4〜508 ms、CC82で設定
書き戻すもの
入力 + ½ × 反対チャンネルの遅延音。
だから L↔R をピンポンする
リバーブ(Freeverb)
CC91 size · CC93 wet
コム8本 Σ オールパス4本(直列) wet
遅延 D
短い固定タップを多数 — コム8本が
810〜1230、オールパス4本が163〜403
書き戻すもの
入力 + 減衰させた帰還(コム)、
またはオールパスのバタフライ。g=広さ
28状態のFSMがタンクを1周 — 1ステップにつきBRAMを1回読み書き(ce8)
エコー+コーラス
dst 1
L: コム8本
dst 5
L: AP4本
dst 13
R: コム8本
dst 17
R: AP4本
dst 25
28
演算器は1つだけ。L→Rの順で時分割共有 — 28 × 6 = 1サンプル3,125クロック中の168クロックなので、2チャンネルを直列に回してもタダ。
1サンプルの一生 ― 31.25µs、3,125クロック
32kHzとは、3,125マスタークロックごとにステレオサンプルを1つ出すということ。その3,125クロックの内訳です。
サンプル周期
― 32kHzサンプル1つ分。100MHzで3,125クロック、長さは実寸比
サンプル N+1 ― これから作る分
・Nを引き渡した瞬間にエンジンが走り出し、次を作るのに約2,300クロックかかる
サンプル N ― これから送り出す分
・エンジンが前の周期に作り終えていたもの。ティックはそれを引き出すだけ
エンジン
32ボイス・各約24ce
エフェクトFSM
28ステップ・1/ce8
UART TX
4バイト @ 2Mbaud
avldを上げてティック待ち ― 約820clk
dstは0に戻り、この周期の仕事は終わり
約960clk このレーンは空き
約2,300 clk(32ボイス × 約24ce × 3)
168 clk(28 × 6)
約2,000 clk ― 4バイト × 10ビット × 50 clk/ビット
0
1,000
2,000
3,125clk・31.25µs
MIDI入力はこの図の外 ― バイトは届いた次のceで受け取られ、周期とは無関係(1バイト約500clk)
クロック単位
― 同じ周期をティック周辺で拡大。長い区間は ‖ で省略(約2,300clkの走査、約2,000clkのTX、アイドル)
clk(100MHz)
ce(÷3・エンジン)
ce8(÷6・エフェクト)
サンプル準備完了(avld)
32ボイス走査(ce)
stick — 32kHzパルス
オーディオ引き渡し(ardy)
エフェクトFSM(dst)
dst 1→28
UART TX 出力
TX
エンジンは常に1サンプル先 ― エフェクトがNを加工している裏で、エンジンはもうN+1を作っています。
別々の回路なので、重なりはタダ。ティックがNをエフェクトFSMに渡し、同じハンドシェイクがエンジンをN+1へ走らせます。上限を決めるのはエンジンの約2,300clkのスキャンです。
役割分担: DSLX と Verilogシェル
ƒ
純粋なDSP演算 → DSLX
オシレーター、SVF、ADSR、LFO、ユニゾン、ミキサー — コンパイラのパイプライン化とビット幅の絞り込みが効くところ
ミリ秒でユニットテストでき、ハンドシェイクの管理も不要
タイミング付きメモリと制御 → Verilog
ブロックRAMには同期読み出し が必要。XLSが出すのは非同期読み出しで、BRAMには推論されません
エフェクトはパイプライン演算ではなくメモリポートのスケジューリング — 28状態のFSMが乗算器1つをL→Rの順に共有
エフェクトチェーン
エンジンはモノラル。ステレオ感はシェルが作ります — 逆相コーラス、ピンポンエコー、+23サンプルずらした8コムのFreeverb
16K×16の循環バッファ4本 = ブロックRAM 32個。これがチップ上の律速リソースです
チップ上でどれだけ使っているか
リポジトリに入っているVivadoビルド(Artix-7 xc7a35t)のreport_utilizationより。
DSP48スライス90個中26個 — 乗算はすべてファブリック外へ
29%
エンジンとシェルはCLBファブリック、乗算はDSP48、ディレイラインはBRAM
差し替え可能な3つの配置配線バックエンド
バックエンド DSP48 BRAM クロック / レート 備考
Vivado✓ 26 ✓ 32 ÷3 · 32 kHz
配布しているビットストリーム。クリティカルパス約18.5 ns、余裕約10 ns。クローズドソース。
openXC7✗ ✓ ÷4 · 28 kHz
完全オープンで、実際のFmaxレポートも出ます。ただしnextpnrはDSPのCARRYCASCINピンを(まだ)配線できません。
F4PGA / VPR✗ ✗ ÷4 · 28 kHz
完全オープン。乗算はソフト実装でスライス使用率が約90%。このプロジェクトの出発点であり、設計を形作った制約。
ボトルネックはXLSではなく、配置配線バックエンドの方でした。 まったく同じRTLが
Vivadoでは26個のDSP48と32個のBRAMに推論され、クリティカルパスが半減(約40 → 約18.5 ns)して
本来の32 kHzが戻ります。
プリセットは回して作らず、探索した
1バンク128個、ノブは誰も回していません。1つずつ
CMA-ES に23次元のCC空間を探索させた結果です。
シード
カテゴリ別の初期点
レンダリング
engine.py:synth.xの再実装
ロス
目標との距離を1つの数に
CMA-ES
23次元ベクトル/1世代13個
最良パッチ
1バンク128個のJSONへ
ターゲット
実録音の単音(NSynth/GMサウンドフォント)
次の候補パッチ群 ― 1ターゲットあたり約800レンダリング
ロスの箱の中身
― まず2つの音を同じ音量にそろえます。比べるのは音の大きさではなく音そのものの性格。次の3つを採点して足し合わせます
どんな成分でできているか
マルチ解像度STFT
×1
どの高さの音や倍音が入っているか。細かく見た
場合とざっくり見た場合の両方で確かめます。
全体の色あい
メル風バンド
×2
同じものを24本の帯にぼかしたもの。明るいか暗いか、
細いか太いか。倍音1本のズレは失点にしません。
時間ごとの形
振幅包絡
×3
音がどう立ち上がり、どう消えていくか。これが無いと
長く伸びる音と短く弾く音が同点になってしまいます。
CMA-ESの箱の中身
― ここでは2次元で描いていますが実際の探索軸は23本。輪の重なりがロスの地形で、× がターゲットの音
1 · サンプリング
候補パッチを楕円からサンプリング。中心が現時点の
最良解、楕円の形が「どの軸をどれだけ探るか」。
2 · 順位をつけて半分に
13個すべてを鳴らして採点し、良い半分だけ残す。
微分は一切しない ― ロスは評価するだけ。
3 · 中心と形を更新
中心を勝ち残りの側へ動かし、楕円をその方向へ伸ばす。
13個×約62世代で、あわせて約800レンダリング。
スペクトログラム上で近いことと、耳で良い音であることは別物でした。
中央値22.9/28.5は目盛り(同一音≒0)の上では悪くありません。ただ、この点数だけでは「良い音」を定義できず、減算合成のコアではブラス系の音色そのものが作れません。
05
つまずきと学び
互いのことを知らない3つのツールを積み重ねているので、継ぎ目は必ず漏れます。
実際に痛い目を見たところと、そこから学んだことを。
オープンなフローの制約が、設計全体を決めた
乗算はすべてLUT+キャリーのソフト乗算器に → 乗算器は極力小さく 。オペランドの型を細くして、XLSの幅絞り込みパスに縮めてもらう
非同期読み出しはBRAMにならない
XLSのROMは巨大なマルチプレクサのまま。シェルに手書きした同期読み出しRAM だけがブロックRAMに載ります
遅いクロックが作れない → すべてを100 MHzで動かし、グローバルなクロックイネーブル で歩を進める(マルチサイクルの技)
ツールはマルチサイクルを認識できず、そのパスを失敗と報告します → 生のレポートを毎ビルド保存して測る 。配置にはばらつきがあるので余裕も残す
固定小数点との格闘 — 浮動小数点シミュレーションでは出ないバグ
ラッチしてしまうフィルター
高レゾナンスではSVFの内部状態がクランプの上下端に張り付き、無音に。明るいポリフォニックFMでは
フルスケールのリミットサイクルに固まり、再生時間の96%が振り切れ ていました。
対策: リーキー積分器(毎サンプル約1%)で極を単位円の内側へ寄せ、自励振動が減衰するように。
浮動小数点シミュレーションでは一度も現れず、実機だけが見せてくれました。
暴走したリバーブ
ダンピングを小さなシフト量で実装したせいで可聴帯域が潰れ、さらに 16ビットの状態が
ラップアラウンド。符号が反転して帰還が暴走しました。
対策: ダンピングを (old+new)/2 に。原理的にオーバーフローせず、帯域も保たれます。
加えてリセット時にBRAMをクリア。電源投入時のゴミが帰還ループの種になるためです。
ラップさせず、飽和させる
ラップアラウンドは巨大な不連続、つまり広帯域のクリックノイズです。ミキサーでクランプし、
デモでは厚い和音のベロシティを控えめに保ちます。
検証は録音全体のスペクトログラムで。FFTを1スライスだけ見て合格にしたら、
実際の音は壊れていた、ということがありました。
エージェントのループを実際に機能させたもの
すべての機能を機械が0〜100点で採点。「だいたい良さそう」は無し。エージェントはその数値を読んで直します
NumPy/numbaで書いたエンジンのモデルがRTLを写し取っています。FMの強さ、リバーブのダンピング、プリセット探索はすべてシミュレーションで先に確かめ、その後で 6分のビルドを使いました
シミュレーションを実機に較正する
同じパッチをシミュレーションと実機で比較するプローブを用意。実機で検証するまで、合わせ込んだプリセットは「シミュレーション上で最適」なだけ。録音は非決定的なので、N回のうちベストを採り、書き込み直す
フォーマットを変えたら、読む側を全部見直す
ボードをステレオ化したのにホスト側はモノラルのまま読んでいて、全音が1オクターブ低く。相対音程でのチェックだったため見逃していました
06
まとめ & Q&A
持ち帰ってほしいこと、資料の場所、そして皆さんからの質問。
まとめ
01 HLSを使うと、DSPハードウェアがソフトウェアのように書ける。制御が要るところだけ薄いシェルで
02 機械が採点できる信号をすべての機能が出すなら、エージェントは本物のハードウェアを作れる
03 効いてくるのはサイクルタイム。速いビルド + ソフトウェアモデル = 1時間あたりの反復回数
04 信じずに測る。タイミングレポート、スペクトログラム、シミュレーションと実機の較正
05 摩擦が生まれるのはツールの継ぎ目。記録に残せば、それ自体が再利用できる資産になる
参考リンク
github.com/kazunori279/xls32-fpga-synth — Apache-2.0。ビルド済みビットストリーム同梱
デモ動画: youtu.be/2ROr9M_ZlVY
ARCHITECTURE.md — ブロック毎の詳解: コード、データフロー、タイミングチャート。
ECP5シェルは ARCHITECTURE_tiliqua.md
DEVELOPMENT.md — マイルストーンの履歴とツールチェーンの苦戦ログ。
移植の分は DEVELOPMENT_tiliqua.md
Google XLS — google.github.io/xls
F4PGA — f4pga.org · openXC7 · yosys + nextpnr · openFPGALoader
Basys 3 — digilent.com · Tiliqua — apf.audio
ありがとうございました
ご質問をどうぞ
github.com/kazunori279/xls32-fpga-synth
A
付録 ECP5への移植
同じエンジンは、Eurorackモジュールでもあります。ここから先はすべて2枚目のボードの話 —
シェルが何になる必要があったのか、そして同じソースを小さいダイに収めろと言われたとき、
どの数字が動いたのか。
…そして同じエンジンは、Eurorackモジュールでもあります
ここから先は「1つの設計・2枚のボード」の話です。数字が違うところは両方載せます。
Basys 3 — 開発用ボード
Digilent Basys 3、Xilinx Artix-7 xc7a35t。32音 。音声もMIDIも1本のUSB UARTを
通り、ホスト側のFFTで採点します。プロジェクトの出発点であり、エンジンに手を入れたときに
最初に測るのは今もこちら。
Verilogのシェル · 出荷ビットストリームはVivadoでビルド。
Tiliqua R5 — 楽器
apf.audioのTiliqua、Lattice ECP5 LFE5U-25F。小さいダイで24音 。音はEurorackの
ジャックから本物のコーデック経由で出て、TRS MIDI入力と720×720p60のDVIビジュアライザ付き。
USB Audio Class 2準拠なので、どのDAWからもドライバなしで見えます。
Amaranthのシェル · yosys + nextpnr-ecp5で完全オープン。
synth.x の中身は、自分がどちらのボードにいるかを知りません。ECP5への移植は
同じソースを違う --pipeline_stages で再コンパイルしただけ — 48から12へ —
書き直しではありません。パイプラインを手で配置せず、コンパイラにスケジュールさせたことの配当です。
共通、無改造:
生成された engine.v 1つ · ブラウザパネル1つ · host/ のツールチェーン1つ ·
175ケースの採点スイート1つ。どちらのボードを相手にするかはフラグ1つです。
2つのシェル、1つのエンジン
生成された engine.v は両ボードでまったく同じ論理です。違う数字はすべて、
その周りのシェル側にあります。
Basys 3 · Artix-7 Tiliqua · ECP5
シェルと配置配線 Verilogの rtl/top.v — Vivado / F4PGA / openXC7
Amaranthの gateware/ — yosys + nextpnr-ecp5、完全オープン
エンジンのクロック 100 MHz、エンジンは÷3のclock enableで駆動
12.288 MHz — SI5351がファブリックへ直結、FPGA内PLLなし
パイプライン段数 STAGES=48 → 1サンプル768サイクル
STAGES=12 → 224。このクロックでは384サイクルしか使えないため
サンプルレート 32 kHz — シェルがサンプルを押し込む
コーデックが引く : 48 kHzの要求が3/2リサンプラを通り、エンジンにはちょうど32 kHzで届く
音声とMIDI 16bit PCMとMIDIが1本の2 MbaudのUARTを共有
AK4619経由でEurorackジャックへ · USB-C 1本でUAC2音声とUSB-MIDI · TRS MIDI入力
乗算器 DSP48E1を26個(90個中)
MULT18X18Dを27個(28個中) — ダイ上の1個を残して全部
見えるもの 16個のLEDがボイス動作の彗星、7セグ表示
720×720p60のDVI — 1ボイス1タイル、フレームバッファなし
ECP5の狭い18×18タイルに合わせて乗算を組み直したら、Basys 3 のビルドまで安くなりました。
LUTが78個減り、パスが0.32 ns短くなり、DSP48は26個のまま — 音声3,000サンプルはビット単位で一致。
移植の配当が、元のボードへ逆流した形です。
クロックドメインは4つ、エンジンの分はFPGA製ではない
Basys 3は発振器1個・PLLなし・÷3のenableでした。こちらはエンジンのクロックが
基板の反対側のSI5351から届き、FPGAはそれを分周すらしません。
ドメイン 周波数 供給元 そこで動くもの
audio
12.288 MHz
SI5351の clk0 を直結
XLSエンジン、そのブートROM、ビジュアライザのタップ
sync + usb60 MHz
48 MHz発振器からECP5のPLLで生成 — そしてULPI PHYがここに固定する
それ以外すべて: MIDI、エフェクト、コーデック、USBのtee、lunaのスタック全部
dvi(+dvi5x)39.07 MHz
SI5351の clk1 → 2つ目の ECP5 PLL
ボイスタイルとTMDS PHY
fast120 MHz ECP5のPLL
SDK側の基盤 — この設計では未使用
sync で÷1のまま動かす
60 MHz、ドメイン1つ、載せ替えなし。
不可。60 MHzに届く STAGES は存在せず、最良でも48で59.2 MHz。
しかもデバイスのFFを70%食います。
…clock enableを付ける
Basys 3のやり方: 全レジスタを÷3でゲート。
これも不可。enableはレジスタ間パスを緩めません。Vivadoにはマルチサイクルだと
教えて ありますが、nextpnr-ecp5に同等の指定はありません。
採用
専用クロックを与える
12.288 MHz、もともと基板に載っている部品から。
STAGES=12 は27.5 MHzで閉じ、必要なのは7.2 MHz — 1.7倍の余裕 。
エンジンは毎エッジ動きます。
ドメイン間はこれだけ: AsyncFIFO が2本、MIDI入力に
深さ4、音声出力に深さ8。fix_verilog.py がBasys 3向けに注入するclock enableポートも
このVerilogに残っています — 同じ Verilogだからです。ここでは常時Highに縛られた死に端子です。
同じソースを、48段ではなく12段にスケジュール
--pipeline_stages はソース変更ではなくcodegenのフラグです。そして
12.288 MHzのクロックにエンジンを収めるために動かす必要があったのは、このつまみだけでした。
32 kHzの1サンプルを計算するのに要するエンジンサイクル
384 — 12.288 MHzのクロックが出せる全サイクル
STAGES=48
768 — 3,125クロック使えるBasys 3なら問題なし
STAGES=12
32音で224
出荷ビルド
24音で約168 — 1周期の43.8%
1音1サイクルではない理由
1サンプルの費用は voices × およそ
STAGES/2 エンジンサイクルです。1つのボイスが段数の半分だけパイプラインに居座るので、
コンパイラが選んだ段数がそのまま上のバーの長さになります。
段数はスループットではない
STAGES を上げると、エンジンが必要とする
クロックも到達できる Fmaxも一緒に上がります。買えるのはタイミング余裕で、代金はFF。
収まるどの値でも持続可能なレートは57 kHzを超えます。この移植でサンプルレートは一度も制約では
ありませんでした。制約は面積です。
48も収まる、ただし高い
ECP5で STAGES=48 は59.2 MHzで閉じ — USB PHYが
要求する60 MHzには届かず — しかもすでに93.5%埋まっているダイでFFの70% を持っていきます。
バックエンド1つ、シード固定、出力はアーカイブ
ビルドの前半は1文字違わず共通です。後半にVivadoは登場せず、
top.bit では終わりません。
フラグ以外は共通
core/synth.x → ir_converter → opt →
codegen → fix_verilog.py。同じスクリプト、同じピン留めしたXLSビルド、
違うのは数字1つ。XLSはarm64バイナリを配布していないので、ここは今も
linux/amd64 のDockerで動きます
その先はVerilogでなくAmaranth
top.py がエンジンの周りのシェルをelaborateします。エンジンは
パスではなく中身で ビルドに渡されます — yowaspのyosysはWASI上で動くため、
自分の作業ディレクトリの外にあるファイルが見えないからです
バックエンドは1つ、完全オープン
yosys → nextpnr-ecp5 → ecppack、すべて yowasp 経由でApple Siliconネイティブ。
リファレンスコアが端から端まで35.8秒 (Basys 3のVivadoパスはリモートVMが必要でした)。
配置配線は固定: --router router2 --router2-tmg-ripup --seed 7 --timing-allow-fail。
最後のフラグは、この設計が60 MHzを閉じたことがないからです
出力はビットストリームではない
manifest.json に clk0_hz: 12288000 を持つ .tar.gz で、
ブートローダのスロット7 に書きます。ブートローダは毎回のコールドブートでその
manifestからSI5351をプログラムする — エンジンのクロックはゲートウェアではなく
フラッシュイメージ側にあります
ダイの93.5%と、6%の賭け
Artix-7側はチップの半分ほどで収まります。ECP5側にその余裕はなく、このスライドの内容は
すべてそこから来ています。
どの指標で見ても満杯
24音で論理セル22,722 / 24,288 — 93.5% 、32音なら98.3% ·
乗算器28個中27個 · ブロックRAM 56個中53個
クロックが閉じない
USB PHYがそのドメインを60 MHz に固定する一方、nextpnrの24音ビルドは56.63 止まり。
出荷するということはシリコンがモデルより6%速い 方に賭けること — 32音では同じ賭けが
29% になり、実際に1台のモジュールで外れています
効くのは配置
この占有率ではルータのシードが結果を決めます。同じネットリストで24シードのスイープが
51.60 → 56.63 MHzに散らばり 、しかも勝者は移りません: 56.63を取るシードは、1コミット前の
ネットリストでは下位の50.90でした
gitハッシュすら効く
ビルドスタンプはROMなので、ツリーが汚れて付く6文字がROM幅を変え、ネットリストを変え、
くじを引き直させます。リリースビルドはクリーンツリー のビルドであり、ネットリストが
動けばスイープはやり直しです
フレームバッファのない720×720p60
置き場所がなかったから — そして、持たなかったおかげで設計は小さく なりました。
ビームを追いかける
各ピクセルの色は、送出の1サイクル前に、ビーム位置と32バイトのストア から計算します。
1ボイスあたり15bitが1ワード、明るさはエンベロープ、色相はピッチから。
同じ情報をフレームバッファで持つと1.5 MB 。ダイ全体でブロックRAMは56個です。
クロック載せ替えはBRAM 1個
そのストアはデュアルポートです。audioドメイン(12.288 MHz)から書き、
dvi(39.07 MHz)から読む。FIFOもハンドシェイクも同期化回路もありません。
互いの都合を知る必要がないのは、読む側が常に最新値だけを欲しがるから。
ポートが渡すのはまさにそれです。
元が取れた
SDKの映像パスはPSRAMから流します。そこはエコーのディレイラインが住んでいた場所でした。
フレームバッファをやめた結果、2つ目の利用者が消え、続いてPSRAM自体が消えました。
24,107セル(99%)→ 23,404(96%) 、当該ドメインのFmaxは
40.17 → 44.71 MHz。画面が増えたのに、設計は縮みました。
タイル自体は235セル — デバイスの1.0% です。ホスト側で描いてピクセルを流し込む案は、
測って却下しました。必要な帯域が93 MB/s、相手はバルクエンドポイントを持たず上限およそ40 MB/sの
USBデバイスです。
オブジェクト1つで、ループが2枚目のボードに届いた
同じ175ケース、同じ採点、同じしきい値 — UARTのバイトを読んでいた Transport が、
UAC2のキャプチャを開く実装に変わっただけです。
出荷している24音ビルドは実機で99.8 / 100(A+) — 174 pass、1 warn、0 fail、
175回のキャプチャでUSBフレーム欠落0.00%
唯一のwarnはフィルタスイープの明るさ測定で、両ボード共通 。移植より前からあります
2枚はサンプルレートが食い違うので、スイートには相手がどちらかを教え、そのうえで
実際に得られたレートをアサートさせています
そして、話の筋に合わない部分: より古くて遅い 32音の
ビットストリームが、ここでは完璧に動き、製作者の2台のうち1台では動きませんでした 。
スタティックタイミング的にはどちらも動かないはずです。このダイでは動いています。
未解決・原因未特定のまま。32音ではなく24音を
このリポジトリの正式ビルドにしている理由でもあります。