The project manager catches you after the kickoff. The DMA RTL lands in two weeks, and the two-DMA subsystem tapes out in four months. How long do you need to verify it? The engineer before you said "two weeks, it's a simple block". Three months later that block was still on the critical path, and nobody trusted the next estimate that team gave. What does a number look like that you can defend in the room and still be proud of in month four?
An estimate is a forecast with stated uncertainty, not a promise. A defensible one has three properties. It is bottom-up: built from the testplan's rows and the tasks each needs, not from the size of the RTL or a gut feeling. It is a range: each task has a low and a high value, and the spread tells the reader how much is unknown. And it lists its assumptions, because most estimates fail when an assumption breaks, not because the arithmetic was wrong.
A builder quoting a kitchen works the same way. The quote is itemised (cabinets, plumbing, electrics), each line has a range, there is a contingency line held separately, and there is a list of exclusions: "assumes the wiring behind the wall is to code". When the wall comes down and the wiring is not to code, the builder points at the assumption instead of absorbing the cost silently.
Split effort into two kinds. Build effort (agents, scoreboards, tests) scales with the plan and estimates well. Convergence effort (debugging failures, closing coverage, reviewing holes) scales with how buggy the design is and how hard the last holes are, and it is the part most estimates forget. Allow for it explicitly, as a proportion of build effort, and say so.
The rows below are for one experienced engineer taking the DMA from nothing to block sign-off, with the testplan as input. Days are working days. The reasoning column is the part an interviewer actually listens to.
Task | Low | High | Reasoning |
|---|---|---|---|
Read the spec, write the testplan, review it with design | 3 | 5 | About twenty rows; one review meeting and a round of rework |
Env, in-house APB agent, interrupt monitor | 2 | 4 | Assumes an APB agent already exists and only needs integrating |
AXI-Stream agent: driver, monitor, reactive sink, backpressure knobs | 4 | 6 | New; the ready/valid slave and back-to-back beats are the hard half |
Scoreboard and predictor, with the busy-write, TLAST and DONE rules | 3 | 5 | The model is where spec fine print lives; it is also where war story 9 came from |
Register model, adapter, predictor, built-in sequences | 2 | 3 | Generated model; prediction mode and volatile fields need care |
Coverage model from the plan | 2 | 3 | Plan-named items, plus crosses pruned by risk |
Protocol and design assertions, bound to the DUT | 2 | 4 | APB and AXI-Stream rules, DONE timing, interrupt equation |
Tests: random, directed (zero, errors, busy writes, unmapped, reset), closure | 6 | 10 | Roughly half the rows need a directed test |
Build subtotal | 24 | 40 | |
Debug and bug turnaround | 8 | 16 | 30 to 40% of build: first RTL drops are buggy, and so is a new bench |
Coverage closure and code-coverage review | 4 | 8 | The long tail: the last holes need directed work and exclusion reviews |
Gate-level smoke support | 1 | 3 | A few tests on the netlist; most GLS effort sits with the chip |
Reviews and sign-off package | 2 | 4 | Code review, plan review, the sign-off sheet |
Total | 39 | 71 | About 8 to 14 engineer-weeks |
Assumptions, written under the table and sent with it: an in-house APB agent exists; the register model is generated from the register description; the RTL arrives feature-complete and stable enough to run on day one; one engineer, not a pool; no power domains inside the block. If the APB agent has to be written too, add 3 to 5 days; if the RTL arrives in drops, add a re-verification pass per drop.
The number you commit to is not the low end. A common choice is the midpoint (55 days, 11 weeks) plus a buffer of about 15%, so roughly 13 weeks, with a checkpoint at week 4 where the bench is complete and the first random runs are clean, at which point you re-estimate the rest with real bug data.
Because the subsystem env reuses the block env unchanged, the subsystem estimate covers only what is new: the integration rows that no block bench can see.
Task | Low | High | Reasoning |
|---|---|---|---|
Integration testplan | 2 | 3 | Only chain-level rows: data through both, backpressure across, per-instance interrupts and addresses |
Subsystem top, env from two block envs, end-to-end checker | 2 | 4 |
|
Chip register model with submaps; block sequences reused | 1 | 2 | Thirty lines of chip model; block sequences take a model handle |
Integration tests | 4 | 6 | Chained jobs, backpressure propagation, one DMA alone, both at once, reset |
Debug, closure, reviews | 6 | 12 | Integration bugs are fewer but slower to localise |
Total | 15 | 27 | About 3 to 5.5 weeks on top of the block |
Without reuse the subsystem would cost roughly another block estimate, and its checks would disagree with the block's. That comparison is the strongest argument you have for spending a few days making the block bench reusable.
Spec churn. A changed rule invalidates rows, checks and tests. Mitigation: plan IDs and a reviewed plan diff, so the cost of a change is visible the day it lands.
Late or unstable RTL. Every week the RTL slips is a week of bench work without a DUT to run on. Mitigation: build against a model or a stub, and agree a definition of "RTL ready".
Tool and VIP surprises. The course hit a Verilator internal error on a comparator default and a coverage build that crashed 8 runs in 20. Each cost days nobody planned.
People. Holidays, a second project, a new hire who needs mentoring. An estimate in engineer-weeks is not a calendar estimate.
Hold the buffer as one line owned by the lead, not spread across tasks. Buffers spread across tasks get spent by those tasks (work expands to fill the time); a central buffer is spent only when a named risk fires, and its burn rate is itself a signal.
Sooner or later the answer comes back: you have nine weeks, not thirteen. The senior move is not to agree and work weekends, and not to refuse. It is to show what the shorter schedule buys and what it leaves unverified, and let the person who owns the risk choose. Cut in an order you can defend:
Cut first | Saves | Risk it leaves |
|---|---|---|
Stress and performance runs beyond the spec | Days of runs and debug | Low for function; record it as not verified |
Cross cells that carry no risk (prune by plan) | Closure time | Low, if each pruned cell has a reason |
Chasing random closure: use directed tests for the known holes | The long tail | Fewer unexpected interleavings found |
Prove register rules formally instead of writing directed register tests | Several directed tests | Needs someone fluent in formal, and the proofs need their own line in the estimate (setup, properties, convergence); it moves effort, it does not remove it |
Defer gate-level smoke to the chip team's GLS list | 1 to 3 days | Reset and X issues found later and dearer |
And say what you will not cut: the data-path checks, every error path in the spec, register access rules, reset mid-job, and the check column for every row. A schedule cut that removes checks does not save time; it moves bug-finding to the lab. Then say it in one sentence the room can act on:
Once work starts, track it against the plan, not against tasks. The simplest burn-down is plan items hit in the merged regression, week by week. Here is the course's own closure history, from the real logs of earlier lessons:
Step | Plan items newly hit | Merged total |
|---|---|---|
| len=1, 2-4, 5-15; irq_en=1; outcome=done; clear=done; gap=1-3, 4+; tlast=on-time | 9 of 17 |
Add | len=0 | 10 of 17 |
Add the closure test (scratch) | len=16, len>16, irq_en=0, outcome=err, clear=err, tlast=early | 16 of 17 |
Add the back-to-back driver (scratch) | gap=0 | 17 of 17 |
The curve looks finished, and the fault campaign showed it was not: 5 of 8 faults detected. So track two burn-downs side by side: plan items hit, and plan items whose check exists and has been seen to fail (a planted bug or a fault). The second one is the honest measure of progress, and it lags the first.
A team once estimated a block as a list of build tasks: agents, scoreboard, twenty tests. They delivered every task on time, in week seven of eight. Then the first full regression produced forty failures, half of them bench bugs, and coverage stalled at 85% with holes the random tests could not reach. Six more weeks went into debug and closure that appeared nowhere in the plan, the block became the critical path, and the next estimate that team gave was cut in half by a manager who no longer believed it.
Nothing in the build estimate was wrong. It simply described the part of the work that is easy to see. The fix is the two lines this lesson's table carries: debug as a share of build, and closure as its own task, both visible from day one. A related trap is the project that is "90% done" for half its schedule: when the measure is tasks completed rather than plan items checked, the last 10% is where the convergence work hides.
Re-estimate openly, because an assumption broke. Each drop needs a regression and debug pass, and the error rows (E1 to E3, S5) cannot close until the last drop, so they move to the end of the schedule. Show the new range and the critical path, and propose mitigations: build and test the error checks against a model now, so they are ready when the RTL arrives.
The debug line is mostly about the design, not the bench: a new block's first drops have bugs, and each one costs reproduction, root cause, a fix and a re-run. It also covers bench bugs, which are real (the course bench had a model that ignored a DUT rule). Removing the line does not remove the work; it makes the schedule wrong in a predictable way. Offer to track debug effort separately so the share can be calibrated next time.
Not necessarily. Plan coverage measures stimulus, not checking. Look at the second burn-down: how many rows have a check that has been seen to fail. On the course bench the fault campaign still missed three faults at 17 of 17. Spend the time on the missing checks and a fault or mutation run before calling it early.
Next: the estimate assumed the block env would be reused upstream. A Block Bench That Upstream Can Reuse shows what that assumption costs to make true.