Verilog HDL Concepts, Assignments, and Digital Circuit Implementation

1. Verilog Implementation of Digital Circuits

1a) 4-to-1 MUX Implementation (AND-OR-INVERTER Logic)

Illustrate and write the Verilog description and stimulus code for an AND-OR-INVERTER based 4-to-1 multiplexer.

Verilog Description

module mux4_to_1(out, i0, i1, i2, i3, s1, s0);
  output out;
  input i0, i1, i2, i3;
  input s1, s0;
  
  wire s1n, s0n;
  wire y0, y1, y2, y3;
  
  not (s1n, s1);
  not (s0n, s0);
  
  // AND gates for selection
  and (y0, i0, s1n, s0n); // Select 00
  and (y1, i1, s1n, s0);  // Select 01
  and (y2, i2, s1, s0n);  // Select 10
  and (y3, i3, s1, s0);   // Select 11
  
  // OR gate for output
  or (out, y0, y1, y2, y3);
endmodule

Stimulus Code (Testbench)

module stimulus;
  reg I0, I1, I2, I3;
  reg s1, s0;
  wire OUTPUT;
  
  // Instantiate the MUX
  mux4_to_1 mymux(OUTPUT, I0, I1, I2, I3, s1, s0);
  
  initial
  begin
    // Initialize inputs
    I0 = 1'b1; I1 = 1'b0; I2 = 1'b1; I3 = 1'b0;
    
    #1 $display ("I0 = %b, I1 = %b, I2 = %b, I3 = %b", I0, I1, I2, I3);
    
    // Case 1: s1=0, s0=0 (Select I0)
    s1 = 0; s0 = 0;
    #1 $display ("s1 = %b, s0 = %b, OUTPUT = %b\n", s1, s0, OUTPUT);
    
    // Case 2: s1=0, s0=1 (Select I1)
    s1 = 0; s0 = 1;
    #1 $display ("s1 = %b, s0 = %b, OUTPUT = %b\n", s1, s0, OUTPUT);
    
    // Case 3: s1=1, s0=0 (Select I2)
    s1 = 1; s0 = 0;
    #1 $display ("s1 = %b, s0 = %b, OUTPUT = %b\n", s1, s0, OUTPUT);
    
    // Case 4: s1=1, s0=1 (Select I3)
    s1 = 1; s0 = 1;
    #1 $display ("s1 = %b, s0 = %b, OUTPUT = %b\n", s1, s0, OUTPUT);
    
    #1 $finish;
  end
endmodule

1b) Verilog Assignments and Procedural Blocks

(i) Blocking and Non-Blocking Assignments

Blocking Assignments (`=`)

Blocking assignment statements are executed in the order they are specified in a sequential block. They block the execution of the next statement until the current assignment is complete.

reg x, y, z;
reg [15:0] reg_a, reg_b;
integer count;

initial
begin
  x = 0; y = 1; z = 1;
  count = 0;
  reg_a = 16'b0; reg_b = reg_a;
  
  // These assignments execute sequentially
  #15 reg_a[2] = 1'b1; // Blocks execution for 15 time units
  #10 reg_b[15:13] = {x, y, z}; // Blocks execution for 10 time units
  count = count + 1; // Executes only after the previous #10 delay
end
Non-Blocking Assignments (`<=`)

Non-blocking assignments allow scheduling of assignments without blocking the execution of the statements that follow in a sequential block. They are typically used for modeling synchronous sequential logic.

reg x, y, z;
reg [15:0] reg_a, reg_b;
integer count;

initial
begin
  x = 0; y = 1; z = 1;
  count = 0;
  reg_a = 16'b0; reg_b = reg_a;
  
  // These assignments are scheduled to happen concurrently at future times
  reg_a[2] <= #15 1'b1;
  reg_b[15:13] <= #10 {x, y, z};
  count <= count + 1; // Scheduled immediately, without waiting for #15 or #10
end

(ii) Sequential and Parallel Blocks with Examples

Sequential Block (`begin` and `end`)

The keywords begin and end are used to group statements into a sequential block. Statements within this block execute one after the other.

Sequential without Delays
reg x, y; reg [1:0] z, w;
initial
begin
  x = 1'b0;
  y = 1'b1;
  z = {x, y}; // Executes after y = 1'b1
  w = {y, x}; // Executes after z = {x, y}
