HardwareTeams.com - The #1 job board and blog for electrical and computer engineers


Introduction to SystemVerilog by example - building an ALU #

Arithmetic Logic Unit #

In this tutorial we will build an ALU module

            +-------------------------------+
operandA -->|                               |
            |           Arithmetic          |
operandB -->|           Logic Unit          |--> result
opcode   -->|                               |
            +-------------------------------+
                 |                    |
                 |                    |
                zero                carry

Requirements #

operandA, operandB, and opcode are input signals to the ALU module. The module performs various arithmetic and logical operations based on the opcode input and provides the result as an output. Additionally, the ALU module outputs the zero and carry signals to indicate the corresponding conditions.

The ALU performs the following operations:

Opcode Operation Operator
000 Addition +
001 Subtraction -
010 Bitwise AND &
011 Bitwise OR |
100 Bitwise XOR ^
101 Left Shift «
110 Right Shift »
Default Zero Result

The ALU also includes additional logic to detect zero and carry

Writing the Module #

module ALU #(parameter DATA_WIDTH = 8)
       (input [DATA_WIDTH-1:0] operandA,
        input [DATA_WIDTH-1:0] operandB,
        input [2:0] opcode,
        output [DATA_WIDTH-1:0] result,
        output zero,
        output carry);

    // Internal wire declarations
    reg  [DATA_WIDTH-1:0] result_t; 
    wire [DATA_WIDTH:0] sum;    // Stores the result_t of addition
    wire [DATA_WIDTH-1:0] difference;    // Stores the result_t of subtraction
    wire [DATA_WIDTH-1:0] bitwiseAnd;    // Stores the result_t of bitwise AND
    wire [DATA_WIDTH-1:0] bitwiseOr;     // Stores the result_t of bitwise OR
    wire [DATA_WIDTH-1:0] bitwiseXor;    // Stores the result_t of bitwise XOR
    wire [DATA_WIDTH-1:0] shiftLeft;     // Stores the result_t of left shift
    wire [DATA_WIDTH-1:0] shiftRight;    // Stores the result_t of right shift

    // Adder
    assign sum = operandA + operandB;

    // Subtractor
    assign difference = operandA - operandB;

    // Bitwise AND
    assign bitwiseAnd = operandA & operandB;

    // Bitwise OR
    assign bitwiseOr = operandA | operandB;

    // Bitwise XOR
    assign bitwiseXor = operandA ^ operandB;

    // Left Shift
    assign shiftLeft = operandA << operandB;

    // Right Shift
    assign shiftRight = operandA >> operandB;


    // result_t selection based on opcode
    always @(*)
    begin
        case (opcode)
            3'b000: result_t = sum;         // Addition
            3'b001: result_t = difference;  // Subtraction
            3'b010: result_t = bitwiseAnd;  // Bitwise AND
            3'b011: result_t = bitwiseOr;   // Bitwise OR
            3'b100: result_t = bitwiseXor;  // Bitwise XOR
            3'b101: result_t = shiftLeft;   // Left Shift
            3'b110: result_t = shiftRight;  // Right Shift
            default: result_t = 0;          // Default case (no operation)
        endcase
    end

    // Zero detection
    assign zero = (result_t == 0);

    // Carry detection
    assign carry = (sum[DATA_WIDTH] != 0);

    assign result = result_t;
endmodule

How Carry Works #

The carry logic determines whether a carry is generated during the addition operation.

  • It is implemented using the expression carry = (sum[DATA_WIDTH] != 0);.
  • sum[DATA_WIDTH] represents the most significant bit (MSB) of the sum signal, which is the result of the addition operation.
  • If the MSB of sum is non-zero, it indicates that a carry has occurred, and carry is set to 1. Otherwise, carry is set to 0.

always @* #

The always @* infers **combinatorial logic** in SystemVerilog.

The always @* block is typically used for continuous assignments, where the output signals are assigned values based on the current values of the input signals. The block automatically re-evaluates and updates the output whenever any input signal within the always block changes, ensuring that the output stays synchronized with the inputs.

SystemVerilog parameter #

In SystemVerilog, the parameter concept allows you to define and use constant values that can be used throughout your design. Parameters provide a way to configure and customize your modules without modifying the source code. They are commonly used to define configurable sizes, thresholds, or any other constant values that may vary between different instances of a module.

