Recreate A CD4029B Counter In Verilog

July 23, 2026

FPGA + Verilog · Chapter 6 of 7

A datasheet is a set of claims. Turn those claims into an interface, state transitions, and tests that can reject a wrong implementation.

The CD4029B is a four-stage presettable binary-or-decade up/down counter with carry for cascading. Recreating its useful digital behavior is better than inventing another unconstrained first circuit because the source of truth already exists outside our code.

This is a functional HDL recreation for learning. It does not claim transistor-level, voltage, propagation-delay, input-threshold, or package equivalence to the physical CMOS part.

TI CD4029B datasheet Start from the simple counter Download the recreation Arrays and checks lab
Functional block diagram of a four-bit presettable up-down binary-or-decade counter with clock, control inputs, state outputs, and carry
Original Greenforest I/O functional model. It names externally observable roles before choosing an HDL implementation.

Begin With The Part’s Behavioral Contract

The Texas Instruments datasheet names the CD4029B a CMOS presettable up/down counter. Its digital behavior contains six ideas that deserve separate tests:

  1. It holds a four-bit state and exposes that state as four buffered outputs.
  2. It selects binary counting or BCD-decade counting.
  3. It selects up or down.
  4. It advances on a positive clock transition only when counting is enabled.
  5. Preset enable loads four jam inputs independently of the ordinary count step.
  6. An active-low carry output identifies the terminal state for cascading when carry-in permits counting.

The physical pin names are slightly hostile to software intuition. CARRY IN is low when counting is enabled, so it also acts as an active-low clock enable. BINARY/DECADE is high for binary and low for decade. UP/DOWN is high for up and low for down. PRESET ENABLE is high when jam inputs are loaded.

Preset enable = 1
Load the four jam-input bits. The physical part specifies an asynchronous preset path.
Carry in = 1
Inhibit advancement; hold the count when preset is not active.
Carry in = 0, up = 1
Advance on the rising edge; wrap 15→0 in binary or 9→0 in decade.
Carry in = 0, up = 0
Retreat on the rising edge; wrap 0→15 in binary or 0→9 in decade.
Carry out
Normally high; low at the selected mode’s maximum while counting up, or at zero while counting down, when carry-in is low.

That table is already more useful than a paragraph that says “make a CMOS counter.” Each row can become stimulus plus an expectation.

Decide What “Recreate” Means Before Writing RTL

The physical CD4029B can asynchronously transfer jam-input data into its count when preset enable is high. A portable FPGA flip-flop usually offers a clocked data input and a limited set of asynchronous controls, often reset or set—not an arbitrary four-bit asynchronous data load.

Therefore keep two contracts visible:

Silently changing asynchronous preset into synchronous load would be a bad recreation. Naming the adaptation teaches a more important lesson: an HDL port is a contract, and the target fabric constrains which contracts map cleanly.

Write The FPGA-Portable Counter

The simple counter from chapter 4 supplies the clocked-state skeleton. Expand it with mode, direction, enable, jam data, and a synchronous load:

module cd4029b_counter (
    input  wire       clk,
    input  wire       load,
    input  wire       enable,
    input  wire       binary_mode,
    input  wire       up,
    input  wire [3:0] jam,
    output reg  [3:0] q,
    output wire       carry_out_n
);
    wire [3:0] maximum = binary_mode ? 4'd15 : 4'd9;
    wire terminal = up ? (q == maximum) : (q == 4'd0);

    assign carry_out_n = ~(enable && terminal);

    always @(posedge clk) begin
        if (load) begin
            q <= jam;
        end else if (enable) begin
            if (up)
                q <= terminal ? 4'd0 : q + 1'b1;
            else
                q <= terminal ? maximum : q - 1'b1;
        end
    end
endmodule

The names are intentionally FPGA-friendly. enable is the logical inverse of the part’s active-low carry-in or clock-enable input. binary_mode is the positive form of the binary/decade selection. load is synchronous and must not be advertised as an exact replacement for the physical preset path.

In decade mode, constrain tests and normal use to valid BCD states zero through nine. The physical part allows jam inputs, but behavior from invalid decade states is not something to invent from intuition; derive it from the detailed device logic if an application needs it.

The complete runnable pair is cd4029b_counter.v plus cd4029b_counter_tb.v. Its Makefile and README.md keep the compile command and acceptance message beside the source.

Waveform showing a four-bit counter held at zero by reset and advancing on five rising clock edges after reset is released
Original Greenforest I/O waveform for the smaller included counter lab. The CD4029B recreation adds more controls, but the same edge-and-observation discipline remains.

Turn The Datasheet Into Test Vectors

Do not begin by staring at a waveform. Begin with claims that name starting state, controls, event, and expected result:

Load
With jam=5 and load=1, the next rising edge must produce five.
Hold
With enable=0, repeated rising edges must preserve the loaded value.
Binary up
14→15→0; carry-out is low while the enabled counter is at 15 and up is selected.
Binary down
1→0→15; carry-out is low while the enabled counter is at zero and down is selected.
Decade up
8→9→0; no state 10 appears.
Decade down
1→0→9; no state 15 appears.
Mode boundary
Run binary and decade boundary tests independently so a hard-coded four-bit wrap cannot masquerade as full support.

A task can remove clock-driving repetition without hiding expectations:

task tick;
    begin
        @(negedge clk);
        @(posedge clk);
        #1;
    end
endtask

task expect_q;
    input [3:0] expected;
    begin
        if (q !== expected)
            $fatal(1, "expected q=%0d, observed q=%0d", expected, q);
    end
endtask

The one-time-unit delay is not decorative. A nonblocking assignment schedules the register update after the active region of the clock event. Sample after that update or use clocking constructs designed for race-free verification.

Assertions Make The Acceptance Test Executable

An assertion is the executable form of “this must be true here.” For a small Icarus-compatible testbench, an immediate check plus $fatal is enough:

if (binary_mode == 1'b0 && q > 4'd9)
    $fatal(1, "invalid decade state: %0d", q);

SystemVerilog also has concurrent assertions for temporal properties, but simulator support and invocation flags matter. Begin with checks the chosen tool demonstrably runs. Then expand to properties such as “if enabled in binary-up mode at 15, the next sampled state is zero.”

The manuscript’s repeated USC language link is consolidated in chapter 5. This chapter owns assertion practice instead of repeating that external PDF a third time.

Packed eight-bit vector and an unpacked array of four words, followed by a constant-bound loop expanded into four lanes and an assertion checking a result
Original Greenforest I/O diagram. Shape is part of the language and often part of the hardware; a loop and an assertion have different jobs.

Packed Vectors And Unpacked Arrays Are Different Shapes

The handwritten “Verilog subtleties” list points repeatedly at arrays and indexing because tiny punctuation changes the type:

logic [7:0] byte_value;       // one packed eight-bit vector
logic [7:0] memory [0:3];    // four unpacked elements, each an eight-bit vector

In the first declaration, byte_value[7] selects one bit. In the second, memory[2] selects a byte and memory[2][7] selects one bit of that byte. The packed range is written before the name; the unpacked range follows the name.

Range direction is also real. [7:0] and [0:7] each contain eight bits, but their left and right indices differ. Do not assume that a bus, file format, or module boundary shares your preferred direction. State the mapping and test a non-symmetric pattern so reversed bits cannot pass unnoticed.

Whole-array assignment and multidimensional ports are much better supported in SystemVerilog than in older Verilog, but support still differs among simulator versions and synthesis tools. A construct accepted by one simulator is not automatically portable synthesizable hardware. Compile the supplied arrays-and-checks lab with the exact simulator and synthesizer versions you intend to use.

A For Loop Can Mean Repeated Hardware

The language-shapes lab computes the parity of four bytes:

always_comb begin
    for (int lane = 0; lane < 4; lane++) begin
        parity[lane] = ^memory[lane];
    end
end

In simulation, the procedural loop executes according to HDL scheduling. In synthesis, the fixed bound allows the tool to elaborate four parity lanes. It is not a CPU loop that borrows one parity unit four times unless you explicitly describe a clocked controller and shared resource.

When Icarus reports a baffling loop or array error, reduce the problem before blaming the algorithm: confirm -g2012, confirm that the file is parsed as SystemVerilog, move declarations to syntax the installed version supports, and isolate a minimal example. Tool mode and language revision are part of the experiment.

Tasks, Functions, And Loops Solve Different Problems

A function returns a value and is useful for a calculation such as a reference next-state function. A task can perform a sequence of testbench actions, consume simulation time, and update several outputs; tick and expect_q are examples. A generate loop creates or conditions structural instances during elaboration. A procedural loop repeats statements inside one process.

Those categories overlap in surface syntax but not in intent. Keep synthesizable functions free of timing controls. Keep timed testbench tasks outside the design. Keep loop bounds static when you expect repeated combinational hardware. Confirm the supported subset in both simulator and synthesizer.

The Domain Language From Source To Board

Verilog module
A named hardware-design unit with ports, declarations, behavior or structure, and optional child instances.
Netlist
A graph of cells and connections produced or consumed by a design tool. It may be generic or device-specific.
Technology mapping
Rewrite generic logic into resources the target provides—such as LUTs, carry cells, memories, and flip-flops.
Place and route
Choose physical resource locations and legal programmable connections, then analyze whether paths meet target timing.
Bitstream
The device-specific configuration result emitted after implementation and packing—not a “compiled .v module” and not a portable executable.

The sequence is: source and selected top → synthesis and netlist → technology mapping → placement and routing with board constraints → configuration packing → bitstream → loader. Chapter 7 makes every artifact in that chain concrete.

The Manuscript’s Reference Trail, Reorganized

The original curriculum stored several useful questions as bare links. They are retained by topic instead of scattered through the course:

Community answers are useful records of edge cases, not language standards. Keep the runnable local example and selected tool version beside every conclusion drawn from them.

Acceptance Check

The recreation is complete only when binary up, binary down, decade up, decade down, load, hold, both wrap boundaries, and carry polarity are self-checking; the waveform shows no invalid decade state; and the documentation states clearly that the FPGA-portable load is synchronous while the physical part’s preset path is asynchronous.