end
Sequential with Delay
reg x, y; reg [1:0] z, w;
initial
begin
  x = 1'b0;
  #5 y = 1'b1; // Waits 5 units after x=0
  #10 z = {x, y}; // Waits 10 units after y=1
  #20 w = {y, x}; // Waits 20 units after z={x,y}
end
Parallel Block (`fork` and `join`)

Parallel blocks, specified by keywords fork and join, provide interesting simulation features by allowing statements to execute concurrently. The block finishes when the longest running statement completes.

Parallel with Delay
reg x, y;
reg [1:0] z, w;
initial
fork
  x = 1'b0; // Executes immediately
  #5 y = 1'b1; // Executes after 5 units
  #10 z = {x, y}; // Executes after 10 units
  #20 w = {y, x}; // Executes after 20 units
join // Block finishes at time 20
Parallel with Deliberate Race Condition
reg x, y;
reg [1:0] z, w;
initial
fork
  x = 1'b0;
  y = 1'b1;
  z = {x, y}; // Reads x and y immediately
  w = {y, x}; // Reads y and x immediately
join // All statements execute concurrently at time 0

2. Verilog Tasks, Functions, and MUX Implementation

2a) Comparison of Verilog Tasks and Functions

The key differences between Verilog tasks and functions are:

FeatureTasksFunctions
InvocationCan enable other tasks and functions.Can enable other functions, but not other tasks.
TimingMay contain delay, event, or timing control statements.Must execute in zero simulation time; must not contain any delay or timing control statements.
ArgumentsMay have 0 or more arguments of type input, output, or inout.Must have at least one input argument. Cannot have output or inout arguments.
Return ValueDo not return a value, but can pass multiple values through output and inout arguments.Always return a single value (the function name acts as the return register).

2b) Verilog Code for 8-to-1 MUX using Case Statements

module mux8_1(S, I, Y);
  input [2:0] S; // Select lines
  input [7:0] I; // Data inputs
  output Y;
  reg Y;
  
  always @(S or I)
  begin
    case(S)
      3'b000: Y = I[0];
      3'b001: Y = I[1];
      3'b010: Y = I[2];
      3'b011: Y = I[3];
      3'b100: Y = I[4];
      3'b101: Y = I[5];
      3'b110: Y = I[6];
      3'b111: Y = I[7];
      default: Y = 1'bx; // Handle undefined states
    endcase
  end
endmodule

3. Advanced Verilog Constructs and Arithmetic Circuits

3a) Explanation of Verilog Procedural Statements

1. If-Else Statement

Used for conditional decision making, similar to the if statement in C. If the condition is true, statement1 executes; otherwise, statement2 executes.

Syntax:

if (condition)
    statement1;
else
    statement2;

Example:

always @(a or b) begin
    if (a > b)
        max = a;
    else
        max = b;
end

2. For Loop

Used to repeat operations a fixed number of times. Primarily used for looping through arrays/registers in testbenches or generating repetitive logic (when used with generate).

Syntax:

for (initialization; condition; update)
    statement;

Example:

reg [7:0] mem [0:15]; // 16-byte memory
integer i;

always @(*) begin
    for (i = 0; i < 16; i = i + 1)
        mem[i] = 8'h00; // Initialize memory
end

3. While Loop

Used for conditional looping where the number of iterations is not fixed at compile time. This construct is generally non-synthesizable if used with unknown iteration bounds.

Syntax:

while (condition)
    statement;

Example:

integer i;
initial begin
    i = 0;
    while (i < 5) begin
        $display("i = %d", i);
        i = i + 1;
    end
end

4. CaseX Statement

The case statement is used for multi-way branching, similar to switch in C. caseX treats ‘x’ and ‘z’ values in the case expression as don’t cares, making it useful for decoding logic or FSMs where certain inputs are irrelevant.

Syntax:

case (expression)
    value1: statement1;
    value2: statement2;
    default: default_statement;
endcase

Example:

always @(sel or a or b) begin
    case (sel)
        2'b00: y = 0;
        2'b01: y = a;
        2'b10: y = b;
        default: y = 8'hFF;
    endcase
end

3b) 4-bit Full Adder with Carry Look-Ahead Logic (CLA)

