Skip to main content

Playground · research instrument

On-device AI

FPGA Verilog Live Sculptor

Type Verilog and watch silicon sculpt itself: live parser infers LUTs/registers/DSPs/BRAM, estimates logic depth and critical path, lights a CLB floorplan, draws cycle-accurate setup/hold waveforms with slack, flashes red on violation, and locks a harmonic triad when timing closes. Place & Route is animated — all client-side, no server.

Independent research instrument — not claimed as MakerPortal shipped product code. Methods, equations, assumptions, and limitations are disclosed so you can inspect what the page does and does not establish.

Verilog RTL

// live parse

LUTs

Regs

DSP / BRAM

Modules
Lines
Logic levels
T_arrival ns
T_required ns
Setup slack ns
Hold slack ns
F_max MHz
Util %

Critical paths (est)

    How parsing maps

    • assign → 1 LUT + op weight
    • always @(*) → 2 LUTs, comb
    • always @(posedge clk) + <= → registers
    • * → DSP48, reg [..][..] → BRAM
    • T_route grows with spread slider — mimics congestion

    CLB floorplan — LUTs lighting, spine = clk, orange=DSP purple=BRAM

    LUT Reg DSP BRAM
    PartnerPCBWay5% first order commission + $10 coupon, friend gets $5 credit.
    Explore ↗

    Timing visualizer — CLK, Data arrival, Required, Slack

    Gray = clock period, grid 1ns
    Yellow arrow = T_arrival (logic+route)
    Cyan lane = hold min, Red region = violation
    Green flash = slack≥0, lock chord

    Anatomy of the sculptor

    Parser → Netlist → Delay

    1. Strip: remove // and /* */ comments, preserve line numbers for LUT tracing.
    2. Lex: regex for module, assign, always @(posedge), always_comb, <=, operators. No elaboration — fast O(n).
    3. Infer: LUT = assigns + ⌈0.35·bitwise⌉ + ⌈0.5·unary-~⌉ + ⌈0.55·addSub⌉ + 2·combBlocks + muxes. Regs = non-blocking <= assignments — the first depth-0 assignment operator in a statement, so if (a <= b), a for bound and assign comparisons are not counted. DSP = isolated * (sensitivity star excluded). BRAM = reg [...] mem [...].
    4. Levels: N_logic = ⌈1.2·log2(LUTs) + 0.15·assigns + 0.25·muxes⌉ mimics fanin growth. Depth visualized in heat.
    5. Floorplan: 14×14 CLB grid (196 LUTs). Regs cluster towards central clock spine (col 6-7). DSP stacked bottom, BRAM right edge. Placement animates in PnR to show iterations.

    Delay model

    Tarr=Tcq+Nlogic(TLUT+Troute)+NDSPTDSP+NBRAMTBRAMT_{arr}=T_{cq}+N_{logic}(T_{LUT}+T_{route})+N_{DSP}T_{DSP}+N_{BRAM}T_{BRAM}

    T_route slider maps to congestion 0.12 → 0.57 ns per hop.

    Visual-audio closure

    • Floorplan cells: base #1e232e, lit LUT #a3e635 with alpha = 0.5 + 0.5·(depth/max). Animated fill sweep simulates placer passes.
    • Timing canvas: CLK as 50% duty square, scaled so 1 ns = ~36 px. Data arrival drawn as rising edge with arrow at T_arr. Required = T_clk - T_setup - T_jitter.
    • Setup violation: red overlay between T_arr and T_required when slack<0, pulses via sin flash at 6Hz.
    • Hold lane: thin cyan trace at T_arr_min=0.22·T_arr. If < T_hold, lane flashes red & sub-blast.
    • Audio: Web Audio, 3 oscillators saw→sine, just major 4:5:6 (220,275,330Hz). Gain=0 when violation, ramps to 0.1–0.14 on closure. Spatial: left/right staggered ±15ms to widen.

    Hold check

    slackhold=Tarr,minThold,Tarr,min0.22Tarrslack_{hold}=T_{arr,min}-T_{hold},\quad T_{arr,min}\approx0.22\,T_{arr}

    The math and physics, in full

    Setup slack

    slacksetup=TclkTsetupTuncertTarrivalslack_{setup}=T_{clk}-T_{setup}-T_{uncert}-T_{arrival}

    Positive means data arrives before setup window. T_uncert includes jitter slider.

    Hold slack

    slackhold=Tarr,minThold,  Fmax=1Tarr+Tsetupslack_{hold}=T_{arr,min}-T_{hold},\; F_{max}=\frac{1}{T_{arr}+T_{setup}}

    Logic levels & utilization

    Nlogic=1.2log2(LUTs)+0.15Assigns+0.25Muxes,  U=LUTs196100%N_{logic}=\lceil 1.2\log_2(LUTs)+0.15\cdot Assigns+0.25\cdot Muxes\rceil,\; U=\frac{LUTs}{196}\cdot100\%

    Gear behind this build

    Verilog stack · 5 picks

    Hardware picks5

    More gear across every app: the full Gear list →

    Hardware Kit Builder

    Build the physical hardware platform. Select your components below to generate a live, real-time bill of materials and build instructions.

    Build this lab

    Verilog → FPGA hardware path

    Arty A7-35T Artix-7 + SparkFun MicroMod + Thing Plus RP2350 — synthesize live, validate timing with real LUT/FF utilization. MicroMod & RP2350 are SparkFun Originals (10%).

    Prices shown were retrieved from the Amazon Product Advertising API on 19 July 2026 and are indicative only — the price and availability on Amazon at the time of purchase apply.

    Estimated total

    $545

    Prices from Amazon catalog cache · may change

    Open primary listing ↗

    Kit Total

    Buy ↗

    Core solver — TypeScript

    // Mirrors src/lib/verilog-netlist.ts — the parser this page actually runs.
    // The listing below is kept fragment-identical to the lib by
    // verilog-netlist.test.ts; editing one without the other fails the suite.
    // Tells `q <= d;` apart from the RELATIONAL <= in `if (a <= b)`,
    // `for (i = 0; i <= 7; ...)` and `assign lt = a <= b;`. The regex this
    // replaced matched any <= with a semicolon after it, so a purely
    // combinational module reported registers it does not have.
    export function countNonBlocking(src) {
      const statements = []; let depth = 0, start = 0;
      for (let i = 0; i < src.length; i++) {          // cut at ; / begin / end / else,
        const c = src[i];                             // but only at paren depth 0, so a
        if (c === '(') { depth++; continue; }         // for(...;...;...) header stays whole
        if (c === ')') { if (depth > 0) depth--; continue; }
        if (depth !== 0) continue;
        if (c === ';') { statements.push(src.slice(start, i)); start = i + 1; continue; }
        if (c === 'b' || c === 'e') {
          if (/[\w$]/.test(src[i - 1] ?? '')) continue;
          const kw = /^(?:begin|end|else)\b/.exec(src.slice(i, i + 6));
          if (!kw) continue;                          // endmodule / endcase fail \b
          statements.push(src.slice(start, i)); i += kw[0].length - 1; start = i + 1;
        }
      }
      statements.push(src.slice(start));
      let count = 0;
      for (const stmt of statements) {
        let d = 0;                        // no assign special case: lhs = rhs puts = first
    
        for (let i = 0; i < stmt.length; i++) {       // first depth-0 assignment op decides
          const c = stmt[i];
          if (c === '(') { d++; continue; }
          if (c === ')') { if (d > 0) d--; continue; }
          if (d !== 0) continue;
          if (c === '<' && stmt[i + 1] === '=') { count++; break; }
          if (c === '=' && stmt[i + 1] !== '=' && !'=!<>'.includes(stmt[i - 1] ?? '')) break;
        }
      }
      return count;
    }
    
    export function parseVerilogToNetlist(src, clkPeriod, routingSpread, jitter) {
      const noComments = src.replace(/\/\/.*$/gm,'').replace(/\/\*[\s\S]*?\*\//g,'');
      const modules = [...noComments.matchAll(/module\s+(\w+)/g)].map(m=>m[1]);
      const assigns = (noComments.match(/^\s*assign\s+/gm)||[]).length;
      const alwaysPos = (noComments.match(/always\s*@\s*\(\s*posedge/gi)||[]).length + (noComments.match(/always_ff\s*@/gi)||[]).length;
      const alwaysComb = (noComments.match(/always\s*@\s*\(\s*\*/g)||[]).length + (noComments.match(/always_comb/gi)||[]).length;
      const ops = (noComments.match(/[&|^]/g)||[]).length;
      const tildes = (noComments.match(/~/g)||[]).length;
      const addSub = (noComments.match(/(?<![=!<>])\+(?!\+)|(?<![=!<>])-(?!-)/g)||[]).length;
      // isolated * not part of ** and not the @(*) sensitivity star
      const noSensitivity = noComments.replace(/@\s*\(\s*\*\s*\)/g, '@ALL');
      const dsp = (noSensitivity.match(/(?<!\*)\*(?!\*)/g)||[]).length;
      const nb = countNonBlocking(noComments);
      const bram = (noComments.match(/reg\s*\[[^\]]+\]\s*\w+\s*\[[^\]]+\]|\bBRAM|\bRAMB|mem\[|memory/gi)||[]).length;
      const muxes = (noComments.match(/\bcase\b|\?\s*:/g)||[]).length;
      let luts = assigns + Math.ceil(ops * 0.35) + Math.ceil(tildes * 0.5) + Math.ceil(addSub * 0.55) + alwaysComb * 2 + muxes;
      if (luts === 0 && noComments.trim().length > 10) luts = 1;
      const regs = nb>0? nb : alwaysPos*2;
      const levels = Math.max(1, Math.ceil(Math.log2(Math.max(1,luts))*1.2 + assigns*0.15 + muxes * 0.25));
      const tLut=0.35, tRoute=0.12+routingSpread*0.45, tDsp=1.15, tCq=0.18, tSetup=0.08, tHold=0.05;
      const arrival = tCq + levels*(tLut+tRoute) + dsp*tDsp + bram*0.9;
      const required = clkPeriod - tSetup - jitter;
      const slack = required - arrival;
      const holdSlack = arrival*0.22 - 0.05;
      const fmax = 1000/(arrival + tSetup);
      return { modules, luts, regs, dsps:dsp, brams:bram, levels, arrival, required, slack, holdSlack, fmax };
    }

    Export · Soft gate

    Export Verilog + timing report

    Your RTL plus synthesized LUT/FF estimate and slack — free watermarked, clean after email. Use with Digilent Arty A7-35T + MicroMod carrier (SparkFun Originals).

    File · verilog-module.v

    verilog-module.vtext/x-verilog+ watermark line on free path

    Free download adds a small footer: # Export from makerportal.ai — free watermarked build. Unlock c…Clean export removes footer. Both are generated fresh from your current sim tuning.

    Privacy: email stays in your browser localStorage (mp_export_email_verilog-live-sculptor) + unlock flag (mp_export_unlock_verilog-live-sculptor). If Buttondown username is configured, we also POST to Buttondown (privacy-first mode, no tracking pixels per D-014). See privacy → affiliates & email.

    Unlock clean export

    Soft gate — no hard paywall, no Clerk. Email stays local unless you explicitly check the newsletter box. Unsubscribe anytime. RSS at /rss.xml.

    Export → Fab bonusAfter export, your tuned stackup can be ordered via PCBWay/JLCPCB CTA (when live) — see /privacy#affiliates for live merchants.

    Fabricate this design

    From sim → PCB

    Export your live Verilog with LUT/slack header + notes for MicroMod carrier PCB. Order carrier on PCBWay Shared Project (10% PCB+SMT) or JLCPCB Brand Advocate path when live.

    Export-ready CTA

    What you get

    • Stackup CSV / Gerbers tuned in sim (W/H/εr, microstrip Z₀, array spacing → fab notes)
    • PCBWay Shared Project if available — 10% PCB + 10% SMT revenue when others order same design
    • Privacy-safe — no extra tracking beyond merchant checkout; disclosure in /privacy#affiliates
    Simverilog-live-sculptorNo export yet — tune then export

    Order boards

    Order on PCBWayReferral · 5%+

    PCBWay Shared Projects pay 10% PCB + 10% SMT — one fab conversion beats many Amazon electronics clicks.

    StatusPCBWay live · JLCPCB pending

    Frequently asked questions

    How does LUT counting work without synthesis?

    We lex Verilog with regex: each `assign`, each operator chain, and each combinational `always @(*)` infers LUTs. An assign is 1 LUT + ceil(0.35*bitwiseOps) + ceil(0.5*unary-~) + ceil(0.55*addSubs); each case/ternary mux adds ~1; a comb always block is 2 LUTs. It is not Vivado, but correlates within ~30% for <200 LUT designs. DSP inference is any `*` not part of `**` and not the `@(*)` sensitivity star, BRAM is pattern `reg [A:0] mem [B:0]`.

    How is timing estimated?

    Critical path T_arr = T_cq + N_logic*(T_LUT+T_route) + N_DSP*T_DSP + N_BRAM*T_BRAM. T_LUT=0.35ns, T_route=0.12ns+ spread*0.45ns, T_DSP=1.15ns, T_cq=0.18ns. Logic levels N_logic = ceil(1.2*log2(LUTs)+0.15*assigns+0.25*muxes). Slack_setup = T_clk - T_setup - T_jitter - T_arr. T_setup=0.08ns. Hold slack = T_arr_min - T_hold, T_arr_min≈0.22*T_arr, T_hold=0.05ns.

    Why do I hear a chord only when timing closes?

    Audio uses 3 sine oscillators forming a just-intoned major triad (110Hz base: 220Hz, 275Hz, 330Hz ≈ 4:5:6). Master gain ramps to 0.12 only when slack_setup ≥0 and slack_hold ≥0. If slack <0, gain →0 and a 30Hz sub is pulsed for hold violations. This is intentional biofeedback: green ears = closed timing.

    What maps to the floorplan canvas?

    A 14×14 CLB grid (196 LUT slots). Placement order is heuristic: registers anchor to nearest clock spine column (center), LUTs spread by fanout estimate from textual dependency. DSPs appear as 2×2 orange tiles, BRAM as 1×3 purple bars. Color intensity = logic depth estimate. When you click Place & Route, we animate incremental lighting to mimic PnR iterations, adding routing congestion heat on high spread.

    Can I paste my own SystemVerilog?

    Yes. Parser ignores comments, handles `always_ff @(posedge)`, `always_comb`, `logic`, `module`. It does not elaborate generates. For big files >500 lines, we cap floorplan to first 196 LUTs and show utilization % >100 as overflow, which matches real FPGA overflow behavior.

    Shareable still

    The instrument, captured—not illustrated.

    This 16:9 frame is rendered from the real browser instrument above. It is the page's canonical preview for image search, link unfurls, and posts that need to show what the tool actually does.

    Download 1280 × 720 JPEG
    FPGA Verilog Live Sculptor — live MakerPortal instrument screenshot
    Canonical capture · real UI · no generated scientific artwork