Beginning Logic Design – Part 5

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.

ALU block symbol from Wikipedia

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:

  1. `timescale 1ns / 1ps
  2. module top ();
  3. endmodule
`timescale 1ns / 1ps

module top ();
endmodule

And my alu.sv as:

  1. `timescale 1ns / 1ps
  2. module alu ();
  3. endmodule
`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.

  1. `timescale 1ns / 1ps
  2. module alu (
  3. input logic clock,
  4. input logic [7:0] a,
  5. input logic [7:0] b,
  6. output logic [7:0] y
  7. );
  8. endmodule
`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.

  1. `timescale 1ns / 1ps
  2. module top ();
  3. logic clock;
  4. logic [7:0] a;
  5. logic [7:0] b;
  6. logic [7:0] y;
  7. alu myALU (
  8. clock,
  9. a,
  10. b,
  11. y
  12. );
  13. endmodule
`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.

  1. initial begin
  2. clock = 0;
  3. a = 0;
  4. b = 0;
  5. end
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.

  1. always begin
  2. #1 clock = ~clock;
  3. end
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

  1. `timescale 1ns / 1ps
  2. module top ();
  3. logic clock;
  4. logic [7:0] a;
  5. logic [7:0] b;
  6. logic [7:0] y;
  7. alu myALU (
  8. clock,
  9. a,
  10. b,
  11. y
  12. );
  13. initial begin
  14. clock = 0;
  15. a = 0;
  16. b = 0;
  17. end
  18. always begin
  19. #1 clock = ~clock;
  20. end
  21. endmodule
`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.

  1. typedef enum logic [3:0] {
  2. ADD,
  3. SUBTRACT,
  4. INCREMENT,
  5. DECREMENT,
  6. BIT_AND,
  7. BIT_OR,
  8. BIT_XOR,
  9. BIT_NOT,
  10. SHIFT_LEFT,
  11. SHIFT_RIGHT,
  12. ROTATE_LEFT,
  13. ROTATE_RIGHT
  14. } opcode;
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.

  1. module alu (
  2. input logic clock,
  3. input opcode operation,
  4. input logic [7:0] a,
  5. input logic [7:0] b,
  6. output logic [7:0] y
  7. );
  8. endmodule
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.

  1. always_ff @ (posedge clock) begin
  2. case (operation)
  3. ADD: begin
  4. y <= a + b;
  5. end
  6. endcase
  7. end
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

  1. `timescale 1ns / 1ps
  2. package ALU;
  3. typedef enum logic [3:0] {
  4. ADD,
  5. SUBTRACT,
  6. INCREMENT,
  7. DECREMENT,
  8. BIT_AND,
  9. BIT_OR,
  10. BIT_XOR,
  11. BIT_NOT,
  12. SHIFT_LEFT,
  13. SHIFT_RIGHT,
  14. ROTATE_LEFT,
  15. ROTATE_RIGHT
  16. } opcode;
  17. endpackage
  18. import ALU::*;
  19. module alu (
  20. input logic clock,
  21. input opcode operation,
  22. input logic [7:0] a,
  23. input logic [7:0] b,
  24. output logic [7:0] y
  25. );
  26. always_ff @ (posedge clock) begin
  27. case (operation)
  28. ADD: begin
  29. y <= a + b;
  30. end
  31. endcase
  32. end
  33. endmodule
`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!

Save

Save

Save

Leave a Reply