OVM/OVM методология/Полный Testbench
Материал из Wiki
Содержание |
Полный тестбенч
Чтобы ответить на вопросы does-it-work и are-we-done, нам нужно больше, чем просто драйвера и мониторы. Мы должны собрать информацию о покрытии для ответа на are-we-done вопрос, нам нужно эталонная модель и механизм сравнения функции эталонной модели, чтобы ответить на вопрос does-it-work, и нам нужны механизм контроля, чтобы перезапускать испытательный стенд в соответствующее время. Наконец, нам нужны адаптеры и коннекторы, чтобы все это соединить вместе.
Блоки float-point
Эта и последующие главы иллюстрируют методы построения испытательного стенда для дизайна с использования с плавающей запятой(FPU). Эта конструкция принимает FP операнды и оператор, и вычисляет результат. В данном разделе представлен пример, который использует на уровне транзакций FPU, чтобы проиллюстрировать построение корзины покрытия OVM. На рисунке ниже показана организация примера.
58 forever begin 59 get_port.get(req); 60 rsp = compute(req); 61 put_port.put(rsp); 62 end 63 endtask
Интерфейс связи с FPU осуществляется через встроенный tlm_transport_channel (см. раздел «Транспорт» на стр. 58). Для упрощения кодирования вызова run(), мы обьявим get_port и put_port для подключения к внутренней стороне транспортного канал slave. Get_port для получения запросов и put_port для возвращения ответов.
23 class fpu_tlm extends ovm_component; 24 25 ovm_transport_export #(fpu_request, 26 fpu_response) transport_export; 27 28 local tlm_transport_channel #(fpu_request, 29 fpu_response) mstr_chan; 30 local ovm_blocking_get_port #(fpu_request) get_port; 31 local ovm_blocking_put_port #(fpu_response) put_port; 44 function void connect(); 45 transport_export.connect(mstr_chan.transport_export); 46 get_port.connect(mstr_chan.blocking_get_request_export); 47 put_port.connect(mstr_chan.blocking_put_response_export); 48 endfunction
75 case(op) 76 OP_ADD: result = req.a + req.b; 77 OP_SUB: result = req.a - req.b; 78 OP_MUL: result = req.a * req.b; 79 OP_DIV: 80 if (req.b <= 1.0e-38 && req.b >= -1.0e-38) 81 result = _nanf(); // div by zero 82 else 83 result = req.a / req.b; 84 OP_SQR: 85 if (req.a < 0.0) 86 result = _nanf(); 87 else 88 result = req.a ** 0.5; // square root 89 endcase
Состояние case переключается при запросе операции, и каждая из ветвей case выполняет определенные вычисления. Состояние DIV сначала проверяет, если делитель равен нулю, поскольку деление на ноль не определено. В RTL версии FPU, деление на нуль, будет вызывать исключение, когда операция деления выбрана и делитель равен нулю. В случае с Квадратным корнем проверяется, если его операнд меньше нуля, так как квадратный корень из отрицательного числа также не определен. В обоих случаях, результат имеет значение NaN, то есть не является числом, которое является IEEE значение с плавающей точкой по стандарту для неопределенных значений. Ключевым фактором конструкции транспортного вентиля TAP является то, что она не потребляет нисколько времени на свое выполнение. То есть, не должно быть цикла дельта задержки между транспортными вызовом в генераторе и транспортным вызовом в slave. Чтобы удовлетворить это требование, мы реализуем блокирование транспортных интерфейса непосредственно с помощью ovm_blocking_transport_imp. Наша реализации transport() перенаправляет запрос на загрузку и ответ обратно на получение. Он также формирует запрос и ответ на пару и отправляет его из порт анализа.
89 task transport(input fpu_request req, 90 output fpu_response rsp); 91 92 fpu_pair pair; 93 94 transport_port.transport(req, rsp); 95 pair = new(req, rsp); 96 pair_ap.write(pair); 97 98 endtask file: 07_complete_testbenches/01_tlm_reference/top.sv
Подставляя реализации вместо подключения к каналу, мы можем избежать любых задержек, связанных с перемещением данных через канал.
7.2 Коллекции покрытия
Ключевым компонентом в ответе на вопрос are-we-done - вопрос коллекции покрытия. Её роль, как следует из названия, состоит в сборе информации по функционированию во время прогонов моделирования. Покрытие является количественным характеристикой о том, сколько конструкции теста выполнилось. Коллекция покрытия получает информацию о том, что было выполнено и использует ее для вычисления ответа на вопрос are-we-done. Коллекция покрытия строится в OVM как расширение ovm_subscriber абстрактного базового класса. Как вы можете видеть ниже, subscriber имеет реализацию интерфейса анализа, который содержит одну неблокирующую функцию, write().
virtual class ovm_subscriber #(type T = int) extends ovm_component; typedef ovm_subscriber #(T) this_type; ovm_analysis_imp #(T, this_type) analysis_export; function new(string name, ovm_component parent); super.new(name, parent); analysis_export = new( "analysis_imp", this ); endfunction pure virtual function void write( input T t ); endclass
Так как это абстрактный класс, необходимо расширить(наследовать) ovm_subscriber и определить свою собственную реализацию функции write(), чтобы сохранять покрытия. Ovm_analysis_imp связывает разъем для фактической реализации интерфейса. Мы называем это analysis_export потому, что внешне он выглядит так же, как экспорт, потому что он обеспечивает реализацию вызывающего порта. При расширении ovm_subscriber, вы просто подключить analysis_export в нужный analysis_port, и запустите на выполнение его. В процессе работы функция write() собирает данные из объекта, переданного в качестве аргумента и обрабатывает их. Обработка может быть любого рода, до тех пор, пока она сохраняет неблокирующую семантику. SystemVerilog дает возможность построить covergroup(груп покрытия) для сбора и обработки фактических данных об покрытии. Как правило, write() будет копировать свой вклад сделки в соответствующие поля в переменной класса, который затем оцифровывается в covergroup. В сущности write() выступает в роли абонента, который обеспечивает средства для подключения covergroup с другими компонентами OVM, которые являются источниками данных для анализа.
57 covergroup fpu_cov; 58 cons_op : coverpoint m_op {bins adds = (OP_ADD [* 2]); 59 bins subs = (OP_SUB [* 2]); 60 bins muls = (OP_MUL [* 2]); 61 bins divs = (OP_DIV [* 2]); 62 bins sqrs = (OP_SQR [* 2]); } 63 endgroup
Конечно, реализация covergroups и критерия достижения полноты охвата зависит от конкретного приложения. Особенность covergroup - он используется для иллюстрации концепции. Функция write() имеет три обязанности в нашей коллекции покрытия. Оно копирует данные от транзакции и передает в переменную класса, так что данные видны для covergroup, он вызывает sample() из covergroup, и этим проверяет, был ли достигнут порог полноты покрытия. Вызов sample() дает указания covergroup посмотреть на текущие значения соответствующих переменных покрытия и обновить свои счетчик.
78 function void write(input fpu_pair t); 79 80 real coverage; 81 m_op = t.req.op; 82 m_round = t.req.round; 83 84 fpu_cov.sample(); 85 86 coverage = fpu_cov.get_inst_coverage(); 87 if(coverage >= coverage_threshold) begin 88 done = 1; 89 end 90 91 endfunction
В дополнение к подсчету информации по покрытию, коллекция покрытия позволяет тестбенчу закрыться, когда покрытие достигнет порога. Он делает это согласованно с верхним уровнем окружения. Окружение верхнего уровня вызывает global_stop_request () в качестве первого (и единственного) вызова, для задачи run(). Это приводит к вызову функции stop() во всех компонентах, которые имеют enable_stop_interrupt установленным в 1, включая наш коллекцию покрытия. Когда все задачи stop() вернутся, то моделирование останавливается. Наши коллекции покрытий не выключаются по вызову задачи stop(), пока не будет установлено флага, который укажет, что на полное покрытие было достигнуто.
96 task stop(string ph_name); 97 wait (done == 1); 98 ovm_report_info(“stop”, “allowing stop”); 99 endtask
Обратите внимание, что нам нужен любой явный путь сообщения между коллекцией покрытия и тестом. Stop_request механизм в OVM автоматически обрабатывает соответствующее уведомление. Тест не будет завершена, пока все компоненты в тестбенче (которые имеют свой enable_stop_interrupt набор бит) не закончат выполнение их stop() методов, или будет превышено время ожидания. И так, только потому, что задача stop() в этом компоненте будет разблокирована, это не означает, что моделирование будет немедленно прекращено. Это лишь означает, что с точки зрения этой коллекции покрытий, можно прекращать моделирование. Может быть, что есть другие компоненты, в которых задача stop() по-прежнему заблокирована по той или или иной причине. Только когда все задачи stop() вернут значения будет начата остановка(перезапуск).
7.3 FPU агент
Когда вы проверяете на уровне транзакций модель FPU с RTL, то это необходимо для того, чтобы протокол интерфейса осуществляется в полном объеме и надлежащим образом. Чтобы проиллюстрировать эту задачу проверки, мы используем RTL версии модели FPU написаны на VHDL. Интерфейс FPU прост, что делает его хорошим кандидатом для примера. Рисунок 7-2 показывает распиновку на блок FPU. Она имеет два 32-разрядных входных шин для А и В операндов и 32-битная шина для вывода результата. 2-битная входная шина определяет режим округления, а 3-битный входная шина определяет операцию, которая будет выполнена. Восемь выходных контактов сигнала исключения, по одному на контакт.
32-разрядные А и B операнды и 32-разрядных результат являются значения с плавающей точкой и представлены с помощью IEEE 754 стандарт для двоичного представления числа с плавающей точкой. В ниже таблицах кратко приведены функции FPU.
FPU управляется start пином. Расчет начинается по следующий переднему фронту тактовой сигнала, когда устанавливается значение на start пине. FPU устанавливает пин готовности вывода результата, если расчет завершен. Устройство содержит конвейер с глубиной 1, поэтому, когда устанавливает контакт ready, это означает, что результат предыдущего расчета доступен на выходах. При использовании FPU в testbenches удобно рассматривать его как протокола для создания драйверов, монитор и так далее, и инкапсулировать их в агентов. В то время как интерфейс для FPU не является протоколом общего назначения, как наш HFPB протокол или другие, такие как USB, PCI, и так далее, то думая о нем в данном случае, нам позволяется создавать многократно используемые компоненты для построения testbenches для FPU или устройств, использующих FPU. Организация агент FPU показано на рисунке 7-3 ниже. Она организована так же, как HFPB агент. Он имеет мастеров и драйвера, которые преобразуют транзакции в контактный уровень активности. Она также имеет шину контактного уровня, монитор, talker, и коллекцию покрытия. Одним из важных отличий является то, что он не slave, поэтому вы не можете действительно использовать агент как отдельная модель шины. Поскольку интерфейс FPU это протокол для доступа к конкретному устройству, а не к шине или протокол связи,то нет никакой проблемы.
Как и в нашем HFPB агент, агент FPU включает в себя специфичные для протокола коллекции покрытия. Монитор обнаруживает запрос и ответ операций по выводу на уровне интерфейса и собирает их в транзакции fpu_pair, которая включает в себя как запрос и ответ операций встроенный в него. Один из способов настроить агент - настроить его через фабрику для создания экземпляров различных коллекционеров покрытия, в зависимости от того, что вы пытаетесь достичь в вашем тесте. Для агента FPU есть три пути для управления операций на шине: мастер, мастер канала передачи и секвенсор-драйвер комбинации. Мастер и мастер канала передачи используют транзакции объектов, производных от ovm_transaction. Секвенсор-драйвер использует транзакции объектов, производных от ovm_sequence_item. Разница в том, что последовательность элементов содержит дополнительные механизмы, которые позволяют им быть транспортированы через секвенсор для драйвера. Не смотря на то, что содержание последовательности элементов и операций, получено из различных базовых классов, они являются идентичными. В главе 8 обсуждается последовательность и последовательность элементов более тщательно. Эти три компонента для управления операциями FPU являются взаимоисключающими. Хорошие стиль кодинга, диктуют, чтобы интерфейс конфигурации для агента FPU гарантировал, что в нем будет применен один из способов создания (а не оба).
7.4 Табло результатов
The term scoreboard is a generic term for a wide range of component types whose function is to answer does-it-work questions. The essential characteristic of a scoreboard is that it collects data about the operation of the DUT as the simulation proceeds and compares it with a reference of some sort to determine if the DUT is functioning correctly. A scoreboard can be as simple as a trigger that recognizes when a flag is raised or a truth table, as in the simple testbench in Section 1.2.2. Or it can be as complex as a complete reference model of a complete system design. For the FPU design, we embed a scoreboard inside a reference model. The The reference for the FPU contains the transaction-level implementation of the FPU and a scoreboard to compare the data generated by reference with the results from the RTL DUT. Figure 7-4 shows the reference, along with an example design that uses it.