Project 7: 4-bit Up/Down Counter with Load
Build a counter that can count up, down, and load a value.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Intermediate |
| Time Estimate | 4-5 hours |
| Main Programming Language | Verilog (Alternatives: VHDL, SystemVerilog) |
| Alternative Programming Languages | VHDL, SystemVerilog |
| Coolness Level | Medium |
| Business Potential | Low |
| Prerequisites | Flip-flops, Counters, Priority logic |
| Key Topics | Counters, Load/enable, Wraparound |
1. Learning Objectives
- Implement a counter with multiple control inputs
- Define a clear priority order
- Test wraparound behavior
2. All Theory Needed (Per-Concept Breakdown)
Flip-Flops and Registers
Description/Expanded Explanation of the concept
Flip-flops are the fundamental storage elements in synchronous digital logic. A register is a collection of flip-flops that store multi-bit values. Correct usage requires edge-triggered logic and non-blocking assignments.
Definitions & Key Terms
- Flip-flop -> edge-triggered storage element
- Register -> N-bit storage built from flip-flops
- Reset -> initializes state to known values
Mental Model Diagram (ASCII)
clk edge -> [FF] -> q

How It Works (Step-by-Step)
- On each clock edge, the flip-flop samples D.
- The output Q holds the sampled value until the next edge.
- Reset forces Q to a known value.
Minimal Concrete Example
always @(posedge clk) begin
if (reset) q <= 0;
else q <= d;
end
Common Misconceptions
- “reg always means storage.” -> It is a variable that may infer storage.
- “Blocking assignments are fine for registers.” -> Use non-blocking.
Check-Your-Understanding Questions
- Why is non-blocking used for sequential logic?
- What is the difference between a latch and a flip-flop?
- What is the role of reset?
Where You’ll Apply It
- This project: used in Section 3.2 and Section 4
- Also used in: P07-4-bit-updown-counter-with-load.md, P08-shift-register-led-chaser.md
Counters and Clock Enables
Description/Expanded Explanation of the concept
Counters are registers that increment or decrement each clock cycle. Clock enables allow you to update a register only when needed, which is safer than gating clocks. They are essential for timing, PWM, and pacing visible outputs.
Definitions & Key Terms
- Counter -> register that increments/decrements
- Clock enable -> condition that allows a register update
- Prescaler -> counter used to slow down a signal
Mental Model Diagram (ASCII)
clk -> [counter] --tick--> [slow logic]

How It Works (Step-by-Step)
- Increment counter each clock edge.
- When counter reaches a limit, assert a tick.
- Use tick as a clock enable for slower logic.
Minimal Concrete Example
if (tick) out <= out + 1;
Common Misconceptions
- “Gating the clock is simpler.” -> It can break timing and glitch.
- “Counters are always free.” -> Wide counters consume resources.
Check-Your-Understanding Questions
- Why use clock enables instead of gating clocks?
- How do you choose counter width for a target frequency?
- What happens when a counter overflows?
Where You’ll Apply It
- This project: used in Section 3.2 and Section 5
- Also used in: P08-shift-register-led-chaser.md, P10-pwm-generator-led-dimmer-servo-control.md
Verification with Testbenches and Waveforms
Description/Expanded Explanation of the concept
Testbenches are simulation-only modules that apply stimulus and check outputs. Waveforms (VCD) are the hardware engineer’s microscope; they reveal timing, glitches, and ordering problems. A good testbench is deterministic and covers edge cases.
Definitions & Key Terms
- Testbench -> a non-synthesizable module that drives a DUT
- VCD -> Value Change Dump waveform file
- Deterministic test -> same inputs produce same outputs every run
Mental Model Diagram (ASCII)
[Testbench] -> [DUT] -> [VCD] -> [GTKWave]

