Language standard pins:
- Synthesizable RTL: IEEE 1800-2009 (iverilog parses with
-g2012— the 2012 parser handles 2009 code) - SV verification code: IEEE 1800-2012
- 2012+ features (checker, interface class, let, soft constraint) are verification-only — never in RTL
1. Project Overrides (Take Precedence Over lowRISC)
IMPORTANT — These rules differ from the lowRISC guide and must always be applied.
- Port direction prefix (mandatory): input
i_, outputo_, bidirio_—i_data,o_valid,io_sda. Suffixes (_i/_o/_io) are forbidden (lowRISC uses suffixes; this project does not). Exception: clock/reset ports take no prefix (clk,sys_clk,rst_n,sys_rst_n). - Clock naming:
clk(single) or{domain}_clk(multi:sys_clk,pixel_clk) — neverclk_i. - Reset naming:
rst_n(single) or{domain}_rst_n(multi) — active-low async mandatory, neverrst_ni. - CamelCase fully prohibited (lowRISC allows it for params/enums — this project does not):
parameter
ALL_CAPS(DATA_WIDTH), localparamL_prefix +ALL_CAPS(L_ADDR_BITS), enum valuesALL_CAPS(ST_IDLE). All identifiers:snake_caseorALL_CAPSonly.
2. Naming Conventions
| Target | Rule | Example |
|---|---|---|
| Module | snake_case |
axi_lite_slave |
| Parameter (externally configurable) | ALL_CAPS |
DATA_WIDTH, DEPTH |
| Local parameter (internal only) | L_ prefix + ALL_CAPS |
L_ADDR_BITS, L_CNT_MAX |
| Type (typedef) | snake_case_t suffix |
state_t, bus_req_t |
| Enum type (typedef enum) | snake_case_e suffix |
state_e, cmd_type_e |
| Enum values | ALL_CAPS |
ST_IDLE, WAIT_RESP |
| `define macros | ALL_CAPS |
MAX_DEPTH, ASSERT_ON |
| Instances | u_ prefix |
u_fifo, u_arbiter |
| Generate blocks | gen_ prefix |
gen_pipeline_stage |
| Signals (internal) | snake_case |
write_enable, addr_valid |
UVM Exception: UVM class member handles use
m_prefix per industry convention (m_driver,m_monitor).u_prefix applies to RTL module instances only.
3. Filename Conventions
| Type | Pattern | Example |
|---|---|---|
| Module | module_name.sv |
axi_lite_slave.sv |
| Package | module_name_pkg.sv |
cabac_pkg.sv |
| Interface | module_name_if.sv |
axi_if.sv (iverilog unsupported — do NOT generate, §5.3) |
| Testbench | tb_module_name.sv |
tb_axi_lite_slave.sv |
| SVA bind | sva_module_name.sv |
sva_axi_lite_slave.sv |
One module per file, filename matches module name.
4. Language Rules (lint-enforced deltas)
logiconly (reg/wireforbidden);always_ff(sequential,<=) /always_comb(combinational,=); noalways_latch- ANSI ports (
input logic/output logic); shared types via packages (_pkg.sv); usetypedef enum/typedef struct packed defaultmandatory in allcasestatements (latch prevention); no combinational loops- No
initialblocks or#delayin synthesizable code; no magic numbers (useparameter/localparam)
5. Tool-Specific Caveats
5.1 Declaration Order — Xcelium Strict (IEEE 1800 §12.5)
All identifiers must be declared before first use. Xcelium (xmvlog) strictly enforces sequential declaration visibility within a module; Verilator/iverilog are lenient.
Mandatory module structure order (see templates/module-template.sv for full scaffold):
1. parameter declarations 2. port declarations (ANSI)
3. import statements 4. typedef / localparam / enum
5. internal signal declarations -- declaration boundary (no declarations below) --
6. assign statements 7. submodule instances (u_ prefix)
8. always_comb blocks 9. always_ff blocks
10. assertions (SVA)
Reordering concurrent statements has zero synthesis/simulation impact, but declarations
MUST always precede their first reference. Package file: module_name_pkg.sv.
5.2 VCS Strict always_ff Rules (Verification TB Caveat)
VCS enforces IEEE 1800 always_ff semantics strictly — a variable driven by always_ff must NOT also be driven by initial, always_comb, or task blocks (ICPD error). Verilator and iverilog are lenient on this.
RTL code: No issue — RTL should never mix always_ff with initial for the same signal.
Verification TB code (coverage counters, debug registers): If a testbench variable needs both sequential update (posedge clk) and procedural initialization (initial or task), use always @(posedge clk) instead of always_ff:
// BAD — VCS ICPD error: cov_cnt driven by always_ff AND initial
always_ff @(posedge clk) cov_cnt <= cov_cnt + 1;
initial cov_cnt = 0;
// GOOD — always @(posedge) allows multiple drivers in TB
always @(posedge clk) cov_cnt <= cov_cnt + 1;
initial cov_cnt = 0;
This applies to testbench only — synthesizable RTL must always use always_ff.
slang detection: slang -Weverything catches this same always_ff multi-driver violation
at lint time (RTL mode). For TB files, use slang --allow-dup-initial-drivers to permit the
initial + always_ff pattern. The run_lint.sh --tool slang wrapper auto-detects RTL vs TB
based on file paths.
5.3 iverilog Incompatible Constructs (Do Not Generate in RTL)
Applies to synthesizable RTL only. Verification TBs may use
interfaceif the target simulator supports it.
interface/modportand unpackedstruct/union— unsupported by iverilog; use port lists / individual signals orpackedversionstypedef struct packed/typedef union packedare supported (OK to use)- Do not modify existing or user-added code that contains these constructs
6. Design Style (Preferred)
6.1 Registered Outputs (Preferred)
Drive module outputs directly from a flip-flop. When a register/pipeline stage is needed, place it at the output (compute → register → output port), NOT at the input followed by combinational logic to the port. Registered outputs give the consumer a full clock period, keep the critical path inside the module, and keep hierarchical STA/reuse clean. Combinational (unregistered) outputs are acceptable for thin glue/passthrough logic, but must be a deliberate choice — not the default place to put a needed flop.
6.2 Function / Task Purity (No Hidden External Dependencies)
function/task should operate only on their arguments (pure). Avoid reading module-level
signals not passed in — hidden inputs cause simulation-sensitivity surprises, obscure synthesis
intent, and hurt reuse. If an external (non-argument) dependency is genuinely unavoidable,
document it in a MANDATORY header comment at the top of the function/task:
// External deps (read): cfg_mode, base_addr
7. Storage Selection and SRAM Wrappers
Storage Selection by Size
| Total Bits | Access Pattern | Implementation | Rationale |
|---|---|---|---|
| ≤256 | any | Flip-flop array (logic [W-1:0] name [0:D-1]) |
SRAM overhead exceeds benefit |
| 257–4096 | 1 R/W | sram_sp wrapper |
Area-efficient; register acceptable with rationale |
| 257–4096 | R+W simultaneous | sram_tp (single-clock) or sram_dp (dual-clock) |
Separate read/write ports |
| >4096 | any | SRAM wrapper (mandatory) | Register file wastes area and power |
| any | >2 ports | Flip-flop array (register file) | Multi-port SRAM macros are rare in modern processes |
Wrapper code: templates/sram_sp.sv, templates/sram_tp.sv, templates/sram_dp.sv — copy into
rtl/common/ if not already present. Parameters DEPTH/WIDTH; instances named u_mem_{purpose};
synchronous read (1-cycle latency, matches real SRAM macro behavior). Register file (flip-flop
array) reads are combinational (0-cycle) — use only when downstream logic requires same-cycle data.
Synthesis Behavior (translate_off blackbox + compiled macro)
The wrapper's behavioral array is guarded with // synopsys translate_off … translate_on, so
DC/Genus skip it at synthesis (simulators still run it) — the 2-D array is never elaborated into
flip-flops. A process define (e.g. +define+RAT_MEM_TSMC_N22, passed by run_syn.sh --mem-process) activates a compiled-macro `ifdef branch instead. A real macro needs BOTH
--mem-process (activate branch) AND --mem-lib <macro.db|.lib> (resolve timing); missing either
→ the wrapper is blackboxed (set_dont_touch + set_disable_timing) with a WARNING.
Detail: plugin_docs/specs/2026-05-26-synth-memory-blackbox-design.md.
Anti-Pattern: Combinational Read on Large Storage (DO NOT)
A >4096-bit register array read via always_comb mux (e.g., 4096×13-bit line buffer) produces
500K+ gate equivalents, 5h+ DC compile, and a 4096:1 mux per bit on the critical path.
Use an SRAM wrapper with synchronous read instead.