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

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

.pdf
Скачиваний:
0
Добавлен:
06.09.2026
Размер:
1 Мб
Скачать
каких-либо других действий. Это опциональный пункт – для воз­врата драйвер должен поместить ответ в канал методом put.
Работа с последовательностями достаточно однотипна для их большинства. Для упрощения и ускорения написания кода в UVM можно использовать готовые макросы.
Самый простой из них – `uvm_do(Item/Seq). Этот макрос при­нимает на вход последовательность или отдельную транзакцию, выполняет для нее все шесть функций, которые находятся в body() в приведенном примере. Следовательно, функция может быть за­менена кодом:
task body();
`uvm_do(req)
endtask
Еще один полезный макрос – `uvm_do_with(Item/Seq, Con-
straints), который также выполняет транзакцию или последова-
тельность на генераторе последовательностей, но с заданием огра­ничений для генерации случайных данных – Constraints. Что это такое, рассмотрим чуть позже.
Также есть макросы, которые только создают элемент –
`uvm_create(Item/Seq), только отправляют его – `uvm_send(Item/Seq), отправляют с заданием случайных значе-
ний – `uvm_rand_send(Item/Seq) и т.п. Полный список можно найти в описании библиотеки.
Отдельно следует отметить макрос `uvm_declare_p_sequencer
SEQUENCER), который создает переменную с именем p_sequencer и
типом генератора последовательностей, указанным в SEQUENCER. Изначально последовательности оторваны от тестового окружения, на котором они выполняются. Единственной переменной, связан­ной с окружением, является m_sequencer. Эта переменная указыва­ет на генератор последовательностей, на котором исполняется те­кущая последовательность. Но m_sequencer имеет тип
uvm_sequencer_base – класса, от которого унаследованы все гене-
раторы последовательностей. По этой причине через m_sequencer нельзя получить доступ к расширенным полям класса конкретного генератора последовательностей. Для этого и создается переменная
p_sequencer, которая будет иметь уже заданный тип.
– 61 –
6.1. Задание случайных значений
Выше упоминалось, что перед отправкой транзакции могут быть «рандомизированы». Рандомизация – процесс создания чего-то случайного. System Verilog randomization – процесс генерации случайных значений для переменной.
SystemVerilog предоставляет несколько методов для генерации случайных данных. Используя любой из этих методов, переменная может быть рандомизирована:
1.
$urandom() и $random()
2.
$urandom_range()
3. std :: Randomize()
4. Randomize()
Функция $urandom() возвращает новое 32-битное случайное число. $random () аналогично $urandom(), но генерирует числа со знаком:
variable = $urandom(seed);
Функция
$urandom_range() возвращает целое число без знака в
указанном диапазоне:
variable=$urandom_range( int unsigned maxval, int unsigned minval = 0 );
Переменные можно рандомизировать с помощью метода std ::
randomize. Он может принимать встроенные ограничения, ис-
пользуя предложение «with».
std::randomize (variable);
std::randomize (variable) with { constraint's; };
В качестве ограничений выступают операции сравнения. Например:
std::randomize(addr, data) with { addr<= 256; data == addr* 4;};
– 62 –
Метод randomize() используется для рандомизации случайных полей классов. Рассмотрим его подробно, так как это основной ме­тод для задания случайных значений полям классов в UVM.
Переменные класса, которые получают случайные значения при рандомизации, называются случайными переменными. Чтобы сде­лать переменные случайными, переменные класса должны быть объявлены с использованием ключевых слов rand и randc моди­фикатора типа.
Следующие типы могут быть объявлены как rand и randc:
переменные любого целочисленного типа;
массивы;
размер массива;
дескриптор объекта.
Переменные, объявленные с ключевым словом rand, являются стандартными случайными переменными. Их значения равномерно распределены по всему диапазону.
Для переменных, объявленных с ключевым словом randc, зна­чения переменных рандомизации не повторяют случайное значение до тех пор, пока не будет присвоено каждое возможное значение.
rand bit [3:0] addr;
randc bit wr_rd;
Чтобы рандомизировать переменные объекта, пользователь должен вызвать метод
randomize ().
object.randomize();
При необходимости можно отключать рандомизацию перемен­ных, используя функцию
rand_mode ().
addr. rand_mode(0)
Метод
pre_randomize() и post_randomize(), которые вызываются перед
и после основного. Функция
randomize() имеет два вспомогательных метода –
pre_randomize() может использо-
ваться для установки предварительных условий перед рандомиза-
– 63 –
цией объекта. Например, пользователи могут реализовать логику управления рандомизацией в функции
pre_randomize, т.е. вклю-
чение или отключение рандомизации с помощью метода
rand_mode(). Функция post_randomization может использоваться
для проверки и выполнения постусловий после рандомизации объ­екта. Например, можно переопределять рандомизированные значе­ния или напечатать значения переменных.
6.2. Ограничения
Как правило, случайные значения для полей классов требуется задавать не просто так, а в рамках заданных ограничений. Напри­мер, адрес не должен выходить за диапазон значений адресного пространства, а коды операций должны быть из списка разрешен­ных и т.п. Для задания подобного рода правил и используются ограничения – Constraints.
Ограничения на случайную величину должны быть записаны в блоках ограничений, и для них выполняются следующие свойства:
ограничения – это члены класса, такие же как функции и пе-
ременные;
ограничения должны иметь уникальное имя в классе;
ограничения состоят из условий или выражений для ограни-
чения или управления значениями для случайной величины;
ограничения могут быть определены внутри класса или вне
класса, как методы extern. Ограничения, определенные вне класса, называется внешними ограничениями.
Усложним класс транзакций uart_item. Теперь в нем есть поля, которые определяются случайным образом. Предполагается, что каждая такая транзакция заставит драйвер передать или прочитать четыре байта данных data по заданному адресу addr. Операция определяется полем команды cmd, а скорость работы при пере­даче – полем speed.
typedef enum{ s9600; s19200; s38400; s57600; s115200; s230400; s460800; s921600} uart_item_speed_e;
class uart_item extends uvm_sequence_item;
rand int unsigned addr;
rand int unsigned data;
– 64 –
rand byte cmd;
rand uart_item_speed_e speed;
...
endclass: uart_item
Если теперь вызвать для данной транзакции метод
randomize(),
то все поля получат случайные и никак не связанные значения. Но предположим, что необходимо, чтобы команда cmd принимала зна­чения только 0 или 1. Для этого надо создать ограничение:
constraint cmd_с { cmd> 2;}
Добавление такого ограничения в класс позволит получать при рандомизации поле
cmd только 0 или 1.
Во время рандомизации может потребоваться рандомизировать переменную в пределах диапазона или списка значений, а возмож­но, за пределами диапазона. Для этого используется ключевое сло­во
inside. Пусть в нашем примере адрес получает свое значение
только в двух заданных интервалах. Следующее ограничение поз­волит получить адрес, равный 0, в интервале от 128 до 255 или от 1024 до 2047:
constraint addr_с{ addrinside{0,[128:255],[1024:2047]};}
Если необходимо, можно генерировать значения за исключени­ем заданного диапазона – тогда перед inside ставится отрицание:
!(inside { … })
По умолчанию все допустимые значения появляются равнове­роятно. Но, как правило, существуют некие критические значения, проверка которых должна выполняться с наибольшей вероятно­стью. Или же просто требуется задать распределение значений. Для этого существует ключевое слово
dist. Оно позволяет выполнять
взвешенное распределение. Каждому из возможных значений при­сваивается вес, который определяет, как часто это значение будет выполняться. Например, чтобы команда чтения
«0» выполнялась в
два раза чаще команды записи:
– 65 –
cmd_с2 {cmddist {0 2, 1 := 1};}
Переменные могут быть связаны условными отношениями при рандомизации. Для объявления условных отношений между двумя переменными может быть использован оператор импликации. Опе­ратор импликации обозначается символом ->. Оператор имплика­ции помещается между выражением и ограничением:
expression ->constraint
Например, нужно, чтобы по адресу 0 всегда записывалась за­данная константа:
constraint data_c {(addr == 0 and cmd == 1)-> data == 100500;}
SystemVerilog поддерживает использование цикла
foreach
внутри блока ограничений, которое упрощает ограничение масси­ва. Цикл Ограничение
foreach выполняет итерацию по элементам массива.
foreach будет применимо к массиву с одним или не-
сколькими элементами, поэтому необходимо указать или ограни­чить размер динамического массива:
rand byte addr[];
constraint asize{ addr.size < 4;}
constraint avalues {foreach( addr[i]) addr[i] inside{4,8,12,16};}
В данном случае массив addr получится длиной не более трех элементов со значениями 4, 8, 12, 16.
Ограничения в классе могут быть отключены с помощью вызова метода
constraint_mode() и во время рандомизации не будут учи-
тываться. По умолчанию все ограничения включены. Метод от­ключения ограничений аналогичен методу
rand_mode (). Блок
ограничения, по аналогии со свойствами класса, можно определить как статический, включив в его определение ключевое слово static. Изменение режима статического ограничения (включение или от­ключение) будет затронуто во всех экземплярах класса. Статиче­ский класс может быть включен или отключен любым объектом
– 66 –
своего класса, изменение режима с помощью одного объекта будет отражать все другие объекты того же типа класса.
В некоторых случаях ограничение не может быть выражено в одной строке. В таких случаях можно использовать функции для ограничения случайной величины.
Ограничения SystemVerilog решаются в двух направлениях, что означает, что ограничения для всех случайных величин будут ре­шаться параллельно.
Рассмотрим пример ограничения:
rand bit a;
rand bit[3:0] b;
constraint c_name {(a ==1)->b ==0;}
Мы видим, что «b» зависит от «a», но система видит, что «a» за­висит от «b», а «b» зависит от «a», т.е. если «b» встроено в ограни­чение как «0», то для удовлетворения «a» должно принимать зна­чение «1». Это может приводить к снижению вероятности появле­ния отдельных наборов – в данном примере «a» может принимать два значения, «b» – 16 значений.
Поскольку значение «b» находится в диапазоне [0:15], вероят­ность получения b == 0 мала. Это повлияет на значение «a»: зна­чение b! = 0 приведет к тому, что значение «a» станет «0».
– 67 –
Лекция 7. UVM тест и виртуальные последовательности
Введение
В лекции рассматривается верхние уровни иерархии тестового окружения: UVM табло, которое отвечает за сбор статистики, ана­лиз покрытия и проверку результатов; UVM окружение, которое объединяет в себе все агенты, и UVM тест, выполняющий проверку заданных пунктов тестового плана. Также в лекции описаны меха­низмы работы виртуальных последовательностей, обеспечивающих взаимодействие нескольких агентов.
7.1. UVM окружение
7.1.1. Табло
Агенты привязываются к отдельным интерфейсам тестируемого устройства. Если оно имеет более одного интерфейса, то необхо­димо подключение нескольких агентов. Объединение агентов и их настройка происходит в окружении. Также в окружение должен входить элемент для проверки результатов работы агентов – табло scoreboard.
Табло является важнейшим элементом самоконтроля тестового окружения. Как правило, табло проверяет правильность работы окружения на функциональном уровне. Уровень проверки зависит от реализации и может варьироваться.
В общем случае табло получает два потока данных – от монито­ра на входе и на выходе из устройства. Данные со входа подаются на эталонную модель устройства, которая формирует ожидаемый ответ. Этот ответ в дальнейшем сравнивается с фактическим.
class uart_scoreboard extends uvm_scoreboard;
`uvm_component_utils(uart_scoreboard)
uvm_analysis_export #(uart_item) sb_export_before;
uvm_analysis_export #(uart_item) sb_export_after;
– 68 –
uvm_tlm_analysis_fifo #(uart_itemion) before_fifo;
uvm_tlm_analysis_fifo #(uart_item) after_fifo;
uart_item transaction_before;
uart_item transaction_after;
function new(string name, uvm_component parent);
super.new(name, parent);
transaction_before = new("transaction_before");
transaction_after = new("transaction_after");
endfunction:new
function void build_phase(uvm_phase phase);
super.build_phase(phase);
sb_export_before = new ("sb_export_before",this);
sb_export_after = new ("sb_export_after",this);
before_fifo = new("before_fifo",this);
after_fifo = new("after_fifo",this);
endfunction: build_phase
function void connect_phase(uvm_phase phase);
sb_export_before.connect(before_fifo.analysis_export);
sb_export_after.connect(after_fifo.analysis_export);
endfunction: connect_phase
task run();
forever begin
before_fifo.get(transaction_before); after_fifo.get(transaction_after); compare();
end
endtask: run
virtual function void compare();
...
endfunction: compare endclass: uart_scoreboard
Табло наследуется от класса uvm_scoreboard. Поскольку к нему подключаются мониторы, необходимо иметь соответствующие TLM порты. Как говорилось ранее, мониторы используют
uvm_analysis_port, соответственно, у табло должен присутство-
вать uvm_analysis_export. Поскольку транзакции в общем случае поступают от входного и выходного мониторов не одновременно,
– 69 –
то необходимо предусмотреть буферы для их сбора на входе в таб­ло – два uvm_tlm_analysis_fifo.
В дальнейшем устройство табло достаточно простое. В кон­структоре создаются необходимые переменные для транзакций, буферы FIFO и порты создаются в фазе build. В фазе connect про­исходит подключение входных портов к нечном цикле вычитывает транзакции из двух
fifo. Фаза run в беско-
fifo и вызывает
функцию compare, которая должна проверить, совпадает ли резуль­тат с ожидаемым. По результатам сравнения табло может вывести сообщение об ошибке, увеличить счетчики успешных или не­успешных транзакций, сделать что-то еще.
7.1.2. Окружение
Окружение UVM – это достаточно простой класс, наследуемый от класса
uvm_env. Его задача – подключение агентов к табло и их
настройка.
class uart_env extends uvm_env;
`uvm_component_utils(uart_env)
uart_agent u_agent;
bus_agent b_agent;
uart_scoreboard u_sb;
function new(string name, uvm_component parent); super.new(name, parent); endfunction:new
function void build_phase(uvm_phase phase); super.build_phase(phase);
u_agent = uart_agent::type_id::create("u_agent",this);
b_agent = uart_agent::type_id::create("b_agent",this);
u_sb = uart_scoreboard::type_id::create("u_sb",this);
b_agent.is_active =UVM_PASSIVE endfunction: build_phase
function void connect_phase(uvm_phase phase);
– 70 –