How It Works (Step-by-Step)
- Initialize inputs to known values.
- Apply stimulus over time.
- Dump waveforms and check outputs.
- Add assertions or PASS/FAIL messages.
Minimal Concrete Example
initial begin
$dumpfile("wave.vcd");
$dumpvars(0, tb);
a = 0; b = 1; #10;
$finish;
end
Common Misconceptions
- “If it simulates once, it’s correct.” -> Cover all relevant cases.
- “Waveforms are optional.” -> They are often the only way to debug timing.
Check-Your-Understanding Questions
- Why keep testbench and DUT separate?
- What is the purpose of
$dumpvars? - How do you make a testbench deterministic?
Where You’ll Apply It
- This project: used throughout Section 6 (testing)
- Also used in: all other projects in this folder
3. Project Specification
3.1 What You Will Build
A 4-bit counter with up/down control, load, enable, and reset.
3.2 Functional Requirements
- Requirement 1: Load overrides count
- Requirement 2: Count up/down when enabled
- Requirement 3: Wrap around at 0 and 15
3.3 Non-Functional Requirements
- Performance: Stable operation at the target clock and interfaces.
- Reliability: Deterministic outputs on all defined inputs.
- Usability: Clear ports and documented behavior.
3.4 Example Usage / Output
{p['example_usage']}
3.5 Data Formats / Schemas / Protocols
{p[‘data_format’]}
3.6 Edge Cases
- Load and enable asserted together
- Wrap at bounds
3.7 Real World Outcome
3.7.1 How to Run (Copy/Paste)
vvp counter_tb
3.7.2 Golden Path Demo (Deterministic)
Run the demo command above with the provided testbench and confirm the outputs match the golden transcript.
3.7.3 CLI Transcript
load=1 data=9 -> count=9
up=1 en=1 -> 10,11,12
3.7.4 Failure Demo (Expected)
# Example failure case
ERROR: Output mismatch at vector 3
Expected: 0x0A, Got: 0x0B
EXIT CODE: 1
Notes:
- Exit code 0 indicates all tests passed
- Exit code 1 indicates a test failure
4. Solution Architecture
4.1 High-Level Design
[inputs] -> [core logic] -> [outputs]

4.2 Key Components
| Component | Responsibility |
|---|---|
| counter | Register + add/sub logic |
4.3 Data Structures (No Full Code)
// Example signals (adapt to your design)
reg [7:0] state_reg;
reg [7:0] data_reg;
4.4 Algorithm Overview
Key Algorithm: Core control flow
- Initialize state/reset conditions.
- Apply inputs and compute outputs.
- Update state on clock edges (if sequential).
Complexity Analysis:
- Time: O(1) per cycle
- Space: O(N) for registers and logic
5. Implementation Guide
5.1 Development Environment Setup
iverilog -v
# Ensure GTKWave is installed for waveform viewing
5.2 Project Structure
project-root/
|-- src/
| |-- top.v
| |-- core.v
|-- tb/
| |-- tb.v
|-- Makefile
|-- README.md

