Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Верификация цифровых устройств. Курс лекций. Учебное пособие
.pdf
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
driver.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 (<substitute_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 –
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
