sequence advanced application
Nested sequence
A new sequence can generate two packages alternately:
class case0_sequence extends uvm_sequence #(my_transaction); virtual task body(); my_transaction tr; repeat (10) begin `uvm_do_with(tr, {tr.crc_err == 1; tr.dmac == 48'h980F;}) //Generate CRC error packet `uvm_do_with(tr, {tr.crc_err == 0; tr.pload.size() == 1500; //Generate long packet tr.dmac == 48'hF675;}) end endtask endclass
It seems that writing like this is particularly troublesome. In the two different packages generated, the first constraint has two and the second constraint has three. What if there are ten constraints? If 30 test cases in the whole verification platform use such two packages, it is necessary to add these codes to the sequence of these test cases, which is a terrible thing and especially error prone. Now that CRC has been defined_ Seq and long_seq, a simpler method is in the body of a sequence, except that UVM can be used_ In addition to the transaction generated by the do macro, you can also start other sequences, that is, start another sequence within one sequence, which is a nested sequence:
class case0_sequence extends uvm_sequence #(my_transaction); ... virtual task body(); crc_seq cseq; long_seq lseq; ... repeat (10) begin cseq = new("cseq"); cseq.start(m_sequencer); lseq = new("lseq"); lseq.start(m_sequencer); end ... endtask ... endclass
Call the well-defined sequence directly in the new body of sequence, so as to realize the reuse of sequence. This function is very powerful. M in the above code_ The sequencer is case0_sequence pointer to the sequencer used after startup. But generally speaking, it is not so troublesome. You can use uvm_do macro to do these things:
class case0_sequence extends uvm_sequence #(my_transaction); ... virtual task body(); crc_seq cseq; long_seq lseq; ... repeat (10) begin `uvm_do(cseq) `uvm_do(lseq) end ... endtask ... endclass
uvm_ The first parameter in the do series macro can be a pointer to a transaction or a sequence. When the first parameter is sequence, it calls the start task of this sequence.
Except UVM_ In addition to the do macro, the UVM described earlier_ Send macro, uvm_rand_send macro, UVM_ The first parameter of the create macro can be a pointer to sequence. The only exception is start_item and finish_item, the parameters of these two tasks must be pointers to transaction.
*Use the rand type variable in the sequence
In the definition of transaction, rand is usually used to modify variables, indicating that this field should be randomized when randomize is called. In fact, the rand modifier can also be used in the sequence. The following sequence has a member variable ldmac:
class long_seq extends uvm_sequence#(my_transaction); rand bit[47:0] ldmac; ... virtual task body(); my_transaction tr; `uvm_do_with(tr, {tr.crc_err == 0; tr.pload.size() == 1500; tr.dmac == ldmac;}) tr.print(); endtask endclass
This sequence can be called by the top-level sequence as the bottom-level sequence:
class case0_sequence extends uvm_sequence #(my_transaction); ... virtual task body(); long_seq lseq; ... repeat (10) begin `uvm_do_with(lseq, {lseq.dmac == 48'hFFFF;}) end ... endtask ... endclass
Any rand modifier can be added to the sequence to regulate the transaction generated by it. Both sequence and transaction can call randomize for randomization, and both can have member variables with rand modifier. To some extent, the boundary between them is vague. That's why UVM_ The reason why do series macros can accept sequence as their argument.
When defining rand type variables in sequence, pay attention to the naming of variables. Many people are used to the fact that the name of the variable is consistent with the name of the corresponding field in the transaction:
class long_seq extends uvm_sequence#(my_transaction); rand bit[47:0] dmac; ... virtual task body(); my_transaction tr; #15 `uvm_do_with(tr, {tr.crc_err == 0; tr.pload.size() == 1500; tr.dmac == dmac;}) tr.print(); endtask endclass
In case0_ Start the above sequence in sequence and restrict the dmac address to 48 'hFFFF. At this time, it will be found that the dmac of the generated transaction is not 48' hFFFF, but a random value! This is because when you run to the #15 line of the above code, the compiler first goes to my_transaction looks for dmac. If it is found, it will not be looked for. That is #15, the last lines of code are equivalent to:
`uvm_do_with(tr, {tr.crc_err == 0; tr.pload.size() == 1500; tr.dmac == tr.dmac;})
long_ dmac in SEQ does not work. Therefore, when defining a rand type variable in sequence to pass constraints to the generated transaction, the name of the variable must be different from the name of the corresponding field in the transaction.
*Match of transaction type
A sequencer can only generate one type of transaction. If a sequence wants to start on this sequencer, the type of transaction it generates must be this transaction or derived from this transaction.
If the type of transaction generated in a sequence is not such a transaction, an error will be reported:
class case0_sequence extends uvm_sequence #(my_transaction); your_transaction y_trans; virtual task body(); repeat (10) begin `uvm_do(y_trans) end endtask endclass
The premise of nested sequences is that the transaction s generated by all sequences in the set can be accepted by the same sequencer.
Is there any way to hand over two distinct transaction s to the same sequencer? Yes, just set the data type that the sequencer and driver can accept to uvm_sequence_item:
class my_sequencer extends uvm_sequencer #(uvm_sequence_item); class my_driver extends uvm_driver#(uvm_sequence_item);
You can send my alternately in the sequence_ Transaction and your_transaction:
class case0_sequence extends uvm_sequence; my_transaction m_trans; your_transaction y_trans; ... virtual task body(); ... repeat (10) begin `uvm_do(m_trans) `uvm_do(y_trans) end ... endtask `uvm_object_utils(case0_sequence) endclass
The problem is that the data type received in the driver is uvm_sequence_item, if you want to use my_transaction or your_ cast conversion must be used for member variables in transaction:
task my_driver::main_phase(uvm_phase phase); my_transaction m_tr; your_transaction y_tr; ... while(1) begin seq_item_port.get_next_item(req); if($cast(m_tr, req)) begin drive_my_transaction(m_tr); `uvm_info("driver", "receive a transaction whose type is my_transaction", UVM_MEDIUM) end else if($cast(y_tr, req)) begin drive_your_transaction(y_tr); `uvm_info("driver", "receive a transaction whose type is your_transaction", UVM_MEDIUM) end else begin `uvm_error("driver", "receive a transaction whose type is unknown") end seq_item_port.item_done(); end endtask
*p_ Use of sequencer
Consider a case where the following member variables exist in the sequencer:
class my_sequencer extends uvm_sequencer #(my_transaction); bit[47:0] dmac; bit[47:0] smac; ... virtual function void build_phase(uvm_phase phase); super.build_phase(phase); void'(uvm_config_db#(bit[47:0])::get(this, "", "dmac", dmac)); void'(uvm_config_db#(bit[47:0])::get(this, "", "smac", smac)); endfunction `uvm_component_utils(my_sequencer) endclass
In build_ Use config in phase_ DB:: get gets the values of these two member variables. When the sequence sends a transaction, the destination address must be set to dmac and the source address to smac. The problem is how to get the values of these two variables in the body of sequence?
M was introduced when the nested sequence was introduced earlier_ Sequencer is a member variable belonging to each sequence, but if M is used directly_ The sequencer obtains the values of these two variables:
virtual task body(); ... repeat (10) begin `uvm_do_with(m_trans, {m_trans.dmac == m_sequencer.dmac; m_trans.smac == m_sequencer.smac;}) end ... endtask
Writing as above will cause compilation errors. Its root is m_ The sequencer is uvm_sequencer_base (uv m_sequencer's base class) type, not my_ Of type sequencer. m_ The prototype of sequencer is:
protected uvm_sequencer_base m_sequencer;
But because case0_sequence in my_ Start on sequencer, where M_ The sequencer is essentially my_sequencer type, so it can be in my_ In sequence, M is converted by cast transformation_ Convert sequencer to my_ Type sequencer and reference dmac and smac in it:
virtual task body(); my_sequencer x_sequencer; ... $cast(x_sequencer, m_sequencer); repeat (10) begin `uvm_do_with(m_trans, {m_trans.dmac == x_sequencer.dmac; m_trans.smac == x_sequencer.smac;}) end ... endtask
The above process is a little troublesome. In the actual verification platform, there are many cases where the member variables in the sequencer are used. Considering this situation, UVM built a macro: uvm_declare_p_sequencer(SEQUENCER). The essence of this macro is to declare a member variable of sequence type. For example, when defining sequence, use this macro to declare the type of sequencer:
class case0_sequence extends uvm_sequence #(my_transaction); my_transaction m_trans; `uvm_object_utils(case0_sequence) `uvm_declare_p_sequencer(my_sequencer) ... endclass
It is equivalent to declaring the following member variables:
class case0_sequence extends uvm_sequence #(my_transaction); my_sequencer p_sequencer; ... endclass
After the UVM, the M_ The sequencer is converted to P by cast_ sequencer. This process is in pre_body() was completed before. Therefore, the member variable p can be used directly in the sequence_ Sequencer to reference dmac and smac:
class case0_sequence extends uvm_sequence #(my_transaction); ... virtual task body(); ... repeat (10) begin `uvm_do_with(m_trans, {m_trans.dmac == p_sequencer.dmac; m_trans.smac == p_sequencer.smac;}) end ... endtask endclass
*Derivation and inheritance of sequence
As a class, sequence can derive other sequences from it:
class base_sequence extends uvm_sequence #(my_transaction); `uvm_object_utils(base_sequence) `uvm_declare_p_sequencer(my_sequencer) function new(string name= "base_sequence"); super.new(name); endfunction //define some common function and task endclass class case0_sequence extends base_sequence; ... endclass
Because each sequence is similar in the same project, many common functions or tasks can be written in the base sequence, and other sequences are derived from this sequence.
There is no problem using ordinary sequence like this, but for those who use uvm_declare_p_sequence declaration p_ The base sequence of the sequencer. Do you want to call this macro declaration P in the derived sequence_ sequencer?
The answer to this question is no, because UVM_ declare_ p_ The essence of sequence is to declare a member variable p in base sequence_ sequencer. When another sequence derives from it, P_ The sequencer is still a member variable of the new sequence, so there is no need to declare it again. If you declare it again, the system will not report an error:
class base_sequence extends uvm_sequence #(my_transaction); `uvm_object_utils(base_sequence) `uvm_declare_p_sequencer(my_sequencer) ... endclass class case0_sequence extends base_sequence; `uvm_object_utils(case0_sequence) `uvm_declare_p_sequencer(my_sequencer) ... endclass
Although this is equivalent to declaring two member variables p in succession_ Sequencer, but since one of the two member variables belongs to the parent class and the other belongs to the child class, there will be no error.