Based on the lowRISC Verilog style guide.
| Element | Convention | Example |
|---|---|---|
| Signals | lower_snake_case |
write_enable |
| Modules | lower_snake_case |
prim_fifo_sync |
| Parameters | UpperCamelCase |
DataWidth, NumLanes |
| Types | lower_snake_case_t |
state_t, ctrl_reg_t |
| Enums | type _e, values UpperCamelCase |
state_e, StIdle |
| Packages | lower_snake_case_pkg |
rbox_pkg |
| Ports | suffix _i, _o, _io |
clk_i, data_o |
| Next-state / flopped | _d / _q |
state_d, state_q |
| Active-low | _n suffix (or _ni for ports) |
rst_ni |
// Combinational — always_comb, never always @*
always_comb begin
state_d = state_q;
unique case (state_q)
StIdle: if (start_i) state_d = StActive;
StActive: if (done_i) state_d = StIdle;
default: state_d = StIdle;
endcase
end
// Sequential — always_ff with async-assert/sync-deassert reset
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
state_q <= StIdle;
end else begin
state_q <= state_d;
end
end// Good — scoped, type-safe
module my_block #(
parameter int unsigned Width = 8,
parameter int unsigned Depth = 4
) ( ... );
// Bad — global namespace pollution
`define MY_BLOCK_WIDTH 8Use packages for shared constants:
package my_block_pkg;
typedef enum logic [1:0] {
StIdle = 2'b00,
StActive = 2'b01,
StDone = 2'b10
} state_e;
endpackage// Good
typedef struct packed {
logic valid;
logic [31:0] addr;
logic [63:0] data;
} req_t;
// Bad
logic [96:0] req; // what is bit 47?// Good — tool-checked, no synopsys pragmas needed
unique case (sel)
2'b00: ...
2'b01: ...
2'b10: ...
2'b11: ...
endcase
// Bad
case (sel) // synopsys full_case parallel_caseAsync-assert, sync-deassert — works on both FPGA and ASIC:
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
// reset values
end else begin
// normal operation
end
end- Active-low reset signal named
rst_ni - Use a reset synchronizer at the top-level only
- No
initialblocks for register initialization (ASIC-incompatible)
- Avoid introducing new ad hoc latches in ordinary RTL. But when translating original CORE-ET logic, preserve intentional latch-based and phase-sensitive behavior explicitly; do not collapse
LATCH/LATCH_P2-style logic or latch-backed RF timing into edge-triggered flops just because it is simpler to code. - No ad hoc gated clocks. Use
prim_clk_gatefor intentional clock gating rather than hand-rolled gating logic in functional RTL. - BRAM inference: Registered output, synchronous read:
logic [W-1:0] mem [D]; always_ff @(posedge clk_i) begin if (we_i) mem[waddr_i] <= wdata_i; rdata_o <= mem[raddr_i]; end
- No tri-states inside the fabric.
$clog2for address sizing, not magic numbers.
The synthesis flow uses Yosys with the slang SystemVerilog frontend. These patterns can work in simulation but fail or elaborate differently in synthesis:
Parameterized casts in constant-sensitive expressions are fragile across synthesis frontends. Use explicit slices or zero/sign extension instead.
// BAD — fragile in synthesis
result = HartsL'(m);
// GOOD — explicit bit-select, same behavior
result = m[HartsL-1:0];
// GOOD — for all-ones constants
result = {HartsL{1'b1}}; // instead of HartsL'(63)Yosys requires for loop bounds to be constant for unrolling. Move runtime conditions inside the loop body:
// BAD — Yosys cannot unroll (limit is runtime)
for (int m = 0; (m < N) && (m <= limit_q); m++)
result = data[m];
// GOOD — constant bound, runtime condition inside
for (int m = 0; m < N; m++)
if (m[W-1:0] <= limit_q)
result = data[m];- One module per file. File name = module name.
- Package file:
<block>_pkg.sv. - Include guards not needed (packages, not headers).
Use Verilator-compatible style:
// synthesis translate_off
`ifdef VERILATOR
always_ff @(posedge clk_i) begin
if (rst_ni && condition)
$error("message");
end
`else
assert property (@(posedge clk_i) disable iff (!rst_ni) ...)
else $error("message");
`endif
// synthesis translate_on