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

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:

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

Save

Save

Save

Beginning Logic Design – Part 4

Hello and welcome to part 4 of my Beginning Logic Design series.

In the last couple posts we used Logicly to cover some introductory topics such as the basic logic gates, some combinational circuits for doing addition, and some sequential circuits we can use to store bits of data.

As we move on I’d like to cover increasingly more useful building blocks, but we’ll quickly outgrow what we can manage nicely in an educational logic simulator. While you can persevere and eventually build an entire CPU in Logicly, this is difficult and impractical.

Another reason to move beyond the basic logic simulator is I’d like to build some of these designs to see them operate in real life. While I have admiration for the folks that are building computers from scratch, I’d like an easier way. So today we’re going to change things up significantly!

Today we’re going to start exploring SystemVerilog and FPGAs!



System What Now?

SystemVerilog is a hardware description language (HDL). An HDL is a language that lets you describe hardware with code, rather than designing with schematics.

This gives a ton of benefits. Primarily it let’s us focus more on what we’re trying to accomplish, rather than some of the nifty tricks we have to do with basic logic gates to implement designs. We give our HDL to programs that can convert our descriptions into actual designs.

SystemVerilog and similar languages play a large role in the design of modern integrated circuits. This is especially true in the design of ASICs and designs for FPGAs. Since building your own ASIC has a fairly high cost for a hobbyist, we’ll look at FPGAs and tools provided by the vendors that make them.

Getting Started With FPGAs

There are two massive FPGA focused companies and a few other notable contenders. The biggest players are Altera (now owned by Intel) and Xilinx. Since the two big guys each have roughly 1/3 of the FPGA market it’s best to start with one of them since it’s easier to find boards and documentation.

I am more familiar with the Xilinx tools and I find their software, Vivado, is a bit less cluttered and makes simulation a bit easier, so I’ll be using that.

To get started you’ll need to register a free Xilinx account and download Vivado. The download is several gigabytes so it will take some time to download and install.

Hello Vivado

Using these tools can be daunting, so let’s jump right in and get something basic going before we get cold feet. Let’s open Vivado!

We’ll hit Create Project and step through the wizard. The first page of the wizard will introduce itself, then we’ll be prompted to name the project and select a directory for it to live in. I’ll call mine hello-vivado and store it in /tmp since I don’t intend to keep this project.

On the next page, I’ll select the project type, I’ll choose to RTL Project so we are ready for anything. I’ll skip the option to specify sources at this time.

The next page confused me a lot when I first tried to use Vivado with minimal FPGA experience, it asks you to pick a default FPGA out of their catalog of a bajillion parts. For simulated projects it doesn’t really matter, but I intend to buy the Arty A7 development board in the near future so I can run my designs on real hardware. I’ll choose the Artix-7 FPGA that board uses, part number xc7a35ticsg324-1l.

With that selected we can hit next and finish to get into our project.

There is a ton of functionality in Vivado, but we’ll ignore a lot of it for now. Let’s add a new file to our Sources. There is a plus sign we can use to add a new source file.

In the prompt that comes up, select Add or create design sources, and hit next. The screen will now present an interface for you to add or create files.

I’ll click Create File and select SystemVerilog as the file type. A common practice in FPGA design is to encapsulate your whole design as a module named top, and I will follow that convention.

I’ll hit OK followed by finish to close out of the add/create dialog. After you hit finish another prompt will come up to help you name the module and add I/O ports (similar to the connections that we had on components made in Logicly.) Just hit OK to accept its defaults and OK again to confirm. We’ll now have our top module in the Design Sources of our project.

We can double click on the top.sv file to open it in an editor. Vivado creates SystemVerilog files with a large comment for documenting some notes about the module, for brevity I’ll be deleting those comments from my files and I’ll indent things a bit differently.

`timescale 1ns / 1ps


module top ();
endmodule

This is our new empty module! The `timescale at the top tells the hardware simulator that we want to look at time measured in nanoseconds (a billionth of a second) and have values rounded to the nearest picosecond (a trillionth of a second).

The module keyword starts our description of a module named top. The parenthesis between top and ; is where we can describe the signals coming in and out of our module. The endmodule keyword indicates the end of our module definition.

To start, let’s recreate our 1 bit half adder from the second post. That component took 2 inputs, a and b and returned a sum and a carry. The first step is to add our inputs and outputs to the module.

module top (
  input logic a,
  input logic b,
  output logic sum,
  output logic carry
);

SystemVerilog has logic operators that are syntactically similar to most C-influenced languages.

 

We can use the assign keyword along with our logic operators to implement our half adder. This assignment goes within our module definition.

`timescale 1ns / 1ps

module top (
  input logic a,
  input logic b,
  output logic sum,
  output logic carry
);

  assign sum = a ^ b,
    carry = a & b;

endmodule

Our half adder is done, let’s see it in action! We can start a simulation by looking for Run Simulation in the Flow Navigator on the left or from the Flow menu in the menu bar. We’ll select Run Behavioral Simulation since we’re not worried about timings for this design.

When we start the simulator, Vivado will elaborate the design, this means interpreting our HDL and determining how that design can be built. It’ll then open a new interface to let us interact with the simulated design.

Simulation view in Vivado

The leftmost panel shows us the various modules in our design. The middle panel shows us the objects that are part of our selected module. The panel on the right opens up a wave viewer so we can watch the specific signals we’re interested in. Right now it shows our modules inputs and outputs.

It ran our simulation for some time too, a million picoseconds (1000 nanoseconds) have flown right by! We can use the zoom controls in the wave viewer to see everything that’s happened. So far, we haven’t set inputs to any value so the outputs are currently undefined.

To set a to a value, somewhat like we did with the toggle switches in Logicly, we can right click on a from the Objects panel or from the waveform viewer and choose Force Constant...

Setting a to 1

I’ll set a to 1 and b to 0 using this method. Then to continue simulation, we can use these time controls that are near the top of Vivado.

The play button will run the simulation indefinitely, which is not helpful in this case. I’ll instead edit the runtime field to run for 1 us (microsecond), and click the play with (T) to simulate for that specific amount of time.

After running one more microsecond, you can see the sum and carry did respond appropriately to our input on a and b. I can continue to change the values to validate the modules operation, but will leave it there for now.

Now we know that this design works, let’s look at it! In the Flow Navigator under RTL ANALYSIS we can click on Open Elaborated Design and look at the schematic that was built from the HDL.

Well doesn’t that look familiar! That will conclude this post of my Beginning Logic Design series, as always please leave any questions or feedback you may have in the comments! Keep tinkering!