module fulladder(sum, c_out, a, b, c_in);
    output [3:0] sum;
    output c_out;
    input  [3:0] a, b;
    input  c_in;

    wire p0, g0, p1, g1, p2, g2, p3, g3;
    wire c1, c2, c3, c4;

    // Generate (G) and Propagate (P) signals for each bit
    assign p0 = a[0] ^ b[0];    assign g0 = a[0] & b[0];
    assign p1 = a[1] ^ b[1];    assign g1 = a[1] & b[1];
    assign p2 = a[2] ^ b[2];    assign g2 = a[2] & b[2];
    assign p3 = a[3] ^ b[3];    assign g3 = a[3] & b[3];

    // Carry generation using CLA logic
    assign c1 = g0 | (p0 & c_in);
    assign c2 = g1 | (p1 & g0) | (p1 & p0 & c_in);
    assign c3 = g2 | (p2 & g1) | (p2 & p1 & g0) | (p2 & p1 & p0 & c_in);
    assign c4 = g3 | (p3 & g2) | (p3 & p2 & g1) | (p3 & p2 & p1 & g0) | (p3 & p2 & p1 & p0 & c_in);

    // Sum outputs
    assign sum[0] = p0 ^ c_in;
    assign sum[1] = p1 ^ c1;
    assign sum[2] = p2 ^ c2;
    assign sum[3] = p3 ^ c3;

    // Final carry out
    assign c_out = c4;
endmodule

4. Counters and Structural Modeling

4a) 4-bit Ripple Counter and Stimulus Code

4-bit Synchronous Counter Code (Often Misnamed Ripple)

module ripple_counter (
    input clk,
    input reset,
    output [3:0] q
);

    reg [3:0] q_reg;

    assign q = q_reg;

    // Note: This implementation is synchronous, as all flip-flops share the same clock.
    always @(posedge clk or posedge reset) begin
        if (reset)
            q_reg <= 4'b0000;
        else
            q_reg <= q_reg + 1;
    end
endmodule

Stimulus Code (Testbench)

module tb_ripple_counter;

    reg clk;
    reg reset;
    wire [3:0] q;

    // Instantiate the counter
    ripple_counter uut (
        .clk(clk),
        .reset(reset),
        .q(q)
    );

    // Clock generation: toggle every 5 time units
    initial begin
        clk = 0;
        forever #5 clk = ~clk;
    end

    // Stimulus
    initial begin
        $display("Time\tReset\tQ");
        $monitor("%0t\t%b\t%04b", $time, reset, q);

        reset = 1;  // Apply reset
        #10;
        reset = 0;  // Release reset

        #100;       // Run for 100 time units
        $finish;
    end

endmodule

4b) Structural Description and Testbench for Logic Gate Diagram

(Assuming the diagram represents an AND gate followed by an OR gate: OUT = (A & B) | C)

Verilog Code

module D(out, a, b, c);
  output out;
  input a, b, c;
  wire e;
  
  // AND gate with 5 unit delay
  and #(5) a1(e, a, b);
  
  // OR gate with 4 unit delay
  or #(4) o1(out, e, c);
endmodule

Stimulus Code

module stimulus;
  reg A, B, C;
  wire OUT;
  
  // Instantiate the module
  D d1(OUT, A, B, C);
  
  initial begin
    // Initial state: A=0, B=0, C=0. OUT should be 0 (after delays).
    A = 1'b0; B = 1'b0; C = 1'b0;
    
    // Change 1: A=1, B=1, C=1. OUT should become 1 (after 5+4=9 units).
    #10 A = 1'b1; B = 1'b1; C = 1'b1;
    
    // Change 2: A=1, B=0, C=0. OUT should become 0 (after 5+4=9 units).
    #10 A = 1'b1; B = 1'b0; C = 1'b0;
    
    #20 $finish;
  end
endmodule

(Note: The simulation waveform cannot be generated in this text format.)

5. Verilog Delay Types and Structural Implementations

5a) Explanation of Verilog Delay Types

1) Rise, Fall, and Turn-off Delays

In Verilog, you can specify different delay values for rise (0 to 1), fall (1 to 0), and turn-off (X/Z transitions) transitions of signals to simulate real hardware behavior more accurately.

Syntax:

#(rise_delay, fall_delay, turnoff_delay)

Example:

module delays_demo;
  reg a;
  wire b;

  // Buffer with rise=2, fall=4, turnoff=6 units
  buf #(2, 4, 6) (b, a);

  initial begin
    a = 0;
    #5 a = 1;    // Rise delay of 2 will apply to b
    #5 a = 0;    // Fall delay of 4 will apply to b
    #5 a = 1'bz; // Turn-off delay of 6 will apply to b
  end