5.3 The Core Question You’re Answering
“How do counters combine state, control, and priority of operations?”
5.4 Concepts You Must Understand First
- Flip-flops
- Counters
- Priority logic
5.5 Questions to Guide Your Design
- What priority should load have?
- Should count wrap or saturate?
5.6 Thinking Exercise
Write a truth table for load/en/up behavior.
5.7 The Interview Questions They’ll Ask
- Why use clock enable instead of gating clock?
- How to implement wraparound?
5.8 Hints in Layers
- Order logic as reset -> load -> count.
- Use ternary for up/down.
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Counters | Digital Design and Computer Architecture | Ch. 3 |
5.10 Implementation Phases
Phase 1: Foundation
Goals:
- Establish core module structure
- Implement minimal behavior
Tasks:
- Scaffold module ports and internal signals
- Write a minimal testbench that compiles
Checkpoint: Simulation runs without errors
Phase 2: Core Functionality
Goals:
- Implement full logic
- Verify edge cases
Tasks:
- Complete core logic
- Add directed tests for edge cases
Checkpoint: All tests pass and waveforms match expectations
Phase 3: Polish & Edge Cases
Goals:
- Improve readability
- Document behavior
Tasks:
- Add comments and README notes
- Expand tests for unusual inputs
Checkpoint: Design is deterministic and documented
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale |
|---|---|---|---|
| Reset strategy | Sync / Async | Sync | Simpler timing closure |
| Test coverage | Directed / Exhaustive | Exhaustive for small logic | Prevents missed cases |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples |
|---|---|---|
| Unit Tests | Test core logic | Small vectors |
| Integration Tests | Test modules together | Full system |
| Edge Case Tests | Boundary conditions | Max/min values |
6.2 Critical Test Cases
- Test 1: Load overrides count
- Test 2: Wrap at 0 and 15
6.3 Test Data
Use deterministic vectors and document expected outputs.
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution |
|---|---|---|
| Wrong priority | Load ignored when en is high | Order if/else correctly |
7.2 Debugging Strategies
- Inspect waveforms at key internal signals
- Add temporary debug outputs to verify state
- Reduce testcases to the smallest failing case
7.3 Performance Traps
- Overly wide counters or combinational paths can reduce max clock
8. Extensions & Challenges
8.1 Beginner Extensions
- Add parameterization for widths
- Add optional features (enable, reset)
8.2 Intermediate Extensions
- Add configuration registers
- Build a simple driver or demo program
8.3 Advanced Extensions
- Integrate with another project in this series
- Implement a hardware demo on FPGA
9. Real-World Connections
9.1 Industry Applications
- Digital control systems and embedded peripherals
- FPGA prototyping and validation
9.2 Related Open Source Projects
- Yosys / nextpnr toolchain for open-source FPGA flow
- Example HDL projects in the FPGA community
9.3 Interview Relevance
- Demonstrates RTL thinking and verification skills
10. Resources
10.1 Essential Reading
- Digital Design and Computer Architecture - Focus on Ch. 3
10.2 Video Resources
- Search for project-specific HDL walkthroughs and waveforms
10.3 Tools & Documentation
- Icarus Verilog
- GTKWave
10.4 Related Projects in This Series
- See adjacent projects in
VERILOG_FROM_ZERO_PROJECTS/
11. Self-Assessment Checklist
11.1 Understanding
- I can explain the core concept without notes
- I can predict waveform behavior for basic inputs
11.2 Implementation
- All functional requirements are met
- All tests pass
- Edge cases are documented
11.3 Growth
- I can explain this project in an interview
- I documented at least one lesson learned
12. Submission / Completion Criteria
Minimum Viable Completion:
- Functional requirements implemented
- Testbench passes
- Waveforms inspected
Full Completion:
- All minimum criteria plus
- Edge cases covered and documented
Excellence (Going Above & Beyond):
- Hardware demo on FPGA
- Clear write-up of lessons learned
Appendix A: Deep Dive Walkthrough
A.1 Signal Map and Priority Order
- Inputs:
clk,reset,en,up,load,din[3:0] - Output:
q[3:0]
Recommended priority (highest to lowest): reset -> load -> enable count -> hold
A.2 Behavior Table
| reset | load | en | up | action | |——-|——|—-|—-|———————| | 1 | X | X | X | q <= 0 | | 0 | 1 | X | X | q <= din | | 0 | 0 | 1 | 1 | q <= q + 1 | | 0 | 0 | 1 | 0 | q <= q - 1 | | 0 | 0 | 0 | X | q <= q (hold) |
A.3 Wrap-Around Notes
- Counting up from
1111wraps to0000. - Counting down from
0000wraps to1111.
A.4 Deterministic Vector Set
- Load
1001, then count up 3 cycles ->1100. - Count down 2 cycles ->
1010. - Disable
enand verify hold.
A.5 Waveform Debug Tips
- Ensure
loadandenare not asserted simultaneously unless defined. - If priority is wrong, load might be ignored or double-counts appear.
13. Deep Dive Appendix
13.1 Timing and Resource Budget
- A counter is a register plus increment/decrement logic.
- Load and direction muxes add combinational depth before the DFFs.
- The critical path is the adder/mux feeding the register input.
13.2 Waveform Interpretation Guide
- count should update only on clock edges.
- load should override count when asserted.
- dir determines +1 vs -1; verify rollover from 0 to 15 and 15 to 0.
Example:
count=0000, dir=down -> next=1111
count=1111, dir=up -> next=0000
13.3 Hardware Bring-Up Notes
- Use a slow clock divider to see count on LEDs.
- Map load_value to switches and a load button to capture it.
- Show carry/borrow on an LED for debugging.
13.4 Alternate Implementations and Trade-offs
- Binary counter: simple, compact.
- Gray counter: changes only one bit at a time, good for CDC.
- Ring counter: single hot bit, easy decoding.
13.5 Additional Exercises
- Add a terminal-count pulse for chaining counters.
- Make the counter modulo N (not just power of two).
- Add a pause input that freezes count without losing state.