Keep your code warning-free, consistent and easy to read.
- Use 4 spaces per indent level.
- Do not use tab characters.
if (condition) begin
assign value = 1'b1;
end- Modules:
PascalCaseprefixed withVX_. - Signals:
lower_snake_case. - Macros:
UPPER_SNAKE_CASE. - Parameters:
UPPER_SNAKE_CASE. - Generate block name prefix with
g_. - Clock name: clk.
- Reset name: reset.
- Comment use
//.
-
conditional statement with spacing before parenthesis and begin/end
if (condition) begin assign valid = 1'b1; end
-
begin/endare mandatory on everyif,else if,else,for,while,repeat, andforeverbody — even when the body is a single statement. The single-statement shortcut is forbidden because:- Adding a second statement to the branch silently re-scopes the first to be unconditional (the next statement falls outside the implicit one-line body). This is a perennial source of bugs.
- Diff hygiene: changing a one-liner into a multi-statement block produces a noisy multi-line diff that obscures the actual change.
// BANNED — single-statement shortcut if (intra_x_wrap) intra_offset[0] <= 0; else intra_offset[0] <= intra_x_n; // REQUIRED — always begin/end if (intra_x_wrap) begin intra_offset[0] <= 0; end else begin intra_offset[0] <= intra_x_n; end
-
switch statement with spacing before parenthesis and begin/end
case (op_type) INST_ALU, INST_BR: ex = EX_ALU; INST_LSU: ex = EX_LSU; default: ex = EX_NONE; endcase
-
Generate loops with
genvarand block labels:for (genvar i = 0; i < NUM_LANES; ++i) begin : g_lanes ... end
-
with backpressure use
validandreadysignala:interface VX_dispatch_if (); logic valid; dispatch_t data; logic ready; modport master ( output valid, output data, input ready ); modport slave ( input valid, input data, output ready ); endinterface -
No backpressure with
validsignal:interface VX_writeback_if (); logic valid; writeback_t data; modport master ( output valid, output data ); modport slave ( input valid, input data ); endinterface -
Buffering ownership. Pipeline/buffer stages on an interface belong to the producer/distribution side — the arb, fork, or xbar that drives the bus — via their standard
*_OUT_BUFknobs (see §11 library modules). A.slaveconsumer must use the interface as delivered: it must not internally re-register the incoming bus to fix timing. Consumer-side latching desynchronizes that consumer from every other endpoint of a shared broadcast/fork (breaking the bus's delivery contract) and hides the retiming from the module that owns the route. If a path into a consumer fails timing, raise theOUT_BUFdepth at the driving distribution module (or add a registered slice at the boundary in the parent), never inside the leaf. -
Register your outgoing external interfaces. The corollary of buffering ownership: every module registers the signals it drives onto an interface — the forward
valid/dataof a master port, thereadyof a slave port — at its own output boundary, via an output elastic buffer (VX_elastic_buffer, aVX_*_bus_slice, or the module's own*_OUT_BUFknob set to a registered depth).
Vortex uses explicit warning management i.e. we directly resolve the warning inside the code. Warnings that exist inside external code should be resolved using Verilator.vlt lint file. There are some code structures that Verilator's static analyzer doesn't know how to handle properly (e.g. cyclic loops in arrays) and will throw a warning, for those types of error use the corresponding warning handling macros defined in VX_platform.vh.
-
Blanket
/* verilator lint_off … *///* verilator lint_on … */pragmas are forbidden in Vortex RTL. They suppress warnings over wide spans, hide future regressions, and bypass the per-signal review the macros below enforce. Use`UNUSED_VAR/`UNUSED_PARAM/`UNUSED_PIN/`UNUSED_SPARAMto tag the specific signal/pin/param being silenced. Warnings inside third-party code go in Verilator.vlt, not pragmas embedded in.svfiles.// BANNED — blanket scope silencer /* verilator lint_off UNUSED */ wire [31:0] dbg_lo; wire [31:0] dbg_hi; /* verilator lint_on UNUSED */ // REQUIRED — per-signal tag wire [31:0] dbg_lo; wire [31:0] dbg_hi; `UNUSED_VAR ({dbg_lo, dbg_hi})
-
Unused variables
`UNUSED_VAR (a) `UNUSED_VAR ({a, B, C}) -
Unused parameters
`UNUSED_PARAM (COUNT) `UNUSED_SPARAM (NAME)
-
Unused pin
VX_onehot_encoder #( .N (NUM_WAYS) ) way_idx_enc ( .data_in (tag_matches), .data_out (hit_idx), `UNUSED_PIN (valid_out) ); -
Other warnings
// Silencing Circular Combinational Logic warnings in Verilator.. logic [N-1:0] G [LEVELS+1] /* verilator split_var*/;
- runtime macro will include always block
`RUNTIME_ASSERT(cond, ("%t: invalid a; a=0x%0h", $time, a))
- static assertion can check parameter or localparam
`STATIC_ASSERT(cond, ("invalid parameter: N=%0d", N))
- Preserve indent of nested code and shift pre-processor left by one level
Base version (before):
always_comb begin
decode_valid = issue_valid;
if (is_mtype) begin
if (is_dp) begin
decode_unit = UNIT_MULDIV_DP;
end else begin
decode_unit = UNIT_MULDIV;
end
end else if (is_fp) begin
decode_unit = UNIT_FPU;
end else begin
decode_unit = UNIT_ALU;
end
endAdding ifdef (after):
always_comb begin
decode_valid = issue_valid;
if (is_mtype) begin
`ifdef EXT_M_ENABLE
if (is_dp) begin
decode_unit = UNIT_MULDIV_DP;
end else begin
decode_unit = UNIT_MULDIV;
end
`else
decode_unit = UNIT_MULDIV;
`UNUSED_VAR (is_dp)
`endif
end else if (is_fp) begin
decode_unit = UNIT_FPU;
end else begin
decode_unit = UNIT_ALU;
end
end- Arguments inside the
`TRACEmust be comma-separated.
Correct:
`TRACE(2, ("%t: %s req: wid=%0d, pc=0x%0h\n", $time, INSTANCE_ID, wid, pc))Incorrect (space-separated entries):
`TRACE(2, ("%t: %s req: wid=%0d pc=0x%0h\n", $time, INSTANCE_ID, wid, pc))Comments describe what the adjacent code does and why, not the process that produced it. Prefer self-documenting code — good abstractions and consistent naming — and drop comments on code whose intent is already obvious; keep the rest brief, one or two lines per block as the norm (longer only where genuinely warranted, at the author's discretion), since over-detailed comments obscure the code and drift out of sync with later changes. Never embed development metadata or history (phase/step/version/part/feature/bug numbers, "proposal", "spec"), debugging or change narration ("fixing bug…", "was broken because…" — that is what commit messages are for), or references to design documents. Comments and names must not reference the other implementation layer's internals: host-side models (SimX, runtime, drivers) must not name RTL signals or parameters, and RTL must not name host-side/SimX details. The layers evolve independently, so any such reference silently goes stale. These rules apply to every source file and script.
Strive for moderate combinatorial logic depths that balance latency with synthesis portability. Our baseline for timing closure is the U55C prototyping board running at 300 MHz, so paths should be kept short enough to meet this frequency. When a cross-module path fails timing, add the register at the producing distribution module's OUT_BUF — never by latching the interface inside the consumer (see §4, Buffering ownership).
Before writing new RTL, consult the hardware IP library in hw/rtl/libs/ — the hardware_library.md reference catalogs the reusable, parameterized modules it provides: elastic buffers and flow control, arbiters, mux/demux, stream fork/join/pack/dispatch, crossbars and interconnect, encoders/decoders, arithmetic (multipliers, dividers, adders, CSA trees), RAM/FIFO primitives, memory adapters, and bit-manipulation utilities. Prefer instantiating an existing library module over hand-rolling equivalent logic: the library modules carry consistent valid/ready handshake semantics, inherit the FPGA/ASIC synthesis support, and are already verified, so reuse avoids duplicating tested logic and the subtle handshake/timing bugs that re-implementation invites. If a needed primitive is genuinely missing, add it to the library rather than embedding a one-off in a block.
Declare and instantiate modules and parameterized interfaces with one parameter/port per line, vertically aligned so the diff stays clean when entries are added or renamed.
-
Module header — one
parameterand one port per line. Align the=of the parameter defaults into a column, and align the port names after the direction/type so the names form a column.module VX_example #( parameter N = 4, parameter LANES = 1, parameter USE_DSP = 0 ) ( input wire [LANES-1:0][N-1:0] a, input wire [LANES-1:0][N-1:0] b, output wire [LANES-1:0][2*N-1:0] p );
-
Instantiation — one
.param/.portconnection per line; do not pack several onto one line. Pad the names so the opening(of every connection lines up in a column.// REQUIRED VX_example #( .N (4), .LANES (2), .USE_DSP (USE_DSP) ) u_example ( .a (a_in), .b (b_in), .p (p_out) ); // BANNED — multiple connections per line VX_example #(.N(4), .LANES(2), .USE_DSP(USE_DSP)) u_example ( .a(a_in), .b(b_in), .p(p_out));
-
Parameterized interface instances follow the same rule — never pack the params onto the declaration line.
// REQUIRED VX_axi_if #( .ADDR_W (ADDR_W), .DATA_W (DATA_W) ) axi_bus (); // BANNED — packed params on the declaration line VX_axi_if #(.ADDR_W(ADDR_W), .DATA_W(DATA_W)) axi_bus ();