Origin of Sequence
The initial driver has raise_objection and drop_objection
And the driver(tr) contained in the middle of the two
Because if you write in this way, you have to change the code every time you want to send a different package, which is very error-prone and poor scalability.
Therefore, you can put the same code each time in the driver, and each time a different code can be separated into one
Gen_pkt function
But there will be problems in the process of using gen_pkt, because if you want to use the functions with the same name are gen_pkt, but the functions are different, you need to use virtual functions and overloads, reload gen_pkt as virtual, When you want to use this gen_pkt, you need to instantiate a new driver with an overloaded gen_pkt function, which is quite troublesome to think about.
The other is to write a function with a different name, but then the driver still needs to change the code for each test case to add the function with a different name, and we are discussing it to avoid this problem, so this is obviously not advisable of.
Sequence start and execution
The startup method of Sequence is
My_seq.start(sequencer);
or
用default_sequence
After the sequence is started, the body task of the sequence will be automatically executed, as well as pre_body and post_body tasks
These two are before and after the body is executed
Start of multiple sequce
In fact, multiple sequences can be started at the same time, but at this time, you need to consider the start time of these sequences. This is the arbitration mechanism of the sequence. There are many arbitration algorithms for the sequence. If you want to change the default arbitration algorithm, you need to use
env.i_agt.sqr.set_arbitration(SEQ_ARB_STRICT_FIFO);
arbitration: arbitration
Change it.
The sequencer's arbitration algorithm is SEQ_ARB_FIFO by default. It will strictly follow the first-in-first-out order, without considering priority. SEQ_ARB_WEIGHTED is a weighted arbitration; SEQ_ARB_RANDOM is selected completely at random; SEQ_ARB_STRICT_FIFO is strictly based on priority. When there are multiple sequences with the same priority, they are selected in the order of first-in first-out; SEQ_ARB_STRICT_RANDOM is strictly based on priority. Same
For a sequence of one priority, select randomly from the highest priority; SEQ_ARB_USER is a new arbitration algorithm that users can customize.
First, for transaction, it has the concept of priority
Want to operate on his priority, you need to use uvm_do_pri
Second, for sequence, it also has the concept of priority
Its priority is actually the priority of the transaction in it. When you set the priority of a sequence, you want to set the priority of all transactions in it.
Sequencer lock operation and grab operation
Lock is to allow a sequence to occupy the sequencer until it unlocks itself.
Like the lock operation, the grab operation is also used to temporarily own the sequencer, but the grab operation has a higher priority than the lock operation. lock request is inserted
The sequencer is the last of the arbitration queue. When it reaches it, all the arbitration requests before it have ended. The grab request is placed at the top of the sequencer arbitration queue.
Almost has the ownership of the sequencer as soon as it is issued:
Sequence validity
During arbitration, the sequencer will check the return result of the is_relevant function of the sequence. If it is 1, the sequence is valid, otherwise it is invalid. Therefore, the sequence can be invalidated by overloading the is_relevant function:
is_relevant: meaningful
Invalidation means that the sequencer will not execute the sequence unless it becomes valid.
After the Sequencer has sent all transactions, if there is an invalid sequence at this time, it will automatically call the wait_for_relevant function of the invalid sequence. This function must contain operations that make the invalid sequence valid again. Otherwise, the entire system will fall into an endless loop.
In normal use, the is_relevant function and wait_for_relevant function must be overloaded at the same time.
Sequence related macros
`uvm_do is the most foolish method for generating transactions containing random data
`uvm_do_with provides constraints on certain fields when randomizing
`uvm_do_pri is used to set the priority of the sequence
`uvm_do_on is used to pair sequencers with sequence. When there are multiple sequencers, this macro can work.
In this case, you can actually know what with, pri, and on mean. As long as there is with, it includes the function of with, and the other is the same, so you can see at a glance what this macro does.
In addition to uvm_do, you can also use uvm_create and uvm_send to generate transaction
`uvm_create(my_transaction) generates a transaction
assert(my_transaction)
my_transaction.num = num_set;
`uvm_send(my_transaction)
In addition, there is also uvm_send_pri, the same as the previous usage
`uvm_rand_send series macro
my_transaction = new(“my_transaction“)
`uvm_rand_send(my_transaction)
The significance of the uvm_rand_send series of macros and uvm_send series of macros is mainly that if a transaction occupies a large amount of memory, then it is likely that the two transactions sent before and after will use the same memory, but the contents can be different, which saves memory.
In other words, when the memory occupied by transaction is relatively large, we should use uvm_send instead of uvm-do. In this way, the memory consumption is smaller. If the transaction itself is small, there is no need to worry too much about which one to use.
How does macro do these things?
Macro operation depends on two tasks
start_item
finish_item
`uvm_do macro operation, equivalent to
tr = new ("tr");
start_item(tr,100);
assert(tr.randomize())
finish_item(tr,100);
The second parameter of these two tasks is priority.
Must operate at the same time.
uvm_do related operations
The uvm_do macro encapsulates too many operation steps, so its flexibility is insufficient. In order to make up for these shortcomings, uvm provides which interface is used to expand uvm_do
pre_do
mid_do
post_do
The order of execution is
Nested sequence
Suppose, there are two sequences that generate crc error packets
Now to write a new sequence, it can alternately generate these two different crc error packets
If you simply integrate the code, it is easy to make mistakes when the code is relatively long.
To avoid errors, sequence can start another sequence in its own content
crc_seq cseq;
longcrc_seq lseq;
repeat(10) begin
cseq = new(“cseq”);
lseq = new(“lseq”);
cseq.start(m_sequencer);
lseq.start(m_sequencer);
end
m_sequencer is the pointer to the sequencer used by case0_sequence after startup
You can also use a more concise approach:
`uvm_do (cseq);
`uvm_do (lseq);
This is really easy to use.
Series of macros such as uvm_send can be used to manipulate sequence.
As mentioned above, only, the parameters of start_item and finish_item can only be transaction
Use rand type variable in sequence
rand bit[47:0] ldmac;
Means that this field will be randomized when using randomize,
So when you use it
virtual task body();
my_transaction tr;
`uvm_do_with(tr,{tr.crc_err==0;
tr.pload.size()==1500;
tr.dmac=ldmac;})
tr.print();
endtask
In this way, the dmac in transaction is the random value of ldmac
The above sequence can be used as a low-level sequence, and its role is to randomize the ldmac of the transaction
repeat(10) begin
`uvm_do_with (lseq, {lseq.ldmac == 48'hFFFF;})
end
Any number of rand modifiers can be added to the sequence to regulate the transaction it generates. In fact, transaction and sequence have many similarities, they can both use the rand modifier, and both can be used in uvm_do.
One thing to pay attention to when using the transaction of the sequence specification itself, the rand variable name in the sequence cannot be the same as the variable name in the transaction, because if they are the same, the compiler will first look for this variable in the transaction when compiling the program. , If there is, then it no longer cares about the variables in the sequence.
Transaction type matching problem
A sequence can only generate one type of transaction. If another sequence wants to be started in this sequence, its transaction must be the transaction of the upper sequence or its subclass.
If you want to send multiple transactions in a sequence, then its parameters need to be changed to uvm_sequence_item, and at the same time, there must be corresponding processing in the driver.
Use of p_sequencer
The previous article mentioned a m_sequencer, this pointer is the pointer of the sequencer after the sequence is started. Its type is uvm_sequencer_base (the base class of uvm_sequencer)
Instead of the my_sequencer type.
When there is such a situation, I sequencer got a dmac value inside, want to sequence with it, that is, to access the member variables in the sequence which sequencer inside.
If you use m_sequencer, it will be very troublesome. See page 437 of the book pdf for details.
In the actual verification platform, sequencer member variables are used in many cases. Taking this into account, uvm has built a macro
uvm_declare_p_sequencer(SEQUENCER),
The essence of this macro is to declare a member variable of type SEQUENCER.
You can write like this in the sequence
`uvm_declare_p_sequencer(my_sequencer)
This sentence is equivalent to
my_sequencer p_sequencer;
After writing this sentence, uvm automatically converts m_sequencer to p_sequencer through cast, so P_sequencer.dmac can be used directly in the sequence to get the member variables in the sequencer of dmac
Derivation and inheritance of sequence
Since sequence is a class, it can also derive other sequences
For the derived sequence , if you want to access the member variables in the corresponding sequencer , you just need to declare p_sequencer with uvm_declare_p_sequence in base_sequence, because the member variables declared by the base class can be used by its subclasses.
The use of virtual sequence
If, a dut has more than one interface, but two or more interfaces.
At this time, you need to instantiate another env to test another interface, and at the same time add a set of my_if, set multiple default sequences to apply excitation to the two data ports.
In the new verification platform, there are two drivers. If we want to send a long packet to driver0 first, on this basis, driver1 will start sending packets. How can we do that?
The first is to use global variables, but I don't like to give this example, it is easy to be biased. In fact, the problem cannot be solved fundamentally. For details, see uvm actual combat book pdf page447
In order to solve this problem, uvm provides a virtual sequence, I call him virtual sequence.
Its role is to unify the scheduling sequence, this function is also quite powerful. Powerful
This function is used in the use of the most common register model.
First, before creating a virtual sequence, you need to create a virtual sequence r
Very simple, we can use a picture to see the difference between using or not using virtual sequence
In the virtual sequence, you can use uvm_do_on to schedule and send the transaction
`uvm_declare_p_sequence(my_vsqr);
The p_sqr0 is the function of the virtual sequencer created first
Here seq0 and seq1 do not need to do anything, just need to be the same as the most common seq, and the scheduling work has been given to vseq.
The work of starting the sequence can also be started manually instead of `uvm_do_on. One advantage of manual starting is that you can pass some values in it.