A DMA engine (illustrative) was verified with a testbench model of memory that always answered within ten cycles. The memory controller was verified with a testbench model of a requester that never issued more than four reads at once. Both blocks passed everything. Connected, the DMA issued eight reads, the controller's response queue overflowed, and data went missing. Neither block was wrong by its own testbench. Each testbench was wrong about the other block.
Why do blocks that pass their own verification fail together?
The obvious belief is that verified blocks compose: if A meets its specification and B meets its specification, A connected to B works. That holds only if the testbench's model of each neighbour behaves exactly as the real neighbour will. Testbench models are simplified, and they encode the verification engineer's assumptions about the neighbour. Every assumption that differs from the real neighbour is an integration bug waiting at the boundary.
Where integration bugs hide | Example |
|---|---|
Limits each side assumes of the other | Outstanding requests, burst lengths, response latency |
Behaviour the specification leaves open | Whether back-pressure can last forever; ordering between channels |
Connectivity | Signals swapped, tied off, or connected to the wrong instance |
Shared resources | Address maps, interrupts, clocks and resets shared by several blocks |
Protocol corners | A legal but unusual sequence one side never produces in its own tests |
Protocol checkers at every interface: assertions for the interface's rules, bound at both block and SoC level, so a neighbour that breaks a rule is caught at the boundary.
Testbench models as aggressive as the specification allows: maximum outstanding, worst latency, back-pressure at any time, not the polite behaviour of one real neighbour.
Subsystem tests with the real neighbours before full SoC integration (lesson 0.6).
Interface specifications with explicit limits, so assumptions are written down and checkable on both sides.
Practice | Purpose |
|---|---|
Verification IP for standard interfaces | An independent model that exercises everything the standard allows |
Bound protocol checkers reused at every level | The same rules checked wherever the interface appears |
Connectivity checks at SoC level | Find miswired and tied-off signals directly |
Integration test plans per interface | Each boundary gets tests with both real neighbours |
DCRouter's registers show a small version of the same class. Three Bug Zoo bugs live where the register block meets the rest of the design: input 2's error counter also counting good packets (BUG_14), one output's packet counter that cannot be cleared (BUG_16), and a clear that reaches all four error counters (BUG_19). Traffic tests never notice, because traffic does not read registers; only tests that combine traffic with register checks see the disagreement.
Yes, if the specification allows longer back-pressure: a future or different neighbour may do it, and that behaviour was never tested.
So the interface's rules are enforced wherever it appears; a real neighbour that breaks them is caught at the boundary, not three blocks later.
Blocks are verified against stand-ins for their neighbours; differences in assumptions become integration bugs.
Make stand-ins as aggressive as the specification allows and write limits down.
Bind protocol checkers at every interface and test with real neighbours before SoC level.