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:
- Module header with descriptive comment block
- Parameter declarations (
parameter/localparam) with comments - Port declarations using SystemVerilog
logictype (preferred) or Verilogwire/reg - Internal signal declarations with comments
- 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
- 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):
// 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
endExample — Correct:
// 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
endRule 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:
- Have a
defaultassignment at the top for every output signal - Ensure every
ifhas a correspondingelse - Ensure every
casehas adefaultbranch - Ensure all output signals are assigned in every branch
Example — WILL infer a latch:
// BUG: latch inferred — y not assigned when en=0
always_comb begin
if (en) y = a & b;
endExample — Latch-free:
// 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// 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
endRule 3: Reset Strategy
Choose the appropriate reset strategy based on the target technology and requirements.
Asynchronous Assert, Synchronous De-assert (Recommended Default):
// 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
endSynchronous Reset (ASIC Preferred):
always_ff @(posedge clk) begin
if (!rst_n) begin
// reset assignments
end else begin
// normal operation
end
endReset value conventions:
- Registers: reset to known value (typically
'0, i.e., all zeros) - Counters: reset to
'0 - State machines: reset to
IDLEstate (encoded as'0or 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:
// =============================================================================
// 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:
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 caseorpriority casefor synthesis optimization hints
Rule 9: Finite State Machine Patterns
Use 3-block FSM for clarity and maintainability:
// 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
endState 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
localparamwith explicit values (notenumfor 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:
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 downstreamAsync 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_ffblock uses only<=(non-blocking) - [ ] Every
always_combblock uses only=(blocking) - [ ] No mixed assignment types in any single always block
- [ ] Every
always_combblock has default assignments for all outputs - [ ] Every
ifhaselse(in combinational blocks) - [ ] Every
casehasdefault(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."
// =============================================================================
// 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}});
endmoduleExample 2: AXI-Stream FIFO (Simplified)
Input: "Create a synchronous FIFO with AXI-Stream style valid/ready handshake."
// =============================================================================
// 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];
endmoduleExample 3: Priority Arbiter
Input: "Create an N-input priority arbiter with the lowest index having highest priority."
// =============================================================================
// 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;
endmoduleAnti-Patterns: What NOT to Generate
Anti-Pattern 1: Mixed Assignments in One Block
// NEVER generate code like this:
always @(posedge clk) begin
tmp = a + b; // blocking — WRONG
result <= tmp; // non-blocking — simulation-synthesis MISMATCH
endAnti-Pattern 2: Missing else / default
// NEVER generate code like this (latch inferred):
always_comb begin
if (en) out = data;
endAnti-Pattern 3: Reset Missing from Some Registers
// 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
endAnti-Pattern 4: Combinational Loops
// NEVER create combinational loops:
assign a = b & c;
assign c = a | d; // a depends on c, c depends on a — infinite loopAnti-Pattern 5: Unsized Constants
// 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 additionAnti-Pattern 6: Blocking Assignment for Sequential Logic
// NEVER:
always_ff @(posedge clk) begin
count = count + 1; // = in sequential block — race condition in simulation
endInteraction with Other Skills
fsm-generator: Use for complex state machine generation; this skill covers simple FSMsfifo-generator: Use for full async/sync FIFO implementations with Gray codingcdc-synchronizer: Use for single and multi-bit CDC structuresrtl-reviewer: Use after generation to check for issues this skill should have avoidedsdc-writer: Generate timing constraints for the module produced by this skilltestbench-simple: Generate a testbench for the module produced by this skillassertion-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.