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

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

.pdf
Скачиваний:
0
Добавлен:
06.09.2026
Размер:
1 Мб
Скачать
super.connect_phase(phase); u_agent.uart_ap.connect(u_sb.sb_export_before);
b_agent.bus_ap.connect(u_sb.sb_export_after);
endfunction: connect_phase
endclass: uart_env
В класс окружения в данном примере подключаются два аген­та – для интерфейса
uart и для шины. При этом интерфейс шины
настраивается на пассивный режим работы в фазе build. Также подключается табло для сбора транзакций. Порты TLM соединяют­ся с агентами в фазе connect. Данное простое окружение для блока
uart может в дальнейшем, при переходе на другой уровень отлад-
ки, быть интегрировано в окружение более высокого уровня, по аналогии с подключением агентов здесь.
7.2. Тест
После того как готово UVM окружение, начинается разработка теста. Очень часто, особенно для крупных проектов, разработкой теста и разработкой тестового окружения занимаются разные лю­ди. Тестовое окружение универсально и используется в нескольких тестах. Тесты же направлены на проверку заданных функций и по­крытия конкретных пунктов тестового плана. Как правило, созда­ется базовый тестовый класс, в который подключается тестовое окружение и выполняются общие для всех тестов действия, а далее отдельные тесты наследуются от этого базового класса для реали­зации требуемого от них функционала. Рассмотрим класс базового теста:
class base_uart_test extends uvm_test;
uart_env m_top_env;
uart_cfg m_cfg0;
`uvm_component_utils (base_test)
function new(string name,uvm_component parent=null);
super.new (name, parent);
endfunction
virtual function void build_phase (uvm_phase phase);
super.build_phase (phase);
– 71 –
m_top_env = uart_env::type_id::create ("m_top_env",this);
m_cfg0 = uart_cfg::type_id::create ("m_cfg0",this);
set_cfg_params ();
uvm_config_db #(uart_cfg):: set (this, "m_top_env.b_agent", "m_cfg0", m_cfg0);
endfunction
virtual function voidset_cfg_params ();
if(! uvm_config_db #(virtualuart_if):: get (this, "", "uart_if", m_cfg0.vif))
begin
`uvm_error (get_type_name (), "DUT Interface not found !")
end
m_cfg0.active = UVM_ACTIVE;
endfunction
virtual function void end_of_elaboration_phase (uvm_phase phase);
uvm_top.print_topology ();
endfunction
function void start_of_simulation_phase (uvm_phase phase);
super.start_of_simulation_phase (phase);
uvm_config_db#(uvm_object_wrapper)::set(this,"m_top_env.uar t_agent.sequencer.main_phase","default_sequence", base_sequence::type_id::get());
("m_seq");
endclass
endfunction
virtualtask run_phase (uvm_phase phase);
uart_seq m_seq = uart_seq::type_id::create
super.run_phase(phase);
phase.raise_objection (this);
m_seq.start(m_env.seqr);
phase.drop_objection (this);
endtask
– 72 –
Все тесты наследуются от класса. В начале теста создаются де­скрипторы используемых в тесте UVM окружения и конфигураци­онного объекта. Конфигурационный объект – класс, унаследован­ный от
uvm_object и включающий в себя все необходимые поля
для настройки окружения. В приведенном примере он будет иметь только одно поле active для выбора режима работы агентов.
В фазе build создаются экземпляры всех объектов и вызывается функция set_cfg_params, которая выполняет настройку конфигу­рационного объекта, после чего этот объект заносится в конфигу­рационную базу UVM. На работе конфигурационной базы следует остановиться чуть подробнее.
Конфигурационная база uvm_config_db обеспечивает доступ к централизованной базе данных, где можно хранить и получать ин­формацию о типе. config_db может содержать скалярные объекты, дескрипторы классов, очереди, списки или даже виртуальные ин­терфейсы. База данных имеет две таблицы – имен и типов, и каж­дый ресурс, помещенный в базу данных, заносится в обе. Таким образом, каждый ресурс может быть извлечен по имени или типу. База данных доступна во всех элементах тестового окружения. Для сохранения и извлечения данных из базы используются статиче­ские методы uvm_config_db :: set и uvm_config_db :: get со­ответственно.
void uvm_config_db#(type T = int)::set(uvm_component cntxt, string inst_name, string field_name, T value);
typeT используется в качестве параметра класса uvm_config_db
для идентификации типа объекта, который заносится в
uvm_config_db. Для переменных перечислимого типа надо исполь-
зовать
int.
cntxt – указатель на класс-наследник класса uvm_component,
этот параметр определяет область видимости записи.
inst_name – имя объекта относительно cntxt, в котором видно
запись.
field_name – имя записи, value – записываемое значение.
Параметры функции
set () – cntxt, inst_name и field_name
позволяют использовать несколько разных путей к одному и тому же объекту.
Cntxt использует фактическую иерархию объектов,
– 73 –
тогда как inst_name и field_name используют путь иерархии с именами, данными объектам в методе
В полях
inst_name и field_name допускается использование
create () / new ().
символов «*», «+», «?» для обозначения произвольных символов.
«*» означает ноль или больше произвольных символов, «+» – один
и больше, а
Для извлечения данных из базы данных используется метод
uvm_config_db#(<type>)::get(uvm_component cntxt, string inst_name, string field_name, ref value);
«?» – строго один.
get:
Параметры данной функции аналогичны функции set, за ис­ключением того, что функция использует их не для записи, а для поиска.
Еще одна полезная функция UVM это build-time
configuration, т.е. настройка во время сборки. Во время фазы
build, при вызове родительской реализации функции для
uvm_component, происходит автоматический вызов функции get
для всех полей класса, которые есть в базе денных. Для этого поля должны быть зарегистрированы в базе uvm с помощью макросов, и
super.build_phase(phase) должна вызываться в фазе build.
Касательно применения uvm_config_db. В состав окружения в общем случае входят несколько агентов, в каждый агент, как ми­нимум, входит драйвер, монитор. Используя uvm_config_db, можно задать значения конфигураций для всех компонент теста напря­мую, прописав верные пути. Но правильнее передавать конфигура­ции по иерархии. Т.е. тест задает настройки окружения, окружение передает их агенту, при необходимости внеся правки и задав зна­чения по умолчанию, если от теста ничего не получено. И так же драйвер получает данные от агента.
7.3. Виртуальные последовательности
и генераторы последовательностей
В примерах последовательностей, которые были рассмотрены ранее, каждая последовательность управляла только одним драйве­ром и исполнялась только на одном генераторе последовательно­стей. Это могли быть и вложенные последовательности, где одна
– 74 –
последовательность могла поочередно запускать подпоследова­тельности, но тем не менее, все они исполнялись только на одном генераторе.
В реальности последовательности за исключением наиболее простых и базовых требуют одновременного взаимодействия с не­сколькими интерфейсами. Это, в свою очередь, требует наличия генератора последовательностей, которые будут иметь доступ к нескольким агентам.
Генераторы последовательностей, которые содержат в себе де­скрипторы нескольких дочерних генераторов, называют виртуаль- ными генераторами последовательностей. Соответственно, по­следовательности, вызывающие запуск дочерних на нескольких генераторах, – виртуальными последовательностями. Термин «виртуальный» применяется к генераторам и последовательностям, так как ни те, ни другие не взаимодействуют напрямую с драйве­ром, т.е. не генерируют транзакции. Виртуальные генераторы по­следовательностей не исполняют последовательности сами, а за­пускают их на дочерних генераторах, а виртуальные последова­тельности не генерируют транзакции, а контролируют взаимную работу других последовательностей.
Рассмотрим следующий пример:
class uart_v_seqr extends uvm_sequencer;
uvm_sequencer#(uart_item)u_seqr;
uvm_sequencer#(bus_item) b_seqr;
function new(string name='uart_v_seqr', uvm_component parent);
... endfunction
endclass
class uart_v_seq_base extends uvm_sequence;
`uvm_object_utils(uart_v_seq_base)
`uvm_declare_p_sequencer(uart_v_seqr)
function new(string name='uart_v_seq_base');
super.new(name)
endfunction
endclass
– 75 –
class uart_v_seq1 extends uart_v_seq_base;
`uvm_object_utils(uart_v_seq1)
function new(string name='uart_v_seq1');
super.new(name)
endfunction
task body();
uart_sequence u_seq;
bus_sequence b_seq;
u_seq = uart_sequence::type_id::create('u_seq');
b_seq = bus_sequence::type_id::create('b_seq');
fork begin
u_seq.start(p_sequencer.u_seqr);
b_seq.start(p_sequencer.b_seqr);
end
...
join
endtask endclass
class base_test extends uvm_test;
`uvm_component_utils(base_test);
uart_v_seqr v_sqr;
uart_env env;
function new(string name='base_test',uvm_component par­ent =null);
endfunction
function build_phase(uvm_phase phase);
env = uart_env::type_id::create('env');
v_sqr = uart_v_seqr::type_id::create('v_sqr');
endfunction
function connect_phase(uvm_phase phase);
v_sqr.u_seqr = env.u_agent.sequencer;
v_sqr.b_seqr = env.b_agent.sequencer;
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection();
v_seq1 v_seq;
vseq = v_seq1::type_id::create('v_seq');
v_seq.start(v_sqr);
phase.drop_objection();
endtask endclass
– 76 –
Класс uart_v_seqr описывает виртуальный генератор последо­вательностей. Этот генератор содержит в себе два дескриптора до­черних генераторов – u_seqr и b_seqr, которые позволяют испол­няемой на нем последовательности получать доступ в два различ­ных агента, подключенных к интерфейсу uart и шине.
uart_v_seq_base – базовая виртуальная последовательность,
которая является основой для создания последовательностей в те­сте. Используя макрос `uvm_declare_p_sequencer, она получает дескриптор виртуального генератора последовательностей, с по­мощью которого последовательности, унаследованные от данной, например uart_v_seq1, смогут получать к нему доступ. Как расска­зывалось в одной из предыдущих лекций, этот макрос создает пе­ременную p_sequencer, являющуюся указателем на генератор по­следовательностей, на котором запущена вызвавшая его последо­вательность.
Класс uart_v_seq1 – виртуальная последовательность, которая создает две подпоследовательности и запускает их на двух различ­ных генераторах, получив к ним доступ через указатель на свой генератор последовательности p_sequencer.
В base_test иллюстрируется работа с виртуальным генерато­ром и виртуальными последовательностями. При создании окру­жения виртуальный генератор последовательностей создается точ­но так же, как и все компоненты – с использованием функции cre-
ate. В фазе подключения два дескриптора этого генератора под-
ключаются к двум, соответствующим им генераторам последова­тельностей в агентах окружения.
В фазе run создается экземпляр виртуальной последовательно­сти, которая и запускается на виртуальном генераторе последова­тельностей.
В данном примере следует обратить внимание еще на одну де­таль. В начале фазы run теста вызывается метод фазы
phase.raise_objection().Этот метод накладывает возражение на
переключение фаз, и пока это возражение не будет снято, фазы не будут переключены. По завершении выполнения виртуальной по­следовательности метод phase.drop_objection(); снимает возра­жение на завершение фазы. Если нет возражений от других эле­ментов окружения, фаза будет завершена.
– 77 –
Лекция 8. Утверждения и функциональное покрытие
Введение
После того как тестовое окружение готово и тесты написаны, возникают вопросы: как убедиться, что тесты проверили все, что надо? Что не было нарушений протокола, которые не отразились на результате, но могут стать причиной фатальных ошибок в реаль­ном устройстве? Для этого язык SystemVerilog содержит такие кон­струкции, как assert и covergroup. Эти два механизма подробно рассмотрены в лекции.
8.1. Утверждения
В SystemVerilog есть два вида утверждений: непосредственные (assert) и параллельные (assert property).
Непосредственные утверждения являются процедурными опе­раторами. Assert – утверждение, что что-то должно быть истиной, подобно оператору if. Разница в том, что оператор if не утверждает, что выражение истинно, а просто проверяет его истинность.
if(A == B)...
assert(A == B);
В отличие от if, если утверждение не верно, то генерируется ошибка. По аналогии с if, assert имеет две ветки возможных дей­ствий – на случай успешного и неуспешного выполнения утвер­ждения. Причем любое действие может быть пропущено.
assert(A==B) $display("OK. A equals B");
else $error("It's gone wrong");
Нарушение утверждений имеет связанные с ними уровни важ­ности. Существуют три системных уровня важности, которые мо­гут быть включены в ветку нарушения: $fatal, $error (по умол­чанию), $warning или $info. В качестве действия в случае успеха
– 78 –
или неудачи может выступать любое процедурное выражение язы­ка. В том числе объединенное в
begin…end.
Параллельные утверждения устанавливают, что указанные свойства должны быть истинными. Например, «сигналы чтения и записи никогда не должны использоваться вместе».
assert property(!(Read&&Write));
Параллельные утверждения проверяются на протяжении всего моделирования. Они обычно появляются вне
initial или always
блоков в модулях, интерфейсах и программах.
При этом параллельные утверждения могут выполнять не толь­ко одномоментные сравнения, но и контролировать последователь­ности действий.
assert property (@ (posedgeClk) Req | -> ## [1: 2] Ack);
Req – простая последовательность (просто логическое выраже-
ние), а ##[1: 2]Ack – более сложное выражение последовательно­сти, означающее, что Ack имеет значение true хотя бы на одном из двух следующих тактов.
Конструкция импликации «|->» позволяет пользователю кон­тролировать последовательности на соответствие определенным критериям, например, прикрепить условие к последовательности и учитывать последовательность, только если условие является ис­тинным. Левый операнд импликации называется предшествующей последовательностью, а правый – последующей.
Если нет совпадения с выражением предшествующей последо­вательности, импликация завершается успешно, возвращая
true.
Если есть совпадение, то для каждого успешного завершения предшествующей последовательности проверяется выражение по­следующей последовательности.
Существует две формы импликации: перекрывающиеся с по­мощью оператора «|->» и не перекрывающиеся с помощью опе­ратора «|=>».
Для перекрывающейся импликации, если есть совпадение в вы­ражении предшествующей последовательности, первый элемент
– 79 –
последующего выражения последовательности оценивается на том же такте:
s1 |-> s2;
Например, если последовательность s1 выполняется, то и s2 должна выполняться. Если s1 не выполняется, то s2 не важно.
Для параллельных утверждений свойства могут указываться как непосредственно в операторе, так и создаваться отдельно, и затем использоваться. Аналогичным образом последовательности могут выделяться в отдельные блоки:
sequence req_sq
Req;
endsequence
sequence ack_sq
##[1:2] Ack;
endsequence
property not_read_and_write_p;
not(Read && Write);
endproperty
property handshake;
@(posedge Clk) req_sq |-> ack_sq;
endproperty
assert property(not_read_and_write_p);
assert property(handshake);
В приведенном примере одно из свойств работает асинхронно, а второе срабатывает по переднему фронту синхроимпульса. Син­хронизация может задаваться в последовательности, свойстве или указываться перед утверждением в always блоке:
always @(posedge clk) assert property (p);
Большинство систем имеет сигнал сброса. И зачастую работа утверждения во время сброса может нарушаться. По этой причине в утверждениях надо указывать, что они не должны проверяться
– 80 –