Algorithmic Reasoning

  1. What Is Population Count?

POPULATION COUNT, often shortened to POPCOUNT, means:

Count how many bits in a binary vector are equal to 1.

Example:

data = 8'b10110100

The 1 bits are:

1

0

1

1

0

1

0

0

There are:

4 ones

Therefore:

popcount(data) = 4

  1. Population Count Is Different From Counting Consecutive Ones

This distinction is extremely important.

Consider:

8'b00101111

The total number of 1 bits is:

5

So:

population count = 5

But the number of consecutive 1 bits starting from the least-significant side is:

4

because:

bit 0 = 1

bit 1 = 1

bit 2 = 1

bit 3 = 1

bit 4 = 0

Therefore:

Population count

≠

Count of trailing consecutive ones

  1. Determining the Required Output Width

Suppose the input contains:

255 bits.

The smallest possible population count is:

0

The largest possible population count is:

255

Therefore, the output must be able to represent:

0 through 255

An 8-bit unsigned value can represent:

0 through 255

Therefore:

output reg [7:0] out;

is sufficient for the population count of a 255-bit input.

  1. Why 8 Bits Are Enough for a 255-Bit Input

An unsigned N-bit value can represent:

0 through 2^N - 1

For:

N = 8

we get:

0 through 255

Therefore:

8 bits

can represent every possible population count for a 255-bit vector.

This is an important general rule:

The width of the RESULT should be based on the RANGE OF POSSIBLE RESULTS.

It does not need to match the width of the input.

  1. Input Width and Result Width Can Be Very Different

Suppose:

input wire [254:0] in;

This input has:

255 bits.

But the output does NOT need 255 bits.

Why?

Because we are not copying the input.

We are counting how many bits are equal to 1.

The answer can only range from:

0 to 255

Therefore:

8 result bits

are sufficient.

  1. General Result-Width Reasoning

For an input containing M bits:

minimum population count = 0

maximum population count = M

The output therefore needs enough bits to represent:

M

Conceptually:

Input width

↓

Determine maximum possible result

↓

Choose enough output bits to represent that maximum

This same reasoning can be useful in many other digital-design problems.

  1. The Incorrect Algorithm

Consider code conceptually like:

i = 0;

out = 0;

