XLS32: AIエージェントだけで
作ったFPGAシンセサイザー

Google XLS(DSLX)で書かれた32音ポリフォニックシンセ。設計・実装・実機検証まで、 ループエンジニアリングによってAIコーディングエージェントが一貫して行いました。
XLS32 · HLS / FPGAシンセ 2026年7月 · 50分
アジェンダ
  1. 00自己紹介2分
  2. 01XLS32とは6分
  3. 02技術スタック16分
  4. 03AIエージェントとループエンジニアリング9分
  5. 04アーキテクチャ詳解8分
  6. 05つまずきと学び5分
  7. 06まとめ + Q&A4分
  8. A付録 — ECP5への移植質問があれば
00
自己紹介
まずは30秒だけ自己紹介を。そして2012年に手作りしたFPGAシンセの話 — この発表で作り直すのは、実はそれと同じ楽器です。
はじめまして
佐藤一憲
Kazunori Sato
@kazunori_279  ·  東京  ·  Xは2008年から
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日
FPGAにつないだSparkFunのMIDIブレイクアウト基板
SparkFunのMIDIブレイクアウトをDE0へ。31.25kbpsの調歩同期シリアルを4倍オーバーサンプルする ステートマシンをVerilogで書いて、まず 90h 3Ch 40h がLEDに出たのが最初の一歩。
8音ポリフォニー2012年12月19日
8音ミックス出力のオシロスコープ波形
サイン波のルックアップテーブルとノート番号→ステップ幅の変換表。8個のWaveGenがMIDIを 次段へ受け流してボイスを割り当てます。8bitで足すとクリップしたので、ミックスはDACの12bit側へ。
ブラウザのツマミ2012年12月29日
chrome.serialでシリアルポートを列挙するChrome DevTools
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とスペクトログラムによって検証しています。

XLS32のブラウザUI
USB越しにFPGAシンセをライブで鳴らすWeb UI
デモ: Web UIから実機を鳴らす
ブラウザUI 静的ページ — サーバー不要
アナログ風パネル — つまみはMIDI CCを送信。画面上の鍵盤、PCのキーボード、 Web MIDIコントローラーのいずれからでも演奏可能。シリアルポートの占有と音声ストリームの バイト境界合わせもタブ自身が行う
▼ MIDIバイト列Web Serial · USB UART · 2 Mbaud16ビットステレオ ▲
Basys 3ボード シンセ回路そのもの
ビルド済みのfirmware/top.bitを書き込むだけ — FPGAが全サンプルを計算。 デモにツールチェーンは不要
デモ動画 — Web UIでボードを鳴らす様子、音はシンセ本体の出力
youtu.be/2ROr9M_ZlVY
ボードが通しで演奏した2曲 — いずれも4パート、音はシンセ本体の出力:
バッハ · プレリュード ハ長調4パート · 1:51 サン=サーンス · 白鳥4パート · 2:18
一行たりとも手書きしていない
そしてすべての機能を機械が検証している 設計全体を作ったのはClaude Code(Opus 4.8)。ビルド → 計測 → 修正という 自己検証ループを、ネットワーク越しに、誰もボードを見ていない状態で回しました。 しかもほぼすべての機能は1週間の旅行中にスマートフォンから開発されています。
02
技術スタック
なぜシンセをFPGAファブリックに載せるのか — そしてそれを可能にする3つの技術: ボード、言語、ツールチェーン。
減算合成シンセ入門
倍音の豊かな波形から始めて、削っていく。このスライドの各ブロックはすべてXLS32内に回路として存在します。
オシレーター
倍音の多い生の波形を作る — ノコギリ波、矩形波、三角波、ノイズ。 デチューンとユニゾンで音を太くします。
フィルター
ここが「減算」の部分。レゾナンス付きフィルターがスペクトルを削り、 カットオフを動かすと音色が変化します。
アンプ
A D S R
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→ ビットストリーム
LUTファブリック

数千個の ルックアップテーブルとフリップフロップ。プログラマブルな配線がそれらを任意の論理に結びます。

DSPスライス