endmodule

2) Assignment Delay, Implicit Assignment Delay, and Net Declaration Delay

These methods introduce delays into signal assignments for simulation purposes.

a) Assignment Delay (Procedural)

This delay is specified when assigning a value to a signal using blocking (`=`) or non-blocking (`<=`) assignments in procedural blocks (like always or initial).

Example:

always @(posedge clk)
    q <= #5 d; // Non-blocking assignment delay of 5 time units
b) Implicit Assignment Delay (Continuous)

This occurs in continuous assignments (assign) where the delay is specified before the expression.

Example:

assign #3 y = a & b; // Output y updates 3 time units after a or b changes
c) Net Declaration Delay

Delay can also be specified at the time of declaring a wire or net, applying a delay to any value driven onto that net.

Example:

wire #4 delayed_signal = original_signal;

5b) Structural Verilog Implementations

2-to-4 Decoder with Tri-State Output

module decoder_2_4(D, E, I);
  output [3:0] D; // Decoder outputs
  input E;        // Enable signal (Active High)
  input [1:0] I;  // Select inputs
  
  wire [1:0] Ibar;
  wire [3:0] s; // Intermediate decoded signals
  
  // Inverters for select lines
  not N1 (Ibar[1], I[1]);
  not N2 (Ibar[0], I[0]);
  
  // AND gates for decoding (s[i] = minterm i)
  and A1 (s[0], Ibar[1], Ibar[0]); // 00
  and A2 (s[1], Ibar[1], I[0]);    // 01
  and A3 (s[2], I[1], Ibar[0]);    // 10 (Fixed typo in original logic)
  and A4 (s[3], I[1], I[0]);      // 11
  
  // Tri-state buffers (bufif1: output is high-Z if control E is low)
  bufif1 B1 (D[0], s[0], E);
  bufif1 B2 (D[1], s[1], E); // Added missing connection D[1]
  bufif1 B3 (D[2], s[2], E);
  bufif1 B4 (D[3], s[3], E);
endmodule

Full Adder using Two Half Adders

module full_adder(sum, carry, A, B, Cin);
  output sum, carry;
  input A, B, Cin;
  wire S0, X0, X1;
  
  // HA1: Inputs B, Cin. Outputs: Sum (S0), Carry (X0)
  half_adder HA1(S0, X0, B, Cin);
  
  // HA2: Inputs A, S0. Outputs: Sum (sum), Carry (X1)
  half_adder HA2(sum, X1, A, S0);
  
  // Final Carry: OR of carries from HA1 and HA2
  or O1 (carry, X1, X0);
endmodule

module half_adder(P, Q, X, Y);
  output P, Q; // P=XOR (Sum), Q=AND (Carry)
  input X, Y;
  
  xor X1(P, X, Y);
  and A1(Q, X, Y);
endmodule

6. Tasks and Bitwise Operations

6a) Task Declaration and Invocation Example

A task in Verilog is used to group a sequence of statements that can be reused. Unlike functions, tasks can contain delay, timing controls, and have multiple outputs.

module task_example;
  reg [3:0] a, b;
  reg [4:0] temp; // Register to hold the task output

  // Declare the task
  task add_and_display;
    input [3:0] x, y;
    output [4:0] sum;
    begin
      sum = x + y;
      $display("At time %0t: %0d + %0d = %0d", $time, x, y, sum);
    end
  endtask

  initial begin
    a = 4; b = 5;
    // Task invocation: arguments are passed by position
    add_and_display(a, b, temp);
    #10;

    a = 10; b = 3;
    // Task invocation again
    add_and_display(a, b, temp);
    #10;

    $finish;
  end
endmodule

6b) Verilog Bitwise and Reduction Operations

Given A = 4’b1101 and B = 4’b0101:

  1. &A (Reduction AND of A)

    A = 1 & 1 & 0 & 1 = 0

  2. A << 2 (Left shift A by 2)

    1101 << 2 = 110100. Since A is 4 bits, the result is truncated/padded to 4 bits: 4’b0100

  3. {2{B}} (Concatenate two copies of B)

    B = 0101. Result: 8’b01010101

  4. A ^ B (Bitwise XOR)

    1101 ^ 0101 = 4’b1000

  5. A | B (Bitwise OR)

    1101 | 0101 = 4’b1101