<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.0.1">Jekyll</generator><link href="https://john-gentile.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://john-gentile.com/" rel="alternate" type="text/html" /><updated>2026-05-04T04:04:04+00:00</updated><id>https://john-gentile.com/feed.xml</id><title type="html">John-Gentile</title><subtitle>Personal website and blog for John Gentile.</subtitle><author><name>John Gentile</name></author><entry><title type="html">FPGA DSP Design &amp;amp; Verification with LLMs</title><link href="https://john-gentile.com/2026/03/24/FPGA_DSP_Design_and_Verification_with_LLMs.html" rel="alternate" type="text/html" title="FPGA DSP Design &amp;amp; Verification with LLMs" /><published>2026-03-24T00:00:00+00:00</published><updated>2026-05-04T04:03:58+00:00</updated><id>https://john-gentile.com/2026/03/24/FPGA_DSP_Design_and_Verification_with_LLMs</id><content type="html" xml:base="https://john-gentile.com/2026/03/24/FPGA_DSP_Design_and_Verification_with_LLMs.html"><![CDATA[<p>Put simply, LLMs have gotten crazy good recently, going beyond typical software coding tasks, to even tackling HDL design. As those with experience in digital design know, this domain is not as simple to “code” as software: you’re really trying to coerce a chain of- often proprietary- EDA tools to map a desired function to a series of wires and hardware-specific primitives. Nonetheless, and even with much less open-source/high-quality training data, recent models have made leaps and bounds progress.</p>

<p>As an example, we want to design a canonical <a href="https://john-gentile.com/kb/dsp/NCO_DDS.html">Numerically Controlled Oscillator</a> for use in a DSP FPGA design. The following HDL was generated with <a href="https://claude.ai/">Claude Opus 4.6</a> with the below simple prompt and no other project nor system context:</p>
<blockquote>
  <p>Create a simple Numerically Controlled Oscillator (NCO) for use in an FPGA in modern/idiomatic SystemVerilog</p>
</blockquote>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">IPython.display</span> <span class="kn">import</span> <span class="n">Markdown</span> <span class="k">as</span> <span class="n">md</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'./FPGA_NCO_files/nco.sv'</span><span class="p">,</span> <span class="s">'r'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
    <span class="n">f_contents</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="n">read</span><span class="p">()</span>

<span class="n">md</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="se">\n</span><span class="s">```verilog</span><span class="se">\n</span><span class="si">{</span><span class="n">f_contents</span><span class="si">}</span><span class="se">\n</span><span class="s">```"</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-verilog highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// =============================================================================</span>
<span class="c1">// Numerically Controlled Oscillator (NCO)</span>
<span class="c1">// =============================================================================</span>
<span class="c1">//</span>
<span class="c1">// Architecture:</span>
<span class="c1">//   Phase Accumulator (N bits) → Truncated Phase (L bits) → Quarter-Wave LUT → Output</span>
<span class="c1">//</span>
<span class="c1">//   f_out = (FCW * f_clk) / 2^N</span>
<span class="c1">//   Phase resolution:  2π / 2^N</span>
<span class="c1">//   Spurious floor:    ~-6.02 * L dBc (truncation spurs before dithering)</span>
<span class="c1">//</span>
<span class="c1">// Quarter-wave symmetry exploits sin() symmetry to store only [0, π/2),</span>
<span class="c1">// reducing LUT depth from 2^L to 2^(L-2) entries.</span>
<span class="c1">//</span>
<span class="c1">// Parameters:</span>
<span class="c1">//   PHASE_ACC_W  – Phase accumulator width N (frequency resolution)</span>
<span class="c1">//   LUT_ADDR_W   – Truncated phase / LUT address width L (spur performance)</span>
<span class="c1">//   OUTPUT_W     – Output sample width (SNR ≈ 6.02*OUTPUT_W dB)</span>
<span class="c1">//   DITHER_EN    – Add 1-bit LFSR dither to truncated phase to whiten spurs</span>
<span class="c1">// =============================================================================</span>

<span class="k">module</span> <span class="n">nco</span> <span class="p">#(</span>
    <span class="k">parameter</span> <span class="kt">int</span> <span class="n">PHASE_ACC_W</span> <span class="o">=</span> <span class="mi">32</span><span class="p">,</span>
    <span class="k">parameter</span> <span class="kt">int</span> <span class="n">LUT_ADDR_W</span>  <span class="o">=</span> <span class="mi">12</span><span class="p">,</span>
    <span class="k">parameter</span> <span class="kt">int</span> <span class="n">OUTPUT_W</span>    <span class="o">=</span> <span class="mi">16</span><span class="p">,</span>
    <span class="k">parameter</span> <span class="kt">bit</span> <span class="n">DITHER_EN</span>   <span class="o">=</span> <span class="mb">1'b1</span>