while ((i < 8) && (in[i] == 1'b1)) begin

out = out + 1;

i = i + 1;

end

At first glance, this may look like it is counting 1 bits.

But it does NOT implement population count correctly.

There are two separate problems.

  1. Problem #1: The Loop Examines Only 8 Bits

The condition contains:

i < 8

Therefore, the code can only examine:

in[0]

through

in[7]

But suppose the input is:

input wire [254:0] in;

That contains:

255 bits.

Therefore, the loop is ignoring:

in[8]

through

in[254]

Even if everything else were correct, this loop could not calculate the full population count.

  1. Problem #2: The Current Bit Is Part of the Loop Condition

The condition also contains:

in[i] == 1'b1

This means:

Continue looping ONLY while the current bit is 1.

Therefore, the moment the loop encounters:

in[i] = 0

the loop terminates.

This is not what population count requires.

Population count must continue examining the remaining bits even after encountering a zero.

  1. What Does the Incorrect Algorithm Actually Count?

The broken loop:

while ((i < 8) && (in[i] == 1'b1))

starts at:

in[0]

and continues only while the bits remain 1.

Therefore, it counts:

the number of CONSECUTIVE 1s beginning at the least-significant bit.

Example:

in = 8'b00101111

Starting from the LSB:

bit 0 = 1

bit 1 = 1

bit 2 = 1

bit 3 = 1

bit 4 = 0

The loop stops at bit 4.

Therefore:

broken algorithm result = 4

But the total population count is:

5

because there is another 1 at bit 5.

  1. The Central Algorithm Rule

This is one of the most important ideas from this Unit.

LOOP / TRAVERSAL CONDITION

↓

Determines WHICH elements are examined

LOGIC INSIDE THE LOOP

↓

Determines WHAT happens with each examined element

For population count:

Traversal should examine:

EVERY relevant bit

Then:

inside the loop

↓

decide whether the current bit contributes 1 to the count

  1. Incorrect Way to Think

Incorrect idea:

"Keep looping while the current bit is 1."

This produces:

Stop at first 0

That means:

only an initial run of 1s is examined.

  1. Correct Way to Think

Correct idea:

"Visit every bit."

Then for each bit:

If bit = 1

↓

increase count

If bit = 0

↓

do not increase count

Then continue to the next bit either way.

Conceptually:

bit 0

↓

inspect

↓

bit 1

↓

inspect

↓

bit 2

↓

inspect

↓

...

↓

final bit

  1. Correct Conceptual Algorithm

Conceptually:

count = 0

for every bit in the input:

if current bit = 1

count = count + 1

At the end:

count = number of 1 bits in the entire vector

The important point is:

A zero changes the amount added.

A zero does NOT terminate the traversal.

  1. Why a Zero Should Not Stop the Loop

Suppose:

data = 8'b10001001

Starting from the least-significant side:

1

0

0

1

0

0

0

1

Population count:

3

If the loop stopped at the first zero:

it would return:

1

which is clearly wrong.

Therefore:

0 bit

↓

contributes zero

NOT:

0 bit

↓

stop examining the rest of the vector

  1. Directly Adding the Bit

Because each individual binary bit is numerically either:

0

or:

1

we can use:

count = count + in[i];

Conceptually:

If:

in[i] = 0

then:

count = count + 0

So the count does not change.

If:

in[i] = 1

then:

count = count + 1

So the count increases by one.

  1. Why count = count + in[i] Works

Each bit already represents exactly the amount it should contribute.

Bit = 0

↓

contribution = 0

Bit = 1

↓

contribution = 1

Therefore:

count = count + in[i];

is a compact way to express:

"Add one if the current bit is 1; otherwise add zero."

  1. Explicit 'if' vs Direct Bit Addition

These two approaches can express the same counting idea.

EXPLICIT FORM:

if (in[i])

count = count + 1;

DIRECT ADDITION:

count = count + in[i];

The first may be easier to understand initially.

The second is more compact.

The important algorithm is the same:

every bit is examined

and

each 1 contributes one

  1. A Correct Population-Count Structure

A clean conceptual structure is:

count = 0

↓

Visit every input bit

↓

For each bit:

add its value to count

↓

Final count

The exact HDL syntax is less important than understanding this algorithmic structure.

  1. Why Some Incorrect Test Cases Can Still Pass

This is a very important debugging lesson.

Suppose the broken algorithm counts consecutive 1s starting at the LSB.

Now test:

1

Binary:

0001

Population count = 1

Trailing consecutive ones = 1

So the wrong algorithm passes.

Now test:

3

Binary:

0011

Population count = 2

Trailing consecutive ones = 2

Again, the wrong algorithm passes.

Now test:

7

Binary:

0111

Population count = 3

Trailing consecutive ones = 3

The wrong algorithm passes again.

  1. Passing Tests Does Not Necessarily Mean the Algorithm Is Correct

If several test inputs happen to have the same result under:

the intended algorithm

and:

the incorrect algorithm

then passing those tests proves very little.

For:

1

3

7

the set bits form one uninterrupted run beginning at the least-significant side.

Therefore:

population count

and:

count trailing consecutive ones

happen to agree.

  1. A Better Test Case

Use a value whose 1s are separated by zeros.

Example:

10101

Population count:

3

But consecutive ones from the LSB:

bit 0 = 1

bit 1 = 0

So the broken algorithm returns:

1

Now the error becomes obvious.

  1. Another Strong Test Case

Consider:

10000001

Population count:

2

But the incorrect trailing-one algorithm sees:

bit 0 = 1

then:

bit 1 = 0

and stops.

Result:

1

Therefore, this test distinguishes the algorithms immediately.

  1. Good Tests Target Likely Failure Modes

A good test set should not merely include:

easy values

or:

values that look different numerically.

It should include patterns that challenge the assumptions of the algorithm.

For population count, useful patterns include:

- all zeros

- all ones

- isolated one

- separated ones

- alternating bits

- one high-order 1

- combinations of low-order and high-order 1s

This helps expose incorrect traversal logic.

  1. Edge Case: All Zeros

Suppose:

input = all zeros

Expected population count:

0

A correct algorithm should:

examine every bit

but never increase the count.

Final result:

0

  1. Edge Case: All Ones

For a 255-bit input containing all ones:

Expected population count:

255

This is also why the output must be able to represent:

255

An 8-bit unsigned output is exactly sufficient.

  1. Edge Case: One High-Order One

Suppose only:

in[254] = 1

and every other bit is 0.

Population count:

1

A correct algorithm must still detect that high-order 1.

Any algorithm that only examines:

the first 8 bits

will incorrectly return:

0

  1. Why i < 8 Is Especially Misleading

The input may have originally reminded the programmer of:

an 8-bit quantity

or:

an 8-bit output

But the traversal limit must correspond to:

the number of INPUT ELEMENTS that need to be examined.

For:

input [254:0] in

the relevant index range is:

0 through 254

not:

0 through 7

  1. Output Width Does Not Determine Loop Bound

This is another important distinction.

The output is:

8 bits wide.

But that does NOT mean:

the loop should execute 8 times.

Why?

The output width tells us:

how many bits are needed to REPRESENT the count.

The loop bound tells us:

how many input elements must be EXAMINED.

For a 255-bit input:

output width = 8 bits

but:

number of input bits to inspect = 255

  1. Input Count vs Result Representation

Keep these separate:

INPUT SIZE

↓

How many elements exist?

RESULT RANGE

↓

What is the largest possible answer?

OUTPUT WIDTH

↓

How many bits are needed to represent that answer?

For the 255-bit population-count problem:

Input size = 255 bits

Maximum result = 255

Output width = 8 bits

  1. Population Count Is a Reduction-Like Operation

Population count takes:

many individual input bits

and produces:

one numerical result

Conceptually:

many bits

↓

count contributions

↓

single count value

This makes it a type of vector-reduction operation, but unlike XOR/AND/OR reduction operators, the result can require multiple bits.

  1. Population Count vs Reduction XOR

Reduction XOR answers:

"Is the number of 1s odd or even?"

Population count answers:

"Exactly how many 1s are present?"

For example:

data contains 5 ones

Then:

population count = 5

reduction XOR = 1

because 5 is odd.

These are different operations.

  1. Population Count vs Leading/Trailing-One Count

POPULATION COUNT:

Count all 1 bits anywhere in the vector.

TRAILING-ONE COUNT:

Count consecutive 1 bits beginning from the least-significant side.

LEADING-ONE COUNT:

Count consecutive 1 bits beginning from the most-significant side.

These operations can return very different answers for the same vector.

  1. Example Comparing the Three

Consider:

8'b11010111

Population count:

6

Trailing consecutive ones from the LSB:

3

Leading consecutive ones from the MSB:

2

Therefore:

popcount = 6

trailing-one count = 3

leading-one count = 2

This demonstrates why the algorithm's termination condition matters.

  1. Debugging by Asking "What Does This Code Actually Compute?"

When code fails, do not only ask:

"Why isn't my population count working?"

Instead ask:

"What function does this code actually implement?"

For:

while ((i < 8) && in[i])

the answer is:

"Count consecutive low-order ones, up to 8 positions."

Once the actual function is identified, the mismatch becomes much easier to diagnose.

  1. Separate the Specification From the Implementation

SPECIFICATION:

Count every 1 bit in the 255-bit vector.

IMPLEMENTATION:

The Verilog code written to achieve that.

If the implementation instead:

stops at the first zero

then:

the implementation does not match the specification.

This is an important general hardware-design debugging principle.

  1. A Correct Result on One Input Does Not Prove Correct Hardware

Suppose:

input = 7

and the output is correctly:

3

This shows only:

The circuit behaves correctly for that particular input.

It does NOT prove:

The circuit implements population count for all possible inputs.

Digital logic must be judged against the complete intended function.

  1. Why Adversarial Tests Matter

An ADVERSARIAL TEST is designed to expose a likely weakness or incorrect assumption.

If you suspect the algorithm stops at the first zero:

choose a test containing:

1s after a zero.

Example:

10101

That directly challenges the suspected failure mode.

This is much stronger than repeatedly testing:

1

3

7

15

which all have consecutive low-order ones.

  1. Population Count and Hardware

A population-count description must ultimately become hardware capable of combining contributions from many bits.

The exact FPGA implementation is determined by synthesis.

We should therefore avoid assuming that the source code must become one literal sequential chain exactly matching the procedural order.

The important requirement is:

the final hardware must produce the correct count.

  1. Source Algorithm vs Physical Structure

Suppose the source uses a loop that conceptually accumulates:

count = count + in[i];

That does not guarantee that the FPGA physically contains one adder repeatedly reused 255 times during one combinational evaluation.

As learned previously:

SOURCE-CODE STRUCTURE

≠

GUARANTEED PHYSICAL FPGA STRUCTURE

Synthesis may restructure the logic while preserving the required result.

  1. Common Mistake: Using the Current Data Value to Control Traversal

For population count:

Current bit value should determine:

CONTRIBUTION

not:

whether all later bits are visited.

Incorrect:

Continue only while current bit = 1

Correct mental model:

Always continue through the required input range

and use current bit to decide whether the count increases.

  1. Common Mistake: Matching the Loop Bound to the Output Width

Output:

8 bits

does NOT imply:

8 iterations.

The output width exists because:

255 requires 8 bits to represent.

The loop must still examine:

255 input bits.

  1. Common Mistake: Trusting Only Convenient Tests

Values such as:

1

3

7

15

all have contiguous 1s beginning at the LSB.

They are therefore poor tests for distinguishing:

population count

from:

trailing-one count.

A correct verification strategy needs structurally different bit patterns.

  1. Common Mistake: Forgetting the Maximum Result

If the input contains M bits:

population count can equal M.

Therefore, when sizing the result, always ask:

"Can the output represent the case where every input bit is 1?"

For 255 bits:

maximum result = 255

An 8-bit unsigned result can represent exactly that.

  1. Final Comparison

POPULATION COUNT

Meaning:

Count every bit equal to 1 in the complete vector.

TRAILING CONSECUTIVE ONES

Meaning:

Count consecutive 1 bits beginning at the least-significant side until the first 0 appears.

TRAVERSAL CONDITION

Purpose:

Determines which elements are examined.

CONTRIBUTION LOGIC

Purpose:

Determines whether/how each examined element affects the result.

RESULT WIDTH

Purpose:

Provides enough bits to represent every possible answer.

WEAK TEST CASE

A test that does not distinguish the correct algorithm from a plausible incorrect algorithm.

ADVERSARIAL TEST CASE

A test deliberately chosen to expose a suspected failure mode.

  1. Final Mental Model

For population count:

STEP 1

Determine what must be examined.

For a 255-bit vector:

ALL 255 BITS

STEP 2

Determine how each element contributes.

bit = 0

↓

add 0

bit = 1

↓

add 1

STEP 3

Determine the result range.

minimum = 0

maximum = 255

STEP 4

Choose enough output bits.

8 unsigned bits

↓

0 through 255

STEP 5

Test patterns that can expose incorrect logic.

Do not only use:

0001

0011

0111

Also use:

10101

10000001

alternating patterns

high-order isolated ones

The most important ideas are:

POPULATION COUNT MEANS COUNTING ALL 1 BITS IN THE VECTOR.

THE OUTPUT WIDTH SHOULD BE BASED ON THE MAXIMUM POSSIBLE COUNT.

A 255-BIT INPUT HAS A MAXIMUM POPULATION COUNT OF 255.

AN 8-BIT UNSIGNED OUTPUT CAN REPRESENT 0 THROUGH 255.

THE WIDTH OF THE OUTPUT DOES NOT DETERMINE HOW MANY INPUT BITS MUST BE EXAMINED.

A LOOP CONDITION DETERMINES WHICH ELEMENTS ARE VISITED.

LOGIC INSIDE THE LOOP DETERMINES WHETHER AN ELEMENT CONTRIBUTES TO THE RESULT.

INCLUDING in[i] AS A LOOP-CONTINUATION CONDITION CAN CAUSE THE LOOP TO STOP AT THE FIRST ZERO.

THE BROKEN EXAMPLE COUNTS CONSECUTIVE LOW-ORDER ONES, NOT TOTAL ONES.

count = count + in[i] WORKS BECAUSE EACH BIT CONTRIBUTES EITHER 0 OR 1.

PASSING A FEW TEST CASES DOES NOT PROVE AN ALGORITHM IS CORRECT.

VALUES SUCH AS 1, 3, AND 7 CAN HIDE THE ERROR BECAUSE THEIR SET BITS ARE CONSECUTIVE FROM THE LSB.

TESTS WITH SEPARATED 1 BITS ARE MUCH BETTER FOR EXPOSING THIS FAILURE.

WHEN DEBUGGING HDL, ASK WHAT FUNCTION THE CODE ACTUALLY IMPLEMENTS RATHER THAN ONLY ASKING WHY IT FAILED THE INTENDED FUNCTION.