[LONG] [sqr] send_request failed to cast sequence item

I’m struggling since far too long and I’m running out of ideas, so I’d like to share my pain with you.
I am trying to launch a register sequence on a layered sequencer as recommended by the UVM User Guide 5.9.2.3. So I have the following elements:

  • a uvm_reg_sequence wich performs register access to configure the dut
  • a uvm_env extended class to instantiate the register model and a sub-environment containing the bus agent
  • a top virtual sequence that starts the uvm_reg_sequence and other sequences
  • a simple test that starts the virtual sequence

The trick is that my uvm_reg_sequence is parametrized with the register model type so that I can reuse it at top level by a new specialization with a different register block. I therefore believe I have some type issue related to that, let’s see.

Starting from the uvm_reg_sequence:


class reg_config_bringup_vseq #(type R=uvm_reg_block, type B=uvm_sequence #(uvm_reg_item)) extends reg_config_base_vseq #(R, B);

  `uvm_object_param_utils(reg_config_bringup_vseq#(R, B))

  function new(string name="reg_config_bringup_vseq");
    super.new(name);
  endfunction

  virtual task body();

    `uvm_info(get_type_name(), "Register configuration sequence started", UVM_NONE)
    super.body();

    write_reg(regmodel.apb.link_ctrl, status, data);
    assert(status == UVM_IS_OK);

    `uvm_info(get_type_name(), "Register configuration sequence ended", UVM_NONE)
  endtask: body

endclass: reg_config_bringup_vseq


The reg_config_base_vseq class takes care of getting the register model from the config_db and store it in a handle called regmodel.

Then we have the uvm_env class which instantiates the register model, the adapter and predictor, an additional specialized register sequencer and a translation sequence:


class my_env extends uvm_env;
// ...
  mm_reg_block        regblock;
  reg_model_cfg       ral_cfg;
  apb_mm_regblock_obj ral;

  // virtual register sequencer
  uvm_sequencer#(uvm_reg_item) reg_sqr;

  // Translation sequence
  typedef uvm_reg_sequence #(uvm_sequence #(apb_trans)) reg2apb_seq_t;
  reg2apb_seq_t                reg2apb_seq;

  virtual function void build_phase(uvm_phase phase);
    // ...    
    ral = apb_mm_regblock_obj::type_id::create("ral", this);
    reg_predictor = reg_predictor_t::type_id::create("apb2reg",  this);
    reg_sqr = uvm_sequencer#(uvm_reg_item)::type_id::create("reg_sqr", this);

  endfunction: build_phase

  virtual function void connect_phase(uvm_phase phase);
    ral.default_map.set_sequencer(reg_seqr,null);                // !!! we do not pass the adapter to the address_map
    reg2apb_seq = reg2apb_seq_t::type_id::create("reg2apb_seq"); // we create the translation sequence
    reg2apb_seq.reg_seqr = reg_sqr;                              // we assign the translation seq own sequencer to our specialized one
    reg2apb_seq.adapter =                                        // we pass the adapter to the translation seq.
           apb_mm_reg2apb_adapter::type_id::create("apb_mm_reg2apb_adapter", this);

    sub_env.apb.mon.item_collected_port.connect(reg_predictor.bus_in);
  endfunction: connect_phase

  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);

    reg2apb_seq.start(sub_env.apb.sqr);                          // we start the translation seq. on the bus sequencer
  endtask: run_phase

endclass: my_env


Up till now there should not be much of a problem (I believe), while now things may get a bit confusing when it comes to specialize the above reg_config_bringup_vseq, here it is:


class bringup_vseq extends base_vseq;

  `uvm_object_utils(bringup_vseq)

  // handles to sequences provided by the sequencer
  typedef reg_config_bringup_vseq#(apb_mm_regblock_obj, apb_base_seq) reg_config_bringup_vseq_t; // !!! The uvm_reg_sequence is specialized with an
                                                                                                 // apb_base_seq which is a uvm_sequence#(apb_trans)
  reg_config_bringup_vseq_t cfg_vseq;
  parallelRdAfterWrSeq      pci_vseq;

  function new(string name="bringup_vseq");
    super.new(name);
  endfunction

  virtual task body();
    `uvm_info(get_type_name(), "Top level sequence started", UVM_NONE)
    super.body();

    cfg_vseq = reg_config_bringup_vseq_t::type_id::create("cfg_vseq");
    pci_vseq = parallelRdAfterWrSeq::type_id::create("pci_vseq");

    // Reminder: here we rely on the virtual sequencer to have correctly
    // configured the low level sequencers

    if(!cfg_vseq.randomize()) `uvm_error("RAND","FAILED");
    cfg_vseq.start(reg_sqr);           // !!! Here we start the register sequence on the reg sequencer

    if(!pci_vseq.randomize()) `uvm_error("RAND","FAILED");
    pci_vseq.start(pci_sqr);

  endtask

endclass: bringup_vseq


The base_vseq takes care of getting a virtual sequencer which takes care of passing the sequencer handles including the register sequencer reg_sqr. The cfg_vseq has been specialized with the correct register block and bus sequence.

Last but not least, the test class, which becomes very simple:


class bringup_test extends base_test;

  `uvm_component_utils(rpcs_bringup_test)

  bringup_vseq vseq;
  // ... omitting other uvm tasks/functions

  task run_phase(uvm_phase phase);
    super.run_phase(phase);
    phase.raise_objection(this);
    vseq = bringup_vseq::type_id::create("vseq");
    vseq.start(top_env.sqr);   // Start the virtual sequence on the virtual sequencer
    phase.drop_objection(this);
  endtask: run_phase

