SystemVerilog coding convention and design guideline skill. Enforces lowRISC style + project overrides for all .sv/.v file generation. Covers naming, module structure, power optimization, FPGA considerations, and pipelining for timing closure.
Target standard: IEEE 1800-2009 for synthesizable RTL.
interface/modport is 2009 standard but iverilog unsupported — do NOT generate in RTL (see §4.3)-g2012 for parser compatibility (2012 parser handles 2009 code)Baseline: lowRISC SystemVerilog Coding Style Guide (https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md) Project overrides take precedence over default lowRISC rules. </Purpose>
<Use_When>
<Do_Not_Use_When>
systemc skillrtl-p5s-func-verify skill<Why_This_Exists> Consistent coding standards ensure lint pass rate, synthesis quality, and team readability all at once. Although based on lowRISC, this project has its own overrides for port naming, clock/reset rules, etc., so they are managed as a separate skill to ensure all SV-generating agents reference the same rules. </Why_This_Exists>
<Execution_Policy>
templates/module-template.sv as the starting point for new modulesexamples/good-vs-bad.sv for correct/incorrect pattern examplesreferences/coding-style-guide.md for detailed project overrides vs. original lowRISC rules
</Execution_Policy>IMPORTANT — The following 3 rules differ from the lowRISC guide and must always be applied.
i_, Output: o_, Bidirectional: io_ — mandatoryi_data, o_valid, io_sda (NOT data_i, valid_o)i_ prefix (clk, sys_clk, rst_n, sys_rst_n)_i, _o, _io) is forbiddenclk (default) or {domain}_clk (multi-clock)sys_clk, pixel_clk, axi_clkclk_i, NOT suffixrst_n (default) or {domain}_rst_n (multi-domain)rst_n, sys_rst_n, pixel_rst_n (NOT rst_ni)UpperCamelCase for Parameters and Enum valuesALL_CAPS (DATA_WIDTH, NOT DataWidth)L_ prefix + ALL_CAPS (L_ADDR_BITS, NOT AddrBits)ALL_CAPS (ST_IDLE, NOT StIdle)IMPORTANT — CamelCase is completely prohibited. All identifiers must use only
snake_caseorALL_CAPS. Detailed rules: seereferences/coding-style-guide.md.
| 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.
| Forbidden (CamelCase) | Correct Form |
|---|---|
parameter int Width = 8 | parameter int unsigned WIDTH = 8 |
localparam AddrBits = $clog2(Depth) | localparam L_ADDR_BITS = $clog2(DEPTH) |
StIdle, StProcess | ST_IDLE, ST_PROCESS |
Any UpperCamelCase | ALL_CAPS or snake_case |
| 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, §4.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.
logic (using reg/wire is forbidden)always_ff for sequential (non-blocking <=)always_comb for combinational (blocking =)typedef enum / typedef struct packed_pkg.sv)input logic / output logic (ANSI style)parameter or localparamreg, wire keywords is forbiddenalways_latch is forbidden (except for explicit latches; generally prohibited)initial blocks in synthesizable code are forbiddendefault is mandatory in all case statements#delay in synthesizable code is forbiddenalways_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.
This restriction applies to synthesizable RTL code only. Verification TBs may use
interfaceif the target simulator supports it.
interface / modport — unsupported by iverilog. Use port lists insteadstruct / union — unsupported by iverilog. Use individual signals or packed versionstypedef struct packed / typedef union packed are supported (OK to use)IMPORTANT — IEEE 1800 §12.5 requires identifiers to be declared before first use. Xcelium (xmvlog) strictly enforces sequential declaration visibility. Reordering concurrent statements (assign, always_ff, always_comb) has zero synthesis/simulation impact, but declarations MUST always precede their first reference.
module_name_pkg.sv <- Shared type/constant definitions
module_name.sv <- Module implementation (order is MANDATORY):
1. parameter declarations
2. port declarations (ANSI style)
3. import statements
4. typedef / localparam / enum <- all types and constants
5. internal signal declarations <- all signals before any logic
-- declaration boundary ------------ (no declarations below this line)
6. assign statements <- continuous assignments
7. submodule instances (u_ prefix)
8. always_comb blocks <- combinational logic
9. always_ff blocks <- sequential logic
10. assertions (SVA)
See templates/module-template.sv for complete scaffold.
The items below apply only when specific optimizations are needed. See the
<Advanced>section for detailed patterns and code examples.
<Advanced> section A.1<Advanced> section A.2<Advanced> section A.3<Advanced> section A.4// Clock gating pattern
logic clk_enable;
logic gated_clk;
// Use dedicated ICG cell (synthesis tool maps to library cell)
assign gated_clk = sys_clk & clk_enable; // For simulation only
// Synthesis: replace with ICG instantiation or let tool infer
// Operand isolation — prevent unnecessary switching in multiplier
logic [15:0] mul_a_gated, mul_b_gated;
assign mul_a_gated = i_mul_valid ? i_mul_a : '0;
assign mul_b_gated = i_mul_valid ? i_mul_b : '0;
assign mul_result = mul_a_gated * mul_b_gated;
| 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 |
Place in rtl/common/sram_sp.sv. One R/W port, single clock. Instance with u_mem_ prefix.
module sram_sp #(
parameter int DEPTH = 256,
parameter int WIDTH = 32
) (
input logic clk,
input logic i_ce,
input logic i_we,
input logic [$clog2(DEPTH)-1:0] i_addr,
input logic [WIDTH-1:0] i_wdata,
output logic [WIDTH-1:0] o_rdata
);
logic [WIDTH-1:0] mem [0:DEPTH-1];
always_ff @(posedge clk) begin
if (i_ce) begin
if (i_we) begin
mem[i_addr] <= i_wdata;
end
o_rdata <= mem[i_addr]; // 1-cycle read latency
end
end
endmodule
Place in rtl/common/sram_tp.sv. Separate read and write ports, single clock.
Use when simultaneous read+write is needed within the same clock domain.
module sram_tp #(
parameter int DEPTH = 256,
parameter int WIDTH = 32
) (
input logic clk,
// Write port
input logic i_wen,
input logic [$clog2(DEPTH)-1:0] i_waddr,
input logic [WIDTH-1:0] i_wdata,
// Read port
input logic i_ren,
input logic [$clog2(DEPTH)-1:0] i_raddr,
output logic [WIDTH-1:0] o_rdata
);
logic [WIDTH-1:0] mem [0:DEPTH-1];
always_ff @(posedge clk) begin
if (i_wen) begin
mem[i_waddr] <= i_wdata;
end
if (i_ren) begin
o_rdata <= mem[i_raddr]; // 1-cycle read latency
end
end
endmodule
Place in rtl/common/sram_dp.sv. Separate read and write ports, dual clock (wclk/rclk).
Use at clock domain crossings (e.g., async FIFO memory backend, cross-domain shared buffer).
module sram_dp #(
parameter int DEPTH = 256,
parameter int WIDTH = 32
) (
// Write port (write clock domain)
input logic wclk,
input logic i_wen,
input logic [$clog2(DEPTH)-1:0] i_waddr,
input logic [WIDTH-1:0] i_wdata,
// Read port (read clock domain)
input logic rclk,
input logic i_ren,
input logic [$clog2(DEPTH)-1:0] i_raddr,
output logic [WIDTH-1:0] o_rdata
);
logic [WIDTH-1:0] mem [0:DEPTH-1];
always_ff @(posedge wclk) begin
if (i_wen) begin
mem[i_waddr] <= i_wdata;
end
end
always_ff @(posedge rclk) begin
if (i_ren) begin
o_rdata <= mem[i_raddr]; // 1-cycle read latency (rclk domain)
end
end
endmodule
Note: sram_dp has two always_ff blocks writing different signals (mem from wclk, o_rdata from rclk),
which is correct — no multi-driver conflict since each signal has a single driver.
// BAD: >4096-bit register array with combinational MUX
// Real case: 4096×13-bit line buffer → 500K+ gate equivalents,
// 5h+ DC compile, 55% area wasted, 4096:1 MUX per bit on critical path
logic signed [12:0] lb_mem [4096];
always_comb begin
rd_data = lb_mem[rd_addr]; // 4096:1 MUX — area/timing/compile disaster
end
// GOOD: Use SRAM wrapper with synchronous read (1-cycle latency)
sram_tp #(.DEPTH(4096), .WIDTH(13)) u_mem_line_buf (
.clk (clk),
.i_wen (wr_en), .i_waddr(wr_addr), .i_wdata(wr_data),
.i_ren (rd_en), .i_raddr(rd_addr), .o_rdata(rd_data) // 1-cycle latency
);
`ifdef SYNTHESIS guard| Resource | Inference Pattern | Notes |
|---|---|---|
| BRAM | logic [W-1:0] mem [0:D-1] + sync read | Async read infers distributed RAM |
| DSP | a * b + c pattern | Better inference with pipeline registers |
| SRL | shift register (always_ff chain) | Auto-inferred, no explicit control needed |
create_clock, set_input_delay, set_output_delay are identicalset_property IOSTANDARD, set_property LOC(* mark_debug = "true" *) attribute to signals targeted for debugu_ prefix conventionrtl-synth-check skill// Before: long combinational path
assign o_result = func_a(func_b(func_c(i_data)));
// After: 2-stage pipeline
logic [W-1:0] stage1_q;
always_ff @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) stage1_q <= '0;
else stage1_q <= func_c(i_data);
end
assign o_result = func_a(func_b(stage1_q));
compile_ultra -retime, Genus: syn_opt -retiming)dont_touch to registers targeted for retimingo_ready propagates backward from the next stage's i_ready<Tool_Usage> This skill is not executed directly. It is referenced by agents that generate SV code (e.g., rtl-coder, sva-extractor). Agents should follow the conventions defined here. </Tool_Usage>
typedef enum logic [1:0] { ST_IDLE, ST_ENCODE, ST_DONE } state_e;
</Good>
<Bad>
CamelCase, suffix, reg/wire, magic numbers:
```systemverilog
module cabac_encoder #(
parameter int CtxAddrWidth = 9 // WRONG: CamelCase
) (
input wire clk_i, // WRONG: suffix, wire
input reg rst_ni, // WRONG: reg, suffix
input [8:0] ctx_addr_i, // WRONG: suffix, magic width
output bin_valid_o // WRONG: no type, suffix
);
localparam AddrBits = 4; // WRONG: CamelCase, no L_ prefix
typedef enum logic [1:0] {
StIdle, StEncode, StDone // WRONG: CamelCase enum values
} state_e;
<Escalation_And_Stop_Conditions>
<Final_Checklist>
i_/o_/io_ prefix mandatoryclk (single) or {domain}_clk (multi), Reset: rst_n or {domain}_rst_nALL_CAPS, localparam L_ prefix, enum values ALL_CAPSlogic only (no reg/wire)always_ff (sequential), always_comb (combinational)default present in all case statementsu_ prefix, generate gen_ prefixlogic/typedef/localparam before first assign/always (no forward references)sram_sp/sram_tp/sram_dp wrapper from rtl/common/ (not inline array)u_mem_{purpose}
</Final_Checklist>