DEV Community

Javad
Javad

Posted on

Embedded Systems & IoT: The deep dive into Verilog and HDL languages

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 of assign
  • Bit manipulation - Controlling every single wire
  • Constants & Macros - localparam and define
  • Conditional logic - Hardware-style if/else
  • Blocking vs Non-blocking - The begin/end magic
  • 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
Enter fullscreen mode Exit fullscreen mode

🚪 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
Enter fullscreen mode Exit fullscreen mode

🔄 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

🎯 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
Enter fullscreen mode Exit fullscreen mode

🔢 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
Enter fullscreen mode Exit fullscreen mode

🔧 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
Enter fullscreen mode Exit fullscreen mode

🎩 `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
Enter fullscreen mode Exit fullscreen mode

🎭 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
Enter fullscreen mode Exit fullscreen mode

🧱 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
Enter fullscreen mode Exit fullscreen mode

🎮 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
Enter fullscreen mode Exit fullscreen mode

🎉 CONGRATULATIONS! You've Mastered Intermediate Verilog!

What You've Accomplished:

  1. System Tasks: Mastered $stop, $display, $finish
  2. Module I/O: Understand inputs, outputs, inouts
  3. Variables: Can explain wire vs reg like a pro
  4. Assignments: Know when to use assign vs procedural
  5. Bit Manipulation: Can slice and dice any bus
  6. Constants: Use parameter, localparam, and `define
  7. Conditionals: Hardware-style if/else/case
  8. Blocks: Proper use of begin/end
  9. Complete Project: Built a real traffic light controller!

Your Next Challenges:

  1. Learn SystemVerilog - The enhanced version with classes, interfaces
  2. Study FPGA Architecture - How your code maps to actual hardware
  3. Build a RISC-V CPU - The ultimate Verilog project!
  4. Learn Formal Verification - Prove your design is correct
  5. 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!
Enter fullscreen mode Exit fullscreen mode

👋 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:

  1. A night mode (flashing yellows)
  2. Traffic density sensors
  3. Priority for public transport
  4. 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)