| name | veriloga |
| description | Write Verilog-A behavioral modules for analog/mixed-signal IC design (Cadence Virtuoso / Spectre). Covers 12 circuit categories. TRIGGER FIRST (before evas-sim) when the user needs to write or generate a new .va file. Key trigger phrases: "write", "create", "generate", "code", "implement" + "Verilog-A / veriloga / va / .va / behavioral model". Also triggers on: review/fix Verilog-A, "behavioral model", "veriloga", "analog HDL", circuit spec → behavioral model, "voltage-domain", "current-domain". Do NOT defer to evas-sim for authoring — evas-sim is for running an already-written file.
|
Verilog-A Writer
Write correct, Virtuoso-ready Verilog-A behavioral modules. Rules extracted from 1,638
real-world .va files plus a 171-module reference library (14,311 LOC).
How to Use This Skill
Core workflow:
- Identify the circuit category → see Category Index
- Apply all mandatory rules below
- Start from template
assets/template.va
- Classify domain → scan code for voltage vs current constructs (see Domain Classification)
- Verify (optional) → hand off testbench/simulation to the verification stack (see Smoke Test)
- Explain usage → list every port (direction, what to connect) and key parameters
Reference policy:
- Start with the curated core DUT examples only
- Open
assets/examples-archive/ only if the core set does not cover the module
- Treat
signal-source/ and measurement-helpers/ as supplemental, not default references
Need guidance? Search these resources in this order:
- Category-specific patterns →
references/categories/adc-sar.md, comparator.md, etc.
- Project overrides (naming, supply voltage, headers) →
references/customize.md
- Domain classification help →
references/domain-routing.md
- EVAS-vs-Spectre parity gate for idt/idtmod decisions →
references/evas-parity-gate.md
- EVAS simulator support check →
references/evas-capabilities.manifest
- ADC behavioral verification →
references/adc-testbench-guide.md (runnable examples live under ../behavioral-va-eval/examples/)
- Advanced syntax (string parsing, current-domain functions, conditional compilation) →
references/verilog-a-advanced.md
- Working examples → core DUT examples first;
assets/examples-archive/ only as fallback
First-Pass Rule For ADC Metric Tasks
If the task involves ADC verification or ADC dynamic metrics (SAR, flash, pipeline, TIADC, etc.),
the first pass must follow references/adc-testbench-guide.md.
Required first-pass workflow:
- Read
references/adc-testbench-guide.md before writing the verification script
- Use the latest stable
adctoolbox release on PyPI; verify the current version before installing or pinning
- Use
find_coherent_frequency() to choose the stimulus frequency
- Use
analyze_spectrum() for spectrum and dynamic-metric analysis
- Report at least these fields when available:
enob, sndr_db, sfdr_db, snr_db, thd_db, bin_idx
Forbidden as the primary method:
- Hand-rolled FFT via
np.fft, scipy.signal, or custom ENOB/SNDR math
- CSV-only inspection as the main verification path
CSV inspection is allowed only as a secondary sanity check after the adctoolbox flow
has already produced the primary ADC metrics.
Reference File Guide
When you need help with a specific task:
| Task | Reference | Purpose |
|---|
| "How do I structure a comparator / ADC / DAC / filter?" | references/categories/ | Circuit-specific best practices, patterns, edge cases |
| "What naming convention should I use?" | references/customize.md | Project-specific overrides for ports, parameters, file headers |
| "Is my module voltage-domain or current-domain?" | references/domain-routing.md | Classification guide + mixed-domain splitting strategies |
| "Can EVAS simulate my voltage-domain module?" | references/evas-capabilities.manifest | Check EVAS supported constructs |
| "How do I verify my ADC behavioral model?" | references/adc-testbench-guide.md | ADC verification guidance; runnable voltage-domain flows belong to behavioral-va-eval |
| "Show me working examples" | Core DUT examples below | Default to the curated DUT set; use the broader archive only when needed |
| "What's this advanced syntax for?" | references/verilog-a-advanced.md | String parsing, $vt, branch, @(timer), conditional compilation, etc. |
Scope: This Skill Writes DUT Modules Only
This skill only writes the DUT (Device Under Test) — the Verilog-A behavioral module.
To simulate, you need a testbench. That is a separate concern handled by the evas-sim skill.
| Step | What | File | Skill |
|---|
| 1 | Write behavioral module | .va | this skill |
| 2 | Write testbench + run simulation | .scs | evas-sim |
Correct naming convention:
- DUT:
sar_adc.va, comparator.va, dac_8b.va — behavioral module, no tb_ prefix
- Testbench:
tb_sar_adc.scs, tb_comparator.scs — .scs netlist, tb_ prefix here
NEVER create tb_*.va. See evas-sim/SKILL.md for testbench structure and simulation workflow.
Example Selection Policy
Do not browse the full assets/examples/ tree by default.
- Default reference set: the curated core DUT examples below
- Fallback reference set:
assets/examples-archive/
- Supplemental only:
signal-source/, measurement-helpers/, and helper-heavy calibration patterns
If the request matches a common DUT category, stay within the core set unless it is clearly insufficient.
When you must consult assets/examples-archive/, treat it as legacy reference material and run
references/check_spectre_portability.py before copying any electrical-bus read pattern into a
Spectre-target design.
Mandatory Rules
Violating any rule causes simulator errors or silently wrong results.
Header Requirement: Always include the standard Verilog-A headers
For portable Spectre/Virtuoso-ready Verilog-A, start every .va file with:
`include "constants.vams"
`include "disciplines.vams"
electrical comes from disciplines.vams. If that include is missing, Spectre may report a
misleading syntax error near the first electrical declaration even though the real problem is the
missing header.
Rule 1: All signals use electrical type
No wire, logic, or reg. Spectre accepts two port styles:
Style A: ANSI inline — each port on its own line, each with its own electrical
module example (
inout electrical VDD,
inout electrical VSS,
input electrical clk_i,
output electrical [3:0] dout_o
);
Cadence Spectre VACOMP pitfall: electrical does NOT carry across a comma.
inout electrical VDD, VSS gives VSS no discipline → Spectre error.
EVAS tolerates this; Spectre rejects it. Always one port per line.
| Style | EVAS | Cadence Spectre |
|---|
inout electrical VDD, VSS | ✓ tolerates | ✗ VSS has no discipline |
inout electrical VDD,
inout electrical VSS, | ✓ | ✓ |
Style B: Old-style separated — discipline declared separately, comma-sharing is safe here:
module example (VDD, VSS, clk_i, dout_o);
inout VDD, VSS;
input clk_i;
output [3:0] dout_o;
electrical VDD, VSS, clk_i;
electrical [3:0] dout_o;
NOT accepted — combined inout electrical in body:
module example (VDD, VSS, clk_i, dout_o);
inout electrical VDD, VSS; // WRONG — Spectre syntax error
Vector (Array) Ports
For multi-bit buses, use vector notation with MSB at left, LSB at right (MSB:LSB).
Default convention: [31:0] means bit 31 is MSB, bit 0 is LSB.
Correct — vector ports (preferred for buses):
// Single-ended 10-bit data buses
input electrical [9:0] data_i [0:31]; // 32 channels, 10-bit each
output electrical [15:0] result_o; // Single 16-bit output
// Clock array
input electrical clk_i [0:31]; // 32 clock signals
Wrong — individual port enumeration:
// DON'T do this for large buses:
input electrical [9:0] DIN_0, DIN_1, DIN_2, ..., DIN_31; // Verbose and error-prone
input electrical CLK_0, CLK_1, CLK_2, ..., CLK_31; // Hard to maintain
Access vector elements:
genvar ch;
real out_target;
analog begin
// Constant/genvar indexing is fine for repeated analog contributions.
for (ch = 0; ch < 32; ch = ch + 1) begin
out_target = sampled_val[ch] ? vh : vl;
V(sampled_o[ch]) <+ transition(out_target, 0, trise, tfall);
end
end
For procedural/event code, do not read an electrical bus with a runtime loop index such as
V(data_i[i]) inside @(cross(...)) or @(initial_step). Spectre often rejects that pattern even
when EVAS accepts it.
Rule 2: Power ports are inout, not input
input VDD silently breaks power-aware simulation.
Rule 3: Supply voltages — read from ports or parameterize
Never hardcode 1.8 directly. Two approaches:
// Option A: from ports // Option B: parameterized
vh = V(VDD); parameter real vdd = 1.0;
vl = V(VSS); parameter real vth = vdd / 2.0;
vth = (vh + vl) / 2.0;
Default threshold: vth = (vdd + vss) / 2.
Rule 4: All declarations at module level
parameter, real, integer, genvar must appear before analog begin.
Correct:
module example (input electrical clk_i, output electrical [3:0] dout_o);
parameter integer nbits = 4;
real vth;
integer state;
genvar k;
analog begin
vth = 0.5; // Initialize in analog block
// ... rest of module
end
endmodule
Wrong — declaration inside analog block:
module example (input electrical clk_i, output electrical [3:0] dout_o);
analog begin
integer state; // ERROR: declaration not at module level
real vth; // ERROR: declaration not at module level
// ...
end
endmodule
Rule 5: Be strict about integer vs genvar
This is a major Spectre portability pitfall.
- Use
integer for procedural/runtime loops inside @(initial_step), @(cross(...)), and other
event blocks that update variables or arrays.
- Use
genvar for contribution/elaboration-style loops that stamp repeated analog contributions such
as V(out[k]) <+ ... or I(branch[k]) <+ ....
- Do not use an
integer loop variable to index an electrical bus in expressions like
V(code_i[k]) or I(bus[k]) inside procedural code; Spectre often rejects this. Unroll the bus
access explicitly or restructure the code.
- EVAS may still compile some of these runtime-indexed bus reads. Treat EVAS success as an EVAS-only
result, not as proof of Spectre portability.
- If Spectre is unavailable, run
python veriloga/references/check_spectre_portability.py path/to/file.va
and explicitly state that the model was not Spectre-compiled.
Correct - integer for runtime/procedural loops:
integer i;
analog begin
@(initial_step or cross(V(CLKS)-vdd/2, +1)) begin
for (i = `NUM_ADC_BITS; i >= 1; i = i - 1) begin
dout[i] = 0;
end
end
end
Correct - genvar for repeated analog contributions:
genvar k;
analog begin
for (k = 0; k < N_BIT; k = k + 1) begin
if ((code >> k) & 1)
V(DOUT_o[k]) <+ transition(V(VDD), 0, tedge);
else
V(DOUT_o[k]) <+ transition(V(VSS), 0, tedge);
end
end
Wrong - integer indexing an electrical bus in procedural code:
integer k;
analog begin
@(cross(V(clk_i) - vth, +1)) begin
for (k = 0; k < 4; k = k + 1) begin
if (V(code_i[k]) > vth)
code = code + (1 << k);
end
end
end
Safer rewrite - explicitly unroll electrical-bus reads:
analog begin
@(cross(V(clk_i) - vth, +1)) begin
code = 0;
if (V(code_i[0]) > vth) code = code + 1;
if (V(code_i[1]) > vth) code = code + 2;
if (V(code_i[2]) > vth) code = code + 4;
if (V(code_i[3]) > vth) code = code + 8;
end
end
Rule 6: Initialize state in @(initial_step)
Uninitialized variables default to 0 or garbage depending on simulator.
Rule 6 corollary — startup style: keep startup-dependent state explicit inside
@(initial_step) even when simulators can see preceding analog assignments. Use it to seed PRBS,
counters, lock streaks, and nonzero defaults. Avoid relying on ambiguous startup ordering when a
single explicit initialization is clearer.
Rule 7: Edge detection uses @(cross()) with direction
+1 rising, -1 falling. Omit direction only when both edges are needed.
Rule 8: Outputs use transition() — prefer a discrete target variable
`default_transition 10p
real out_target;
out_target = state ? vh : vl;
V(out_o) <+ transition(out_target, 0);
Pitfall: Multiple <+ to the same node adds contributions, not overwrites.
Use a temporary variable and assign once.
Project style rule: even if some simulators accept more compact expressions, benchmark gold and
first-pass authored DUTs should prefer the explicit target-variable form above. It is easier to
audit and keeps EVAS / Spectre authoring style aligned.
Multi-output corollary — target-buffer pattern: for multiple transition-driven
outputs, do not put transition() inside if/case/runtime for branches.
Declare one target per output, or a real target[0:N-1] array. Update only those
targets inside events and conditions. Put the electrical contributions at analog
top level:
real out_t[0:3];
integer j;
genvar k;
analog begin
@(initial_step) begin
for (j = 0; j < 4; j = j + 1) out_t[j] = vlo;
end
@(cross(V(clk) - vth, +1)) begin
out_t[0] = state0 ? vhi : vlo;
out_t[1] = state1 ? vhi : vlo;
out_t[2] = state2 ? vhi : vlo;
out_t[3] = state3 ? vhi : vlo;
end
for (k = 0; k < 4; k = k + 1) begin
V(out[k]) <+ transition(out_t[k], 0, tr, tf);
end
end
If a simulator or benchmark port style makes genvar inconvenient, explicitly
unroll the contribution lines. The important rule is that the transition()
contributions are unconditional and outside runtime integer loops.
Category Index
| Category | Reference File | Domain |
|---|
| ADC / SAR | references/categories/adc-sar.md | voltage |
| DAC | references/categories/dac.md | either |
| Comparator | references/categories/comparator.md | voltage |
| PLL / Clock | references/categories/pll-clock.md | either |
| Sample & Hold | references/categories/sample-hold.md | voltage |
| Amplifier & Filter | references/categories/amplifier-filter.md | current |
| Digital Logic | references/categories/digital-logic.md | voltage |
| Signal Source | references/categories/signal-source.md | voltage (supplemental) |
| Passive & Model | references/categories/passive-model.md | current |
| Measurement Helpers | references/categories/measurement-helpers.md | voltage (supplemental / verification-leaning) |
Testbench (.scs) | references/categories/testbench-spectre.md | N/A |
| Power & Switch | references/categories/power-switch.md | either |
| Calibration | references/categories/calibration.md | voltage |
Module Template
Start from assets/template.va. Reference the core DUT examples below before browsing assets/examples-archive/.
Core DUT Examples
Use these first. They are the primary authoring references for this skill:
assets/examples/adc-sar/sar_logic_4b_sync.va
assets/examples/adc-sar/sar_logic_4b_async.va
assets/examples/adc-sar/sar_4b_behavioral.va
assets/examples/comparator/comp_fire_reset.va
assets/examples/comparator/comp_latching.va
assets/examples/dac/dac_4b_binary_weighted.va
assets/examples/digital-logic/and2_gate.va
assets/examples/digital-logic/dff_set_reset.va
assets/examples/sample-hold/single_edge_sampler.va
assets/examples/pll-clock/pfd_with_reset.va
assets/examples/amplifier-filter/lpf_1st_order.va
assets/examples/power-switch/conductance_switch.va
assets/examples/passive-model/rlc_network.va
Supplemental Examples
The files under assets/examples-archive/ are secondary references. They are
useful for edge cases, variants, helpers, or legacy patterns, but they should
not be the first examples surfaced by this skill.
In particular:
signal-source/ is supplemental and often serves as stimulus support
measurement-helpers/ is supplemental and verification-leaning
- helper-style ADC files such as ideal comparators or small conversion blocks
should not outrank the core SAR/DAC examples above
Runnable verification flows, .scs testbenches, and example simulations belong
to ../behavioral-va-eval/examples/, not this DUT example archive.
Useful Syntax — Common Patterns
Mathematical Constants — Define at Top
Verilog-A does not predefine PI. Define at module top or use numeric literals:
Option A: Use define macro
`define PI 3.14159265359
module example (...);
// ...
phase = 2 * `PI * freq * $abstime;
endmodule
Option B: Use numeric literal
module example (...);
// ...
phase = 2 * 3.14159265359 * freq * $abstime;
endmodule
V(A, B) — differential voltage
Dp = V(VINP, VINN) > VOS; // compare differential pair against offset
@(above()) — level-sensitive threshold
@(above(V(RST) - vth)) begin // fires at t=0 if already true
state = 0;
end
Internal voltage nodes
voltage [15:0] shadow;
real shadow_target;
real out_target;
shadow_target = active ? 1.0 : 0.0;
out_target = V(shadow[i]) > 0.5 ? vh : vl;
V(shadow[i]) <+ transition(shadow_target, 0, 100p, 1p);
V(OUT[i]) <+ transition(out_target, 0);
transition() as intermediate variable
real clk_state;
real clk_delayed;
real clkout_target;
clk_state = cond ? 1.0 : 0.0;
clk_delayed = transition(clk_state, 200p, 1p, 1p);
clkout_target = clk_delayed > 0.5 ? vh : vl;
V(CLKOUT) <+ transition(clkout_target, 0);
analog single-line (no begin/end)
analog
V(sigout) <+ k1 * V(sigin1) + k2 * V(sigin2);
generate — compile-time loop
generate j (0, 7) begin
comp_var[j] = 1.0 + mismatch * abs($random(j) / `MAXINT);
end
$random() — mismatch modeling
r1 = abs($random(seed) / `MAXINT); // uniform [0, 1]
cap = cap_nominal * (1.0 + r1 * mismatch);
@(final_step) — end-of-simulation
@(final_step) begin
$strobe("samples = %d, avg = %f", cnt, sum / cnt);
$fclose(fh);
end
@(initial_step("analysis")) — filtered init
`define PI 3.14159265359
@(initial_step("ac", "dc")) begin
c = 1 / (2 * `PI * r * bandwidth);
end
$abstime — simulation time
`define PI 3.14159265359
phase = 2 * `PI * freq * $abstime;
$bound_step() — timestep control
$bound_step(1.0 / (32 * inst_freq));
$display / $strobe / $finish
$display("val = %f at t = %e", val, $abstime); // immediate
$strobe("val = %f", val); // end of timestep
$finish; // terminate
File I/O: $fopen / $fstrobe / $fclose
@(initial_step) fh = $fopen("output.dat", "w");
@(timer(next)) $fstrobe(fh, "%e\t%e", $abstime, V(sig));
@(final_step) $fclose(fh);
idtmod() — modular integration (VCO)
`define PI 3.14159265359
phase = idtmod(freq, 0, 1);
V(out) <+ amp * sin(2 * `PI * phase);
case/endcase
case (state)
0: out = vl;
1: out = vh;
endcase
Advanced Syntax
For specialized use cases (string parsing, analysis-specific code, current-domain functions, etc.),
see references/verilog-a-advanced.md.
Domain Classification
Scan the analog begin block to classify the module's domain.
Construct → Domain
Voltage-domain (event-driven — EVAS):
Read from EVAS manifest for the latest list. Obtain manifest in order:
- EVAS CLI:
evas --capabilities (if installed)
- GitHub:
https://raw.githubusercontent.com/Arcadia-1/EVAS/main/evas-capabilities.manifest
- Local cache:
references/evas-capabilities.manifest
If fetched successfully, update the local cache.
Current-domain (KCL solver — OpenVAF + ngspice) — these are permanent, won't change:
I() <+, V(a,b) <+ I(a,b)*R, ddt(), idt(), idtmod(), laplace_nd(),
limexp(), slew(), $vt/$temperature, flicker_noise(), white_noise(),
branch, nature/discipline
Domain-neutral (either):
V() read-only, parameter/real/integer, if/else/for/generate,
`define/`ifdef/`include, math functions, exclude/from
Decision tree
1. Has current-domain constructs? (I() <+, ddt, idt, idtmod, laplace_nd, limexp, slew, branch)
YES → also has voltage-only? (@(cross), transition, genvar, arrays)
YES → MIXED: reject, suggest splitting (see domain-routing.md)
NO → CURRENT-DOMAIN → OpenVAF + ngspice
NO → VOLTAGE-DOMAIN → EVAS (or pure V() <+ expression default)
"Either" categories (DAC, PLL, Power/Switch) require code-level analysis.
idt/idtmod Policy (EVAS-first)
For modules intended to be checked in both EVAS and Spectre:
- Generate EVAS-friendly model code first (event/discrete implementation)
- Run EVAS pre-check first; require functional dynamics before cross-simulator checks
- Run Spectre verification on the same stimulus and compare reports
- If mismatch exists, prioritize EVAS-side semantic fixes first
- EVAS fixes must not change EVAS matrix-solver core principles
- If repeated EVAS fixes are ineffective, then inspect model-code assumptions
- Enable
idt() / idtmod() only when dual-simulator A/B evidence proves better consistency
Mandatory flow:
- Build non-idt baseline model
- Run EVAS pre-check
- Run Spectre verification on identical testbench/stimulus
- If mismatch is above threshold, patch EVAS semantics and re-run
- After multiple ineffective EVAS patches, inspect model code
- Build idt/idtmod candidate only when justified
- Keep idt/idtmod only if mismatch metrics improve and lock behavior does not regress
Use references/evas-parity-gate.md as the acceptance standard.
Benchmark Authoring Notes
These notes matter when the DUT you write will later become benchmark gold or will be evaluated in
the behavioral-veriloga-eval pipeline.
Port discipline note
Use one ANSI-inline electrical port per line. Do not rely on comma-sharing such as
inout electrical VDD, VSS.
PRBS / LFSR note
Initialize PRBS seeds to a nonzero state in @(initial_step), for example:
@(initial_step) begin
lfsr = 7'h01;
end
PLL parity note
If a DUT will feed a PLL / clock benchmark, the benchmark metadata must use
"parity_policy": "pll_task_aware". The evaluation should compare lock / relock behavior,
directional control pulses, and vctrl trends, not pointwise waveform error.
tb-generation handoff note
When handing a DUT to a tb-generation task, the benchmark metadata should keep scoring to
compile / execution gates only:
{
"scoring": ["dut_compile", "tb_compile"],
"parity_policy": "not_required"
}
Requirement-to-code checklist
For benchmark or eval-style DUT generation, the final assistant response must
include an explicit coverage checklist that maps each prompt requirement to the
generated code location. Use file/line references when available, or a precise
identifier when line numbers are not available yet.
The checklist must cover at least:
- Required ports and bus widths
- Required public parameters
- Reset, lock, ready, or end-of-conversion behavior
- Noise, jitter, mismatch, or monitor behavior named in the prompt
$bound_step() or equivalent timestep control for oscillator/source tasks
idt() / idtmod() acceptance or rejection under references/evas-parity-gate.md
Do not summarize this as "all requirements implemented." If a prompt item is
intentionally not implemented, mark it as not applicable or deferred and give
the simulator/parity reason. Before finalizing a benchmark answer, re-read the
prompt/eval metadata and confirm the signature items above are visible in the
code or explicitly rejected by the domain-routing rules.
Simulation Routing
See references/domain-routing.md for full details.
EVAS capabilities: fetch manifest per § Domain Classification above.
Local cache: references/evas-capabilities.manifest.
- Voltage-domain → ready for EVAS as-is
- Current-domain → delegate to
openvaf skill
- Mixed → do NOT simulate; guide user to split (see domain-routing.md § Mixed)
Smoke Test
Current-domain
Delegate to openvaf skill: compile check → load check → tran sanity.
Voltage-domain
Use EVAS for local simulation when the CLI is installed/configured.
If EVAS is unavailable in the current environment, fall back to a static
compatibility check against evas-capabilities.manifest.
If EVAS passes but Spectre is unavailable, also run the Spectre portability check
at references/check_spectre_portability.py and state clearly that the result is
"EVAS-validated, not Spectre-compiled".
Mixed-domain
Do not attempt. Refer to domain-routing.md § Mixed.
ADC Characterization & Verification
For ADC-related modules (SAR, flash, pipeline, TIADC, etc.), verification must use
the latest stable adctoolbox release on PyPI as the primary analysis path.
- Verify the current package version before installing or pinning
- As of 2026-03-20, the latest PyPI release is
adctoolbox==0.6.4
- First read and follow
references/adc-testbench-guide.md
- Never hand-roll FFT with
np.fft, scipy.signal, or custom metric formulas
- Always call
analyze_spectrum() for dynamic metrics
- Report at least:
result['enob'], result['sndr_db'], result['sfdr_db'],
result['snr_db'], result['thd_db'], result['bin_idx']
- Metrics to review include ENOB, SNDR, SFDR, THD, SNR; TIADC work may also need
per-channel mismatch, nonlinearity, and jitter analysis
- CSV-only checks are secondary sanity checks only; they do not replace the primary
adctoolbox-based verification flow
- Always call
find_coherent_frequency() to avoid spectral leakage
- Full workflow:
references/adc-testbench-guide.md · Runnable examples: ../behavioral-va-eval/examples/
Customization
Read references/customize.md at session start for project-specific overrides
(port naming, supply voltage, file headers, simulator config).