Welcome Back, Future Hardware Hero! ⚡
Ready to level up your Verilog skills? Last time, we covered the basics. Today, we're diving DEEP into the core concepts that separate hobbyists from professional hardware designers! Buckle up - we're about to get technical! 🔧
📚 What You'll Learn Today:
-
System tasks beyond
$display- Meet$stop! - Input/Output ports - The gates of your module
-
Variables in Verilog -
wire,reg,parameter, and the magic ofassign - Bit manipulation - Controlling every single wire
-
Constants & Macros -
localparamanddefine - Conditional logic - Hardware-style if/else
-
Blocking vs Non-blocking - The
begin/endmagic - A complete project - Putting it ALL together!
⏹️ What is $stop? (The Pause Button for Simulation)
$stop temporarily pauses simulation, while $finish ends it completely!
module stop_example;
reg clk = 0;
integer counter = 0;
// Generate a clock
always #5 clk = ~clk;
initial begin
$display("🚀 Simulation starting at time %0d", $time);
// Run for 50 time units
#50;
$display("⏸️ Pausing simulation at time %0d", $time);
// PAUSE HERE - Can resume later!
$stop;
// After continuing...
$display("▶️ Resuming simulation at time %0d", $time);
#50;
$display("🏁 Final simulation time: %0d", $time);
$finish;
end
// Another process that counts clock edges
always @(posedge clk) begin
counter = counter + 1;
$display(" Clock edge #%0d at time %0d", counter, $time);
end
/*
Practical Use Cases for $stop:
1. Debugging complex waveforms
2. Checking intermediate values
3. Manual inspection during simulation
4. Conditional breakpoints
Try this in simulation:
- Run until $stop
- Inspect signals
- Type "continue" to resume
*/
endmodule
🚪 Inputs & Outputs: The Doors to Your Module
Think of a module as a black box with labeled connectors!
// Anatomy of a module
module smart_module (
// INPUT PORTS - Information coming IN
input wire data_in, // Single bit input
input wire [7:0] bus_in, // 8-bit bus input
input wire clk, // Clock input
input wire reset_n, // Active-low reset (n = negative)
// OUTPUT PORTS - Information going OUT
output reg data_out, // Single bit output (registered)
output wire [15:0] result, // 16-bit combinational output
output reg ready, // Status output
// INOUT PORTS - Bidirectional (like a data bus)
inout wire [7:0] data_bus // Can be input OR output
);
// REAL-WORLD ANALOGY:
// Think of your smartphone:
// INPUTS: Touch screen, buttons, microphone, sensors
// OUTPUTS: Display, speaker, vibration motor
// INOUT: USB-C port (charges AND transfers data)
endmodule
// Detailed example: UART Transmitter
module uart_tx (
// INPUTS (from the system)
input wire clk_50mhz, // 50MHz system clock
input wire reset, // Global reset
input wire [7:0] tx_data, // Data to transmit
input wire tx_start, // Start transmission signal
// OUTPUTS (to the outside world)
output reg tx_pin, // Physical TX pin (goes to RX of other device)
output wire tx_busy, // Status: currently transmitting
output wire tx_done // Status: transmission complete
);
// Internal logic here...
endmodule
🔄 The Four Musketeers: assign, wire, parameter, reg
1. wire - The Highway, Not the Car
A wire is a CONNECTION, not storage! It carries values from one point to another.
module wire_example;
// Declaration
wire a, b, c; // Single wires
wire [31:0] data_bus; // 32-bit wire bus
wire signed [15:0] temperature; // Signed 16-bit wire
// Wires MUST be driven by:
// 1. assign statements
// 2. module outputs
// 3. primitives (gates)
// Example: Connecting gates
wire and_out, or_out, not_out;
// Gate primitives driving wires
and gate1(and_out, a, b); // and_out = a & b
or gate2(or_out, b, c); // or_out = b | c
not gate3(not_out, a); // not_out = !a
/*
CRITICAL INSIGHT:
wire = Electrical wire in real circuit
No memory! If nothing drives it, it's 'Z' (high-impedance)
Think of it like a water pipe:
- Water flows through (value transmitted)
- No water tank (no storage)
- Multiple sources can connect (multiple drivers with resolution)
*/
endmodule
2. reg - The Memory Element
A reg stores values until explicitly changed. But WAIT - it doesn't always mean a physical register!
module reg_example;
// Declaration
reg flag; // Single bit register
reg [7:0] counter = 8'h00; // 8-bit register with initial value
reg [63:0] big_buffer; // 64-bit register
// reg can be used in:
// 1. always blocks
// 2. initial blocks
// 3. As module outputs (output reg)
// Behavioral example
always @(posedge clock) begin
if (reset) begin
counter <= 0; // Reset counter
end else begin
counter <= counter + 1; // Increment counter
end
end
/*
COMMON CONFUSION CLARIFIED:
reg ≠ Physical Register!
reg = Behavioral storage in simulation
Only becomes physical register when used in clocked always block!
Example of reg that's NOT a register:
always @(*) begin
comb_result = a + b; // comb_result is reg but combinational!
end
*/
endmodule
3. assign - The Continuous Connection
assign creates a permanent, continuous connection between expressions.
module assign_example;
wire [3:0] sum, difference;
wire [7:0] product;
wire [3:0] a = 4'b1100; // 12 decimal
wire [3:0] b = 4'b0011; // 3 decimal
// Continuous assignments - ALWAYS active
assign sum = a + b; // 12 + 3 = 15 (4'b1111)
assign difference = a - b; // 12 - 3 = 9 (4'b1001)
assign product = a * b; // 12 * 3 = 36 (8'b00100100)
// Conditional assignments
wire [3:0] selected_value;
assign selected_value = (select) ? a : b;
// Bitwise operations
wire [3:0] and_result;
assign and_result = a & b; // 1100 & 0011 = 0000
// Concatenation
wire [7:0] concatenated;
assign concatenated = {a, b}; // 11000011
/*
WHY assign IS SPECIAL:
1. Continuous: Always evaluating
2. Order doesn't matter (unlike procedural code)
3. Can only drive wires (not regs)
4. Represents actual hardware connections
Think: assign = Soldering wires together
It's permanent while circuit is powered!
*/
// Real hardware equivalent:
// assign sum = a + b;
// becomes → [Adder Circuit] with inputs a,b and output sum
endmodule
4. parameter - The Configurable Constant
parameter creates named constants that can be changed per-instance!
module configurable_adder #(
// Parameters with default values
parameter WIDTH = 8, // Default: 8-bit
parameter DELAY = 1, // Simulation delay
parameter MAX_VALUE = 255 // Maximum value
) (
input wire [WIDTH-1:0] a,
input wire [WIDTH-1:0] b,
output wire [WIDTH:0] sum // Extra bit for carry
);
// Use parameters in design
assign sum = a + b;
// Parameter-dependent logic
generate
if (WIDTH > 16) begin
// Special logic for wide adders
$display("Using carry-lookahead for %0d-bit adder", WIDTH);
end
endgenerate
endmodule
// Instantiate with different parameters
module top;
// 8-bit adder (uses defaults)
configurable_adder adder8 (.a(a8), .b(b8), .sum(sum8));
// 16-bit adder
configurable_adder #(.WIDTH(16)) adder16 (.a(a16), .b(b16), .sum(sum16));
// 32-bit adder with custom max
configurable_adder #(
.WIDTH(32),
.MAX_VALUE(1000) // Clamp at 1000
) adder32 (.a(a32), .b(b32), .sum(sum32));
/*
PARAMETER POWER:
1. Design reuse: One module, many configurations
2. Easy scaling: Change width without rewriting
3. Design exploration: Try different architectures
4. Documentation: Named constants explain design intent
*/
endmodule
🎯 The Great Debate: wire vs reg - Why wire Can't Store Values
This is THE most important concept in Verilog! Let's settle it once and for all:
module wire_vs_reg;
// ======================
// WIRE: The Highway
// ======================
wire highway; // Just a connection
assign highway = source; // Must be driven continuously
// Can't do this:
// wire x = 1; // Then later: x = 0; ← ERROR!
// Real hardware: Wires are metal traces on silicon
// ======================
// REG: The Parking Lot
// ======================
reg parking_lot; // Can hold value
always @(posedge clk) begin
parking_lot <= new_car; // Stores until next clock
end
// Real hardware: reg in clocked block = Flip-flop
// ======================
// CRITICAL DIFFERENCE
// ======================
/*
WIRE:
- Connection between points
- No memory/storage
- Must be driven continuously
- Multiple drivers allowed (with resolution)
- Represents combinational logic
REG:
- Can hold value over time
- Can be used in procedural blocks
- Can represent:
a) Flip-flops (in clocked blocks)
b) Combinational logic (in always @*)
c) Latches (if not properly coded)
*/
// ======================
// PRACTICAL EXAMPLE
// ======================
reg [3:0] counter_reg; // Storage element
wire [3:0] counter_wire; // Connection
// This works: reg stores value
always @(posedge clk) begin
if (reset)
counter_reg <= 0;
else
counter_reg <= counter_reg + 1;
end
// This FAILS: wire can't store
// always @(posedge clk) begin
// counter_wire <= counter_wire + 1; // ERROR!
// end
// But this works: wire shows reg's value
assign counter_wire = counter_reg;
endmodule
🔢 Bit Banging: Mastering Bit Manipulation
Control every single wire in your bus!
module bit_manipulation;
// ======================
// DECLARATION STYLES
// ======================
wire [7:0] byte_data; // 8 bits: [7] is MSB, [0] is LSB
reg [15:0] word_data; // 16 bits
wire [31:0] dword_data; // 32 bits
wire [63:0] qword_data; // 64 bits
// ======================
// BIT SELECTION
// ======================
wire [7:0] data = 8'b10101100;
wire msb = data[7]; // 1 (bit 7)
wire lsb = data[0]; // 0 (bit 0)
wire nibble = data[3:0]; // 4'b1100 (bits 3-0)
// ======================
// PART SELECTION
// ======================
wire [3:0] upper_nibble = data[7:4]; // 4'b1010
wire [2:0] three_bits = data[6:4]; // 3'b010
wire single_bit = data[5]; // 1'b1
// ======================
// CONCATENATION { }
// ======================
wire [15:0] concatenated;
assign concatenated = {data, data}; // 16'b1010110010101100
assign concatenated = {4'b1010, 4'b1100, 8'hFF}; // Mix and match!
// ======================
// REPLICATION {n{ }}
// ======================
wire [15:0] replicated;
assign replicated = {2{data}}; // Same as {data, data}
assign replicated = {4{4'b1010}}; // 16'b1010101010101010
assign replicated = {8{1'b1}}; // 8'b11111111
// ======================
// BIT MANIPULATION
// ======================
reg [7:0] vector = 8'b01010101;
// Set bit 3 to 1
vector[3] = 1'b1; // 8'b0101**1**101
// Clear bits 6-4
vector[6:4] = 3'b000; // 8'b**000**1101
// Toggle bit 0
vector[0] = ~vector[0]; // If was 1, becomes 0
// ======================
// REAL-WORLD EXAMPLE
// ======================
// 32-bit CPU register manipulation
reg [31:0] cpu_register = 32'hDEADBEEF;
// Extract opcode (bits 31-26)
wire [5:0] opcode = cpu_register[31:26];
// Extract register numbers (bits 25-21, 20-16)
wire [4:0] rs = cpu_register[25:21];
wire [4:0] rt = cpu_register[20:16];
// Extract immediate value (bits 15-0)
wire [15:0] immediate = cpu_register[15:0];
// Sign extend immediate to 32 bits
wire [31:0] sign_extended = {{16{immediate[15]}}, immediate};
// If immediate[15] is 1: {16'b1111111111111111, immediate}
// If immediate[15] is 0: {16'b0000000000000000, immediate}
endmodule
🔧 localparam: The Local Configuration Hero
localparam is like parameter, but CAN'T be changed from outside!
module localparam_example;
// ======================
// PARAMETER: Changeable
// ======================
parameter WIDTH = 8; // Can be overridden
// ======================
// LOCALPARAM: Fixed
// ======================
localparam DEPTH = 256; // Fixed for this module
localparam MSB = WIDTH - 1; // Can depend on parameters
// Common uses:
localparam IDLE = 2'b00;
localparam READ = 2'b01;
localparam WRITE = 2'b10;
localparam ERROR = 2'b11;
// State machine states
localparam [2:0] S_IDLE = 3'b000,
S_START = 3'b001,
S_DATA = 3'b010,
S_STOP = 3'b011,
S_ERROR = 3'b100;
// Memory sizes
localparam ADDR_WIDTH = 10;
localparam MEM_DEPTH = 1 << ADDR_WIDTH; // 2^10 = 1024
/*
WHEN TO USE LOCALPARAM:
1. Internal constants that shouldn't change
2. State encodings
3. Derived values (like MSB = WIDTH-1)
4. Magic numbers you want to name
5. Array sizes
RULE OF THUMB:
- Use parameter for things users might change
- Use localparam for internal implementation details
*/
// REAL EXAMPLE: UART Baud Rate Generator
module uart #(
parameter CLK_FREQ = 50_000_000 // 50MHz clock
) (
// ports...
);
// Local constants based on parameter
localparam BAUD_RATE = 115200;
localparam BAUD_COUNT = CLK_FREQ / BAUD_RATE;
localparam BAUD_HALF = BAUD_COUNT / 2;
// These are fixed once CLK_FREQ is set
// User can't change them independently
endmodule
endmodule
🎩 `define: The Global Macro Magic
`define creates text macros that work ACROSS your entire design!
// ======================
// GLOBAL DEFINITIONS FILE
// ======================
// constants.vh (Verilog Header file)
`ifndef CONSTANTS_VH
`define CONSTANTS_VH
// Global constants
`define CLOCK_FREQ 50_000_000
`define RESET_ACTIVE 1'b0 // Active-low reset
`define TRUE 1'b1
`define FALSE 1'b0
// Error codes
`define ERR_NONE 4'h0
`define ERR_TIMEOUT 4'h1
`define ERR_CRC 4'h2
`define ERR_OVERRUN 4'h3
// Bit widths
`define DATA_WIDTH 32
`define ADDR_WIDTH 16
// Delays (simulation only)
`define CLK_PERIOD 20 // 20ns = 50MHz
`define RESET_TIME 100
// Debug macros
`define DEBUG_MESSAGES // Comment out to disable
`ifdef DEBUG_MESSAGES
`define DBG(msg) $display("DEBUG: %s", msg)
`else
`define DBG(msg) // Empty when not debugging
`endif
`endif // CONSTANTS_VH
// ======================
// USING MACROS
// ======================
module processor;
// Include the definitions
`include "constants.vh"
// Use macros
reg [`DATA_WIDTH-1:0] data_bus;
wire [`ADDR_WIDTH-1:0] address;
initial begin
`DBG("Processor starting");
if (`RESET_ACTIVE == 1'b0) begin
$display("Using active-low reset");
end
#`RESET_TIME; // Wait reset time
`DBG("Reset complete");
end
// Clock generation using macro
reg clk = 0;
always #(`CLK_PERIOD/2) clk = ~clk;
/*
`DEFINE VS PARAMETER:
`define:
- Global text substitution
- Works across files
- No scope, just text replacement
- Can be used anywhere (even in module names!)
parameter:
- Scoped to module instance
- Strongly typed
- Can be overridden per-instance
- Better for configuration
USE `define FOR:
- Truly global constants
- Debug macros
- Conditional compilation
- File paths
USE parameter FOR:
- Module configuration
- Instance-specific values
- Design parameters
*/
// ADVANCED MACRO EXAMPLE
`define REGISTER(NAME, WIDTH) \
reg [WIDTH-1:0] NAME; \
always @(posedge clk) begin \
if (reset) NAME <= 0; \
else NAME <= NAME + 1; \
end
// Use the macro to create registers
`REGISTER(counter, 8) // Creates an 8-bit counter
`REGISTER(timer, 16) // Creates a 16-bit timer
`REGISTER(status, 4) // Creates a 4-bit status register
endmodule
🎭 Conditional Logic: Hardware-Style If/Else
In hardware, if/else creates MULTIPLEXERS, not jumps!
module conditional_logic;
// ======================
// SINGLE LINE IF
// ======================
wire [3:0] result;
assign result = (enable) ? input_a : input_b;
// Hardware: 4-bit 2-to-1 multiplexer
// ======================
// MULTI-LINE IF
// ======================
reg [7:0] processed_data;
always @(*) begin
// if-else creates priority logic
if (mode == 2'b00) begin
processed_data = data_in;
end
else if (mode == 2'b01) begin
processed_data = data_in + 1;
end
else if (mode == 2'b10) begin
processed_data = data_in << 1; // Shift left
end
else begin // mode == 2'b11
processed_data = ~data_in; // Invert
end
end
// Hardware: Chain of comparators + multiplexers
// ======================
// CASE STATEMENT
// ======================
reg [1:0] state;
reg [3:0] output_value;
always @(posedge clk) begin
case (state)
2'b00: begin // IDLE state
output_value <= 4'h0;
if (start) state <= 2'b01;
end
2'b01: begin // RUN state
output_value <= output_value + 1;
if (output_value == 4'hF) state <= 2'b10;
end
2'b10: begin // DONE state
output_value <= 4'hA;
if (ack) state <= 2'b00;
end
default: begin // ERROR/CATCH-ALL
output_value <= 4'hF;
state <= 2'b00;
end
endcase
end
// Hardware: Parallel comparators + multiplexer
// ======================
// NESTED CONDITIONALS
// ======================
always @(*) begin
if (reset) begin
result = 0;
end else begin
if (enable) begin
if (direction) begin
result = data + 1;
end else begin
result = data - 1;
end
end else begin
result = data;
end
end
end
/*
HARDWARE REALITY CHECK:
Every if/else creates:
- Comparator circuits
- Multiplexer circuits
- Potential priority encoders
BAD PATTERN (creates latches):
always @(*) begin
if (condition) begin
output = something;
end
// Missing else → LATCH CREATED!
end
GOOD PATTERN (fully specified):
always @(*) begin
if (condition) begin
output = something;
end else begin
output = something_else; // Always specify else!
end
end
*/
endmodule
🧱 begin & end: The Code Block Delimiters
Think of begin/end as curly braces {} in other languages!
module begin_end_example;
// ======================
// SINGLE STATEMENT (no begin/end needed)
// ======================
always @(posedge clk)
counter <= counter + 1; // One statement, no begin/end
// ======================
// MULTIPLE STATEMENTS (need begin/end)
// ======================
always @(posedge clk) begin
// begin starts the block
if (reset) begin
counter <= 0;
status <= IDLE;
end else begin
counter <= counter + 1;
if (counter == MAX) begin
status <= DONE;
end
end
// end ends the block
end
// ======================
// NAMED BLOCKS
// ======================
always @(posedge clk) begin : COUNTER_LOGIC
// You can name blocks for debugging!
if (reset) begin
counter <= 0;
end else begin : INCREMENT_LOGIC
counter <= counter + 1;
end
end
// ======================
// GENERATE BLOCKS
// ======================
generate
for (i = 0; i < 8; i = i + 1) begin : BIT_SLICE
// Each iteration gets its own scope
assign data_out[i] = data_in[i] & enable[i];
end
endgenerate
// ======================
// INITIAL BLOCKS
// ======================
initial begin : INITIALIZATION
// Everything between begin/end runs at time 0
clk = 0;
reset = 1;
#10 reset = 0;
// Nested blocks
begin : TEST_SEQUENCE
test_input = 8'hAA;
#20;
test_input = 8'h55;
end
end
/*
KEY POINTS:
1. begin/end groups multiple statements
2. Required when >1 statement in block
3. Creates a new scope for local variables
4. Can be named for debugging
5. Analogous to {} in C/Java
COMMON MISTAKES:
// ERROR: Missing begin/end for multiple statements
always @(posedge clk)
stmt1;
stmt2; // Only stmt1 is in always block!
// CORRECT:
always @(posedge clk) begin
stmt1;
stmt2;
end
*/
// REAL-WORLD COMPLEX EXAMPLE
always @(posedge clk or posedge reset) begin : MAIN_FSM
if (reset) begin : RESET_HANDLER
state <= IDLE;
counter <= 0;
data_out <= 0;
end else begin : NORMAL_OPERATION
case (state)
IDLE: begin
if (start) begin
state <= PROCESSING;
counter <= 0;
end
end
PROCESSING: begin : PROCESS_BLOCK
counter <= counter + 1;
data_out <= calculate(counter);
if (counter == MAX_COUNT) begin
state <= DONE;
end
if (error) begin : ERROR_HANDLING
state <= ERROR_STATE;
end
end
// ... more states
endcase
end
end
endmodule
🎮 THE GRAND FINALE: Complete Project - Smart Traffic Light Controller!
Let's build a REAL hardware system using EVERYTHING we learned!
// ======================
// TRAFFIC LIGHT CONTROLLER
// ======================
// File: traffic_light.v
// Global constants
`define RED 2'b00
`define YELLOW 2'b01
`define GREEN 2'b10
`define NS 1'b0 // North-South
`define EW 1'b1 // East-West
module traffic_controller #(
// Configurable timings (in clock cycles)
parameter GREEN_TIME_NS = 500, // 50 seconds at 10Hz
parameter YELLOW_TIME = 50, // 5 seconds
parameter GREEN_TIME_EW = 300, // 30 seconds
parameter ALL_RED_TIME = 10 // 1 second
) (
// Inputs
input wire clk, // 10Hz clock (0.1s period)
input wire reset, // Active-high reset
input wire emergency, // Emergency vehicle detection
input wire pedestrian_ns, // Pedestrian button NS
input wire pedestrian_ew, // Pedestrian button EW
// Outputs
output reg [1:0] light_ns, // NS lights: [RED, YELLOW, GREEN]
output reg [1:0] light_ew, // EW lights
output reg walk_ns, // NS pedestrian walk signal
output reg walk_ew, // EW pedestrian walk signal
output reg [7:0] debug_out // Debug output
);
// ======================
// LOCAL PARAMETERS
// ======================
localparam [2:0] S_NS_GREEN = 3'b000,
S_NS_YELLOW = 3'b001,
S_ALL_RED = 3'b010,
S_EW_GREEN = 3'b011,
S_EW_YELLOW = 3'b100,
S_EMERGENCY = 3'b101;
// ======================
// INTERNAL REGISTERS
// ======================
reg [2:0] state, next_state;
reg [9:0] timer; // 10-bit timer (up to 102.3 seconds)
reg pedestrian_ns_reg, pedestrian_ew_reg;
// ======================
// CONTINUOUS ASSIGNMENTS
// ======================
wire timer_done = (timer == 0);
wire [9:0] timer_max;
// Timer maximum based on state
assign timer_max =
(state == S_NS_GREEN) ? GREEN_TIME_NS :
(state == S_NS_YELLOW) ? YELLOW_TIME :
(state == S_ALL_RED) ? ALL_RED_TIME :
(state == S_EW_GREEN) ? GREEN_TIME_EW :
(state == S_EW_YELLOW) ? YELLOW_TIME :
0; // Emergency or others
// ======================
// STATE REGISTER
// ======================
always @(posedge clk or posedge reset) begin
if (reset) begin
state <= S_NS_GREEN;
timer <= GREEN_TIME_NS;
pedestrian_ns_reg <= 0;
pedestrian_ew_reg <= 0;
end else begin
state <= next_state;
// Update pedestrian request registers
if (pedestrian_ns) pedestrian_ns_reg <= 1;
if (pedestrian_ew) pedestrian_ew_reg <= 1;
// Timer logic
if (emergency) begin
timer <= 0; // Immediate transition
end else if (timer_done) begin
timer <= timer_max; // Reload for next state
end else begin
timer <= timer - 1; // Count down
end
end
end
// ======================
// NEXT STATE LOGIC
// ======================
always @(*) begin
// Default: stay in current state
next_state = state;
case (state)
S_NS_GREEN: begin
if (emergency) begin
next_state = S_EMERGENCY;
end else if (timer_done) begin
next_state = S_NS_YELLOW;
end
end
S_NS_YELLOW: begin
if (timer_done) begin
next_state = S_ALL_RED;
end
end
S_ALL_RED: begin
if (timer_done) begin
next_state = S_EW_GREEN;
end
end
S_EW_GREEN: begin
if (emergency) begin
next_state = S_EMERGENCY;
end else if (timer_done) begin
next_state = S_EW_YELLOW;
end
end
S_EW_YELLOW: begin
if (timer_done) begin
next_state = S_NS_GREEN;
end
end
S_EMERGENCY: begin
if (!emergency) begin
// Return to appropriate state
next_state = S_NS_GREEN;
end
end
default: next_state = S_NS_GREEN;
endcase
end
// ======================
// OUTPUT LOGIC
// ======================
always @(*) begin
// Default outputs
light_ns = `RED;
light_ew = `RED;
walk_ns = 0;
walk_ew = 0;
debug_out = 8'h00;
case (state)
S_NS_GREEN: begin
light_ns = `GREEN;
light_ew = `RED;
walk_ns = pedestrian_ns_reg;
walk_ew = 0;
debug_out = 8'b00000001;
// Clear pedestrian request if walk signal was given
if (pedestrian_ns_reg && timer < 100) begin
// Last 10 seconds of green
walk_ns = 1;
end
end
S_NS_YELLOW: begin
light_ns = `YELLOW;
light_ew = `RED;
debug_out = 8'b00000010;
end
S_ALL_RED: begin
light_ns = `RED;
light_ew = `RED;
debug_out = 8'b00000100;
end
S_EW_GREEN: begin
light_ns = `RED;
light_ew = `GREEN;
walk_ns = 0;
walk_ew = pedestrian_ew_reg;
debug_out = 8'b00001000;
if (pedestrian_ew_reg && timer < 60) begin
walk_ew = 1;
end
end
S_EW_YELLOW: begin
light_ns = `RED;
light_ew = `YELLOW;
debug_out = 8'b00010000;
end
S_EMERGENCY: begin
// Flash all yellows
light_ns = (timer[3]) ? `YELLOW : `RED; // Blink every 1.6s
light_ew = (timer[3]) ? `YELLOW : `RED;
debug_out = 8'b10000000;
end
endcase
// Clear pedestrian requests when walk signal was given
if (walk_ns) pedestrian_ns_reg <= 0;
if (walk_ew) pedestrian_ew_reg <= 0;
end
// ======================
// DEBUGGING OUTPUT
// ======================
always @(posedge clk) begin
if (state != next_state) begin
$display("[%0t] State change: %s -> %s",
$time, state_name(state), state_name(next_state));
end
end
// Helper function for state names (simulation only)
function string state_name(input [2:0] s);
case (s)
S_NS_GREEN: state_name = "NS_GREEN";
S_NS_YELLOW: state_name = "NS_YELLOW";
S_ALL_RED: state_name = "ALL_RED";
S_EW_GREEN: state_name = "EW_GREEN";
S_EW_YELLOW: state_name = "EW_YELLOW";
S_EMERGENCY: state_name = "EMERGENCY";
default: state_name = "UNKNOWN";
endcase
endfunction
endmodule
// ======================
// TESTBENCH
// ======================
module traffic_controller_tb;
// Inputs
reg clk;
reg reset;
reg emergency;
reg pedestrian_ns;
reg pedestrian_ew;
// Outputs
wire [1:0] light_ns;
wire [1:0] light_ew;
wire walk_ns;
wire walk_ew;
wire [7:0] debug_out;
// Instantiate the controller
traffic_controller #(
.GREEN_TIME_NS(50), // Shorter for simulation
.YELLOW_TIME(5),
.GREEN_TIME_EW(30),
.ALL_RED_TIME(2)
) dut (
.clk(clk),
.reset(reset),
.emergency(emergency),
.pedestrian_ns(pedestrian_ns),
.pedestrian_ew(pedestrian_ew),
.light_ns(light_ns),
.light_ew(light_ew),
.walk_ns(walk_ns),
.walk_ew(walk_ew),
.debug_out(debug_out)
);
// Clock generation (10Hz = 0.1s period)
initial begin
clk = 0;
forever #50 clk = ~clk; // 50ms half period
end
// Test sequence
initial begin
$display("=== Traffic Controller Test ===");
// Initialize
reset = 1;
emergency = 0;
pedestrian_ns = 0;
pedestrian_ew = 0;
#100;
// Release reset
reset = 0;
$display("[%0t] Reset released", $time);
// Test normal operation
#1000;
// Test pedestrian request
$display("[%0t] Pedestrian NS request", $time);
pedestrian_ns = 1;
#100;
pedestrian_ns = 0;
#500;
// Test emergency vehicle
$display("[%0t] Emergency vehicle detected", $time);
emergency = 1;
#500;
emergency = 0;
$display("[%0t] Emergency cleared", $time);
// Test pedestrian EW during emergency return
pedestrian_ew = 1;
#100;
pedestrian_ew = 0;
// Run for a while longer
#2000;
$display("[%0t] Test completed", $time);
$finish;
end
// Monitor important signals
initial begin
$monitor("[%0t] NS: %s, EW: %s, WALK_NS: %b, WALK_EW: %b, STATE: %s",
$time,
light_name(light_ns),
light_name(light_ew),
walk_ns,
walk_ew,
get_state_name(debug_out));
end
// Helper functions for display
function string light_name(input [1:0] light);
case (light)
`RED: light_name = "RED ";
`YELLOW: light_name = "YELLOW";
`GREEN: light_name = "GREEN ";
default: light_name = "ERROR ";
endcase
endfunction
function string get_state_name(input [7:0] debug);
case (debug)
8'b00000001: get_state_name = "NS_GREEN ";
8'b00000010: get_state_name = "NS_YELLOW";
8'b00000100: get_state_name = "ALL_RED ";
8'b00001000: get_state_name = "EW_GREEN ";
8'b00010000: get_state_name = "EW_YELLOW";
8'b10000000: get_state_name = "EMERGENCY";
default: get_state_name = "UNKNOWN ";
endcase
endfunction
endmodule
🎉 CONGRATULATIONS! You've Mastered Intermediate Verilog!
What You've Accomplished:
- ✅ System Tasks: Mastered
$stop,$display,$finish - ✅ Module I/O: Understand inputs, outputs, inouts
- ✅ Variables: Can explain
wirevsreglike a pro - ✅ Assignments: Know when to use
assignvs procedural - ✅ Bit Manipulation: Can slice and dice any bus
- ✅ Constants: Use
parameter,localparam, and`define - ✅ Conditionals: Hardware-style if/else/case
- ✅ Blocks: Proper use of
begin/end - ✅ Complete Project: Built a real traffic light controller!
Your Next Challenges:
- Learn SystemVerilog - The enhanced version with classes, interfaces
- Study FPGA Architecture - How your code maps to actual hardware
- Build a RISC-V CPU - The ultimate Verilog project!
- Learn Formal Verification - Prove your design is correct
- Contribute to OpenCores - Join real hardware projects
Remember These Golden Rules:
// 1. wire = connections, reg = storage (mostly)
// 2. assign = continuous, always = procedural
// 3. Use parameters for configurability
// 4. Always specify all cases in combinational logic
// 5. Think hardware, not software!
👋 Final Farewell & Challenge
You now have the knowledge to design REAL digital circuits! From traffic lights to CPUs, the world of hardware design is yours to explore.
YOUR MISSION: Take the traffic light controller and add:
- A night mode (flashing yellows)
- Traffic density sensors
- Priority for public transport
- Network connectivity for smart city integration
Share your enhanced design on GitHub and tag it #VerilogMaster!
Stay curious, keep designing, and remember: Every great chip starts with a single line of Verilog! 🚀
Questions? Want to show off your designs? Found a bug in the tutorial?
Join the conversation on GitHub or - let's build the future of hardware together!
Top comments (0)