claude-skill-verilog

Verilog/SystemVerilog coding style and Verilator workflow guidance

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "claude-skill-verilog" with this command: npx skills add londey/claude-skill-verilog/londey-claude-skill-verilog-claude-skill-verilog

Verilog/SystemVerilog Guidance

Apply when working with .v, .sv, .vh, .svh files or running Verilator.

Documentation

All modules, wires, and registers require comments:

// Module: counter
// Purpose: Simple up-counter with synchronous reset
module counter #(
    parameter WIDTH = 8  // Counter bit width
) (
    input  logic             clk,      // System clock
    input  logic             rst_n,    // Active-low reset
    output logic [WIDTH-1:0] count     // Current count value
);

Fixed-Point Notation

Document all fixed-point values using TI-style Q notation:

  • Qm.n — signed: m integer bits (including sign bit), n fractional bits, total width = m + n bits.
  • UQm.n — unsigned: m integer bits, n fractional bits, total width = m + n bits.

Use Q notation in signal comments, localparam descriptions, and module-level documentation.

logic signed [15:0] attr_val;      // Interpolated attribute, Q4.12
logic        [15:0] depth;         // Fragment depth, UQ16.0
logic signed [15:0] deriv_dx;      // dAttr/dx per scanline step, Q4.12

Naming Conventions

  • Active-low signals: use _n suffix (e.g., rst_n, chip_select_n)
  • Clocks: clk or clk_<domain>
  • Use descriptive names over abbreviations

always_ff: Simple Assignments Only

always_ff blocks must contain ONLY simple non-blocking assignments. No logic, no expressions - this ensures Verilator simulation matches synthesized behavior. (Exceptions: memory inference and async reset synchronizers require conditional logic - see those sections.)

// CORRECT - simple assignment
always_ff @(posedge clk) begin
    count <= count_next;
    state <= state_next;
end

// WRONG - logic in always_ff
always_ff @(posedge clk) begin
    count <= count + 1;           // Move to always_comb
    state <= enable ? RUNNING : IDLE;  // Move to always_comb
end

always_comb: All Logic Here

All combinational logic belongs in always_comb blocks:

always_comb begin
    count_next = count + 8'd1;
    state_next = enable ? RUNNING : IDLE;
end

Formatting

  • One statement per line — never chain multiple statements or assignments on a single line
  • One declaration per line
  • Explicit bit widths on all literals
  • Start files with `default_nettype none
  • Always use begin/end blocks for if, else, case items (prevents bugs when adding code later)
  • Prefer to keep modules under ~500 lines; if a module grows significantly larger, consider refactoring into smaller sub-modules
`default_nettype none

module example (
    input  logic        clk,
    input  logic        rst_n,
    input  logic [7:0]  data_in,
    output logic [7:0]  data_out
);

    logic [7:0] data_reg;    // Registered data
    logic [7:0] data_next;   // Next state value
    logic       valid;       // Data valid flag

    localparam logic [7:0] INIT_VAL = 8'd0;

endmodule

