Beginning Logic Design – Part 6

Hello and welcome to Part 6 of my Beginning Logic Design series. In the last post I laid out the start of an ALU design. In this round I will be completing the ADD operation including support for the various status flags and build some tests to validate it.

As a quick reference, here’s the description I laid out in the previous post.

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.



Adding Status Signals to Addition

To complete out the ADD operation as specified, it needs to factor in a carry input signal and output the other status signals as well.

Following the general structure of what the 6502 architecture does, I’ll reference this 6502 opcode document for which flags are modified by which operations, and this 6502 processor document as a reference to the meanings for each flag. I also found this article on the overflow flag  helpful to wrap my head around its meaning and purpose.

To understand what all these flags mean, some familiarity with representing signed numbers and two’s complement is needed to understand the semantics of these flags, and it also helps to understand how subtraction is implemented in digital machines. I suggest this Computerphile video for an overview on unsigned, signed magnitude and twos complement binary representation.

Here’s how each flag should be responding in our addition circuit:

  • zero should be 1 if the result of the addition is zero
  • sign should be 1 if the the result could be interpreted as a negative number (in signed addition)
  • carry should be 1 if the addition results in an unsigned number that is wider than 8 bits
  • overflow should be 1 if the addition resulted in a number that is outside of the range supported by an 8 bit signed number: sum < -128 or sum > 127

To get started on this, the first thing I’ll do is add the input and output ports for these signals to my ALU. I’ll also use assign to set them all to 0 for now.

module alu (
    input logic clock,
    input opcode operation,
    input logic [7:0] a,
    input logic [7:0] b,
    input logic carry_in,
    output logic [7:0] y,
    output logic zero,
    output logic sign,
    output logic carry_out,
    output logic overflow
);

    assign zero = 0,
        sign = 0,
        carry_out = 0,
        overflow = 0;

Then I’ll also update my top module to include variables to route these in and out of the alu.

module top ();
    logic clock;
    opcode operation;
    logic [7:0] a;
    logic [7:0] b;
    logic carry_in;
    logic [7:0] y;
    logic zero;
    logic sign;
    logic carry_out;
    logic overflow;
    
    alu myALU (
        clock,
        operation,
        a,
        b,
        carry_in,
        y,
        zero,
        sign,
        carry_out,
        overflow
    );
    

...

I’ll also add carry_in = 0; in my initial block within top to set that signal to 0 at the start.

Test Driven Development

Many software developers are supporters of test driven development, but in the hardware world the concept of writing your tests before your implementation this is the norm.

It can be incredibly easy to add a flaw to our designs. The ADD operation here supports signed and unsigned addition. Interestingly enough the only difference between signed and unsigned addition is what the number means to you, the potential user of the ALU. If we do the operation 80 + 80, and intended that be unsigned addition result would be 160, but we intended signed addition it would erroneously be -96 because the number went beyond the maximum number a signed 8-bit number can support (127).

If we look at this in hexadecimal it is 0x50 + 0x50 = 0xA0 in either case, but the hexadecimal number 0xA0 has a different value if looked at as a signed (160) or unsigned (-96) number. We use the the carry_out status flag to let us know when our addition exceeds what can be accurately represented in our unsigned integer range [0, 255]. Similarly, we use the overflow status flag to know when a signed addition goes beyond what can accurately be represented in our signed integer range [-128, 127].

Implementing an adder that let’s us see all of this can be tricky, so let’s identify some of the special cases we need to be able to handle. The first round of tests I will write will use the table from this article on the 6502 overflow flag as a base.

Marked unsigned operations are carry outs, marked signed operations are overflows

I’ll now start writing some tests for my ALU. For this, I’ll use assert statements to check if the outputs are matching what I’d expect. Here’s the top module modified to include a test that validates a case where both overflow and carry should be set.

initial begin
    clock = 0;
    // Test 208 + 144
    a = 208;
    b = 144;
    carry_in = 0;
    operation = ADD;
    #2 assert(y == 96);
    assert(zero == 0);
    assert(sign == 0);
    assert(carry_out == 1);
    assert(overflow == 1);

    #1 $finish();
   
end

I added $finish(); at the end to stop simulation there. Now I’ll fire off the simulator and see how it goes.

In the simulator window there’s no indication of an error, but in the Tcl Console at the bottom I can see some output that indicates some assertions failed.

Error: Assertion violation
Time: 2 ns  Iteration: 0  Process: /top/Initial30_1  File: /home/kwilke/suchprogramming/my-alu/my-alu.srcs/sources_1/new/top.sv
Error: Assertion violation
Time: 2 ns  Iteration: 0  Process: /top/Initial30_1  File: /home/kwilke/suchprogramming/my-alu/my-alu.srcs/sources_1/new/top.sv
$finish called at time : 3 ns : File "/home/kwilke/suchprogramming/my-alu/my-alu.srcs/sources_1/new/top.sv" Line 44

The error message shows when the assertion failed, but no information is given as to why. To make the error more informational I can add an else statement followed by a $error() call to have the simulator spit out a more descriptive error.

#2 assert(y == 96) else $error("Sum assertion failed.");
assert(zero == 0) else $error("Zero assertion failed.");
assert(sign == 0) else $error("Sign assertion failed.");
assert(carry_out == 1) else $error("Carry assertion failed.");
assert(overflow == 1) else $error("Overflow assertion failed.");

Now when I run the simulation, I can more clearly see what has failed.

