EN
← Back to Skills
IntermediateRTL Design

Verilog Module Generator

Generate complete, synthesizable Verilog/SystemVerilog modules from natural language specifications. Encodes latch-free patterns, correct reset strategy, blocking vs non-blocking rules, and signal naming conventions.

💡 Copy this Skill description into your AI Agent's system prompt (Cursor, Claude Code, Copilot), then describe your design needs. The Agent will generate code following the rules defined in this Skill. Download the .md file at the bottom of this page.


name: verilog-module description: > Generate a complete, synthesizable, and production-quality Verilog/SystemVerilog module from a natural language functional specification. This skill encodes the accumulated best practices of experienced digital IC design engineers: proper reset strategy, latch-free combinational logic, correct use of blocking vs non-blocking assignments, parameterization, clean signal naming, and synthesis-aware coding patterns. Every generated module should pass lint, synthesis, and code review on first pass. category: rtl-design level: intermediate

Purpose

Transform a natural language description of a digital circuit into a complete, synthesizable Verilog/SystemVerilog module that follows industry best practices. The output must be ready for synthesis, simulation, and code review with zero modifications required.

Input Specification

The user provides a description that may include any combination of:

Required Information

  • Functional behavior: What the circuit does (e.g., "a binary counter with enable")
  • Interface: Port names and directions, either explicitly or implied by the description

Optional Information (if not provided, use sensible defaults)

  • Clock name and edge: Default to clk, posedge triggered
  • Reset name, polarity, and strategy: Default to rst_n (active-low), asynchronous assert / synchronous de-assert
  • Parameterization: Any widths, depths, or configurable values
  • Target standard: Verilog-2001, SystemVerilog (IEEE 1800-2017), or vendor-specific
  • Coding style preference: Signal naming convention, indentation width (default: 4 spaces)

Output Specification

Generate a complete file containing:

  1. Module header with descriptive comment block
  2. Parameter declarations (parameter / localparam) with comments
  3. Port declarations using SystemVerilog logic type (preferred) or Verilog wire/reg
  4. Internal signal declarations with comments
  5. Functional implementation properly separated into:
    • Sequential always blocks (registers, counters, state)
    • Combinational always blocks (next-state logic, combinational outputs)
    • Continuous assignments (assign) for simple combinational logic
  6. No dead code, no inferred latches, no undriven signals

Core Design Rules

Rule 1: Blocking vs Non-Blocking Assignments

This is the single most important rule in RTL design. Getting this wrong causes simulation-synthesis mismatches that are extremely hard to debug.

| Context | Use | Rationale | |---------|-----|-----------| | Sequential always block (@posedge clk) | <= (non-blocking) | Models flip-flop behavior: all RHS sampled before any LHS updates | | Combinational always block (@*) | = (blocking) | Models immediate combinational update | | Continuous assignment (assign) | Not applicable | assign is inherently combinational and continuous | | Never mix = and <= in the same always block | — | Causes simulation-synthesis mismatch |

Example — Incorrect (will cause mismatch):

systemverilog
// DO NOT DO THIS — mixed assignments in one block
always @(posedge clk) begin
    if (en) begin
        tmp = a + b;        // blocking — WRONG in sequential block
        result <= tmp;      // non-blocking
    end
end

Example — Correct:

systemverilog
// Separate combinational and sequential logic
always_comb begin
    tmp = a + b;            // blocking in combinational block — CORRECT
end

always_ff @(posedge clk) begin
    if (en) begin
        result <= tmp;      // non-blocking in sequential block — CORRECT
    end
end

Rule 2: Latch Avoidance (Zero Tolerance)

A latch is inferred when a combinational output is not assigned in every possible execution path. Latches are almost never intentional and cause severe problems: asynchronous feedback loops, timing analysis difficulties, and DFT failures.

Every combinational always block MUST:

  1. Have a default assignment at the top for every output signal
  2. Ensure every if has a corresponding else
  3. Ensure every case has a default branch
  4. Ensure all output signals are assigned in every branch

Example — WILL infer a latch:

systemverilog
// BUG: latch inferred — y not assigned when en=0
always_comb begin
    if (en) y = a & b;
end

Example — Latch-free:

systemverilog
// CORRECT: y has default value, assigned in all paths
always_comb begin
    y = '0;             // default assignment
    if (en) y = a & b;  // override when enabled
end
systemverilog
// CORRECT: case with default
always_comb begin
    next_state = IDLE;  // default
    case (state)
        IDLE:  if (start) next_state = RUN;
        RUN:   if (done)  next_state = IDLE;
        default: next_state = IDLE;  // explicit default
    endcase
