Part 5 · Sequences · Intermediate
p_sequencer Declare: uvm_declare_p_sequencer & dma_xfer_vseq
Strongly typed p_sequencer access, macro usage, full dma_xfer_vseq walkthrough, and sub-sequence delegation.
Why uvm_declare_p_sequencer
Inside a sequence body(), p_sequencer is the handle to the sequencer the sequence was started on. For virtual sequences, that must be the soc_virtual_sequencer type — not generic uvm_sequencer_base — so you can access p_sequencer.apb_sqr without casting.
class dma_xfer_vseq extends uvm_sequence;
// [VSEQ] Declares p_sequencer as soc_virtual_sequencer type
`uvm_declare_p_sequencer(soc_virtual_sequencer)
rand bit [31:0] src_addr, dst_addr;
rand int unsigned len;
`uvm_object_utils(dma_xfer_vseq)
function new(string name = "dma_xfer_vseq");
super.new(name);
endfunction
endclass[SEQ] without vs with declare macro
WITHOUT `uvm_declare_p_sequencer:
p_sequencer is uvm_sequencer_base
p_sequencer.apb_sqr → compile error (no such member)
WITH `uvm_declare_p_sequencer(soc_virtual_sequencer):
p_sequencer typed as soc_virtual_sequencer
p_sequencer.apb_sqr → apb_sequencer handle ✓Full dma_xfer_vseq body — step by step
The virtual sequence creates sub-sequences, passes scenario knobs, and starts them on the appropriate handles from p_sequencer:
task body();
prog_dma_seq prog = prog_dma_seq::type_id::create("prog");
axi_wr_seq wr = axi_wr_seq::type_id::create("wr");
axi_rd_seq rd = axi_rd_seq::type_id::create("rd");
// Step 1: [APB] program DMA registers — sequential
prog.src_addr = src_addr;
prog.dst_addr = dst_addr;
prog.len = len;
prog.start(p_sequencer.apb_sqr);
// Step 2: [AXI] parallel read/write while DMA active
fork
begin
wr.addr = src_addr;
wr.num_beats = len;
wr.start(p_sequencer.axi_sqr);
end
begin
rd.addr = dst_addr;
rd.num_beats = len;
rd.start(p_sequencer.axi_sqr);
end
join
// Step 3: [VSEQ] virtual seq never calls start_item — only sub-sequences do
endtaskNote: prog.start uses p_sequencer.apb_sqr — the real APB sequencer — not p_sequencer itself. The virtual sequencer is the launch point; sub-sequences run on protocol sequencers.
Delegation flow diagram
[VSEQ] start() delegation chain
TEST
vseq.start(env.v_sqr)
│
▼
dma_xfer_vseq running ON soc_virtual_sequencer
p_sequencer == env.v_sqr
│
├── prog.start(p_sequencer.apb_sqr)
│ │
│ ▼
│ prog_dma_seq ON apb_agent.sequencer
│ start_item/finish_item → apb_driver → DUT
│
└── wr.start(p_sequencer.axi_sqr)
│
▼
axi_wr_seq ON axi_agent.sequencer
start_item/finish_item → axi_driver → DUTRules for p_sequencer usage
Virtual sequence starts on v_sqr — test calls vseq.start(env.v_sqr).
Sub-sequences start on p_sequencer.<agent>_sqr — real sequencers.
Virtual sequence body() never calls start_item — no driver on v_sqr.
Sub-sequences may use their own p_sequencer (typed to apb_sequencer etc.).
Sub-sequence vs virtual sequence p_sequencer
Each sub-sequence has its own p_sequencer type. When prog_dma_seq runs on apb_sqr, its p_sequencer is the APB sequencer — declared with `uvm_declare_p_sequencer(apb_sequencer). The virtual sequence's p_sequencer and the sub-sequence's p_sequencer are different objects — no conflict.
[SEQ] two p_sequencer contexts — no collision
dma_xfer_vseq.p_sequencer → soc_virtual_sequencer (env.v_sqr)
prog_dma_seq.p_sequencer → apb_sequencer (apb_agent.sqr)
(set when prog.start(apb_sqr) runs)
Each sequence sees the sequencer it was started onKey takeaways
uvm_declare_p_sequencer gives strongly typed p_sequencer in body().
Virtual seq starts on v_sqr; sub-sequences start on p_sequencer.<agent>_sqr.
Never start_item in a virtual sequence — delegate to sub-sequences.
Common pitfalls
Missing macro — p_sequencer.apb_sqr compile error or wrong cast.
sub_seq.start(p_sequencer) — wrong; start on p_sequencer.apb_sqr.
Virtual seq calling start_item — fatal; no driver on virtual sequencer.