Problem: a legal burst generator

Specify and build a burst generator whose bursts stay aligned and inside a page, prove it reaches the end of the page, and check every burst with separate code.

Lesson
5.7
Time
30 min
Runs on
Verilator
Check
5 questions

A memory controller corrupted data whenever a burst ended exactly at the end of a page, and the bug reached the lab. The burst generator used addr + 4 * beats < 256, which forbids exactly the bursts that end there, so no test ever produced one. Every random burst was legal, and the one case that mattered had been made illegal by mistake.

Bursts crowd up to the page end and never reach it
01234addr224248240208236beats71384end (exclusive)252240252end 256 never generated

One column per generated burst near the top of the page. With < 256, the largest end is 252.

The rules in bytes

The burst class in this lesson describes a bus where a page is 256 bytes and each beat moves 4 bytes. A burst has 1 to 8 beats, starts on a 4-byte boundary, and must not cross the end of the page.

Before any code, the rules go into a short written spec that the class and the checker are both built from. That way a mistake in one shows up as a disagreement with the other.

Write every rule in bytes, because addr counts bytes and beats does not. A burst of beats beats covers 4 * beats bytes, so its exclusive end, the first byte after the burst, is addr + 4 * beats.

RuleConstraintTempting mistake
aligned startaddr[1:0] == 0leaving it out, so 0x41 is generated
1 to 8 beatsbeats inside {[1:8]}[0:8], which allows an empty burst
stays in the pageint'(addr) + 4 * beats <= 256addr + beats <= 256 (beats are not bytes), or < 256

The three rules of a legal burst and the mistakes each one invites.

Exclusive end, inclusive last byte

The last byte of the burst is the end minus 1, and it must be at most 255. So the rule is end <= 256, and a burst that ends at exactly 256 is legal: at 252, one beat covers bytes 252 to 255.

Strict comparison

constraint page {
  int'(addr) + 4 * beats < 256;
}

Forbids every burst whose last byte is 255, so the page end is never tested.

Exclusive end up to the page size

constraint page {
  int'(addr) + 4 * beats <= 256;
}

Allows a burst to end exactly at the page end and forbids any that crosses it.

Both versions produce only legal bursts, so a legality check alone cannot tell them apart. Only a test that asks for the page end, or counts how often it is reached, shows the difference.

Widths in the sum

The end can reach 256, which needs 9 bits, while addr has 8. In int'(addr) + 4 * beats <= 256, the cast and the 32-bit literal 4 make the whole expression 32 bits wide, so nothing wraps.

Trouble starts when the end is stored in a narrow variable. With bit [7:0] end8 = addr + 4 * beats;, an end of 260 wraps to 4 and passes any later check. Keep the end in an int, or compute it inside the comparison, as the constraint does.

The checker needs the same care. A legal_burst function that takes addr as bit [7:0] must widen it before adding. Otherwise it wraps exactly where the class does not, and the two disagree for the wrong reason.

Directed edges first

Before trusting random traffic, ask for the bursts on each side of every rule. Each request is a call to randomize() with, and the expected result comes from the rules, written down before the run.

RequestExclusive endExpected
addr == 252, beats == 1256succeeds
addr == 224, beats == 8256succeeds
addr == 252, beats == 2260fails
addr == 228, beats == 8260fails
addr == 8'h41-fails: misaligned

Directed requests at the edges of the rules.

A request that should fail is as valuable as one that should succeed. If 252 x 2 succeeds, the page rule is missing; if 252 x 1 fails, the rule is too strict, which is the bug in the opening story.

Run these requests every time the class changes, before any random traffic. A change that breaks an edge usually leaves the random traffic legal, so only a directed request notices it. The requests also document the rules: a reader sees at a glance where the page ends and how alignment works.

Random traffic, checked and counted

Then generate random bursts and check each one with a separate function, legal_burst(addr, beats), written from the rules and not copied from the class. Also count how many bursts end exactly at 256. If that count stays at 0 over a long run, the generator cannot reach the page end.

Uniform traffic reaches the page end rarely: about 4 bursts in 200 in the example. To test it more often, weight it with a dist on a condition: (int'(addr) + 4 * beats == 256) dist {1 := 1, 0 := 3}; makes about one burst in four end at the page end.

Run the same check once more in the scoreboard, on each burst the DUT actually receives. If the driver changes a field after randomization, only that check sees it.

Keep the count in the test log even after the weights are in place. It is one number per run, and it turns "we think we test the page end" into a figure anyone can check.

Try it

Both bursts that end exactly at 256 are accepted, and the one that would end at 260 is rejected. All 200 random bursts pass the separate check, and 4 of them reach the boundary.

Change <= 256 to < 256 in the class and predict which lines change. Then add the weighted dist from the last section and predict the last number.

RUNVerilator · simulator service
Edit the code, then run it
Expected: 252 x 1 (ends at 256): ok=1 224 x 8 (ends at 256): ok=1 %Warning-UNSATCONSTR: main.sv:5: Unsatisfied constraint: 'constraint legal { addr[1:0] == 0; beats inside {[1:8]}; int'(addr) + 4 * beats <= 256; }' %Warning-UNSATCONSTR: main.sv:5: Unsatisfied constraint: 'constraint legal { addr[1:0] == 0; beats inside {[1:8]}; int'(addr) + 4 * beats <= 256; }' %Warning-UNSATCONSTR: main.sv:22: Unsatisfied constraint: 'ok = t.randomize() with { addr == 252; beats == 2; };' %Warning-UNSATCONSTR: main.sv:22: Unsatisfied constraint: 'ok = t.randomize() with { addr == 252; beats == 2; };' 252 x 2 (ends at 260): ok=0 200 random bursts: 0 illegal, 4 end exactly at 256

Check yourself

Predict the outputThis class mixes up beats and bytes. Predict the exact output.
class burst_txn;
  rand bit [7:0]    addr;
  rand int unsigned beats;
  constraint legal { addr[1:0] == 0; beats inside {[1:8]}; int'(addr) + beats <= 256; }
endclass

module tb;
  burst_txn t = new;
  int ok;
  initial begin
    ok = t.randomize() with { addr == 252; beats == 4; };
    $display("ok=%0d end=%0d", ok, int'(t.addr) + 4 * t.beats);
  end
endmodule
Spot the bugEach option is the page rule in the burst class, next to addr[1:0] == 0 and beats inside {[1:8]}. The harness asks for bursts that end at 256 (must succeed) and at 260 (must fail), then checks 100 random bursts. Which option is wrong?
ConceptIs a burst with addr == 224 and beats == 8 legal on this bus?
ConceptAfter 10,000 random bursts the separate check has found no illegal burst. Which extra number shows whether the generator can reach the end of the page?
ConceptWhy is bit [7:0] end8 = addr + 4 * beats; a risky way to compute the end?

Remember

  • Write burst rules in bytes: the exclusive end is addr + 4 * beats.
  • A burst may end exactly at the page size, so the rule is end <= 256.
  • Keep the end in a wide enough type; a narrow variable wraps it silently.
  • Test the edges with directed requests, including ones that must fail.
  • Check random bursts with separate code, and count how often a burst ends exactly at the page end.

Practice