Here’s how the parameter concept works in SystemVerilog:

  1. Declaration:

    • Parameters are declared within a module or a module instance declaration.
    • The syntax to declare a parameter is parameter <type> <name> = <value>;.
    • <type> can be any SystemVerilog data type (such as integer, bit, reg, or custom types).
    • <name> is the identifier for the parameter.
    • <value> is the constant value assigned to the parameter.
  2. Usage:

    • Parameters can be used within the module to define signal sizes, constants, or control logic.
    • To use a parameter, you can reference it by its name within the module code.
    • Parameters can be used in expressions, assignments, conditional statements, or wherever a constant value is required.
    • Parameters can be used for defining array sizes, data widths, threshold values, control signals, or any other constant values within your design.
  3. Configuration:

    • When instantiating a module, you can provide specific parameter values to customize its behavior.
    • The syntax for configuring a parameter during module instantiation is #(.param_name(value)).
    • You can assign a value directly or use another parameter or constant as the value.
    • By configuring parameters during instantiation, you can have different instances of the same module with different parameter values.

Benefits of using parameters:

  • Flexibility: Parameters allow you to easily customize your modules without modifying the source code, enabling greater reusability.
  • Readability: Parameters make the code more readable and self-explanatory by providing meaningful names for constant values.
  • Simplicity: Parameters simplify the modification and configuration of module instances, as you can change their behavior by simply updating the parameter values during instantiation.
  • Code consistency: Parameters ensure that the same constant values are used consistently throughout your design, reducing the chances of errors caused by mismatched values.

TestBench #

The following testbench can be run by saving the above ALU and below testbench and running them in iverilog:

  1. alu.sv
  2. test_alu.sv
  3. iverilog -o test_alu alu.sv tb_alu.sv
  4. vvp test_alu
module ALU_Testbench;
    // Parameters
    parameter DATA_WIDTH = 8;
    
    // Inputs
    reg [DATA_WIDTH-1:0] operandA;
    reg [DATA_WIDTH-1:0] operandB;
    reg [2:0] opcode;
    
    // Outputs
    wire [DATA_WIDTH-1:0] result;
    wire zero;
    wire carry;
    wire overflow;
    
    // Instantiate the ALU module
    ALU #(DATA_WIDTH) dut (
        .operandA(operandA),
        .operandB(operandB),
        .opcode(opcode),
        .result(result),
        .zero(zero),
        .carry(carry)
    );
    
    // Clock generation
    reg clk;
    always #5 clk = ~clk;
    
    // Testbench stimulus
    initial begin
        // Initialize inputs
        operandA = 8'hAB;   // Example value
        operandB = 8'hCD;   // Example value
        opcode = 3'b000;    // Addition
        
        // Apply stimulus for 10 clock cycles
        repeat (10) begin
            #10;   // Wait for a rising edge of the clock
            
            // Display inputs and expected output
            $display("operandA = %h, operandB = %h, opcode = %b", operandA, operandB, opcode);
            case (opcode)
                3'b000: $display("Expetced Result: %h", operandA + operandB);       // Addition
                3'b001: $display("Expetced Result: %h", operandA - operandB);       // Subtraction
                3'b010: $display("Expetced Result: %h", operandA & operandB);       // Bitwise AND
                3'b011: $display("Expetced Result: %h", operandA | operandB);       // Bitwise OR
                3'b100: $display("Expetced Result: %h", operandA ^ operandB);       // Bitwise XOR
                3'b101: $display("Expetced Result: %h", operandA << operandB);      // Left Shift
                3'b110: $display("Expetced Result: %h", operandA >> operandB);      // Right Shift
                default: $display("Expetced Result zero"); // Default case (no operation)
            endcase
            // Display actual output
            $display("Actual Result: %h", result);
            
            // Update inputs for the next iteration
            operandA = operandA + 1;
            operandB = operandB + 1;
            opcode = opcode + 1;
        end
        
        // End simulation
        $finish;
    end
    
endmodule

Results #

operandA = ab, operandB = cd, opcode = 000
Expetced Result: 78
Actual Result: 78

operandA = ac, operandB = ce, opcode = 001
Expetced Result: de
Actual Result: de

operandA = ad, operandB = cf, opcode = 010
Expetced Result: 8d
Actual Result: 8d

operandA = ae, operandB = d0, opcode = 011
Expetced Result: fe
Actual Result: fe

operandA = af, operandB = d1, opcode = 100
Expetced Result: 7e
Actual Result: 7e

operandA = b0, operandB = d2, opcode = 101
Expetced Result: 00
Actual Result: 00

operandA = b1, operandB = d3, opcode = 110
Expetced Result: 00
Actual Result: 00

operandA = b2, operandB = d4, opcode = 111
Expetced Result zero
Actual Result: 00
HardwareTeams.com Copyright © 2024
comments powered by Disqus