UVM/UVM Cookbook/Sequences/Virtual
- Sequences
- Virtual
- VirtualSequencer
- Slave [1]
- Driver
- Analysis Components & Techniques
- UVM Tutorial for Candy Lovers
Sequences/Virtual
Виртуальная последовательность представляет собой последовательность, которая управляет генерацией входных воздействий используя несколько sequencer`ов. Поскольку последовательности, sequencer`ы и driver`ы ориентированы на определённые интерфейсы, почти все testbench`и требуют виртуальную последовательность для координирования воздействий через разные интерфейсы и для взаимодействия между ними. Виртуальная последовательность часто является верхним уровшенм в иерархии последовательностей (sequence hierarchy). Виртуальная последовательность также может упоминаться как "ведущая-последовательность" ('master sequence') или "координационная/координирующая последовательность" 'co-ordinator sequence'.
Виртуальная последовательность отличается от обычной последовательности тем, что её основным назначением не является отправление транзакций (sequence items). Вместо этого она генерирует и выполняет последовательности на различных заданных агентах. Для этого она содержит указатели на классы (handles) для заданных sequencer`ов и они используются при запуске последовательностей.
// Creating a useful virtual sequence type: typedef uvm_sequence #(uvm_sequence_item) uvm_virtual_sequence; // Virtual sequence example: class my_vseq extends uvm_virtual_sequence; ... // Handles for the target sequencers: a_sequencer_t a_sequencer; b_sequencer_t b_sequencer; task body(); ... // Start interface specific sequences on the appropriate target sequencers: aseq.start( a_sequencer , this ); bseq.start( b_sequencer , this ); endtask endclass |
Для того, чтобы виртуальная последовательность работала, указатели на классы sequencer`ов должны быть назначены. Как правило, виртуальная последовательность создаётся в классе test в задаче (task) фазы run (run_phase) и назначения указателей классов sequencer`ов внутри объекта виртуальной последовательности выполняются с помощью теста. После того, как указатели на классы () sequencer`ов назначены, виртуальная последовательность запускается с помощью пустого (null) указателя на класс sequencer`а.
my_seq vseq = my_seq::type_id::create("vseq"); vseq.a_sequencer = env.subenv1.bus_agent.sequencer; vseq.b_sequencer = env.subenv2.subsubenv1.bus_agent3.sequencer; vseq.start( null ); |
Есть несколько вариаций на тему виртуальной последовательности. Там нет ничего, чтобы остановить виртуальную последовательность Будучи запущенным на секвенсор и отправки элементы последовательность в этой секвенсор в то же время выполняя другие последовательности на их целевых секвенсоров. Виртуальный последовательность не должна быть выполнена с помощью теста, он может быть выполнен среде инкапсулирующего ряд агентах. Для большого испытательный стенд со многими агентами и несколько проблемных областей может быть несколько виртуальная последовательности, работающие одновременно.
In addition to target sequencer handles, a virtual sequence may also contain handles to other testbench resources such as register models which would be used by the sub-sequences.
Recommended Virtual Sequence Initialization Methodology
In order to use the (U)OVM effectively, many organisations separate the implementation of the testbench from the implementation of the test cases. This is either a conceptual separation or a organisational separation. The testbench implementor should provide a test base class and a base virtual sequence class from which test cases can be derived. The test base class is responsible for building and configuring the verification environment component hierarchy, and specifying which virtual sequence(s) will run. The test base class should also contain a method for assigning sequence handles to virtual sequences derived from the virtual sequence base class. With several layers of vertical reuse, the hierarchical paths to target sequencers can become quite long. Since the hierarchical paths to the target sequencers are known to the testbench writer, this information can be encapsulated for all future test case writers.
Virtual seq vertical reuse.gif
As an example consider the testbench illustrated in the diagram. To illustrate a degree of virtual reuse, there are four target agents organised in two sub-environments within a top-level environment. The virtual sequence base class contains handles for each of the target sequencers:
class top_vseq_base extends uvm_sequence #(uvm_sequence_item); `uvm_object_utils(top_vseq_base) uvm_sequencer #(a_seq_item) A1; uvm_sequencer #(a_seq_item) A2; uvm_sequencer #(b_seq_item) B; uvm_sequencer #(c_seq_item) C; function new(string name = "top_vseq_base"); super.new(name); endfunction endclass: top_vseq_base |
In the test base class a method is created which can be used to assign the sequencer handles to the handles in classes derived from the virtual sequence base class.
class test_top_base extends uvm_test; `uvm_component_utils(test_top_base) env_top m_env; function new(string name = "test_top_base", uvm_component parent = null); super.new(name, parent); endfunction function void build_phase(uvm_phase phase); m_env = env_top::type_id::create("m_env", this); endfunction: build_phase // Method to initialise the virtual sequence handles function void init_vseq(top_vseq_base vseq); vseq.A1 = m_env.m_env_1.m_agent_a.m_sequencer; vseq.C = m_env.m_env_1.m_agent_c.m_sequencer; vseq.A2 = m_env.m_env_2.m_agent_a.m_sequencer; vseq.B = m_env.m_env_2.m_agent_b.m_sequencer; endfunction: init_vseq endclass: test_top_base |
In a test case derived from the test base class the virtual sequence initialisation method is called before the virtual sequence is started.
class init_vseq_from_test extends test_top_base; `uvm_component_utils(init_vseq_from_test) function new(string name = "init_vseq_from_test", uvm_component parent = null); super.new(name, parent); endfunction task run_phase(uvm_phase phase); vseq_A1_B_C vseq = vseq_A1_B_C::type_id::create("vseq"); phase.raise_objection(this); init_vseq(vseq); // Using method from test base class to assign sequence handles vseq.start(null); // null because no target sequencer phase.drop_objection(this); endtask: run_phase endclass: init_vseq_from_test |
The virtual sequence is derived from the virtual sequence base class and requires no initialisation code.
class vseq_A1_B_C extends top_vseq_base; `uvm_object_utils(vseq_A1_B_C) function new(string name = "vseq_A1_B_C"); super.new(name); endfunction task body(); a_seq a = a_seq::type_id::create("a"); b_seq b = b_seq::type_id::create("b"); c_seq c = c_seq::type_id::create("c"); a.start(A1); fork b.start(B); c.start(C); join endtask: body endclass: vseq_A1_B_C |
This example illustrates how the target sequencer handles can be assigned from the test case, but the same approach could be used for passing handles to other testbench resources such as register models and configuration objects which may be relevant to the operation of the virtual sequence or its sub-sequences.
![]() |
Download a complete working example: (tarball: test_init_uvm.tgz) |
Alternative Methods For Initialising A Virtual Sequence
In the preceding example, the virtual sequence is initialised from the test class, this is the recommended approach since it is so simple to implement. However, there are several alternative approaches that could be used:
- Putting the initialisation method in the test package rather than making it a member of a test base class. This allows several variants of the init method to exist without having to define different base classes.
- Putting the initialisation method in a mapping package which imports the env package(s) and the virtual sequence package. This separates out the initialisation from the test. Several initialisation methods could be defined for different virtual sequences. The mapping package would be imported into the test package.
package my_virtual_sequence_mapping_pkg; // // This package is specific to the test env and to the virtual sequence // import my_sequence_pkg::*; import my_env_pkg:*; function void init_my_virtual_sequence_from_my_env( my_virtual_sequence vseq , my_env env ); vseq.fabric_ports[0] = env.env1.a_agent.sequencer; vseq.fabric_ports[1] = env.env2.a_agent.sequencer; vseq.data_port = env.env1.b_agent.sequencer; vseq.control_port = env.env2.c_agent.sequencer; end // Other virtual sequence initialisation methods could also be defined endpackage |
- Using the uvm_config_db to pass sequencer handles from the env to the virtual sequence. This can be made to work in small scaled environments, but may breakdown in larger scale environments, especially when multiple instantiations of the same env are used since there is no way to uniquify the look-up key for the sequencer in the uvm_config_db
// Inside the env containing the target sequencers: // function void connect_phase(uvm_phase phase); // uvm_config_db #(a_sequencer)::set(null, "Sequencers", "a_sqr", a_agent.m_sequencer); uvm_config_db #(b_sequencer)::set(null, "Sequencers", "b_sqr", b_agent.m_sequencer); // endfunction // Inside the virtual sequence base class: // a_sequencer A; b_sequencer B; // Get the sequencer handles back from the config_db // task body(); if(!uvm_config_db #(a_sequencer)::get(null, "Sequencers", "a_sqr", A)) begin `uvm_error("body", "a_sqr of type a_sequencer not found in the uvm_config_db") end if(!uvm_config_db #(b_sequencer)::get(null, "Sequencers", "b_sqr", B)) begin `uvm_error("body", "b_sqr of type b_sequencer not found in the uvm_config_db") end // .... endtask |
- Using the find_all() method to find all the sequencers that match a search string in an environment. Again this relies on the sequencer paths being unique which is an assumption that will most likely break down in larger scale environments.
// // A virtual sequence which runs stand-alone, but finds its own sequencers class virtual_sequence_base extends uvm_sequence #(uvm_sequence_item); `uvm_object_utils(virtual_sequence) // Sub-Sequencer handles bus_sequencer_a A; gpio_sequencer_b B; // This task would be called as super.body by inheriting classes task body; get_sequencers(); endtask: body // function void get_sequencers; uvm_component tmp[$]; //find the A sequencer in the testbench tmp.delete(); //Make sure the queue is empty uvm_top.find_all("*m_bus_agent_h.m_sequencer_h", tmp); if (tmp.size() == 0) `uvm_fatal(report_id, "Failed to find mem sequencer") else if (tmp.size() > 1) `uvm_fatal(report_id, "Matched too many components when looking for mem sequencer") else $cast(A, tmp[0]); //find the B sequencer in the testbench tmp.delete(); //Make sure the queue is empty uvm_top.find_all("*m_gpio_agent_h.m_sequencer_h", tmp); if (tmp.size() == 0) `uvm_fatal(report_id, "Failed to find mem sequencer") else if (tmp.size() > 1) `uvm_fatal(report_id, "Matched too many components when looking for mem sequencer") else $cast(B, tmp[0]); endfunction: get_sequences endclass: virtual_sequence_base |
The Virtual Sequencer - An Alternative Methodology For Running Virtual Sequences
An alternative methodology for running virtual sequences is to use a virtual sequencer, which is a uvm_sequencer which contains the handles for the target sequencers. In this methodology, the virtual sequence is started on the virtual sequencer and it gets the handles for the target sequencers from the virtual sequencer. The limitation of this approach is that it is a fixed implementation which is very tightly coupled to the local hierarchy of an env and this adds complications with vertical reuse.