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.
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.
| Rule | Constraint | Tempting mistake |
|---|---|---|
| aligned start | addr[1:0] == 0 | leaving it out, so 0x41 is generated |
| 1 to 8 beats | beats inside {[1:8]} | [0:8], which allows an empty burst |
| stays in the page | int'(addr) + 4 * beats <= 256 | addr + 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.
| Request | Exclusive end | Expected |
|---|---|---|
addr == 252, beats == 1 | 256 | succeeds |
addr == 224, beats == 8 | 256 | succeeds |
addr == 252, beats == 2 | 260 | fails |
addr == 228, beats == 8 | 260 | fails |
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.
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 256Check yourself
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
- Aligned burst request Implement · Easy
- Catch the generators that cross a bank Verify · Medium