end

Rule 3: Reset Strategy

Choose the appropriate reset strategy based on the target technology and requirements.

Asynchronous Assert, Synchronous De-assert (Recommended Default):

systemverilog
// Use this pattern for the internal synchronized reset
logic rst_sync_n;

always_ff @(posedge clk or negedge rst_async_n) begin
    if (!rst_async_n) begin
        rst_sync_n <= 1'b0;
    end else begin
        rst_sync_n <= 1'b1;  // de-asserts synchronously
    end
end

// Use rst_sync_n for all downstream logic
always_ff @(posedge clk or negedge rst_sync_n) begin
    if (!rst_sync_n) begin
        // reset assignments
    end else begin
        // normal operation
    end
end

Synchronous Reset (ASIC Preferred):

systemverilog
always_ff @(posedge clk) begin
    if (!rst_n) begin
        // reset assignments
    end else begin
        // normal operation
    end
end

Reset value conventions:

  • Registers: reset to known value (typically '0, i.e., all zeros)
  • Counters: reset to '0
  • State machines: reset to IDLE state (encoded as '0 or parameter)
  • FIFO pointers: reset to '0
  • Data valid signals: reset to 1'b0 (invalid)
  • Ready/acknowledge signals: reset to 1'b0

Rule 4: Signal Naming Conventions

Use a consistent, readable naming convention.

| Element | Convention | Example | |---------|-----------|---------| | Module name | lowercase_with_underscores | sync_fifo, axi_arbiter | | Port names | lowercase_with_underscores | data_in, wr_en | | Internal signals | lowercase_with_underscores | next_state, count_en | | Parameters | UPPERCASE_WITH_UNDERSCORES | DATA_WIDTH, FIFO_DEPTH | | Localparams | UPPERCASE_WITH_UNDERSCORES | IDLE, ACTIVE | | Active-low signals | suffix _n | rst_n, ready_n | | Clock signals | clk | clk, sys_clk | | Enable signals | prefix or suffix _en | wr_en, en_count | | Registered outputs | prefix _r (optional) | data_r, addr_r | | Next-state signals | prefix next_ | next_state, next_count | | Synchronized signals | suffix _sync | sig_sync, pulse_sync |

Rule 5: Module Header and Documentation

Every module must have a header comment block containing:

systemverilog
// =============================================================================
// Module: counter
// Description: Parameterized binary up-counter with enable, synchronous clear,
//              and overflow flag. Resets asynchronously.
// Parameters:
//   WIDTH  - Counter bit width (default: 8)
// Ports:
//   clk    - System clock (posedge)
//   rst_n  - Asynchronous reset (active low)
//   en     - Count enable (active high)
//   clear  - Synchronous clear (active high)
//   count  - Current counter value [WIDTH-1:0]
//   ovf    - Overflow flag (asserts when count wraps from max to 0)
// Author: ICHDL Agent
// =============================================================================

Rule 6: SystemVerilog Preferred Constructs

Prefer SystemVerilog (IEEE 1800-2017) over legacy Verilog-2001:

| Instead of | Use | Reason | |-----------|-----|--------| | reg / wire | logic | Single type for both, clearer intent | | always @(posedge clk) | always_ff @(posedge clk) | Intent-explicit, checked by tools | | always @* | always_comb | Auto-sensitivity, checked for latch | | integer (for genvar) | genvar | Intent-explicit for generate loops | | 'b0 (unsized) | '0 (auto-sized) | Saves bugs from width mismatches | | parameter (for constants) | localparam | Prevents unintended override | | function | function automatic | Avoids simulation issues with static storage |

Rule 7: Parameterization

Make modules reusable through parameterization:

systemverilog
module counter #(
    parameter int WIDTH      = 8,      // Counter bit width
    parameter int MAX_COUNT  = 255,    // Rollover value
    parameter bit USE_OVF    = 1       // Include overflow output
) (
    // ports...
);

// Internal calculations based on parameters
localparam int COUNT_BITS = $clog2(MAX_COUNT + 1);

Always use $clog2() for address/pointer widths derived from depths. Never hard-code bit widths that depend on parameters.

Rule 8: Synthesis-Aware Coding

Write code that synthesizes efficiently:

Avoid:

  • Multi-dimensional arrays as register files (depends on tool support)
  • Division and modulo operators (synthesize to huge combinational logic)
  • Recursive functions
  • Dynamic loops with non-constant bounds
  • Multiple drivers on the same signal from different always blocks

Prefer:

  • Power-of-2 divides using shift operators (>>, <<)
  • State machines as separate combinational + sequential blocks (3-block FSM)
  • Registered outputs for critical paths
  • Explicit unique case or priority case for synthesis optimization hints

Rule 9: Finite State Machine Patterns

Use 3-block FSM for clarity and maintainability:

systemverilog
// Block 1: State register (sequential)
always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        state <= IDLE;
    end else begin
        state <= next_state;
    end
end

// Block 2: Next-state logic (combinational)
always_comb begin
    next_state = state;  // default: stay
    case (state)
        IDLE:   if (start)  next_state = RUN;
        RUN:    if (done)   next_state = IDLE;
        default:             next_state = IDLE;
    endcase
end

// Block 3: Output logic (combinational or registered)
always_comb begin
    // Default outputs
    ready = 1'b0;
    done_o = 1'b0;
    case (state)
        RUN: begin
            ready = 1'b1;
            if (counter == MAX) done_o = 1'b1;
        end
        default: ; // all outputs already at default
    endcase
end

State encoding guidelines:

  • FPGA: prefer one-hot encoding (register-rich architectures)
  • ASIC: prefer binary or Gray encoding for more than 16 states
  • Always define states as localparam with explicit values (not enum for maximum portability)

Rule 10: Clock Domain Crossing (CDC) Patterns

When the module interfaces with multiple clock domains, use proven CDC patterns:

Two-stage synchronizer for single-bit signals:

systemverilog
logic [1:0] sig_sync;

always_ff @(posedge dst_clk or negedge dst_rst_n) begin
    if (!dst_rst_n) begin
        sig_sync <= '0;
    end else begin
        sig_sync <= {sig_sync[0], sig_async};
    end
end
// sig_sync[1] is the synchronized signal — use this downstream

Async FIFO for multi-bit data: Use Gray-coded pointers, dual-port RAM, and proper empty/full detection. (Generate using the fifo-generator skill for full implementation.)

Rule 11: Code Generation Checklist

Before outputting the final module, verify:

  • [ ] Every always_ff block uses only <= (non-blocking)
  • [ ] Every always_comb block uses only = (blocking)
  • [ ] No mixed assignment types in any single always block
  • [ ] Every always_comb block has default assignments for all outputs
  • [ ] Every if has else (in combinational blocks)
  • [ ] Every case has default (in combinational blocks)
  • [ ] Reset strategy is consistent throughout
  • [ ] All ports have defined directions
  • [ ] All signals are properly sized (no width mismatches)
  • [ ] Internal signals are declared (no implicit wires)
  • [ ] Parameters use $clog2() where appropriate
  • [ ] Module header comment is present and complete
  • [ ] Signal naming follows conventions
  • [ ] No undriven or floating inputs
  • [ ] No multiple drivers on the same signal

Examples

Example 1: Simple Counter

Input: "Create a parameterized binary up-counter with enable and overflow detection."

systemverilog
// =============================================================================
// Module: counter
// Description: Parameterized binary up-counter with enable, synchronous clear,
//              and overflow flag.
// =============================================================================

module counter #(
    parameter int WIDTH = 8
) (
    input  logic                clk,
    input  logic                rst_n,
    input  logic                en,
    input  logic                clear,
    output logic [WIDTH-1:0]    count,
    output logic                ovf
);

    logic [WIDTH-1:0] next_count;

    // Combinational: next count value
    always_comb begin
        next_count = count;       // default
        if (clear) begin
            next_count = '0;
        end else if (en) begin
            next_count = count + 1'b1;
        end
    end

    // Sequential: count register
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            count <= '0;
        end else begin
            count <= next_count;
        end
    end

    // Overflow detection
    assign ovf = en && (count == {WIDTH{1'b1}});

endmodule

Example 2: AXI-Stream FIFO (Simplified)

Input: "Create a synchronous FIFO with AXI-Stream style valid/ready handshake."

systemverilog
// =============================================================================
// Module: axis_fifo
// Description: Synchronous FIFO with valid/ready (backpressure) handshake.
//              Uses registers for small depths, suitable for control paths.
// =============================================================================

module axis_fifo #(
    parameter int DATA_WIDTH = 8,
    parameter int DEPTH      = 4
) (
    input  logic                    clk,
    input  logic                    rst_n,

    // Write side
    input  logic                    s_valid,
    output logic                    s_ready,
    input  logic [DATA_WIDTH-1:0]   s_data,

    // Read side
    output logic                    m_valid,
    input  logic                    m_ready,
    output logic [DATA_WIDTH-1:0]   m_data
);

    localparam int PTR_WIDTH = $clog2(DEPTH);

    logic [DATA_WIDTH-1:0] mem [0:DEPTH-1];
    logic [PTR_WIDTH-1:0] wr_ptr, rd_ptr;
    logic [PTR_WIDTH:0]   count;  // extra bit for full detection

    // Write logic
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            wr_ptr <= '0;
        end else if (s_valid && s_ready) begin
            mem[wr_ptr] <= s_data;
            wr_ptr <= wr_ptr + 1'b1;
        end
    end

    // Read logic
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            rd_ptr <= '0;
        end else if (m_valid && m_ready) begin
            rd_ptr <= rd_ptr + 1'b1;
        end
    end

    // Counter logic
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            count <= '0;
        end else begin
            case ({(s_valid && s_ready), (m_valid && m_ready)})
                2'b01: count <= count - 1'b1;
                2'b10: count <= count + 1'b1;
                default: ;
            endcase
        end
    end

    // Status signals
    assign s_ready = (count < DEPTH[PTR_WIDTH:0]);
    assign m_valid = (count > 0);
    assign m_data  = mem[rd_ptr];

endmodule

Example 3: Priority Arbiter

Input: "Create an N-input priority arbiter with the lowest index having highest priority."

systemverilog
// =============================================================================
// Module: priority_arbiter
// Description: Fixed-priority arbiter: lowest-indexed request wins.
//              Grant is one-hot. If no requests, grant = 0.
// =============================================================================

module priority_arbiter #(
    parameter int N = 4
) (
    input  logic [N-1:0]    req,
    output logic [N-1:0]    gnt,
    output logic            any_gnt
);

    always_comb begin
        gnt = '0;
        for (int i = 0; i < N; i++) begin
            if (req[i]) begin
                gnt[i] = 1'b1;
                break;  // lowest index wins
            end
        end
    end

    assign any_gnt = |req;

endmodule

Anti-Patterns: What NOT to Generate

Anti-Pattern 1: Mixed Assignments in One Block

systemverilog
// NEVER generate code like this:
always @(posedge clk) begin
    tmp = a + b;         // blocking — WRONG
    result <= tmp;       // non-blocking — simulation-synthesis MISMATCH
end

Anti-Pattern 2: Missing else / default

systemverilog
// NEVER generate code like this (latch inferred):
always_comb begin
    if (en) out = data;
end

Anti-Pattern 3: Reset Missing from Some Registers

systemverilog
// NEVER have un-resettable registers in the same module unless intentional:
always_ff @(posedge clk) begin
    count1 <= count1 + 1;  // no reset — may need one
    // but count2 is resettable — inconsistency
end

Anti-Pattern 4: Combinational Loops

systemverilog
// NEVER create combinational loops:
assign a = b & c;
assign c = a | d;  // a depends on c, c depends on a — infinite loop

Anti-Pattern 5: Unsized Constants

systemverilog
// NEVER use unsized literals where width matters:
assign result = count + 1;     // '1' is 32-bit — width mismatch
// ALWAYS size appropriately:
assign result = count + 1'b1;  // explicit 1-bit addition

Anti-Pattern 6: Blocking Assignment for Sequential Logic

systemverilog
// NEVER:
always_ff @(posedge clk) begin
    count = count + 1;  // = in sequential block — race condition in simulation
end

Interaction with Other Skills

  • fsm-generator: Use for complex state machine generation; this skill covers simple FSMs
  • fifo-generator: Use for full async/sync FIFO implementations with Gray coding
  • cdc-synchronizer: Use for single and multi-bit CDC structures
  • rtl-reviewer: Use after generation to check for issues this skill should have avoided
  • sdc-writer: Generate timing constraints for the module produced by this skill
  • testbench-simple: Generate a testbench for the module produced by this skill
  • assertion-writer: Generate formal assertions for the module's interfaces

Disclaimer

This skill is provided "as is" for reference and educational purposes only. Code generated using this skill should be independently reviewed and verified before use in any production design, tape-out, or safety-critical application. The author(s) and ICHDL assume no liability for any errors, omissions, or damages arising from the use of this skill or any code generated with it.

Usage Rights

You are free to use, modify, and distribute this skill for personal or commercial purposes. Attribution to ICHDL (ichdl.com) is appreciated but not required. Redistribution of this skill in substantially unmodified form must retain this notice.

Download this Skill as .md file

Copy into your AI Agent's system prompt to activate this Skill.