Классификация моделей:
- Поведенческая модель
- Функциональная модель
- Структурные модели
- Модель производительности
- Модель интерфейса
6) Типы перечисления (
Enumerated types): упрощение записи и анализа кода;
Динамические массивы декларируются без указания их длинны с пустыми скобками []. Для
выделения памяти под массив используется оператор new[]. Если указать дополнительное имя в
операторе new[], то значение будет копироваться в новый элемент (листинг 16.13.).
8. Поведенческие модели в Verilog
Неблокирующее присваивание
Блокирующее присваивание
x = x + 1;
y = y + 1;
end
x <= x + 1;
y <= y + 1;
end
x = x + 1;
y = x;
end
Здесь
x увеличится на 1, а y примет значение x + 1. Чтобы записать это выражение
неблокирующими присваиваниями, потребовалась бы такая запись:
x <= x + 1;
y <= x + 1;
end
или:
y = input_value >> 4;
y = y + center_offset;
y = 3 * y;
Эти две записи эквивалентны. Но вторую запись нельзя понимать как
последовательную цепочку вычислений. Это верно лишь в том смысле, что всё
выражение действительно выстраивается в схему, в которой сначала отрезаются
4 младших разряда, результат и второй операнд идут на вход сумматора, а выход
сумматора отдается умножителю на три. Так это представляется в электрической
схеме и человек для удобства нарисует эту схему слева направо и читать он ее
будет последовательно. Но в получившейся схеме все это выражение выполняется
непрерывно, так же как и в предыдущей записи с неблокирующим
присваиванием. Запись результата в регистр, как и следовало ожидать,
происходит по фронту тактового импульса.
11. Операторы assign/deassign и force/release
12. Реализация подпрограмм в Verilog. Задачи и функции.
(
https://docs.google.com/document/d/14w8UdSupoIr_m8x6Ax5t1gwHzcOoDjW22T3tvh
B_GYE/edit) лк, там примеры, синтаксис
Как и в любом другом языке программирования подпрограммы позволяют
разбивать большой код на отдельные фрагменты. Verilog располагает двумя
типами подпрограмм: задачи (task) и функции (function). Основная цель функции
– сформировать значение, которое может быть использовано в выражении. Она
всегда возвращает одно значение и должна иметь хотя бы один входной аргумент.
В задаче допускается генерирование значений нескольких выходных переменных
или вообще ни одного, она может не иметь и входных аргументов. Задачи
допускают применение задержек, временного и событийного управления,
функции – нет, поэтому в функциях не должно быть операторов, начинающихся с
символов #, @ или wait. Для декларации аргументов в подпрограммах
используются такие же ключевые слова, как и для описания портов модулей.
Параметр identifier описывает имя функции или
задачи. Для функции – это также имя
возвращаемого значения, для которого
параметром range_of_type задается тип и размер.
Если последний пропущен, то по умолчанию
функция возвращает однобитовое значение
типа reg. Можно также присвоить возвращаемому
значению тип: integer, real, realtime или time. Аргументы
задач, объявленные как inout и out, могут
использоваться только в процедурных
операторах и должны быть: переменной
регистрового класса (reg, integer, real, realtime или time),
памятью, конкатенацией регистров
переменных памяти, элементом или
диапазоном регистрового вектора.
Входые аргументы функции могут быть
описаны одним из двух способов. В первом
случае после имени функции ставится точка с
запятой. После этого следует описание
одного или нескольких входных портов
(старый стиль). Во втором случае описание
входов записывается в круглых скобках,
через запятую после имени функции (новый
стиль). Пример определяет функцию getbyte, с
использованием первого (листинг 6.1 а)) или
второго (листинг 6.1 б)) способа описания
входных параметров. Аналогичным образом
определяются входные параметры и для зада,
что иллюстрируется листингом на примере
задачи my_task, которая имеет пять аргументов:
два входных, два выходных и один
двунаправленный.
module Blocking_ass
( input clk,
reg [2:0] a;
reg [2:0] b;
reg [2:0] c;
reg [2:0] d;
always@(posedge clk)
begin
end
endmodule
1)Распределенная задержка
2)Сосредоточенная задержка
3)Задержка пути
Динамические массивы
Динамические массивы декларируются без указания их длинны с пустыми скобками []. Для выделения
памяти под массив используется оператор new[]. Если указать дополнительное имя в операторе new[], то
значение будет копироваться в новый элемент (листинг 16.13.).
Листинг 16.13. Использование динамических массивов
int dyn[], d2[]; // Пустой динамический массив
initial begin
dyn = new[5]; // Выделение памяти для 5 элементов
foreach (dyn[j])
dyn[j] = j; // Инициализация элементов
d2 = dyn; // Копирование динамического массива
d2[0] = 5; // Изменение копии
$display(dyn[0],d2[0]); // Вывод обоих значений (0 и 5)
dyn = new[20](dyn); // Увеличение размера и копирование
dyn = new[100]; // Выделение памяти для 100 новых
// элементов. Старые значения при этом будут потеряны
dyn.delete; // Удаление всех элементов
end
Можно присваивать между собой значения массивов с динамической и фиксированной длиной, если они
имеют одинаковый тип данных int и одинаковую длину. Если присвоить массив с фиксированной
длиной динамическому массиву, то компилятор вызовет конструктор new[], чтобы выделить память под
массив, а затем скопирует в нее новые значения.
Очереди
Новый тип данных. Упрощает процесс поиска и сортировки в структурах. Такой же быстрый, как и
массивы с фиксированной длиной; многообразен как связанный список. Подобно динамическим
массивам очереди могут увеличиваться и уменьшаться в размере во время моделирования, также можно
легко добавлять и удалять любые элементы, как это показано в следующем примере. Декларируется как
массив, но с использованием символа доллара $ (листинг 2.16). Размер массива может быть указан, но
это необязательно.
Очередь может сохранять данные типа данных, которые он получил в момент декларации очереди
int q1 [$]; //пустая очередь, без указания размера
int q2 [$] = {1,2,3,5,8}; // безразмерная очередь,
// инициализируется пятью элементами
initial begin
q.insert(1, j); // {0,1,2,5}, помещает 1 перед 2
q.insert(3, b); // {0,1,2,3,4,5}, помещает значение b[] после 2
q.delete(1); // {0,2,3,4,5} , удаляет элемент с индексом 1
// Следующие операторы самые быстрые
q.push_front(6); // {6,0,2,3,4,5}, добавляет элемент в начало списка
j = q.pop_back; // {6,0,2,3,4} j = 5
q.push_back(8); // {6,0,2,3,4,8}, добавляет элемент в конец списка
j = q.pop_front; // {0,2,3,4,8} j = 6
foreach (q[i])
$display(q[i]);
end
2)
delete(value)— Метод удаляет элемент из описанной индексной позиции.
3)
push_front(<value>)— Добавляет значение в новую позицию в начале очереди.
4)
push_back(<value>)— Добавляет значение в новую позицию в конце очереди.
5)
variable = pop_front()— Удаляет первый элемент из очереди и возвращает в его значение.
6)
variable = pop_back()— Удаляет последний элемент из очереди и возвращает в его значение.
7)
insert(<index>,<value>) — Изменяет значение элемента в указанной позиции без изменения размера
очереди.
8)
variable = <queue_name>[<index>] — Возвращает значение элемента из указанной позиции без изменения
размера очереди.
9)
variable = size()— Возвращает текущее количество элементов в очереди.
Запись в заполненную очередь и чтение из пустой очереди приведет к ошибке времени выполнения.
2.5.2 Параметризируемая память FIFO
Этот пример (листинг 2.17.) иллюстрирует использование очередей и их методов в моделях,
разработанных с помощью SystemVerilog.
Пример представляет собой простейшую память FIFO, которая создается с применением очередей в
SystemVerilog. Размер и разрядность данных FIFO контролируется параметрами. Помещение в и
удаление данных из памяти FIFO, точно также как и проверка элементов в FIFO реализуется с помощью
методов очередей.
Очередь также моделируется с помощью SystemVerilog типов с двумя состояниями int и bit. Где
содержание шины и разрешение цепи не важны, типы с двумя состояниями улучшат
производительность.
В среде верификации очереди могут использоваться для сбора информационных данных.
Листинг 2.17. Модель FIFO с использованием очередей
`timescale 1ns / 1ns
где
data_type – тип данных элементов массива, может быть любой тип, разрешенный для массивов
фиксированной длины; array_id – имя декларируемого массива; index_type – тип данных, используемый
для индекса или *. Символ * - означает, что массив может индексироваться любым целым выражением.
При указании типа индекса, используемое выражение для индексации должно быть только указанного
типа.
Листинг 2.18. представляет декларирование, инициализацию и использования ассоциативных массивов.
Их декларация выполняется с помощью группового символа [*].
Листинг 2.18. Декларирование, инициализация и использование ассоциативных массивов
initial begin
logic [63:0] assoc[*], idx = 1;
// Инициализация разрозненными значениями
repeat (64) begin
assoc[idx] = idx;
idx = idx << 1;
end
// Перебор всех индексов с помощью foreach
foreach (assoc[i])
$display("assoc[%h] = %h", i, assoc[i]);
// Перебор всех индексов с помощью функция
if (assoc.first(idx))
begin // Получение первого индекса
do
$display("assoc[%h]=%h", idx, assoc[idx]);
while (assoc.next(idx)); // Получение следующего индекса
end
// Поиск и удаление первого элемента
assoc.first(idx);
assoc.delete(idx);
end
Ассоциативный массив assoc состоит из рассеянных элементов 1, 2, 4, 8, 16, и т.д. Для перебора
элементов не удобно использовать оператор for, для этой цели лучше подходит операция foreach. Для
более точного контроля можно также использовать функции first и next в цикле do...while. Эти функции
принимают значение индекса, в качестве аргумента и возвращают 0 или 1, в зависимости от наличия
элементов в массиве. Ассоциативные массивы могут быть также адресованы с помощью строкового
индекса. В примере (листинг 2.19) выполняется чтение пар имен из файла в ассоциативный массив. Если
попытаться прочитать элемент, который еще не существует, то SystemVerilog вернет 0 или X для типов
для типов с 2-мя или 4-мя значениями, соответственно. Функция exists может быть использования для
проверки существования элемента.
Листинг 2.19. Использование ассоциативного массива с индексом string
// Входной файл содержит: 42 min_address 42 max_address 1492
int switch[string], min_address, max_address;
initial begin
int i, r, file;
string s;
file = $fopen("switch.txt", "r");
while (! $feof(file)) begin
r = $fscanf(file, "%d %s", i, s);
switch[s] = i;
end
$fclose(file);
// Получение минимального адреса, по умолчанию 0
mid_address = switch["min_address"];
// Получение максимального адреса, по умолчанию 1000
if (switch.exists("max_address"))
max_address = switch["max_address"];
else
max_address = 1000;
end
Ассоциативный массив может быть сохранен симулятором в виде дерева. Кроме того, допускается
использовать дополнительные служебные сигналы (overhead), когда необходимо сохранить массив с
далеко стоящими значениями индексов, например для пакетов, адресуемых 32-разрядным или
64-разрядным значениями данных.
2.6.1 Методы ассоциативных массивов
Определено несколько методов для анализа и манипулирования элементами ассоциативных массивов.
1) Функция ()
num
function int num();
Метод
num() возвращает число элементов в ассоциативном массиве. Возвращает 0, если массив пустой.
int item[*];
item[ 2’b3 ] = 1;
item[ 16’hffff ] = 2;
item[ 4b’1000 ] = 3;
$display( "%0d entries\n", imem.num ); // prints "3 entries"
2) Функция ()
delete
function void delete( [input index] );
3) Функция exists()
function int exists( input index );
4) Функция ()
first
function int first( ref index );
5) Функция
last()
function int last( ref index );
6) Функция
next()
function int next( ref index );
7) Функция
prev()
function int prev( ref index );
Если аргумент, передаваемый в любой из четырех методов перемещения для ассоциативных массивов,
меньше чем размер соответствующего индекса, то функция возвращает значение -1. Поэтому
необходимо копировать столько данных, сколько может подойти аргументу.
Например:
string aa[*];
byte ix;
int status;
aa[1000] = "a";
status = aa.first(ix);
// status is –1
// ix is 232 (последние 8 значащих битов от 1000)
Эти три блока имеют бесконечную природу моделирования, как и оператор always. Однако они
улучшают стиль описания аппаратуры и предназначены для разработки синтезируемых моделей
RTL-уровня.
Программным средствам нет необходимости определять тип аппаратуры из контекста описания
операторов. Если стиль записи операторов не удовлетворяет характеру блока, то система разработки
генерирует предупреждение.
3.1.1. Комбинационный процедурный блок.
В отличие от обычного блока always, блок always_comb не требует указания специального списка
чувствительности. Он создается автоматически и в него включаются все переменные, значение которых
читается в процедурном блоке, за исключением тех, что объявлены в нем. В следующем примере
оператор always_combбудет выполняться при каждом изменении переменных a или b.
always_comb
if (!mode)
y = a + b;
else
y = a - b;
Листинг 3.1 представляет пример использования always_comb для описания комбинационных фрагментов
схемы контроллера.
Листинг 3.1. Модель контроллера с использованием always_comb
module controller (output logic read, write,
input instr_t instruction,
input logic clock, resetN);
always_comb begin
case (State)
WAITE: NextState = LOAD;
LOAD: NextState = STORE;
STORE: NextState = WAITE;
endcase
end
always_comb begin
read = 0; write = 0;
if (State == LOAD && instruction == FETCH) read = 1;
else if (State == STORE && instruction == WRITE) write = 1;
end
endmodule
Существует различие в моделировании между always_comb и always. Первый выполняется один раз в
нулевой момент моделирования, после активации всех процедурных блоков.
Verilog-2001 предлагает использовать в списке чувствительности блока always групповой символ @* или
@(*). Однако это только более короткая запись и не дает таких преимуществ как always_comb. (листинг
3.2).
Листинг 3.2. Различие между always_combи always.
always @* begin // Обозначает @(data)
a1 = data << 1;
b1 = decode ();
...
end
Пример с листинга 3.3 использует always_latch. 5-битовый счетчик, выполняющий счет от 0 до 31. Вход
ready контролирует начало выполнения счета. Вход ready имеет значение 1 короткий период времени.
Поэтому, когда ready переключается в 1, модель сохраняет это значение для внутреннего сигнала enable.
Защелка сохраняет значение 1, пока счетчик не достигнет значения 31, и затем выполняется очищение
сигнала enable, не давая счетчику выполняться снова, до следующего поступления сигнала ready.
Листинг 3.3. Использование процедурного блока always_latch
module register_reader (input clk, ready, resetN,
output logic [4:0] read_pointer);
logic enable; // внутренний сигнал, разрешающий счет
logic overflow; // внутренний флаг переполнения счетчика
Все сигналы в списке чувствительности должны быть записаны с указанием фронта posedge или negedge.
Событийный контроль внутри блока не допускается.
Блоки , always_latchи always_ffявляются синтезируемыми.
always_comb
32. Структурные модели: соединение портов. Псевдонимы цепей.
Соединение портов
В Verilog использовались соединение сигналов по позиции или по имени. SystemVerilog предлагает три
упрощенные формы описания связей портов:
1)
.name(“dot-name”) соединение портов;
2)
.* (“dot-star”) соединение портов;
3) С помощью интерфейсов.
Выполняет соединение портов с совпадающими по имени сигналами, упрощая выражение Verilog
.data(data) (листинг 4.4 а)) до
.data в SystemVerilog (листинг 4.4 б)). Соединение
.* (листинг 4.4
в))связывает все порты с соответствующими по имени сигналами.
Листинг 4.4 Примеры соединений портов
а) //Verilog стиль
// Копия модуля с именным соединением портов
pc_stack pcs (
.program_counter(program_counter),
.program_address(program_address),
.clk(clk),
.resetN(resetN),
.instruct_reg(instruct_reg),
.data_bus(data_bus),
.status_reg(status_reg));
prom prom (.dout(program_data),
.clk(clk), .address(program_address));
все имена указывают на одну линию. Не важно, в каком порядке выполняется связь имен, поскольку
оператор
alias не является оператором присваивания. Псевдонимы могут быть использованы только для
цепей и должны быть одного типа.
wire [3:0][7:0] n2;
alias n2 = n1; // n1 и n2 имеют размер 32-бита
wire [39:0] d_in;
wire [7:0] crc;
wire [31:0] data;
alias data = d_in[31:0]; // Цепь размером в 32 бита
alias crc = d_in[39:32]; // Цепь размером в 8 бита
Использование псевдонимов с конструкциями .name и .* позволяет значительно упростить запись связей
между портами в иерархических проектах.
Листинг 4.5 представляет структурную SystemVerilog-модель устройства с рис. 4.3 использованием .*
связи портов без использования псевдонимов. Этот же пример с применением псевдонимов приведен в
листинге 4.6.
ROM i1 ( .* );
program_count i2 ( .*);
address_reg i3 ( .* );
endmodule
33. Интерфейсы.Modport.
Группирование сигналов с помощью modport
В предыдущих примерах применялось соединения без указания направления сигнала в интерфейсе.
Однако один и тот же сигнал в различных модулях может играть различную роль. Например, в одном
случае он будет входом, а в другом - выходом. Тогда такой порт внутри интерфейса объявляется через
modport.
Конструкции modport не содержат тип данных или разрядность порта. Эта информация указывается при
декларации сигналов интерфейса. В modport описывается только направление портов: input, output, inout
или ref.
SystemVerilog предлагает два способа указать применяемый modport: в операторе реализации копии
компонента и в декларации портов модуля при его определении. Оба стиля спецификации являются
синтезируемыми.
Первый стиль, в операторе реализации копии модуля копия интерфейса соединяется с портом модуля,
при этом следующим образом может быть указан определенный modport:
<имя копии модуля>.<имя modport>
Например:
chip_bus bus; // Копия интерфейса
primary i1 (bus.master); // Использования резима master
Пример (листинг 5.8) иллюстрирует соединение двух модулей вместе с интерфейсом chip_bus. Модуль
primary соединяется с интерфейсом в режиме master, а модуль secondary – в режиме slave:
Листинг 5.8: Выбор режима modport в момент создания копии модуля
chip_bus (
interface input logic clock, resetN);
modport master (...);
modport slave (...);
endinterface
primary (
module pins);
interface // Абстрактный порт интерфейса
...
endmodule
Если
modportуказывается в декларации интерфейса модуля, то применяется синтаксис:
<имя интерфейса>.<имя modport>
В этом случае должно быть указано явное имя интерфейса. Тогда в операторе реализации копии модуля
приводится только имя копии интерфейса без описания режима modport. Пример (листинг 5.10:)
представляет запись режима modportв декларации портов модуля.
Листинг 5.10. Выбор режима modport в момент описания портов модуля
interface chip_bus (input logic clock, resetN);
modport master (...);
modport slave (...);
endinterface
Таким образом, выбрать modport можно либо для копии модуля, либо при описании его портов, но
никогда одновременно (листинг 5.11). В один момент допускается указывать тип modport только в одной
из этих двух конструкций. Если направление портов не описаны, то по умолчанию все порты
интерфейса имеют режим inout и тип ref. Кроме этого в интерфейсе можно объявлять внутренние
сигналы, являющиеся локальными для него.
Листинг 5.11. Использование интерфейса с конструкциями modport
// ------------------- Определение интерфейса -----------------
interface main_bus (input logic clock, resetN, test_mode);
wire [15:0] data;
wire [15:0] address;
logic [ 7:0] slave_instruction;
logic slave_request, bus_grant, bus_request;
logic slave_ready, data_ready;
logic mem_read, mem_write;
modport master (inout data,
output address,
output slave_instruction, slave_request,
output bus_grant,
output mem_read, mem_write,
input bus_request,
input slave_ready, data_ready,
input clock, resetN,
input test_mode
);
modport slave (inout data,
inout address,
output mem_read, mem_write,
output bus_request, slave_ready,
input slave_instruction, slave_request,
input bus_grant,
input data_ready,
input clock, resetN
);
modport mem (inout data,
output data_ready,
input address,
input mem_read, mem_write
);
endinterface
//---------------------- Модуль верхнего уровня --------------------
module top (input logic clock, resetN, test_mode);
logic [15:0] program_address, jump_address;
logic [ 7:0] instruction, next_instruction, data_b;
main_bus bus ( .* ); // Копия интерфейса
processor proc1 (.bus(bus.master), .* );
slave slave1 (.bus(bus.slave), .* );
instruction_reg ir ( .* );
test_generator test_gen (.bus(bus), .* );
dual_port_ram ram (.bus(bus.mem), .* ,
.data_b(next_instruction) );
endmodule
Системная спецификация
Тут ссылка на лекцию, там намного конкретнее по каждому пункту. (оставил это
на выбор отвечающего)
а) // D-триггер
б) // С асинхронным сбросом
в) // С синхронной установкой
г) // С сигналом разрешения синхронизации
а) б)
в) г)
Рис. 11.15. Синтез моделей D-триггера с синхронизацией по фронту
Packet p;
initial begin
p = new; // Создание пакета
assert(p.randomize());
transmit(p); // Какая-то пользовательская функция, которая передает
значения дальше
end
Управляющие генерацией константы записываются в конструкцию constraint и
группируется с помощью фигурных скобок { }. Такие скобки используются,
потому что в них заключается декларативный код, а не процедурный, где
необходимо применять begin...end. Если в процессе генерации значения
возникнет ошибка, то функция randomize возвращает 0, что отслеживается с
помощью прямой ассерции.
39. Создание и использование ограничений для
псевдослучайного генерирования значений. Условные
ограничения, выбор значений из массива , приоритеты
значений.(приоритеты не найдены!)
Класс Stim (Листинг 13.2) содержит класс с псевдослучайной генерацией
значений и несколькими блоками констант. Первый блок c_stim задает
диапазон возможных значения для len. Множество значений может быть
создано также с помощью оператора inside, как для переменной dst. При этом
задается условие, что данное ограничение будет действовать, если бит
congestion_test равен 1.
SystemVerilog создает множество возможных значений и выбирает из
него значения с равной вероятностью, если нет других условий ограничения
переменной.
class Stim;
const bit [31:0] SRC_ADDR = 42;
typedef enum {READ, WRITE, CONTROL} stim_t;
randc stim_t type; // Переменная типа перечисления
rand bit [31:0] len, src, dst;
bit congestion_test;
constraint c_stim {
len < 1000;
len > 0;
src inside {0, [2:10], [100:107]};
if (congestion_test) {
dst inside {[SRC_ADDR - 100 : SRC_ADDR + 100]};
}
}
endclass
______________________________
Days days;
initial begin
days = new;
days.choices = ‘{Days::SUN, Days::SAT};
assert (days.randomize());
$display("Random weekend day %s\n", days.choice.name);
days.choices = ‘{Days::MON, Days::TUE, Days::WED, Days::THU,
Days::FRI};
assert (days.randomize());
$display("Random week day %s", days.choice.name);
end
Packet p;
initial begin
p = new;
// Создание длинных данных long, отключаа ограниничение
для коротких short
p.c_short.constraint_mode(0);
assert (p.randomize());
transmit(p);
// Создание коротких данных, через отключение всех
ограничени
// а затем разрешается использования ограничений для
коротких данных
p.constraint_mode(0);
p.c_short.constraint_mode(1);
assert (p.randomize());
transmit(p);
end
проекта.
· основываются
на спецификации проекта и поэтому не зависят от реального
кода модели или структуры.
Конструкции функционального покрытия в SystemVerilog осуществляют
следующие действия:
· Сбор
информации о покрытии переменных и выражений, а также о
перекрестном покрытии между ними.
· Автоматическое
или пользовательское создание корзин покрытия
· Фильтрацию
условий на нескольких уровнях
· Использование
событий и последовательностей для автоматического
переключения сбора значений (sampling) покрытия
· Покрытие
вызовов процедур и очередей.
· Управление
и регулирование формирования покрытия с помощью
директивы
coverage_spec_or_option ::=
{attribute_instance} coverage_spec | {attribute_instance} coverage_option ;
coverage_option ::=
member_identifier = expression | type_option.member_identifier = expression
option.
coverage_spec ::=
cover_point | cover_cross
coverage_event ::=
clocking_event |
@@( block_event_expression )
block_event_expression ::=
block_event_expression or block_event_expression | begin hierarchical_btf_identifier
| hierarchical_btf_identifier
end
Синтаксис реализации объекта covergroup:
covergroup_variable_identifier = new [ ( list_of_arguments ) ]
(сори за некст ответ, но я хз что тут может быть важно , а что нет)
Если указано событие синхронизации, то оно определяет моменты времени для сбора
значений с точек покрытия. В противном случае, пользователь должен задать
синхронизацию с помощью процедурных операторов. Это можно выполнить через
встроенный метод sample(). Опция strobe может быть использована для изменения
моментов считывания данных. Когда опция strobe не установлена, значения точек
покрытия считываются по событию экземпляра модели покрытия. Если событие
синхронизации встречается несколько раз за время моделирования, то значения точек
покрытия должны быть также прочитаны несколько раз.
В качестве альтернативы для синхронизации может быть использовано выражение
блокового события, считывание контрольных точек покрытия может быть выполнено в
начале или конце работы заданного именного блока, задачи, функции или метода
класса. Если выражение блокового события начинается с ключевого слова begin, за
которым следует иерархический идентификатор, обозначающий именной блок, задачу,
функцию или метод класса, то событие переключаются сразу до выполнения первого
оператора указанных конструкций. Выражение блокового события, которое описано с
помощью ключевого слова end, выполняется сразу после последнего оператора
указанной конструкции.
Группа покрытия covergroup может содержать одну или несколько точек покрытия,
которые задаются переменной или выражением. С каждой точкой покрытия
связывается множество корзин определяемых значениями, которые записываются в
корзины, или передаваемыми значениями. Корзины могут быть явно определены
пользователем или автоматически созданные с помощью инструментов моделирования.
{ red, green, blue } color;
enum
covergroup g1 @(posedge clk);
c:
coverpoint color;
endgroup
В примере создается группа покрытия g2, которая содержит две точки покрытия и две
декларации пересечения покрытий cross. Явно заданные точки покрытия с метками
Offset и Hue определены для переменных pixel_offset и pixel_hue. Далее для
переменных color и pixel_adr задается анализ совместного покрытия.
В группе покрытия могут присутствовать одна или несколько опций, контролирующих
и регламентирующих структуру и способ сбора информации о покрытии. Опции
покрытия могут быть заданы для группы покрытия в целом или для отдельных ее
пунктов.
covergroup
cov1 @m_z; // встроенная covergroup
coverpoint m_x;
coverpoint m_y;
endgroup
function new
();
cov1 = new;
endfunction
endclass
В примере значения полей m_x and m_y класса xyz собираются при каждом изменении
переменной m_z. Если группа покрытия определяется внутри класса и нет явной
декларации переменной данной группы в пределах класса, то она будет иметь такое же
имя, которое группа покрытия получила при описании. Таким образом, в предыдущем
примере переменная cov1 соответствует группе покрытия.
Встроенная в класс группа покрытия может определять модель покрытия для
защищенных и локальных свойств класса без возможности ее переопределения в классе
наследнике. Класс может иметь несколько групп покрытия. Следующий пример
представляет класс MC, содержащий две группы покрытия.
C;
class M
logic
[3:0] m_x;
local logic
m_z;
bit
m_e;
covergroup
cv1
posedge clk);
@(
coverpoint m_x;
endgroup
covergroup
cv2
@m_e ;
coverpoint m_z;
endgroup
endclass
Перекрестное покрытие
Конструкция cross в SystemVerilog позволяет комбинировать значения двух или более точек покрытия в
группу. Оператор cross допускает только точки покрытия или простые имена переменных. Если
необходимо использовать выражения, иерархические имена или переменные, следует описать для них
метку с помощью coverpoint, а затем уже использовать эту метку в операторе cross.
В примере 17.28 создаются точки покрытия для tr.kind и tr.point. Затем эти точки используются для
создания условий перекрестного покрытия для всех возможных комбинаций. Для точки покрытия kind
SystemVerilog создаст 16 корзин (auto[0]...auto[15]), для точки покрытия port – 8 корзин(auto[0]...auto[7]).
Тогда для перекрестного покрытия этих двух точек будет создано 128(8х16) корзин (<auto[0], auto [0]>,
<auto[0], auto
[1]>, …).
class Transaction;
endclass
Transaction tr;
covergroup CovPort;
покрытия kind
port: coverpoint tr.port // Создания точки покрытия
port
endgroup
Случайный testbench создает 200 транзакций и формирует отчет о покрытии. Обратите внимание, даже
если были генерируются все возможные значения для kind и port, около 1/8 части всех возможных
комбинаций не было достигнуто.
«Точка покрытия» определяет интегральное выражение, которое требуется покрыть. Оценка выражения
точки покрытия происходит, когда выбирается «группа покрытия».
45. Свойства(Property)
Свойство определяет поведение проекта. Свойство может быть использовано для верификации, как
спецификация предположения, проверка или покрытие. Для того чтобы использовать поведение для
верификации, используются операторы assert, assume, или cover. Декларация свойства само по себе не
производит результат.
Результат вычисления свойств либо «истина», либо «ложь». Существует семь видов свойств:
последовательность (sequence), отрицание (negation), дизъюнкция(disjunction), конъюнкция (conjunction),
if...else, импликация (implication) и создание копии (instantiation).
not property_expr
Ключевое слово not обозначает, что при вычислении свойства возвращается противоположное значение
для property_expr. Таким образом, если property_expr примет значение «истина», то not property_expr
соответствует значению «ложь», и если property_expr оценивается как «ложь», то not property_expr будет
«истина».
property_expr1 or property_expr2
Свойство соответствует значению «истина» если и только если как минимум одно из свойств
property_expr1 и property_expr2 «истина».
if (expression_or_dist) property_expr1
или
Свойство в первом случае оценивается как «истина», если и только если либо expression_or_dist
принимает значение «ложь», либо property_expr1 – «истина». Во втором случае свойство возвращает
значение истина, когда либо expression_or_dist и property_expr1 возвращают значение «истина», либо
expression_or_dist вычисляется в «ложь», а property_expr2 – «истина».
или
Прямые ассерции используются в процедурном коде в любом месте в конструкциях initial и always.
Выражение вычисляется сразу, при выполнении оператора. Операторы в ветвях pass и fail, если они
присутствуют, выполняются мгновенно после вычисления выражения.
Процедурный код testbench может проверять значения сигналов проекта, переменных и выполнять
необходимые действия, в случае обнаружения ошибки. Например, в проекте выполняется проверка
запроса шины request и ожидается, что ответ будет получен двумя тактами позже. Тестирование этой
ситуации можно выполнить с помощью оператора if.
Процедурные асерции имеют ветви then- и else-, при необходимости, в них могут быть добавлены
операторы.
Другой тип ассерций – параллельные ассерции, выполняют проверку сигнала в течение всего процесса
моделирования. При описании таких ассерций используется синхронизация.
Во-первых, в дополнение к прямой записи в проекте, подобно операторам в блоках always или initial,
параллельные ассерции могут быть реализованы декларативно, на уровне непрерывных операторов
присвоения и реализации модулей (подобно непрерывному присвоению) за границами процедурных
блоков. Второе отличие заключается в том, что параллельные ассерции позволяют описывать для
проверки системы временное поведение, вместо комбинационных условий, как в прямых ассерциях
Синтаксис
[.modport_identifier] list_of_virtual_interface_decl ;
list_of_virtual_interface_decl::=
variable_identifier[=interface_instance_identifier]
{ , variable_identifier [ = interface_instance_identifier ] }
join. Родительский процесс блокируется, пока все порожденные им процессы процессы не завершат
завершат свое выполнение выполнение.
join_any. Родительский процесс блокируется, пока хотя бы один любой указанный в нем процесс не
завершит свое выполнение.
join_none. Родительский процесс продолжает свое выполнение параллельно с процессами
порожденными в fork. Порожденные процессы не начнут выполнение, пока родительский поток не
выполнит блокирующий оператор.
UVM
динамические.
Для реализации процесса генерирования тестовых данных в UVM определены следующие классы:
System UVCs - могут использовать интерфейсные, модульные и другие системные UVCs. Могут не
иметь своих собственных агентов и использовать другие UVCs, формируя на их основе среду
верификации большего размера для целых систем, например cpu, мосты (bridge), мобильные телефоны
Механизмы конфигурации
● Factory
● Config db(Configuration)
Базовый класс для UVM классов данных и иерархии, и содержит основные методы автоматизации
работы с классами библиотеки UVM. Его наследники uvm_sequence_item описывает транзакцию, а
uvm_sequence - последовательнось транзакций. Это так называемая динамическая составляющая среды
верификации. Все составляющие среду верификации компоненты построены на основе класса
uvm_component, который умеет управлять фазами моделирования. Это статические элементы среды
верификации. Конфигурация среды верификации осуществляется с помощью классов uvm_resource_db и
uvm_config_db. Для моделирования состояния регистров и памяти в проекте предназначены классы
uvm_reg, uvm_reg_block, uvm_reg_file, uvm_reg_field и uvm_mem.
55.TLM-интерфейсы в UVM
3) Надежность в использовании
4) Меньше кода
5) Легкость реализации
В то время как как DUT функционирует на уровне сигналов, то для задач верификации удобнее более
удобный уровень транзакций. На этом уровне думает и инженер, когда анализирует и описывает
функционирование системы.
TLM - моделирования уровня транзакций - это стиль моделирования для построения абстрактных
моделей компонентов и систем. TLM основан на транзакциях - объектах, которые содержат различные
относящиеся к протоколу данных для абстрактного представления процессов нижнего уровня. TLM
представляет семейство абстрактных уровней описания, начиная с циклического уровня моделирования
(cycle-accurate modeling). Наиболее известные уровни TLM: циклический(cycle-accurate), с
аппроксимированным временем(approximately-timed), loosely-timed, без времени и token-level.
TLM-1 и TLM-2.0 - два TLM-моделирования систем, которые были разработаны как промышленные
стандарты для построения моделей уровня транзакций. Оба были разработаны для SystemC и
стандартизованы с TLM Working Group от Open SystemC Initiative (OSCI). TLM-1 доведен до стандарта
в 2005, а TLM-2.0 в 2009. OSCI объединился с Accellera в 2013 и текущим стандартом SystemC является
IEEE 1666-2011.
TLM-1 и TLM-2 имеют общую базу, и часть разработчиков TLM-1, продолжили потом работать и над
TLM-2, однако они совершенно разные. TLM-1 - система для передачи сообщений. Интерфейсы или не
используют время, или основываются на подсказках для выбора времени. Ни один из интерфейсов не
использует точного временного описаний. Хотя TLM-2.0 также может передавать данные и
синхронизацию между независимыми процессами, он главным образом создавался для
высокопроизводительного моделирования memory-mapped bus-based систем. Обя эти подмножества
были реализованы в SV и доступны через UVM.
UVM Sequencer контролирует поток тестовых наборов (UVM Sequence Item),генерируемый одним или
несколькими последовательностями(UVM Sequence). Служит арбитром для контроля за потоком
транзакций от нескольких последовательностей.
Драйвер эмулирует логику, управляющую DUT, он получает от секвенсора отдельные транзакции (UVM
Sequence Item) и передает их на интерфейс DUT. Таким образом драйвер изменяет уровень абстракции,
преобразуя транзакции(transaction-level) во входные сигналы (pin-level). От включает TLM-порт, по
которому тестовые наборы от секвенсора поступают в драйвер, и подключается к интерфейсу DUT, для
управления сигналами на его входах.
Драйвер и секвенсор формируют трафик на входах DUT, но они не применяются для оценки
функционального покрытия и выполнения формальной верификации (чекеров и ассерций), эти задачи
делегируются мониторам.
Для целей верификации бывает удобно создавать одну общую структуру среды верификации, а потом
“на лету” заменять одни компоненты и данные на другие. Для этого в методологии верификации
адаптировали механизм фабрик(factory), существовавший до этого в программировании. В UVM
фабрики можно использовать только с классами, производными от uvm_object и uvm_component.
1) Регистрация
2) Создание
3) Переопределение
Регистрация
Тип класса регистрируется для factory в момент своего создания. Для этого используются
предопределенные скрипты (которые уже были рассмотрены ранее).
`uvm_component_utils(class_type_name)
`uvm_object_utils(class_type_name)
`uvm_component_param_utils(class_type_name #(params))
`uvm_object_param_utils(class_type_name #(params))
`uvm_object_utils(packet)
endclass
// параметризируемый класс
`uvm_object_param_utils(packet #(T,mode))
endclass
`uvm_component_utils(driver)
endclass
`uvm_component_param_utils(driver#(T,mode))
endclass
Создание
Для создания объекта или компонента в UVM рекомендуется использовать статический метод create()
вместо new().
Синтаксис :
Функция create() возвращает объект класса type_name. Объект переопределяется фабрикой, основываясь
на контексте определяемым полным именем родителя. Аргумент context, если он определен, заменяет
контекст родителя. Новый объект будет иметь имя листа и родителя. Классы, основанные на uvm_object,
не требуют указывать второй аргумент.
// 1
class_type object_name;
...
object_name = clss_type::type_id::create("object_name",this);
// 2
mytype data; // данные должны быть типа mytype или производного от него.
data = mytype::type_id::create("data");
...
endtask
Переопределение
Переопределять классы и объекты можно основывая на строки имени или типе класса. Существует 4
метода для переопределения. Первые два меняют тип данных для определенного объекта класса или
группы объектов, если строка full_inst_path содержит специальные символы. Различие между ними
заключается в форме задания типа класса, через ссылку (uvm_object_wrapper) или строку. Тип
uvm_object_wrapper - абстрактный интерфейс к созданным объектам. Вторая пара методов заменяет
один типа класса на другой.
1. Переопределение объекта по индексу типа:
Заменяет для объекта full_inst_path типа "original_type" на "override_type", где "override_type" является
наследником от "original_type".
Вместо этих функций могут быть использованы статические методы set_type_override и set_inst_override.
Первая функция заменяет тип класса глобально, для всех объектов в среде верификации; второй только
для заданного строкой объекта.
Пример:
ubus_master_driver::type_id::set_type_override(ubus_new_master_driver::get_type);