Close

Why 11th-order Maclaurin? Not CORDIC, not LUT — the four reasons

A project log for FPGA Spectrum Engine

10,240 independent oscillators · 1-sample latency · 0.001 Hz resolution

tsuneoohnakaTsuneo.Ohnaka 04/25/2026 at 13:350 Comments

Why 11th-order Maclaurin? Not CORDIC, not LUT — and why the implementation is an open arena

When you tell someone "I generate 10,240 sinusoids on FPGA," the first response is almost always one of two questions: "CORDIC?" or "big lookup table?"

Neither. Every bin in this engine generates its sine value through direct evaluation of an 11th-order Maclaurin series, computed on a fixed-latency DSP pipeline. One result per clock, per pipeline. Five pipelines in parallel. 10,240 sines per sample period at 48 kHz.

This log is about why that choice — direct polynomial evaluation — was forced by the architecture. It is also about something more interesting: once that choice is made, the actual evaluation strategy on silicon is wide open. The mathematics is fixed. The implementation is an arena.

The mathematics

The Maclaurin series for sine is one of the first things you meet in calculus:

sin(x) = x − x³/3! + x⁵/5! − x⁷/7! + x⁹/9! − x¹¹/11! + ···

Truncating at the x¹¹ term, the remainder bound is:

|R₁₁(x)| ≤ |x|¹³ / 13!

For input range |x| ≤ π/2, the worst-case error is about 1.3 × 10⁻⁷, which corresponds to roughly 23 bits of effective precision — enough headroom for a 24-bit DAC with a few bits of guard against accumulated quantization noise downstream.

Range reduction is trivial. A standard quadrant-fold reduces the input to [0, π/2], which costs one comparator and at most one subtraction at the head of the pipeline. Range reduction is not the interesting part of this design.

Reason 1 — Verifiability

The coefficients in the polynomial are 1/n! — exactly. They are not the result of a Remez optimization. They are not table-fitted. They are not vendor-supplied IP block constants. They are mathematical necessities, computable from a textbook in two minutes.

