OVM/OVM методология/Transaction-Level моделирование
- OVM методология
- Введение
- Основы верификации
- Основы ООП
- Transaction-Level моделирование
- Механика OVM
- Основы Testbench
- Повторное использование Testbench (Reuse)
- Полный Testbench
Содержание |
Процесс проектирования электронной системы включает в себя последовательное замещение абстрактных идей конкретной их реализацией, пока не будет достигнуто представление, которое может быть изготовлено на кремнии. С момента появления цифровой интегральной схемы, электронные сообщества тщательно определили и кодировали абстракции, начиная с переключателей и логических элементов, для обеспечения среды в которой работает проект. RTL является примером среды, часто используемой для создания проектов. Есть много инструментов на основе RTL, которые делают ее удобной для создания проекта и его верификации. Однако, как только проект становится больше и сложнее, становится более представлять их с помощью абстракции выше, чем RTL. Уровень транзакций становится популярным для создания первого представления проекта, которое может быть смоделировано и проанализировано. В этой главе описаны основные понятия transaction-level моделирования (TLM). Модели transaction-level состоят из нескольких процессов, которые обмениваются информацией посредством транзакций.
Абстракция
RTL моделирование использует дискретную модель времени. Связь между процессами осуществляется с помощью сетки ( цепи), и процесс активации происходит при изменении значения во входной сети ( цепи). Для сравнения, transaction-level модели (моделирующей схемы) могут быть синхронизированы или не синхронизированы и использовать шину для обмена данными между процессами. Вместо того чтобы посылать отдельные биты туда и обратно, процессы общаются, посылая транзакции друг другу посредством вызова функций. Мир TLM включает в себя ряд моделей(моделирующих схем) вычислений с различным временем, системами связи и процессами активации моделей (моделирующей схемы). В каждом конкретном случае, содержание связей находится на более высоком уровне абстракции, чем отдельные биты. Таким образом, transaction-level модель находится на более высоком уровне абстракции (более абстрактна), чем RTL модель. Объединяя понятия абстракции и модели (моделирующей схемы) вычислений, видно, что создание абстрактных моделей означает абстрагирование времени, данных и функций.
Абстракция времени. Время абстракция в симуляторе относится к тому, насколько внутренние состояния схемы совместимы. Модели, запущенные в управляемых событиями симуляторах (например, логических симуляторах) используют дискретное представление времени, то есть события происходят в определенные моменты времени. События, как правило (хотя и не всегда) вызывают запущенный процесс. Чем больше событий происходит в симуляции, больше процессов вызывается, и с большим числом процессов происходит снижении скорости моделирования. Абстрагирование время уменьшает количество точек, где схема должна быть совместимой и общего числа событий и процессов, которые будут активированы. Например, в RTL модели, каждая сеть должна быть согласована после каждого изменения. В cycle-accurate абстракции, схема должна быть синхронизирована с синхросигналом, устраняя все события, что происходит между фронтами синхроимпульса. В transaction-level модели, состояния схемы должны быть синхронизированы в конце каждой операции, каждая из которых может охватывать много тактов.
Абстракция данных. Данные относятся к объектам, передаваемым между компонентами. В RTL моделях, данные относятся к отдельным битам, которые передаются по сети между компонентами. В transaction-level моделях, данных представлены в виде транзакций, гетерогенных структур, которые содержат произвольные коллекции элементов. Рассмотрим пакет в устройстве связи. На самом низком уровне детализации, пакет содержит бит начала и стоп-биты, заголовок, информацию исправления ошибок, размер полезной нагрузки, нагрузки, и завершитель (??????). В более абстрактной модели, только нагрузка и размер нагрузки могут быть необходимы. Другие данные не нужны для выполнения расчетов.
Абстракция функций. Функция модели является набором всех операций, которая она делать на каждое событие. Абстрагирование функций уменьшает этот набор или заменяет его более простыми вычислениями. Например, в ALU, вы можете выбрать использование родной операции умножении поставляемой в язык моделирования, вместо кодирования a shift-and-add алгоритма умножения. Последний может быть частью реализации, но на более высоком уровне, детали a shift-and-add - неважны. Базовые элементы, которые являются частью языка, определяет, как вы можете абстрагировать функцию. В gate-level языке, например, можно создавать сложные поведения, начиная с логических элементов. В RTL языка, построение поведения основывается на арифметических и логических операциях над регистрами. В TLM, вы реализуете функциональность схемы с помощью вызовов функций произвольной сложности.
Для целей функциональной верификации, RTL самый низкий уровень абстракции, который мы должны рассмотреть. С синтезаторы могут эффективно конвертировать RTL в логические элементы, нам не нужно затрагивать более низкий уровень детализации.
Определение транзакции
Для более подробного рассмотрения TLM, мы должны сделать шаг назад и определить понятие транзакции.
Это самое общее определение транзакции. Оно утверждает, что транзакция – это все, что происходит в проекте (или модуле или подсистеме проекта) между двумя точками времени. Хотя оно точное, оно является настолько общим, что не приводит к практическому применению. Более полезные определения следующие:
Транзакция — передача управления и данных между двумя элементами (компонентами)
Это аппаратно-ориентированные понятие транзакции. Когда смотришь на часть аппаратного обеспечения, вы можете легко определить элементы, между которыми передается управление или данные. В проекте с шинной архитектурой, чтение и запись на шину может быть вызвано транзакцией. В пакетной системе связи, отправка пакета представляет собой транзакцию.
Ниже третье определение:
Это определение программно-ориентированного понятия транзакции. В transaction-level модели, деятельность инициируется путем вызова функции. Вызов функции содержит параметры, которые "посылаются" (передаются) вызываемой функции, а возвращаемое значение функции содержит данные, которые возвращаются вызванной функцией. Вызванная функция может блокировать и вызвать задержку (в синхронной системе) или может вернуться (выполниться) немедленно.
Интерфейсы
Прежде чем вдаваться в подробности о том, как построить transaction-level модель, мы сначала обсудим интерфейсы. Термин интерфейс используется в несколько случаях в OVM, каждый раз с немного другим смыслом. Это прискорбный факт истории, то что слово стало означать различные вещи. В большинстве случаев значение будет понятно из контекста, в котором этот термин используется. Различных применения :
- Интерфейс в SystemVerilog
- Интерфейс объекта
- Интерфейс DUT
Интерфейс в SystemVerilog. SystemVerilog предоставляет конструкцию, называемую интерфейс, который является одним из основных объектов контейнер, из которого строится проект в SystemVerilog. Мы используем виртуальные интерфейсы, которые указывают на интерфейсы для подключения module-based аппаратуры к class-based testbenches.
Интерфейс объекта. Общедоступные задачи и функции, доступные для объекта, образуют его интерфейс. Есть два небольших варианта этого значения интерфейса. Одним из них является прямой. Посмотрите на класс и определите, какие задачи и функций, доступны пользователю класса. Это его интерфейс. Другое значение состоит в ссылке на базовый класс, который определяет набор задач и функций, доступных для работы производного класса. Это значение интерфейса обычно используется в объектно-ориентированных языках, которые поддерживают множественное наследование, таких как C++ или Java. В этих языках, вы можете установить требование, что производный класс определяет функциональность, наследуемую от интерфейса базового класса.
Интерфейс print_if определяет прототипы для функций печати. Любой класс, который наследуется от print_if затем обязан осуществлять print()и sprint(). SystemVerilog не поддерживает множественное наследование, но он поддерживает истинно виртуальные интерфейсы. Истинно виртуальный интерфейс - интерфейс во втором контексте (базовый класс, который определяет набор задач и функций прототипов), который не имеет реализаций. Истинно виртуальная версия print_if выглядит следующим образом в SystemVerilog:
virtual class print_if; pure virtual function void print(); pure virtual function string sprint(); endclass
Даже если SystemVerilog не поддерживает множественное наследование, и OVM построено на SystemVerilog, важно понять истинно виртуальные интерфейсы и наследование интерфейсов, потому что они активно используются в OVM. В частности, TLM ports and exports являются производными от интерфейса класс tlm_if_base.
DUT интерфейс. Часть аппаратуры, как правило, доступны через свои интерфейсы. В этом контексте, интерфейс состоит из контактов и протоколов используемых для связи с устройством. Например, устройство может иметь USB интерфейс.
TLM идиомы
В данном разделе рассматриваются основные средства передачи транзакций между компонентами. Мы put, get, и transport формы связи транзакций. Эти примеры не используют библиотеку OVM, так как они предназначены для иллюстрации основных механизмов связи на transaction-level с минимальными затратами. В следующем разделе мы рассмотрим более полный пример, который использует OVM библиотека для связи.
Put
В put конфигурации, один компонент отправляет операции другому компоненту. Операция называется put. Инициатором является компонентом, который инициирует передачу, а целевой компонент, который получает результат. Используя TLM номенклатуру, мы говорим, что инициатор put транзакции цели.
Рисунок 3-2 показывает, что А предает операцию в пункт Б. Инициатор имеет порт, рисуется как квадрат, и цель имеет export, изображается в виде круга. Поток управления направлен от квадрата к кругу, то есть, А вызывает B, который содержит реализацию методов port. Стрелка показывает направление поток данных, и в этом случае, это означает, что данные будут двигаться от А до B. Мы можем проиллюстрировать код для этих компонентов с producer и consumer. producer является инициатором и consumer является целью. Мы должны строить эти компоненты таким образом, что они не знали друг о друге априори. Чтобы сделать это, мы используем истинно виртуальный интерфейс для определения функции, который будет использоваться для передачи данных между инициатором и целью. Во-первых, давайте взглянем на SystemVerilog версию producer.
46 class producer; 47 48 put_if put_port; 49 50 task run(); 51 52 int randval; 53 54 for(int i=0; i<10; i++) 55 begin 56 randval = $random %100; 57 $display(“producer: sending %4d”, randval); 58 put_port.put(randval); 59 end 60 61 endtask 62 63 endclass : producer file: 03_tlm/01_put/put.sv
producer - это класс, который создается динамически. Он имеет два ключевых элемента, Run () задачу и put_port. Run () задача является простой задачей, которая повторяется 10 раз и передает 10 транзакций. Для простоты, наши операции являются целыми числами. На практике, транзакция может быть сколь угодно сложным объектом, таким как структура или класс.
Чтобы передать операции, producer вызывает put() на put_port. Что такое put_port? Это не порт в традиционном смысле Verilog. Это ссылка на put_if. Что такое put_if? Put_if это виртуальный интерфейс класса расположенный между инициатором (producer) и целью (consumer).
39 virtual class put_if; 40 pure virtual task put(int val); 41 endclass : put_if
put_if класс с истинно виртуальными задачами, т.е. задачами, не имеющими реализацию. Без реализации всех своих задач и функций, виртуальный класс не может быть создан сам по себе. Он должен быть базовым классом другого класса, который создается. В нашем случае, класс, производный от истинно виртуального put_if является consumer.
68 class consumer extends put_if; 69 task put(int val); 70 $display(“consumer: receiving %4d”, val); 71 endtask : put 72 endclass : consumer file: 03_tlm/01_put/put.sv
consumer содержит реализацию put(); истинно виртуальные задачи определены в put_if. Задача put() реализация принимает аргументы, переданные ей, и печатает их. put_if играет ключевую роль в соединении consumer и producer. Ссылка на него на стороне producer, которую мы называем порт, устанавливает требование, что должна быть реализация функций и задач интерфейса, к которому этот объект будет привязан. consumer является производным от интерфейса и, следовательно, в нем должны быть реализованы истинно виртуальные задачи, удовлетворяющие требованиям.
Модуль верхнего уровня связывающий producer и consumer.
77 module top; 78 79 producer p; 80 consumer c; 81 82 initial begin 83 // instantiate producer and consumer 84 p = new(); 85 c = new(); 86 // connect producer and consumer 87 // through the put_if interface class 88 p.put_port = c; 89 p.run(); 90 end 91 endmodule : top file: 03_tlm/01_put/put.sv
Обратите внимание, что оператор присваивания:
88 p.put_port = c;
Он образует связь между producer и consumer. Когда new() вызывается на р, чтобы создать новый экземпляр producer, член put_port не имеет значения. Во время выполнения произойдет ошибка, если put_port. put () вызывается до связи назначения. Назначение С к p.put_port дает ссылку для consumer, который содержит реализацию задачи put() интерфейса.
Get
Дополнением к put является get. В этом устройстве, инициатор получает транзакции от цели. Поток управления такой же, от инициатора к цели, но направление потока данных обратное. Инициатор получает транзакции от цели. В этом случае consumer является инициатором, producer - целью. Consumer инициирует вызов producer для получения транзакции.
Рисунок 3-3 очень похож на рисунок 3-2. Разница лишь в том, что здесь стрелка от цели к инициатору, а не наоборот. Это означает, что потоки данных от цели к инициатору. Ниже SystemVerilog consumer (инициатор).
62 class consumer; 63 64 get_if get_port; 65 66 task run(); 67 int randval; 68 for(int i=0; i<10; i++) 69 begin 70 get_port.get(randval); 71 $display(“consumer: receiving %4d”, randval); 72 end 73 endtask 74 endclass file: 03_tlm/02_get/get.sv
consumer имеет задачу, run(), которая выполняется 10 раз, чтобы получить 10 транзакций. Producer как в put примере, consumer имеет здесь порт. Кроме того, как в put примере, порт ссылается на истинно виртуальный интерфейс, в данном случае он называется get_if.
41 virtual class get_if; 42 pure virtual task get(output int t); 43 endclass : get_if file: 03_tlm/02_get/get.sv
является истинно виртуальным интерфейс, который определяет функцию get(). Цель (producer) строится аналогично, как и цель в put примере. Она содержит реализацию функции интерфейса. Этот producer генерирует случайное число между 0 и 99.
48 class producer extends get_if; 49 50 task get(output int t); 51 int randval; 52 randval = $random % 100; 53 $display(“producer: sending %4d”, randval); 54 t = randval; 55 endtask 56 57 endclass : producer file: 03_tlm/02_get/get.sv
Связи на высшем уровне будут выглядеть очень похожими.
79 module top; 80 81 producer p; 82 consumer c; 83 84 initial begin 85 // instantiate producer and consumer 86 p = new(); 87 c = new(); 88 // connect producer and consumer through the get_if 89 // interface class 90 c.get_port = p; 91 c.run(); 92 end 93 endmodule : top file: 03_tlm/02_get/get.sv
После создания объектов producer и consumer, используя операцию new(), два объекта будут соединяться с помощью связей назначения.
Transport
Transport является двунаправленным интерфейсом. Интерфейс обеспечивает передачу транзакций от инициатора к цели и от цели обратно к инициатору. Как правило, мы используем этот механизм, чтобы смоделировать запрос-ответ протоколы. Когда речь идет о компонентах с двунаправленным интерфейсов, мы используем термины master (ведущего) и slave (ведомого), а не инициатор и цель.
master (A) делает и put и get за один вызов функции. Как мы видели в предыдущих примерах, put() и get() принимают один аргумент, аргумент они помещают или извлекают. Тем не менее, функция transport() имеет два аргумента, запрос и ответ. Она посылает запрос и возвращает ответ. slave (B) принимает запрос и возвращает ответ.
Давайте сначала посмотрим на истинно виртуальный интерфейс.
37 virtual class transport_if; 38 pure virtual task transport(input int request, 39 output int response); 40 endclass : transport_if file: 03_tlm/03_transport/transport.sv
Интерфейс содержит одну функцию, transport(), которая принимает два аргумента: запрос, который передается цели и ответ, который возвращается инициатору.
Master вызывает transport(), создает запрос и отправляет его slave, используя transport. Он обрабатывает ответ, который возвращается.
45 class master; 46 47 transport_if port; 48 49 task run(); 50 51 int request; 52 int response; 53 54 for(int i=0; i<10; i++) 55 begin 56 request = $random % 100; 57 $display(“master: sending request %4d”, 58 request); 59 port.transport(request, response); 60 $display(“master: receiving response %4d”, 61 response); 62 end 63 64 endtask 65 endclass : master file: 03_tlm/03_transport/transport.sv
slave реализует функцию transport(). В нашем примере, она делает некоторые тривиальные обработки запроса для создания ответа.
70 class slave extends transport_if; 71 72 task transport(input int request, output int response); 73 $display(“slave: receiving request %4d”, 74 request); 75 response = -request; 76 $display(“slave: sending response %4d”, 77 response); 78 endtask 79 80 endclass file: 03_tlm/03_transport/transport.sv
Связи верхнего уровня между master и slave работают точно так же как и в примере с put и get.
85 module top; 86 87 master m; 88 slave s; 89 90 initial begin 91 // instantiate the master and slave 92 m = new(); 93 s = new(); 94 95 // connect the master and slave through 96 // the port interface 97 m.port = s; 98 m.run(); 99 end 100 101 endmodule : top file: 03_tlm/03_transport/transport.sv
Связь назначения связывает master и slave. После выполнения соединения, master может использовать соединения для прямого вызова функций slave.
Блокирующие интерфейсы против не блокирующих
Интерфейсы мы рассмотрели ранее блокирующие. Это означает, что функции и задачи блокируют выполнение до их завершения. Им не разрешается заканчиваться неудачей. Не существует механизма для блокировки при аварийном завершении или иным образом изменять поток управления. Они просто ждать, пока запрос будет выполнен. В синхронизированной системе это означает, что время может пройти между вызовом и возвращением полученного результата.
В put конфигурации, у нас есть два компонента, producer и consumer. Producer генерирует случайное число и посылает его к consumer, используя put(). Прежде чем вызывается put(), нет активности в consumer. Вызов put() вызывает активность в consumer, которая выводит значение аргумента. За то время, что consumer является активным, producer ожидает. Это природа блокирующего вызова. Вызывающий должен ждать, пока вызов завершиться для возобновления выполнения.
Теперь сравните это описание с неблокирующим вызовом. Неблокирующий вызов возвращается немедленно. Семантика неблокирующего вызова гарантирует, что вызов возвращается в том же дельта цикле, в котором он был запущен, то есть без задержки, даже на один дельта цикл.
Истинно виртуальный интерфейс, который соединяет неблокирующего slave и master выглядит так же, как другие истинно виртуальные интерфейсы, которые мы видели. Значительная разница в том, что nb_get () возвращает значение статуса вместо транзакции.
41 virtual class get_if; 42 pure virtual function int nb_get(output int t); 43 endclass : get_if file: 03_tlm/04_nonblocking/nbget.sv
master (consumer) должен проверить состояние, возвращаемое из nb_get (), чтобы определить, завершена ли функция успешно. Отметим также, что мы ввели время в модель. Consumer проверяет каждый 4 нс доступность значения.
78 class consumer; 79 80 get_if get_port; 81 82 task run(); 83 int randval; 84 int ok; 85 86 for(int i=0; i<20; i++) 87 begin 88 #4; 89 if(get_port.nb_get(randval)) 90 $display(“%t: consumer: receiving %4d”, $time, randval); 91 else 92 $display(“%t: consumer: no randval”, $time); 93 end 94 endtask 95 endclass file: 03_tlm/04_nonblocking/nbget.sv
producer организован как функция и задача. Задача будет раздвоена (порождена), чтобы запуститься как непрерывный процесс. Он генерирует новые случайные значения, что consumer будет захватывать. Тем не менее, каждая случайная величина доступна только для 2 нс из 7 нс цикла. Функция - реализация nb_get, которая возвращает значение, запускает периодически выполнение run().
48 class producer extends get_if; 49 50 int randval = 0; 51 int rand_avail = 0; 52 53 function int nb_get(output int t); 54 if(rand_avail) begin 55 $display(“%t: producer: sending %4d”, 56 $time, randval); 57 t = randval; 58 return 1; 59 end 60 return 0; 61 endfunction 62 63 task run(); 64 forever begin; 65 #5; 66 randval = $random % 100; 67 rand_avail = 1; 68 #2; 69 rand_avail = 0; 70 end 71 endtask 72 73 endclass : producer file: 03_tlm/04_nonblocking/nbget.sv
Когда мы запускаем пример, мы видим, что не каждый вызов nb_get () успешен.
4: consumer: no randval 8: consumer: no randval 12: producer: sending -99 12: consumer: receiving -99 16: consumer: no randval 20: producer: sending -39 20: consumer: receiving -39 24: consumer: no randval 28: producer: sending -9 28: consumer: receiving -9 32: consumer: no randval 36: consumer: no randval 40: producer: sending 57 40: consumer: receiving 57 44: consumer: no randval 48: producer: sending -71 48: consumer: receiving -71 52: consumer: no randval 56: producer: sending -14 56: consumer: receiving -14 60: consumer: no randval 64: consumer: no randval 68: producer: sending 29 68: consumer: receiving 29 72: consumer: no randval 76: producer: sending 18 76: consumer: receiving 18 80: consumer: no randvalБлокировать get конфигурацию смог только один процесс - consumer, который постоянно обращался с запросом к producer, чтобы тот отправил новое значение. Неблокирующий вариант состоит из двух этапов: consumer регулярно опрашивает producer, чтобы получить новое значение, и producer генерирует новые значения асинхронно по отношению к consumer. Наши неблокирующий producer генерирует доступное случайное значение каждые 7 нс. Он ждет 5 нс, а затем создает новое значение и новое значение действительно в течение 2 нс. Флаг rand_avail устанавливается, когда случайное значение доступно и сбрасывается в противном случае.
Реализация nb_get () в этом примере должна проверить rand_avail есть ли что отправить. Если нет, то она возвращает 0, чтобы указать, что запрос не удался. Если есть что-то, то он посылает его и возвращает 1, чтобы показать успех.
Блокирующие интерфейсы полезны для работы с двумя компонентами синхронно. Вызовы блокируются до тех пор, пока запрошенная операция выполняется, не смотря на то, сколько это потребует времени. С другой стороны, неблокирующие интерфейсы полезны при асинхронной передаче данных. Они не ждут и могут быть использованы для опроса целей, как в приведенном примере.