Skip to content

UVM RAL · Chapter 7 · RAL Sequences

Register Sequences

Every register access has to be driven from somewhere, and in a UVM environment that somewhere is a sequence. A register sequence is a subclass of the base register-sequence class whose body calls write, read, update, and mirror on registers by name, letting you write stimulus in register terms rather than bus terms. Three things make it work. It carries a model handle you point at the register model, it runs on the bus sequencer so its accesses become real transactions, and each access takes a parent link so it is properly sequenced. This page shows how to write and run a register sequence, where it sits in the stimulus layering from test down to bus, and breaks the most basic mistake of never setting the sequence's model handle, so it has no registers to operate on and does nothing.

Foundation11 min readUVM RALuvm_reg_sequenceRegister StimulusmodelSequencerparent

Chapter 7 · Section 7.1 · RAL Sequences

1. Why Should I Learn This?

Register sequences are where all your register stimulus lives, so knowing how to write one — with its model handle, its sequencer, and .parent(this) on each access — is the practical foundation of the whole register-test layer. Get the register sequence right and every register operation you have learned becomes usable stimulus you can run, layer, and reuse; get its basics wrong — no model, wrong sequencer, missing parent — and the sequence silently does nothing or accesses the wrong thing, which is a confusing failure precisely because the register operations inside it look correct.

Learning the register sequence, and its place in the stimulus layering, is what turns the register model and its access methods into a running test. It is the vehicle that carries everything from Chapters 4–6 into actual verification, and the base for the built-in sequences (7.2) and the custom register tests to come.

2. Industry Story — the sequence that operated on nothing

An engineer writes their first register sequence — a uvm_reg_sequence subclass whose body() configures a block: model.cfg.write(...), model.ctrl.write(...), a few reads. In the test, they create the sequence and start it on the bus sequencer. Nothing happens — no bus activity, no configuration, and depending on how the handles resolve, a null-handle error somewhere in body().

The cause is that the sequence's model handle was never set. uvm_reg_sequence carries a model field, and the sequence's body() reaches registers through it — model.cfg — but the test created the sequence and started it without ever assigning seq.model = env.reg_model. So model is null, and model.cfg is a null dereference (or the sequence has no register model to operate on and does nothing useful). The register operations in body() are all correct; the sequence simply was never handed the model they operate on. The fix is one line in the test — seq.model = env.reg_model; before seq.start(...). The lesson: a register sequence operates on registers through its model handle, so the test must set seq.model to the register model before starting it; without it, the sequence has no registers to reach and either nulls out or does nothing, however correct its register operations look.

3. Concept — the register sequence and the stimulus layering

A register sequence is built on uvm_reg_sequence, and it has three essentials:

  • The model handle. uvm_reg_sequence carries a model field (typed as the register block, or the base uvm_reg_block). The test sets it to the register model, and body() reaches registers through it: model.cfg.write(status, value, .parent(this)).
  • Running on a sequencer. The sequence is started on a sequencer — typically the bus agent's sequencer that the map is bound to (via set_sequencer, 5.1) — so its register accesses become real bus transactions on that bus. uvm_reg_sequence can also wrap an existing sequencer.
  • .parent(this) on each access. Passing .parent(this) to a register operation associates that access with the running sequence, so it participates in the sequencer's arbitration and sequencing as a child of this sequence, rather than as an unparented access.

Where it sits — the stimulus layering, from the test down to the bus:

  • A test creates the register sequence, sets its model, and starts it on the sequencer.
  • Optionally a virtual sequence coordinates the register sequence with other stimulus (data traffic, interrupts) on a virtual sequencer.
  • The register sequence calls register operations by name.
  • Each register operation is routed by RAL through the frontdoor (map, adapter, bus sequencer) to the DUT (Chapters 4–5).

Here is that layering:

Register stimulus layers: test, virtual sequence, register sequence, register operations, RAL frontdoor to bustestcreates the register sequence, sets seq.model = reg_model, starts it on the sequencercreates the register sequence, sets seq.model = reg_model, starts it on the sequencervirtual sequence (optional)coordinates the register sequence with other stimulus (data, interrupts) on a virtual sequencercoordinates the register sequence with other stimulus (data, interrupts) on a virtual sequencerregister sequence (uvm_reg_sequence)reaches registers through its model handle; body() calls write/read/update by name, each with .parent(this)reaches registers through its model handle; body() calls write/read/update by name, each with .parent(this)register operationsmodel.cfg.write(status, value, .parent(this)) — abstract per-register access in register termsmodel.cfg.write(status, value, .parent(this)) — abstract per-register access in register termsRAL frontdoor → map / adapter / bus sequencer → DUTeach operation becomes a real bus transaction (Chapters 4-5)each operation becomes a real bus transaction (Chapters 4-5)
Figure 1 — the register-stimulus layers, top to bottom. A test creates the register sequence, sets its model handle, and starts it on the sequencer. An optional virtual sequence can coordinate it with other traffic. The register sequence (a uvm_reg_sequence) reaches registers through its model handle and calls write/read/update by name, each access taking .parent(this). RAL turns each operation into a real bus transaction via the frontdoor. You write stimulus in register terms at the sequence layer; RAL turns it into bus terms below.

