Skip to content

UVM

Reusable Traffic Generation

Building traffic generators portable across tests, levels, and projects — parameterized with knobs, decoupled through standard interfaces, factory-overridable, and packaged as a sequence library that ships with the agent.

Advanced Sequences · Module 10 · Page 10.6

The Engineering Problem

The sequences modules built up a deep toolkit — composition, control, random generation, virtual/layered/nested sequences, arbitration, parallelism (Modules 9–10). All of it converges on one goal: reusable stimulus. A traffic generator written for one test, baked into one testbench, is a one-off — rewritten for the next test, reinvented for the next project, broken when the agent moves from block-level to SoC-level. The payoff of all the advanced techniques is wasted unless the result is portable: a generator written once that works across tests, across integration levels, and across projects.

This chapter is the discipline that makes traffic generation reusable. A reusable generator follows a few principles: it's parameterized (behavior in knobs, nothing hardcoded), it's decoupled (reaches context only through standard interfaces — p_sequencer, the config DB — never hardcoded handles or paths), it's factory-overridable (items and sub-sequences created via the factory, so they can be substituted), and it's packaged as a self-contained sequence library that ships with the agent/VIP. Follow them and one well-built generator serves many scenarios and survives relocation; violate them — hardcode a path, an address, a handle — and the generator breaks the moment its context changes. This chapter is reusable traffic generation: the reuse axes, the principles, the packaging, and why a hardcoded path is the bug that breaks reuse.

What makes traffic generation reusable across tests, levels, and projects — parameterization, decoupling through standard interfaces, factory-overridability, and packaging as a sequence library — and why does a hardcoded path or handle break reuse the moment the agent is relocated?

Motivation — why reusable stimulus is the goal

Reusable traffic generation is the point of the whole sequences toolkit, because stimulus is expensive to build and valuable to reuse:

  • Good stimulus is expensive, so reuse amortizes it. A thorough, constrained-random traffic generator for a real protocol is a significant investment. Reusing it across tests, levels, and projects amortizes that cost; rewriting it each time wastes it.
  • Agents get reused, so their stimulus must travel with them. An agent built for a block is reused in the subsystem and the SoC. If its traffic generator is baked into the block test, the reuse stops at the agent boundary; if the generator ships with the agent, it travels too.
  • Tests vary, so the generator must be parameterized. A reusable generator faces many scenarios — short/long, read-heavy/write-heavy, with/without errors. Behavior must live in knobs the test sets, not hardcoded values, or every variation forks a new generator.
  • Topology changes on relocation, so context must come through stable interfaces. When an agent moves from block to SoC, its hierarchical path changes. A generator that reaches context by hardcoded path breaks; one that reaches it through p_sequencer/config DB — interfaces that move with the agent — survives.
  • Types get substituted, so creation must go through the factory. Reuse often means overriding — a derived item, an error-injecting variant. A generator that creates via the factory (type_id::create) is overridable; one that uses new can't be substituted.

The motivation, in one line: the sequences toolkit exists to produce reusable stimulus, and reuse across tests, levels, and projects requires generators that are parameterized (knobs), decoupled (stable interfaces, not hardcoded paths), factory-overridable, and packaged with the agent — so the expensive work of building good stimulus is done once and reused everywhere.

Mental Model

Hold reusable traffic generation as building a library, not a one-off script:

