In this lab, you will build a 4-bit adder/subtractor using a hierarchical design methodology. You will construct a complex system by assembling and reusing simpler, verified components - this mirrors real-world engineering practices.
By now you should be comfortable with the Vivado workflow: creating projects, running simulations, synthesizing designs, and programming the Basys 3. In this lab you will apply that workflow to a more complex, multi-module circuit.
Our approach is bottom-up:
- Half Adder: The most basic building block - adds two single bits.
- Full Adder: Built from two half adders - handles a carry-in bit for columnar addition.
- Adder/Subtractor: Four full adders with XOR logic controlled by a mode signal M.
When M=0, the circuit adds: A + B.
When M=1, the circuit subtracts using two's complement: A + ~B + 1 = A - B.
Design Hierarchy:
rippleadder (4-bit adder/subtractor - TOP LEVEL)
├── XOR gates (B[i] ^ M → bw[i] for each bit)
├── fulladder_0 (bit 0 - LSB, Cin = M)
├── fulladder_1 (bit 1)
├── fulladder_2 (bit 2)
└── fulladder_3 (bit 3 - MSB)
└── Each fulladder contains:
├── halfadder_0 (A XOR B, A AND B)
└── halfadder_1 (sum XOR Cin, carry logic)
Before writing any Verilog, you will create a hand-drawn RTL (Register-Transfer Level) diagram of the complete adder/subtractor circuit. Your instructor will walk you through this process in class.
An RTL diagram is your blueprint for writing Verilog. It adds implementation details that a simple schematic does not:
- Module instances with unique names (e.g.,
fa0,fa1) - Internal wires with explicit names (e.g.,
bw[0],co[0]) - Port connections showing exactly what connects to what
A | B | Sum | Carry
0 | 0 | 0 | 0
0 | 1 | 1 | 0
1 | 0 | 1 | 0
1 | 1 | 0 | 1
- Sum = A XOR B
- Carry = A AND B
Sum = A ^ B(XOR)Carry = A & B(AND)
A | B | Cin | Sum | Cout
0 | 0 | 0 | 0 | 0
0 | 0 | 1 | 1 | 0
0 | 1 | 0 | 1 | 0
0 | 1 | 1 | 0 | 1
1 | 0 | 0 | 1 | 0
1 | 0 | 1 | 0 | 1
1 | 1 | 0 | 0 | 1
1 | 1 | 1 | 1 | 1
Sum = A ^ B ^ Cin(three-way XOR)Cout = (A & B) | (Cin & (A ^ B))(carry when two or more inputs are 1)
The full adder is built using two half adders and one OR gate:
- First half adder adds inputs A and B
- Second half adder adds the sum from the first with Cin
- OR gate combines the carry outputs from both half adders
The adder/subtractor extends the ripple carry adder with a mode signal M and XOR gates on the B inputs:
- Each B[i] passes through an XOR gate with M to produce bw[i]
- When M=0:
bw[i] = B[i](passes B through unchanged for addition) - When M=1:
bw[i] = ~B[i](inverts B for subtraction) - M also connects to the carry-in of the first full adder (provides the +1 needed for two's complement)
- B =
0011, ~B =1100, Cin = 1 - A + ~B + 1 =
0111+1100+1=10100 - Sum =
0100(4), Cout = 1
Before writing any Verilog, draw a complete RTL diagram for the 4-bit adder/subtractor showing:
- All 4 XOR gates with inputs (B[i], M) and outputs (bw[i])
- All 4 full adder instances with unique names
- All internal carry wires with names
- M connected to both the XOR gates and FA0's carry-in
- All port connections labeled
Your RTL diagram is the map you will follow when writing your Verilog code. You will present it to your TA during sign-off.
- Draw RTL diagram - Hand-drawn diagram of the complete adder/subtractor (in-class)
- Implement
rtl/halfadder.v- Complete the half adder logic - Implement
rtl/fulladder.v- Build a full adder from two half adders - Complete
tb/fulladder_tb.v- Declare signals, instantiate UUT, and write test vectors - Implement
rtl/rippleadder.v- XOR gates, carry chain, and four full adders - Complete
rtl/adder_wrapper.v- Instantiate the rippleadder - Simulate and test - Verify all modules in Vivado
- Program the Basys 3 - Validate on hardware with addition and subtraction
- Download the Lab 4 template files
- Launch Vivado and create a new project
- Set the project location outside of the template directory
- Add all
.vfiles fromrtl/as design sources - Add all testbench files from
tb/as simulation sources - Add the constraint file from
constraints/ - Select the Basys3 board as target
- Open
rtl/halfadder.vand implement the logic - Set
halfadder_tbas the simulation top module and run simulation - Verify all 4 test cases pass
- A working half adder is required for all subsequent steps
- Open
rtl/fulladder.vand implement using twohalfadderinstances - Open
tb/fulladder_tb.vand complete the 3 TODO sections:- Declare input and output signals
- Instantiate the fulladder as
UUT - Write all 8 test vectors
- Set
fulladder_tbas simulation top and run simulation - Debug both files until all 8 test cases pass
- Open
rtl/rippleadder.vand implement the XOR gates and full adder chain - Use your hand-drawn RTL diagram as your guide - translate it directly to Verilog
- Set
rippleadder_tbas simulation top and run simulation - Verify all 12 test cases pass (6 addition + 6 subtraction)
- Complete
rtl/adder_wrapper.vwith the rippleadder instantiation - Set
adder_wrapperas the top-level module for synthesis - Synthesize, implement, and generate bitstream
- Program the Basys 3 and test with the cases below
When you push your code, GitHub Actions will automatically compile and test each module:
| CI Job | What It Tests | Files Compiled |
|---|---|---|
test-halfadder |
Half adder in isolation | halfadder.v + halfadder_tb.v |
test-fulladder |
Full adder (uses half adder) | halfadder.v + fulladder.v + fulladder_tb.v |
test-rippleadder |
Full adder/subtractor system | halfadder.v + fulladder.v + rippleadder.v + rippleadder_tb.v |
All three jobs must show TEST PASSED for full credit.
sw[3:0]= A[3:0] (first 4-bit number)sw[7:4]= B[3:0] (second 4-bit number)sw[15]= M (mode: down = add, up = subtract)
led[7:0]mirrorssw[7:0](confirms your inputs)led[8]shows M (mode indicator)led[14:11]displays the 4-bit Sumled[15]displays Cout
| A (sw[3:0]) | B (sw[7:4]) | Sum (led[14:11]) | Cout (led[15]) | Notes |
|---|---|---|---|---|
| 3 | 2 | 5 | 0 | Basic addition |
| 5 | 7 | 12 | 0 | Carries within 4 bits |
| 7 | 8 | 15 | 0 | Maximum without overflow |
| 15 | 1 | 0 | 1 | Overflow: 16 wraps to 0 |
| A (sw[3:0]) | B (sw[7:4]) | Sum (led[14:11]) | Cout (led[15]) | Notes |
|---|---|---|---|---|
| 5 | 3 | 2 | 1 | Basic subtraction |
| 7 | 7 | 0 | 1 | Equal values |
| 8 | 3 | 5 | 1 | Larger minus smaller |
| 0 | 1 | 15 | 0 | Borrow: result wraps |
| 3 | 8 | 11 | 0 | Borrow: A < B |
Note: In subtraction mode, Cout=1 means no borrow (A >= B). Cout=0 means borrow occurred (A < B) and the result has wrapped around.
├── rtl/
│ ├── halfadder.v # Half adder (you implement)
│ ├── fulladder.v # Full adder using half adders (you implement)
│ ├── rippleadder.v # 4-bit adder/subtractor (you implement)
│ └── adder_wrapper.v # FPGA interface wrapper (you complete)
├── tb/
│ ├── halfadder_tb.v # Half adder testbench (provided)
│ ├── fulladder_tb.v # Full adder testbench (you complete)
│ └── rippleadder_tb.v # Adder/subtractor testbench (provided)
├── constraints/
│ └── basys3.xdc # Pin assignments for Basys3
└── .github/workflows/
└── test.yml # CI - auto-tests on push
- Check that all module names match between files
- Verify that all wires are properly declared
- Ensure proper port connections in instantiations
- Case sensitivity:
Coutis different fromcout- names must match exactly
- Wrong testbench set as the simulation top
- Incorrect testbench stimulus timing
- Wrong expected results in comparisons
- Wrong switch/LED assignments - check constraint file
- Bitstream not loading - verify board connection
- Unexpected results - verify logic in simulation first