4. Mental Model — a register sequence is stimulus written in register-ese

5. Working Example — writing and running a register sequence

A register sequence configures a block in register terms, each access parented to the sequence:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class cfg_reg_seq extends uvm_reg_sequence;
  `uvm_object_utils(cfg_reg_seq)
  function new(string name = "cfg_reg_seq"); super.new(name); endfunction
 
  virtual task body();
    uvm_status_e s;  logic [31:0] got;
    // Reach registers through the model handle; parent each access to this sequence.
    model.cfg.write(s, 32'h0000_0013, .parent(this));
    if (s != UVM_IS_OK) `uvm_error("SEQ", "cfg write failed")
 
    model.ctrl.enable.write(s, 1'b1, .parent(this));   // field-level access (2.2)
 
    // Poll a status register in register terms.
    do model.status.read(s, got, .parent(this));
    while (model.status.done.get() != 1);
  endtask
endclass

The test creates the sequence, sets its model, and starts it on the sequencer:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// In the test's run_phase:
cfg_reg_seq seq = cfg_reg_seq::type_id::create("seq");
seq.model = env.reg_model;                 // REQUIRED: give the sequence its model
seq.start(env.apb_agent.sequencer);        // run on the bus sequencer the map is bound to

The sequence body is pure register-ese — no bus signals — and each access is parented to the sequence; the test supplies the two bindings the sequence cannot supply itself: the model and the sequencer. Omit the model, and the next section is what happens.

6. Debugging Session — the register sequence with no model

1

A register sequence whose model handle is never set has no registers to reach, so it nulls out or does nothing

MODEL NOT SET
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The test creates and starts the register sequence but never sets its model:
cfg_reg_seq seq = cfg_reg_seq::type_id::create("seq");
// BUG: seq.model = env.reg_model;  <-- missing
seq.start(env.apb_agent.sequencer);
// Inside body(): model.cfg.write(...) -> 'model' is null -> null dereference / no access
Symptom

Starting the register sequence produces no register activity — no bus transactions, no configuration — and typically a null-handle error somewhere in body() where a register is first named (model.cfg). The sequence code looks correct: the register operations, the polling, the field accesses are all well-formed. Because the failure is a null reference on model rather than an error in a register operation, it can be mistaken for a bug in the register model or the sequencer connection, when the sequence simply was never given the model it operates on.

Root Cause

A uvm_reg_sequence reaches registers through its model handle, and the test never set it, so model is null. Every register access in body() is written as model.<reg>.<op>(), which dereferences the null model handle — the sequence has no register model to operate on. The register operations themselves are correct, and the sequencer may be perfectly connected, but the sequence was never handed the paper to write on: without seq.model set, body()'s first register reference nulls out. This is distinct from a missing adapter (5.1) or unconnected map (3.1) — those break the routing of a valid access; this breaks the sequence's access to the model in the first place.

Fix

Set the sequence's model before starting it: seq.model = env.reg_model; (as in Section 5). Now body()'s register references resolve to real registers, and the accesses run. The rule the bug teaches: a register sequence operates on registers only through its model handle, so the test must set seq.model to the register model before start(); without it, the sequence's first register reference nulls out and no stimulus runs, however correct the register operations look. When a register sequence produces no activity and nulls out on a register name, check that seq.model was set.

7. Common Mistakes

  • Not setting seq.model. The sequence has no register model to reach; body() nulls out on the first register name.
  • Starting on the wrong sequencer. Register accesses run on the sequencer the sequence is started on (and the map's bound sequencer); a mismatch misroutes them.
  • Omitting .parent(this) on register accesses. The access is not associated with the running sequence, so it does not participate in the sequence's arbitration/context.
  • Writing bus-level stimulus in a register sequence. Write in register terms; let RAL produce the bus (the portability of 5.1).
  • Confusing the register sequencer with the bus sequencer. Register accesses ultimately run on the map's bound bus sequencer.

8. Industry Best Practices

  • Set seq.model (and the sequencer) in the test before start(). The two bindings the sequence cannot supply itself.
  • Write register stimulus in register terms. Name registers and operations; keep bus knowledge in the adapter (5.1).
  • Parent every register access to the sequence. .parent(this) so accesses are properly sequenced and arbitrated.
  • Use a virtual sequence to coordinate register and data stimulus. Configuration through the register sequence, data through another, on a virtual sequencer.
  • Reuse register sequences across tests. They are portable register-ese; parameterize rather than rewrite.

9. Interview / Review Questions

10. Key Takeaways

  • A register sequence (uvm_reg_sequence) is how you drive registers from a test — stimulus written in register terms, calling write/read/update on registers by name.
  • It needs three things: a model handle (set by the test to the register model), a sequencer to run on (the bus sequencer the map is bound to), and .parent(this) on each register access (author-supplied inside body()).
  • The test supplies the model and the sequencer — the two environment-specific bindings the portable sequence cannot know itself.
  • The stimulus layering is test → (virtual sequence) → register sequence → register operations → RAL frontdoor → bus; you author intent in register terms and RAL produces the bus.
  • The signature bug is seq.model not set: the sequence's first register reference nulls out and no stimulus runs, however correct the register operations look — set seq.model before start().