A reusable traffic generator is a well-designed library, not a one-off script — configurable, decoupled, extensible, and packaged, so it's used everywhere instead of rewritten each time. A one-off script hardcodes its inputs, reaches directly into whatever's around it, and works only where it was written. A library is the opposite, and the same four properties that make software libraries reusable make traffic generators reusable. It's configurable: behavior comes from parameters the caller sets (knobs), so one library covers many uses rather than a script per case. It's decoupled: it depends only on stable interfaces (it calls an API, never reaches into another module's internals), so it keeps working when its surroundings change — for a generator, that means reaching context through p_sequencer/config DB, never a hardcoded path. It's extensible: callers can substitute parts (a derived type), so it adapts without being rewritten — for a generator, the factory. And it's packaged: it ships as a self-contained unit (the library travels with whatever uses it), so it's available wherever the agent goes. The script that hardcodes a path breaks the moment you move it; the library that calls an interface travels.

So building reusable traffic is building a library: parameterize the behavior, depend only on stable interfaces, make parts substitutable, and package it with the agent — so the generator is used, not rewritten. The hardcoded one-off is the anti-pattern; the configurable, decoupled, packaged library is the goal.

Visual Explanation — the axes of reuse

The defining picture is one generator, many uses: a single well-built traffic generator reused across tests, across integration levels, and across projects.

One reusable traffic generator reused across tests, integration levels, and projectsacross tests (knobs)across levels (decoupled)acrosslevels…across projects (packaged)acrossprojects…Reusable traffic generatorparameterized · decoupled · factory ·packagedAcross testssmoke / stress / error — onegenerator, many configsAcross levelsblock → subsystem → SoC — samegeneratorAcross projectsa VIP's generator reused on manychips12
Figure 1 — a reusable traffic generator is reused across three axes. Across tests: the same generator, parameterized differently, serves smoke, stress, and error tests. Across integration levels: the same generator works whether the agent is standalone (block) or embedded in a subsystem or SoC. Across projects: a protocol VIP's generator is reused on many chips. One generator at the center, reused outward along all three axes — which is what the parameterization, decoupling, and packaging principles enable.

The figure shows the three axes along which a good generator is reused, and what enables each. Across tests: the same generator, parameterized differently (knobs — Module 9.6), serves a smoke test (light traffic), a stress test (flood), and an error test (fault injection) — one generator, many configs, no rewriting per test. This axis is enabled by parameterization. Across integration levels: the same generator (and its agent) works whether the agent is standalone at block level, or embedded in a subsystem, or deep in an SoC — because the generator is decoupled (reaches context through interfaces that move with the agent, not hardcoded paths that break on relocation). This axis is enabled by decoupling. Across projects: a protocol VIP's generator (an AXI traffic generator, a PCIe generator) is reused on many chips — the investment in one good generator pays off across the company's projects. This axis is enabled by packaging (the generator ships with the VIP) and factory-overridability (each project can substitute variants). The unifying picture is one generator at the center, reused outward along all three axes — and the principles in the next sections are precisely what make that possible. A one-off generator reaches none of these axes; a well-built one reaches all three, which is why reuse is the payoff of the whole toolkit.

RTL / Simulation Perspective — a reusable generator's principles in code

A reusable generator's four principles are visible in its code: knobs for parameterization, p_sequencer/config for decoupling, the factory for overridability, and a package for distribution.

a reusable traffic generator: parameterized, decoupled, factory, packaged
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// (shipped in a package with the agent/VIP — self-contained, no testbench-specific dependencies)
class traffic_gen_seq extends uvm_sequence #(bus_item);
  `uvm_object_utils(traffic_gen_seq)
  `uvm_declare_p_sequencer(bus_sequencer)        // DECOUPLED: typed sequencer access, not hardcoded handles
 
  rand int unsigned num;                          // PARAMETERIZED: knobs, nothing hardcoded
  rand int unsigned base_addr;
  constraint c_def { num inside {[10:1000]}; }    // overridable by derived/config
 
  function new(string name="traffic_gen_seq"); super.new(name); endfunction
 
  task body();
    bus_item req;
    // DECOUPLED: reach context through the config DB / p_sequencer — NOT a hardcoded path
    if (!uvm_config_db#(int)::get(m_sequencer, "", "base_addr", base_addr)) base_addr = 0;
    repeat (num) begin
      req = bus_item::type_id::create("req");      // FACTORY: overridable item type (not new)
      start_item(req);
      assert(req.randomize() with { addr >= base_addr; });
      finish_item(req);
    end
  endtask
endclass
//   reuse: a test sets the knobs (num, constraints) and config (base_addr); the generator adapts —
//          no edit to the generator across tests, levels, or projects

The code embodies the four principles. Parameterized: num, base_addr, and the constraint are knobsrand fields and an overridable constraint (c_def) — so a test shapes the traffic by setting them, not by editing the generator (Module 9.6). Decoupled: the generator reaches context only through standard interfaces — `uvm_declare_p_sequencer(bus_sequencer) for typed sequencer access (Module 9.7), and uvm_config_db#(int)::get(...) for the base address — never a hardcoded hierarchical path or a directly-grabbed handle, so it survives relocation (the DebugLab is what happens when this is violated). Factory-overridable: items are created with bus_item::type_id::create (not new), so a test or project can substitute a derived item type (an error-injecting variant) via a factory override (Module 6). Packaged: the comment notes it ships in a package with the agent/VIPself-contained, with no testbench-specific dependencies, so it travels with the agent across levels and projects. The reuse, noted at the bottom, is the payoff: a test sets the knobs and config, and the generator adapts — no edit across tests, levels, or projects. The shape to carry: knobs (parameterize) + p_sequencer/config (decouple) + type_id::create (factory) + package (distribute) = a generator written once and reused everywhere.

Verification Perspective — the four reuse principles

The four principles are a checklist: a generator that satisfies all four is reusable; missing any one limits reuse along one axis. Knowing which principle each axis depends on is knowing how to build (and audit) a reusable generator.

The four reuse principles each enable a facet of reuse→ acrosstests→ across levels→ substitutedtypes→ travels with agentParameterizeknobs, nothing hardcodedReuse across testsone generator, many configsDecouplep_sequencer/config, notpathsReuse across levelssurvives relocationFactory-overridabletype_id::createSubstituted typeserror variants, derived itemsPackageself-contained libraryTravels with the agentavailable everywhere12
Figure 2 — the four reuse principles and what each enables. Parameterize (knobs, nothing hardcoded) → reuse across tests. Decouple (context through p_sequencer/config DB, never hardcoded paths) → reuse across integration levels (survives relocation). Factory-overridable (type_id::create) → reuse with substituted types (across projects/variants). Package (self-contained library shipping with the agent) → reuse anywhere the agent goes. Each principle enables one facet of reuse; all four together make a generator fully portable.

The four principles map to the facets of reuse, and a generator needs all four to be fully portable. Parameterizereuse across tests: with behavior in knobs (Module 9.6), one generator covers smoke, stress, and error tests by configuration; a generator that hardcodes its count or mix needs a fork per test. Decouplereuse across levels: by reaching context through p_sequencer/config DB — interfaces that move with the agent — the generator survives relocation from block to SoC; a generator that hardcodes a hierarchical path breaks when the path changes (the DebugLab). Factory-overridablereuse with substituted types: by creating via type_id::create, a test or project can override the item or sub-sequence type (an error variant); a generator using new can't be substituted. Packagetravels with the agent: by shipping as a self-contained library with the agent/VIP, the generator is available wherever the agent is instantiated; a generator scattered in test files doesn't travel. The verification value of the checklist is diagnostic: if a generator can't be reused along some axis, the missing principle tells you why — can't vary it per test? not parameterized; breaks at SoC? not decoupled; can't inject errors? not factory-overridable; not available in the new env? not packaged. So building a reusable generator is satisfying all four, and auditing one for reuse is checking each — the principles are both the construction recipe and the review checklist.

Runtime / Execution Flow — packaging a generator with the agent

The principle that makes a generator travel is packaging: the traffic library ships with the agent/VIP, so wherever the agent is instantiated, its stimulus comes along. The structure shows how.

A VIP package bundles the agent and its sequence library so stimulus travels with the agentVIP package: agent + sequence library travel togetherVIP package: agent + sequence library travel togetherVIP package (ships as a unit)the reusable verification component — instantiated at block, subsystem, or SoC level.the reusable verification component — instantiated at block, subsystem, or SoC level.Agent (driver · monitor · sequencer)the structure that drives/observes the interface — reused at every level.the structure that drives/observes the interface — reused at every level.Sequence library (traffic generators)the parameterized, decoupled, factory-overridable generators — travel with the agent.the parameterized, decoupled, factory-overridable generators — travel with the agent.Test (parameterizes + runs)at any level, sets knobs/config and runs a generator on the agent's sequencer — no rewrite.at any level, sets knobs/config and runs a generator on the agent's sequencer — no rewrite.
Figure 3 — a reusable generator is packaged with the agent (a VIP). The VIP package contains the agent (driver, monitor, sequencer) AND its sequence library (the traffic generators). When the VIP is instantiated — at block, subsystem, or SoC level — both the agent and its sequences come along. A test (at any level) reaches into the library, parameterizes a generator, and runs it on the agent's sequencer. The stimulus travels with the agent because it's packaged together, which is what makes reuse across levels and projects practical.

Packaging is what turns the principles into practical reuse, because it's what makes the generator available wherever the agent goes. The VIP package — the reusable verification component — ships as a unit that contains both the agent (driver, monitor, sequencer — the structure that drives and observes the interface) and the sequence library (the parameterized, decoupled, factory-overridable traffic generators). The key is that the two are bundled: when the VIP is instantiated — at block level standalone, or embedded in a subsystem, or deep in an SoC — both the agent and its sequences come along together. A test at any level then reaches into the library, parameterizes a generator (sets its knobs and config), and runs it on the agent's sequencer — without rewriting the generator, because it's the same library that shipped with the agent. This is why packaging is the principle that operationalizes reuse: parameterization, decoupling, and factory-overridability make a generator capable of reuse, but packaging with the agent makes it present wherever reuse is needed. The anti-pattern is a generator written in a test file, separate from the agent — it's not present when the agent is reused elsewhere, so the reuse stops. So a reusable traffic generator is not just well-built; it's shipped with its agent, so that instantiating the agent anywhere brings its stimulus along — which is exactly what makes block-to-SoC and cross-project reuse practical rather than aspirational.

Waveform Perspective — one generator, many traffic profiles

A reusable generator's parameterization is visible on a timeline: the same generator, configured differently, produces different traffic — which is what lets one generator serve many tests.

One reusable generator, three configs, three traffic profiles

12 cycles
One reusable generator, three configs, three traffic profilesconfig A (light): generator reads knobs → sparse trafficconfig A (light): gene…config B (stress): same generator → dense trafficconfig B (stress): sam…config C (error): same generator → error transactions (E)config C (error): same…one generator, three configs — no editone generator, three c…clkconfigAAABBBBCCCC--cfg_readitemt--tttttEtEt--t0t1t2t3t4t5t6t7t8t9t10t11
Figure 4 — the same generator parameterized three ways. cfg_read marks the generator reading its knobs/config at the start of each run. With config A (light), it produces a few transactions (item sparse). With config B (stress), the same generator floods (item dense). With config C (error), it injects error transactions (item shows E). One generator, three configs, three profiles — no edit to the generator. Parameterization is what lets one reusable generator serve smoke, stress, and error tests.

The waveform shows parameterization enabling reuse — the same generator, three behaviors. Each run begins with cfg_read (the generator reading its knobs and config, as in the code), and the resulting item traffic differs entirely by configuration. With config A (light), the generator produces sparse traffic — a few transactions — serving a smoke test. With config B (stress), the same generator produces dense, back-to-back traffic — a stress test. With config C (error), the same generator produces error transactions (E) interleaved — an error test (factory-overridden item type, or an error knob). The point is that one generator produced three completely different traffic profiles with no edit — only the configuration changed. This is the across-tests axis of reuse made concrete: parameterization means a single generator covers many scenarios by configuration, not by forking a new generator per test. Extend the picture and the other axes appear: the same generator (decoupled) produces these profiles whether the agent is at block or SoC level, and (packaged) wherever the agent is instantiated. The waveform's signature — identical generator, configuration-driven variety — is exactly what reusable traffic generation delivers: build the generator once, and configure it for everything.

DebugLab — the generator that broke when the agent moved to SoC

A traffic generator that worked at block level but broke when its agent was reused in an SoC

Symptom

A traffic generator worked perfectly in the block-level testbench, where the agent was instantiated near the top. But when the same agent was reused in the SoC testbench — instantiated deep in the hierarchy — the generator crashed (or silently misbehaved): it failed to reach a component it needed, with a null handle. The generator code was unchanged and correct at block level; it only broke when the agent was relocated to a deeper hierarchy.

Root cause

The generator reached context by a hardcoded hierarchical path — valid at block level, wrong at SoC level where the path changed:

why relocation broke the generator
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
block-level hierarchy:   uvm_test_top.env.agent          ← generator hardcoded this path
SoC-level hierarchy:     uvm_test_top.soc_env.sub.agent  ← the agent moved — path is now DIFFERENT
 
generator (block):
  cfg_comp = uvm_top.find("uvm_test_top.env.agent.cfg");  // ✗ HARDCODED absolute path
  ... use cfg_comp ...
 
at SoC: the path "uvm_test_top.env.agent.cfg" no longer exists → find returns null → crash
  (the agent is now at uvm_test_top.soc_env.sub.agent — the hardcoded path is wrong)
 
fix — reach context through a RELOCATION-INDEPENDENT interface, not a hardcoded path:
  // via p_sequencer (the sequencer the generator runs on — moves with the agent):
  cfg = p_sequencer.cfg;
  // or via the config DB (set relative to the agent, retrieved by the generator):
  if (!uvm_config_db#(agent_cfg)::get(m_sequencer, "", "cfg", cfg)) `uvm_fatal(...)

This is the hardcoded-path-breaks-reuse bug, the canonical reusability failure. The generator needed to reach a component's context (a config), and it reached it by a hardcoded absolute hierarchical pathuvm_top.find("uvm_test_top.env.agent.cfg"). That path was correct at block level, where the agent sat at uvm_test_top.env.agent. But when the same agent was reused in the SoC testbench, it was instantiated at a different path — uvm_test_top.soc_env.sub.agent — so the hardcoded path no longer existed, find returned null, and the generator crashed. The generator wasn't wrong; it was not relocation-independent — it assumed a fixed topology, which is exactly what reuse violates. The fix is to reach context only through interfaces that move with the agent: p_sequencer (the sequencer the generator runs on, which is part of the agent, so it relocates with it) or the config DB (with the value set relative to the agent, so the generator's relative retrieval works at any path). Both are relocation-independent — they don't name an absolute path, so they work wherever the agent is instantiated. The general lesson, and the chapter's thesis: a reusable generator must be topology-independent — it must never hardcode a hierarchical path, an absolute handle, or any assumption about where the agent sits — because reuse means the agent moves, and anything that names a fixed location breaks on the move. Reach context through p_sequencer/config DB, which travel with the agent.

Diagnosis

The tell is a generator that works at one level but breaks when the agent is relocated. Diagnose reuse-breaking dependencies:

  1. Correlate the break with relocation. A generator correct at block level but broken at SoC (or vice versa), unchanged, points at a topology dependency — something that assumed a fixed location.
  2. Look for hardcoded paths/handles. Search the generator for absolute hierarchical strings (uvm_top.find(...), hardcoded paths) or directly-grabbed handles — these break on relocation.
  3. Check how context is reached. Confirm the generator reaches config/siblings through p_sequencer or the config DB (relocation-independent), not absolute paths.
  4. Audit the other principles too. A generator that can't relocate often also hardcodes counts/addresses (not parameterized) or uses new (not factory) — check the full reuse checklist.
Prevention

Make the generator topology-independent and parameterized:

  1. Reach context only through p_sequencer/config DB. Never hardcode a hierarchical path or absolute handle; use interfaces that move with the agent, so the generator survives relocation.
  2. Put all behavior in knobs. Counts, addresses, mixes — make them rand fields or config, not hardcoded, so the generator varies by configuration across tests.
  3. Create via the factory. type_id::create for items and sub-sequences, so types can be substituted per test/project.
  4. Package with the agent. Ship the generator in the agent's/VIP's library, self-contained, so it travels wherever the agent is instantiated and stays available across levels and projects.

The one-sentence lesson: a reusable traffic generator must be topology-independent — reaching context through relocation-independent interfaces (p_sequencer, config DB), never a hardcoded hierarchical path — because reuse means the agent moves (block → SoC), and a hardcoded path that's correct in one place returns null the moment the agent is relocated.

Common Mistakes

  • Hardcoding a hierarchical path or absolute handle. It breaks when the agent is relocated (block → SoC); reach context through p_sequencer/config DB, which move with the agent.
  • Hardcoding counts, addresses, or mixes. A generator with baked-in values needs a fork per variation; put behavior in knobs (Module 9.6) so it varies by configuration.
  • Creating items with new. Non-factory creation can't be overridden; use type_id::create so types can be substituted (Module 6).
  • Writing the generator in the test, not the agent's library. A generator in a test file doesn't travel with the agent; package it in the agent's/VIP's library.
  • Assuming a fixed topology or what else runs. A reusable generator makes no assumptions about siblings or where it sits; depend only on stable interfaces.
  • Coupling the generator to one test's specifics. Test-specific logic embedded in the generator limits it to that test; keep the generator generic and parameterize per test.

Senior Design Review Notes

Interview Insights

Four principles. First, parameterization: behavior lives in knobs — rand fields and config — with nothing hardcoded, so one generator covers many scenarios by configuration, which gives reuse across tests; a smoke test, a stress test, and an error test are the same generator configured differently rather than separate generators. Second, decoupling: the generator reaches context only through standard interfaces like p_sequencer and the config DB, never through hardcoded hierarchical paths or absolute handles, which gives reuse across integration levels because those interfaces move with the agent, so the generator survives relocation from block to subsystem to SoC. Third, factory-overridability: items and sub-sequences are created with type_id::create rather than new, so a test or project can substitute a derived type, like an error-injecting variant, through a factory override, which supports reuse with variants across projects. Fourth, packaging: the generator ships as a self-contained sequence library bundled with the agent or VIP, so it travels wherever the agent is instantiated and is available at every level and on every project. Together these turn a generator from a one-off into a library: configurable, decoupled, extensible, and packaged. The way to think about it is that a one-off script hardcodes its inputs and reaches into its surroundings and works only where written, while a library parameterizes its inputs, depends only on stable interfaces, allows substitution, and ships as a unit — and the same properties that make software libraries reusable make traffic generators reusable. Missing any one principle limits reuse along the corresponding axis, so a fully reusable generator satisfies all four.

The usual cause is that the generator reaches context by a hardcoded hierarchical path that's correct at block level but wrong once the agent is relocated to a deeper position in the SoC. At block level, the agent might sit at uvm_test_top.env.agent, and if the generator does something like uvm_top.find of an absolute path to the agent's config, that works because the path is correct there. But reuse means the same agent gets instantiated somewhere else — in the SoC it might be at uvm_test_top.soc_env.sub.agent — so the hardcoded path no longer exists, find returns null, and the generator crashes or misbehaves with a null handle. The generator code is unchanged and was correct at block level; it broke only because the agent moved and the generator assumed a fixed topology, which is exactly what reuse violates. The fix is to reach context only through interfaces that move with the agent and don't name an absolute location: p_sequencer, which is the sequencer the generator runs on and is part of the agent, so it relocates with it; or the config DB, where the value is set relative to the agent and retrieved relatively by the generator, so the retrieval works at any path. Both are relocation-independent because they don't hardcode where the agent sits. The general lesson is that a reusable generator must be topology-independent — never hardcoding a hierarchical path, an absolute handle, or any assumption about where the agent is — because reuse means the agent moves, and anything that names a fixed location breaks on the move. So the symptom of correct-at-one-level-broken-at-another, with unchanged code, almost always points to a hardcoded path or handle that needs to become a p_sequencer or config DB access.

Exercises

  1. Audit for reuse. Given a generator that hardcodes a path, a transaction count, and uses new, list which reuse principle each violates and the fix.
  2. Decouple the context. Rewrite a generator that reaches a config via a hardcoded path to reach it through p_sequencer/config DB, and explain why it now survives relocation.
  3. Map principle to axis. For each reuse axis — across tests, across levels, across projects — name the principle that most enables it.
  4. Package it. Explain why a generator written in a test file doesn't travel with the agent, and where it should live instead.

Summary

  • Reusable traffic generation builds generators portable across three axes — tests, integration levels, and projects — which is the payoff of the entire sequences toolkit.
  • It follows four principles: parameterize (behavior in knobs, nothing hardcoded → across tests), decouple (context through p_sequencer/config DB, never hardcoded paths → across levels, survives relocation), factory-overridable (type_id::create → substituted types), and package (self-contained library shipping with the agent/VIP → travels everywhere).
  • Decoupling is what survives relocation: a reusable generator must be topology-independent, reaching context through interfaces that move with the agent — never a hardcoded hierarchical path, which is correct in one place and null the moment the agent is relocated.
  • Packaging operationalizes reuse: parameterization/decoupling/factory make a generator capable of reuse, but shipping it with the agent makes it present wherever the agent is instantiated — a generator stranded in a test file doesn't travel.
  • The durable rule of thumb: build traffic generators like libraries — parameterized (knobs), decoupled (stable interfaces, never hardcoded paths), factory-overridable (type_id::create), and packaged with the agent — so one well-built generator is configured for every test, survives block-to-SoC relocation, adapts via overrides, and travels to every project, instead of being rewritten each time.

Next — Sequencer Architecture: the sequences modules built everything that runs on a sequencer; the next module opens the sequencer itself — its internal architecture, how it brokers sequences and the driver, and the structure beneath the arbitration and handshake you've been using.