Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Верификация цифровых устройств. Курс лекций. Учебное пособие

.pdf
Скачиваний:
0
Добавлен:
06.09.2026
Размер:
1 Мб
Скачать
task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(u_item);
drive_item(u_item);
seq_item_port.item_done();
end
endtask: run_phase
endclass: uart_driver
Класс драйвера нашего устройства наследуется от класса драй-
uvm_driver с заданием типа транзакций, получаемых им от
вера генератора последовательностей. Далее идет макрос для регистра­ции созданного класса. Это делает доступным использование мак­росов
uvm для выполнения базовых операций, таких как копирова-
ние и печать, с данным классом. Однако практика использования этих макросов показала, что они затрудняют отладку кода и делают его более громоздким, нежели реализованные вручную методы классов. Поэтому в данном курсе использование данных макросов не затрагивается.
Далее создается виртуальный интерфейс. Как известно, интер­фейсы в SystemVerilog позволяют объединять сигналы в группы и также дают дополнительный функционал по их подключению. Ин­терфейс SystemVerilog является статическим по своей природе, то­гда как классы являются динамическими. По этой причине не раз­решается объявлять интерфейс внутри классов, но разрешается ссылаться на интерфейс или указывать на него. Виртуальный ин­терфейс – это переменная типа интерфейса, которая по своей сути является указателем на реальный интерфейс и используется в клас­сах для обеспечения доступа к его сигналам.
После объявления интерфейса идет функция «конструктор клас­са». Большинство конструкторов в
uvm обязательно имеют два па-
раметра – строку с именем экземпляра и указатель на родительский элемент в иерархии.
Следующей идет функция build_phase(), которая запускается в фазе build, и настройка компонент. В данном случае настройка сводится к подключению виртуального интерфейса к физическому. Причем, настройка выполняется с помощью конфигурационной базы UVM. Про эту базу речь пойдет позже.
– 51 –
Задача run_phase() запускается при старте фазы run и выпол­няется до ее завершения. В этой задаче очередная транзакция счи­тывается из порта seq_item_port, передается на обработку во вспомогательную задачу, которая будет отправлять данные через интерфейс, а затем в seq_item_port записывается подтверждение обработки транзакции.
5.2.2. Монитор
Второй важный компонент каждого агента – монитор. Его зада­ча – обратная задаче драйвера, а именно: перевод сигналов из ин­терфейса в транзакции TLM для последующего разбора и анализа.
Монитор может быть как самым простым, который получает от­дельные байты с интерфейса и передает их в форме транзакций выше по иерархии, так и более сложным, который выделяет данные на более высоком уровне абстракции. Также в составе монитора допускается наличие базовых проверок, например, на соответствие протоколу и функции сбора покрытия. При этом функции сбора покрытия и анализа протокола должны быть выключаемыми для сохранения возможности работы с некорректными транзакциями. Все мониторы наследуются от класса uvm_monitor, который в свою очередь унаследован от класса uvm_component. Рассмотрим пример монитора:
class uart_monitor extends uvm_monitor;
virtual uart_if vif;
bit checks_enable =1;.
bit coverage_enable = 1;
uvm_analysis_port #(uart_item) item_collected_port;
event cov_transaction; // Events needed to trigger covergroups
uart_item uart_collected;
`uvm_component_utils_begin(uart_monitor)
`uvm_field_int(checks_enable, UVM_ALL_ON)
`uvm_field_int(coverage_enable, UVM_ALL_ON)
`uvm_component_utils_end
covergroup cov_trans @cov_transaction;
option.per_instance = 1;
... // Coverage bins definition
endgroup : cov_trans
– 52 –
function new(string name, uvm_component parent);
super.new(name, parent);
cov_trans =new();
cov_trans.set_inst_name({get_full_name(), ".cov_trans"});
uart_collected=new();
item_collected_port =new("item_collected_port",this);
endfunction:new
virtual task run_phase(uvm_phase phase);
collect_transactions(); // collector task.
endtask: run
virtual protected task collect_transactions();
foreverbegin
@(posedge vif.rx);
...
if (checks_enable)
perform_transfer_checks();
if(coverage_enable)
perform_transfer_coverage();
item_collected_port.write(uart_collected);
end
endtask: collect_transactions
virtual protected function void perform_transfer_coverage();
-> cov_transaction;
endfunction: perform_transfer_coverage
virtual protected function void perform_transfer_checks();
...
endfunction: perform_transfer_checks
endclass: uart_monitor
Так же, как и в состав драйвера, в состав монитора входит ука­затель на интерфейс, который позволит получить доступ к сигна­лам на выходе тестируемого устройства. Этот интерфейс аналогич­ным образом настраивается на этапе фазы build.
Биты checks_enable и coverage_enable предназначены для управления проверкой и сбором покрытия. Эти функции включены по умолчанию, но могут быть изменены.
– 53 –
Важным элементом монитора является uvm_analysis_port – порт для передачи собранных транзакций, который в отличие от порта драйвера позволяет подключение нескольких получателей; т.е. данные, которые соберет монитор, могут использоваться для проверки (при необходимости в нескольких блоках), сбора стати­стики, вывода информации и т.п.
По аналогии с драйвером, монитор регистрируется в
uvm для ис-
пользования макросов. При этом, поскольку монитор имеет встро­енные переменные, они также могут быть зарегистрированы для участия в макросах копирования, вывода и т.п.
Данный монитор предусматривает сбор покрытия. Для этого ис­пользуются covergroup, речь о которых пойдет дальше. Для функ­ционирования сбора покрытия создается внутренние событие –
cov_transaction, которое будет запускать covergroup.
Переменная uart_itemuart_collected используется для фор­мирования транзакций и передачи их всем заинтересованным бло­кам. При этом данная транзакция не обязательно равна транзакции, использованной в драйвере – uart_transaction.
Следует помнить, что все объекты классов надо создавать в кон­структоре – в функции new(). run_phase() запускается во время моделирования и выполняет основную работу монитора – сбор и анализ сигналов. При этом в данном примере этот функционал вы­несен в отдельную задачу virtual protected task col-
lect_transactions. Это позволяет при необходимости перегрузить
эту задачу, простым действием получив новый монитор на основе существующего, но с другой логикой обработки сигналов, напри­мер, при переходе на другой физический интерес. Функции per-
form_transfer_coverage() и perform_transfer_checks() реали-
зуют функционал сбора покрытия и проверки данных. Они также могут быть перегружены при необходимости, что делает создан­ный класс шаблоном для большого спектра возможных интерфей­сов.
После проверки и сбора покрытия полученный элемент тран­закции записывается в порт. При этом выполняется его копирова­ние, т.е. в порт записывается не текущий объект, а его копия, тогда как с текущим объектом можно продолжать работу в следующей итерации.
– 54 –
5.2.3. Агент
Как говорилось ранее, в состав простого агента должны входить драйвер, монитор и генератор последовательностей («секвенсор»). Задача последнего – исполнение последовательностей, т.е. форми­рование транзакций на основе заданных правил и передача их драйверу. В большинстве несложных тестовых окружений класс генератора последовательностей uvm_sequencer не перегружается, так как не требует внесения какого-либо дополнительного функци­онала.
Общая структура и реализация агента показана на рис. 5.1 и в листинге ниже.
class uart_agent extends uvm_agent;
...
uvm_sequencer #(uart_item) sequencer;
uart_driver driver;
uart_monitor monitor;
uvm_analysis_port#(uart_item) uart_ap;
...
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase)
Рис. 5.1. Общая структура агента
– 55 –
monitor = uart_monitor::type_id::create("monitor",this);
if(is_active == UVM_ACTIVE)begin
sequencer = uvm_sequencer#(uart_item)::type_id::create("sequence r",this);
driver = uart_driver::type_id::create("driver",this);
end
endfunction: build_phase
virtual function void connect_phase(uvm_phase phase);
if(is_active == UVM_ACTIVE)begin
driv­er.seq_item_port.connect(sequencer.seq_item_export);
end
monitor.item_collected_port.connect(uart_ap);
endfunction: connect_phase
endclass: uart_agent
В данном агенте, по аналогии с другими классами, в начале должно быть подключение макросов
uvm – в коде оно опущено для
краткости. Далее идет объявление дескрипторов драйвера, монито­ра и генератора последовательностей. Также создается порт
uart_ap для передачи данных от монитора.
Интерес представляет функция фазы build. Как многократно го­ворилось ранее, эта функция должна обеспечить создание всех ди­намических объектов – в нашем случае их три: драйвер, монитор и генератор последовательностей. Но в данном примере для их со­здания используется не стандартный конструктор
type_id::create.
new(), а метод
В данном случае конструктор будет вызван не напрямую, а че­рез фабрику
uvm. Макросы, которые вызываются при создании
каждого нового класса, регистрируют созданный тип данных в фабрике
uvm. Она представляет собой, в частности, таблицу соот-
ветствия типов и их реализаций. При вызове статического метода
create происходит обращение к этой таблице и вызов нужного
конструктора. Такой механизм создания классов позволяет очень просто выполнить подмену типа на тип-наследник, просто заменив соответствующую запись в фабрике. Например, если необходимо
– 56 –
протестировать работу DUT в случае некорректных входных дан­ных, можно создать новый класс драйвера, наследуемый от суще­ствующего, и заставить его на генерацию данных с ошибками, например, эмулируя замыкание на 0 или 1 какой-либо линии.
Далее тест заносит в фабрику запись об изменении типа с по­мощью функции
<original_type> :: type_id :: set_type_override (<sub­stitute_type> :: get_type (), replace);
После этого тест будет работать с измененными типами.
Возвращаясь к функции build_phase агента, следует обратить внимание, что тут используется свойство is_active – это свойство класса
uvm_agent, которое определяет режим работы агента.
В активном режиме агент включает в себя все три компонента и выполняет как генерацию входных воздействий, так и сбор ре­зультатов. В пассивном режиме агент включает в себя только мо­нитор. Этот режим предназначен для пассивного наблюдения и сбора покрытия. Свойство is_active может быть задано как напрямую, так и через конфигурационную базу данных, как в слу­чае с
vif драйвера и монитора.
В connect_phase для активного агента происходит соединение компонент драйвера и генератора последовательностей. Порт мо­нитора item_collected_port «пробрасывается» наружу через порт агента
uart_ap.
– 57 –
Лекция 6. UVM генерация тестовых последовательностей
Введение
Для формирования тестовых воздействий в UVM применяется механизм последовательностей. В лекции рассмотрены механизмы для создания и управления последовательностями. Для ограниче­ния случайных параметров последовательности используется си­стема ограничений – constraint. Это один из основных инструмен­тов случайного тестирования. В лекции детально рассказывается о его применении.
6.1. Последовательности
Задача созданных агентов – в активном режиме формировать те­стовые воздействия на DUT. Для этого на драйвер должны посту­пать транзакции, формируемые генератором последовательностей. Возникает вопрос: чем определяется, какие транзакции формирует генератор? Для этого в UVM определен класс последовательно­сти – uvm_sequencer.
Последовательность UVM – это набор кода System Verilog, ко­торый запускается для того, чтобы «что-то происходило». Обычно последовательность создает транзакцию, рандомизирует ее и от­правляет в секвенсор, а затем в драйвер. Сами по себе последова­тельности не являются данными, они лишь генерируют эти данные.
Последовательности состоят из нескольких транзакций, которые совместно образуют необходимый сценарий воздействия на DUT. Тестовое окружение может включать в себя библиотеку базовых последовательностей вместо одиночных транзакций. Эти последо­вательности могут в дальнейшем использоваться разработчиком для написания теста. Этот подход повышает эффективность по­вторного использования общих моделей воздействий и сокращает время разработки отдельных тестов. Последовательности могут вызывать другие последовательности, создавая тем самым более сложные сценарии.
– 58 –
Для того чтобы запустить последовательность на каком-либо генераторе последовательностей, используется метод
start().
Предварительно можно настроить случайные поля последователь­ности.
virtual task start(uvm_sequencer_base sequencer,
uvm_sequence_base parent_sequence =null,
int this_priority=-1,
bit call_pre_post=1 );
Для метода только первый параметр обязательный – он задает генератор последовательностей, на котором должна быть исполне­на данная последовательность. Второй параметр указывает на ро­дительскую последовательность, если одна запускается из другой. Третий параметр определяет приоритет последовательности на случай, если несколько последовательностей будут запущены на одном генераторе. И последний параметр определяет необходи­мость запуска _pre и _post задач.
При вызове задачи start из нее будут последовательно вызы­ваться следующие методы:
seq.pre_start(); (task)
seq.pre_body(); (task)если call_pre_post == 1
parent_seq.pre_do() (task)если parent_seq !=null
parent_seq.mid_do(this)(func)если parent_seq !=null
seq.body() (task)
parent_seq.post_do(this)(func)если parent_seq !=null
seq.post_body() (task)если call_pre_post == 1
sub_seq.post_start() (task)
Следует обратить внимание, что mid_do и post_do – функции, т.е. не занимают время моделирования, а остальные методы – зада­чи. Эти методы так же, как и pre_do, запускаются для родитель­ской последовательности. Методы pre_body и post_body запустят­ся, только если установлен соответствующий флаг. Таким образом, основной и задачей, которая исполняет последовательность, явля­ется body.
– 59 –
class uart_sequence extends uvm_sequence#(uart_item);
`uvm_object_utils (uart_sequence)
function new(string name = "uart_sequence"); super.new (name); endfunction
task pre_body (); ...
endtask
task body (); req = uart_item::type_id::create("req"); wait_for_grant(); assert(req.randomize()); send_request(req); wait_for_item_done(); get_response(rsp);
endtask
task post_body(); ... endtask
endclass
В качестве параметра в созданный класс передается тип тран­закций, которые он будет генерировать и принимать в качестве от­вета. После запуска задачи body() в ней создается новый элемент последовательности. req – это уже существующая в классе uvm_sequence переменная для хранения сформированной транзак­ции.
Задача wait_for_grant() блокирует дальнейшее выполнение до получения от генератора последовательностей запроса, который тот, в свою очередь, получает от драйвера при вызове им метода
get_next_item. После получения такого запроса выполняется
настройка транзакции и ее передача в генератор последовательно­сти методом send_request. wait_for_item_done() ожидает, пока генератор последовательностей сообщит о завершении обработки текущей транзакции. Это произойдет после вызова драйвером ме­тода item_done().
get_response позволяет драйверу вернуть ответ в последова-
тельность для определения статуса завершившейся транзакции или
– 60 –