OVM/Basic OVM/Session5 - Connecting Components
Здравствуйте, я Джон Эйнли из компании Дулос. Это пятое занятие по основам OVM. Его тема - соединение компонентов. Мы познакомимся с тем, как создавать несколько компонентов OVM в окружении верификации и как собирать их воедино. Также мы подробнее рассмотрим архитектуру отдельного компонента.
На предыдущем занятии я говорил, что типичный компонент OVM состоит из контроллера, драйвера и монитора. Идея в том, чтобы использовать компонент как стандартный строительный блок в окружении верификации OVM. Такая конфигурация компонентов называется агентом. Эта структура не является единственно возможной. Вы не обязаны строить окружение верификации исключительно из агентов с такой структурой, однако это неплохая отправная точка, позволяющая обеспечить стандартизацию и согласованность между компонентами верификации из различных источников. Поэтому при обсуждении создания и соединения компонентов будет разумно использовать описанную структуру агентов в качестве примера, отправной точки. И именно так мы сейчас и поступим.
Я уже неоднократно упоминал компоненты OVM, и теперь самое время взглянуть на формальную иерархию классов OVM. Здесь мы вступаем на территорию объектно-ориентированного программирования. На слайде представлена диаграмма иерархии классов в обозначениях, немного напоминающих язык UML. Классы, изображенные ниже, расширяют, или наследуют тем, что изображены выше. Внутри широкой рамки в нижней части диаграммы показаны все виды компонентов OVM. Они применяются для описания структуры. В OVM имеется много классов, причем все они наследуют единственному базовому классу ovm_component, который в свою очередь является производным от класса ovm_report_object, а тот - от класса ovm_object, с которым мы встречались на предыдущем занятии. В нижней части диаграммы мы видим различные компоненты OVM, в частности ovm_env для уже знакомого нам ovm_test, а также такие компоненты, как ovm_agent и ovm_driver. Позже мы узнаем, что некоторые из компонентов полностью взаимозаменяемы и отличаются только названиями. Но есть также компоненты с уникальными свойствами, присущими только им. Во многих случаях у вас есть выбор. Например, можно воспользоваться как ovm_env, так и ovm component. А это означает, что выбор того или иного компонента OVM - вопрос стиля кодирования и единообразия, а не технической необходимости. Тем не менее, на данном занятии я буду выбирать подходящий тип компонента исходя из той роли, которую он играет.
Стало быть, в данном случае мы возьмем компонент ovm_agent. Итак, создадим определенный пользователем компонент верификации, расширив базовый класс ovm_agent. Однако подчеркну еще раз: я мог бы расширить ovm_component, или даже ovm_monitor или ovm_env, и в данном случае получился бы точно такой же результат.
Итак, в строке 2 мы регистрируем my_agent как ovm_component. Это стандартный код, который следует включать обязательно. Впоследствии агент будет создавать экземпляры контроллера и драйвера, поэтому мы объявляем в классе два описателя: my_sequencer_h и my_driver_h.
Далее следует метод new, конструктор класса, и стандартные методы, build и connect. С методом build мы уже встречались ранее, а метод connect для нас внове. Он служит для соединения дочерних компонентов, и чуть позже я подробнее объясню, как он работает.
Однако начнем с расширения метода build. Сначала метод build, как обычно, вызывает метод базового класса super.build,а затем создает экземпляры контроллера и драйвера, с помощью волшебного заклинания type_id::create. Передаваемые методу create аргументы очень важны. Первый аргумент, как и раньше, это имя экземпляра,которое должно совпадать с именем переменной. Второй аргумент – родитель, мы всегда будем использовать в этом качестве "this", встроенную в SystemVerilog переменную, которая обозначает текущий объект, с которым мы работаем. В объектно-ориентированной терминологии метод create называется фабричным. Фабричные методы - это один из так называемых паттернов объектно-ориентированного программирования, то есть заведомо хороших способов решить определенную задачу.Конкретно, фабричный метод позволяет создать объект и в то же время переопределить тип объекта, создаваемого где-то в другом месте. На первый взгляд кажется, что здесь мы создаем объекты типа my_sequencer и my_driver, и по умолчанию так оно и есть. Однако имеется возможность переопределить типы компонентов, создаваемых этими фабричными методами. Так, в каком-то конкретном тесте, не изменяя исходный код данного конкретного агента ни на йоту, мы могли бы переопределить поведение фабричных методов, заставив их создавать иные компоненты, последовательности или драйверы. С помощью этого механизма мы можем настраивать поведение того или иного компонента верификации в конкретном тесте: А. не изменяя ни единой строки кода и Б. не заставляя автора компонента верификации предвидеть все возможные модификации. Ему достаточно просто воспользоваться фабричным методом. Так что это весьма полезный механизм.
Мы рассмотрели метод build, применяемый для конструирования компонентов в окружении верификации. Далее идет метод connect, который служит для соединения компонентов более низкого уровня между собой. В данном случае соединяются драйвер и контроллер. Как видите, метод connect агента вызывает метод connect драйвера, чтобы соединить оба компонента нижнего уровня. Сам код соединения – это обычный исполняемый код. Технически его можно было бы включить в метод build, и все работало бы точно так же. Однако стилистически правильнее и настоятельно рекомендуется размещать код соединения компонентов нижнего уровня в теле метода connect, хотя бы из соображений единообразия.
Взглянув на код метода connect более пристально, вы увидите ссылки на два выделенных жирным шрифтом компонента:
sequence_item_port и sequence_item_export.
Понятия порта и экспортера - выходцы из мира моделирования на уровне транзакций. Они заимствованы из стандарта System C TLM. В данном контексте можно считать, что порты и экспортеры – это просто описатели дочерних компонентов, необходимые для того, чтобы их можно было связать. Итак, чтобы связать драйвер с контроллером, мы берем описатель драйвера, sequence_item_port, описатель контроллера, sequence_item_export, и соединяем их, устанавливая соединение между контролем и драйвером. Можете считать, что эти порты уровня транзакций - своеобразные вариации на тему портов в системах Verilog или VHDL. На самом деле, они не полностью эквивалентны портам VHDL или Verilog, но, если смотреть с высоты птичьего полета, то выполняют они ту же функцию соединения между собой деталей внутреннего устройства отдельных компонентов.
Итак, я познакомил вас с методами build и connect, которые в OVM называются фазовыми методами. А теперь я опишу сами фазы OVM более формально. Идея в том, что каждый компонент в иерархии верификации должен сконструировать свои дочерние компоненты и должен обладать неким основным поведением, выполняемым в ходе моделирования. Кроме того, он может выполнять какие-то служебные функции: открывать файлы, выводить результаты и так далее. Поэтому действия всех компонентов верификации в процессе моделирования необходимо координировать. Именно для этого и предназначены стандартные фазы OVM. Каждый компонент выполняет стандартный набор фаз. Этот механизм позволяет координировать поведение всех компонентов, входящих в иерархию верификации. Давайте рассмотрим пример. Мы создадим пользовательский класс my_component, расширяющий базовый класс ovm_component. Как вы, наверное, помните, класс ovm_component является базовым для целого семейства различных классов компонентов. Обычно выбирается какой-то конкретный класс, скажем ovm_agent, ovm_driver или ovm_monitor, который отражает ваше намерение. Но технически вполне допустимо создавать пользовательский класс, расширяя ovm_component.
В данном примере мы так и поступим. Кое-что из показанного в этом примере мы уже видели раньше. Начинаем с метода new. Метод new, строго говоря, не является фазой, это просто конструктор, часть языка SystemVerilog. Но для удобства можете считать, что это фаза 0. Как правило, в OVM метод new не должен делать ничего, кроме вызова метода new базового класса.
Далее идет метод build, который мы начали писать ранее. Он создает дочерние компоненты. Как я уже отмечал, методы new и build должны вызывать соответственно методы new и build базового класса. Таким образом, оба метода new и build вызываются в порядке строго сверху вниз, то есть начиная с уровня окружения верификации вглубь. Пока вы еще не освоились с OVM более основательно, очень важно помнить, что компоненты в окружении верификации строятся сверху вниз. Напротив, все остальные фазовые методы вызываются снизу вверх.
Следующей стандартной фазой (необязательной) является фаза соединения, на которой соединяются порты и экспортеры дочерних компонентов, а точнее их виртуальные интерфейсы. Методы connect вызываются снизу вверх, начиная с компонентов верификации самого нижнего уровня.
Затем идет стандартная фаза start_of_simulation, которая, как следует из названия, вызывается в начале моделирования. Название этой фазы заимствовано из системы System C. В System C имеется аналогичный обратный вызов, выполняемый в начале моделирования, и называется он точно так же: start_of_simulation. Обычно на этой фазе производятся такие служебные операции, как открытие файлов для чтения или записи в процессе моделирования.
Затем начинается само моделирование, оно выполняется на фазе run, и, как мы видели раньше, run - это единственная задача. В Verilog задача, конечно, может быть продолжительной, выполнять обработчики событий, расходуя на это модельное время. За фазой run следуют еще две фазы, предназначенные для выполнения служебных операций в самом конце моделирования.
На слайде показана фаза report, на которой можно вывести отчет о результатах после того как собственно моделирование завершилось. Таким образом, фазовые методы позволяют координировать и синхронизировать действия разнообразных компонентов, находящихся на разных уровнях иерархии верификации.
Резюмируем все, что мы узнали об анатомии компонента OVM. Компания Mentor настоятельно рекомендует при написании компонента OVM располагать его элементы в порядке, показанном на данном слайде. Так, во второй строке мы всегда регистрируем компонент в библиотеке классов OVM. Затем объявляются все внешние интерфейсы, включая как виртуальные интерфейсы, так и порты и экспортеры, если их необходимо объявить явно. Далее идут объявления описателей внутренних компонентов, в данном случае контроллера и драйвера. И потом все стандартные фазовые методы в надлежащем порядке: сначала конструктор, за ним собственно фазы, build, connect и так далее.
Итак, на этом занятии я объяснил, как создавать экземпляры нескольких компонентов в иерархии верификации. Мы видели, что использование фабричных методов позволяет создавать компоненты так, чтобы в отдельных тестах один компонент можно было подменить другим. Мы видели, что стандартные фазы используются для координации действий нескольких компонентов. А также что OVM предписывает способ структурирования компонентов верификации с тем, чтобы повысить степень их повторного использования, а также поддержать единый стиль кодирования. Мы познакомились с типичной структурой: имеется так называемый агент, который состоит из контроллера, драйвера и монитора, а окружение верификации обычно содержит несколько агентов. Как правило, один агент представляет один коммуникационный канал, шину или порт в тестируемом устройстве. Например, если в тестируемом устройстве имеется шина Amber, то в качестве ее формирователя мог бы выступать агент Amber. Если имеется USB-соединение, то для USB-порта заводится отдельный агент. И так далее. Окружение верификации, как правило, содержит несколько агентов, по одному для каждого из основных интерфейсов тестируемого устройства. А, помимо них, существуют другие компоненты верификации, задача которых - координировать и суммировать действия всех агентов.