縦のストライプ状に並ぶ 積和演算専用ブロック — ファブリックを使わない高速な演算器です。

ブロックRAM

同じくストライプ状に並ぶ オンチップ専用メモリ — バッファやテーブル用。読み出しはクロック同期です。

1011 ビットストリーム

いわば「プログラム」。 電源投入時に読み込まれる回路の記述で、設計そのものがチップになります

XLS32が使うArtix-7 xc7a35t: LUT 20,800個 · DSPスライス90個 · 36 Kb × 50個のブロックRAM · 100 MHzのクロック1本 — このシンセは1クロックごとに1オーディオサンプルを計算する、文字どおりの回路です。

基板上のXilinx Artix-7 XC7A35T
実際に使っているチップ: 基板上のArtix-7 XC7A35T — 写真: Pedant01, Wikimedia Commons, CC BY-SA 3.0
FPGAは実世界のどこで動いているか
締め切りを決めるのが物理法則で、レイテンシをマイクロ秒・ナノ秒で測るような場所すべてです。
スバル・レヴォーグ(2代目)
自動車

スバル・レヴォーグの運転支援システムアイサイト。 Zynq UltraScale+上のステレオカメラ画像処理 — 1台に1個のFPGA-SoCが数百万台に搭載。

スバルがXilinxを採用 — プレスリリース ↗

写真: Tokumeigakarinoaoshima, Wikimedia Commons, CC BY-SA 4.0

DiGiCo SD5 デジタルミキシングコンソール
音響・映像

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 Lightning II各型
航空宇宙・防衛

F-35のアビオニクス。ロッキード・マーティンはXilinx FPGAを 機体全体で数万個規模で調達 — レーダーとセンサーの決定的な処理に使われています。

数万個のFPGA — Military Aerospace ↗

写真: U.S. Air Force / SSgt. K. Slivinske, パブリックドメイン

AMD Alveo UL3524 アクセラレータカード
高頻度取引(HFT)

マーケットメイカーや自己勘定トレーダーは ティック・トゥ・トレードの経路をFPGA論理で実装 — 相場データを解析し発注するまで、 ワイヤーからワイヤーまで1マイクロ秒未満

AMDの取引専用 FPGA — プレスリリース ↗

画像 © Advanced Micro Devices, Inc., amd.com

業界はさまざまでも理由はひとつ: 締め切りを決めるのが物理法則なら、論理はハードウェアに入る。 XLS32はその同じ武器を音楽に借りてきました。

Verilogと標準的な開発フロー
ハードウェア記述言語。レジスタ単位、配線単位、クロック単位で回路を記述していきます。
RTLを書く
always @(posedge clk)   q <= d & en;
レジスタ、配線、alwaysブロック — さらにパイプライン段数、ハンドシェイク、 ビット幅まですべて手作業で管理します。
シミュレーション
clk q
テストベンチで設計を動かす(iverilog、Verilator)。サイクル精度ですが、 書いたテストベンチ以上のことは分かりません。
論理合成
yosysやVivadoがRTLをLUT・DSP・BRAMに対応づける — 実チップのプリミティブによるネットリストになります。
配置配線
ネットリストをダイ上に収め、タイミングを検証 — ここが遅い工程で、1回に数分から数時間かかります。
書き込み・実機確認
1 0 1 1
JTAG経由でビットストリームを書き込み、実機で検証します。

タイミングが通らなければRTLの編集に逆戻り。このループこそが反復コストであり、 XLS32の方法論全体 — HLS + クラウドビルド + 機械採点による検証 — はそれを縮めるために組み立てられています。

