OVM/OVM методология/Повторное использование Testbench
Материал из Wiki
- OVM методология
- Введение
- Основы верификации
- Основы ООП
- Transaction-Level моделирование
- Механика OVM
- Основы Testbench
- Повторное использование Testbench (Reuse)
- Полный Testbench
Содержание |
Reuse
Построение проекта(архитектуры) тетсбенча, кодинг, отладки и тестирования драйверов, мониторов и других компонент тестбенча может занять довольно много времени. Очевидный способ улучшения производительности верификации является повторное использование компонентов. Это звучит достаточно просто, но чтобы сделать компонент действительно многоразовым, некоторые идеи (мысли, концепты) должны быть введены в его архитектуру и конструкцию. Подумать о том, как сделать компонент многоразовым, как вы хотите повторно использовать компоненты и какую степень свободы должны поддерживать компоненты.
6.1 Типы Повторное использование (или повторное использование типов)
Основное средство, чтобы сделать компоненты пригодными для повторного использования - это инкапсуляции(объединение) всех данных и функций внутри четко определенного интерфейса. Интерфейс определяет, как вы можете изменить, обращаться и запрашивать (извлекать данные из) компонент. Всякий доступ будет запрещен кроме того, что конкретно разрешен в интерфейсе. Мы рассмотрим четыре метода для создания многоразовых элементов тестбенча: вызовы функций, параметризованные классы, наследование и конфигурации. Каждый из этих методов представляет собой разные способы изменить структуру или поведение, используя интерфейс. В каждом из этих методов, информация подаваемая снаружи вызывает изменение структуры или поведение элемента. Первые три способа сделать элементы многоразовыми упоминались в главе 2, где мы обсуждали объектно-ориентированное программирование.
- Вызов функций. - алгоритм или другая функциональность заключенная в вызове функции. Всякий раз, когда вы нуждаетесь в этой функциональности, вы можете просто вызвать функцию, а не копировать и вставить код или переписать его полностью в этом место. Функции могут принимать параметры, значения которых изменяют поведение функции.
- Наследование. Инкапсуляция данных и функциональности произвольной сложности в единый объект, который скрывает сложность, так что объект может быть опущен на место и осуществляет свою деятельность через интерфейсы. Добавление или изменение функциональности по наследству - есть способ повторного использования базового объекта и воспользоваться любой функциональностью, которая в нем содержится.
- Параметризованные классы обеспечивают способ создания повторно используемых классов. Классы с параметрами образует template1, которые могут быть созданы несколько раз с различными параметрами образуют семейство классов. Скалярные значения и типы могут быть использованы в качестве параметров. Каждый экземпляр класса параметризованных называется специализацией. Для определения специализации параметры становятся частью типа.
- Конфигурации в процессе выполнения. Настраиваемые элементы могут изменить свое поведение или структуру через установку флагов, переключателей, или конфигурации переменных.
6.2 Многоразовые компоненты
Чтобы изучить, как строить повторно используемые компоненты, давайте рассмотрим пример простого memory master управляющего памятью через транспортный канал, и все это на уровне транзакций. Давайте посмотрим на каждый из этих компонентов подробно, чтобы увидеть, как они строятся с использованием методов повторного использования.
Заголовок класса для памяти мастера показывает, что класс является производным от другого параметризованного базового класса, hfpb_master_base. Базовый класса параметризованный идентично производному класса.
27 class hfpb_random_mem_master 28 #(int DATA_SIZE=8, int ADDR_SIZE=16) 29 extends hfpb_master_base #(DATA_SIZE, ADDR_SIZE);
Мы сделали предположение, что пользователи протокол HFPB, скорее всего, строить различные виды устройств управления операциями на шине HFPB. Hfpb_master_base позволяет нам поместить структуры и функции в базовом классе, который будет использоваться всеми шинами. Таким образом, в строительстве нашего УУ(устройство управления), hfpb_random_mem_master, мы повторно будем использовать функциональные возможности, предоставляемые в базовом классе. HFPB УУ на основе базового класса содержит множество вещей.
36 class hfpb_master_base 37 #(int DATA_SIZE=8, int ADDR_SIZE=16) 38 extends ovm_component; 39 40 typedef hfpb_master_base 41 #(DATA_SIZE, ADDR_SIZE) this_type; 42 typedef ovm_component_registry 43 #(this_type) type_id; 44 45 ‘include “hfpb_parameters.svh” 46 47 ovm_transport_port 48 #(hfpb_tr_t, hfpb_tr_t) transport_port; 49 50 ovm_barrier objection; 51 protected hfpb_addr_map #(ADDR_SIZE) addr_map;
Он содержит определение типа из ovm_component_registry, что приводит тому что компоненты должны быть зарегистрированы в фабрике. Он содержит транспортный порт, масив обьектов, и карту адресов. Все эти объекты, которые могут быть использованы masters, производные от этого класса. База HFPB мастер-класс является примером использования объектно-ориентированного наследования, техники повторного использования. Преимущество использования наследования является то, что вам нежно только писать и проверять код для объектов в базовом классе один раз. Каждый раз, когда вы используете повторно базовый класс для создания нового устройства управления шиной, вы гарантированно наследуете структуру мастера. Например, вы всегда будете знать, что мастера, полученные из hfpb_master_base будет иметь транспортный порт, и его имя будет transport_port. hfpb_random_mem_master, полученный мастером, сможет рандомизировать последовательность транзакций в память. Некоторые параметры, max_bursts и max_burst_size, будут управлять рандомизацией. max_bursts максимальное количество очередей, которые будут выпущены мастером в тесте, и max_burst_size максимальное количество транзакций в одной очереди. Вместо жесткого кодирования этих значений, мы делаем их доступными для объекта через средство конфигурации.
54 max_burst_size = 16; 55 if(!get_config_int(“max_burst_size”, max_burst_size)) begin 56 $sformat(s, “max burst size not specified, using default of %0d”, max_burst_size); 57 ovm_report_warning(“build”, s); 58 end 59 $sformat(s, “max burst size: %0d”, max_burst_size); 60 ovm_report_info(“build”, s); 61 62 max_bursts = 100; 63 if(!get_config_int(“max_bursts”, max_bursts)) begin 64 $sformat(s, “max bursts not specified, using default of %0d”, max_bursts); 65 ovm_report_warning(“build”, s); 66 end 67 $sformat(s, “max bursts: %0d”, max_bursts); 68 ovm_report_info(“build”, s);
23 class hfpb_driver #(int DATA_SIZE=8, int ADDR_SIZE=16) 24 extends 25 ovm_driver #(hfpb_seq_item #(DATA_SIZE, ADDR_SIZE), 26 hfpb_seq_item #(DATA_SIZE, ADDR_SIZE));
Драйвер имеет два параметра, DATA_SIZE и ADDR_SIZE. Каждый имеет значение по умолчанию, так что если вы не предоставите один, по умолчанию используется. Каждый раз при объявлении объекта параметризованного типа, необходимо создать специализацию, копию кода со значением параметра и заменить его имя. Вы никогда не увидеть специализированный код, компилятор берет на себя управление им без вашего вмешательства. Драйвер является производным от ovm_driver, базовый класс, который рассматривается в главе 8. ovm_driver класс с параметризованной последовательностью типов элементов посылаемых между ним и секвенсером. В случае протокол HFPB, последовательностью параметризованных элементов является DATA_SIZE и ADDR_SIZE. Код драйвера написан таким образом, что любое место, где есть нужна либо размерность шины данных или размер адресной шины, используются DATA_SIZE и ADDR_SIZE параметры в качестве постоянных. Мы можем использовать параметризованные компоненты в различных ситуациях, и, таким образом, мы поддерживаем независимость любого конкретного значения либо DATA_SIZE или ADDR_SIZE. Затем, изменяя параметры, мы можем влиять на структуру компонента. Таким образом, мы можем использовать наши параметризованные компоненты в системах с другим адресом и шириной шины данных.
Создавая все HFPB компоненты так, чтобы они были параметризованы таким же образом, мы можем построить целый испытательный стенд, что не зависит от ширины адреса и шины данных. Начиная с верхнего уровня, мы передаем ADDR_SIZE и DATA_SIZE параметров в самый верхний модуль. Это единственное место, где необходимо указать значения для DATA_SIZE и ADDR_SIZE. Везде, значения, будут получены через параметры классов.
83 module top; 84 85 parameter int DATA_SIZE = 8; 86 parameter int ADDR_SIZE = 9; 87 88 env #(DATA_SIZE, ADDR_SIZE) e; 89 90 initial begin 91 e = new(“env”); 92 run_test(); 93 end 94 95 endmodule file: 06_reuse/01_TL/top.sv
Эти параметры передаются в ENV, самый верхний уровень компонента тестбенча, создан как специально параметризованный класс. ENV, конечно, это параметризованный компонент, параметры которых также DATA_SIZE и ADDR_SIZE. Кроме того, любой компонент создаваемый в ENV, которые зависят от адреса или размера шины данных, аналогичным образом параметризованы.
42 class env #(int DATA_SIZE=8, int ADDR_SIZE=16) 43 extends ovm_component; 44 45 hfpb_mem #(DATA_SIZE, ADDR_SIZE) mem; 46 hfpb_random_mem_master #(DATA_SIZE, ADDR_SIZE) mm; 47 hfpb_addr_map #(ADDR_SIZE) addr_map; 48 49 tlm_transport_channel 50 #(hfpb_transaction #(DATA_SIZE, ADDR_SIZE), 51 hfpb_transaction #(DATA_SIZE, ADDR_SIZE)) 52 transport_channel; file: 06_reuse/01_TL/top.sv
6.3 Агенты
Это довольно часто распространено когда при создании testbenches вы можете увидеть много повторяющихся экземпляров и соединений. Это типично для подключения драйверов и мониторов во многом похожим способом. Кроме того, вы будете часто замечать что, когда вы повторяете определенные блоки компоненты, вы захотите, чтобы они были всегда сразу настроены. Индивидуальная настройка экземпляров и компонентов может внести скучные ошибоки. Агенты решают эту проблему. Агенты все для организации повторного использования. Они могут повторно использовать мониторы, драйвера и другие компоненты, которые являются частью конкретного протокола, и они сами образуют компоненты многократного использования путем создания интерфейса по всем подчиненным компонентам. Агент содержит все элементы протокола внутри вместе в одном пакете. Вы можете легко применять протокол в испытательном стенде путем создания экземпляра этого пакета, а не по отдельности экземпляр драйвера, монитор и других специфичных для протокола компонентов.
Агент является оберткой всех компонент, которые реализуют протокол. Он служит в качестве интерфейса для всех компонентов протокола. Интерфейс имеет несколько форм: параметры класса, port и export, виртуальный интерфейс и интерфейс конфигурации. Параметры класса передаются на подчиненные компоненты протокола. Ports и exports обеспечивают вход и выход пунктов для транзакций; виртуальный интерфейс обеспечивает вывод на уровне подключения контактов и настройки интерфейса позволяет включить или отключить различные компоненты для настройки поведения агента для конкретных приложений.
Рисунок 6-2 показывает простой агент только с драйвером и монитором. Внешние интерфейсы включают экспорт для передачи транзакции, analysis port (который делает доступными все шины транзакций), а виртуальный интерфейс для подключения к шине на уровне контактов.
В некоторых случаях вам не понадобится как монитор так и драйвер. Скажем вам нужно всего лишь драйвер. Вы все еще можете использовать агент и просто выключить монитор. Агент использует конфигурацию системы, чтобы определить, какие внутренние компоненты включена или выключена. Has_driver и has_monitor флаги (в данном случае) используются для выбора, какие компоненты включены.
Простой агент с выключенными функциями драйвера используется просто в качестве монитора. И наоборот, вы можете выключить монитор и работать с устройством в качестве драйвера. Конечно, это не имеет смысла, чтобы выключить оба и драйвер и монитор, потому что тогда агент не будет ничего делать.
Если вы хотите использовать либо драйвер или монитор, то зачем помощью агента? Почему бы не использовать просто экземпляр драйвера или монитора по мере необходимости? Причина проста: повторное использование. Если мы создаем в окружении только драйвер, а затем решим, что нужен монитор, то мы должны взять текстовый редактор, и изменить код среду для создания экземпляра монитора и подключить его. Поскольку мы использовали агентов, мы можем просто включить has_monitor флаг и не нужно изменять окружающую среду кода.
6.4 Многоразовое использование HFBP
Агент HFPB является весьма параметризованным и настраиваемым устройством. Он содержит все специфичные для протокола компоненты, необходимые для протокола HFPB в одном экземпляров компонента. Это включает в себя мастера, драйвер (мы объясним разницу в ближайшее время), секвенсор, salve, и coverage collector (коллекция покрытия). Связи между этими компонентами указанна в агенте. Как уже говорилось выше этот простой агент обладает параметрами класса, коллекцией на уровне транзакций port export, виртуальный интерфейс для подключения на уровне соединения и конфигурацию интерфейса.
Параметры в заголовке класса агента, DATA_SIZE и ADDR_SIZE, используется при создании внутренней структуры агента.
138 class hfpb_agent #(int DATA_SIZE=8, ADDR_SIZE=16) 139 extends ovm_agent;
Внешние соединения включают в себя виртуальный интерфейс для подключения на уровне соединения, канал export для традиционного TLM, порт последовательность для подключения к секвенсор, массив salave export (по одному для каждого slave), и analysis port для передачи операции признанных на шине на уровне подключения.
142 virtual hfpb_if #(DATA_SIZE, ADDR_SIZE) m_bus_if; 143 144 ovm_transport_export 145 #(hfpb_transaction #(DATA_SIZE, ADDR_SIZE), 146 hfpb_transaction #(DATA_SIZE, ADDR_SIZE)) 147 transport_export; 148 149 ovm_seq_item_pull_port 150 #(hfpb_seq_item#(DATA_SIZE, ADDR_SIZE), 151 hfpb_seq_item#(DATA_SIZE, ADDR_SIZE)) 152 seq_item_port; 153 154 ovm_slave_export 155 #(hfpb_transaction #(DATA_SIZE, ADDR_SIZE), 156 hfpb_transaction #(DATA_SIZE, ADDR_SIZE)) 157 slave_export []; 158 159 ovm_analysis_port 160 #(hfpb_transaction #(DATA_SIZE, ADDR_SIZE)) 161 analysis_port;
Агент содержит collection протоколов компонентов. Обратите внимание, что все они (наборы) объявлены как локальные, за исключением секвенсор. Весь доступ к этим компонентам осуществляется через интерфейсы которые только что перечислили, а не непосредственно к компонентам. Это сокрытие данных позволяет нам гарантировать, что агент остается многоразовым, не позволяя пользователям сформировать неправильное зависимости агента от внутреннего объекты.
164 local hfpb_master #(DATA_SIZE, ADDR_SIZE) master; 165 local hfpb_driver #(DATA_SIZE, ADDR_SIZE) driver; 166 local hfpb_slave #(DATA_SIZE, ADDR_SIZE) slave []; 167 local hfpb_monitor #(DATA_SIZE, ADDR_SIZE) monitor; 168 local hfpb_coverage #(DATA_SIZE, ADDR_SIZE) cov; 169 local hfpb_talker #(DATA_SIZE, ADDR_SIZE) talker; 170 hfpb_sequencer #(DATA_SIZE, ADDR_SIZE) sequencer;
Причина, по которой секвенсор не объявлены как локальные, потому что это необходимо для получения доступа секвенсор для того, чтобы управлять им. В нашем HFPB агенте у нас есть и мастер и драйвер. Разница между master и драйвером состоит в том, что мастер содержит транспортный export, а драйвер seq_item_pull_port для последовательностей, которые связаны с секвенсором. (Мы обсудим последовательности и секвенсоры в главе 8). Кроме того, объекты которые принимаются на входе мастера должны быть производными от ovm_transaction и наборы значений, которые принимаются драйвером должны быть производным от ovm_sequence_item. Это два разных средства для перемещения операции запрос и ответ внутрь и из агента. Мастер используется для традиционных структурных моделей уровня транзакций, и драйвер для обработки транзакций (в виде последовательности элементов), генерации последовательностей. Протокол HFBP позволяет работать ровно с одним мастером и одним или несколькими slave, поэтому мастера и драйвера являются взаимоисключающими (взаимозаменяемыми). Это не имеет смысла в данном протоколе, чтобы иметь более одного устройства управляющего шиной. Важно заметить, что интерфейсы и внутренние компоненты имеют класс параметров, которые совпадают с агентом, то есть DATA_SIZE и ADDR_SIZE. При создании семейства компонентов для протокола HFPB, которые параметризованы одинаково, мы можем перейти к параметрам и не указывать значения этих параметров более одного раза. Не все внутренние компоненты и интерфейсы экземпляров используются каждый раз, когда и агент. Создаются только те, которые необходимы для указанной конфигурации. Вместо того, чтобы создавать отдельные агенты для каждой возможной конфигурации, которые были бы очень неуклюжими, мы будем использовать конфигурацию объекта в OVM, чтобы изменить структуру агента. Наши HFPB агент имеет ряд параметров конфигурации, которые контролируют его структуру.
175 local bit has_monitor; 176 local bit has_coverage; 177 local bit has_talker; 178 local bit has_master; 179 local bit has_driver; 180 local bit has_sequencer; 181 local int unsigned slaves; 182 local hfpb_vif #(DATA_SIZE, ADDR_SIZE) vif;
В build() и connect() агент использует значения этих параметров конфигурации для управления тем как создаются суб-компоненты и как они связаны. Значение для каждого параметра конфигурации получены с помощью вызовов get_config_int (или, в случае VIF, get_config_obj). В функции агента build() представлен серию вызовов get_config_*, чтобы получить все необходимые сведения о конфигурации этого компонента.
199 if(!get_config_int(“has_monitor”, has_monitor)) begin 200 has_monitor = 0; 201 monitor = null; 202 end 203 204 if(!get_config_int(“has_coverage”, 205 has_coverage)) begin 206 has_coverage = 0; 207 cov = null; 208 end . . .
Как только функция build() получает все параметры конфигурации, она также делает любую необходимую проверку ошибок и проверку их согласованности. Каждый вызов get_config_* заключен в условный оператор, который проверяет состояние вызова. Если вызов не удается, значит нет пункта с указанным именем для текущей области в базе данных конфигурации, то нужно убедится, что параметр конфигурации установлен на правильное значение. А также другие переменные параметры, по мере необходимости. Например, если has_monitor не передается, то убедитесь, что он установлен в 0 и указатель монитора является недействительным(null). Если has_coverage установлен в 1, мы также устанавливаем has_monitor в 1, поскольку не имело бы никакого смысла иметь коллектор(корзину) покрытия без монитора. И так далее. Дальше в по порядку, мы используем параметры конфигурации для построения внутренностей агента. В качестве примера, если has_master установлен, то мы создаем экземпляр компонента master и transport export, который эта компонента будет использовать для подключения к внешним компонентам.
284 if(has_master) begin 285 master = new(“master”, this); 286 transport_export = new(“transport_export”, this); 287 end
Позже, в фазе connect, мы снова будем использовать параметр has_master конфигурации, на этот раз, чтобы определить есть ли подключение к transport export.
340 if(has_master) begin 341 transport_export.connect(master.transport_export); 342 end
Эта проверка необходима, потому что если has_master равен 0, то мы не знаем есть ли хоть один экземпляр компонента мастер, ни transport export экземпляр. Проверка значения has_master снова гарантирует, что мы не пытаемся подключить компоненты, экземпляров которых не было. VIF виртуальной интерфейс также получит через конфигурацию объекта, используя технику интерфейс объекта, описанного в разделе 4.7. Строго говоря, интерфейс объекта это не параметр конфигурации. Виртуальные интерфейсы являются частью соединения конструкции. Если вы составите агент из виртуального интерфейса, вероятно, агент не будет работать правильно в дизайне. Мы можем использовать наши высоко настраиваемые агенты в различных направлениях. В таблице ниже приведены некоторые интересные конфигурации
Mode | Config Settings |
---|---|
Monitor |
has_monitor = 1 has_coverage = don't care has_talker = don't care has_master = 0 has_driver = 0 has_sequencer = 0 slaves = 0 |
Master |
has_monitor = don't care has_coverage = don't care has_talker = don't care has_master = 1 has_driver = 0 has_sequencer = 0 slaves = 0 |
Driver |
has_monitor = don't care has_coverage = don't care has_talker = don't care has_master = 0 has_driver = 1 has_sequencer = 0 slaves = 0 |
Sequencer |
has_monitor = don't care has_coverage = don't care has_talker = don't care has_master = 0 has_driver = 1 has_sequencer = 1 slaves = 0 |
В режиме монитора, все компоненты выключен, кроме монитора. Затем агент действует строго в качестве монитора. Таким же образом можно настроить aster режим, выключив все, кроме master, а затем работать с агентом исключительно как с мастером. Вы можете включить или отключить любой компонент или подмножество компонентов агента. Так произошла инкапсуляции всех протоколов отдельных компонентов в единый компонент, называемый агентом, то используя OVM объекты конфигурации, вы можете создать один компонент, который может быть использован для различных приложений. Одной из причин всего этого конфигурирования является обеспечение повторного использования компонентов тестбенча на уровне блоков, без изменений, в системного уровня тестбенча. Мы обсудим это подробнее в главе 9.
6.5 Пример агента
As an example of using an agent, we’ll transform the design shown in Figure 6-1 to use an agent instead of a transport channel. We’ll also add a second memory.
В качестве примера использования агента, мы будем трансформировать дизайн показанный на рисунке 6-1, чтобы использовать агента вместо транспортного канала. Мы также добавим вторую памяти.
В этом примере, агент выступает в качестве модели шины. Она содержит мастер и slave, и все связи между ними. Кроме того, он содержит монитор. В этом примере мы настроим агентом следующим образом:
80 set_config_int(“hfpb_agent”, “has_monitor”, 0); 81 set_config_int(“hfpb_agent”, “has_master”, 1); 82 set_config_int(“hfpb_agent”, “slaves”, 2); 83 set_config_int(“hfpb_agent”, “has_talker”, 0); 84 set_config_object(“*”, “addr_map”, addr_map, 0); file: 06_reuse/02_RTL/top.sv
Монитор, мастер, и talker включен, и настроена на два slave. Кроме того, в поставляется карта адреса, в который определяет, какая часть адресного пространства занимает памяти каждого slave. talker является абонентом устройства, подключенного к порту анализа. Он просто выводит транзакции, распознанные монитором. Talker включается, когда вы хотите видеть печатных отчет обо всех операциях, которые проходят через агента. С учетом этой конкретной конфигурации, мы можем эффективно построить топологию показанную на рисунке 6-7.
120 module top; 121 122 parameter int DATA_SIZE = 8; 123 parameter int ADDR_SIZE = 9; 124 125 env #(DATA_SIZE, ADDR_SIZE) e; 126 hfpb_vif #(DATA_SIZE, ADDR_SIZE) hfpb_vif_obj; 127 128 clk_rst cr(); 129 clock_reset ck (cr); 130 hfpb_if #(DATA_SIZE, ADDR_SIZE) bus_if (cr.clk, cr.rst); 131 132 initial begin 133 134 e = new(“env”); 135 hfpb_vif_obj = new(bus_if); 136 set_config_object(“*”, “hfpb_vif”, hfpb_vif_obj, 0); 137 138 fork 139 ck.run(); 140 join_none 141 142 run_test(); 143 end 144 145 endmodule file: 06_reuse/02_RTL/top.sv
Кроме того, мы используем технику интерфейс объекта, описанного в разделе 4.7 для передачи виртуального интерфейса в окружении построенного как класс. За счет применения параметризованных классов и конфигураций OVM объектов мы можем создать единый компонент, агент HFPB, что позволяет реализовать различные топологии и конфигурации компонентов протокола HFPB.
6.6 Итог
Одним из основных ключей к повышению производительности и надежности вашей процесса верификации есть повторное использование. Повторное использование компонентов экономит время, не занимая времени для написания нового кода. Бывшие в употреблении компонентами являются более надежными, в силу того, что они были использованы в нескольких приложениях. Многоразовые компоненты не пропадают бесследно, вы должны поставить некоторые мысли о том, как вы будете повторно использовать конкретную компоненту. К счастью, SystemVerilog и OVM обеспечивают некоторые средства, которые способствуют разработке элементов тестбенча для многоразового использования.