`default_nettype wire

Yosys Synthesis Compatibility (DD-034)

All synthesizable RTL must work with both Verilator (lint/simulation) and Yosys (ECP5 synthesis). Yosys supports a subset of SystemVerilog via read_verilog -sv. Code that passes Verilator may still fail Yosys synthesis.

Constructs to avoid in synthesizable RTL:

AvoidUse instead
return <expr>; in functionsfunction_name = <expr>; (Verilog-2005 style)
interface / modportExplicit port lists
unique case / priority casePlain case with default
Multi-dimensional packed arrays in portsFlatten to single vectors
// CORRECT - Yosys-compatible function
function automatic logic [7:0] add_saturate(input logic [7:0] a, input logic [7:0] b);
    logic [8:0] sum;
    sum = {1'b0, a} + {1'b0, b};
    add_saturate = sum[8] ? 8'hFF : sum[7:0];
endfunction

// WRONG - return statement (Yosys rejects this)
function automatic logic [7:0] add_saturate(input logic [7:0] a, input logic [7:0] b);
    logic [8:0] sum;
    sum = {1'b0, a} + {1'b0, b};
    return sum[8] ? 8'hFF : sum[7:0];
endfunction

Always verify with make synth (not just verilator --lint-only) when using SystemVerilog features.

Testing with Verilator

Every module requires a testbench. Build and run with Verilator:

# Build testbench
verilator --binary -Wall module_tb.sv module.sv

# Run simulation
./obj_dir/Vmodule_tb

Testbench structure:

module counter_tb;
    logic       clk = 1'b0;  // System clock
    logic       rst_n;       // Active-low reset
    logic [7:0] count;       // DUT output

    counter dut (
        .clk(clk),
        .rst_n(rst_n),
        .count(count)
    );

    always begin
        #5 clk = ~clk;
    end

    initial begin
        rst_n = 1'b0;
        #20 rst_n = 1'b1;
        #100;
        $display("Test complete, count=%d", count);
        $finish;
    end
endmodule

Verilator Linting

Run linting on all files and fix all warnings:

verilator --lint-only -Wall module.sv
  • Fix all warnings - do not suppress with pragmas
  • Key warnings: WIDTH (bit-width mismatch), UNUSED, UNDRIVEN

Verilator Simulation Flags

Recommended flags for simulation builds:

verilator --binary \
    -Wall \
    -Wno-fatal \
    -j 0 \
    --assert \
    --timing \
    --trace-fst \
    --trace-structs \
    --main-top-name "-" \
    --x-assign unique \
    --x-initial unique \
    module_tb.sv module.sv
FlagPurpose
-WallEnable all warnings
-Wno-fatalDon't exit on warnings (allows full report)
-j 0Fully parallelized compilation
--assertEnable SystemVerilog assertions
--timingEnable timing constructs
--trace-fstDump waveforms as FST (compressed)
--trace-structsHuman-readable struct dumps
--main-top-name "-"Remove extra TOP module wrapper
--x-assign uniqueReplace X with random constant per-build
--x-initial uniqueRandomly initialize uninitialized variables

Module Instantiation

  • One module per file, filename matches module name
  • Always use named port connections (never positional)
// CORRECT - named connections
counter #(
    .WIDTH(16)
) u_counter (
    .clk    (clk),
    .rst_n  (rst_n),
    .count  (count_value)
);

// WRONG - positional connections
counter u_counter (clk, rst_n, count_value);

Avoiding Latches

Latches are inferred when signals aren't assigned in all paths. Prevent with:

  • Default assignments at start of always_comb
  • Cover all cases including default
always_comb begin
    // Default assignments first
    data_next = data_reg;
    valid_next = 1'b0;

    case (state)
        IDLE: begin
            data_next = 8'd0;
        end
        LOAD: begin
            data_next = data_in;
        end
        default: begin
            data_next = data_reg;
        end
    endcase
end

Reset Handling

Use synchronous resets when possible. For external async resets, synchronize first.

Note: Async reset synchronizers require conditional logic in always_ff for the reset condition - this is a necessary exception similar to memory inference.

// Synchronous reset (preferred)
logic [7:0] count;       // Counter register
logic [7:0] count_next;  // Next counter value

always_comb begin
    count_next = rst_n ? (count + 8'd1) : 8'd0;
end

always_ff @(posedge clk) begin
    count <= count_next;
end

// Reset synchronizer for external async reset
logic [1:0] rst_sync;       // Synchronizer flip-flops
logic [1:0] rst_sync_next;  // Next synchronizer value

always_comb begin
    rst_sync_next = {rst_sync[0], 1'b1};
end

always_ff @(posedge clk or negedge rst_async_n) begin
    if (!rst_async_n) begin
        rst_sync <= 2'b00;
    end else begin
        rst_sync <= rst_sync_next;
    end
end
assign rst_n = rst_sync[1];

FSM Patterns

Separate state register from next-state logic. Use enums for state encoding.

typedef enum logic [1:0] {
    IDLE,
    RUN,
    DONE
} state_t;

state_t state;       // Current state register
state_t state_next;  // Next state value

// Next-state logic (combinational)
always_comb begin
    state_next = state;
    case (state)
        IDLE: begin
            if (start) begin
                state_next = RUN;
            end
        end
        RUN: begin
            if (finish) begin
                state_next = DONE;
            end
        end
        DONE: begin
            state_next = IDLE;
        end
        default: begin
            state_next = IDLE;
        end
    endcase
end

// State register (sequential)
always_ff @(posedge clk) begin
    state <= state_next;
end

Clock Domain Crossing (CDC)

Single-bit signals: use 2-FF synchronizer. Multi-bit: use gray coding or handshake.

// 2-FF synchronizer for single-bit CDC
logic [1:0] sync_reg;       // Synchronizer flip-flops
logic [1:0] sync_reg_next;  // Next synchronizer value
logic       signal_sync;    // Synchronized output

always_comb begin
    sync_reg_next = {sync_reg[0], signal_src};
end

always_ff @(posedge clk_dst) begin
    sync_reg <= sync_reg_next;
end
assign signal_sync = sync_reg[1];

// Gray code for multi-bit counters crossing domains
function automatic logic [WIDTH-1:0] bin2gray(input logic [WIDTH-1:0] bin);
    bin2gray = bin ^ (bin >> 1);
endfunction

Memory Inference

Use standard patterns for RAM/ROM inference by synthesis tools.

Note: Memory patterns are an exception to the "simple assignments only" rule for always_ff. Synthesis tools require these specific patterns to correctly infer RAM/ROM primitives.

// Single-port RAM
logic [DATA_WIDTH-1:0] mem [0:DEPTH-1];  // Memory array

always_ff @(posedge clk) begin
    if (we) begin
        mem[addr] <= wdata;
    end
    rdata <= mem[addr];
end

// ROM (initialized memory)
logic [7:0] rom [0:255];  // ROM array
initial $readmemh("rom_data.hex", rom);

always_ff @(posedge clk) begin
    rdata <= rom[addr];
end

Assertions (SVA)

Use assertions for verification. They're enabled with --assert in Verilator.

// Immediate assertion
always_comb begin
    assert (count < MAX_COUNT) else $error("Count overflow");
end

// Concurrent assertions
property p_valid_handshake;
    @(posedge clk) disable iff (!rst_n)
    valid |-> ##[1:3] ready;
endproperty

assert property (p_valid_handshake)
    else $error("Handshake timeout");

// Cover property (for functional coverage)
cover property (@(posedge clk) state == DONE);

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

Wip Ldm Os Private

LDM OS ... shared infrastructure for AI agents. Identity, memory, ownership, collaboration, compatibility, payments. One system across all your AIs.

Registry SourceRecently Updated
Automation

dchat

Decentralized P2P bot-to-bot messaging over NKN. Send and receive text, images, audio, and files without any centralized server. Private, encrypted, serverless.

Registry SourceRecently Updated
Automation

Ai Writing Agent

AI writing assistant that generates articles, blog posts, and structured content

Registry SourceRecently Updated
Automation

Agentmemo

Give your AI agent persistent memory and human-in-the-loop approval — across sessions, across models. AgentMemo is a cloud API + MCP server that lets agents...

Registry SourceRecently Updated