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