<span class="p">)</span> <span class="p">(</span>
    <span class="kt">input</span>  <span class="kt">logic</span>                    <span class="n">clk</span><span class="p">,</span>
    <span class="kt">input</span>  <span class="kt">logic</span>                    <span class="n">rst_n</span><span class="p">,</span>

    <span class="c1">// Control</span>
    <span class="kt">input</span>  <span class="kt">logic</span> <span class="p">[</span><span class="n">PHASE_ACC_W</span><span class="o">-</span><span class="mi">1</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span>  <span class="n">fcw</span><span class="p">,</span>          <span class="c1">// Frequency control word</span>
    <span class="kt">input</span>  <span class="kt">logic</span> <span class="p">[</span><span class="n">PHASE_ACC_W</span><span class="o">-</span><span class="mi">1</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span>  <span class="n">phase_offset</span><span class="p">,</span> <span class="c1">// Phase modulation input</span>

    <span class="c1">// Output — signed two's complement</span>
    <span class="kt">output</span> <span class="kt">logic</span> <span class="kt">signed</span> <span class="p">[</span><span class="n">OUTPUT_W</span><span class="o">-</span><span class="mi">1</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span> <span class="n">sin_out</span><span class="p">,</span>
    <span class="kt">output</span> <span class="kt">logic</span> <span class="kt">signed</span> <span class="p">[</span><span class="n">OUTPUT_W</span><span class="o">-</span><span class="mi">1</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span> <span class="n">cos_out</span><span class="p">,</span>
    <span class="kt">output</span> <span class="kt">logic</span>                       <span class="n">valid</span>
<span class="p">);</span>

    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="c1">// Derived parameters</span>
    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="k">localparam</span> <span class="kt">int</span> <span class="n">QUARTER_DEPTH</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">**</span> <span class="p">(</span><span class="n">LUT_ADDR_W</span> <span class="o">-</span> <span class="mi">2</span><span class="p">);</span> <span class="c1">// π/2 worth of entries</span>
    <span class="k">localparam</span> <span class="kt">int</span> <span class="n">DISCARD_BITS</span>  <span class="o">=</span> <span class="n">PHASE_ACC_W</span> <span class="o">-</span> <span class="n">LUT_ADDR_W</span><span class="p">;</span>

    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="c1">// Phase accumulator</span>
    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="kt">logic</span> <span class="p">[</span><span class="n">PHASE_ACC_W</span><span class="o">-</span><span class="mi">1</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span> <span class="n">phase_acc</span><span class="p">;</span>

    <span class="k">always_ff</span> <span class="o">@</span><span class="p">(</span><span class="kt">posedge</span> <span class="n">clk</span> <span class="kt">or</span> <span class="kt">negedge</span> <span class="n">rst_n</span><span class="p">)</span> <span class="k">begin</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">rst_n</span><span class="p">)</span>
            <span class="n">phase_acc</span> <span class="o">&lt;=</span> <span class="mb">'0</span><span class="p">;</span>
        <span class="k">else</span>
            <span class="n">phase_acc</span> <span class="o">&lt;=</span> <span class="n">phase_acc</span> <span class="o">+</span> <span class="n">fcw</span><span class="p">;</span> <span class="c1">// free-running, wraps modulo 2^N</span>
    <span class="k">end</span>

    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="c1">// Optional truncation dither (LFSR-based)</span>
    <span class="c1">//   Adds pseudorandom noise into discarded LSBs to whiten truncation spurs.</span>
    <span class="c1">//   Galois LFSR, maximal-length for 16-bit: x^16 + x^14 + x^13 + x^11 + 1</span>
    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="kt">logic</span> <span class="p">[</span><span class="mi">15</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span> <span class="n">lfsr</span><span class="p">;</span>

    <span class="k">generate</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">DITHER_EN</span><span class="p">)</span> <span class="k">begin</span> <span class="o">:</span> <span class="n">gen_dither</span>
            <span class="k">always_ff</span> <span class="o">@</span><span class="p">(</span><span class="kt">posedge</span> <span class="n">clk</span> <span class="kt">or</span> <span class="kt">negedge</span> <span class="n">rst_n</span><span class="p">)</span> <span class="k">begin</span>
                <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">rst_n</span><span class="p">)</span>
                    <span class="n">lfsr</span> <span class="o">&lt;=</span> <span class="mh">16'hACE1</span><span class="p">;</span> <span class="c1">// nonzero seed</span>
                <span class="k">else</span>
                    <span class="n">lfsr</span> <span class="o">&lt;=</span> <span class="o">{</span><span class="n">lfsr</span><span class="p">[</span><span class="mi">14</span><span class="o">:</span><span class="mi">0</span><span class="p">],</span> <span class="n">lfsr</span><span class="p">[</span><span class="mi">15</span><span class="p">]</span> <span class="o">^</span> <span class="n">lfsr</span><span class="p">[</span><span class="mi">13</span><span class="p">]</span> <span class="o">^</span> <span class="n">lfsr</span><span class="p">[</span><span class="mi">12</span><span class="p">]</span> <span class="o">^</span> <span class="n">lfsr</span><span class="p">[</span><span class="mi">10</span><span class="p">]</span><span class="o">}</span><span class="p">;</span>
            <span class="k">end</span>
        <span class="k">end</span> <span class="k">else</span> <span class="k">begin</span> <span class="o">:</span> <span class="n">gen_no_dither</span>
            <span class="k">assign</span> <span class="n">lfsr</span> <span class="o">=</span> <span class="mb">'0</span><span class="p">;</span>
        <span class="k">end</span>
    <span class="k">endgenerate</span>

    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="c1">// Phase truncation + offset + dither → LUT address</span>
    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="kt">logic</span> <span class="p">[</span><span class="n">PHASE_ACC_W</span><span class="o">-</span><span class="mi">1</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span> <span class="n">phase_total</span><span class="p">;</span>
    <span class="kt">logic</span> <span class="p">[</span><span class="n">LUT_ADDR_W</span><span class="o">-</span><span class="mi">1</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span>  <span class="n">phase_trunc</span><span class="p">;</span>

    <span class="k">assign</span> <span class="n">phase_total</span> <span class="o">=</span> <span class="n">phase_acc</span> <span class="o">+</span> <span class="n">phase_offset</span><span class="p">;</span>

    <span class="c1">// Add dither into the rounding region then truncate</span>
    <span class="k">always_comb</span> <span class="k">begin</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">DITHER_EN</span> <span class="o">&amp;&amp;</span> <span class="n">DISCARD_BITS</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="k">begin</span>
            <span class="k">automatic</span> <span class="kt">logic</span> <span class="p">[</span><span class="n">PHASE_ACC_W</span><span class="o">-</span><span class="mi">1</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span> <span class="n">dithered</span><span class="p">;</span>
            <span class="n">dithered</span>    <span class="o">=</span> <span class="n">phase_total</span> <span class="o">+</span> <span class="o">{{</span><span class="n">LUT_ADDR_W</span><span class="o">{</span><span class="mb">1'b0</span><span class="o">}}</span><span class="p">,</span> <span class="n">lfsr</span><span class="p">[</span><span class="n">DISCARD_BITS</span><span class="o">-</span><span class="mi">1</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span><span class="o">}</span><span class="p">;</span>
            <span class="n">phase_trunc</span> <span class="o">=</span> <span class="n">dithered</span><span class="p">[</span><span class="n">PHASE_ACC_W</span><span class="o">-</span><span class="mi">1</span> <span class="o">-:</span> <span class="n">LUT_ADDR_W</span><span class="p">];</span>
        <span class="k">end</span> <span class="k">else</span> <span class="k">begin</span>
            <span class="n">phase_trunc</span> <span class="o">=</span> <span class="n">phase_total</span><span class="p">[</span><span class="n">PHASE_ACC_W</span><span class="o">-</span><span class="mi">1</span> <span class="o">-:</span> <span class="n">LUT_ADDR_W</span><span class="p">];</span>
        <span class="k">end</span>
    <span class="k">end</span>

    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="c1">// Quarter-wave LUT with symmetry decomposition</span>
    <span class="c1">//</span>
    <span class="c1">//   quadrant[1:0] = phase_trunc MSBs</span>
    <span class="c1">//   Q0 (00): sin(θ)        =  LUT[ addr          ]</span>
    <span class="c1">//   Q1 (01): sin(π-θ)      =  LUT[ ~addr         ]   (mirror)</span>
    <span class="c1">//   Q2 (10): sin(π+θ)      = -LUT[ addr          ]   (negate)</span>
    <span class="c1">//   Q3 (11): sin(2π-θ)     = -LUT[ ~addr         ]   (mirror+negate)</span>
    <span class="c1">//</span>
    <span class="c1">//   cos(θ) = sin(θ + π/2), so feed (phase_trunc + 2^(L-2)) into same logic.</span>
    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="kt">logic</span> <span class="p">[</span><span class="mi">1</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span>               <span class="n">sin_quad</span><span class="p">,</span> <span class="n">cos_quad</span><span class="p">;</span>
    <span class="kt">logic</span> <span class="p">[</span><span class="n">LUT_ADDR_W</span><span class="o">-</span><span class="mi">3</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span>    <span class="n">sin_addr</span><span class="p">,</span> <span class="n">cos_addr</span><span class="p">;</span>
    <span class="kt">logic</span>                     <span class="n">sin_neg</span><span class="p">,</span>  <span class="n">cos_neg</span><span class="p">;</span>
    <span class="kt">logic</span>                     <span class="n">sin_mirror</span><span class="p">,</span> <span class="n">cos_mirror</span><span class="p">;</span>

    <span class="c1">// Cosine phase = sin phase + π/2 (add 1 to quadrant)</span>
    <span class="kt">logic</span> <span class="p">[</span><span class="n">LUT_ADDR_W</span><span class="o">-</span><span class="mi">1</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span> <span class="n">cos_phase</span><span class="p">;</span>
    <span class="k">assign</span> <span class="n">cos_phase</span> <span class="o">=</span> <span class="n">phase_trunc</span> <span class="o">+</span> <span class="n">LUT_ADDR_W</span><span class="err">'</span><span class="p">(</span><span class="n">QUARTER_DEPTH</span><span class="p">);</span> <span class="c1">// +2^(L-2)</span>

    <span class="k">assign</span> <span class="n">sin_quad</span>   <span class="o">=</span> <span class="n">phase_trunc</span><span class="p">[</span><span class="n">LUT_ADDR_W</span><span class="o">-</span><span class="mi">1</span> <span class="o">-:</span> <span class="mi">2</span><span class="p">];</span>
    <span class="k">assign</span> <span class="n">cos_quad</span>   <span class="o">=</span> <span class="n">cos_phase</span>  <span class="p">[</span><span class="n">LUT_ADDR_W</span><span class="o">-</span><span class="mi">1</span> <span class="o">-:</span> <span class="mi">2</span><span class="p">];</span>

    <span class="k">assign</span> <span class="n">sin_mirror</span> <span class="o">=</span> <span class="n">sin_quad</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
    <span class="k">assign</span> <span class="n">cos_mirror</span> <span class="o">=</span> <span class="n">cos_quad</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
    <span class="k">assign</span> <span class="n">sin_neg</span>    <span class="o">=</span> <span class="n">sin_quad</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
    <span class="k">assign</span> <span class="n">cos_neg</span>    <span class="o">=</span> <span class="n">cos_quad</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>

    <span class="k">assign</span> <span class="n">sin_addr</span> <span class="o">=</span> <span class="n">sin_mirror</span> <span class="o">?</span> <span class="o">~</span><span class="n">phase_trunc</span><span class="p">[</span><span class="n">LUT_ADDR_W</span><span class="o">-</span><span class="mi">3</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span>
                                 <span class="o">:</span>  <span class="n">phase_trunc</span><span class="p">[</span><span class="n">LUT_ADDR_W</span><span class="o">-</span><span class="mi">3</span><span class="o">:</span><span class="mi">0</span><span class="p">];</span>
    <span class="k">assign</span> <span class="n">cos_addr</span> <span class="o">=</span> <span class="n">cos_mirror</span> <span class="o">?</span> <span class="o">~</span><span class="n">cos_phase</span><span class="p">[</span><span class="n">LUT_ADDR_W</span><span class="o">-</span><span class="mi">3</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span>
                                 <span class="o">:</span>  <span class="n">cos_phase</span><span class="p">[</span><span class="n">LUT_ADDR_W</span><span class="o">-</span><span class="mi">3</span><span class="o">:</span><span class="mi">0</span><span class="p">];</span>

    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="c1">// LUT storage — inferred as block RAM (single-port, registered output)</span>
    <span class="c1">// Stores unsigned magnitude: round(2^(OUTPUT_W-1) - 1) * sin(2π·k / 4·DEPTH)</span>
    <span class="c1">// for k in [0, QUARTER_DEPTH).</span>
    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="kt">logic</span> <span class="p">[</span><span class="n">OUTPUT_W</span><span class="o">-</span><span class="mi">2</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span> <span class="n">sin_lut</span> <span class="p">[</span><span class="n">QUARTER_DEPTH</span><span class="p">];</span> <span class="c1">// unsigned magnitude</span>
    <span class="kt">logic</span> <span class="p">[</span><span class="n">OUTPUT_W</span><span class="o">-</span><span class="mi">2</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span> <span class="n">cos_lut</span> <span class="p">[</span><span class="n">QUARTER_DEPTH</span><span class="p">];</span>

    <span class="k">initial</span> <span class="k">begin</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">k</span> <span class="o">&lt;</span> <span class="n">QUARTER_DEPTH</span><span class="p">;</span> <span class="n">k</span><span class="o">++</span><span class="p">)</span> <span class="k">begin</span>
            <span class="n">sin_lut</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="n">OUTPUT_W</span><span class="err">'</span><span class="p">($</span><span class="nb">rtoi</span><span class="p">(</span>
                <span class="p">$</span><span class="nb">floor</span><span class="p">(((</span><span class="mf">2.0</span> <span class="o">**</span> <span class="p">(</span><span class="n">OUTPUT_W</span> <span class="o">-</span> <span class="mi">1</span><span class="p">))</span> <span class="o">-</span> <span class="mf">1.0</span><span class="p">)</span>
                       <span class="o">*</span> <span class="p">$</span><span class="nb">sin</span><span class="p">(</span><span class="mf">2.0</span> <span class="o">*</span> <span class="mf">3.14159265358979323846</span> <span class="o">*</span> <span class="kt">real</span><span class="err">'</span><span class="p">(</span><span class="n">k</span><span class="p">)</span> <span class="o">/</span> <span class="kt">real</span><span class="err">'</span><span class="p">(</span><span class="mi">4</span> <span class="o">*</span> <span class="n">QUARTER_DEPTH</span><span class="p">))</span>
                       <span class="o">+</span> <span class="mf">0.5</span><span class="p">)</span>
            <span class="p">));</span>
            <span class="n">cos_lut</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="n">sin_lut</span><span class="p">[</span><span class="n">k</span><span class="p">];</span> <span class="c1">// same data, separate copy for dual-port inference</span>
        <span class="k">end</span>
    <span class="k">end</span>

    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="c1">// Pipeline stage 1: LUT read (registered for BRAM inference)</span>
    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="kt">logic</span> <span class="p">[</span><span class="n">OUTPUT_W</span><span class="o">-</span><span class="mi">2</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span> <span class="n">sin_mag_r</span><span class="p">,</span> <span class="n">cos_mag_r</span><span class="p">;</span>
    <span class="kt">logic</span>                <span class="n">sin_neg_r</span><span class="p">,</span> <span class="n">cos_neg_r</span><span class="p">;</span>

    <span class="k">always_ff</span> <span class="o">@</span><span class="p">(</span><span class="kt">posedge</span> <span class="n">clk</span><span class="p">)</span> <span class="k">begin</span>
        <span class="n">sin_mag_r</span> <span class="o">&lt;=</span> <span class="n">sin_lut</span><span class="p">[</span><span class="n">sin_addr</span><span class="p">];</span>
        <span class="n">cos_mag_r</span> <span class="o">&lt;=</span> <span class="n">cos_lut</span><span class="p">[</span><span class="n">cos_addr</span><span class="p">];</span>
        <span class="n">sin_neg_r</span> <span class="o">&lt;=</span> <span class="n">sin_neg</span><span class="p">;</span>
        <span class="n">cos_neg_r</span> <span class="o">&lt;=</span> <span class="n">cos_neg</span><span class="p">;</span>
    <span class="k">end</span>

    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="c1">// Pipeline stage 2: sign application → two's complement output</span>
    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="k">always_ff</span> <span class="o">@</span><span class="p">(</span><span class="kt">posedge</span> <span class="n">clk</span> <span class="kt">or</span> <span class="kt">negedge</span> <span class="n">rst_n</span><span class="p">)</span> <span class="k">begin</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">rst_n</span><span class="p">)</span> <span class="k">begin</span>
            <span class="n">sin_out</span> <span class="o">&lt;=</span> <span class="mb">'0</span><span class="p">;</span>
            <span class="n">cos_out</span> <span class="o">&lt;=</span> <span class="mb">'0</span><span class="p">;</span>
        <span class="k">end</span> <span class="k">else</span> <span class="k">begin</span>
            <span class="n">sin_out</span> <span class="o">&lt;=</span> <span class="n">sin_neg_r</span> <span class="o">?</span> <span class="o">-</span><span class="kt">signed</span><span class="err">'</span><span class="p">(</span><span class="o">{</span><span class="mb">1'b0</span><span class="p">,</span> <span class="n">sin_mag_r</span><span class="o">}</span><span class="p">)</span>
                                 <span class="o">:</span>  <span class="kt">signed</span><span class="err">'</span><span class="p">(</span><span class="o">{</span><span class="mb">1'b0</span><span class="p">,</span> <span class="n">sin_mag_r</span><span class="o">}</span><span class="p">);</span>
            <span class="n">cos_out</span> <span class="o">&lt;=</span> <span class="n">cos_neg_r</span> <span class="o">?</span> <span class="o">-</span><span class="kt">signed</span><span class="err">'</span><span class="p">(</span><span class="o">{</span><span class="mb">1'b0</span><span class="p">,</span> <span class="n">cos_mag_r</span><span class="o">}</span><span class="p">)</span>
                                 <span class="o">:</span>  <span class="kt">signed</span><span class="err">'</span><span class="p">(</span><span class="o">{</span><span class="mb">1'b0</span><span class="p">,</span> <span class="n">cos_mag_r</span><span class="o">}</span><span class="p">);</span>
        <span class="k">end</span>
    <span class="k">end</span>

    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="c1">// Valid strobe — accounts for 2-cycle pipeline latency</span>
    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="kt">logic</span> <span class="p">[</span><span class="mi">1</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span> <span class="n">pipe_valid</span><span class="p">;</span>

    <span class="k">always_ff</span> <span class="o">@</span><span class="p">(</span><span class="kt">posedge</span> <span class="n">clk</span> <span class="kt">or</span> <span class="kt">negedge</span> <span class="n">rst_n</span><span class="p">)</span> <span class="k">begin</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">rst_n</span><span class="p">)</span>
            <span class="n">pipe_valid</span> <span class="o">&lt;=</span> <span class="mb">'0</span><span class="p">;</span>
        <span class="k">else</span>
            <span class="n">pipe_valid</span> <span class="o">&lt;=</span> <span class="o">{</span><span class="n">pipe_valid</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="mb">1'b1</span><span class="o">}</span><span class="p">;</span>
    <span class="k">end</span>

    <span class="k">assign</span> <span class="n">valid</span> <span class="o">=</span> <span class="n">pipe_valid</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>

    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="c1">// Assertions (simulation only)</span>
    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="c1">// synthesis translate_off</span>
    <span class="k">initial</span> <span class="k">begin</span>
        <span class="k">assert</span> <span class="p">(</span><span class="n">LUT_ADDR_W</span> <span class="o">&gt;=</span> <span class="mi">4</span><span class="p">)</span>
            <span class="k">else</span> <span class="p">$</span><span class="n">fatal</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s">"LUT_ADDR_W must be &gt;= 4 for quarter-wave decomposition"</span><span class="p">);</span>
        <span class="k">assert</span> <span class="p">(</span><span class="n">PHASE_ACC_W</span> <span class="o">&gt;=</span> <span class="n">LUT_ADDR_W</span><span class="p">)</span>
            <span class="k">else</span> <span class="p">$</span><span class="n">fatal</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s">"PHASE_ACC_W must be &gt;= LUT_ADDR_W"</span><span class="p">);</span>
        <span class="k">assert</span> <span class="p">(</span><span class="n">OUTPUT_W</span> <span class="o">&gt;=</span> <span class="mi">2</span><span class="p">)</span>
            <span class="k">else</span> <span class="p">$</span><span class="n">fatal</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s">"OUTPUT_W must be &gt;= 2 for signed representation"</span><span class="p">);</span>
    <span class="k">end</span>
    <span class="c1">// synthesis translate_on</span>

<span class="k">endmodule</span>

</code></pre></div></div>

<p>^ this looks pretty good for one-shot output! I mean, even with “simple” in the prompt, it went beyond the basic “phase accumulator -&gt; LUT” design and right to implementing:</p>
<ul>
  <li>A quarter-wave LUT to save on space with built-in <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>sin</mi><mo>⁡</mo></mrow><annotation encoding="application/x-tex">\sin</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6679em;"></span><span class="mop">sin</span></span></span></span> value generation within the code (no external memory loading required)!</li>
  <li>Built-in LFSR for phase dithering to increase SFDR.</li>
  <li>Assertions to check for proper instantiation and usage.</li>
</ul>

<p>But now we have to verify the design- let’s switch to <a href="https://code.claude.com/docs/en/overview">Claude Code</a> to give the context of our <code class="highlighter-rouge">nco.sv</code> HDL file and let it generate a testbench and <code class="highlighter-rouge">Makefile</code> for use by <a href="https://www.veripool.org/verilator/">Verilator</a>:</p>
<blockquote>
  <p>@nco.sv is a simple Numerically Controlled Oscillator in SystemVerilog. We want to write a simple unit test to verify basic output- write a simple SystemVerilog testbench as well as Makefile targeting Verilator simulator (Verilator is already installed on the system).</p>
</blockquote>

<p>Claude goes right to work making the following testbench, but not without using it’s Bash tool first to find the current Verilator version installed (<code class="highlighter-rouge">$$ verilator --version</code>) to make sure the testbench matches supported conventions (brilliant!):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'./FPGA_NCO_files/nco_tb.sv'</span><span class="p">,</span> <span class="s">'r'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
    <span class="n">f_contents</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="n">read</span><span class="p">()</span>

<span class="n">md</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="se">\n</span><span class="s">```verilog</span><span class="se">\n</span><span class="si">{</span><span class="n">f_contents</span><span class="si">}</span><span class="se">\n</span><span class="s">```"</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-verilog highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// =============================================================================</span>
<span class="c1">// NCO Testbench</span>
<span class="c1">// =============================================================================</span>
<span class="c1">// Basic verification:</span>
<span class="c1">//   1. Reset behavior — outputs zero, valid deasserted</span>
<span class="c1">//   2. Valid strobe asserts after 2-cycle pipeline latency</span>
<span class="c1">//   3. DC check — FCW=0 produces constant output</span>
<span class="c1">//   4. Sine/cosine quadrature — cos leads sin by ~90°</span>
<span class="c1">//   5. Output bounded within signed range</span>
<span class="c1">// =============================================================================</span>

<span class="k">module</span> <span class="n">nco_tb</span><span class="p">;</span>

    <span class="c1">// Use small parameters for fast simulation</span>
    <span class="k">localparam</span> <span class="kt">int</span> <span class="n">PHASE_ACC_W</span> <span class="o">=</span> <span class="mi">16</span><span class="p">;</span>
    <span class="k">localparam</span> <span class="kt">int</span> <span class="n">LUT_ADDR_W</span>  <span class="o">=</span> <span class="mi">8</span><span class="p">;</span>
    <span class="k">localparam</span> <span class="kt">int</span> <span class="n">OUTPUT_W</span>    <span class="o">=</span> <span class="mi">12</span><span class="p">;</span>

    <span class="kt">logic</span>                          <span class="n">clk</span><span class="p">;</span>
    <span class="kt">logic</span>                          <span class="n">rst_n</span><span class="p">;</span>
    <span class="kt">logic</span> <span class="p">[</span><span class="n">PHASE_ACC_W</span><span class="o">-</span><span class="mi">1</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span>       <span class="n">fcw</span><span class="p">;</span>
    <span class="kt">logic</span> <span class="p">[</span><span class="n">PHASE_ACC_W</span><span class="o">-</span><span class="mi">1</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span>       <span class="n">phase_offset</span><span class="p">;</span>
    <span class="kt">logic</span> <span class="kt">signed</span> <span class="p">[</span><span class="n">OUTPUT_W</span><span class="o">-</span><span class="mi">1</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span>   <span class="n">sin_out</span><span class="p">;</span>
    <span class="kt">logic</span> <span class="kt">signed</span> <span class="p">[</span><span class="n">OUTPUT_W</span><span class="o">-</span><span class="mi">1</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span>   <span class="n">cos_out</span><span class="p">;</span>
    <span class="kt">logic</span>                          <span class="n">valid</span><span class="p">;</span>

    <span class="n">nco</span> <span class="p">#(</span>
        <span class="p">.</span><span class="n">PHASE_ACC_W</span> <span class="p">(</span><span class="n">PHASE_ACC_W</span><span class="p">),</span>
        <span class="p">.</span><span class="n">LUT_ADDR_W</span>  <span class="p">(</span><span class="n">LUT_ADDR_W</span><span class="p">),</span>
        <span class="p">.</span><span class="n">OUTPUT_W</span>    <span class="p">(</span><span class="n">OUTPUT_W</span><span class="p">),</span>
        <span class="p">.</span><span class="n">DITHER_EN</span>   <span class="p">(</span><span class="mb">1'b0</span><span class="p">)</span>         <span class="c1">// disable dither for deterministic checks</span>
    <span class="p">)</span> <span class="n">dut</span> <span class="p">(</span>
        <span class="p">.</span><span class="n">clk</span>          <span class="p">(</span><span class="n">clk</span><span class="p">),</span>
        <span class="p">.</span><span class="n">rst_n</span>        <span class="p">(</span><span class="n">rst_n</span><span class="p">),</span>
        <span class="p">.</span><span class="n">fcw</span>          <span class="p">(</span><span class="n">fcw</span><span class="p">),</span>
        <span class="p">.</span><span class="n">phase_offset</span> <span class="p">(</span><span class="n">phase_offset</span><span class="p">),</span>
        <span class="p">.</span><span class="n">sin_out</span>      <span class="p">(</span><span class="n">sin_out</span><span class="p">),</span>
        <span class="p">.</span><span class="n">cos_out</span>      <span class="p">(</span><span class="n">cos_out</span><span class="p">),</span>
        <span class="p">.</span><span class="n">valid</span>        <span class="p">(</span><span class="n">valid</span><span class="p">)</span>
    <span class="p">);</span>

    <span class="c1">// Clock: 10 ns period</span>
    <span class="k">initial</span> <span class="n">clk</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">always</span> <span class="p">#</span><span class="mi">5</span> <span class="n">clk</span> <span class="o">=</span> <span class="o">~</span><span class="n">clk</span><span class="p">;</span>

    <span class="c1">// Max positive value the LUT can produce</span>
    <span class="k">localparam</span> <span class="kt">int</span> <span class="n">MAX_MAG</span> <span class="o">=</span> <span class="p">(</span><span class="mi">2</span> <span class="o">**</span> <span class="p">(</span><span class="n">OUTPUT_W</span> <span class="o">-</span> <span class="mi">1</span><span class="p">))</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>

    <span class="kt">int</span> <span class="n">errors</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

    <span class="c1">// Helper: run N clock cycles</span>
    <span class="k">task</span> <span class="k">automatic</span> <span class="n">tick</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span> <span class="o">=</span> <span class="mi">1</span><span class="p">);</span>
        <span class="kt">repeat</span> <span class="p">(</span><span class="n">n</span><span class="p">)</span> <span class="o">@</span><span class="p">(</span><span class="kt">posedge</span> <span class="n">clk</span><span class="p">);</span>
    <span class="k">endtask</span>

    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="c1">// Main test sequence</span>
    <span class="c1">// -------------------------------------------------------------------------</span>
    <span class="k">initial</span> <span class="k">begin</span>
        <span class="p">$</span><span class="nb">dumpfile</span><span class="p">(</span><span class="s">"nco_tb.vcd"</span><span class="p">);</span>
        <span class="p">$</span><span class="nb">dumpvars</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">nco_tb</span><span class="p">);</span>

        <span class="c1">// ---- Init ----</span>
        <span class="n">rst_n</span>        <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="n">fcw</span>          <span class="o">=</span> <span class="mb">'0</span><span class="p">;</span>
        <span class="n">phase_offset</span> <span class="o">=</span> <span class="mb">'0</span><span class="p">;</span>
        <span class="n">tick</span><span class="p">(</span><span class="mi">4</span><span class="p">);</span>

        <span class="c1">// ---- Test 1: Reset behaviour ----</span>
        <span class="p">$</span><span class="nb">display</span><span class="p">(</span><span class="s">"[TEST 1] Reset behavior"</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">sin_out</span> <span class="o">!==</span> <span class="mb">'0</span> <span class="o">||</span> <span class="n">cos_out</span> <span class="o">!==</span> <span class="mb">'0</span><span class="p">)</span> <span class="k">begin</span>
            <span class="p">$</span><span class="nb">display</span><span class="p">(</span><span class="s">"  FAIL: outputs not zero in reset (sin=%0d cos=%0d)"</span><span class="p">,</span> <span class="n">sin_out</span><span class="p">,</span> <span class="n">cos_out</span><span class="p">);</span>
            <span class="n">errors</span><span class="o">++</span><span class="p">;</span>
        <span class="k">end</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">valid</span> <span class="o">!==</span> <span class="mb">1'b0</span><span class="p">)</span> <span class="k">begin</span>
            <span class="p">$</span><span class="nb">display</span><span class="p">(</span><span class="s">"  FAIL: valid asserted during reset"</span><span class="p">);</span>
            <span class="n">errors</span><span class="o">++</span><span class="p">;</span>
        <span class="k">end</span>

        <span class="c1">// ---- Release reset ----</span>
        <span class="o">@</span><span class="p">(</span><span class="kt">posedge</span> <span class="n">clk</span><span class="p">)</span> <span class="n">rst_n</span> <span class="o">&lt;=</span> <span class="mi">1</span><span class="p">;</span>

        <span class="c1">// ---- Test 2: Valid strobe latency ----</span>
        <span class="p">$</span><span class="nb">display</span><span class="p">(</span><span class="s">"[TEST 2] Valid strobe latency"</span><span class="p">);</span>
        <span class="n">tick</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">valid</span> <span class="o">!==</span> <span class="mb">1'b0</span><span class="p">)</span> <span class="k">begin</span>
            <span class="p">$</span><span class="nb">display</span><span class="p">(</span><span class="s">"  FAIL: valid high too early (cycle 1)"</span><span class="p">);</span>
            <span class="n">errors</span><span class="o">++</span><span class="p">;</span>
        <span class="k">end</span>
        <span class="n">tick</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>  <span class="c1">// 2 cycles after reset release</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">valid</span> <span class="o">!==</span> <span class="mb">1'b1</span><span class="p">)</span> <span class="k">begin</span>
            <span class="p">$</span><span class="nb">display</span><span class="p">(</span><span class="s">"  FAIL: valid not asserted after 2 pipeline stages"</span><span class="p">);</span>
            <span class="n">errors</span><span class="o">++</span><span class="p">;</span>
        <span class="k">end</span>

        <span class="c1">// ---- Test 3: FCW=0 → constant output ----</span>
        <span class="p">$</span><span class="nb">display</span><span class="p">(</span><span class="s">"[TEST 3] FCW=0 constant output"</span><span class="p">);</span>
        <span class="n">fcw</span> <span class="o">=</span> <span class="mb">'0</span><span class="p">;</span>
        <span class="n">tick</span><span class="p">(</span><span class="mi">10</span><span class="p">);</span>
        <span class="k">begin</span>
            <span class="kt">logic</span> <span class="kt">signed</span> <span class="p">[</span><span class="n">OUTPUT_W</span><span class="o">-</span><span class="mi">1</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span> <span class="n">s0</span><span class="p">,</span> <span class="n">c0</span><span class="p">,</span> <span class="n">s1</span><span class="p">,</span> <span class="n">c1</span><span class="p">;</span>
            <span class="n">s0</span> <span class="o">=</span> <span class="n">sin_out</span><span class="p">;</span> <span class="n">c0</span> <span class="o">=</span> <span class="n">cos_out</span><span class="p">;</span>
            <span class="n">tick</span><span class="p">(</span><span class="mi">5</span><span class="p">);</span>
            <span class="n">s1</span> <span class="o">=</span> <span class="n">sin_out</span><span class="p">;</span> <span class="n">c1</span> <span class="o">=</span> <span class="n">cos_out</span><span class="p">;</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">s0</span> <span class="o">!==</span> <span class="n">s1</span> <span class="o">||</span> <span class="n">c0</span> <span class="o">!==</span> <span class="n">c1</span><span class="p">)</span> <span class="k">begin</span>
                <span class="p">$</span><span class="nb">display</span><span class="p">(</span><span class="s">"  FAIL: output changed with FCW=0"</span><span class="p">);</span>
                <span class="n">errors</span><span class="o">++</span><span class="p">;</span>
            <span class="k">end</span>
        <span class="k">end</span>

        <span class="c1">// ---- Test 4: Run a tone — check outputs stay bounded ----</span>
        <span class="c1">// FCW = 2^(N-4) gives f_out = f_clk/16 — completes one full cycle in 16 samples</span>
        <span class="p">$</span><span class="nb">display</span><span class="p">(</span><span class="s">"[TEST 4] Bounded output with active tone"</span><span class="p">);</span>
        <span class="n">fcw</span> <span class="o">=</span> <span class="mi">16</span><span class="err">'</span><span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="p">(</span><span class="n">PHASE_ACC_W</span> <span class="o">-</span> <span class="mi">4</span><span class="p">));</span>
        <span class="n">tick</span><span class="p">(</span><span class="mi">4</span><span class="p">);</span> <span class="c1">// flush pipeline</span>
        <span class="k">begin</span>
            <span class="kt">int</span> <span class="n">bounded_ok</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
            <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">64</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="k">begin</span>
                <span class="n">tick</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">sin_out</span> <span class="o">&gt;</span> <span class="n">MAX_MAG</span> <span class="o">||</span> <span class="n">sin_out</span> <span class="o">&lt;</span> <span class="o">-</span><span class="n">MAX_MAG</span> <span class="o">||</span>
                    <span class="n">cos_out</span> <span class="o">&gt;</span> <span class="n">MAX_MAG</span> <span class="o">||</span> <span class="n">cos_out</span> <span class="o">&lt;</span> <span class="o">-</span><span class="n">MAX_MAG</span><span class="p">)</span> <span class="k">begin</span>
                    <span class="p">$</span><span class="nb">display</span><span class="p">(</span><span class="s">"  FAIL: output out of range at sample %0d (sin=%0d cos=%0d)"</span><span class="p">,</span>
                             <span class="n">i</span><span class="p">,</span> <span class="n">sin_out</span><span class="p">,</span> <span class="n">cos_out</span><span class="p">);</span>
                    <span class="n">bounded_ok</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
                    <span class="n">errors</span><span class="o">++</span><span class="p">;</span>
                <span class="k">end</span>
            <span class="k">end</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">bounded_ok</span><span class="p">)</span> <span class="p">$</span><span class="nb">display</span><span class="p">(</span><span class="s">"  PASS: all samples within [-%0d, %0d]"</span><span class="p">,</span> <span class="n">MAX_MAG</span><span class="p">,</span> <span class="n">MAX_MAG</span><span class="p">);</span>
        <span class="k">end</span>

        <span class="c1">// ---- Test 5: Quadrature relationship ----</span>
        <span class="c1">// With a slow tone, sample near zero-crossing of sin where cos should be near peak</span>
        <span class="p">$</span><span class="nb">display</span><span class="p">(</span><span class="s">"[TEST 5] Quadrature check (cos leads sin by ~90 deg)"</span><span class="p">);</span>
        <span class="n">fcw</span> <span class="o">=</span> <span class="mi">16</span><span class="err">'</span><span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="p">(</span><span class="n">PHASE_ACC_W</span> <span class="o">-</span> <span class="mi">4</span><span class="p">));</span> <span class="c1">// same tone</span>
        <span class="n">rst_n</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="n">tick</span><span class="p">(</span><span class="mi">4</span><span class="p">);</span>
        <span class="o">@</span><span class="p">(</span><span class="kt">posedge</span> <span class="n">clk</span><span class="p">)</span> <span class="n">rst_n</span> <span class="o">&lt;=</span> <span class="mi">1</span><span class="p">;</span>
        <span class="n">tick</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span> <span class="c1">// pipeline flush — phase_acc ≈ 0 at this point</span>
        <span class="c1">// Near phase=0: sin≈0, cos≈+peak</span>
        <span class="k">begin</span>
            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">cos_out</span> <span class="o">&gt;</span> <span class="n">sin_out</span><span class="p">))</span> <span class="k">begin</span>
                <span class="p">$</span><span class="nb">display</span><span class="p">(</span><span class="s">"  FAIL: at phase~0 expected cos &gt; sin (sin=%0d cos=%0d)"</span><span class="p">,</span> <span class="n">sin_out</span><span class="p">,</span> <span class="n">cos_out</span><span class="p">);</span>
                <span class="n">errors</span><span class="o">++</span><span class="p">;</span>
            <span class="k">end</span> <span class="k">else</span> <span class="k">begin</span>
                <span class="p">$</span><span class="nb">display</span><span class="p">(</span><span class="s">"  PASS: cos &gt; sin near phase 0 (sin=%0d cos=%0d)"</span><span class="p">,</span> <span class="n">sin_out</span><span class="p">,</span> <span class="n">cos_out</span><span class="p">);</span>
            <span class="k">end</span>
        <span class="k">end</span>

        <span class="c1">// ---- Summary ----</span>
        <span class="n">tick</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">errors</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
            <span class="p">$</span><span class="nb">display</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">=== ALL TESTS PASSED ==="</span><span class="p">);</span>
        <span class="k">else</span>
            <span class="p">$</span><span class="nb">display</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">=== %0d ERROR(S) ==="</span><span class="p">,</span> <span class="n">errors</span><span class="p">);</span>

        <span class="p">$</span><span class="nb">finish</span><span class="p">;</span>
    <span class="k">end</span>

<span class="k">endmodule</span>

</code></pre></div></div>

<p>Now Claude knows it needs to generate a Makefile to launch the simulation and check outputs:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'./FPGA_NCO_files/Makefile'</span><span class="p">,</span> <span class="s">'r'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
    <span class="n">f_contents</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="n">read</span><span class="p">()</span>

<span class="n">md</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="se">\n</span><span class="s">```makefile</span><span class="se">\n</span><span class="si">{</span><span class="n">f_contents</span><span class="si">}</span><span class="se">\n</span><span class="s">```"</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># NCO Verilator testbench
# Usage:
#   make        — build and run the testbench
#   make wave   — open waveform in GTKWave (if installed)
#   make clean  — remove generated files
</span>
<span class="nv">TOP</span>      <span class="o">:=</span> nco_tb
<span class="nv">SOURCES</span>  <span class="o">:=</span> nco.sv nco_tb.sv
<span class="nv">OBJ_DIR</span>  <span class="o">:=</span> obj_dir

<span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">all run wave clean</span>

<span class="nl">all</span><span class="o">:</span> <span class="nf">run</span>

<span class="c"># Verilator compile: SystemVerilog, enable timing, trace (VCD)
</span><span class="nl">$(OBJ_DIR)/V$(TOP)</span><span class="o">:</span> <span class="nf">$(SOURCES)</span>
	verilator <span class="nt">--binary</span> <span class="nt">--timing</span> <span class="nt">-j</span> 0 <span class="nt">--trace</span> <span class="se">\</span>
		<span class="nt">-Wno-WIDTHEXPAND</span> <span class="nt">-Wno-WIDTHTRUNC</span> <span class="nt">-Wno-INITIALDLY</span> <span class="nt">-Wno-IMPLICITSTATIC</span> <span class="se">\</span>
		<span class="nt">--top-module</span> <span class="nv">$(TOP)</span> <span class="nv">$(SOURCES)</span>

<span class="nl">run</span><span class="o">:</span> <span class="nf">$(OBJ_DIR)/V$(TOP)</span>
	./<span class="nv">$(OBJ_DIR)</span>/V<span class="nv">$(TOP)</span>

<span class="nl">wave</span><span class="o">:</span> <span class="nf">run</span>
	gtkwave <span class="nv">$(TOP)</span>.vcd &amp;

<span class="nl">clean</span><span class="o">:</span>
	<span class="nb">rm</span> <span class="nt">-rf</span> <span class="nv">$(OBJ_DIR)</span> <span class="nv">$(TOP)</span>.vcd

</code></pre></div></div>

<p>It then runs the Makefile, sees an error related to <code class="highlighter-rouge">IMPLICITSTATIC</code> lifetime, adjusts the Makefile to ignore that flag in the one variable, and re-runs to show a successul testbench execution:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>- V e r i l a t i o n   R e p o r t: Verilator 5.046 2026-02-28 rev vUNKNOWN-built20260228
- Verilator: Built from 0.061 MB sources in 3 modules, into 0.093 MB in 9 C++ files needing 0.000 MB
- Verilator: Walltime 2.420 s (elab=0.006, cvt=0.011, bld=2.392); cpu 0.021 s on 12 threads; allocated 11.875 MB
./obj_dir/Vnco_tb
[TEST 1] Reset behavior
[TEST 2] Valid strobe latency
[TEST 3] FCW=0 constant output
[TEST 4] Bounded output with active tone
  PASS: all samples within [-2047, 2047]
[TEST 5] Quadrature check (cos leads sin by ~90 deg)
  PASS: cos &gt; sin near phase 0 (sin=783 cos=1871)

=== ALL TESTS PASSED ===
- nco_tb.sv:156: Verilog $finish
</code></pre></div></div>

<p>Let’s go one step further- let’s have Claude use it’s Python skills to generate a <a href="https://www.cocotb.org/">cocotb</a> testbench to numerically verify the NCO output:</p>
<blockquote>
  <p>Now that we have a basic unit testbench in @nco_tb.sv let’s create a <code class="highlighter-rouge">cocotb</code> Python verification file that we can run to plot the output spectrum of @nco.sv and measure the SFDR.</p>
</blockquote>

<p>This gives us another <a href="./FPGA_NCO_files/Makefile.cocotb">Makefile to launch the cocotb sim</a> and then the following Python testbench:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'./FPGA_NCO_files/test_nco.py'</span><span class="p">,</span> <span class="s">'r'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
    <span class="n">f_contents</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="n">read</span><span class="p">()</span>

<span class="n">md</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="se">\n</span><span class="s">```python</span><span class="se">\n</span><span class="si">{</span><span class="n">f_contents</span><span class="si">}</span><span class="se">\n</span><span class="s">```"</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">"""
cocotb testbench for NCO – captures output samples, plots the spectrum, and measures SFDR.

Run with:
    make -f Makefile.cocotb

Requires: cocotb, cocotb-test, numpy, matplotlib
Simulator: Icarus Verilog (iverilog) or Verilator with cocotb support
"""</span>

<span class="kn">import</span> <span class="nn">cocotb</span>
<span class="kn">from</span> <span class="nn">cocotb.clock</span> <span class="kn">import</span> <span class="n">Clock</span>
<span class="kn">from</span> <span class="nn">cocotb.triggers</span> <span class="kn">import</span> <span class="n">RisingEdge</span><span class="p">,</span> <span class="n">ClockCycles</span>
<span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="n">np</span>
<span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>


<span class="c1"># ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
</span>

<span class="k">def</span> <span class="nf">signed_val</span><span class="p">(</span><span class="n">signal</span><span class="p">,</span> <span class="n">width</span><span class="p">):</span>
    <span class="s">"""Convert an unsigned cocotb signal value to signed Python int."""</span>
    <span class="n">val</span> <span class="o">=</span> <span class="n">signal</span><span class="p">.</span><span class="n">value</span><span class="p">.</span><span class="n">integer</span>
    <span class="k">if</span> <span class="n">val</span> <span class="o">&gt;=</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="p">(</span><span class="n">width</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)):</span>
        <span class="n">val</span> <span class="o">-=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="n">width</span>
    <span class="k">return</span> <span class="n">val</span>


<span class="k">async</span> <span class="k">def</span> <span class="nf">reset_dut</span><span class="p">(</span><span class="n">dut</span><span class="p">,</span> <span class="n">cycles</span><span class="o">=</span><span class="mi">4</span><span class="p">):</span>
    <span class="s">"""Assert reset for *cycles* clock edges, then release."""</span>
    <span class="n">dut</span><span class="p">.</span><span class="n">rst_n</span><span class="p">.</span><span class="n">value</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">dut</span><span class="p">.</span><span class="n">fcw</span><span class="p">.</span><span class="n">value</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">dut</span><span class="p">.</span><span class="n">phase_offset</span><span class="p">.</span><span class="n">value</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">await</span> <span class="n">ClockCycles</span><span class="p">(</span><span class="n">dut</span><span class="p">.</span><span class="n">clk</span><span class="p">,</span> <span class="n">cycles</span><span class="p">)</span>
    <span class="n">dut</span><span class="p">.</span><span class="n">rst_n</span><span class="p">.</span><span class="n">value</span> <span class="o">=</span> <span class="mi">1</span>
    <span class="k">await</span> <span class="n">ClockCycles</span><span class="p">(</span><span class="n">dut</span><span class="p">.</span><span class="n">clk</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>  <span class="c1"># wait for pipeline to fill
</span>

<span class="c1"># ---------------------------------------------------------------------------
# Parameters (must match the cocotb runner / Makefile generics)
# ---------------------------------------------------------------------------
</span><span class="n">PHASE_ACC_W</span> <span class="o">=</span> <span class="mi">16</span>
<span class="n">LUT_ADDR_W</span> <span class="o">=</span> <span class="mi">8</span>
<span class="n">OUTPUT_W</span> <span class="o">=</span> <span class="mi">12</span>


<span class="c1"># ---------------------------------------------------------------------------
# Test: Capture tone, plot spectrum, measure SFDR
# ---------------------------------------------------------------------------
</span>

<span class="o">@</span><span class="n">cocotb</span><span class="p">.</span><span class="n">test</span><span class="p">()</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">test_nco_spectrum</span><span class="p">(</span><span class="n">dut</span><span class="p">):</span>
    <span class="s">"""Run the NCO at a known tone, capture samples, compute FFT, and report SFDR."""</span>

    <span class="n">clock</span> <span class="o">=</span> <span class="n">Clock</span><span class="p">(</span><span class="n">dut</span><span class="p">.</span><span class="n">clk</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="n">units</span><span class="o">=</span><span class="s">"ns"</span><span class="p">)</span>  <span class="c1"># 100 MHz
</span>    <span class="n">cocotb</span><span class="p">.</span><span class="n">start_soon</span><span class="p">(</span><span class="n">clock</span><span class="p">.</span><span class="n">start</span><span class="p">())</span>

    <span class="k">await</span> <span class="n">reset_dut</span><span class="p">(</span><span class="n">dut</span><span class="p">)</span>

    <span class="c1"># --- Choose FCW for a bin-centered tone ---
</span>    <span class="c1"># N_samples must be a power of 2 for a clean FFT.
</span>    <span class="n">N</span> <span class="o">=</span> <span class="mi">4096</span>
    <span class="c1"># Pick a tone that lands exactly on an FFT bin to avoid spectral leakage:
</span>    <span class="c1">#   bin_index * 2^PHASE_ACC_W / N  =  FCW
</span>    <span class="n">bin_index</span> <span class="o">=</span> <span class="mi">107</span>  <span class="c1"># prime-ish, away from DC and Nyquist
</span>    <span class="n">fcw_val</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">bin_index</span> <span class="o">*</span> <span class="p">(</span><span class="mi">2</span><span class="o">**</span><span class="n">PHASE_ACC_W</span><span class="p">)</span> <span class="o">/</span> <span class="n">N</span><span class="p">)</span>
    <span class="n">dut</span><span class="p">.</span><span class="n">fcw</span><span class="p">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">fcw_val</span>

    <span class="n">f_clk</span> <span class="o">=</span> <span class="mf">100e6</span>
    <span class="n">f_tone</span> <span class="o">=</span> <span class="n">fcw_val</span> <span class="o">*</span> <span class="n">f_clk</span> <span class="o">/</span> <span class="mi">2</span><span class="o">**</span><span class="n">PHASE_ACC_W</span>
    <span class="n">dut</span><span class="p">.</span><span class="n">_log</span><span class="p">.</span><span class="n">info</span><span class="p">(</span>
        <span class="sa">f</span><span class="s">"FCW = </span><span class="si">{</span><span class="n">fcw_val</span><span class="si">}</span><span class="s">  →  f_tone = </span><span class="si">{</span><span class="n">f_tone</span> <span class="o">/</span> <span class="mf">1e6</span><span class="si">:</span><span class="p">.</span><span class="mi">4</span><span class="n">f</span><span class="si">}</span><span class="s"> MHz  (bin </span><span class="si">{</span><span class="n">bin_index</span><span class="si">}</span><span class="s">)"</span>
    <span class="p">)</span>

    <span class="c1"># Let the pipeline settle after changing FCW
</span>    <span class="k">await</span> <span class="n">ClockCycles</span><span class="p">(</span><span class="n">dut</span><span class="p">.</span><span class="n">clk</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span>

    <span class="c1"># --- Collect samples ---
</span>    <span class="n">sin_samples</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">zeros</span><span class="p">(</span><span class="n">N</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="n">np</span><span class="p">.</span><span class="n">float64</span><span class="p">)</span>
    <span class="n">cos_samples</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">zeros</span><span class="p">(</span><span class="n">N</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="n">np</span><span class="p">.</span><span class="n">float64</span><span class="p">)</span>

    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">N</span><span class="p">):</span>
        <span class="k">await</span> <span class="n">RisingEdge</span><span class="p">(</span><span class="n">dut</span><span class="p">.</span><span class="n">clk</span><span class="p">)</span>
        <span class="n">sin_samples</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">signed_val</span><span class="p">(</span><span class="n">dut</span><span class="p">.</span><span class="n">sin_out</span><span class="p">,</span> <span class="n">OUTPUT_W</span><span class="p">)</span>
        <span class="n">cos_samples</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">signed_val</span><span class="p">(</span><span class="n">dut</span><span class="p">.</span><span class="n">cos_out</span><span class="p">,</span> <span class="n">OUTPUT_W</span><span class="p">)</span>

    <span class="c1"># --- Compute power spectrum (dBFS) ---
</span>    <span class="n">window</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">blackman</span><span class="p">(</span><span class="n">N</span><span class="p">)</span>
    <span class="n">sin_windowed</span> <span class="o">=</span> <span class="n">sin_samples</span> <span class="o">*</span> <span class="n">window</span>

    <span class="n">spectrum</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="n">rfft</span><span class="p">(</span><span class="n">sin_windowed</span><span class="p">)</span>
    <span class="n">mag</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nb">abs</span><span class="p">(</span><span class="n">spectrum</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="nb">sum</span><span class="p">(</span><span class="n">window</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span>  <span class="c1"># normalize
</span>    <span class="n">mag_db</span> <span class="o">=</span> <span class="mi">20</span> <span class="o">*</span> <span class="n">np</span><span class="p">.</span><span class="n">log10</span><span class="p">(</span><span class="n">mag</span> <span class="o">+</span> <span class="mf">1e-20</span><span class="p">)</span>  <span class="c1"># dBFS (relative to full-scale sine)
</span>
    <span class="c1"># Normalize so the fundamental is 0 dBFS
</span>    <span class="n">fund_bin</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">argmax</span><span class="p">(</span><span class="n">mag_db</span><span class="p">)</span>
    <span class="n">mag_db</span> <span class="o">-=</span> <span class="n">mag_db</span><span class="p">[</span><span class="n">fund_bin</span><span class="p">]</span>

    <span class="n">freqs_mhz</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="n">rfftfreq</span><span class="p">(</span><span class="n">N</span><span class="p">,</span> <span class="n">d</span><span class="o">=</span><span class="mf">1.0</span> <span class="o">/</span> <span class="n">f_clk</span><span class="p">)</span> <span class="o">/</span> <span class="mf">1e6</span>

    <span class="c1"># --- Measure SFDR ---
</span>    <span class="c1"># Exclude DC (bin 0) and a ±3-bin window around the fundamental
</span>    <span class="n">spur_mask</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">ones</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">mag_db</span><span class="p">),</span> <span class="n">dtype</span><span class="o">=</span><span class="nb">bool</span><span class="p">)</span>
    <span class="n">spur_mask</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="bp">False</span>  <span class="c1"># ignore DC
</span>    <span class="n">guard</span> <span class="o">=</span> <span class="mi">3</span>
    <span class="n">lo</span> <span class="o">=</span> <span class="nb">max</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">fund_bin</span> <span class="o">-</span> <span class="n">guard</span><span class="p">)</span>
    <span class="n">hi</span> <span class="o">=</span> <span class="nb">min</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">mag_db</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">fund_bin</span> <span class="o">+</span> <span class="n">guard</span><span class="p">)</span>
    <span class="n">spur_mask</span><span class="p">[</span><span class="n">lo</span> <span class="p">:</span> <span class="n">hi</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="bp">False</span>

    <span class="n">spur_peak_bin</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">argmax</span><span class="p">(</span><span class="n">mag_db</span><span class="p">[</span><span class="n">spur_mask</span><span class="p">])</span>
    <span class="c1"># Map back to absolute bin index
</span>    <span class="n">spur_abs_bin</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">arange</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">mag_db</span><span class="p">))[</span><span class="n">spur_mask</span><span class="p">][</span><span class="n">spur_peak_bin</span><span class="p">]</span>
    <span class="n">sfdr</span> <span class="o">=</span> <span class="n">mag_db</span><span class="p">[</span><span class="n">fund_bin</span><span class="p">]</span> <span class="o">-</span> <span class="n">mag_db</span><span class="p">[</span><span class="n">spur_abs_bin</span><span class="p">]</span>

    <span class="n">dut</span><span class="p">.</span><span class="n">_log</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s">"Fundamental bin: </span><span class="si">{</span><span class="n">fund_bin</span><span class="si">}</span><span class="s">  (</span><span class="si">{</span><span class="n">freqs_mhz</span><span class="p">[</span><span class="n">fund_bin</span><span class="p">]</span><span class="si">:</span><span class="p">.</span><span class="mi">3</span><span class="n">f</span><span class="si">}</span><span class="s"> MHz)"</span><span class="p">)</span>
    <span class="n">dut</span><span class="p">.</span><span class="n">_log</span><span class="p">.</span><span class="n">info</span><span class="p">(</span>
        <span class="sa">f</span><span class="s">"Largest spur bin: </span><span class="si">{</span><span class="n">spur_abs_bin</span><span class="si">}</span><span class="s">  (</span><span class="si">{</span><span class="n">freqs_mhz</span><span class="p">[</span><span class="n">spur_abs_bin</span><span class="p">]</span><span class="si">:</span><span class="p">.</span><span class="mi">3</span><span class="n">f</span><span class="si">}</span><span class="s"> MHz)"</span>
    <span class="p">)</span>
    <span class="n">dut</span><span class="p">.</span><span class="n">_log</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s">"SFDR = </span><span class="si">{</span><span class="n">sfdr</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="si">}</span><span class="s"> dBc"</span><span class="p">)</span>

    <span class="c1"># --- Plot ---
</span>    <span class="n">fig</span><span class="p">,</span> <span class="n">axes</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="n">subplots</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">7</span><span class="p">),</span> <span class="n">constrained_layout</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

    <span class="c1"># Time-domain (first 128 samples)
</span>    <span class="n">t_us</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">arange</span><span class="p">(</span><span class="mi">128</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="mi">1</span> <span class="o">/</span> <span class="n">f_clk</span><span class="p">)</span> <span class="o">*</span> <span class="mf">1e6</span>
    <span class="n">axes</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">plot</span><span class="p">(</span><span class="n">t_us</span><span class="p">,</span> <span class="n">sin_samples</span><span class="p">[:</span><span class="mi">128</span><span class="p">],</span> <span class="n">label</span><span class="o">=</span><span class="s">"sin"</span><span class="p">,</span> <span class="n">linewidth</span><span class="o">=</span><span class="mf">0.8</span><span class="p">)</span>
    <span class="n">axes</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">plot</span><span class="p">(</span><span class="n">t_us</span><span class="p">,</span> <span class="n">cos_samples</span><span class="p">[:</span><span class="mi">128</span><span class="p">],</span> <span class="n">label</span><span class="o">=</span><span class="s">"cos"</span><span class="p">,</span> <span class="n">linewidth</span><span class="o">=</span><span class="mf">0.8</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.7</span><span class="p">)</span>
    <span class="n">axes</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">set_xlabel</span><span class="p">(</span><span class="s">"Time (µs)"</span><span class="p">)</span>
    <span class="n">axes</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">set_ylabel</span><span class="p">(</span><span class="s">"Amplitude (LSB)"</span><span class="p">)</span>
    <span class="n">axes</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">set_title</span><span class="p">(</span><span class="s">"NCO Time-Domain Output (first 128 samples)"</span><span class="p">)</span>
    <span class="n">axes</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">legend</span><span class="p">()</span>
    <span class="n">axes</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">grid</span><span class="p">(</span><span class="bp">True</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.3</span><span class="p">)</span>

    <span class="c1"># Spectrum
</span>    <span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">plot</span><span class="p">(</span><span class="n">freqs_mhz</span><span class="p">,</span> <span class="n">mag_db</span><span class="p">,</span> <span class="n">linewidth</span><span class="o">=</span><span class="mf">0.6</span><span class="p">)</span>
    <span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">axhline</span><span class="p">(</span>
        <span class="o">-</span><span class="n">sfdr</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s">"r"</span><span class="p">,</span> <span class="n">linestyle</span><span class="o">=</span><span class="s">"--"</span><span class="p">,</span> <span class="n">linewidth</span><span class="o">=</span><span class="mf">0.8</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="sa">f</span><span class="s">"SFDR = </span><span class="si">{</span><span class="n">sfdr</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="si">}</span><span class="s"> dBc"</span>
    <span class="p">)</span>
    <span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">set_xlabel</span><span class="p">(</span><span class="s">"Frequency (MHz)"</span><span class="p">)</span>
    <span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">set_ylabel</span><span class="p">(</span><span class="s">"Magnitude (dBc)"</span><span class="p">)</span>
    <span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">set_title</span><span class="p">(</span>
        <span class="sa">f</span><span class="s">"NCO Output Spectrum  |  f_tone = </span><span class="si">{</span><span class="n">f_tone</span> <span class="o">/</span> <span class="mf">1e6</span><span class="si">:</span><span class="p">.</span><span class="mi">4</span><span class="n">f</span><span class="si">}</span><span class="s"> MHz  |  SFDR = </span><span class="si">{</span><span class="n">sfdr</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="si">}</span><span class="s"> dBc"</span>
    <span class="p">)</span>
    <span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">set_ylim</span><span class="p">([</span><span class="o">-</span><span class="mi">120</span><span class="p">,</span> <span class="mi">5</span><span class="p">])</span>
    <span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">legend</span><span class="p">()</span>
    <span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">grid</span><span class="p">(</span><span class="bp">True</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.3</span><span class="p">)</span>

    <span class="n">fig</span><span class="p">.</span><span class="n">savefig</span><span class="p">(</span><span class="s">"nco_spectrum.png"</span><span class="p">,</span> <span class="n">dpi</span><span class="o">=</span><span class="mi">150</span><span class="p">)</span>
    <span class="n">dut</span><span class="p">.</span><span class="n">_log</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="s">"Saved plot to nco_spectrum.png"</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">close</span><span class="p">(</span><span class="n">fig</span><span class="p">)</span>

    <span class="c1"># --- Pass/fail ---
</span>    <span class="c1"># With 8-bit LUT address (quarter-wave), theoretical spur floor ~ -6*8 = -48 dBc.
</span>    <span class="c1"># Require at least 40 dBc as a sanity check.
</span>    <span class="n">MIN_SFDR</span> <span class="o">=</span> <span class="mf">40.0</span>
    <span class="k">assert</span> <span class="n">sfdr</span> <span class="o">&gt;=</span> <span class="n">MIN_SFDR</span><span class="p">,</span> <span class="sa">f</span><span class="s">"SFDR </span><span class="si">{</span><span class="n">sfdr</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="si">}</span><span class="s"> dBc below threshold </span><span class="si">{</span><span class="n">MIN_SFDR</span><span class="si">}</span><span class="s"> dBc"</span>
    <span class="n">dut</span><span class="p">.</span><span class="n">_log</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="s">"PASS"</span><span class="p">)</span>

</code></pre></div></div>

<p>This gives the following plot and SFDR measurement output:
<img src="./FPGA_NCO_files/nco_spectrum.png" alt="SFDR plot" /></p>

<h2 id="summary">Summary</h2>

<p>In the end, this isn’t a <em>perfect</em> solution- there’s some quirks in the dither logic and we need to pass through a tool like <a href="https://www.amd.com/en/products/software/adaptive-socs-and-fpgas/vivado.html">Vivado’s</a> synthesis flow to know if we can map this to a target FPGA- but this is a massive and quick start to a DSP FPGA design that we can iterate on further. And again, this was done with no system or other context- the true power of LLMs in this domain, and moreover agentic tools like Claude Code, is:</p>
<ul>
  <li>Feeding the rest of your codebase and documentation as context (especially useful in complex DSP projects where we have documentation like waveform specs and ICDs, as well as modeling code, like Matlab).</li>
  <li>Specifying HDL testbench coverage, coding-style, linting, etc. requirements as part of system context (like in Claude Code’s <code class="highlighter-rouge">CLAUDE.md</code> instruction file)- LLMs are particularly useful at the mundane tasks of documenting blocks, adding tests, formatting, etc.</li>
  <li>Hooking up other tools, like Vivado <code class="highlighter-rouge">tcl</code> steps, as <a href="https://en.wikipedia.org/wiki/Model_Context_Protocol">Model Context Protocol (MCP)</a> tools available to be called by the LLM.</li>
</ul>]]></content><author><name>John Gentile</name></author><summary type="html"><![CDATA[Put simply, LLMs have gotten crazy good recently, going beyond typical software coding tasks, to even tackling HDL design. As those with experience in digital design know, this domain is not as simple to “code” as software: you’re really trying to coerce a chain of- often proprietary- EDA tools to map a desired function to a series of wires and hardware-specific primitives. Nonetheless, and even with much less open-source/high-quality training data, recent models have made leaps and bounds progress.]]></summary></entry><entry><title type="html">Unknown Chirp Message Decode</title><link href="https://john-gentile.com/2025/09/30/Unknown_Chirp_Msg_Decode.html" rel="alternate" type="text/html" title="Unknown Chirp Message Decode" /><published>2025-09-30T00:00:00+00:00</published><updated>2026-05-04T04:03:54+00:00</updated><id>https://john-gentile.com/2025/09/30/Unknown_Chirp_Msg_Decode</id><content type="html" xml:base="https://john-gentile.com/2025/09/30/Unknown_Chirp_Msg_Decode.html"><![CDATA[<p>Another unknown signal challenge from <a href="https://github.com/DrSDR/Chirp-Text-Message">DrSDR, the Chip-Text-Message</a>- here a series of linear frequency modulated (LFM) chirps are representing bits, and we need to find the underlying text message in the waveform. An “upchirp” (where frequency is <em>increasing</em> as <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>e</mi><mrow><mi>j</mi><mi>ω</mi><mi>t</mi></mrow></msup></mrow><annotation encoding="application/x-tex">e^{j \omega t}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8247em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8247em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">jω</span><span class="mord mathnormal mtight">t</span></span></span></span></span></span></span></span></span></span></span></span>) represents a bit <code class="highlighter-rouge">1</code>, where as an opposite “downchirp” (where frequency <em>decreases</em> as <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>e</mi><mrow><mo>−</mo><mi>j</mi><mi>ω</mi><mi>t</mi></mrow></msup></mrow><annotation encoding="application/x-tex">e^{-j \omega t}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8247em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8247em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">jω</span><span class="mord mathnormal mtight">t</span></span></span></span></span></span></span></span></span></span></span></span>) represents a bit <code class="highlighter-rouge">0</code>.</p>

<p>We are given the following waveform constants:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">bw</span> <span class="o">=</span> <span class="mf">12e3</span>     <span class="c1"># Bandwidth of chirp sweep (Hz)
</span><span class="n">pw</span> <span class="o">=</span> <span class="mf">50e-3</span>    <span class="c1"># Chirp pulse width (seconds)
</span><span class="n">n_bits</span> <span class="o">=</span> <span class="mi">144</span>  <span class="c1"># Number of bits
</span><span class="n">sps</span> <span class="o">=</span> <span class="mi">2400</span>    <span class="c1"># Samples/symbol
</span></code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="n">np</span>
<span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>
<span class="kn">from</span> <span class="nn">scipy.io</span> <span class="kn">import</span> <span class="n">wavfile</span>
<span class="kn">from</span> <span class="nn">scipy</span> <span class="kn">import</span> <span class="n">signal</span>
<span class="kn">from</span> <span class="nn">rfproto</span> <span class="kn">import</span> <span class="n">plot</span><span class="p">,</span> <span class="n">sig_gen</span>
</code></pre></div></div>

<p>First lets read in the <code class="highlighter-rouge">.wav</code> file and verify the sampling frequency given in the file metadata.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">fs</span><span class="p">,</span> <span class="n">wav_data</span> <span class="o">=</span> <span class="n">wavfile</span><span class="p">.</span><span class="n">read</span><span class="p">(</span><span class="s">"./Chirp_IQ_Fs48KHz.wav"</span><span class="p">)</span>
<span class="n">N</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">wav_data</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Read </span><span class="si">{</span><span class="n">N</span><span class="si">}</span><span class="s"> samples with fs=</span><span class="si">{</span><span class="n">fs</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
</code></pre></div></div>

<p style="font-family:monospace; white-space:pre-wrap">
Read 345600 samples with fs=48000
</p>

<p>Given we know the message is made up of up and down chirps, lets <a href="https://john-gentile.com/kb/dsp/Time-Frequency_Analysis.html">use time-frequency analysis techniques, like generating a spectrogram</a> to see if we can see the linear frequency sweeps.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">input_iq</span> <span class="o">=</span> <span class="n">wav_data</span><span class="p">[:,</span> <span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="mf">1j</span> <span class="o">*</span> <span class="n">wav_data</span><span class="p">[:,</span> <span class="mi">1</span><span class="p">]</span>
<span class="n">min_mag</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nb">min</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="nb">abs</span><span class="p">(</span><span class="n">input_iq</span><span class="p">))</span>
<span class="n">plt</span><span class="p">.</span><span class="n">specgram</span><span class="p">(</span><span class="n">input_iq</span><span class="p">,</span> <span class="n">NFFT</span><span class="o">=</span><span class="mi">256</span><span class="p">,</span> <span class="n">noverlap</span><span class="o">=</span><span class="mi">128</span><span class="p">,</span> <span class="n">pad_to</span><span class="o">=</span><span class="mi">1024</span><span class="p">,</span> <span class="n">Fs</span><span class="o">=</span><span class="n">fs</span><span class="p">,</span> <span class="n">vmin</span><span class="o">=</span><span class="n">min_mag</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s">'Time (s)'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s">'Frequency (Hz)'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div></div>

<p><img src="2025-09-30-Unknown_Chirp_Msg_Decode_files/2025-09-30-Unknown_Chirp_Msg_Decode_7_0.png" alt="png" /></p>

<p>Here we can see there are varying up and down LFM chirps and that the 12 kHz chirp bandwidth is centered around DC (0 Hz).</p>

<p>Similar to <a href="https://en.wikipedia.org/wiki/Pulse_compression">radar pulse compression processing</a>, we can use a <a href="https://en.wikipedia.org/wiki/Matched_filter">matched filter</a> to de-chirp the input signal. Since we are given the pulse width and chirp bandwidth of the modulated bits, the matched filter is simply a prototype of the generated pulse, then reversed and complex conjugated.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">n_chirp</span> <span class="o">=</span> <span class="n">fs</span> <span class="o">*</span> <span class="n">pw</span> <span class="c1"># samp_freq (Samples/sec) * pulse_width (sec) =&gt; Samples
</span><span class="n">upchirp</span> <span class="o">=</span> <span class="n">sig_gen</span><span class="p">.</span><span class="n">cmplx_dt_lfm_chirp</span><span class="p">(</span><span class="mf">1.0</span><span class="p">,</span> <span class="o">-</span><span class="mf">6e3</span><span class="p">,</span> <span class="mf">6e3</span><span class="p">,</span> <span class="n">fs</span><span class="p">,</span> <span class="nb">int</span><span class="p">(</span><span class="n">n_chirp</span><span class="p">))</span>
<span class="n">plt</span><span class="p">.</span><span class="n">specgram</span><span class="p">(</span><span class="n">upchirp</span><span class="p">,</span> <span class="n">pad_to</span><span class="o">=</span><span class="mi">1024</span><span class="p">,</span> <span class="n">Fs</span><span class="o">=</span><span class="n">fs</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s">'Time (s)'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s">'Frequency (Hz)'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>

<span class="n">mf_up</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">conj</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="n">flip</span><span class="p">(</span><span class="n">upchirp</span><span class="p">))</span>

<span class="n">downchirp</span> <span class="o">=</span> <span class="n">sig_gen</span><span class="p">.</span><span class="n">cmplx_dt_lfm_chirp</span><span class="p">(</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">6e3</span><span class="p">,</span> <span class="o">-</span><span class="mf">6e3</span><span class="p">,</span> <span class="n">fs</span><span class="p">,</span> <span class="nb">int</span><span class="p">(</span><span class="n">n_chirp</span><span class="p">))</span>
<span class="n">plt</span><span class="p">.</span><span class="n">specgram</span><span class="p">(</span><span class="n">downchirp</span><span class="p">,</span> <span class="n">pad_to</span><span class="o">=</span><span class="mi">1024</span><span class="p">,</span> <span class="n">Fs</span><span class="o">=</span><span class="n">fs</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s">'Time (s)'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s">'Frequency (Hz)'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>

<span class="n">mf_down</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">conj</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="n">flip</span><span class="p">(</span><span class="n">downchirp</span><span class="p">))</span>
</code></pre></div></div>

<p><img src="2025-09-30-Unknown_Chirp_Msg_Decode_files/2025-09-30-Unknown_Chirp_Msg_Decode_9_0.png" alt="png" /></p>

<p><img src="2025-09-30-Unknown_Chirp_Msg_Decode_files/2025-09-30-Unknown_Chirp_Msg_Decode_9_1.png" alt="png" /></p>

<p>We could do a frequency domain method of convolution (e.g. take FFT of both input signal and matched filter template, then multiply in frequency domain, then IFFT to get back to time domain), but here lets just stay in time domain and convolve (filter) the input signal using both the up and down chirp matched filters. We take the magnitude of the complex outputs to see the convolution peaks.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">mf_output_1</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nb">abs</span><span class="p">(</span><span class="n">signal</span><span class="p">.</span><span class="n">convolve</span><span class="p">(</span><span class="n">input_iq</span><span class="p">,</span> <span class="n">mf_up</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="s">'same'</span><span class="p">))</span>
<span class="n">mf_output_0</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nb">abs</span><span class="p">(</span><span class="n">signal</span><span class="p">.</span><span class="n">convolve</span><span class="p">(</span><span class="n">input_iq</span><span class="p">,</span> <span class="n">mf_down</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="s">'same'</span><span class="p">))</span>

<span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">mf_output_1</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s">'Bit 1'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">mf_output_0</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s">'Bit 0'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">legend</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div></div>

<p><img src="2025-09-30-Unknown_Chirp_Msg_Decode_files/2025-09-30-Unknown_Chirp_Msg_Decode_11_0.png" alt="png" /></p>

<p>We can see lots of convolution peaks! Though given we have 2400 samples/symbol, we should zoom to see that there is lots of dead space (very high SNR post matched filtering!) between detected “bits”.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">mf_output_1</span><span class="p">[:</span><span class="mi">3</span><span class="o">*</span><span class="n">sps</span><span class="p">],</span> <span class="n">label</span><span class="o">=</span><span class="s">'Bit 1'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">mf_output_0</span><span class="p">[:</span><span class="mi">3</span><span class="o">*</span><span class="n">sps</span><span class="p">],</span> <span class="n">label</span><span class="o">=</span><span class="s">'Bit 0'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">legend</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div></div>

<p><img src="2025-09-30-Unknown_Chirp_Msg_Decode_files/2025-09-30-Unknown_Chirp_Msg_Decode_13_0.png" alt="png" /></p>

<p>We were also given that there are 144 total bits in the signal, which does match the length post-matched-filtering:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">print</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">mf_output_0</span><span class="p">)</span> <span class="o">/</span> <span class="n">sps</span><span class="p">)</span>
</code></pre></div></div>

<p style="font-family:monospace; white-space:pre-wrap">
144.0
</p>

<p>We can also visually see that the correlation peaks happen to nicely align around the center of a “symbol window” (e.g. we can see a <code class="highlighter-rouge">0</code> bit peak at around sample 1200 within a 2400 sample/symbol period). Thus, we can build a very simple decoder that looks across both matched filtered outputs in 2400 sample chunks, and whichever chunk has the highest value, we append that bit to an output decode buffer:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">bits</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n_bits</span><span class="p">):</span>
    <span class="n">b0_max</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nb">max</span><span class="p">(</span><span class="n">mf_output_0</span><span class="p">[</span><span class="n">i</span><span class="o">*</span><span class="mi">2400</span> <span class="p">:</span> <span class="p">(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span><span class="o">*</span><span class="mi">2400</span><span class="p">])</span>
    <span class="n">b1_max</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nb">max</span><span class="p">(</span><span class="n">mf_output_1</span><span class="p">[</span><span class="n">i</span><span class="o">*</span><span class="mi">2400</span> <span class="p">:</span> <span class="p">(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span><span class="o">*</span><span class="mi">2400</span><span class="p">])</span>
    <span class="n">bits</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="mi">0</span> <span class="k">if</span> <span class="n">b0_max</span> <span class="o">&gt;</span> <span class="n">b1_max</span> <span class="k">else</span> <span class="mi">1</span><span class="p">)</span>
</code></pre></div></div>

<p>Finally, we can then convert the bit vector to 8-bit ASCII characters to get the final text message output!</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">message</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">num_chars</span> <span class="o">=</span> <span class="n">n_bits</span> <span class="o">//</span> <span class="mi">8</span> <span class="c1"># 8 bits per ASCII char
</span><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_chars</span><span class="p">):</span>
    <span class="n">curr_bits</span> <span class="o">=</span> <span class="n">bits</span><span class="p">[</span><span class="n">i</span> <span class="o">*</span> <span class="mi">8</span> <span class="p">:</span> <span class="p">(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="mi">8</span><span class="p">]</span>
    <span class="n">byte_str</span> <span class="o">=</span> <span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="nb">map</span><span class="p">(</span><span class="nb">str</span><span class="p">,</span> <span class="n">curr_bits</span><span class="p">))</span>
    <span class="n">byte_val</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">byte_str</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
    <span class="n">message</span> <span class="o">+=</span> <span class="nb">chr</span><span class="p">(</span><span class="n">byte_val</span><span class="p">)</span>

<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Final demodulated and decoded message: </span><span class="si">{</span><span class="n">message</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
</code></pre></div></div>

<p style="font-family:monospace; white-space:pre-wrap">
Final demodulated and decoded message: AS8J-RJ873S-J2VAK

</p>]]></content><author><name>John Gentile</name></author><summary type="html"><![CDATA[Another unknown signal challenge from DrSDR, the Chip-Text-Message- here a series of linear frequency modulated (LFM) chirps are representing bits, and we need to find the underlying text message in the waveform. An “upchirp” (where frequency is increasing as ejωte^{j \omega t}ejωt) represents a bit 1, where as an opposite “downchirp” (where frequency decreases as e−jωte^{-j \omega t}e−jωt) represents a bit 0.]]></summary></entry><entry><title type="html">Unknown BPSK Decode</title><link href="https://john-gentile.com/2025/09/29/Unknown_BPSK_Decode.html" rel="alternate" type="text/html" title="Unknown BPSK Decode" /><published>2025-09-29T00:00:00+00:00</published><updated>2026-05-04T04:04:03+00:00</updated><id>https://john-gentile.com/2025/09/29/Unknown_BPSK_Decode</id><content type="html" xml:base="https://john-gentile.com/2025/09/29/Unknown_BPSK_Decode.html"><![CDATA[<p>I saw an interesting DSP challenge come up in <a href="https://www.reddit.com/r/DSP/">r/DSP</a>, <a href="https://github.com/DrSDR/BPSK-Decode">DrSDR/BPSK-Decode</a>, in which a <code class="highlighter-rouge">.wav</code> file is given and we are looking to demodulate and decode the signal to get an “Amazon gift card claim code”.</p>

<p>We are given the following information about it:</p>
<ul>
  <li>Waveform is <a href="https://en.wikipedia.org/wiki/Phase-shift_keying">BPSK</a> I/Q file with frequency and phase offset applied.</li>
  <li>Sampling frequency of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>f</mi><mi>s</mi></msub><mo>=</mo><mn>48</mn><mi>k</mi><mi>H</mi><mi>z</mi></mrow><annotation encoding="application/x-tex">f_{s} = 48kHz</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">s</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord">48</span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span><span class="mord mathnormal" style="margin-right:0.04398em;">Hz</span></span></span></span></li>
  <li>40 samples per bit, 144 bits in waveform, so total samples = 40 * 144</li>
  <li>Bit mapping: <code class="highlighter-rouge">0 = 0deg, I = 1 &amp; Q = 0</code> , <code class="highlighter-rouge">1 = 180deg, I = -1 &amp; Q = 0</code></li>
  <li>18 characters in waveform, each char is 8 bits (ASCII), with MSB sent first</li>
  <li>First 8 bits of message is <code class="highlighter-rouge">01000001</code> or <code class="highlighter-rouge">A</code> in 8-bit ASCII</li>
</ul>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="n">np</span>
<span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>
<span class="kn">from</span> <span class="nn">scipy.io</span> <span class="kn">import</span> <span class="n">wavfile</span>
<span class="kn">from</span> <span class="nn">rfproto</span> <span class="kn">import</span> <span class="n">plot</span>
</code></pre></div></div>

<p>First lets read in the <code class="highlighter-rouge">.wav</code> file and verify the sampling frequency given in the file metadata.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">fs</span><span class="p">,</span> <span class="n">wav_data</span> <span class="o">=</span> <span class="n">wavfile</span><span class="p">.</span><span class="n">read</span><span class="p">(</span><span class="s">"./BPSK_IQ_Fs48KHz.wav"</span><span class="p">)</span>
<span class="n">N</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">wav_data</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Read </span><span class="si">{</span><span class="n">N</span><span class="si">}</span><span class="s"> samples with fs=</span><span class="si">{</span><span class="n">fs</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
</code></pre></div></div>

<p style="font-family:monospace; white-space:pre-wrap">
Read 5760 samples with fs=48000
</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">input_iq</span> <span class="o">=</span> <span class="n">wav_data</span><span class="p">[:,</span> <span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="mf">1j</span> <span class="o">*</span> <span class="n">wav_data</span><span class="p">[:,</span> <span class="mi">1</span><span class="p">]</span>
<span class="n">plot</span><span class="p">.</span><span class="n">IQ</span><span class="p">(</span><span class="n">input_iq</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.1</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div></div>

<p><img src="2025-09-29-Unknown_BPSK_Decode_files/2025-09-29-Unknown_BPSK_Decode_5_0.png" alt="png" /></p>

<h2 id="static-non-realtime-demodulation">Static (Non-Realtime) Demodulation</h2>

<p>We can assume at first that both frequency and phase offsets are statically set in this synthetic case- thus, we can do some simplifications in both approaching each stage of compensation as full passes across the input data, as well in the type of processing to find and compensate for the given offsets towards carrier recovery.</p>

<p>To find the frequency offset error, we can use the <a href="https://dsp.stackexchange.com/questions/32133/phase-synchronization-in-bpsk">squaring trick</a> which wipes off the modulation of a signal when raising to the power of the modulation-order (e.g. <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>x</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">x^{2}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8141em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span> squaring for BPSK, <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>x</mi><mn>4</mn></msup></mrow><annotation encoding="application/x-tex">x^{4}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8141em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">4</span></span></span></span></span></span></span></span></span></span></span></span> for QPSK, etc.), which will then show a tone at double (or quadruple in the case of QPSK) when taking the FFT of the signal as the remaining frequency content is the offset of the double/quadrupled signal.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">x2</span> <span class="o">=</span> <span class="n">input_iq</span> <span class="o">**</span> <span class="mi">2</span>
<span class="n">plot</span><span class="p">.</span><span class="n">spec_an</span><span class="p">(</span><span class="n">x2</span><span class="p">,</span> <span class="n">fs</span><span class="p">,</span> <span class="n">scale_noise</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">norm</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div></div>

<p><img src="2025-09-29-Unknown_BPSK_Decode_files/2025-09-29-Unknown_BPSK_Decode_7_0.png" alt="png" /></p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">freqs</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="n">fftfreq</span><span class="p">(</span><span class="n">N</span><span class="p">,</span> <span class="mi">1</span> <span class="o">/</span> <span class="n">fs</span><span class="p">)</span>
<span class="n">X2</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="n">fft</span><span class="p">(</span><span class="n">x2</span><span class="p">)</span>

<span class="n">f_offset</span> <span class="o">=</span> <span class="n">freqs</span><span class="p">[</span><span class="n">np</span><span class="p">.</span><span class="n">argmax</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="nb">abs</span><span class="p">(</span><span class="n">X2</span><span class="p">))]</span> <span class="o">/</span> <span class="mf">2.0</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Frequency offset seen to be </span><span class="si">{</span><span class="n">f_offset</span><span class="si">}</span><span class="s"> Hz"</span><span class="p">)</span>
</code></pre></div></div>

<p style="font-family:monospace; white-space:pre-wrap">
Frequency offset seen to be 4320.833333333334 Hz
</p>

<p>Now that the frequency offset is found, we can compensate for the rotation by multiplying the input signal by a negative frequency of that offset:</p>

<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>Y</mi><mo>=</mo><mi>x</mi><mo>∗</mo><msup><mi>e</mi><mrow><mo>−</mo><mi>j</mi><mi>ω</mi><mi>t</mi></mrow></msup><mo>=</mo><mi>x</mi><mo>∗</mo><msup><mi>e</mi><mrow><mo>−</mo><mi>j</mi><mn>2</mn><mi>π</mi><mi>f</mi><mi>t</mi></mrow></msup></mrow><annotation encoding="application/x-tex">Y = x * e^{- j \omega t } = x * e^{- j 2 \pi f t }</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.22222em;">Y</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.4653em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8747em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8747em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">jω</span><span class="mord mathnormal mtight">t</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.4653em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8991em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8991em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight" style="margin-right:0.05724em;">j</span><span class="mord mtight">2</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">π</span><span class="mord mathnormal mtight" style="margin-right:0.10764em;">f</span><span class="mord mathnormal mtight">t</span></span></span></span></span></span></span></span></span></span></span></span></span>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">t</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">arange</span><span class="p">(</span><span class="n">N</span><span class="p">)</span> <span class="o">/</span> <span class="n">fs</span>
<span class="n">freq_corrected</span> <span class="o">=</span> <span class="n">input_iq</span> <span class="o">*</span> <span class="n">np</span><span class="p">.</span><span class="n">exp</span><span class="p">(</span><span class="o">-</span><span class="mf">1j</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">np</span><span class="p">.</span><span class="n">pi</span> <span class="o">*</span> <span class="n">f_offset</span> <span class="o">*</span> <span class="n">t</span><span class="p">)</span>
<span class="n">plot</span><span class="p">.</span><span class="n">IQ</span><span class="p">(</span><span class="n">freq_corrected</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.1</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div></div>

<p><img src="2025-09-29-Unknown_BPSK_Decode_files/2025-09-29-Unknown_BPSK_Decode_10_0.png" alt="png" /></p>

<p>We can see that the BPSK constellation is now not rotating at all! However a phase offset is still seen- in the same way we used the squaring trick to remove the modulation to find the frequency offset, the same trick can be used to find the resultant phase offset (which would also be twice the phase offset of the underlying baseband signal). Here again we assume (and see above in the IQ plot) a static phase offset, so we take the average of all angles and apply the opposite phase correction using the phasor:</p>

<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>Y</mi><mo>=</mo><mi>x</mi><mo>∗</mo><msup><mi>e</mi><mrow><mo>−</mo><mi>j</mi><mi>θ</mi></mrow></msup></mrow><annotation encoding="application/x-tex">Y = x * e^{-j \theta}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.22222em;">Y</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.4653em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8991em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8991em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight" style="margin-right:0.05724em;">j</span><span class="mord mathnormal mtight" style="margin-right:0.02778em;">θ</span></span></span></span></span></span></span></span></span></span></span></span></span>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">x2</span> <span class="o">=</span> <span class="n">freq_corrected</span> <span class="o">**</span> <span class="mi">2</span>
<span class="n">theta_avg</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">mean</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="n">angle</span><span class="p">(</span><span class="n">x2</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span>
<span class="n">phase_corrected</span> <span class="o">=</span> <span class="n">freq_corrected</span> <span class="o">*</span> <span class="n">np</span><span class="p">.</span><span class="n">exp</span><span class="p">(</span><span class="o">-</span><span class="mf">1j</span> <span class="o">*</span> <span class="n">theta_avg</span><span class="p">)</span>

<span class="n">plot</span><span class="p">.</span><span class="n">IQ</span><span class="p">(</span><span class="n">phase_corrected</span><span class="p">,</span> <span class="n">alpha</span> <span class="o">=</span> <span class="mf">0.1</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div></div>

<p><img src="2025-09-29-Unknown_BPSK_Decode_files/2025-09-29-Unknown_BPSK_Decode_12_0.png" alt="png" /></p>

<p>Now there’s no phase offset! As well, we can see in this IQ plot that the synthetically generated BPSK dataset has no timing offset, so we can go directly to decimating (downsampling) the data before taking hard-decisions to get output bits.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">sps</span> <span class="o">=</span> <span class="mi">40</span> <span class="c1"># Samples / symbol
</span><span class="n">decimated</span> <span class="o">=</span> <span class="n">phase_corrected</span><span class="p">[::</span><span class="n">sps</span><span class="p">]</span>
<span class="n">plot</span><span class="p">.</span><span class="n">IQ</span><span class="p">(</span><span class="n">decimated</span><span class="p">,</span> <span class="n">alpha</span> <span class="o">=</span> <span class="mf">0.4</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div></div>

<p><img src="2025-09-29-Unknown_BPSK_Decode_files/2025-09-29-Unknown_BPSK_Decode_14_0.png" alt="png" /></p>

<p>For hard-decision bits in BPSK, we can simply take the sign of the real portion:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">bits</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">sample</span> <span class="ow">in</span> <span class="n">decimated</span><span class="p">:</span>
    <span class="n">bit</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">sample</span><span class="p">.</span><span class="n">real</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="k">else</span> <span class="mi">1</span>
    <span class="n">bits</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">bit</span><span class="p">)</span>
</code></pre></div></div>

<p>Given the phase ambiguity of BPSK, we are given the first ASCII character is <code class="highlighter-rouge">A</code>- so if we don’t match that immediately, invert bits (or rotate phase in real-time system) to see if matching.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">preamble</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">]</span>
<span class="k">if</span> <span class="n">bits</span><span class="p">[:</span><span class="mi">8</span><span class="p">]</span> <span class="o">==</span> <span class="n">preamble</span><span class="p">:</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"Preamble found and matches expected, no inversion needed"</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">bits</span><span class="p">)):</span>
        <span class="n">bits</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">bits</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="k">else</span> <span class="mi">1</span>
    <span class="k">if</span> <span class="n">bits</span><span class="p">[:</span><span class="mi">8</span><span class="p">]</span> <span class="o">==</span> <span class="n">preamble</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"Preamble found after inversion"</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"Preamble not found!"</span><span class="p">)</span>

<span class="k">print</span><span class="p">(</span><span class="n">bits</span><span class="p">[:</span><span class="mi">8</span><span class="p">])</span>
</code></pre></div></div>

<p style="font-family:monospace; white-space:pre-wrap">
Preamble found after inversion
[0, 1, 0, 0, 0, 0, 0, 1]
</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">message</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">num_chars</span> <span class="o">=</span> <span class="mi">18</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_chars</span><span class="p">):</span>
    <span class="n">curr_bits</span> <span class="o">=</span> <span class="n">bits</span><span class="p">[</span><span class="n">i</span> <span class="o">*</span> <span class="mi">8</span> <span class="p">:</span> <span class="p">(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="mi">8</span><span class="p">]</span>
    <span class="n">byte_str</span> <span class="o">=</span> <span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="nb">map</span><span class="p">(</span><span class="nb">str</span><span class="p">,</span> <span class="n">curr_bits</span><span class="p">))</span>
    <span class="n">byte_val</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">byte_str</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
    <span class="n">message</span> <span class="o">+=</span> <span class="nb">chr</span><span class="p">(</span><span class="n">byte_val</span><span class="p">)</span>

<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Final demodulated and decoded message: </span><span class="si">{</span><span class="n">message</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
</code></pre></div></div>

<p style="font-family:monospace; white-space:pre-wrap">
Final demodulated and decoded message: ASLU-KZREUU-M9ZAW

</p>]]></content><author><name>John Gentile</name></author><summary type="html"><![CDATA[I saw an interesting DSP challenge come up in r/DSP, DrSDR/BPSK-Decode, in which a .wav file is given and we are looking to demodulate and decode the signal to get an “Amazon gift card claim code”.]]></summary></entry><entry><title type="html">Machine Learning Blog/Website Design</title><link href="https://john-gentile.com/2021/01/16/ML_notes_web_design.html" rel="alternate" type="text/html" title="Machine Learning Blog/Website Design" /><published>2021-01-16T00:00:00+00:00</published><updated>2026-05-04T03:59:20+00:00</updated><id>https://john-gentile.com/2021/01/16/ML_notes_web_design</id><content type="html" xml:base="https://john-gentile.com/2021/01/16/ML_notes_web_design.html"><![CDATA[<p>Building a technical blog or website of any kind poses many unique challenges, but there’s nearly infinite ways to design a site; you can choose from many different content management frameworks and site generator tools. For me, I decided to use <a href="https://jekyllrb.com/">Jekyll</a> static website generator because of the many FOSS extensions and development flexibility, as well as the low-cost (often free, like <a href="https://pages.github.com/">GitHub Pages</a>) hosting ability of static websites. With this base framework, I found a couple great add-ons and tools that help to make a great blog for technical notes, especially when working with Machine Learning notebooks.</p>

<p><a href="https://jupyter.org/">Jupyter Notebooks</a> are currently a very popular way for taking notes on research to prototyping Python code on a variety of frameworks. Jupyter Notebooks are great because the text and equations can be easily entered as Markdown, and the Python code blocks are interactive and executable. The challenge this presents is when you have valuable notes and/or code that you want to maintain as part of a Jupyter Notebook, but still want to have the ability to post the notebook as part of your website (or any HTML medium really).</p>

<p>I’ve tried more intensive methods such as <a href="https://elc.github.io/posts/embed-interactive-notebooks/">embedding notebooks using binder</a> or <a href="https://medium.com/@lzhou1110/how-to-embed-google-colaboratory-into-medium-in-3-steps-487b525b103c">embedding Google Colab notebooks in Medium posts</a>, however the most flexible tool is <code class="highlighter-rouge">nbconvert</code> that shops with Jupyter packages. It can convert notebooks into HTML, Markdown, PDF and a variety of other formats.</p>

<p>For documentation and website purposes, the non-interactive, static presentation should be fine; any necessary support for actual code edits or dynamic response should simply be handled with a link to the Notebook in a full environment with execution support, such as <a href="https://colab.research.google.com/">Google Colab</a>. What’s also great about Google Colab is that <a href="https://colab.research.google.com/github/googlecolab/colabtools/blob/master/notebooks/colab-github-demo.ipynb#scrollTo=8QAWNjizy_3O">you can directly run and edit notebooks on GitHub</a>, and create an easy “Open in Colab” badge like:
<a href="https://colab.research.google.com/github/JohnnyGOX17/john-gentile-website/blob/master/kb/math_and_signal_processing/notebooks/tf_basics.ipynb"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab" /></a></p>

<p>Another great plugin for technical research blogging is <a href="https://github.com/inukshuk/jekyll-scholar">inukshuk/jekyll-scholar</a>; this plugin inserts and formats bibliographies for Jekyll posts and makes citing sources very easy, similar to the process used in LaTeX documents. You can set the citation style to a variety of publication standards, as well as reference standard <code class="highlighter-rouge">*.bib</code> and <code class="highlighter-rouge">*.bibtex</code> bibliography files.</p>

<p>From there, depending on the exact format used, the insertion of a bibliography can be done with a Liquid tag:</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{% bibliography %}
</code></pre></div></div>

<p>Then, citations can be made in text, similar to LaTeX <code class="highlighter-rouge">\cite{author}</code> style, using another inline Liquid tag:</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{% cite author %}
</code></pre></div></div>

<p>These, and even more FOSS plugins, make for an easy pipeline for deploying technical research to static webpages, especially when dealing with Jupyter Notebooks for topics like Machine Learning.</p>]]></content><author><name>John Gentile</name></author><summary type="html"><![CDATA[Building a technical blog or website of any kind poses many unique challenges, but there’s nearly infinite ways to design a site; you can choose from many different content management frameworks and site generator tools. For me, I decided to use Jekyll static website generator because of the many FOSS extensions and development flexibility, as well as the low-cost (often free, like GitHub Pages) hosting ability of static websites. With this base framework, I found a couple great add-ons and tools that help to make a great blog for technical notes, especially when working with Machine Learning notebooks.]]></summary></entry><entry><title type="html">Agile for FW &amp;amp; FPGA Development</title><link href="https://john-gentile.com/2020/12/05/Agile_for_FW.html" rel="alternate" type="text/html" title="Agile for FW &amp;amp; FPGA Development" /><published>2020-12-05T00:00:00+00:00</published><updated>2026-05-04T03:59:20+00:00</updated><id>https://john-gentile.com/2020/12/05/Agile_for_FW</id><content type="html" xml:base="https://john-gentile.com/2020/12/05/Agile_for_FW.html"><![CDATA[<p>Agile development processes have been the popular trend in Software Development for quite awhile now. However Agile is now bleeding into other engineering areas, especially hardware, firmware and FPGA development. While there are general “best practices” in Agile methodology that can apply to virtually any development or decision process, the tooling and day-to-day project management with Agile has several caveats that need to be addressed when applied to hardware and firmware design.</p>

<ul>
  <li>Works great when teams have buy-in, falls apart when people don’t believe in it:
    <ul>
      <li>Use only the minimum amount of tooling/overhead needed to complete the planning/development of agile. Sometimes certain tools (like Jira) or methodologies have too many moving parts and take too much time to use (e.g. number of clicks to create a story, or number of meetings to attend for planning/pointing/etc.) that people are left with less time to even develop and become discouraged. Sometimes the simplicity of Kanban can be greater than the cost of Scrum</li>
      <li>Communication is still key between team members and other teams for an overall product. This is a culture and people problem to solve.</li>
    </ul>
  </li>
  <li>Since HW/FW has a relatively different development process than SW, show iterative progress to stakeholders mainly through development &amp; simulation/testing milestones:
    <ul>
      <li>Due to what’s involved in making an overall FPGA build (time, pinouts, STA, etc.) and the amount of time to have to rearchitect/replace blocks, its more advantageous to only make actual builds that- as far as anyone knows- will have the same architecture as the final system
        <ul>
          <li>Also using common, tightly-controlled interfaces (like AXI), along with pre-architected clock domains, goes a long way to allow multiple people to work on components within the same overall design and shorten the amount of time needed to stitch them all together for a functional build</li>
        </ul>
      </li>
      <li>Documentation &amp; test updates (even if minor) should always be part of the acceptance criteria before a story closes</li>
    </ul>
  </li>
  <li>There’s an underlying need for FW people to learn SW methodologies/tools (like git/commiting-only-necessary-source-files-not-build-products, unit testing/regression/self-checking-tests, portability/OOP, CI/CD of overall build, etc.) to be more successful at adapting to change or completing small, iterative tasks in a shorter period of time</li>
</ul>]]></content><author><name>John Gentile</name></author><summary type="html"><![CDATA[Agile development processes have been the popular trend in Software Development for quite awhile now. However Agile is now bleeding into other engineering areas, especially hardware, firmware and FPGA development. While there are general “best practices” in Agile methodology that can apply to virtually any development or decision process, the tooling and day-to-day project management with Agile has several caveats that need to be addressed when applied to hardware and firmware design.]]></summary></entry></feed>