Module Declaration and Ports
Every Verilog design starts with a module. Think of it like a black box with inputs and outputs.
module my_module (
input clk,
input rst,
input [7:0] data_in,
output [7:0] data_out,
output valid
);
endmodule
Key Points:
input = signals coming INTO your module
output = signals going OUT of your module
[7:0] = 8-bit wide signal (bit 7 down to bit 0)
All ports are implicitly wire type unless specified otherwise
Data Types: wire vs reg
wire
Represents physical connections (like actual wires)
Can't store values on their own
Must be continuously driven by something
Used for combinational logic outputs
wire [7:0] sum;
wire carry_out;
wire [15:0] product;
reg
Represents storage elements (like flip-flops or latches)
Can hold values between clock edges
Used in sequential logic (inside always blocks)
Note: reg doesn't always mean a physical register!
reg [7:0] counter;
reg state;
reg [31:0] memory [0:255];
assign Statement (Combinational Logic)
assign creates continuous assignments - like connecting wires together.
module adder_example (
input [7:0] a, b,
input cin,
output [7:0] sum,
output cout
);
assign sum = a + b + cin;
assign cout = (a + b + cin) > 8'hFF;
assign sum = (cin) ? a + b + 1 : a + b;
assign result = a & b;
assign result = a | b;
assign result = a ^ b;
assign result = ~a;
assign result = a << 2;
assign result = a >> 1;
endmodule
Key Points:
assign is for combinational logic only
Output changes immediately when inputs change
Think of it as permanent wire connections
always Blocks (Sequential and Combinational)
always blocks are where the action happens. They describe behavior.
Sequential Logic (Clocked)
always @(posedge clk) begin
if (rst) begin
q <= 1'b0;
end else begin
q <= d;
end
end
always @(posedge clk) begin
if (rst) begin
counter <= 8'b0;
end else if (enable) begin
counter <= counter + 1;
end
end
always @(posedge clk) begin
shift_reg <= {shift_reg[6:0], serial_in};
end
Combinational Logic (always_comb or always @(*))
always @(*) begin
case (operation)
2'b00: result = a + b;
2'b01: result = a - b;
2'b10: result = a & b;
2'b11: result = a | b;
default: result = 8'b0;
endcase
end
always @(*) begin
if (input_vec[7]) output_code = 3'd7;
else if (input_vec[6]) output_code = 3'd6;
else if (input_vec[5]) output_code = 3'd5;
else output_code = 3'd0;
end
Edge Detection: posedge, negedge
Controls when the always block executes.
always @(posedge clk) begin
end
always @(negedge clk) begin
end
always @(posedge clk or posedge rst) begin
if (rst) begin
end else begin
end
end
always @(*) begin
end
Module Instantiation and Hierarchy
Modules are like LEGO blocks - you build complex designs by connecting simpler modules together.
Basic Module Instantiation
module adder_4bit (
input [3:0] a, b,
input cin,
output [3:0] sum,
output cout
);
assign {cout, sum} = a + b + cin;
endmodule
module calculator (
input [3:0] x, y, z,
output [3:0] result1, result2,
output overflow1, overflow2
);
adder_4bit add1 (
.a(x),
.b(y),
.cin(1'b0),
.sum(result1),
.cout(overflow1)
);
adder_4bit add2 (
.a(result1),
.b(z),
.cin(1'b0),
.sum(result2),
.cout(overflow2)
);
endmodule
Positional vs Named Port Connections
adder_4bit add1 (x, y, 1'b0, result1, overflow1);
adder_4bit add1 (
.a(x),
.b(y),
.cin(1'b0),
.sum(result1),
.cout(overflow1)
);
Generate Statements (Arrays of Modules)
module ripple_carry_adder_16bit (
input [15:0] a, b,
input cin,
output [15:0] sum,
output cout
);
wire [16:0] carry;
assign carry[0] = cin;
assign cout = carry[16];
genvar i;
generate
for (i = 0; i < 16; i = i + 1) begin : adder_stage
full_adder fa (
.a(a[i]),
.b(b[i]),
.cin(carry[i]),
.sum(sum[i]),
.cout(carry[i+1])
);
end
endgenerate
endmodule
Control Structures
if-else Statements
always @(*) begin
if (enable) begin
output_data = input_data;
end else begin
output_data = 8'b0;
end
end
always @(*) begin
if (priority[3]) begin
grant = 4'b1000;
end else if (priority[2]) begin
grant = 4'b0100;
end else if (priority[1]) begin
grant = 4'b0010;
end else if (priority[0]) begin
grant = 4'b0001;
end else begin
grant = 4'b0000;
end
end
always @(posedge clk) begin
if (rst) begin
counter <= 0;
end else if (load) begin
counter <= load_value;
end else if (enable) begin
counter <= counter + 1;
end
end
case Statements
always @(*) begin
case (opcode)
3'b000: result = a + b;
3'b001: result = a - b;
3'b010: result = a & b;
3'b011: result = a | b;
3'b100: result = a ^ b;
3'b101: result = ~a;
3'b110: result = a << 1;
3'b111: result = a >> 1;
default: result = 8'b0;
endcase
end
always @(*) begin
casez (instruction[6:0])
7'b0110011: instr_type = R_TYPE;
7'b0010011: instr_type = I_TYPE;
7'b01100??: instr_type = B_TYPE;
7'b???????: instr_type = UNKNOWN;
default: instr_type = UNKNOWN;
endcase
end
always @(posedge clk) begin
if (rst) begin
state <= IDLE;
end else begin
case (state)
IDLE: begin
if (start) state <= FETCH;
end
FETCH: begin
state <= DECODE;
end
DECODE: begin
state <= EXECUTE;
end
EXECUTE: begin
if (done) state <= IDLE;
end
default: state <= IDLE;
endcase
end
end
Loops (for, while, repeat)
Important: Loops in Verilog create hardware, not software! Each loop iteration becomes parallel hardware.
for Loops
always @(*) begin
integer i;
parity = 1'b0;
for (i = 0; i < 8; i = i + 1) begin
parity = parity ^ data[i];
end
end
integer j;
always @(posedge clk) begin
if (rst) begin
for (j = 0; j < 16; j = j + 1) begin
register_file[j] <= 32'b0;
end
end else if (write_enable) begin
register_file[write_addr] <= write_data;
end
end
generate
for (genvar k = 0; k < 8; k = k + 1) begin : byte_lane
assign byte_valid[k] = |data_bus[k*8 +: 8];
end
endgenerate
while and repeat Loops
always @(*) begin
temp = input_val;
count = 0;
while (temp != 0) begin
count = count + 1;
temp = temp >> 1;
end
end
always @(*) begin
shifted_data = input_data;
repeat (shift_amount) begin
shifted_data = shifted_data << 1;
end
end
Common Data Structures
Arrays and Memories
reg [31:0] registers [0:31];
reg [7:0] memory [0:255][0:3];
always @(posedge clk) begin
if (write_enable) begin
registers[write_addr] <= write_data;
end
read_data <= registers[read_addr];
end
integer i;
initial begin
for (i = 0; i < 32; i = i + 1) begin
registers[i] = 32'b0;
end
end
Packed vs Unpacked Arrays
reg [7:0] packed_array [0:15];
wire [127:0] flat_view = packed_array;
reg unpacked_array [0:15][7:0];
Bit Selection and Slicing
wire [31:0] data = 32'hDEADBEEF;
wire msb = data[31];
wire lsb = data[0];
wire [7:0] byte3 = data[31:24];
wire [7:0] byte0 = data[7:0];
wire [15:0] upper = data[31:16];
wire selected_bit = data[bit_index];
wire [7:0] byte_sel = data[byte_index*8 +: 8];
wire [7:0] byte_sel2 = data[byte_index*8 + 7 : byte_index*8];
Advanced Control Structures
Conditional Operator (Ternary)
assign output = select ? input1 : input0;
assign priority_out = (high_pri) ? high_data :
(med_pri) ? med_data :
(low_pri) ? low_data : default_data;
always @(*) begin
next_state = (current_state == IDLE) ?
(start ? ACTIVE : IDLE) :
(done ? IDLE : ACTIVE);
end
Functions and Tasks
function [7:0] count_ones;
input [31:0] data;
integer i;
begin
count_ones = 0;
for (i = 0; i < 32; i = i + 1) begin
count_ones = count_ones + data[i];
end
end
endfunction
task write_memory;
input [7:0] addr;
input [31:0] data;
begin
@(posedge clk);
mem_addr <= addr;
mem_data <= data;
mem_we <= 1'b1;
@(posedge clk);
mem_we <= 1'b0;
end
endtask
always @(*) begin
ones_count = count_ones(input_vector);
end
always @(posedge clk) begin
if (write_request) begin
write_memory(address, write_data);
end
end
Parameters and Localparam
module fifo #(
parameter WIDTH = 8,
parameter DEPTH = 16
) (
input clk, rst,
input [WIDTH-1:0] din,
input push, pop,
output [WIDTH-1:0] dout,
output full, empty
);
localparam ADDR_BITS = $clog2(DEPTH);
reg [WIDTH-1:0] memory [0:DEPTH-1];
reg [ADDR_BITS:0] wr_ptr, rd_ptr;
endmodule
fifo #(.WIDTH(32), .DEPTH(64)) data_fifo (...);
fifo #(.WIDTH(16), .DEPTH(8)) cmd_fifo (...);
Complete Examples
Simple Counter
module counter (
input clk,
input rst,
input enable,
output reg [7:0] count
);
always @(posedge clk) begin
if (rst) begin
count <= 8'b0;
end else if (enable) begin
count <= count + 1;
end
end
endmodule
State Machine
module fsm (
input clk, rst,
input start, done,
output reg busy,
output reg [1:0] state
);
parameter IDLE = 2'b00;
parameter WORKING = 2'b01;
parameter FINISH = 2'b10;
reg [1:0] next_state;
always @(posedge clk) begin
if (rst) begin
state <= IDLE;
end else begin
state <= next_state;
end
end
always @(*) begin
case (state)
IDLE: begin
if (start) next_state = WORKING;
else next_state = IDLE;
end
WORKING: begin
if (done) next_state = FINISH;
else next_state = WORKING;
end
FINISH: begin
next_state = IDLE;
end
default: next_state = IDLE;
endcase
end
always @(*) begin
busy = (state == WORKING);
end
endmodule
Memory Module
module simple_memory (
input clk,
input [7:0] addr,
input [31:0] data_in,
input we,
output reg [31:0] data_out
);
reg [31:0] memory [0:255];
always @(posedge clk) begin
if (we) begin
memory[addr] <= data_in;
end
data_out <= memory[addr];
end
endmodule
Real-World Example: UART Transmitter
module uart_tx #(
parameter CLOCK_FREQ = 50_000_000,
parameter BAUD_RATE = 115200
) (
input clk, rst,
input [7:0] data,
input send,
output reg tx,
output reg busy
);
localparam CLKS_PER_BIT = CLOCK_FREQ / BAUD_RATE;
localparam COUNTER_BITS = $clog2(CLKS_PER_BIT);
localparam IDLE = 3'b000;
localparam START_BIT = 3'b001;
localparam DATA_BITS = 3'b010;
localparam STOP_BIT = 3'b011;
reg [2:0] state;
reg [COUNTER_BITS-1:0] clk_counter;
reg [2:0] bit_counter;
reg [7:0] tx_data;
always @(posedge clk) begin
if (rst) begin
state <= IDLE;
tx <= 1'b1;
busy <= 1'b0;
clk_counter <= 0;
bit_counter <= 0;
end else begin
case (state)
IDLE: begin
tx <= 1'b1;
busy <= 1'b0;
clk_counter <= 0;
bit_counter <= 0;
if (send) begin
tx_data <= data;
state <= START_BIT;
busy <= 1'b1;
end
end
START_BIT: begin
tx <= 1'b0;
if (clk_counter < CLKS_PER_BIT - 1) begin
clk_counter <= clk_counter + 1;
end else begin
clk_counter <= 0;
state <= DATA_BITS;
end
end
DATA_BITS: begin
tx <= tx_data[bit_counter];
if (clk_counter < CLKS_PER_BIT - 1) begin
clk_counter <= clk_counter + 1;
end else begin
clk_counter <= 0;
if (bit_counter < 7) begin
bit_counter <= bit_counter + 1;
end else begin
bit_counter <= 0;
state <= STOP_BIT;
end
end
end
STOP_BIT: begin
tx <= 1'b1;
if (clk_counter < CLKS_PER_BIT - 1) begin
clk_counter <= clk_counter + 1;
end else begin
state <= IDLE;
end
end
default: state <= IDLE;
endcase
end
end
endmodule
Key Rules and Best Practices
Blocking vs Non-Blocking Assignments
always @(*) begin
temp = a + b;
result = temp + c;
end
always @(posedge clk) begin
temp <= a + b;
result <= temp + c;
end
Rule of Thumb:
Use <= for sequential logic (flip-flops, registers)
Use = for combinational logic (inside always @(*))
Common Patterns
always @(posedge clk) begin
if (rst) begin
counter <= 0;
state <= IDLE;
valid <= 0;
end else begin
end
end
always @(posedge clk) begin
if (rst) begin
data <= 0;
end else if (enable) begin
data <= new_data;
end
end
assign output = (select) ? input1 : input0;
always @(*) begin
outputs = 8'b0;
outputs[address] = 1'b1;
end
Summary
wire: For connections and combinational outputs
reg: For storage and variables in always blocks
assign: Continuous combinational logic
always @(posedge clk): Sequential logic (flip-flops)
always @(*): Combinational logic
<=: Non-blocking (use for sequential)
\=: Blocking (use for combinational)
Modules: Building blocks - connect them to make complex designs
if/case: Control flow - creates multiplexers and decoders in hardware
for loops: Create parallel hardware, not sequential execution
Functions/tasks: Reusable code blocks
Parameters: Make modules configurable
The key is understanding when things happen:
assign and always @(*) react immediately to input changes
always @(posedge clk) only updates on clock edges
Loops unroll into parallel hardware
Each module instance is separate physical hardware