Look again at the end of dma_smoke_test: after the interrupt and the STATUS checks comes #100ns;, then the drop. Delete that one line and run, and sometimes the scoreboard reports SB_LEFTOVER: an expected word that never came out. The test's work was finished. The DUT's was not.
That #100ns is a hand-made drain time. UVM has a built-in one. This lesson shows how it works, why it is per level, what cancels it, and why it is still a guess, which is the problem the next lesson solves.
A pub calls last orders, but nobody is thrown out the moment the bell rings: people get a few minutes to finish their drinks. The objection count reaching zero is the bell. The drain time is the grace period: after an object's total reaches zero, UVM waits that long before treating the object as finished and passing the drop to its parent. If anyone raises again during the wait, the wait is cancelled and the phase carries on.
You set it on the phase's objection for a particular object:
// snippet
task run_phase(uvm_phase phase);
phase.get_objection().set_drain_time(this, 100ns); // set before the count reaches 0
phase.raise_objection(this, "smoke test running");
// ... the job ...
phase.drop_objection(this, "smoke test done"); // the phase waits 100 ns here
endtaskThe drain is stored per object. When an object's total reaches zero, UVM waits that object's drain, then passes the drop to its parent, where the parent's total may reach zero and the parent's drain starts. So a 50 ns drain on the env and a 100 ns drain on the test add up to 150 ns of settling. Most benches set exactly one drain, on the test (this in the test's run_phase), so the number in the code is the number you get.
If two raisers drop at 300 ns and 400 ns, a drain on uvm_top's path starts at 400 ns, when the total actually reaches zero. Dropping early from one component does not start anyone's grace period.
A raise that reaches an object during its drain stops the wait. When the total next reaches zero, the drain starts again from the beginning. This is what makes drain time safe with late stragglers, and also what makes a periodic raiser keep a phase alive forever.
The smoke test's real trace shows the drop and the end of the phase at the same instant, 655 ns, because the test's #100ns happens before the drop and no drain time is set:
UVM_INFO @ 655000: run_objection [OBJTN_TRC] Object uvm_test_top dropped 1 run_objection objection(s) (smoke test done): count=0 total=0
UVM_INFO @ 655000: run_objection [OBJTN_TRC] Object uvm_test_top all_dropped 1 run_objection objection(s) (smoke test done): count=0 total=0
UVM_INFO dma_tb.sv(411) @ 655000: uvm_test_top.env.sb [SB] 8 words matched, 0 mismatchedMove the delay into set_drain_time(this, 100ns) and drop right after the STATUS checks: the dropped line moves 100 ns earlier, the all_dropped line stays at 655 ns, and the result is the same. The difference is that the delay is now visible to UVM. A raise during it, from a scoreboard for example, cancels it instead of being ignored by a blind #100ns.
Drain time should cover the longest legal gap between the stimulus finishing and the last observable effect being checked. For the DMA that is the pipeline from the last accepted input word to the last output word, plus any output backpressure. Measure it on a known-good test, add a margin, and write down where the number came from.
Symptom | Diagnosis | Fix |
|---|---|---|
SB_LEFTOVER now and then | Drain shorter than the worst-case tail | Measure the tail under backpressure and raise the drain. |
Every test idles at the end | Drain far above the measured tail | Lower it to the measured value plus margin. |
Drains set in env and test | They stack, so settling is the sum | Keep one drain, on the test. |
A missing drop 'fixed' with a long drain | The drain is hiding an ownership bug | Find the owner; drain is for trailing work only. |
A DMA team sized their drain from the smoke test: 8 words, no output backpressure, last word out 60 ns after the last word in. They set 100 ns and moved on. Months later, a random test with 16-word jobs and a slow output agent started failing once every few hundred seeds with SB_LEFTOVER: 1 expected word(s) never came out. Nothing was wrong with the DUT. With backpressure, the tail had grown to 140 ns, and the drain cut it off.
Raising the drain to 200 ns made the failure go away, and made every test in the regression 100 ns longer. The real problem is that a drain time is a guess about latency, frozen in a constant. The DMA knows exactly how many words it owes: LEN words per GO. A component that counts those can hold the phase for exactly as long as needed and no longer. That component is the subject of the next lesson.
About 150 ns. The env's total reaches zero and waits 50 ns; then the drop reaches the test, whose total reaches zero, and it waits 100 ns; then the drop reaches uvm_top. Drains are per object and stack along the path.
100 ns after the scoreboard's drop, assuming nothing else raises. The raise cancelled the test's drain; when the total reached zero again, the drain started from the beginning.
Because it is a constant standing in for a variable. Too short and some seeds lose their last items; too long and every test pays idle time. A count of outstanding work, learned from what the DUT was asked to do, ends the test exactly when the work is done.
Next: phase_ready_to_end lets a component look at the bench when the count reaches zero and say not yet, using a count instead of a guess.