Google XLS入門: ハードウェアをソフトウェアとして書く
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=で選択します。
書き込み
1 0 1 1
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まで
M1M3M6aM6bM13–14WEB UIM19
最初の音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 MHz10 nsボード全体の土台 — 発振器1つのみ。MMCM/PLLなし
ce — エンジンのクロック33.3 MHz(÷3)30 nsengine.v の全パイプラインレジスタがこの1本のイネーブルで動く。ボイス1つに約24発。
ce8(エフェクト)16.7 MHz(÷6)60 ns28状態のエフェクトFSMを1ステップ。BRAMを1回読み書き
サンプル周期32 kHz31.25 µs32 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 音量が時間とともにどう動くか ADSR膨らんで消える a_att a_dec a_sus a_rel フィルタADSR CC24-27 明るさが時間とともにどう動くか ADSRカットオフが動く f_att f_dec f_sus f_rel 演奏系 CC7 · bend · CC5 手で加える表情 volbendportsel 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段はバブルです。
アニメーション:共有された48段のパイプラインを、各ボイスがエンジン1サイクルごとに1段ずつ進んでいく様子と、対応するガントチャート。
なぜリングなのか — 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 LRLRL× ½繰り返しが交互に反対側へ跳ぶ 遅延 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より。
ブロックRAM律速リソース
65%
スライスLUT
50%
レジスタ
42%
DSP48スライス90個中26個 — 乗算はすべてファブリック外へ
29%
リソースのフロアプラン
エンジンとシェルはCLBファブリック、乗算はDSP48、ディレイラインはBRAM
差し替え可能な3つの配置配線バックエンド
バックエンドDSP48BRAMクロック / レート備考
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つのツールを積み重ねているので、継ぎ目は必ず漏れます。 実際に痛い目を見たところと、そこから学んだことを。
オープンなフローの制約が、設計全体を決めた
DSP48が推論されない
  • 乗算はすべてLUT+キャリーのソフト乗算器に → 乗算器は極力小さく。オペランドの型を細くして、XLSの幅絞り込みパスに縮めてもらう
非同期読み出しはBRAMにならない
  • XLSのROMは巨大なマルチプレクサのまま。シェルに手書きした同期読み出しRAMだけがブロックRAMに載ります
MMCMもクロック分周も使えない
  • 遅いクロックが作れない → すべてを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
持ち帰ってほしいこと、資料の場所、そして皆さんからの質問。
まとめ
  1. 01HLSを使うと、DSPハードウェアがソフトウェアのように書ける。制御が要るところだけ薄いシェルで
  2. 02機械が採点できる信号をすべての機能が出すなら、エージェントは本物のハードウェアを作れる
  3. 03効いてくるのはサイクルタイム。速いビルド + ソフトウェアモデル = 1時間あたりの反復回数
  4. 04信じずに測る。タイミングレポート、スペクトログラム、シミュレーションと実機の較正
  5. 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

ありがとうございました

ご質問をどうぞ

A
付録
ECP5への移植
同じエンジンは、Eurorackモジュールでもあります。ここから先はすべて2枚目のボードの話 — シェルが何になる必要があったのか、そして同じソースを小さいダイに収めろと言われたとき、 どの数字が動いたのか。
…そして同じエンジンは、Eurorackモジュールでもあります
ここから先は「1つの設計・2枚のボード」の話です。数字が違うところは両方載せます。
Basys 3 — 開発用ボード

Digilent Basys 3、Xilinx Artix-7 xc7a35t32音。音声も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-7Tiliqua · 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で届く
音声とMIDI16bit 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(+dvi5x39.07 MHz SI5351の clk12つ目の ECP5 PLL ボイスタイルとTMDS PHY
fast120 MHzECP5の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.xir_converteroptcodegenfix_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.jsonclk0_hz: 12288000 を持つ .tar.gz で、 ブートローダのスロット7に書きます。ブートローダは毎回のコールドブートでその manifestからSI5351をプログラムする — エンジンのクロックはゲートウェアではなく フラッシュイメージ側にあります
ダイの93.5%と、6%の賭け
Artix-7側はチップの半分ほどで収まります。ECP5側にその余裕はなく、このスライドの内容は すべてそこから来ています。
どの指標で見ても満杯
  • 24音で論理セル22,722 / 24,28893.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音を このリポジトリの正式ビルドにしている理由でもあります。

USB経由で実機から録音 — 4パート、部屋マイクなし、ソフト音源なし:
パネルでモジュールを鳴らすライブデモ Bach · ゴルトベルク アリア4パート · 1:45 Vivaldi · 冬 第2楽章4パート · 1:45
1 / 32
ページ送り   f 全画面