SystemVerilog RTL Design Reference
Coding Standards
Signal Declarations
// Preferred: logic for all signals
logic [7:0] data;
logic valid;
// Avoid: reg/wire (older style)
// reg [7:0] data; // Don't use
// wire valid; // Don't use
Always Blocks
| Block Type |
Use Case |
Assignment |
always_ff |
Sequential logic (flip-flops) |
Non-blocking <= |
always_comb |
Combinational logic |
Blocking = |
always_latch |
Latches (avoid!) |
Blocking = |
// Sequential - non-blocking
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
q <= '0;
else
q <= d;
end
// Combinational - blocking
always_comb begin
y = a & b;
z = y | c; // Uses updated y
end
Design Patterns
Parameterized Module
module fifo #(
parameter int WIDTH = 8,
parameter int DEPTH = 16,
parameter bit FWFT = 1'b0 // First-word-fall-through
) (
input logic clk,
input logic rst_n,
// Write interface
input logic [WIDTH-1:0] wr_data,
input logic wr_en,
output logic full,
// Read interface
output logic [WIDTH-1:0] rd_data,
input logic rd_en,
output logic empty
);
localparam int ADDR_WIDTH = $clog2(DEPTH);
// Memory array
logic [WIDTH-1:0] mem [DEPTH];
// Pointers
logic [ADDR_WIDTH:0] wr_ptr, rd_ptr; // Extra bit for full/empty
// ... implementation
endmodule
FSM with typedef enum
typedef enum logic [2:0] {
IDLE = 3'b001,
RUNNING = 3'b010,
DONE = 3'b100
} state_t;
state_t state, next_state;
// State register (sequential)
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= IDLE;
else
state <= next_state;
end
// Next state logic (combinational)
always_comb begin
next_state = state; // Default: hold
unique case (state)
IDLE: if (start) next_state = RUNNING;
RUNNING: if (complete) next_state = DONE;
DONE: next_state = IDLE;
default: next_state = IDLE; // Safe default
endcase
end
// Output logic (combinational)
always_comb begin
busy = (state == RUNNING);
done = (state == DONE);
end
Pipeline Stage
module pipeline_stage #(
parameter int WIDTH = 32
) (
input logic clk,
input logic rst_n,
// Upstream
input logic [WIDTH-1:0] data_in,
input logic valid_in,
output logic ready_out,
// Downstream
output logic [WIDTH-1:0] data_out,
output logic valid_out,
input logic ready_in
);
// Handshake: transfer when valid && ready
wire transfer_in = valid_in && ready_out;
wire transfer_out = valid_out && ready_in;
// Pipeline register
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_out <= '0;
valid_out <= 1'b0;
end else if (transfer_in || transfer_out) begin
data_out <= data_in;
valid_out <= valid_in;
end
end
// Ready when empty or downstream accepts
assign ready_out = !valid_out || ready_in;
endmodule
Clock Domain Crossing (2FF Synchronizer)
module sync_2ff #(
parameter int WIDTH = 1,
parameter int STAGES = 2
) (
input logic clk_dst,
input logic rst_n,
input logic [WIDTH-1:0] data_src,
output logic [WIDTH-1:0] data_dst
);
logic [WIDTH-1:0] sync_reg [STAGES];
always_ff @(posedge clk_dst or negedge rst_n) begin
if (!rst_n) begin
for (int i = 0; i < STAGES; i++)
sync_reg[i] <= '0;
end else begin
sync_reg[0] <= data_src;
for (int i = 1; i < STAGES; i++)
sync_reg[i] <= sync_reg[i-1];
end
end
assign data_dst = sync_reg[STAGES-1];
endmodule
Generate Blocks
// Conditional generate
generate
if (USE_BRAM) begin : gen_bram
bram_module u_mem (...);
end else begin : gen_lutram
lutram_module u_mem (...);
end
endgenerate
// Iterative generate
generate
for (genvar i = 0; i < N; i++) begin : gen_stages
pipeline_stage #(.WIDTH(WIDTH)) u_stage (
.data_in (stage_data[i]),
.data_out(stage_data[i+1]),
...
);
end
endgenerate
Synthesis Guidelines
Do's
| Pattern |
Why |
always_ff / always_comb |
Clear intent to synthesis tools |
unique case / priority case |
Helps optimization, catches errors |
'0 / '1 for reset values |
Flexible width |
| Synchronous reset (FPGAs) |
Uses flip-flop reset input |
| Named port connections |
.port(signal) - readable, catches errors |
Don'ts
| Anti-Pattern |
Problem |
initial blocks |
Not synthesizable (simulation only) |
#delays |
Not synthesizable |
force/release |
Not synthesizable |
fork/join |
Not synthesizable |
Incomplete case |
Infers latch |
Missing else |
May infer latch |
Reading before writing in always_comb |
Infers latch |
Avoiding Latches
// BAD - infers latch (missing else)
always_comb begin
if (sel)
y = a;
// y not assigned when sel=0 -> LATCH
end
// GOOD - complete assignment
always_comb begin
if (sel)
y = a;
else
y = b;
end
// GOOD - default value first
always_comb begin
y = '0; // Default
if (sel)
y = a;
end
Timing Patterns
Registered Outputs
// Register outputs to improve timing
always_ff @(posedge clk) begin
data_out_reg <= internal_result;
end
assign data_out = data_out_reg;
Pipelining for Timing Closure
// Break long combinational paths with registers
// Before: a -> [long_logic] -> y
// After:
always_ff @(posedge clk) begin
stage1 <= a; // Input register
stage2 <= long_logic_part1; // Pipeline register
y <= long_logic_part2; // Output register
end
Common Modules
| Module |
Key Considerations |
| FIFO |
Gray code pointers for CDC, full/empty flags |
| Arbiter |
Fixed priority, round-robin, or weighted |
| Register File |
Read-during-write behavior (bypass?) |
| Counter |
Overflow handling, enable, load |
| Shift Register |
Parallel load, serial in/out |
| Mux |
One-hot select vs binary select |
| Encoder/Decoder |
Priority vs non-priority |