Hello and welcome to Part 5 of my Beginning Logic Design series. In this post I will begin building an 8-bit ALU (Arithmetic Logic Unit). Through building this ALU I will cover a few more topics on writing SystemVerilog and using Vivado to simulate the designs. As you learn more SystemVerilog, I suggest keeping this cheat sheet handy as a quick reference to notes regarding language syntax.
ALU Model
An ALU is a very common component in various processing systems. The common ALU design takes an input opcode (operation code) to select one of various math or logic operations. Based on the operation selected, one or more inputs will be taken and the result of the operation will be output. ALUs may also read and write status signals based on the operation being performed.
The ALU I’ll be designing in this post will be influenced by the classic 6502 processor that’s at the heart of the NES, Apple II and many other early computer systems. This ALU with support 12 operations:
- Arithmetic Operations
- Add with Carry
- Subtract with Carry
- Increment
- Decrement
- Logic Operations
- AND
- OR
- XOR
- NOT/Negate (not in 6502, but I want my ALU to have it!)
- Shifting Operations
- Arithmetic Shift Left
- Logical Shift Right
- Rotate Left
- Rotate Right
This ALU will also have a few status signals, on the input side it will just have carry in
, on the output side we’ll have carry out
, zero
, sign
and overflow
. I’ll dive a bit more into each of these operations and how the interact with status signals as we implement them.
Building Modules
To get started I’ll fire up Vivado and make a new project that I’ll name my-alu
. I’ll choose RTL Project
for the Project Type and will leave Do not specify sources at this time
unchecked to make a couple files right away.
In the Add Sources page I’ll use the Create File
button to make two SystemVerilog
files: top
and alu
. It’s easy to accidentally leave the file type on Verilog
and the syntax for the older Verilog standard is a little different, so be sure to choose SystemVerilog.
For the Add Contraints page I will just hit next. As before for the Default Part page, I’ll select the xc7a35ticsg324-1L
from the Search drop down to set as the default. After hitting finish I’ll hit OK to skip the module definition prompts.
I’ll then clean up my top.sv
to look like this:
- `timescale 1ns / 1ps
- module top ();
- endmodule
And my alu.sv
as:
- `timescale 1ns / 1ps
- module alu ();
- endmodule
My top
module will help me test my ALU, and the alu
module will eventually be my fully built ALU.
I want this ALU to work synchronously, so I will add an input clock. I’ll also add 2 8-bit inputs and 1 8-bit output to the alu
module. To define an 8 bit logic
variables, I’ll use the syntax [n:0]
to give it a width of n+1
bits, in this case 7
to give me 8
bits.
- `timescale 1ns / 1ps
- module alu (
- input logic clock,
- input logic [7:0] a,
- input logic [7:0] b,
- output logic [7:0] y
- );
- endmodule
With those inputs and outputs defined, I’ll start to add the alu
to the top
module.
I’ll first define a few internal logic
variables to use to connect to the inputs and outputs of the ALU. Then, to instantiate the ALU, I’ll reference the module name alu
then name my instance myALU
and in parenthesis, in the same order as defined in the alu.sv
file, I’ll provide my variables to use as inputs and outputs.
- `timescale 1ns / 1ps
- module top ();
- logic clock;
- logic [7:0] a;
- logic [7:0] b;
- logic [7:0] y;
- alu myALU (
- clock,
- a,
- b,
- y
- );
- endmodule
Now myALU
exists within my top
module, and I have hooked up some inputs and outputs to it.
Setting the Clock
To set the clock, I am going to use some SystemVerilog syntax that is non-synthesizable. This means that actual hardware designs cannot be made from it, it exists in the language specifically for simulation and testing.
I’ll use an initial
block to set all my input variables to 0 at the start of simulation.
- initial begin
- clock = 0;
- a = 0;
- b = 0;
- end
Then I’ll add an always
block that will repeatedly loop during simulation to invert my clock
variable. The #<t>
syntax adds a delay of t
steps before the statement is executed.
- always begin
- #1 clock = ~clock;
- end
In this case, I’ve used #1
and with the timescale
of 1ns / 1ps
this statement will be delayed by 1ns
in simulation.
Here’s how this leaves my top.sv
file
- `timescale 1ns / 1ps
- module top ();
- logic clock;
- logic [7:0] a;
- logic [7:0] b;
- logic [7:0] y;
- alu myALU (
- clock,
- a,
- b,
- y
- );
- initial begin
- clock = 0;
- a = 0;
- b = 0;
- end
- always begin
- #1 clock = ~clock;
- end
- endmodule
I now want to Run Simulation
to verify my clock is toggling on and off as I expect it to. Before I do that though, the simulator in Vivado by default runs for 1000 ns
, which is relatively long for this design. I’d like to reduce that to 20 ns
for now.
I’ll go to Settings
in the PROJECT MANAGER
portion of the Flow Navigator
on the left side of Vivado. Then within Project Settings
I’ll select Simulation
, open the Simulation
tab and set xsim.simulate.runtime
to 20 ns
.
I’ll save my changes there and use Run Simulation->Run Behavioral Simulation
to fire off the sim. After hitting the zoom fit icon I can validate it does flip every nanosecond.
Enumerations in SystemVerilog
One of the advantages of using SystemVerilog instead of plain ol’ Verilog is being able to define enumerations. For this ALU, I need to create an input that can select one of the 12 operations I plan this ALU to support. I’ll use unique binary numbers to represent each operation in hardware and can use an enumeration to reference each operation in code by a more readable name.
I’ll also use typedef
as you would in a language like C to use variables that should be set to a member within my enumeration. I’ll add logic [3:0]
after enum
to ensure the new type only uses 4 bits.
- typedef enum logic [3:0] {
- ADD,
- SUBTRACT,
- INCREMENT,
- DECREMENT,
- BIT_AND,
- BIT_OR,
- BIT_XOR,
- BIT_NOT,
- SHIFT_LEFT,
- SHIFT_RIGHT,
- ROTATE_LEFT,
- ROTATE_RIGHT
- } opcode;
With this enum
defined, I’ll add input opcode operation
to my alu
module’s list of ports.
- module alu (
- input logic clock,
- input opcode operation,
- input logic [7:0] a,
- input logic [7:0] b,
- output logic [7:0] y
- );
- endmodule
Now, within the alu
module definition, I’ll use an always_ff
block to implement synchronized behavior. I’ll use @ (posedge clock)
to indicate that I want this block of behavior to occur on every positive edge
(rise) of my clock
signal.
Within that always_ff
block, I will use case
to vary the behavior based on what operation
is set to, for now I’ll only add the logic for ADD.
- always_ff @ (posedge clock) begin
- case (operation)
- ADD: begin
- y <= a + b;
- end
- endcase
- end
In this situation, I use <=
instead of =
to store this within a register, which is like a D flip-flop with multiple bits, that will have its output wired to y
. This keeps my output synchronized with my input.
I’m just about ready to test the addition operation. In order to reference the opcode
type and enumeration within the top
module, I need to package
that up so I can use the import
statement to refer to it.
I’ll wrap my typedef in package ALU;
… endpackage
to encapsulate it in a package named ALU
. Then I will use import ALU::*;
to import all of it for use in my alu
module.
Here’s how this leaves my afu.sv
- `timescale 1ns / 1ps
- package ALU;
- typedef enum logic [3:0] {
- ADD,
- SUBTRACT,
- INCREMENT,
- DECREMENT,
- BIT_AND,
- BIT_OR,
- BIT_XOR,
- BIT_NOT,
- SHIFT_LEFT,
- SHIFT_RIGHT,
- ROTATE_LEFT,
- ROTATE_RIGHT
- } opcode;
- endpackage
- import ALU::*;
- module alu (
- input logic clock,
- input opcode operation,
- input logic [7:0] a,
- input logic [7:0] b,
- output logic [7:0] y
- );
- always_ff @ (posedge clock) begin
- case (operation)
- ADD: begin
- y <= a + b;
- end
- endcase
- end
- endmodule
For my top
module; I’ll also add the import
line, add an internal variable for operation
and provide it to myAFU
. I’ll set operation
to ADD
within my initial
block and add a few more delayed operations to change a
and b
at different times within the simulation.
Let’s see how this works out.
I have an easier time verifying the math when the numbers are in decimal format. I can select multiple signals in the wave viewer by the first and holding shift and clicking the last, then right click and go to Radix->Unsigned Decimal
to change the number format shown.
As you can verify in the simulation, on every rising clock edge the y
output is updated to show the result of the addition of a + b
!
With a lot of the foundation set for the ALU I’ll end the post here. In the next post I’ll finish implementing the ADD operation by adding status signal handling to help identify some special conditions that may result from the operation and how to setup better testing to validate the design. As always, if you have any questions or feedback please leave a note in the comments. Keep tinkering!