This matters because the entire engine is being distributed as Open Prompt (more on this in Build Log #4). Anyone who wants to regenerate this implementation, with or without LLM assistance, can derive the coefficients themselves. There is no opaque parameter that has to be transferred along with the architecture description. The mathematics is the specification.

A CORDIC implementation has the same property in principle (the rotation angles are arctan(2⁻ᵏ)), but in practice CORDIC implementations on FPGAs come with a thicket of magic numbers — gain compensation factors, micro-rotation orderings, scaling adjustments — that do not survive translation across architectures cleanly. A polynomial sine survives translation perfectly, because the polynomial is the answer.

Reason 2 — Pipeline-natural

Direct polynomial evaluation maps onto a fixed-latency DSP pipeline with one new sine result emerging from the end of the pipeline every clock cycle. The exact structure of that pipeline is an implementation choice (more on this below), but every reasonable structure shares the same property: the polynomial degree determines the pipeline depth, not the throughput. Throughput is one result per clock, period.

This is the property that makes 2,048 bins per pipeline viable. At 100 MHz, a single pipeline produces 100M sines per second. At 48 kHz output rate, that is 2,083 samples worth of throughput per output sample — which I round down to a clean 2,048 bins per module, leaving headroom for control overhead. Five modules give 10,240.

CORDIC also pipelines well, but each CORDIC iteration produces only a partial-precision result; you need 16–20 iterations for 24-bit precision, each with its own pipeline stage, each consuming a DSP block or its equivalent in logic. The DSP-block budget on a Cyclone V — 112 blocks total — is the hard limit on how many bins fit, and direct polynomial evaluation uses that budget more efficiently than CORDIC for the precision target I needed.

Reason 3 — No memory contention

Lookup tables are the obvious alternative. A 16-bit-input, 24-bit-output sine table is 64K × 24 bits = 192 KB, which fits comfortably in M10K block RAM on a Cyclone V. So why not just use it?

Because 2,048 bins all need to read the table simultaneously, every clock cycle.

Block RAM on Cyclone V has two read ports, not 2,048. Time-multiplexing 2,048 reads through two ports means stretching one output sample period across hundreds of clocks per bin, which collapses the throughput target. Replicating the table 1,024 times to feed 2,048 readers consumes more on-chip memory than the device has.

You can use clever interpolation schemes (smaller table + linear or quadratic interpolation) to soften this, but every softening reintroduces a quantization that direct polynomial evaluation simply does not have. The polynomial pipeline has no memory accesses at all. It takes phase in at the front, produces sine out at the back, and consumes nothing but DSP blocks and a small amount of register file space along the way.

This is the property that, more than any other, made the architecture possible. Memory bandwidth was the rate-limiting resource. Removing it from the equation removed the limit.

Reason 4 — Phase precision preserved

In a lookup-table design, you index the table by quantizing your phase to the table's address width. If your table has 16-bit address space, your phase precision is 16 bits at the point of sine evaluation, regardless of how many bits you carried through the upstream NCO (numerically-controlled oscillator) accumulator.

This engine carries 32 bits of phase through the NCO accumulator, and all 32 bits feed directly into the polynomial. No quantization at the boundary. The 0.001 Hz frequency resolution claim — which is the claim that makes this engine interesting for material analysis and SDFT applications, not just music — depends on this property.

A polynomial does not care how many bits you give it. The accuracy is bounded by the truncation order of the series, not by the input precision. Give it 32 bits, give it 48 bits, give it whatever fits in your DSP block — the polynomial just consumes them and returns the corresponding sine.

This is the most subtle of the four reasons, but for a system whose central differentiator is fine frequency resolution, it is also the most consequential.

The Implementation Arena

The four reasons above force the choice of direct polynomial evaluation. They do not force the evaluation strategyThis is where the arena opens.

The 2020 C5G prototype used a particular evaluation strategy that suited the constraints of the time. The current DE10-nano build uses Horner's method as a reference implementation. Other engineers approaching this architecture, with or without LLM collaborators, will land on yet other strategies. All of them can be correct. The selection criteria — DSP-block efficiency, latency, fixed-point error budget, scalability of the polynomial degree — are the variables. The mathematics is the constant.

A few of the open dimensions, in no particular order:

Dimension A — How is x decomposed?

For phase input x = 2π · (ft mod 1), the constant  can be absorbed into the polynomial coefficients themselves. A coefficient table of (2π)ⁿ/n! (each pre-shifted to fit the DSP block input width) lets the pipeline operate on the fractional part of (ft) directly, with no upfront multiplication by .

This is the strategy the 2020 C5G prototype used. It saves a DSP block at the cost of a slightly larger coefficient table. It also opens an interesting variant: if (ft) is treated as a fixed-point value with leading-zero detection, the pipeline can adopt a simplified floating-point approach — selecting from a few pre-shifted coefficient sets according to the leading-zero depth of (ft). The result is precision that is preserved across the full input dynamic range without paying full floating-point hardware cost.

Dimension B — Horner, or accumulate-and-multiply?

Horner's method evaluates the polynomial as nested multiply-adds, working inward from the highest-order term:

sin(x) ≈ x · (1 − x²/6 · (1 − x²/20 · (1 − x²/42 · (1 − x²/72 · (1 − x²/110)))))

Each Horner stage is one DSP block. Total: roughly 7 blocks per pipeline (including  precomputation and the outer ·x).

The accumulate-and-multiply form evaluates the polynomial as a sum of independently-scaled terms, computing successive powers of x by repeated squaring or chaining, and accumulating the scaled results. This form parallelizes the term computations differently and may reach lower latency at the cost of more DSP blocks, depending on the multiplier topology of the target FPGA.

Neither is universally better. The right choice depends on whether the target device's DSP-block-to-LUT ratio is constraining, and on whether latency or area is the dominant cost.

Dimension C — What polynomial degree?

11th-order is what I chose. It hits 23-bit effective precision under the truncation bound, matching a 24-bit DAC with one guard bit.

But:

There is also a tantalizing possibility of adaptive degree selection — picking the polynomial depth at runtime based on the bin's amplitude or the spectral region's importance. A bin contributing 0.01% to the output sum does not need 23 bits of sine precision. Hardware that detects this and routes such bins through a shorter pipeline would save power without measurable audible cost. I have not explored this. Someone reading this might.

Dimension D — How is the error budget partitioned?

Final output precision is bounded by the worst of these noise sources:

  1. Polynomial truncation (controlled by degree)
  2. Coefficient quantization (controlled by coefficient bit-width)
  3. Intermediate fixed-point rounding (controlled by datapath bit-width)
  4. Phase input quantization (controlled by NCO width)

Each costs hardware. The interesting design choice is not "make all four very precise" — that overpays — but balance them so all four contribute roughly equal noise floors. There is no unique solution; different implementers will solve this allocation problem differently, and their choices will visibly differ in DSP-block budget and total error.

Dimension E — What about  precision uplift?

Horner's method computes  once and reuses it. The bit-width chosen for  directly affects the precision of every downstream Horner stage. Should  carry the same number of bits as x, or more? Carrying more bits costs a wider DSP block on that single multiplier; carrying fewer caps the precision of the entire polynomial regardless of degree. There is a non-obvious sweet spot here that depends on the target DSP block's input width.

These five dimensions are not exhaustive. They are the ones I have thought about. There are dimensions I have not seen yet. That is precisely what makes this an arena rather than a textbook problem.

The mathematics is the commons. The implementations are the contributions. An implementation is not a derivative of mine; it is your own answer to the same question.

What this is not

This log is not an attack on CORDIC or on lookup tables. Both are excellent techniques for the right context. CORDIC is unbeatable when you need sine and cosine and arbitrary rotation in one block. LUTs with interpolation are unbeatable when you have plentiful memory bandwidth and modest bin counts.

What this log is, instead, is a record of why direct polynomial evaluation — a technique that is conventionally treated as the boring textbook answer — turns out to be the right answer when your constraint is 2,048 bins per pipeline, one result per clock, no memory accesses allowed. The constraint forced the choice. The choice happened to be the most mathematically transparent option available, which is why I describe it as fortunate rather than clever.

And because the choice is mathematically transparent, the strategy for implementing it is not. That is the arena.

Open Prompt — what is being shared, and how

A reference Verilog skeleton implementing the Horner variant described here will be published as part of the Open Prompt sample set. The skeleton is one implementation, not the implementation. Other engineers regenerating this architecture will produce their own — using different decompositions, different polynomial degrees, different error budgets, different evaluation orders. Those implementations are theirs to publish or not publish, by their own judgment.

What is shared, in three layers:

  1. The architectural specification — the mathematics, the constraints, the reasoning that selects direct polynomial evaluation. This log is part of that layer.
  2. The reasoning trace — the actual design dialogues (with LLM collaborators, with myself, with future readers) that led from the constraints to the implementation. These will be archived as conversation logs.
  3. Sample implementations — the Verilog skeletons, MAX patches, and bin-pattern generators that I personally produce. Reference points, not blueprints.

The full Open Prompt declaration, including the formal three-layer structure and the GitHub repository template, is the subject of Build Log #4.

Coming next

Companion interactive page: https://dsohnaka.github.io/FPGA_Spectrum_Engine/


▼ Build Log 本文(日本語版・併記用)

なぜ11次マクローリンか? CORDIC でも LUT でもない理由 — そして実装はオープンなアリーナである理由

「FPGA で正弦波を10,240本生成する」と言うと、最初に返ってくる質問はほぼ決まって2つのうちのどちらかである:「CORDIC ですか?」あるいは「大きな LUT ですか?」

どちらでもない。本エンジンの全ビンは11次マクローリン級数の直接評価によって正弦値を生成する——固定レイテンシ DSP パイプライン上で計算される。1パイプラインあたり毎クロック1出力。5パイプライン並列。48 kHz サンプル周期あたり10,240の正弦波。

このログはなぜその選択——直接多項式評価——がアーキテクチャによって強制されたかについて述べる。同時に、より興味深い別の事柄についても述べる:いったんその選択がなされると、シリコン上での実際の評価戦略は大きく開かれている。数学は固定されている。実装はアリーナである。

数学的基礎

正弦のマクローリン級数は微積分の最初に出会うものの一つ:

sin(x) = x − x³/3! + x⁵/5! − x⁷/7! + x⁹/9! − x¹¹/11! + ···

x¹¹ 項で打ち切ったときの剰余項の上界:

|R₁₁(x)| ≤ |x|¹³ / 13!

入力範囲 |x| ≤ π/2 において、最悪誤差は約 1.3 × 10⁻⁷、これは有効精度約23ビットに相当する——24ビット DAC に対して、下流の量子化雑音蓄積に対する数ビットの余裕を持って十分な精度。

範囲縮小は自明。標準的な象限折り返しで入力を [0, π/2] に縮約する——パイプライン先頭で1個の比較器と最大1回の減算で済む。範囲縮小は本設計の興味深い部分ではない。

根拠1 — 検証可能性

多項式の係数は 1/n!——正確に。これは Remez 最適化の結果ではない。テーブルフィッティングでもない。ベンダー供給 IP ブロックの定数でもない。これらは数学的必然であり、教科書から2分で計算できる。

これが重要である理由は、本エンジン全体がオープンプロンプトとして配布されるからである(詳細は Build Log #4)。本実装の再生成を望む者は誰でも——LLM 補助の有無を問わず——係数を自身で導出できる。アーキテクチャ記述と一緒に転送しなければならない不透明なパラメータが存在しない。数学そのものが仕様である

CORDIC 実装は原理的には同じ性質を持つ(回転角は arctan(2⁻ᵏ))が、実践において FPGA 上の CORDIC 実装はマジックナンバーの茂みを伴う——ゲイン補償係数、マイクロ回転の順序、スケーリング調整——これらはアーキテクチャ間の翻訳に綺麗には生き残らない。多項式正弦は翻訳に完璧に生き残る——なぜなら多項式そのものが答えだからである。

根拠2 — パイプライン親和性

直接多項式評価は、パイプライン末端から毎クロック新しい正弦値が出てくる固定レイテンシ DSP パイプラインに対応する。そのパイプラインの正確な構造は実装選択である(後述)が、あらゆる合理的な構造が共通して持つ性質がある:多項式の次数はパイプラインの深さを決めるが、スループットは決めない。スループットは毎クロック1結果、それだけである。

これがパイプラインあたり2,048ビンを実現可能にする性質である。100 MHz では単一パイプラインが秒間1億の正弦値を生成する。48 kHz 出力レートでは出力サンプルあたり2,083サンプル分のスループット——これを切り下げて1モジュール2,048ビンとし、制御オーバーヘッドの余裕を残す。5モジュールで10,240。

CORDIC もパイプライン化はできるが、各 CORDIC 反復は部分精度の結果しか生成しない;24ビット精度には16〜20反復が必要で、各反復に独自のパイプライン段、各段に DSP ブロック1つまたは同等の論理リソースを消費する。Cyclone V の DSP ブロック予算——合計112ブロック——が「何ビン搭載できるか」のハード上限であり、直接多項式評価はこの予算を私が必要とした精度目標に対して CORDIC より効率的に使う。

根拠3 — メモリ競合の回避

LUT は明白な代替案である。16ビット入力・24ビット出力の正弦テーブルは 64K × 24bit = 192 KB、Cyclone V の M10K ブロック RAM に余裕で収まる。なぜ単に使わないのか?

2,048ビンが全員、毎クロック、同時にテーブルを読む必要があるからである。

Cyclone V のブロック RAM は読みポートが2つ、2,048ではない。2,048の読みを2ポートで時分割すると、ビンあたり1出力サンプル周期を数百クロックに引き伸ばすことになり、スループット目標が崩壊する。テーブルを1,024複製して2,048の読み手に対応させるとデバイス搭載量を超えるオンチップメモリを消費する。

賢い補間方式(小さなテーブル+線形または二次補間)でこれを和らげることもできるが、和らげるたびに直接多項式評価には存在しない量子化が再導入される。多項式パイプラインはメモリアクセスを一切行わない。先頭から位相が入り、末尾から正弦が出る、その間に消費するのは DSP ブロックと少量のレジスタファイル空間だけである。

これが他のどの理由よりも、本アーキテクチャを可能にした性質である。メモリ帯域がレート制限資源だった。それを方程式から除去することで、制限が消えた。

根拠4 — 位相精度の保存

LUT 設計では、テーブルのアドレス幅まで位相を量子化してテーブルにインデックスする。テーブルが16ビットアドレス空間なら、正弦評価点における位相精度は16ビット——上流の NCO アキュムレータで何ビット運んできたかには無関係に。

本エンジンは32ビットの位相を NCO アキュムレータで運び、32ビット全てを多項式に直接入力する。境界における量子化なし。0.001 Hz 周波数分解能の主張——音楽だけでなく材質分析や SDFT 応用に対して本エンジンを興味深いものにする主張——はこの性質に依存する。

多項式は何ビット与えるかを気にしない。精度は級数の打ち切り次数で決まるのであり、入力精度では決まらない。32ビット与えても、48ビット与えても、DSP ブロックに収まる任意のビット数を与えても——多項式はそれを消費して対応する正弦を返す。

これは4つの根拠の中で最も微妙だが、細かな周波数分解能を中心的差別化要因とするシステムにとっては、最も重大でもある。

実装のアリーナ

上記の4つの根拠は、直接多項式評価の選択を強制する。それらは評価戦略を強制しない。ここでアリーナが開かれる。

2020 年の C5G プロトタイプは当時の制約に適した特定の評価戦略を用いていた。現行の DE10-nano ビルドは Horner 法をリファレンス実装として用いている。本アーキテクチャに取り組む他のエンジニアたちは——LLM 協働者の有無を問わず——さらに別の戦略にたどり着くだろう。それらすべてが正しくあり得る。選択基準——DSP ブロック効率、レイテンシ、固定小数点誤差予算、多項式次数のスケーラビリティ——が変数である。数学は定数である。

開かれている次元のいくつかを、順不同で:

次元A — x をどう分解するか

位相入力 x = 2π · (ft mod 1) について、定数  は多項式係数自体に吸収できる。(2π)ⁿ/n! の係数表(それぞれが DSP ブロック入力幅に収まるよう事前シフト済み)を持てば、パイプラインは (ft) の小数部に対して直接動作でき、 を事前に乗じる必要がない。

これは 2020 C5G プロトタイプが用いた戦略である。係数表が若干大きくなる代わりに DSP ブロックを1個節約する。さらに興味深い変種を開く:もし (ft) を先行ゼロ検出付きの固定小数点値として扱えば、パイプラインは簡易浮動小数点アプローチを採用できる——(ft) の先行ゼロ深度に応じて、いくつかの事前シフト済み係数セットから選択する。結果として、入力ダイナミックレンジ全域で精度が保持され、しかもフル浮動小数点ハードウェアのコストを払わずに済む。

次元B — Horner か、累積積算か

Horner 法は多項式を入れ子の積和として評価する——最高次項から内側へ向けて:

sin(x) ≈ x · (1 − x²/6 · (1 − x²/20 · (1 − x²/42 · (1 − x²/72 · (1 − x²/110)))))

各 Horner 段が DSP ブロック1個。合計:パイプラインあたり約7ブロック( 事前計算と外側の ·x を含む)。

累積積算形式は多項式を独立にスケールされた項の総和として評価し、x の累乗は反復二乗または連鎖で計算し、スケールされた結果を累積する。この形式は項計算を異なる仕方で並列化し、対象 FPGA の乗算器トポロジに依存して、より多くの DSP ブロックを犠牲にして低レイテンシに到達し得る。

どちらが普遍的に優れているということはない。正解は対象デバイスの DSP ブロック対 LUT 比が制約となるか、レイテンシと面積のどちらが支配的コストかに依存する。

次元C — 多項式次数はいくつか

11次は私が選んだ次数である。打ち切り上界の下で23ビット有効精度に届き、24ビット DAC にガードビット1つ分の余裕で適合する。

しかし:

さらに魅惑的な可能性として、適応的次数選択がある——ビンの振幅やスペクトル領域の重要度に基づいて、実行時に多項式深度を選ぶ。出力総和に 0.01% しか寄与していないビンは23ビットの正弦精度を必要としない。これを検出してそのようなビンをより短いパイプラインに振り分けるハードウェアは、可聴コストなしに電力を節約するだろう。私はこれを探索していない。これを読む誰かが探索するかもしれない。

次元D — 誤差予算をどう分配するか

最終出力精度は以下の雑音源のうち最悪のものに支配される:

  1. 多項式打ち切り(次数で制御)
  2. 係数量子化(係数ビット幅で制御)
  3. 中間固定小数点丸め(データパスビット幅で制御)
  4. 位相入力量子化(NCO 幅で制御)

各々がハードウェアコストを伴う。興味深い設計選択は「四つすべてを非常に精密にする」ではない——それは過剰投資である——ではなく、4つすべての雑音床がほぼ等しく寄与するようバランスを取ることである。一意解は存在しない;異なる実装者はこの配分問題を異なる仕方で解き、その選択は DSP ブロック予算と総誤差に視覚的に異なって現れる。

次元E —  の精度上昇問題

Horner 法は  を一度計算し再利用する。 に選んだビット幅は下流の全 Horner 段の精度に直接影響する。 は x と同じビット数を運ぶべきか、それとも多くか? 多く運べばその単一乗算器のために幅広い DSP ブロックが必要になる;少なく運べば次数に関係なく多項式全体の精度が頭打ちになる。ここには対象 DSP ブロックの入力幅に依存する自明でない sweet spot がある。

これら5つの次元は網羅的ではない。私が考えてきたものに過ぎない。まだ私が見ていない次元がある。それこそが、これを教科書問題ではなくアリーナにしている。

数学は共有財産である。実装は貢献である。実装は私の派生物ではない;それは同じ問いに対するあなた自身の答えである。

これは何ではないか

このログは CORDIC や LUT への攻撃ではない。両者とも、適切な文脈においては優れた技術である。CORDIC は正弦かつ余弦かつ任意回転を一つのブロックで必要とするときには敵なしである。補間付き LUT はメモリ帯域が潤沢でビン数が控えめな場合には敵なしである。

このログがそうではなく何であるかといえば、直接多項式評価——慣習的に退屈な教科書解答として扱われる技法——が「パイプラインあたり2,048ビン、毎クロック1結果、メモリアクセスは禁止」という制約のもとでは正しい解答であった、という記録である。制約が選択を強制した。その選択がたまたま入手可能な選択肢の中で最も数学的に透明なものだった——これが、私がこの選択を「賢い」ではなく「幸運だった」と表現する理由である。

そして選択が数学的に透明であるがゆえに、それを実装する戦略はそうではない。それがアリーナである。

オープンプロンプト — 何が、どう共有されるか

ここに述べた Horner 変種のリファレンス Verilog スケルトンはオープンプロンプトのサンプルセットの一部として公開する予定である。このスケルトンは「一つの」実装であり、「この」実装ではない。 本アーキテクチャを再生成する他のエンジニアは各自の実装を生成するだろう——異なる分解戦略、異なる多項式次数、異なる誤差予算、異なる評価順序を用いて。それらの実装は各自のものであり、公開するもしないも各自の判断である。

共有されるものを3層に整理すると:

  1. アーキテクチャ仕様 — 数学、制約、直接多項式評価を選択する推論。このログはその層の一部である
  2. 推論軌跡 — 制約から実装へと導いた実際の設計対話(LLM 協働者との、自己との、未来の読者との対話)。会話ログとしてアーカイブされる
  3. サンプル実装 — 私が個人的に生成する Verilog スケルトン、MAX パッチ、ビンパターン生成器。リファレンスポイントであり設計図ではない

完全なオープンプロンプト宣言——形式的な3層構造と GitHub リポジトリのテンプレートを含む——は Build Log #4 の主題である。

次回予告

Companion interactive page: https://dsohnaka.github.io/FPGA_Spectrum_Engine/

Discussions