«Бог не меняет того, что (происходит) с людьми, пока они сами не изменят своих помыслов.» Коран, Сура 12:13

OVM/OVM методология/Механика OVM

Материал из Wiki

Перейти к: навигация, поиск
Проект Диплом

Вебинары
Литература

* OVM *

Содержание


Механика OVM

Библиотека OVM предоставляет много возможностей для построения testbenches. В этой главе мы ознакомимся с наиболее важными возможностями, которые вы будете использовать почти во всех ваши testbenches.

Компоненты и иерархия

Первичная структура для создания элементов testbench является компонента. Компонента в OVM – это аналогия модулю в Verilog. Компонента OVM сконструирована на основе класса, который обеспечивает ему различные характеристики, чем модуль Verilog и имеет различные использования импликаций. Среди таких характеристик является то, что классы создаются во время выполнения, а не во время разработки, как модули. Таким образом, OVM отвечает за создание компонента и сборки их в иерархии.

69.png [svg]

Рисунок 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.

70.png

Метод connect портов и exports используется для связывания их вместе.

initiator_port.connect(target.export)

Этот метод создает ассоциацию, или связывание, между port и export так, что port может вызывать(запускать) задачи и функции из export. Для успешного соединения, типы port и export должны совпадать. То есть, типы интерфейсов должны быть одинаковыми, и тип объекта, передающегося в интерфейс должны быть одинаковыми.

Подключение через Иерархию

Как и контактами в RTL дизайне, нам нужно подключиться к портам TLM через иерархические границы. Рисунок 4-3 использует простой дизайн, чтобы показать, как сделать эти соединения. Этот проект содержит исходную компоненту с двумя port(выходами), которые в конечном итоге подключаются к двум входам(export). Для осуществления подключения между этими компонентами, мы должны расширить port и export на уровень выше в иерархии.

71.png

Компонент 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
После того как порты в source_wrapper были созданы, они подключаются к портам lower-level компоненты source с помощью метода Connect. Видимость портов на более высоком уровне иерархии достигается таким же образом, как мы видим в sinker.
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 показывает поток управления через иерархическую систему.

72.png

Порт – объект вызова и экспорт - объект вызванной функции или задачи. Вы можете воспринимать порты, как вызов экспорта. Так, в env, мы вызываем connect на put_ports с put_exports в качестве аргументов. Для port-to- port и export-to-export иерархии соединений, вызывающий будет чуть менее очевиден. Если вызов осуществляется со стороны порта, вы можете считать, что вызов lowest-level порта в иерархии является вызовом методов интерфейса upper-level порта. Аналогично, если экспорт объект вызова, вы можете считать upper-level экспорт вызовом lower-level export. В таблице ниже приведены возможные типы подключения:

73.png

Примечание для пользователей 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 реализуются разработчиком компоненты, который обеспечивает соответствующую функциональность. Фаза контроллера гарантирует, что фазы выполняются в надлежащем порядке. Набор стандартных фаз показано в следующей таблице:

74.png

75.png


Каждая фаза имеет определенную цель. разработчики Компонент должны заботиться о том, чтобы функциональность реализованная в каждой фазе 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, которые организованы иерархически. Каждая компонента содержит таблицу конфигураций элементов конфигурации, так как компоненты организованы в качестве дерева, каждый элемент в базе данных может быть уникально размещен по расположению компоненты и имени элемента конфигурации


76.png


Класс ovm_component содержит два набора методов для конфигурации размещения элементов в базу данных и потом для их получения. Это set_config_ * и * get_config_. В таблице ниже приведены оба набора.

77.png

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,

78.png


пусть в элементе 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 основана на структуре данных, которая отображает необходимые типы в переопределенные. По сути, организация представляет собой ассоциативный массив типов, ключом в котором является также тип. При регистрации типа при помощи фабрики, его переопределенным типом является он сам. Таким образом, по умолчанию, когда вы запрашиваете объект этого типа, вы получаете только тип. Фабрика также предоставляет средства для замены переопределенных типов другими типами, чтобы вы могли получить переопределенные типы, которые отличаются от зарегистрированных типов.

79.png

Следующий пример является сильно упрощенной фабрикой, которая показывает, как 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.

80.png


Чтобы зарегистрировать классы с помощью фабрики, каждый из них имеет 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 закончили свою работу. Когда один заканчивает, другой продолжает до тех пор, пока он не выполнит свою работу до конца.

81.png


В 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 будут убиты.