Full Adder

Our HalfAdder is good enough to add any pair of 1-bit numbers together. However, if we want to sum up numbers with more than 1 bit, we need just a little bit more.

A FullAdder is a famous circuit which can sum two 1-bit numbers and a carry. (This is a total of three 1-bit numbers). It is made up of two HalfAdders, thus the name:

mod FullAdder {
    incoming a : Bit
    incoming b : Bit
    incoming carry_in : Bit

    outgoing sum : Bit dependson a, b, carry_in {
        it := halfadder_1.sum
    }
    outgoing carry_out : Bit dependson a, b, carry_in {
        it := halfadder_0.carry_out || halfadder_1.carry_out
    }

    // first, sum a + b
    mod halfadder_0 of HalfAdder {
        it.a := a
        it.b := b
    }

    // then, sum the result with carry_in
    mod halfadder_1 of HalfAdder {
        it.a := halfadder_0.sum
        it.b := carry_in
    }
}

Note

This is the second in a series of three modules which appear in full in Complete Adder Example.

In addition to a and b, FullAdder takes an additional 1-bit input called carry_in. The circuit then adds a, b, and carry_in together and returns their sum as carry_out and sum.

The First Half Adder

The first HalfAdder is this:

// first, sum a + b
mod halfadder_0 of HalfAdder {
    it.a := a
    it.b := b
}

The mod keyword, when used inside a module definition, creates an instance of another module. Here, we give the submodule the name halfadder_0 and using the of keyword, we say it is going to be a HalfAdder.

We create a driver block to connect up the ports of halfadder_0. We write it.a := a, and similarly it.b := b, to thread the incoming port values a and b down to our first half adder.

Once we have wired things up this way, halfadder_0 will sum a and b for us. The sum of a and b are made available through its outgoing ports, which we may refer to as halfadder_0.sum and halfadder_0.carry_out.

On Naming Things

There is a certain kind of designer who is always tempted to use very short names for signals and submodules. Perhaps they might want to write ha instead of halfadder.

But in Virdant, we use highly descriptive and memorable names, even when they are a bit longer. This is one way you can be courteous to the designer who has to read your code.

Part of the reason we have it in Virdant is so you don’t have to type the names out more than once or twice. So always think carefully about your variable names, and don’t skimp on letters.

When naming things that have numbers, it is conventional to start numbering starting at 0 and to always place an underscore before the number. That’s why our “first” half adder is not halfadder_1, nor is it halfadder0, but rather it is halfadder_0.

Second Half Adder

Our second HalfAdder submodule is this:

// then, sum the result with carry_in
mod halfadder_1 of HalfAdder {
    it.a := halfadder_0.sum
    it.b := carry_in
}

The difference here is that we do not sum a and b, but rather halfadder_0.sum and carry_in. Since halfadder_0.sum gives us the value a + b, this has the effect of summing a + b + carry_in, just as we wanted.

Wrapping up the Full Adder

The two remaining parts of FullAdder are a few lines we skipped over which drive the outputs sum and carry_out.

These lines should be straightforward, as long as you remember || is how we say logical OR in Virdant:

outgoing sum : Bit dependson a, b, carry_in {
    it := halfadder_1.sum
}
outgoing carry_out : Bit dependson a, b, carry_in {
    it := halfadder_0.carry_out || halfadder_1.carry_out
}

Waveform

The waveform for this example is much larger:

falsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsefalsetruefalsetruefalsefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalseinputstop.atop.btop.carry_inoutputstop.sumtop.carry_outFirst Half Addertop.halfadder_0.atop.halfadder_0.btop.halfadder_0.sumtop.halfadder_0.carry_outSecond Half Addertop.halfadder_1.atop.halfadder_1.btop.halfadder_1.sumtop.halfadder_1.carry_outfalsetruefalsetruefalsetruetruefalsetruefalseinputstop.atop.btop.carry_inoutputstop.sumtop.carry_outFirst Half Addertop.halfadder_0.atop.halfadder_0.btop.halfadder_0.sumtop.halfadder_0.carry_outSecond Half Addertop.halfadder_1.atop.halfadder_1.btop.halfadder_1.sumtop.halfadder_1.carry_out

It’s larger because we are showing not only the ports for FullAdder, but also the ports for its two submodules, halfadder_0 and halfadder_1.

Note that because of the way the signals are driven, many of these signals are aliases for one another. For example, top.a and halfadder_0.a are copies of the same signal. This is because we have a driver it.a <= a (where it refers to halfadder_0).

If we were only interested in the behavior of the top-level ports, we could trim it down:

falsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetrueInputstop.atop.btop.carry_inOutputstop.sumtop.carry_outfalsetruefalsetrueInputstop.atop.btop.carry_inOutputstop.sumtop.carry_out

Or if we were trying to understand a bug in a design, we might look at a few key ports from different levels of the module hierarchy:

falsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsetruefalsefalsetrueInputstop.atop.btop.carry_inFirst Half Addertop.halfadder_0.atop.halfadder_0.btop.halfadder_0.sumtop.halfadder_0.carry_outfalsetruefalsetruetruefalseInputstop.atop.btop.carry_inFirst Half Addertop.halfadder_0.atop.halfadder_0.btop.halfadder_0.sumtop.halfadder_0.carry_out

This should give you an idea of how there is a bit of an art to how to think about and visualize what a module does.

We didn’t go into as much detail over how FullAdder works this time, so make sure to study the code and the waveforms carefully!

It may look really complicated. Don’t worry too much if you don’t get it at first. It’s enough to understand that we’re just adding three 1-bit numbers together. And we’re doing so with logic gates and by re-using the logic inside of HalfAdder from the last lesson.