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

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

// 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
``````