OVM/OVM методология/Механика OVM
Материал из Wiki
Содержание |
Механика OVM
Библиотека OVM предоставляет много возможностей для построения testbenches. В этой главе мы ознакомимся с наиболее важными возможностями, которые вы будете использовать почти во всех ваши testbenches.
Компоненты и иерархия
Первичная структура для создания элементов testbench является компонента. Компонента в OVM – это аналогия модулю в Verilog. Компонента OVM сконструирована на основе класса, который обеспечивает ему различные характеристики, чем модуль Verilog и имеет различные использования импликаций. Среди таких характеристик является то, что классы создаются во время выполнения, а не во время разработки, как модули. Таким образом, OVM отвечает за создание компонента и сборки их в иерархии.
![]() |
Рисунок 4-1 иллюстрирует простую иерархию компонент. После мы покажем, как построить эту иерархию с помощью средств OVM для создания компонент и объединения их в иерархии:
Самый верхний узел, ENV, является корнем. Корень отличается тем, что он не имеет предка. Все остальные узлы имеют только одного предка. Каждый узел имеет имя. Расположение каждого узла в иерархии может быть идентифицировано уникальным full_name (путем), который строится на нанизывании имен всех узлов между корнем и узлов в запросе, разделяя их сепаратором иерархии, точкой (.). Например, путь к компоненту, второго потомка с2 - top.c2.child2.
Компонент в ОВМ является классом, производным от ovm_component. Простейшая компоненты - листья, те, которые не имеют потомков.
57 class child extends ovm_component; 58 59 function new(string name, ovm_component parent); 60 super.new(name, parent); 61 endfunction 62 63 endclass file: 04_OVM_mechanics/01_hierarchy/top.sv
Конструктор имеет два параметра, имя компоненты и указатель на его предка. Название - простое имя, а не иерархический путь. Предок предоставляет место для подключения новой компоненты в иерархии. Полный путь потомка создается путем объединения имени потомка с полным именем предка, разделенный точкой (.).OVM предоставляет методы для получения и имени и полного пути компоненты:
string get_name(); string get_full_name();
Подчиненные компоненты создаются в функции build(), которая вызывается на этапе сборки (фазы объясняется далее в этой главе). Создание экземпляра компонента включает вызов new() для выделения памяти для нее и передачи соответствующих параметров в конструктор. В компоненте, показанной ниже, мы создаем две подчиненные компоненты, и child1 child2.
71 class component extends ovm_component; 72 73 child child1; 74 child child2; 75 76 function new(string name, ovm_component parent); 77 super.new(name, parent); 78 endfunction 79 80 function void build(); 81 child1 = new(“child1”, this); 82 child2 = new(“child2”, this); 83 endfunction 84 85 endclass file: 04_OVM_mechanics/01_hierarchy/top.sv
Как component, ENV также создает экземпляры двух подчиненных компонент, c1 и c2. Вся иерархия с корнем в модуле, называемым top в нашем проекте.(??)
131 module top; 132 133 env e; 134 135 initial begin 136 e = new(“env”); 137 run_test(); 138 end 139 140 endmodule file: 04_OVM_mechanics/01_hierarchy/top.sv
Вызов new() создает верхний уровень окружающей среды. run_test () начинает выполнение testbench.
В SystemVerilog, модули, интерфейсы и программные блоки создаются во время разработки, в то время как классы создаются после разработки, во время выполнения. Таким образом, чтобы создать иерархию классов, мы должны иметь интерфейс, модуль или программу, который содержит начальный блок, который начинает процесс создание компонент иерархии, основанных на классах. Интерфейсы предназначены для использования в качестве средства связи между двумя модулями и не очень хорошо подходят для использования в качестве корня иерархии классов. Программные блоки или модули могут быть использованы как корень (??). Для нашей простой иерархии, это не имеет значения. Позже, когда мы подключаем класс-компонент для модуля на основе аппаратных средств, мы увидим, что использование модуля предпочтительнее программным блокам.
Перемещение по иерархии
Мы можем исследовать структуры данных, используемые для реализации компонента иерархии с некоторыми методами, предусмотренными в ovm_component. Потомки компоненты хранятся в ассоциативном массиве. Этот массив напрямую не доступен, но он может быть доступен через иерархию API. Этот API-интерфейс похож на встроенные методы SystemVerilog, предназначенные для ассоциативных массивов.
int get_first_child(ref string name); int get_next_child(ref string name); ovm_component get_child(string name); int get_num_children();
get_first_child () и get_next_child () работают вместе для перебора множества потомков, содержащихся в компоненте. get_first_child () извлекает имя первого ребенка в списке. Он возвращает имя в качестве ссылки. get_next_child () возвращает имя следующего ребенка в списке. Он возвращает 1, если имеется следующая имя потомка и 0, если был достигнут конец списка. get_child () преобразует имя в ссылку компоненты.
Используя эти функции, мы можем пройти по компонентам иерархии.
73 function void depth_first(ovm_component node, 74 int unsigned level = 0); 75 76 string name; 77 78 if(node == null) 79 return; 80 81 visit(node, level); 82 83 if(node.get_first_child(name)) 84 do begin 85 depth_first(node.get_child(name), level+1); 86 end while(node.get_next_child(name)); 87 88 endfunction file: 04_OVM_mechanics/utils/traverse.svh
Эта функция будет осуществляющая обход в глубину иерархии, называя visit(). Мы используем get_first_child () и get_next_child () для перебора списка каждого потомка в каждом узле. Для каждого итерации мы вызываем depth_first () рекурсивно. Для нашего маленького проекта, результат будет следующий:
+ env | + env.c1 | | env.c1.child1 | | env.c1.child2 | + env.c2 | | env.c2.child1 | | env.c2.child2
Функция visit() использует глубину узла и определяет является ли он конечным для печати строки каждого узла.
Singleton Top
Компоненты, которые не имеют предков (то есть, параметр предок в конструкторе является нулевым) называют orphans.(??) В OVM вы можете создать столько компоненты без предков, сколько вам необходимо. Тем не менее, не существует такого понятия, как истинные orphans. Любой компонент, чей предок является null, относится к встроенному предку, называемому ovm_top. ovm_top состоит из одного экземпляра ovm_root. Это предок всех компонент, которые имеют предков. В самом деле, env в нашем предыдущем примере является потомком ovm_top. Так как он не имеет предка, то автоматически получают ovm_top в качестве родителя.
singleton - широко известный объектно-ориентированных шаблон проектирования, который имеет private (local) или protected конструкторы и статическую функцию get, которая возвращает тот же самый указатель не смотря на то, сколько раз она вызывается. Это означает, что возможно существование одного экземпляра, и что экземпляр создается автоматически при вызове функции get. ovm_top содержит дескриптор экземпляра singleton ovm_root(???). Он статически инициализируется при вызове ovm_root :: Get (). Вы можете вызвать ovm_root :: Get () в любое время, но в этом нет необходимости, так как ovm_top предусматривает это.
Есть целый ряд полезных следствий существования singleton top-level компоненты. Одним из них является, что вы можете достичь любую компоненту из ovm_top. Если вы запустили алгоритм обхода иерархии, начиная с ovm_top, вы доберетесь до каждой компоненты в системе. Другим следствием является то, что любая компонента, включая порты, экспорт и каналы, которые создаются внутри модуля, достижима из ovm_top. Если вы хотите изменить отчет во всех обработчиках компонент, вы можете сделать это, вызвав одну из иерархических функции отчетности в ovm_top. ovm_top содержит все механизмы синхронизации, которые объясняются далее в этой главе.
Connectivity
Компоненты соединены друг с другом через TLM port и exports. Port и exports обеспечивают средства для компонент, или, точнее, процессы в компонентах для синхронизации и связи друг с другом. Port и exports - объекты, которые образуют пункт связывания для обеспечения межкомпонентной связи. Как уже говорилось в предыдущей главе, exports обеспечивают функции и задачи, которые могут быть вызваны из Port.
Метод connect портов и exports используется для связывания их вместе.
initiator_port.connect(target.export)
Этот метод создает ассоциацию, или связывание, между port и export так, что port может вызывать(запускать) задачи и функции из export. Для успешного соединения, типы port и export должны совпадать. То есть, типы интерфейсов должны быть одинаковыми, и тип объекта, передающегося в интерфейс должны быть одинаковыми.
Подключение через Иерархию
Как и контактами в RTL дизайне, нам нужно подключиться к портам TLM через иерархические границы. Рисунок 4-3 использует простой дизайн, чтобы показать, как сделать эти соединения. Этот проект содержит исходную компоненту с двумя port(выходами), которые в конечном итоге подключаются к двум входам(export). Для осуществления подключения между этими компонентами, мы должны расширить port и export на уровень выше в иерархии.
Компонент source содержит два порта, и first_put_port, second_put_port.
65 class source extends ovm_component; 66 67 ovm_put_port #(trans_t) first_put_port; 68 ovm_put_port #(trans_t) second_put_port; 69 70 function new(string name, ovm_component parent); 71 super.new(name, parent); 72 endfunction 73 74 function void build(); 75 first_put_port = new(“first_put_port”, this); 76 second_put_port = new(“second_put_port”, this); 77 endfunction file: 04_OVM_mechanics/02_connectivity/top.sv
Кроме того, компонента sink создает экспорт и создает его в функции build. Экспорт связан с внутренним каналом, FIFO, от которого компонента может получать объекты во время выполнения.
126 class sink extends ovm_component; 127 128 ovm_put_export #(trans_t) put_export; 129 local tlm_fifo #(trans_t) fifo; 130 131 function new(string name, ovm_component parent); 132 super.new(name, parent); 133 endfunction 134 135 function void build(); 136 put_export = new(“put_export”, this); 137 fifo = new(“fifo”, this); 138 endfunction 139 140 function void connect(); 141 put_export.connect(fifo.put_export); 142 endfunction file: 04_OVM_mechanics/02_connectivity/top.sv
source_wrapper должен создать соединение между внутренним источником компоненты и внешней ее границей. Он делает это соединение, создавая свои порты, которые имеют тот же тип, что и тип портов lower-level, в нашем случае, тех, которые принадлежат к source.
98 class source_wrapper extends ovm_component; 99 100 source s; 101 ovm_put_port #(trans_t) put_port1; 102 ovm_put_port #(trans_t) put_port2; 103 104 function new(string name, ovm_component parent); 105 super.new(name, parent); 106 endfunction 107 108 function void build(); 109 s = new(“source”, this); 110 put_port1 = new(“put_port1”, this); 111 put_port2 = new(“put_port2”, this); 112 endfunction 113 114 function void connect(); 115 s.first_put_port.connect(put_port1); 116 s.second_put_port.connect(put_port2); 117 endfunction 118 119 endclass file: 04_OVM_mechanics/02_connectivity/top.sv
160 class sinker extends ovm_component; 161 162 ovm_put_export #(trans_t) first_put_export; 163 ovm_put_export #(trans_t) second_put_export; 164 165 sink sink1; 166 sink sink2; 167 168 function new(string name, ovm_component parent); 169 super.new(name, parent); 170 endfunction 171 172 function void build(); 173 sink1 = new(“sink1”, this); 174 sink2 = new(“sink2”, this); 175 first_put_export = new(“first_put_export”, this); 176 second_put_export = new(“second_put_export”, this); 177 endfunction 178 179 function void connect(); 180 first_put_export.connect(sink1.put_export); 181 second_put_export.connect(sink2.put_export); 182 endfunction 183 184 endclass file: 04_OVM_mechanics/02_connectivity/top.sv
Два нижних уровня компоненты sink и exports создаются обычным способом. Затем они соединяются с помощью метода подключения export. Сейчас мы создаем port-export соединение между source_wrapper и sinker, также с помощью функции Connect.
192 class env extends ovm_component; 193 194 sinker s; 195 source_wrapper sw; 196 197 function new(string name, ovm_component parent = null); 198 super.new(name, parent); 199 endfunction 200 201 function void build(); 202 s = new(“sinker”, this); 203 sw = new(“source_wrapper”, this); 204 endfunction 205 206 function void connect(); 207 sw.put_port1.connect(s.first_put_export); 208 sw.put_port2.connect(s.second_put_export); 209 endfunction 210 211 task run; 212 global_stop_request(); 213 endtask 214 215 endclass file: 04_OVM_mechanics/02_connectivity/top.sv
Для новых пользователей часто может вызвать трудности определить, порт или экспорт необходимо подключить и какой объект является аргументом. Вы можете легко понять это, следуя потоку управления через систему. Общее правило заключается в том, что запрашиваемый порт вызывается с помощью функции connect() и передачи в нее запрашиваемого port или export в качестве аргумента. Рисунок 4-4 показывает поток управления через иерархическую систему.
Порт – объект вызова и экспорт - объект вызванной функции или задачи. Вы можете воспринимать порты, как вызов экспорта. Так, в env, мы вызываем connect на put_ports с put_exports в качестве аргументов. Для port-to- port и export-to-export иерархии соединений, вызывающий будет чуть менее очевиден. Если вызов осуществляется со стороны порта, вы можете считать, что вызов lowest-level порта в иерархии является вызовом методов интерфейса upper-level порта. Аналогично, если экспорт объект вызова, вы можете считать upper-level экспорт вызовом lower-level export. В таблице ниже приведены возможные типы подключения:
Примечание для пользователей AVM
В AVM-3.0 соединения были сделаны аналогично: вызовам функции connect() из port и export. Кроме того, export-to-export, port-to-export, и port-to-port вызовы были сделаны на разных фазах, export_connections (), Connect (), и import_connections (), соответственно. В OVM, порядок, в котором вызывается connect(собственно происходит подключение), не важен, он может быть вызван в любом порядке. Мы рекомендуем вам создавать подключения в отдельных функциях и их вызов производить в нужное для вас время (к примеру можно назвать функцию connect(), не следует путать с методом connect(), который содержится в входных и выходных портах). OVM поддерживает позднее связывание, (a feature where calls to connect() only make a note that a connection is to be made..) фишка в том что соединение появится только тогда когда произведем вызов функции создания подключения. Позже, перед end_of_elaboration, notes соединения будут согласованы и выполнены. Это позволяет чище использовать модель и избавит от большинства очевидных ошибок подключение при соединении компонент. ( much more forgiving of understandable errors where connect() calls were made in the wrong order.)
Фазы
Для традиционных модулей Verilog полная разработка проекта и его выполнение осуществялется в симуляторе. Так как OVM компоненты - это классы, они создаются и подключаются, и их выполнение начинается за пределами Verilog разработчика. Компоненты создаются вызовом конструктора класса операцией new(), которая выделяет память и выполняет инициализацию. В то время как Verilog время выполнения run-time engine managing instantiation, разработка, и выполнение компонент на основе классов, функциональность компонент разделена на фазы, и контроллер фазы OVM управляет их исполнением.
Каждая фаза представлена в компоненте как виртуального метода (задачи или функция) с тривиальной реализацией по умолчанию. Эти phase callbacks реализуются разработчиком компоненты, который обеспечивает соответствующую функциональность. Фаза контроллера гарантирует, что фазы выполняются в надлежащем порядке. Набор стандартных фаз показано в следующей таблице:
Каждая фаза имеет определенную цель. разработчики Компонент должны заботиться о том, чтобы функциональность реализованная в каждой фазе callback соответствовала фазе определения.
• New технически не является фазой, в том, что она не управляется фазой контроллера. Тем не менее, для каждого компонента, конструктор должен быть выполнен и завершен в целях создания компоненты. Таким образом, new должен выполниться до build() или любого другого последующего этапах выполнения.
• build это место, где новые компоненты, порты и экспорт создаются и настраиваются. Это также рекомендуемое место для вызова set_config_ * и get_config_ * (см. Section4.4).
• connect – место, где компоненты, порты и экспорт созданные в build() связываются.
• end_of_elaboration здесь вы можете выполнить изменение конфигураций, зная, что разработка уже завершена. То есть, вы можете предположить, что все компоненты созданы и соединены.
• start_of_simulation выполняется только до времени 0.
• run pre-defined task фазой. Все выполняемые задачи работают параллельно. Каждая выполняемая задача продолжается, пока ее управление не достигнет оператора EndTask или она явно будет прекращена. Позже в этой главе мы обсудим, как завершать testbenches.
• extract предназначен для сбора информации, касающейся покрытия или другой информации о том, как ответить на вопросы testbench.
• проверка - место, где выполняется любая проверка правильности или проверка валидации извлеченных данных.
• отчет -место , где делаются окончательные отчеты.
Ниже простой пример, использующий ovm_report_info () для иллюстрации порядка выполнения фаз.
38 class sub_component extends ovm_component; 39 40 function new(string name, ovm_component parent); 41 super.new(name, parent); 42 endfunction 43 44 function void build(); 45 ovm_report_info(“build”, ““); 46 endfunction 47 48 function void connect(); 49 ovm_report_info(“connect”, ““); 50 endfunction 51 52 function void end_of_elaboration(); 53 ovm_report_info(“end_of_elaboration”, ““); 54 endfunction 55 56 function void start_of_simulation(); 57 ovm_report_info(“start_of_simulation”, ““); 58 endfunction 59 60 task run(); 61 ovm_report_info(“run”, ““); 62 endtask 63 64 function void extract(); 65 ovm_report_info(“extract”, ““); 66 endfunction 67 68 function void check(); 69 ovm_report_info(“check”, ““); 70 endfunction 71 72 function void report(); 73 ovm_report_info(“report”, ““); 74 endfunction 75 76 endclass file: 04_OVM_mechanics/03_phases/top.sv
В компоненте верхнего уровня, мы создаем две компоненты, каждая из которых в свою очередь создает две sub_component. sub_components являющимися такими же, как и сами компоненты; каждая фаза обратного callback просто печатает строку, определяющую фазу. При выполнении, вы получите следующий результат:
OVM_INFO @ 0 [RNTST] Running test ... OVM_INFO @ 0: env.c1 [build] OVM_INFO @ 0: env.c1.s1 [build] OVM_INFO @ 0: env.c1.s2 [build] OVM_INFO @ 0: env.c2 [build] OVM_INFO @ 0: env.c2.s1 [build] OVM_INFO @ 0: env.c2.s2 [build] OVM_INFO @ 0: env.c1.s1 [connect] OVM_INFO @ 0: env.c1.s2 [connect] OVM_INFO @ 0: env.c1 [connect] OVM_INFO @ 0: env.c2.s1 [connect] OVM_INFO @ 0: env.c2.s2 [connect] OVM_INFO @ 0: env.c2 [connect] OVM_INFO @ 0: env.c1.s1 [end_of_elaboration] OVM_INFO @ 0: env.c1.s2 [end_of_elaboration] OVM_INFO @ 0: env.c1 [end_of_elaboration] OVM_INFO @ 0: env.c2.s1 [end_of_elaboration] OVM_INFO @ 0: env.c2.s2 [end_of_elaboration] OVM_INFO @ 0: env.c2 [end_of_elaboration] OVM_INFO @ 0: env.c1.s1 [start_of_simulation] OVM_INFO @ 0: env.c1.s2 [start_of_simulation] OVM_INFO @ 0: env.c1 [start_of_simulation] OVM_INFO @ 0: env.c2.s1 [start_of_simulation] OVM_INFO @ 0: env.c2.s2 [start_of_simulation] OVM_INFO @ 0: env.c2 [start_of_simulation] OVM_INFO @ 0: env.c2 [run] OVM_INFO @ 0: env.c2.s2 [run] OVM_INFO @ 0: env.c2.s1 [run] OVM_INFO @ 0: env.c1 [run] OVM_INFO @ 0: env.c1.s2 [run] OVM_INFO @ 0: env.c1.s1 [run] OVM_INFO @ 1: env.c1.s1 [extract] OVM_INFO @ 1: env.c1.s2 [extract] OVM_INFO @ 1: env.c1 [extract] OVM_INFO @ 1: env.c2.s1 [extract] OVM_INFO @ 1: env.c2.s2 [extract] OVM_INFO @ 1: env.c2 [extract] OVM_INFO @ 1: env.c1.s1 [check] OVM_INFO @ 1: env.c1.s2 [check] OVM_INFO @ 1: env.c1 [check] OVM_INFO @ 1: env.c2.s1 [check] OVM_INFO @ 1: env.c2.s2 [check] OVM_INFO @ 1: env.c2 [check] OVM_INFO @ 1: env.c1.s1 [report] OVM_INFO @ 1: env.c1.s2 [report] OVM_INFO @ 1: env.c1 [report] OVM_INFO @ 1: env.c2.s1 [report] OVM_INFO @ 1: env.c2.s2 [report] OVM_INFO @ 1: env.c2 [report]
Вы видите, что build()выполняется сверху вниз и остальные фазы запуска снизу вверх. Вы также можете видеть, что каждый этап завершается во всех компонентах до начала следующего этапа. Таким образом, в connect(), например, вы можете расчитывать, что build() завершена во всех компонентах. Вы также заметите, что время запускается после фазы run. В нашем примере задача run - тривиальна, она просто выполняет задержку времени на единицу (# 1).
un_test (), упоминалось ранее в разделе 4.1, инициирует фазу выполнение. Он начинает выполнение фаз по порядку и контролирует, что каждый этап завершен до начала следующего.
Config
Для увеличения повторного использования компонент, желательно объявит их с параметрами, которые могут быть внешне настроены. Конфигурационные средства обеспечивают возможности для этого. Они основаны на базе пар имя-значение, и называются configuration items, которые организованы иерархически. Каждая компонента содержит таблицу конфигураций элементов конфигурации, так как компоненты организованы в качестве дерева, каждый элемент в базе данных может быть уникально размещен по расположению компоненты и имени элемента конфигурации
Класс ovm_component содержит два набора методов для конфигурации размещения элементов в базу данных и потом для их получения. Это set_config_ * и * get_config_. В таблице ниже приведены оба набора.
Set_config_ * функция помещает элемент в конфигурационную базу данных текущего компонента, то есть в экземпляре компонента, в котором функция вызывается. Каждая из этих функций принимают три аргумента, имя, field_name и значение. Аргумент name - имя пути, который представляет набор компонент, которые должны соответсвовать кофигурациям этого элемента. name используется в * get_config_, чтобы найти элементы в базе данных конфигурации. field_name - это имя поля и должно быть уникальным в пределах текущей базы конфигураций. value является частью пары имя-значение и его тип может быть строка, INT, или ovm_object, в зависимости от вызываемой функции. Кроме того, set_config_object принимает копию аргумента для указания, был ли объект передан в качестве значения так как значение должно быть клонировано прежде чем оно будет помещено в базу конфигурации. Функции get_config_ * извлекает элементов из базы данных конфигураций. Эти функции принимают только два аргумента, имя поля и INOUT переменную, который содержит значение расположения элемента. Они также возвращают бит, чтобы указать был ли удачно расположен запрашиваемый объект. * Get_config_ функции не принимают аргументы имени пути, как их set_config_ * коллеги, поскольку они используют путь к текущему компоненту, как ссылку, чтобы найти элементы конфигурации. Они разработаны, чтобы узнать, есть значение элемента конфигурации для текущего контекста, то есть, компонент, в котором функция get_config_ * вызывается.
Алгоритм поиска для извлечения элементов конфигурации использует имя пути компонента запроса и путь к файлу вставляемый в каждый item. Он начинается с поиска конфигурационных элементов в базе данных в верхней компоненте по field_name. Если такой элемент существует, то он затем спрашивает, соответствует ли указанный путь к файлу пути имени компоненты. Если элемента с указанным field_name нет или имена путей не совпадают, то поиск идет с дочерней компоненты. Этот процесс продолжается до соответствия или до достижения компоненты начала поиска.
Путь каждого элемент конфигурации может быть регулярным выражением. Таким образом, мы используем алгоритм соответствия регулярных выражений для нахождения совпадения с путем запрошенной компоненты и именем пути элемента конфигурации. Эффект заключается в соответствии иерархической области. В качестве примера, рассмотрим простую иерархию на рисунке 4-5. Давайте предположим, что в env::build()мы запускаем два вызова set_config_ *:
112 function void build(); 113 c1 = new(“c1”, this); 114 c2 = new(“c2”, this); 115 116 set_config_int(“c2.*”, “i”, 42); 117 set_config_int(“*”, “t”, 19); 118 endfunction file: 04_OVM_mechanics/04_config/top.sv
Это вызовет запись двух конфигурационных элементов в базу данных в env. Обратите внимание на звездочку (*) в имени пути. Имя пути в вызове set_config_ * - регулярное выражение, и дикие символы используются для указать несколько областей, к которыми элемент применяется. По элементу i, c2. * показывает, что в любой области ниже c2 в иерархии, i будет определять указанное значение. В этом случае указанное значение - 42. Если вы опустите звездочку, то элемент конфигурации применяется только к c2, а не к любому из своих потомков.
Состояние конфигурации базы данных для каждого компонента в иерархии после вызова set_config_ * показано на рисунке 4-6,
пусть в элементе top.c1.child1 мы осуществим вызов:
int i; ... get_config_int(“i”, i)
Поиск задает вопрос: Каково значение конфигурации i в иерархических рамках top.c1.child1? Чтобы ответить на этот вопрос, конфигурации базы данных в env ищется в первую очередь. Запись i там говорит, что значение i совпадающее с env.c2. * - 42. Тем не менее, компоненты, из которых был сделан запрос, находятся в c1 sub-hierarchy. Таким образом, не существует соответствия, а get_config_int () возвращает статус неудачи. Запрос в любую компоненту, которая является дочерней c2 будет успешно завершен и вернется значение 42.
Ниже приведен код для функцию build() дочерней компоненты. Это место где эти компоненты ищут конфигурационные значения i и t.
60 function void build(); 61 62 string msg; 63 64 if(!get_config_int(“t”, t)) begin 65 $sformat(msg, “no value for t found in config database, using default value of %0d”, t); 66 ovm_report_warning(“build”, msg); 67 end 68 69 if(!get_config_int(“i”, i)) begin 70 $sformat(msg, “no value for i found in config database, using default value of %0d”, i); 71 ovm_report_warning(“build”, msg); 72 end 73 74 endfunction file: 04_OVM_mechanics/04_config/top.sv
Следующий пример показывает вывода при выполнении этого проекта:
# OVM_INFO @ 0 [RNTST] Running test ... # OVM_WARNING @ 0: env.c1.child1 [build] no value for i found in config database, using default value of 91 # OVM_WARNING @ 0: env.c1.child2 [build] no value for i found in config database, using default value of 91
Запрос на конфигурационный элемент t был успешным во всех контекстах с того момента как вызов set_config_int установил, что tдоступно во всех контекстах. Два запроса на элемент конфигурации i были успешными, и два нет. Результат получился такой потому что мы ограничены наличием i только компонентах на уровне или ниже env.c2. Компоненты на уровне или ниже c1 не могут видеть элемент конфигурации i из-за того, как мы создали базу данных конфигурации.
Конфигурация и фазы
Теперь, когда мы знаем, что набор вызовов для размещения и извлечение элементов в конфигурацию базы данных, следующей задачей, является эффективное применение этих функции для настройки компонент. Конфигурация может быть использована для изменения поведения или структуры testbench. Как правило, поведенческий и структурный режимы определяются, когда testbench начинает работу, поэтому он является наиболее удобным местом для установки параметров конфигурации на одной из ранних стадий, таких как new, build или connect.
Из таблицы фаз, вы можете увидеть, что new и build являются нисходящими фазами, в то время как все остальные фазы восходящие. Так что, если вы хотите установить элемент конфигурации в базу данных на более высокий уровень контекста, чтобы он был подхвачен более низким уровнем, вы должны вызвать функцию set_config_ * в new или build фазе. Фазы выполняется дискретно, это означает, что каждая фаза выполняется до начала следующей. Вы можете установить элементы конфигурации в new или build фазу и извлекать их для использования в установке поведенческого режима или изменения топологии на этапе build. В вашей функции build, сначала вызовите get_config_ * для извлечения элементов из более высоких уровней иерархии для управления конфигурацией текущего уровня. Затем добавьте вызов set_config_ * для помещения элементов конфигурации в базу данных для использования компонентами на более низких уровнях иерархии. Наконец, используя соответствующие элементы конфигурации, обработайте компоненты. Важно вызвать get_config_ * вначале, потому, что указанная информация может влиять на значения, которые установлены на более низких уровнях иерархии.
Например, настройка топологии включает в себя установку параметров топологии на верхнем уровне среды, а затем применение эти параметров в различных компонентах, которые находятся ниже верхнего уровня среды в иерархии. Наш пример имеет шину, которая может иметь любое количество masters или slaves. Число masters и slaves расположены в верхнем уровне среды. Модель шину подхватывает эту конфигурационную информацию и использует ее для построения шины. В build функции верхнего уровня окружающей среды, мы создаем модель шины и настраиваем ее с числом masters и slaves.
129 function void build(); 130 set_config_int(“bus”, “masters”, 4); 131 set_config_int(“bus”, “slaves”, 8); 132 b = new(“bus”, this); 133 endfunction file: 04_OVM_mechanics/05_config_topo/top.sv
Модель шины построена таким образом, что число masters и slaves не фиксировано. Вместо этого, количество приходит из конфигурации системы.
90 function void build(); 91 92 int unsigned i; 93 94 if(!get_config_int(“masters”, masters)) begin 95 $sformat(msg, “\”masters\” is not in the configuration database, using default value of %0d”, masters); 96 ovm_report_warning(“build”, msg); 97 end 98 99 for(i = 0; i < masters; i++) begin 100 $sformat(name, “master_%0d”, i); 101 m = new(name, this); 102 end 103 104 if(!get_config_int(“slaves”, slaves)) begin 105 $sformat(msg, “\”slaves\” is not in the configuration database, using default value of %0d”, slaves); 106 ovm_report_warning(“build”, msg); 107 end 108 109 for(i = 0; i < slaves; i++) begin 110 $sformat(name, “slave_%0d”, i); 111 s = new(name, this); 112 end 113 114 endfunction file: 04_OVM_mechanics/05_config_topo/top.sv
В функции build для модели шины, схема получает необходимую конфигурационную информацию, используя вызовы get_config_int. В каждом случае, Возвращаемое значение проверяется, чтобы определить, была ли получена запрошенная конфигурационная информация. Если нет, сообщение об ошибке предупреждает, что конфигурационный элемент не найден и что будет использовано значение по умолчанию. С точки зрения Best Practices, важно убедиться, что возвращается значение, проверено и выдается предупреждение, в случае ошибки. Без этой проверки, то, что используется значения по умолчанию, могло остаться незамеченным. В некоторых случаях это может быть приемлемо, в других случаях - нет. Человек, создающий модель шины не может знать всех обстоятельств, при которых модель будет использоваться. Поэтому важно сделать все возможное, чтобы модель была надежной. Проверка возвращаемых значений и выдача соответствующих сообщений является одним из способов повышения надежности модели.
В цикле, где мы строим masters, ссылки на каждого нового master сохраняются в той же переменной, m. Каждый новый master заменяет предыдущий. Мы не используем массив для хранения всех компонент. В каждой итерации, мы используем $ sformat, чтобы создать уникальное имя. Конструктор, new(), вызывает super.new (), конструктор в ovm_component базовом классе, который отвечает за вставку вновь созданную компоненту потомка в родительский список. Нет необходимости явно сохранять компоненту, потому что родительская компонента сделает это за нас. Цикл для создания slaves организован таким же образом.
Factory(Фабрика)
Структура testbench определяется организацией компонент в иерархии и то, как эти объекты связаны между собой. Поведение testbench определяется процедурным кодом в фазе callbacks—build, connect, run и так далее. Есть моменты, когда это необходимо изменить поведение или часть структуры извне(снаружи), то есть во время выполнения, не касаясь, кода testbench. Например, чтобы ввести ошибку в систему, вы можете заменить обычный драйвер драйвером с ошибкой, который умышленно вводит ошибки. Вместо повторного кодирования окружающей среды для использования другого драйвера, вы можете использовать factory, чтобы сделать замену автоматически.
Factory обеспечивает средства для замены одного объекта на другой без использования текстового редактора для изменения testbench. Вместо создания объект с помощью new(), вы вызываете функцию create из factory. Factory хранит список зарегистрированных объектов и, при необходимости, набор переопределения связанный с каждой из них. При создании объекта с помощью factory, список переопределения проверяется на наличие такого объекта. Если он есть, то переопределенный объект возвращается. В противном случае, зарегистрированный объект возвращается.
Factory является OVM структурой данных. Она глобальна в области видимости, и только один элемент factory может существовать (то есть, это singleton). Она служит в качестве полиморфных конструкторов, одна функция, которая позволяет создавать различные объекты. Она обеспечивает средства для регистрации объектов, и для их переопределения. Объекты, зарегистрированные как переопределения, должны быть производным от объекта, от которого они переопределялись. Для того, чтобы одна функция возвращала несколько объектов, каждый из этих объектов должны быть получен от общего базового класса. Важным компонентом factory является оболочка, класс, который описывает объект, который мы хотим создать с помощью factory. Структура данных factory основывается на таблице оболочек, проиндексированной по ключу. Оболочка имеет функцию create(), которая обращается к конструктору обернутого объекта.
Использование factory состоит из трех этапов: регистрация, настройки переопределение и создание. На первом этапе необходимо зарегистрировать объект с помощью factory. На втором шаге, добавляется переопределение зарегистрированного объекта. На третьем этапе, вы создаете объект с помощью factory, который будет возвращать либо первоначально зарегистрированный объект или переопределенный, в зависимости от того, переопределение было передано в качестве запрашиваемого объекта.
Как работает Фабрика
Термин factory-Фабрика был придуман The Gang of Four для использования в программном обеспечении в их книге Design Patterns: Elements of Reusable Object-Oriented Software. В этой книге, они определили шаблон, который они называют abstract factory, как интерфейс для создания семейств связанных объектов. Они определили шаблон factory method в качестве интерфейса для создания объектов, относящихся к подклассам для определения, какой объект создавать. Factory в OVM является комбинацией этих двух шаблонов. Она предоставляет средства для создания семейства объектов, а также предоставляет средства для определения, какой именно объект создавать структуре данных фабрики.
Factory OVM основана на структуре данных, которая отображает необходимые типы в переопределенные. По сути, организация представляет собой ассоциативный массив типов, ключом в котором является также тип. При регистрации типа при помощи фабрики, его переопределенным типом является он сам. Таким образом, по умолчанию, когда вы запрашиваете объект этого типа, вы получаете только тип. Фабрика также предоставляет средства для замены переопределенных типов другими типами, чтобы вы могли получить переопределенные типы, которые отличаются от зарегистрированных типов.
Следующий пример является сильно упрощенной фабрикой, которая показывает, как OVM фабрика работает. Фабрика сохраняет основные структуры OVM фабрики, но многие детали были удалены для более понятной иллюстрации. Наша фабрика реализуется в четырех классах, двух базовых классах и двух производных классах, которые делают реальную работу. Двумя базовыми классами являются object_base и wrapper_base. Все объекты, зарегистрированные в фабрике, должны быть получены (в конечном счете) от object_base, а wrapper_base является базовым классом для параметризованной обертки. Factory - это singleton, который содержит ассоциативный массив, содержащий экземпляры обертки. Наконец, обертки являются производными от wrapper_base и параметризованными классами, которые представляют уникальные типы.
Для нашей фабрики, базовые классы тривиальны:
46 class object_base; 47 virtual function void print(); 48 $display(“object_base”); 49 endfunction 50 endclass file: 04_OVM_mechanics/07_toy_factory/top.sv 59 class wrapper_base; 60 virtual function object_base create_object(); 61 return null; 62 endfunction 63 endclass file: 04_OVM_mechanics/07_toy_factory/top.sv
object_base имеет виртуальную функцию print(), которую мы используем для проверки типов, созданных на фабрике. wrapper_base имеет виртуальную функцию create(), полиморфный конструктор, который используется для создания новых объектов. factory - это singleton, то есть его конструктор локальный, и он содержит статическую ссылку на самого себя. Единственный способ создать экземпляр factory является вызов factory::get(). factory содержит ассоциативный массив, который отображает запрашиваемые типы wrapper_base в переопределенные типы wrapper_base.
73 class factory; 74 75 static factory f; 76 wrapper_base override_map[wrapper_base]; 77 78 local function new(); 79 endfunction 80 81 static function factory get(); 82 if(f == null) 83 f = new(); 84 return f; 85 endfunction 86 87 function void register(wrapper_base w); 88 override_map[w] = w; 89 endfunction 90 91 function void set_override(wrapper_base requested_type, 92 wrapper_base override_type); 93 override_map[requested_type] = override_type; 94 endfunction 95 96 function object_base create(wrapper_base 97 requested_type); 98 object_base obj; 99 wrapper_base override_type = 100 override_map[requested_type]; 101 obj = override_type.create_object(); 102 return obj; 103 endfunction 104 105 endclass file: 04_OVM_mechanics/07_toy_factory/top.sv
Метод register() добавляет новую запись в карту. Первоначально, при регистрации, тип не имеет переопределения. Таким образом, мы записали в карту тип, указывающий сам на себя. Set_override () метод заменяет запись в карте новым переопределенным типом. create() метод ищет переопределения на указанный тип, инициирует создание переопределенного типа и возвращает вновь созданный объект.
Класс-оболочка является наиболее интересным классом в нашем созвездии factory-related классов. Хотя он довольно прост, он делает большую часть тяжелой работы. Это основной интерфейс для фабрики, и большинство операций, которые производятся фабрикой, проходят через интерфейс оболочки.
118 class wrapper #(type T=object_base) extends wrapper_base; 119 120 typedef wrapper#(T) this_type; 121 122 static this_type type_handle = get_type(); 123 124 local function new(); 125 endfunction 126 127 function object_base create_object(); 128 T t = new(); 129 return t; 130 endfunction 131 132 static function T create(); 133 T obj; 134 factory f = factory::get(); 135 assert($cast(obj, f.create(get_type()))); 136 return obj; 137 endfunction 138 139 static function this_type get_type(); 140 factory f; 141 if(type_handle == null) begin 142 type_handle = new(); 143 f = factory::get(); 144 f.register(type_handle); 145 end 146 return type_handle; 147 endfunction 148 149 static function void set_override(wrapper_base 150 override_type); 151 factory f = factory::get(); 152 f.set_override(type_handle, override_type); 153 endfunction 154 155 endclass file: 04_OVM_mechanics/07_toy_factory/top.sv
Все функции в wrapper#() являются статическими, за исключением конструктора и create_object (). Мы можем выполнить его статические функции, не заботясь о том, был ли они явно создан экземпляр класса. Так как каждая обертка специализации уникальна. Это означает, что статическая переменная type_handle является уникальной и может быть использован в качестве прокси-сервера для обернутого типа (то есть, типа, передающегося в качестве параметра, который используется для специализации класса). Так как тип является уникальным, и большинство методов являются статическими, мы можем рассматривать типа больше как тип, а не объект.
Тип инициализируется статически. Это происходит в следующей строке:
static wrapper#(T) type_handle = get_type();
Функция get_type () вызывается во время статической инициализации, которая не только создает экземпляр оболочки, но и регистрирует его в фабрике. Для регистрации класса в фабрике, сначала специализируется обертка с типом объекта, который вы оборачиваете. Используйте typedef для специализации оболочки, как показано в следующем примере:
typedef wrapper#(some_type) type_id;
Это определение типа создает оболочку для SOME_TYPE типа, тип, производный от object_base.
Рисунок 4-8 иллюстрирует, как использовать фабрику с некоторыми классами A, B, и С, которые являются производными от family_base.
Чтобы зарегистрировать классы с помощью фабрики, каждый из них имеет typedef из обертки, параметризованной со своим типом. Ниже класс А. Класс B и C похожи. Каждый из них имеет typedef, которая специализируется обертку.
169 class A extends family_base; 170 171 typedef wrapper#(A) type_id; 172 173 virtual function void print(); 174 $display(“A”); 175 endfunction 176 endclass file: 04_OVM_mechanics/07_toy_factory/top.sv
206 function void run(); 207 208 f = factory::get(); 209 210 h = family_base::type_id::create(); 211 h.print(); 212 213 family_base::type_id::set_override(B::type_id::get_type()); 214 215 h = family_base::type_id::create(); 216 h.print(); 217 218 endfunction file: 04_OVM_mechanics/07_toy_factory/top.sv
При написании кода интенсивно используют оператор (::), он используется для обращения к статической функции в factory и в wrapper(). Сначала, мы получаем уникальный экземпляр структуры данных фабрики, затем создаем экземпляр объекта family_base. family_base :: type_id :: get_type() является статической функцией внутри специально обвернутого family_base. Мы проверяем, что экземпляр family_base создается путем вызова print(). Далее, определяем переопределение B для family_base. Опять же, мы создаем экземпляр family_base. На этот раз, так как переопределение сейчас находится на месте, вместо того, чтобы получить экземпляр family_base, мы получаем экземпляр B.
Наша фабрика не содержит все функциональные возможности фабрики, реализованной в OVM. Фабрика OVM обеспечивает отображение строк (имен) в типы. Это позволяет переопределить цепочки, в то время, как фабрика этого не делает. Например, если B переопределяет А и C переопределяет B, когда вы просите экземпляр А, вы получите экземпляр C. Фабрика OVM поддерживает два начальных базовых класса, ovm_object и ovm_component для зарегистрированных объектов, и метод create() для обоих, в то время, фабрика имеет только один первичный класс, object_base.
The OVM Factory API
В этом разделе мы рассмотрим более подробно API OVM фабрики. Он состоит из двух частей, type-based фабрики и string-based фабрики. В string-based фабрике запрашиваемый тип идентифицируются строкой имени. В type-based фабрике, тип идентифицируются по поддерживаемому типу(????). Один тип может быть зарегистрирован двумя способами. Методы выполнения трех этапов (регистрация, установка коррекции, и создание) немного отличаются в каждом случае. Сначала мы рассмотрим type-based фабрику. Вот компонент, называемый драйвер, который регистрирует себя на основе type-based фабрики.
49 class driver extends ovm_component; 50 51 typedef ovm_component_registry#(driver) type_id; 52 53 static function type_id get_type(); 54 return type_id::get(); 55 endfunction 56 57 function string get_type_name(); 58 return “driver”; 59 endfunction 60 61 function new(string name, ovm_component parent); 62 super.new(name, parent); 63 endfunction 64 65 endclass file: 04_OVM_mechanics/06_factory/top.sv
Есть две части для регистрации, поддержка typedef ovm_component_registry # () и supplying статической функции get_type ().typedef создает специализацию ovm_component_registry помощью компонент типа драйвер в качестве параметра. ovm_component_registry # () класс имеет статический инициализатор, который регистрирует, используя структуру фабрики. Таким образом, создание специализации, используя typedef , влечет за собой идентификацию класса по параметру, driver в этом случае, должен быть зарегистрирован в фабрике. Задание переопределения является простой причиной вызова функции set_override и передача переопределенного типа в качестве аргумента, как показано ниже:
105 driver::type_id::set_type_override(error_driver::get_type()); file: 04_OVM_mechanics/06_factory/top.sv
Эта строка выглядит довольно сложными, но на самом деле, это довольно просто. Давайте преобразуем выражение, чтобы полностью понять, что оно значит.
driver – запрашиваемый тип.
driver::type_id – тип обертки.
driver::type_id::set_type_override – the set override function in the specialized wrapper. This is a static function, which is why you need the :: scope operator to refer to it.
error_driver – переопределенный тип.
error_driver::get_type() – статическая функция, возвращаемая тип, поддерживаемый error_driver.
Для создания экземпляра класса с помощью фабрики, мы называем метод создания фабрики, как показано ниже:
102 d1 = driver::type_id::create(“d1”, this); file: 04_OVM_mechanics/06_factory/top.sv
Синтаксис :: работает так же, как и в предыдущем примере - driver относится к запрашиваемому типу, driver::type_id относится к типу специализированной обертки и driver::type_id::create относится к функции создать в специализированной обертке. Этот оператор создает экземпляр driver. Разница между вызовом Create () и new() является то, что create() обращаться к фабрике на наличие переопределенных типов. Если это так, Create () возвращает объект переопределенного типа.
Теперь давайте рассмотрим string-based фабрику. Механизм регистрации string-based фабрики опирается на определение типа как на основе типа фабрики, как показано ниже:
48 typedef ovm_component_registry#(driver, “driver”) type_id;
Единственная разница заключается в добавлении второго параметра параметризованной обертки. Он определяет имя типа, в этом случае driver. Когда ovm_component_registry # () специализируется с двумя параметрами, тип и имя, оболочка регистрирует в с type-based фабрике и на string-based фабрике. Чтобы установить переопределение, используя имя типа, необходимо вызвать API фабрики напрямую, а не использовать обертку API, как показано ниже:
106 factory.set_type_override_by_name(“driver”, 107 “error_driver”);
Это утверждение просто говорит, когда необходимо создать объект, имя типа которого driver, возвращать тип, имя которого error_driver вместо этого. Главная разница между использованием string-based и type-based фабрик в том, как создаются объекты. В type-based API, вы получаете доступ к фабрике через оболочку. В string-based API, вы вызываете фабрику напрямую. Тип возвращаемого ovm_factory :: create_component_by_name () является ovm_component. Сравните это с возвращаемым типом ovm_component_registry (T)::Create, который T. Чтобы получить доступ к предполагаемому типу возвращаемого объекта вам придется downcast его, как показано ниже:
99 assert($cast(d1, 100 factory.create_component_by_name(“driver”, 101 ““, 102 “d1”, 103 this)));
Вызов factory.create_component_by_name () возвращает объект типа ovm_component.$ $cast downcasts возвращается объект типа d1, который является driver. Поскольку тип аргумента create_component_by_name () является строкой, нет никакой проверки во время компиляции, что тип возвращаемого объекта приведен к требуемому типу. Поэтому важно, чтобы проверялся код возвращаемого значения $cast, для определения удалось ли приведение. Фабрика string-based API включает в себя create_object_by_name(). Она используется для создания объектов, производных от ovm_object. Вы должны вызвать $cast to downcast созданный объект по той же причине вы вызываете cast в компонентах, созданных string-based фабрики API.
String-Based or Type-Based?
type-based и string-based фабрики API, каждая имеет свои плюсы и минусы, но, как правило, мы рекомендуем вам использовать type-based фабрику. Она гораздо более надежная, защищена от ошибок в строке названия.
Иногда нет другого выбора; только на string-based фабрику можно использовать. Важным примером этого является, когда вы хотите указать название теста с командной строки. run_test () принимает необязательный строковый аргумент, test_name. Кроме того, эта задача проверяет OVM_TESTNAME аргумент командной строки. Если тестовое имя поддерживается командной строкой либо списком аргументов, run_test () вызывает string-based фабрику для создания экземпляра объекта.
string-based фабрика страдает от двух существенных недостатков. Один из них, уже упоминался, это то, что легко неправильно ввести имя типа, когда вы пишете код. Это может привести к ошибке testbench, потому что невозможно найти объект, или in a subtle bug where the wrong object is instantiated. Вторым недостатком является то, что это трудно, если нет возможности объявить параметризованный класс с использованием строки названия. Рассмотрим, например, параметризованный класс my_class.
class my_class #(type T=int) extends ovm_object; typedef my_class#(T) this_t; typedef ovm_object_registry#(this_t, “my_class#(T)”) type_id; endclass
Мы зарегистрировали его на string-based фабрики, используя имя my_class # (T). Кажется логичным. Теперь рассмотрим две специализации этого класса.
typedef my_class#(A) C1; typedef my_class#(B) C2;
Не существует удобного способа регистрации их фабрикой с использованием имен, которые являются уникальными для специализации. В наших классах в примере, С1 и С2 зарегистрированы с использованием имени my_class # (T). В структуре данных фабрики, это выглядит так, как будто вы пытаетесь зарегистрировать два объекта с одним и тем же именем, что является ошибкой. The remedy is to use the type-based factory API, which is not encumbered with strings
class my_class #(type T=int) extends ovm_object; typedef my_class#(T), this_t; typedef ovm_object_registry#(this_t) type_id; endclass
Оставляя второй аргумент в typedef из type_id, мы говорим фабрике зарегистрировать объект без имени, использовать только тип как ключ поиска. Теперь у специализаций будут свои собственные уникальные типы, и не будет ошибочно считаться, что это один и тот же объект в фабрике.
Недостатком использования type-based фабрики является то, что нет никакого способа поиска объекта строки. Это потому, что без второго аргумента в ovm_object_registry typedef, нет названия, под которым хранится объект. При первом использовании type-based фабрики, может ввести в замешательство, что нет доступного имени. Вы быстро обнаружите, что строка имени не является действительно необходимой. В тех случаях, когда необходимо имя, например, когда вы получаете имена объектов из пользовательского ввода, тогда можно использовать string-based фабрику.
Прекращение работы Testbench
Самым простым способом прекратить выполнение testbench ОВМ является вызов глобальной функции global_stop_request (). Это запросы, которые закрывают testbench. Если нет никаких причин для закрытия, то выполнение testbench будет приостановлено. global_stop_request () представитель ovm_top.stop_request (). Эти два формы семантически эквивалентны. Какие причины будут для того, чтобы не допустить прекращения работы? Каждая компонента имеет виртуальные задачу stop(). Когда вы вызываете global_stop_request (), эта задача вызывается для каждой компоненты, enable_stop_request устанавливается в 1. Когда все задачи остановки выполнились, testbench прекращает работу.
Задача остановки может быть использована для очистки информации, скажите DUT, чтобы прекратил выполнение, служил в качестве объекта завершения работы, или что-нибудь еще вы хотели бы сделать до завершения выполняемой фазы. Поскольку это задача, stop()может потреблять время. stop() задача может запретить выключение блокировкой. она может подождать выполнения некоторого условия или задержки на фиксированное время. stop()задача случит для прекращения(отмены) выполнения запроса. When stop() returns, it allows the request to be granted.
В следующем примере показано, как работает механизм остановки. Этот пример содержит из двух producers транзакций для отправки их к получателю через FIFO. Каждый producers работает независимо от других. Мы хотим убедиться, что оба producers закончили свою работу. Когда один заканчивает, другой продолжает до тех пор, пока он не выполнит свою работу до конца.
В build() функции для топ-уровня среды, в дополнение к экземплярам различных компонент, мы настроим различное число итераций для каждого producer. Так как число итераций для каждого из них разные, один producer завершится перед другими.
127 function void build(); 128 set_config_int(“producer1”, “iterations”, 5); 129 set_config_int(“producer2”, “iterations”, 9); 130 p1 = new(“producer1”, this); 131 p2 = new(“producer2”, this); 132 c = new(“consumer”, this); 133 f = new(“fifo”, this); 134 endfunction file: 04_OVM_mechanics/09_shutdown/top.sv
Мы хотим, чтобы testbench завершил выполнение в определенном порядке, когда все необходимая работа будет сделана, но мы не хотим закрывать преждевременно, когда первый producer завершит работу. Мы используем механизм остановки для достижения этой цели. Каждый producer имеет задачу остановки, которая ждет, пока done станет 1.
77 task stop(string ph_name); 78 ovm_report_info(“stop”, “initating stop”); 79 wait(done == 1); 80 ovm_report_info(“stop”, “shutting down...”); 81 endtask file: 04_OVM_mechanics/09_shutdown/top.sv
Ph_name аргумент содержит имя фазs, в которой вызывается stop() . Хотя по умолчанию есть только на task-based фаза, run() есть возможность произвольно добавить больше task-based фаз (и функций на основе фаз, тоже). Остановка механизм запроса работает во всех task-based фазах, а вызов global_stop_request () работает в текущей task-based фазе. Это приводит к вызову stop(). Так как только одна задача с именем stop возможна в каждой компоненте, ph_name аргумент определяет фазу, в которой она была вызвана. Вы можете использовать это для изменения поведения stop() на основе имени task-based фазы.
Для остановки механизма запроса, мы должны отменить его, установив enable_stop_interrupt в 1. Мы делаем это в конструкторе компоненты.
42 function new(string name, ovm_component p = null); 43 super.new(name,p); 44 iterations = 10; // default value 45 done = 0; 46 enable_stop_interrupt = 1; 47 endfunction
Основной цикл producer прост. Каждая итерация цикла генерирует случайное число и передает его через выходной порт к consumer. В следующем примере, когда цикл завершается, мы устанавливаем done в 1, который запускает задачу stop.
66 for(int i = 0; i < iterations; i++) begin 67 randval = $random % 100; 68 $sformat(s, “sending %4d”, randval); 69 ovm_report_info(“producer”, s); 70 put_port.put(randval); 71 end 72 73 done = 1; file: 04_OVM_mechanics/09_shutdown/top.sv
Заключительная часть - задачи run верхнего уровня среды:
142 task run(); 143 ovm_report_info(“run”, “start”); 144 global_stop_request(); 145 endtask file: 04_OVM_mechanics/09_shutdown/top.sv
После запуска, задача немедленно вызывает global_stop_request (), которая приводит к вызову задачи stop() в producers (потому что enable_stop_interrupt установлен в 1 в каждой). В свою очередь, каждый producer блокируется до соответствующего значения флага done. Когда producer с наименьшими числом итераций заканчивает свое выполнение, о it triggers its local done flag and its stop task returns. However, because there are outstanding blocked stop tasks, the simulation continues. Только, когда все задачи stop будут выполнены моделирование прекращается.
Timeout
Вполне возможно, что моделирование может остановиться, когда ошибка в stop задаче препятствует возвращению значения, заблокированный вызов никогда не разблокируется или бесконечный цикл никогда не остановится. Для предотвращения моделирования от зависания на неопределенный срок, OVM предоставляет два timeout механизма. Однин для задач фаз, а другой для stop задачи. ovm_root содержит две переменные, phase_timeout и stop_timeout. Их типа это Verilog тип tim, и их значения могут быть установлены set_global_timeout и set_global_stop_timeout. Значение по умолчанию переменных 0, что означает тайм-аут отключен.
Когда задача на основе фаз выполняется, такие как Run (), и имеет phase_timeout был установлен в значение больше нуля, то отдельный процесс сторожевой породил, который просто ждет, пока тайм-аута. fork/join конструкция используется для создания этих задач, поэтому, если run задачи завершились до тайм-аута, то тайм-аут игнорируется. С другой стороны, если тайм-аут истекает первым, то он будет инициировать отключение. Фрагмент кода в ovm_root :: run_global_phase () управляющий задачей run и выключением заключается в следующем:
fork : task_based_phase m_stop_process(); begin m_do_phase_all(this,m_curr_phase); wait fork; end #timeout ovm_report_error("TIMOUT", $psprintf("Watchdog timeout of '%0t' expired.", timeout)); join_any disable task_based_phase;
fork состоит из трех процессов, в том числе m_stop_process (), который управляет остановкой запросов; m_do_phase_all (), который приводит к выполнению всех задач run выполняться параллельно, и тайм-аут. Выражение disable после join_any вызывает оставшиеся процессы будут убиты. Так что, если тайм-аут истекает первым, то затем процессы stop и run задача будут убиты. Если run задач завершится первой, то процессы stop и timeout будут убиты. Наконец, если global_stop_request () вызывается, вызывается stop задачи, все они выполняются до конца, а затем stop процесс завершится первым и run задача и timeout будут убиты.