Error: Carry assertion failed.
Time: 2 ns  Iteration: 0  Process: /top/Initial30_1  File: /home/kwilke/suchprogramming/my-alu/my-alu.srcs/sources_1/new/top.sv
Error: Overflow assertion failed.
Time: 2 ns  Iteration: 0  Process: /top/Initial30_1  File: /home/kwilke/suchprogramming/my-alu/my-alu.srcs/sources_1/new/top.sv
$finish called at time : 3 ns : File "/home/kwilke/suchprogramming/my-alu/my-alu.srcs/sources_1/new/top.sv" Line 44

Implementing The Flags

Finding the zero flag is easy, we just need to see if our output is zero.

The sign flag is also easy, we just need to check if the most significant output bit is 1.

The carry flag we can get by looking the result of our addition as 9 bits and looking at the most significant bit there.

The overflow flag is the trickiest, but the easiest way to get that is to take the carry of the 6th (counting from 0) bit’s sum and XOR that with the final carry output.

If I use the y <= a + b; syntax I have now, I can’t access any of the carry values. I need access to the last two carry values to set all of our flags properly. I also want to start factoring the carry in signal.

To break these needs up into easily accessed chunks, I’ll declare a few more internal variables.

logic [7:0] add;
logic [6:0] add_lower_bits;
logic add_carry_6;
logic add_carry_7;

Next, I’ll define some combinational circuits with assign. I’ll first write an assignment that adds the lower bits and provides me my 6th carry bit.

assign {add_carry_6, add_lower_bits} = a[6:0] + b[6:0] + carry_in,
    {add_carry_7, add} = a + b + carry_in;

The curly brackets here combine bits together. In the first statement the addition on the right results 8 bits of output and I use the brackets on the left to put the highest bit in add_carry_6 and the remaining 7 bits in add_lower_bits. In this design I don’t use the lower bits but I set the size so I can easily get to that internal carry. I’ll use a similar assignment for the final carry and sum.

With easy access to all of the addition results I need, I can modify my ADD operation to use them to set my output and flags.

ADD: begin
    y <= add;
    carry_out <= add_carry_7;
    sign <= add[7];
    overflow <= add_carry_7 ^ add_carry_6;
    if (add == 0)
        zero <= 1;
    else
        zero <= 0;
end

Now I’ll need to remove my earlier assign block that was setting all my output flags to 0, and I can run the simulation again!

It works! I’ll write a few more tests to validate some of the other operations but it should be good to go now! Here’s how my files have ended up.

top.sv

`timescale 1ns / 1ps

import ALU::*;

module top ();
    logic clock;
    opcode operation;
    logic [7:0] a;
    logic [7:0] b;
    logic carry_in;
    logic [7:0] y;
    logic zero;
    logic sign;
    logic carry_out;
    logic overflow;
    
    alu myALU (
        clock,
        operation,
        a,
        b,
        carry_in,
        y,
        zero,
        sign,
        carry_out,
        overflow
    );
    
    initial begin
        clock = 0;
        operation = ADD;
        
        // Test 208 + 144
        a = 208;
        b = 144;
        carry_in = 0;
        #2 assert(y == 96) else $error("Sum assertion failed.");
        assert(zero == 0) else $error("Zero assertion failed.");
        assert(sign == 0) else $error("Sign assertion failed.");
        assert(carry_out == 1) else $error("Carry assertion failed.");
        assert(overflow == 1) else $error("Overflow assertion failed.");
    
        // Test 208 + 48 (signed -48 + 48)
        a = 208;
        b = 48;
        carry_in = 0;
        #2 assert(y == 0) else $error("Sum assertion failed.");
        assert(zero == 1) else $error("Zero assertion failed.");
        assert(sign == 0) else $error("Sign assertion failed.");
        assert(carry_out == 1) else $error("Carry assertion failed.");
        assert(overflow == 0) else $error("Overflow assertion failed.");
    
        // Test 80 + 80
        a = 80;
        b = 80;
        carry_in = 0;
        #2 assert(y == 160) else $error("Sum assertion failed.");
        assert(zero == 0) else $error("Zero assertion failed.");
        assert(sign == 1) else $error("Sign assertion failed.");
        assert(carry_out == 0) else $error("Carry assertion failed.");
        assert(overflow == 1) else $error("Overflow assertion failed.");
    
        #1 $finish();
        
    end
    
    always begin
        #1 clock = ~clock;
    end
    
endmodule

alu.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,
    input logic carry_in,
    output logic [7:0] y,
    output logic zero,
    output logic sign,
    output logic carry_out,
    output logic overflow
);
    
    logic [7:0] add;
    logic [6:0] add_lower_bits;
    logic add_carry_6;
    logic add_carry_7;
    
    assign {add_carry_6, add_lower_bits} = a[6:0] + b[6:0] + carry_in,
        {add_carry_7, add} = a + b + carry_in;

    always_ff @ (posedge clock) begin
        case (operation)
            ADD: begin
                y <= add;
                carry_out <= add_carry_7;
                sign <= add[7];
                overflow <= add_carry_7 ^ add_carry_6;
                if (add == 0)
                    zero <= 1;
                else
                    zero <= 0;
            end
        endcase
    end

endmodule

With our ADD operation fully functional that will finish this post. In the next post I will continue adding instructions and tests to this ALU to keep it moving closer to the model I outlined. If you have any feedback or questions please leave a comment. Keep tinkering!

Save

Leave a Reply

Your email address will not be published. Required fields are marked *