Лекция 4. Написание UVM окружения (v 0.3)
Библиотека содержит классы, реализующие интерфейсы для отправки и приема транзакций, а также классы, реализующие эти интерфейсы и позволяющие соединять компоненты между собой. Обычно эти интерфейсы оперируют объектами, предоставляющие собой транзакции типа uvm_object или производные от него. Интерфейсы, реализованные в библиотеке разделаются на три типа: блокирующие, не блокирующие, комбинированные.
virtual class uvm_tlm_if_base #(type T1=int, type T2=int);
Где uvm_tlm_if_base – базовый класс, содержащий описание интерфейсов. Первый аргумент T1 – тип отправляемой транзакции, T2 – тип принимаемой транзакции.
Таблица 5.1 Интерфейсы uvm_tlm_if_base класса
Блокирующие интерфейсы | put (task) | Отправка транзакции. Блок, реализующий этот интерфейс, должен блокировать вызвавший поток выполнения, если транзакцию нельзя отправить немедленно. |
get (task) | Получение транзакций. Блок, реализующий этот интерфейс, должен блокировать вызвавший поток выполнения, если транзакцию нельзя получить немедленно. Каждый новый вызов должен возвращать новый объект заданного типа. | |
peek (task) | Ожидание транзакции. Блок, реализующий этот интерфейс, должен блокировать вызвавший поток выполнения, если транзакцию нельзя получить немедленно. Если транзакция стала доступна, то блок возвращает выполнение вызвавшему потоку. Каждый последующий вызов будет происходить без блокирования выполнения и будет возвращаться один и тот же объект. Так будет происходить до тех пор пока транзакция не будет получена (к примеру с помощью get). Последующий вызов снова заблокирует выполнение вызывающего потока, если транзакция не доступна немедленно. | |
Не блокирующие интерфейсы | try_put (function) | Отправляет транзакцию немедленно, если это возможно и возвращает статус выполнения 1, если нет, то 0. |
can_put (function) | Если компонент, реализующий этот интерфейс, готов получить транзакцию, то возвращает 1, если нет, то 0. | |
try_get (function) | Пытается получить транзакцию немедленно, если это возможно и возвращает статус выполнения 1, если нет, то 0. | |
can_get (function) | Если компонент, реализующий этот интерфейс, готов предоставить немедленно по запросу новую транзакцию, то возвращается 1, если нет - 0. | |
try_peek (function) | Если возможно немедленно получить (подать на выход) транзакцию, то выполняет и возвращает 1. Если транзакция доступна, но не была получена, то при последующих вызовах будет возвращать тот же самый объект. Статус выполнения меняется на 0, если транзакция недоступна немедленно. | |
can_peek (function) | Если новая транзакция доступна, то возвращает только статус выполнения 1, если нет, то 0. | |
Блокирующий комбинированный интерфейс | transport (task) | Выполняет полученный запрос и возвращает ответ в заданный выходной аргумент. Вызывающий поток может блокироваться до тех пор, пока оператор не выполнится. |
Не блокирующий комбинированный интерфейс | nb_transport (function) | Выполняет полученный запрос и возвращает ответ в заданный выходной аргумент. Если есть причины, по которым невозможно выполнить оператор немедленно, то будет возвращен статус 0, иначе 1. |
Анализирующий интерфейс | write (function) | Транслирует транзакции пользовательского типа на любое количество слушателей. Операция должна завершаться без блокировки. |
В UVM TLM библиотеке, как уже говорилось выше, на основе базового класса uvm_tlm_if_base реализованы классы содержащие группы однонаправленных (put, get, peek) и двунаправленных (master, slave, transport), блокирующих и не блокирующих типов интерфейсов, с помощью маскирования методов базового класса. Это классы типа port, export, imp.
Классы типа рort
Объект класса этого типа создается в компонентах, которые требуют соответствующий интерфейс для передачи транзакций. Они могут быть подключены к любому совместимому объекту port, export или imp. Если min_size не равен 0, то объект типа port должен быть соединен, по меньшей мере, с одним из объектов реализующих функции интерфейса.
Классы типа export
Объекты этого типа передают управление компоненту, в котором реализован интерфейс. Они могут быть подключены к любому совместимому объекту типа export или imp. В конечном счете, объект этого типа должен соединяться хотя бы с одним из объектов, реализующих интерфейс.
Классы типа imp
Объекты этого типа обеспечивают доступ к реализации интерфейса для всех портов или экспортов, подключенных к нему. Каждый порт такого типа должен быть связан с компонентом, который реализует соответствующий интерфейс, соответственно при его объявлении и создании указывается тип этого компонента. Допустимы только подключения к imp порту, все подключения другого типа к примеру imp к port или imp к export запрещены.
В зависимости от того, в каких направлениях порты могут обмениваться транзакциями, порты подразделяются на однонаправленные и двунаправленные. В двунаправленных портах может выполняться передача транзакции запроса и ответа, т.е. порт может манипулировать двумя типами транзакций, в то время как в однонаправленном передача одной транзакции осуществляется в одном направлении.
Рис 5.1 Иерархия классов однонаправленных портов
(uvm-1.2\docs\html\images\uvm_ref_tlm_uni_ports.gif)
Классы, описывающие однонаправленные порты, имеют общий вид:
uvm_*_port#(T), uvm_*_export#(T), uvm_*_imp#(T,IMP) и в зависимости от типа реализуемого классе интерфейса, в названии этого класса вместо звездочки будут использоваться слова:
blocking_put
noblocking_put
put
blocking_get
noblocking_get
get
blocking_peek
noblocking_peek
peek
blocking_get_peek
noblocking_get_peek
get_peek
analysis
Рис 5.2 Иерархия классов двунаправленных интерфейсов
(uvm-1.2\docs\html\images\uvm_ref_tlm_bidir_ports.gif)
Класс описывающий двунаправленные порты общий вид
uvm_*_port#(REQ,RSP), uvm_*_export#(REQ,RSP),
uvm_*_imp#(REQ,RSP,IMP,REQ_IMP,RSP_IMP) и в зависимости от типа реализуемого в нем интерфейса, в названии вместо звездочки будет иметь ключевые слова:
blocking_transport
nonblocking_transport
transport
blocking_master
nonblocking_master
master
blocking_slave
nonblocking_slave
slave
Компоненты среды верификации, которые используют механизмы port/export/imp из этой библиотеки, считаются пригодными для последующего использования, являются независимыми друг от друга, модульными, и проще организовывается их взаимодействие. На рисунке 5.3 приведен пример структуры тестового окружения. В среде отображены возможные способы соединения различных интерфейсов передачи данных. Допускается следующие типы соединений.
port -> port
port -> export
export -> export,.. export
export -> imp,....imp
Соединение портов осуществляется путем вызова метода connect из порта, который подключается. Входным параметром connect является подключаемый порт.
uvm_blocking_port#(T) p1; // родительский порт
uvm_blocking_export#(T) e1; // дочерний
…
p1.connect(e1); // подключение p1 к e1
Таблица 5.1 Маски задающие тип порта
Порт | Значение маски | ||||||||||
Однонаправленные | Биты маски | ||||||||||
PORT/EXPORT/IMP | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
uvm_blocking_put_* | 1 | ||||||||||
uvm_noblocking_put_* | 1 | ||||||||||
uvm_put_* | 1 | 1 | |||||||||
uvm_blocking_get_* | 1 | ||||||||||
uvm_noblocking_get_* | 1 | ||||||||||
uvm_get_* | 1 | 1 | |||||||||
uvm_blocking_peek_* | 1 | ||||||||||
uvm_noblocking_peek_* | 1 | ||||||||||
uvm_peek_* | 1 | 1 | |||||||||
uvm_blocking_get_peek_* | 1 | 1 | |||||||||
uvm_noblocking_get_peek_* | 1 | 1 | |||||||||
uvm_get_peek_* | 1 | 1 | 1 | 1 | |||||||
uvm_analysis_* | 1 | ||||||||||
Двунаправленные | |||||||||||
PORT/EXPORT/IMP | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
uvm_blocking_master_* | 1 | 1 | 1 | 1 | |||||||
uvm_noblocking_master_* | 1 | 1 | 1 | 1 | |||||||
uvm_master_* | 1 | 1 | 1 | 1 | 1 | 1 | 1 | ||||
uvm_blocking_slave_* | 1 | 1 | 1 | 1 | |||||||
uvm_noblocking_slave_* | 1 | 1 | 1 | 1 | |||||||
uvm_slave_* | 1 | 1 | 1 | 1 | 1 | 1 | 1 | ||||
uvm_blocking_transport_* | 1 | ||||||||||
uvm_noblocking_transport_* | 1 | ||||||||||
uvm_transport_* | 1 | 1 |
При подключении портов также проводится проверка соответствия подключаемых портов. Проверка осуществляется на основе значений маски, которая определяет его тип. Правило проверки следующее: побитовое “И” значений родительского порта и дочернего должно совпадать со значением маски родительского порта. Например :
“uvm_blocking_put_export.connect(uvm_blocking_put_export)” - допустимо
“uvm_put_port.connect(uvm_blocking_put_export)” - допустимо
“uvm_noblocking_peek_port.connect(uvm_blocking_put_export)” - НЕ допустимо
Рис 5.3 Возможные виды подключения analysis портов по иерархии
Архитектура UVM тестового окружения подразумевает иерархическую структуру, где каждый блок выполняет определённую функцию.
Рис 5.4 Иерархия классов тестового окружения
(uvm-1.2\docs\html\images\uvm_ref_components.gif)
Большинство DUT имеют множество разных сигналов в интерфейсе, с помощью которых реализуется их собственный протокол обмена. UVM agent собирает вместе UVM-компоненты, нацеленные на реализацию конкретного проводного интерфейса. Цель агента реализовать компонент-модуль, который позволит подавать, генерировать и наблюдать сигналы на проводном интерфейсе.
Обычно для того чтобы сгруппировать все компоненты, реализованные внутри агента или внутри окружения используют SystemVerilog package, который после позволяет получать доступ к каждой содержащейся внутри компоненте через одно пространство имен пакета. Рекомендуется предварительно сформировать пакеты для транзакций и пакеты для сиквенсов, которые можно будет запускать на этом агенте, и будут предоставляться вместе с ним. К таким сиквенсам можно отнести сиквенсы для базового функционала агента в режиме мастер и ведомый.
Для того чтобы системы моделирования могли выполнять инкрементальную компиляцию, необходимо использовать, где только это возможно пред-компиляцию пакетов, а после импортировать их. Это позволить ускорить процесс пере-компиляции проекта во много раз.
package sram_agent_pkg; import uvm_pkg::*; //ADD_IMPORT import sram_item_pkg::*; import sram_seq_pkg::*; //ADD_INCLUDE `include "uvm_macros.svh" `include "sram_agent_cfg.sv" `include "sram_slave_memory.sv" `include "sram_slave_q_memory.sv" `include "sram_master_sequencer.sv" `include "sram_monitor.sv" `include "sram_sfr_monitor.sv" `include "sram_fifo_monitor.sv" `include "sram_slave_driver.sv" `include "sram_master_driver.sv" `include "sram_sfr_master_driver.sv" `include "sram_fifo_master_driver.sv" `include "sram_slave_sequencer.sv" `include "reg2sram_adapter.sv" `include "sram_agent.sv" endpackage : sram_agent_pkg |
Рис 5.5 Иерархическая структура агента
Обычно агент содержит driver, monitor, sequencer и объект конфигурации (эти компоненты для удобства использования объединяются в один пакет (agent package)).
Driver – компонент, который преобразует данные полученные в транзакции в активность на проводном интерфейсе. Этот компонент обычно содержит алгоритм преобразования данных в переключения на интерфейсе. В некоторых случаях драйвер может выполнять и обратное преобразование – активность на проводном интерфейсе преобразовывать в данные и заполнять поля транзакции. Драйвер должен наследоваться от класса uvm_driver. Драйвер получает в качестве параметра два типа транзакций, это транзакция запроса и транзакция ответа. По умолчанию класс uvm_driver содержит два порта seq_item_port и rsp_port, которые позволяют отправлять запрос на получение транзакции из сиквенсера (компонент наследуемый от uvm_sequencer) и отправлять ответ в нее.
В стандартном процессе работы драйвер сначала отправляет запрос на получение новой транзакции в сиквенсер, используя стандартный порт seq_item_port, обычно это блокирующее получение транзакции. После, того как транзакция готова и получена, драйвер преобразует данные из транзакции в активность на проводном интерфейсе. Затем формирует ответ и отправляет обратно в сиквенсер. Затем повторяет все действия.
Обычно реализуется пассивный и активный тип драйвера. Можно реализовывать два класса драйвера реализующих эти типы или можно реализовывать один содержащий обе функциональности, при создании задать его конфигурацию. Или можно реализовать универсальный функционал обработки последовательностей транзакций, а алгоритм поведения формировать в сиквенсах. Первый способ позволяет переключаться между типами драйвера без изменения кода с помощью механизма фабрики. Так же этот вариант позволяет вести независимо разработку драйверов. Второй наиболее простой и подходит для простых драйверов с минимальным функционалом не требующих сложных алгоритмов преобразования. Третий способ сложнее в реализации, потому что требует реализации модели поведения в пассивном режиме внутри сиквенса, что не всегда удобно, но является наиболее универсальным и подходящим для случайного тестирования, так как позволяет использовать встроенные механизмы управления выполнением сиквенсов.
В пределах агента считается хорошим стилем настройка всех внутренних параметров в том числе и драйвера с использованием объекта конфигурации через uvm_config_db интерфейсы.
class sram_master_driver #(type SEQ_ITEM = sram_item, type SRAM_VIF = virtual sram_if) extends uvm_driver #(SEQ_ITEM); SRAM_VIF drv_if;
event end_event; bit last_read;
extern function new(string name = "sram_master_driver", uvm_component parent = null); `uvm_component_param_utils_begin(sram_master_driver #( SEQ_ITEM, SRAM_VIF)) `uvm_component_utils_end extern virtual function void build_phase(uvm_phase phase); extern virtual function void end_of_elaboration_phase(uvm_phase phase); extern virtual function void start_of_simulation_phase(uvm_phase phase); extern virtual function void connect_phase(uvm_phase phase); extern virtual task reset_phase(uvm_phase phase); extern virtual task configure_phase(uvm_phase phase); extern virtual task run_phase(uvm_phase phase); extern task wait_last_tr(uvm_phase phase); extern protected virtual task send(SEQ_ITEM tr); extern protected virtual task tx_driver(uvm_phase phase); extern virtual task wait_reset(); endclass : sram_master_driver function sram_master_driver::new(string name = "sram_master_driver",uvm_component parent = null); super.new(name, parent); endfunction : new
function void sram_master_driver::build_phase(uvm_phase phase); super.build_phase(phase); endfunction : build_phase function void sram_master_driver::connect_phase(uvm_phase phase); super.connect_phase(phase); uvm_config_db#(SRAM_VIF)::get(this, "", "sram_if_db_name", drv_if); endfunction : connect_phase function void sram_master_driver::end_of_elaboration_phase(uvm_phase phase); super.end_of_elaboration_phase(phase); if (drv_if == null) `uvm_fatal("NO_CON", "Virtual port not connected to the actual interface instance"); endfunction : end_of_elaboration_phase function void sram_master_driver::start_of_simulation_phase(uvm_phase phase); super.start_of_simulation_phase(phase); endfunction : start_of_simulation_phase task sram_master_driver::reset_phase(uvm_phase phase); super.reset_phase(phase); phase.raise_objection(this,""); phase.drop_objection(this); endtask : reset_phase
task sram_master_driver::configure_phase(uvm_phase phase); super.configure_phase(phase); phase.raise_objection(this,""); phase.drop_objection(this); endtask : configure_phase task sram_master_driver::run_phase(uvm_phase phase); super.run_phase(phase); forever begin fork tx_driver(phase); join_none @(negedge drv_if.rst); disable fork; `uvm_info (get_name(),"Reset CSR asserted", UVM_MEDIUM) wait_reset(); end endtask : run_phase task sram_master_driver::wait_last_tr(uvm_phase phase); fork begin phase.raise_objection(this,""); @(drv_if.mck); if (last_read) @(drv_if.mck); phase.drop_objection(this); end join_none endtask : wait_last_tr task sram_master_driver::tx_driver(uvm_phase phase); SEQ_ITEM tr; forever begin @(drv_if.mck); if (drv_if.busy === 0 && drv_if.rst !== 1'b0) begin // if rst = X all work, it means you don't use rst drv_if.cen <= 0; seq_item_port.get_next_item(tr); send(tr); wait_last_tr (phase); seq_item_port.item_done(); `uvm_info(get_type_name(),$sformatf("DRIVER SEND TRANSACTION : \n%s",tr.convert2string()),UVM_HIGH) end end endtask : tx_driver task sram_master_driver::send(SEQ_ITEM tr); drv_if.cen <= tr.cen; drv_if.wen <= tr.rwd; drv_if.addr <= tr.addr; drv_if.bwen <= tr.bwen; drv_if.din <= tr.din; if (tr.cen == 1 && tr.rwd == 0) last_read = 1; else last_read = 0; endtask : send task sram_master_driver::wait_reset(); drv_if.cen <= 0; drv_if.wen <= 0; drv_if.addr <= 0; drv_if.bwen <= 0; drv_if.din <= 0; endtask : wait_reset |
Рис 5.6 Пример реализации драйвера
Существует также uvm_push_driver тип драйвера, который имеет отличный алгоритм обмена транзакциями с сиквенсером от uvm_drive. Он содержит по умолчанию порт типа req_export вместо seq_item_port. Метод put порта req_export будет вызываться из сиквенсера, в него будет передаваться транзакция и в нем должен реализовываться функционал преобразования транзакции в воздействия на проводном интерфейсе.
Драйвер, который на активность на шине может без создания и обработки транзакций ответить по алгоритму заложенному в нем, называется Responder. Такой драйвер можно использовать в внутри Slave агента. В отличие от Responder, Master Driver всегда работает с транзакциями. Slave driver можно быть реализован с транзакциями и без них.
Роль компонента типа uvm_sequencer в том, чтобы пересылать данные, которые формирует Sequence (генератор последовательностей транзакций) в драйвер, забирать обратно транзакции ответов, если драйвер их генерирует. А также управлением процессом выполнения сиквенсов. Сиквенсер можно сравнит с автоматом, который может обрабатывать последовательности одну или несколько параллельно и выдавать по запросу наружу элементы из этих последовательностей. При чем, есть возможность управлять порядком выдачи транзакций.
Есть два основных типа сиквенсеров uvm_sequencer и uvm_push_sequencer. Иерархия классов, от которых наследуется сиквенсеры представлена на рисунке 5.7.
Рис 5.7 – Иерархия классов предков сиквенсера
(uvm-1.2\docs\html\images\uvm_ref_sequencer.gif)
Uvm_sequencer класс параметризуется типами отправляемой и принимаемой транзакции, а также содержит порт seq_item_export, через который осуществляется обмен транзакциями с драйвером. К компоненту этого типа изначально обращается драйвер, он является инициатором выполнения. Он выполняет запрос на получение нового элемента последовательности транзакций. Внутри сиквенсера содержится список доступных сиквенсов и он выбирает из какой сиквенсы взять следующий элемент-транзакцию согласно заданному правилу или случайному распределению, а затем отдает ее драйверу на выполнение. Пользователю порт seq_item_export из сиквенсера типа uvm_sequncer необходимо вручную соединить с портом драйвера seq_item_port.
driver.seq_item_port.connect(sequencer.seq_item_export); |
Рис 5.8 Пример подключения портов секвенсера и драйвера
Класс uvm_push_sequncer параметризируется типами отправляемой и принимаемой транзакции, а также содержит порт req_port, через который осуществляется отправка транзакций драйверу на выполнение. Отсюда следует основное отличие – сиквенсер этого типа сам инициирует выполнение транзакции и блокирует поток выполнения в сиквенсе, используя механизм блокирующего интерфейса. Обычно сиквенсер такого типа соединяется с драйвером uvm_push_driver. Внутри которого содержится реализация метода put вызываемого из сиквенсера и транслируемого посредством механизма соединения интерфейсов.
Для управления процессом выбора транзакций класс uvm_sequncer_base предоставляет соответсвующие интерфесы: is_blocked, has_lock, lock, grab, unlock, ungrab, is_grabbed, и т.д.
Назначение класса uvm_monitor – наблюдать активность на проводном интерфейсе и преобразовывать её в транзакции, которые после будут отправлены на сравнение или анализ в блок Scoreboard, или на основе этих транзакций будут формироваться новые транзакции, в этом случае транзакции отправятся обратно в секвенсер, а оттуда к ним будет иметь доступ сиквенс и на их основе генерировать новые транзакции. Обмен и пересылка сиквенсов осуществляется через порты анализа. Обычно если протокол, который реализует агент, простой, то монитор имеет один порт типа uvm_analisys_port. Рекомендуется порт такого же типа создавать на уровне агента и соединять их между собой. Тем самым пользователь может подключиться к порту агента и при этом не заботиться о его внутренней структуре.
class sram_monitor #(type SEQ_ITEM = sram_item, type SRAM_VIF = virtual sram_if) extends uvm_monitor;
uvm_analysis_port #(SEQ_ITEM) mon_analysis_port; //TLM analysis port
SRAM_VIF mon_if; bit wen_prev; event send_read_tr; uvm_analysis_port #(uvm_sequence_item) item_collected_port; uvm_analysis_port #(SEQ_ITEM) reg_predictor_port; string mode; extern function new(string name = "sram_monitor",uvm_component parent);
`uvm_component_param_utils_begin(sram_monitor#(SEQ_ITEM,SRAM_VIF)) `uvm_component_utils_end
extern virtual function void build_phase(uvm_phase phase); extern virtual function void end_of_elaboration_phase(uvm_phase phase); extern virtual function void start_of_simulation_phase(uvm_phase phase); extern virtual function void connect_phase(uvm_phase phase); extern virtual task reset_phase(uvm_phase phase); extern virtual task configure_phase(uvm_phase phase); extern virtual task run_phase(uvm_phase phase); extern protected virtual task tx_monitor(); extern protected virtual task get_trans(); extern protected virtual task get_trans_inv(); endclass : sram_monitor function sram_monitor::new(string name = "sram_monitor",uvm_component parent); super.new(name, parent); mon_analysis_port = new ("mon_analysis_port",this); reg_predictor_port = new ("reg_predictor_port", this); endfunction : new function void sram_monitor::build_phase(uvm_phase phase); super.build_phase(phase); item_collected_port = new("item_collected_port",this); endfunction : build_phase function void sram_monitor::connect_phase(uvm_phase phase); super.connect_phase(phase); uvm_config_db#(SRAM_VIF)::get(this, "", "sram_if_db_name", mon_if); uvm_config_db#(string)::get(this, "", "work_mode", mode); endfunction : connect_phase function void sram_monitor::end_of_elaboration_phase(uvm_phase phase); super.end_of_elaboration_phase(phase); if (mon_if == null) `uvm_fatal("NO_CONN", "Virtual port not connected to the actual interface instance"); endfunction : end_of_elaboration_phase function void sram_monitor::start_of_simulation_phase(uvm_phase phase); super.start_of_simulation_phase(phase); endfunction : start_of_simulation_phase task sram_monitor::reset_phase(uvm_phase phase); super.reset_phase(phase); phase.raise_objection(this,""); phase.drop_objection(this); endtask : reset_phase task sram_monitor::configure_phase(uvm_phase phase); super.configure_phase(phase); phase.raise_objection(this,""); phase.drop_objection(this); endtask : configure_phase task sram_monitor::run_phase(uvm_phase phase); fork tx_monitor(); join endtask : run_phase task sram_monitor::tx_monitor(); forever begin if (mode == "ce_we_inv") begin get_trans_inv(); end else begin get_trans(); end end endtask : tx_monitor task sram_monitor::get_trans(); SEQ_ITEM tr_read, tr_read_c; SEQ_ITEM tr_write, tr_write_c; @(mon_if.pck); if (mon_if.pck.cen === 1'b1) begin ……………….. end endtask : get_trans |
Рис 5.9 Реализация монитора
Рекомендуется все необходимые настройки для агента собираться в одном классе. В этом случае конфигурирование всех компонентов агента выполняется на основе значений полей данного класса. Такой объект будет называться Сonfiguration object.
В конечном счете каждый агент должен иметь объект конфигурации, который будет содержать ссылку на виртуальный интерфейс, эта ссылка передается в драйвер и монитор, чтобы они могли проводить обработку активности на интерфейсе. Объект конфигурации также должен содержать информацию, которая позволит настроить внутреннюю структуру агента. К примеру, если проектируется агент для дальнейшего повторного использования, то обязательно должна быть переменная которая настраивает агента как пассивный или активный, а для активного агента должна быть настройка агента как ведущий(master) или ведомый(slave). От этих настроек будет зависеть внутренняя структура - будет создаваться драйвер или секвенсер или только монитор (для пассивного агента), если будет, то мастер драйвер\секвенсер или ведомый драйвер\секвенсер Или, к примеру, параметры, которые будут влиять на тип протокола который будет реализовывать агент. Так же стандартно считается, что в объекте конфигурации присутствует бит, отвечающий за то, будет ли собираться функциональное покрытие или нет.
В свою очередь компоненты driver, monitor, sequencer в процессе своей работы манипулируют транзакциями, следовательно, в пакет агента должен быть включен класс, определяющий тип транзакций, и эти компоненты следует параметризировать типом транзакции или параметрами позволяющими определить эту транзакцию. Так же компоненты monitor и driver работают с проводным интерфейсом, и если требуется одновременно реализовывать несколько однотипных но по-разному сконфигурированных агентов, то придется составляющие компонентов параметризировать еще и типом используемого интерфейса.
class sram_agent_cfg extends uvm_object; uvm_active_passive_enum is_active; bit master; int intf_id;
`uvm_object_utils_begin(sram_agent_cfg) `uvm_field_enum ( uvm_active_passive_enum, is_active, UVM_DEFAULT ) ; `uvm_field_int ( master, UVM_DEFAULT ) ; `uvm_field_int ( intf_id,UVM_DEFAULT ) ; `uvm_object_utils_end
function new(string name = "cbm_env_cfg"); super.new(name); endfunction
function void set_default(); is_active = UVM_PASSIVE; master = 0; intf_id = 1; endfunction : set_default endclass : sram_agent_cfg |
Рис 5.10 Реализация объекта конфигурации
В UVM библиотеке реализован механизм передачи объектов конфигурации и любых параметров. Рекомендуется использовать классы uvm_resource, uvm_resource_db и uvm_config_db для передачи объектов конфигурации и параметров внутри тестового окружения. В большинстве случаев рекомендуется использовать uvm_config_db класс, в котором реализованы интерфейсы для отправки параметра и для его получения, а также этот класс предоставляет доступ к глобальному хранилищу установленных параметров и позволяет передавать и получать параметры по иерархии или по имени. Для этого применяются интерфейсы set, get, exist, wait_modified.
static function bit get (uvm_component cntxt, string inst_name, string field_name, inout T value) static function void set(uvm_component cntxt, string inst_name, string field_name, T value) |
Рис 5.11 Описание интерфейса получение и отправки параметров в базу конфигурации
uvm_config_db#(uvm_bitstream_t)::get(cntxt,...) uvm_config_db#(uvm_object)::set(cntxt,...) |
Рис 5.12 Пример установки и получения параметра используя базу конфигурации
Класс этого типа реализовывает блок сбора функционального покрытия. Этот блок не является обязательным для агента. Однако на уровне окружения он должен присутствовать для оценки полноты покрытия блока функциональными тестами. Класс изначально содержит порт типа uvm_analisys_export с именем analisys_export, к которому можно подключить порт типа uvm_analisys_port из монитора, и на основе значений в приходящих транзакциях проверять группы функционального покрытия. А также виртуальный write(T t) метод который нужно реализовать. Именно в нем обычно и выполняет проверку групп покрытия.
Класс, который реализует блок проверки корректности транзакций. В этом классе может производиться обработка получаемых транзакций, проверка их корректности. Обычно внутри этого класса создаются компоненты сравнения пары транзакций и объект поведенческой модели для сравнения, и соединяются и подключаются к портам трансляции транзакций. Допустим упрощенный вариант компонента этого типа, в таком случае сравнение и поведенческая модель реализована в виде функций и процедур прямо в этом компоненте.
Внутри агента используется очень редко. Обычно это компонент уровня окружения.
Окружение или сокращенно от английского env – это компонент-контейнер группирующий вместе под-компоненты в один блок более высокого уровня иерархии.
К примеру проверяемый блок имеет несколько проводных интерфейсов передачи данных и конфигурации. Для проверки в окружении необходимо использовать несколько агентов способных работать с этими интерфейсами. Также на этом уровне должны быть блоки, проверяющие корректность поведения проверяемого устройства и собирающие покрытие кода. Это и будет UVM окружение (тестовая среда) уровня базовых блоков. Оно используется для группировки агентов необходимых для общения со всеми интерфейсами DUT'а в одном месте. Классы используемые в окружении собираются в SystemVerilog пакет, внутрь которого импортируются пакеты используемых агентов. Кроме класса агента пакет окружения может содержать другие классы.
Может присутствовать компонент Scoreboard, она контролирует, чтобы работа DUT была корректной. На уровне окружения Scoreboard в большинстве случаев будет заниматься сравнением транзакций, приходящих по меньшей мере от двух агентов, которые присутствуют внутри окружения.
Внутри scoreboard на уровне окружения может присутствовать предиктор – это компонент, который вычисляет (предсказывает) ожидаемый ответ на входное воздействие.
Так же на уровне окружения присутствует блок функционального покрытия. Этот компонент собирает и анализирует функциональное покрытие, он обычно содержит одну или несколько групп покрытия (covergroups), которые используются для сбора информации по функциональному покрытию, связанную с теми событиями, которые случились в тестовом окружении на протяжении всего теста. Этот блок узко специализирован и его можно использовать только с конкретным проектируемым DUT. Это значит, что чаще всего, его не получится использовать повторно в другом похожем проекте, но с отличной от предыдущей реализацией DUT
Окружение должно иметь объект конфигурации который будет активироваться тем инженером который пишет тесты, для того чтобы контролировать какие под-блоки окружения нужно создавать а какие нет и в каком режиме они будут работать. Объект конфигурации окружения также должен содержать указатели на объекты конфигурации каждого из агентов, составляющих окружение. Это делается для того чтобы после их можно было назначить и передать агентам в окружении используя интерфейсы реализованные в uvm_config_db классе.
Рис 5.13 Тестовое окружение блочного уровня
(https://s3.amazonaws.com/cookbook.verification.academy/images/500px-Block_level_uvm_hierarchy.gif)
На рисунке выше представлено тестовое окружение (testbench) блочного уровня, который состоит из последовательных тестов. Внутри тестов создается окружение, которое в свою очередь содержит несколько анализирующих компонентов и два агента.
Рис 5.14 Тестовое окружение системного уровня
(https://s3.amazonaws.com/cookbook.verification.academy/images/500px-Integration_level_uvm_hierarchy.gif?AWSAccessKeyId=AKIAIEMNC32PGID7APKA&Expires=1464030717&Signature=8X0S2wYEWV0pJhmOBkVIFi%2BjEjs%3D)
Когда же блоки интегрируют в созданную подсистему (вертикальное повторное использование) – это достигается повторным использованием окружений. Окружения объединяются внутри окружения более высокого уровня, обычно этот класс тоже наследуется от uvm_env.
Обычно класс самого верхнего уровня в тестовом окружении представляет собой наследника от класса uvm_test, этот класс отвечает за конфигурация всего тестового окружения, инициализацию процесса построения объектов тестбенча внутрь по иерархии, после инициализации или генерации последовательностей входных данных и запуска последовательностей на выполнение. Класс, который содержит функции создания настройки окружения и запуска базовых последовательностей действий, свойственных для всех тестов, называют обычно базовым тестом (base_test). Для того, чтобы реализовать проверку блока в тестовом окружении, реализуют множество тестов, но все эти тесты обычно наследуются от базового теста, чтобы не дублировать действия по созданию и настройке тестбенча.
Для запуска тестового окружения UVM содержит таск run_test(), используя который можно запустить тест, установленный по умолчанию. Обычно запуск производят в SVerilog/Verilog модуле верхнего уровня командой.
initial run_test(); |
Рис 5.14 Запуск теста на выполнение в модуле верхнего уровня
Для того чтобы задать имя теста для запуска используются параметры командной строки симулятора +UVM_TESTNAME=”имя_теста”. Запуск разных тестов без изменения кода основывается на механизме замены типа с использованием фабрики, поэтому все классы тестов должны содержать макросы реализующие функционал фабрики(см. макросы).
Продолжим процесс создания тестового окружения. Добавим блок проверки корректности получаемых данных, для этого добавим компаратор, модель для сравнения, блок сбора покрытия.
Проект создан и проверен на сайте www.edaplayground.com
Ссылка на проект: http://www.edaplayground.com/x/2bwY
Будет далее придерживаться принятой концепции именования файлов.
Для того чтобы проверять корректность результатов, реализуем блок наследуемый от uvm_scoreboard. Внутри создадим два порта типа uvm_analysis_imp_sym_sb, которые позволят получать пару транзакций ожидаемую и реальную и после сравнивать их.
`uvm_analysis_imp_decl(_sym_sb_expected) `uvm_analysis_imp_decl(_sym_sb_observed)
class sym_sb extends uvm_scoreboard; uvm_analysis_imp_sym_sb_expected#(vip_tr, sym_sb) expected; uvm_analysis_imp_sym_sb_observed#(vip_tr, sym_sb) observed; int n_obs_thresh = 10; local int m_n_obs; local bit [7:0] m_sb[$]; `uvm_component_utils(sym_sb) function new(string name, uvm_component parent = null); super.new(name, parent); expected = new("expected", this); observed = new("observed", this); endfunction function void build_phase(uvm_phase phase); void'(uvm_config_db#(int)::get(this, "", "n_obs_thresh", n_obs_thresh)); endfunction function void write_sym_sb_expected(vip_tr tr); `uvm_info("SB/EXP", $sformatf("Expected: 0x%h", tr.chr), UVM_MEDIUM) m_sb.push_back(tr.chr); endfunction function void write_sym_sb_observed(vip_tr tr); bit [7:0] exp;
`uvm_info("SB/OBS", $sformatf("Observed: 0x%h", tr.chr), UVM_MEDIUM) exp = m_sb.pop_front(); if (tr.chr !== exp) begin `uvm_error("SB/MISMTCH", $sformatf("Symbol 0x%h observed instead of expected 0x%h", tr.chr, exp)); end m_n_obs++; endfunction task reset_phase(uvm_phase phase); m_n_obs = 0; m_sb.delete(); endtask
task main_phase(uvm_phase phase); phase.raise_objection(this, "Have not checked enough data"); wait (m_n_obs > n_obs_thresh); phase.drop_objection(this, "Enough data has been observed"); endtask endclass |
Рис 5.15 Реализация блока сбора и анализа результатов
В результате в тесовое окружение будет помещен блок сравнения и после подключения на уровне окружения он позволит наблюдать и контролировать выполнение тестов.