endclass: bringup_test


The base_test class takes care of building the environment.
If my understanding is correct, when the specialized reg_config_bringup_vseq_t is started on the register sequencer, it sends a write_reg which will send a generic uvm_reg_item to the bus sequencer, by triggering the following piece of code in the uvm_reg_map:


  if (adapter == null) begin
    rw.set_sequencer(sequencer);
    rw.parent.start_item(rw,rw.prior);
    rw.parent.finish_item(rw);
    rw.end_event.wait_on();
  end

When start_item is executed the things become a little fuzzy to me. The sequencer passed with the set_sequencer is the bus sequencer, but then we are passing rw to the start_item, on a bus sequencer, which I’m not sure how it would interpret it since it cannot handle a generic uvm_reg_item. So the translation between the generic uvm_reg_item and the apb_trans (that our bus sequencer can handle), is not clear where it happens.

So eventually when I run I get a runtime fatal from the bus sequencer which reports the following:
UVM_FATAL @ 12.5ns: uvm_test_top.top_env.sub_env.apb.sqr [sqr] send_request failed to cast sequence item

I apologies for the length of this question, but I’m quite lost with it and can’t seem to understand the bottom of it.

In reply to abasili:

Ok, now I would like to hide myself under a pile of bison poop!
While I was writing the above post I’ve copied bits and pieces of my code, as well as the code from the UVM User Guide. Well… while the code from the User Guide was correct, my real code, that I thought was a very close copy of that, was fundamentally wrong!


  virtual function void connect_phase(uvm_phase phase);
    ral.default_map.set_sequencer(sub_env.apb.sqr,null);         // <------- Argghhhh!!!!
    reg2apb_seq = reg2apb_seq_t::type_id::create("reg2apb_seq"); // we create the translation sequence
    // ... 
    sub_env.apb.mon.item_collected_port.connect(reg_predictor.bus_in);
  endfunction: connect_phase

So essentially my address_map, instead of ‘sending’ the uvm_reg_item to the reg sequencer, it was sending it to the bus sequencer, which didn’t make any sense!
I must say that going through the UVM base code, step by step, taught me a lot of things, but it has been quite a frustrating experience!

For those of you who’ve stuck all along, I owe you an apology and a thank you for the effort to read through my rant (can’t define it otherwise).

Over and out,

  • Al