Part 7 · Environment & Tests · Intermediate

Drain Time in the Env: Settling Trailing Observability

Set and validate drain windows so trailing monitor/scoreboard activity completes after objections clear, without bloating runtime.

What drain time solves

After the last objection drops, passive pipelines may still hold in-flight work: monitor publish queues, analysis FIFOs, predictor pipelines, and scoreboard compare threads. drain_time adds a bounded settle window so this tail work can complete before phase transition.

diagram
[UVM][ENV] closure timeline

t0: test drops final objection
t1: objection count reaches zero
t1..t2: drain_time window (tail activity allowed)
t2: run_phase ends and UVM advances

without drain:
  t2 happens at t1, tail work may be cut off
diagram
[ENV] where tail work comes from

- bus monitor decodes multi-cycle transfer after last drive
- scoreboard waits for matching expected/actual pair
- coverage subscriber flushes sampled queue
- predictor aligns delayed response stream
  • Drain time should model real tail latency, not guesswork.

  • Too small causes false failures or missing checks.

  • Too large silently inflates every test runtime.


Sizing and setting drain_time

systemverilog
class base_test extends uvm_test;
  `uvm_component_utils(base_test)
  int unsigned drain_ns = 200;

  task run_phase(uvm_phase phase);
    phase.phase_done.set_drain_time(this, drain_ns * 1ns);

    phase.raise_objection(this, "main");
    run_main_scenario();
    phase.drop_objection(this, "main done");
  endtask
endclass
systemverilog
function int unsigned estimate_drain_ns(my_env_cfg cfg);
  int unsigned mon_tail = cfg.max_decode_cycles * cfg.clk_period_ns;
  int unsigned sb_tail  = cfg.max_compare_cycles * cfg.clk_period_ns;
  int unsigned fifo_tail = cfg.max_fifo_depth * cfg.clk_period_ns;
  int unsigned budget = mon_tail + sb_tail + fifo_tail;
  if (budget < 50) budget = 50;
  return budget;
endfunction
diagram
[UVM][ENV] drain tuning workflow

1) run with drain=0 and record tail failures
2) measure worst-case tail latency across stress seeds
3) add margin (10-20 percent)
4) lock default in base_test
5) allow plusarg override for experiments only
  • Compute a data-backed default from known pipeline depths.

  • Keep drain override possible but controlled in regression.

  • Re-tune drain when monitor/checker architecture changes.


Validation patterns and limits

Validation code hooks

systemverilog
function void check_phase(uvm_phase phase);
  super.check_phase(phase);
  if (env.sb.pending_actual() != 0)
    `uvm_error("DRAIN", "actual queue not empty at check_phase")
  if (env.sb.pending_expected() != 0)
    `uvm_error("DRAIN", "expected queue not empty at check_phase")
endfunction
systemverilog
task monitor_tail_probe();
  int before = env.mon.publish_count;
  #0;
  #1ns;
  if (env.mon.publish_count > before)
    `uvm_info("TAIL", "monitor still publishing after drop", UVM_LOW)
endtask
diagram
[ENV] signs drain is mis-sized

too small:
  scoreboard empty-queue mismatches near test end
  occasional missing coverage samples

too large:
  every seed spends long idle tail time
  farm throughput drops with no quality gain

Key takeaways

  • Drain time protects trailing observability work after objections clear.

  • Size it from measured tail latency, then keep it tight.

  • Use check_phase invariants to prove drain adequacy.

  • Treat drain changes as performance-sensitive architecture changes.

Common pitfalls

  • Setting a single huge drain value to hide unknown behavior.

  • Assuming drain can fix structural checker deadlocks.

  • Never re-validating drain after adding deeper pipelines.

  • Skipping end-of-phase queue assertions and relying on intuition.