UVM
Driver Responsibilities
What a driver is responsible for — pulling transactions, converting them to pin-level activity on the interface, and completing them — and the separation of concerns that says it must not decide, generate, or judge stimulus.
Drivers · Module 12 · Page 12.1
The Engineering Problem
The previous modules built everything that feeds the driver — transactions (Module 8), sequences (9–10), the sequencer (11) — and treated the driver as the thing on the other end of the handshake. This module is the driver itself, and it opens with the most important question: what is the driver responsible for? Because a driver sits at a tempting boundary — it has the transaction and it touches the pins — there's a constant pull to make it do more than it should: generate stimulus, make decisions, check results. Every one of those is a different component's job, and conflating them is how testbenches lose their reuse and their clean structure.
A driver is a uvm_driver component with a narrow, precise job: pull transactions from the sequencer (get_next_item), convert each into the protocol's pin-level activity on the DUT interface (the "how", Module 8.1) — driving the virtual interface, honoring the protocol's timing — and complete them (item_done, optionally returning a response). That's it. The driver is the sole translation point between the transaction world and the pin world — the only component that drives the pins — and its discipline is executing transactions, not deciding them. It must not decide what to drive (the sequence's job), generate stimulus (the sequence's job), or check correctness (the scoreboard's job). This chapter is the driver's responsibilities: pull, convert, complete — and the separation of concerns that keeps it a dumb executor, which is exactly what makes it reusable.
What is a driver responsible for — pulling transactions, converting them to pin-level activity, and completing them — and what must it not do (decide, generate, or judge stimulus), and why does that separation of concerns matter?
Motivation — why the driver's job is narrow
The driver's responsibilities are deliberately limited because a clean testbench depends on each component doing one thing, and the driver's boundary is where that discipline is most tempting to break:
- One component drives the pins, so the driver owns that — and only that. The pins must be driven by exactly one place (the protocol's "how"), and that's the driver. Giving it more (deciding stimulus, checking) means the pin-driving concern gets tangled with others, so changing one forces touching the driver.
- Stimulus must vary independently, so deciding what belongs to the sequence. Tests change the stimulus constantly (Module 9); if the driver decided what to drive, every new scenario would edit the driver. Keeping what in the sequence and how in the driver lets stimulus vary without touching the driver (Module 9.1's separation).
- Correctness checking must be centralized, so judging belongs to the scoreboard. Whether the DUT behaved correctly is the scoreboard's call, made by comparing observed against expected. A driver that also checks duplicates (and can conflict with) that logic, and couples driving to checking.
- Reuse requires a dumb executor, so the driver makes no decisions. A driver that only executes the transaction it's given — verbatim — is reusable across every scenario (the sequence decides) and every check (the scoreboard judges). A driver with embedded logic is reusable nowhere but the test it was written for.
- The boundary is the translation, so the driver is the bridge, not the brain. The driver's value is being the one place that turns transactions into pins — the bridge between the two worlds. Making it the brain (deciding, judging) destroys the clean two-world structure (Module 8.1's what/how).
The motivation, in one line: a clean testbench needs each component to do one thing, and the driver's one thing is executing transactions on the pins (pull-convert-complete) — not deciding, generating, or judging — because keeping the driver a dumb executor at the translation boundary is what lets stimulus, checking, and driving all vary independently, which is the entire reuse argument.
Mental Model
Hold the driver as the hands that execute the order, not the brain that decides it or the judge that grades it:
The driver is the hands: it takes an order ticket and physically performs it on the equipment — it doesn't write the menu, and it doesn't taste-test the result. Picture a kitchen. The menu and the orders (what to make) come from the front of house (the sequence) — the driver never decides what dish to cook. The quality check (was it made correctly?) is the inspector's job (the scoreboard) — the driver never grades its own work. The driver is the line cook: it takes the order ticket (a transaction), and it physically executes it on the actual equipment (the pins) — following the recipe (the protocol's timing and signaling) precisely — and then rings the bell (
item_done) when the dish is up, sometimes passing back a receipt (the response). Its skill is entirely in the execution — operating the equipment correctly, on time, to spec — not in deciding or judging. And the kitchen works precisely because these roles are separate: the front of house can change the menu (new stimulus) without retraining the cook, and the inspector can change the standards (new checks) without retraining the cook, because the cook only executes. A cook who also decided the menu or graded the food would couple all three jobs into one person — and you couldn't change any without changing the cook.
So the driver is hands, not brain or judge: it executes the transaction on the pins (the recipe), rings done, and passes the receipt — making no decisions about what to cook (the sequence) or whether it was right (the scoreboard). The separation is what lets each role change independently — which is the whole point.
Visual Explanation — the driver as the translation boundary
The defining picture is the driver's position: it sits at the boundary between the transaction world (the sequencer hands it items) and the pin world (it drives the DUT), the sole translator.
The figure shows the driver's exact position and ownership. On the transaction side (left), the driver pulls items from the sequencer (get_next_item, the consumer end of the handshake, Module 11.1). It converts each transaction into pin-level activity — the protocol's "how" (Module 8.1) — and drives the DUT through the virtual interface (the handle to the actual signals, set via config DB, Module 7). It completes each item (item_done, optionally returning a response back to the sequencer). And — the key ownership fact — it is the only component that drives the pins; the monitor observes them in the other direction (it never drives). So the driver owns exactly one boundary: the transaction-to-pin translation, in the drive direction. Everything to its left is the transaction world (sequences decide, the sequencer arbitrates); everything to its right is the pin world (the DUT). The driver is the bridge — and only the bridge. The crucial reading is the narrowness: the driver's box contains pull → convert → complete and nothing else — no stimulus generation (that's left of it), no checking (that's elsewhere). The diagram is the chapter's thesis spatially: the driver is the single translation point between two worlds, owning the drive direction, with a job that starts at the sequencer's hand-off and ends at the pins — a precise, bounded responsibility.
RTL / Simulation Perspective — the driver's run_phase loop
The driver's responsibilities are one small loop in run_phase: pull, convert/drive, complete — repeated forever. The code is the canonical driver, and its brevity is the point.
class my_driver extends uvm_driver #(bus_item); // uvm_driver #(REQ) — a component
`uvm_component_utils(my_driver)
virtual my_if vif; // the virtual interface (the pins)
function new(string name, uvm_component parent); super.new(name, parent); endfunction
function void build_phase(uvm_phase phase);
if (!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif)) // GET the interface (Module 7)
`uvm_fatal("NOVIF", "virtual interface not set")
endfunction
task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req); // (1) PULL the transaction from the sequencer
drive_transaction(req); // (2) CONVERT + DRIVE on the pins (the 'how')
seq_item_port.item_done(); // (3) COMPLETE (optionally item_done(rsp))
end
endtask
task drive_transaction(bus_item tr); // the conversion: transaction → pin activity
@(posedge vif.clk); // honor the protocol's timing
vif.addr <= tr.addr; vif.wr <= !tr.is_read; vif.valid <= 1; // drive the pins from tr's fields
@(posedge vif.clk); vif.valid <= 0; // ... protocol handshake / wait states ...
endtask
// ✗ the driver does NOT: generate tr's content, decide what to send, or check correctness
endclassThe driver is exactly this small, and its smallness is the discipline. It's a uvm_driver #(bus_item) component (permanent, in the agent) that gets the virtual interface in build_phase (the handle to the pins, via config DB, Module 7 — without it, driving crashes). Its run_phase is the canonical forever loop of three steps: (1) pull — get_next_item(req) takes the next transaction from the sequencer (the consumer handshake, Modules 9.4/11.1); (2) convert + drive — drive_transaction(req) translates the transaction's fields into pin activity on the virtual interface, honoring the protocol's timing (clock edges, handshakes, wait states) — this is the "how" (Module 8.1), and it's the driver's core skill; (3) complete — item_done() signals the transaction is driven (optionally item_done(rsp) to return a response, Module 11.3). The drive_transaction task is the conversion: it reads tr's fields (tr.addr, tr.is_read) and drives them onto the pins with the right timing — it does not decide what the fields are. The final comment states the discipline negatively: the driver does not generate the transaction's content (the sequence did), decide what to send (the sequence did), or check correctness (the scoreboard does). The shape to carry: a driver is get_next_item → drive → item_done in run_phase, with the conversion (transaction-to-pins) as its one real job — small, bounded, and decision-free.
Verification Perspective — separation of concerns
The driver's narrowness is one part of a three-way separation — sequence (decide), driver (execute), scoreboard (judge). Seeing the three roles together is seeing why the driver must stay narrow.
The three-way separation is why the driver's job is narrow, and it's the backbone of a maintainable testbench. The sequence decides — it generates the transaction's content and chooses the stimulus (Module 9): what to drive. The driver executes — it converts the transaction into pin activity and drives it, making no decisions: how to drive what it was given. The scoreboard judges — it compares the observed behavior (from the monitor) against expected: whether the DUT was correct. Each component owns exactly one concern — decide, execute, judge — and the power of this is independent change: you can write new stimulus (a new sequence) without touching the driver or scoreboard; change the timing or protocol (the driver's conversion) without touching the sequence or scoreboard; add new checks (the scoreboard) without touching either. This is the reuse the whole methodology delivers, and it depends on the driver staying narrow. If the driver also decided (generated stimulus), then changing the stimulus would mean changing the driver — coupling decide-and-execute. If the driver also judged (checked correctness), then changing the checks would mean changing the driver — coupling execute-and-judge. Either coupling breaks the independence. So the driver's discipline — execute only — is not an isolated rule; it's one leg of the three-way separation, and the whole structure stands or falls on each component staying in its lane. The driver's lane is execution; the DebugLab is what happens when it strays.
Runtime / Execution Flow — the pull-convert-complete loop
At run time, the driver is a perpetual loop: pull a transaction, convert and drive it, complete it, repeat — paced by the back-pressure of driving (Module 8.3). The flow shows the loop and where each responsibility sits.
The loop is the driver's entire runtime existence, and its rhythm connects back to everything before. Pull (step 1): get_next_item takes the next granted transaction from the sequencer — and blocks until one is available, so a driver with no stimulus simply waits (Module 11.1). This is the driver's only input: it receives transactions, it doesn't make them. Convert + drive (step 2): the driver translates the transaction's fields into pin activity on the virtual interface, honoring the protocol's timing (clock edges, handshakes, wait states) — and this consumes simulation time (the time-consuming "how", Module 8.3). This is the driver's one real job, where all its protocol knowledge lives. Complete (step 3): item_done signals the transaction is driven (unblocking the sequence's finish_item, Module 9.4), optionally returning a response (Module 11.3). Loop (step 4): back to pull, forever. The loop is paced by the driving: because step 2 takes real time (gated by the DUT accepting), the driver advances at the DUT's rate — which is the back-pressure that paces the whole stimulus chain (Module 8.3). The runtime insight is that the driver is a steady executor: it consumes transactions and produces pin activity at the DUT's pace, perpetually, making no decisions — just pull, convert, complete. This perpetual, decision-free loop is the driver's whole behavior, and its narrowness (only these three steps) is exactly the discipline the chapter is about.
Waveform Perspective — the driver executing a transaction
The driver's job is visible on a timeline: it pulls a transaction (zero-time), drives it on the pins over real cycles (the conversion), and completes it — turning one transaction into protocol-correct pin activity.
The driver executing one transaction: pull, drive on the pins, complete
12 cyclesThe waveform shows the driver being a driver — executing transactions on the pins. The got_item pulse marks the driver pulling a transaction (get_next_item) — a zero-time hand-off from the sequencer. Then drv_active goes high and the pins (addr, valid, wr) carry the protocol signaling over real clock cycles — addr driven to 0x40, valid asserted, following the protocol's handshake — this is the conversion, the driver translating the transaction's fields into pin activity (the "how", Module 8.1). When the driving completes, item_done fires and the loop repeats — got_item pulses again for the next transaction (addr to 0x8C), driven the same way. The crucial reading is where the information comes from: the field values on the pins (0x40, then 0x8C) came from the transactions (the sequence decided them), and the timing and signaling (when valid asserts, the handshake) came from the protocol (the driver's knowledge) — the driver combined the two but decided neither. That's execution: take the what (the transaction's fields) and perform the how (the protocol's pins). The picture to carry is the driver as a faithful executor: each pulled transaction becomes protocol-correct pin activity, perpetually, with the driver supplying only the timing/signaling and the transaction supplying the content — a clean division that is the driver's entire role, and the foundation the rest of the Drivers module builds on.
DebugLab — the driver that decided what to drive
A driver with stimulus logic baked in, so the testbench couldn't vary stimulus without editing the driver
A testbench's stimulus could not be varied from tests: changing the traffic pattern — different addresses, a different read/write mix, error cases — required editing the driver, not writing a new sequence. Worse, the same driver couldn't be reused in another test or project that needed different traffic. The sequences seemed thin (they barely controlled anything), and the driver was large, full of logic deciding what to send. Stimulus changes meant driver changes — the opposite of how it should work.
The driver had taken on the sequence's responsibility — deciding what to drive (generating/choosing transaction content) — instead of just driving what it was given:
✗ DRIVER doing the sequence's job (deciding what to drive):
task my_driver::run_phase(...);
forever begin
get_next_item(req);
req.addr = compute_next_address(); // ✗ driver DECIDES the address (sequence's job)
if (test_mode == ERROR) req.is_read = 0; // ✗ driver DECIDES the stimulus (sequence's job)
drive_transaction(req); // drives values the DRIVER chose, not the sequence
item_done();
end
→ to change the stimulus, you must edit the DRIVER → not reusable, concerns coupled
✓ DRIVER executing ONLY what it's given (decisions in the sequence):
task my_driver::run_phase(...);
forever begin
get_next_item(req);
drive_transaction(req); // drive the transaction's fields VERBATIM — no decisions
item_done();
end
// the SEQUENCE decides addr, is_read, the mix, error cases (Module 9) — the driver just executesThis is the driver-doing-too-much anti-pattern — a violation of the separation of concerns (Figure 2). The driver had embedded stimulus logic: it computed addresses, branched on test mode, decided what to drive — all of which is the sequence's job (Module 9). The consequence is exactly the symptom: because the driver decided the stimulus, changing the stimulus meant editing the driver, so the driver was not reusable across tests (each needed different decisions baked in) or projects, and the sequences were powerless (they couldn't control what they were supposed to). The driver had coupled the decide concern (sequence's) with the execute concern (its own), and that coupling destroyed the independence that lets stimulus vary freely. The fix is to strip the decisions out of the driver: it should drive the transaction's fields verbatim — drive_transaction(req) using only what req already holds — and all the decisions (address, read/write, mix, error cases) move to the sequence, where they belong (Module 9). The general lesson, and the chapter's thesis: a driver executes transactions, it does not decide them — keeping the driver a dumb executor (pull-convert-complete, driving only what it's given) is what preserves the separation of concerns, so the sequence can vary stimulus, the scoreboard can vary checks, and the driver can vary timing — all independently. A driver that decides (or judges) couples the concerns and breaks the reuse the whole methodology provides.
The tell is stimulus changes requiring driver edits. Diagnose driver-overreach:
- Check whether stimulus changes touch the driver. If varying the traffic (addresses, mix, errors) means editing the driver, the driver is deciding stimulus — a concerns violation.
- Look for decision logic in the driver. Address computation, test-mode branching, content generation, or correctness checks in the driver are responsibilities that belong elsewhere (sequence or scoreboard).
- Check whether the driver drives the transaction verbatim. A correct driver uses only the pulled transaction's fields; one that modifies or generates fields is overreaching.
- Assess sequence thinness. If the sequences barely control the stimulus while the driver is large with logic, the responsibilities have leaked from sequence into driver.
Keep the driver a dumb executor:
- Drive only the pulled transaction's fields, verbatim. The driver makes no decisions about content; it executes what the sequence decided.
- Put all stimulus decisions in the sequence. Addresses, mixes, error cases, test-mode behavior — these are the sequence's job (Module 9), so the driver stays reusable.
- Put all correctness checks in the scoreboard. The driver does not judge results; checking is centralized in the scoreboard, comparing observed vs expected.
- Keep the driver small. A driver should be roughly pull-convert-complete; if it's growing logic, suspect responsibilities have leaked in — move them to the right component.
The one-sentence lesson: a driver executes transactions, it does not decide them — so embedding stimulus logic (addresses, mixes, test-mode decisions) in the driver couples the sequence's decide concern with the driver's execute concern, making stimulus changes require driver edits and breaking reuse; keep the driver a dumb executor that drives only what it's given, and put all decisions in the sequence (and all checks in the scoreboard).
Common Mistakes
- The driver deciding what to drive. Generating or choosing transaction content is the sequence's job; the driver should drive only the pulled transaction's fields verbatim.
- The driver checking correctness. Judging the DUT's behavior is the scoreboard's job; a driver that checks couples driving with judging.
- The driver modifying the transaction it's given. Mutating
req(especially fields others reference) is overreach and risks aliasing; treat the pulled transaction as the sequence's decision to execute, not change. - Forgetting to get the virtual interface. Without the vif (config DB, Module 7), driving crashes on a null handle; get it in
build_phaseand fatal if absent. - Skipping
item_doneon a path. Everyget_next_itemneeds exactly oneitem_done(Module 9.4); a skipped completion hangs the sequence. - A bloated driver. A driver growing logic beyond pull-convert-complete signals leaked responsibilities; keep it small and decision-free.
Senior Design Review Notes
Interview Insights
A driver is a uvm_driver component with a narrow, precise job: pull transactions from the sequencer, convert each into pin-level activity on the DUT interface, and complete them. Concretely, in its run_phase it runs a forever loop of three steps. First, it pulls the next transaction from the sequencer with get_next_item, which blocks until one is granted. Second, it converts that transaction into the protocol's pin-level signaling — driving the virtual interface, honoring the protocol's timing like clock edges, handshakes, and wait states — which is the how, the time-consuming translation from the transaction world to the pin world. Third, it completes the transaction with item_done, optionally returning a response. Then it loops. The driver is the sole translation point between transactions and pins, the only component that drives the pins, and its core skill is the conversion — turning the transaction's fields into protocol-correct pin activity. Equally important is what the driver is not responsible for: it does not decide what to drive, generate stimulus, or check correctness. Deciding what to drive and generating stimulus is the sequence's job; checking whether the DUT behaved correctly is the scoreboard's job. The driver only executes the transaction it's given, verbatim, making no decisions. This narrowness is deliberate, because it's one leg of a three-way separation — sequence decides, driver executes, scoreboard judges — that lets each change independently: new stimulus without touching the driver, new timing without touching the sequence, new checks without touching either. So the driver's responsibility is execution at the pin boundary, nothing more.
A driver should not decide what to drive, generate stimulus, or check correctness — because those are other components' responsibilities, and embedding them in the driver couples concerns and breaks reuse. Deciding what to drive and generating stimulus — choosing addresses, the read/write mix, error cases, test-mode behavior — is the sequence's job. If the driver did this, then changing the stimulus would require editing the driver, so the driver couldn't be reused across tests or projects that need different traffic, and the sequences would be powerless to control what they're supposed to. Checking correctness — judging whether the DUT behaved as expected — is the scoreboard's job, done by comparing observed against expected. If the driver did this, it would couple driving with judging, duplicate the scoreboard's logic, and mean that changing the checks requires editing the driver. The underlying reason is the three-way separation of concerns: the sequence decides, the driver executes, the scoreboard judges, and each owning exactly one concern is what lets each change independently. A driver that decides couples decide-and-execute; a driver that judges couples execute-and-judge; either coupling destroys the independence that is the whole reuse argument. So the discipline is to keep the driver a dumb executor that drives only the transaction it's given, verbatim, making no decisions about content and no judgments about correctness. The practical test is whether stimulus or check changes ever require editing the driver — if they do, a responsibility has leaked in and should move back to the sequence or scoreboard. Keeping the driver narrow is what preserves the clean, reusable structure.
Exercises
- List the responsibilities. State the driver's three core responsibilities and the three things it must not do, with the component that owns each of the latter.
- Spot the overreach. Given a driver that computes addresses and branches on test mode, identify which responsibility has leaked in and where it belongs.
- Write the loop. Sketch the canonical driver
run_phase(pull, convert/drive, complete) and thebuild_phasethat gets the virtual interface. - Defend the separation. Explain why keeping the driver a dumb executor lets stimulus, timing, and checks all change independently.
Summary
- A driver is a
uvm_drivercomponent whose narrow job is pull (get_next_itemfrom the sequencer), convert + drive (translate the transaction into pin-level activity on the virtual interface, honoring protocol timing — the "how"), and complete (item_done, optionally a response). - It is the sole translation point between the transaction world and the pin world — the only component that drives the pins (the monitor observes them) — owning exactly the transaction-to-pin translation in the drive direction.
- Its discipline is execute, don't decide: it must not decide what to drive or generate stimulus (the sequence's job) or check correctness (the scoreboard's job) — it drives only the pulled transaction's fields, verbatim.
- This narrowness is one leg of the three-way separation of concerns — sequence decides, driver executes, scoreboard judges — which lets each change independently (new stimulus, new timing, new checks), the basis of reuse.
- The durable rule of thumb: keep the driver a dumb executor — pull, convert/drive the transaction verbatim, complete — making no stimulus decisions and no correctness checks, because a driver that decides or judges couples the concerns and breaks the reuse the methodology depends on; decisions belong in the sequence, checks in the scoreboard, and only the translation belongs in the driver.
Next — Transaction Conversion: the driver's one real job is converting a transaction into pin activity; the next chapter drills into that conversion — how a transaction's fields map to signal-level driving, the protocol encoding, and the techniques for clean, correct transaction-to-pin translation.