Регистровая модель UVM (UVM register layer) определяет некоторые базовые классы, которые определяют абстрактные операции чтения/записи в регистры и ячейки памяти в DUT (design under test - тестируемый компонент). Использование регистровой модели позволяет существенно упростить и ускорить создание моделей регистровых файлов и блоков памяти, написание тестовых последовательностей для проверки правильности работы регистров в DUT, при этом тестовые последовательности не зависят от используемых физических интерфейсов доступа к регистрам (APB, AXI и др.), что значительно упрощает их повторное использование в новых проектах.
Регистровая модель обычно состоит из набора блоков (классов), иерархия которых повторяет иерархию блоков и регистров в DUT. Блоки регистровой модели могут содержать регистры, регистровые файлы и ячейки памяти.
Классы UVM регистровой модели не применимы “как есть”. Эти классы только обеспечивают некоторые основные возможности. Они должны быть настроены с помощью классов наследников, чтобы обеспечить абстрактное представление, которое соответствует настоящим регистрам и ячейкам памяти в проекте. Так как в проекте большое число регистров и их описание с помощью классов регистровой модели вручную очень трудоёмко и однообразно, то для этого как правило используют автоматизацию с использованием специальных программ. Генератор регистровой модели работает на основе спецификации регистров и ячеек памяти в проекте, заданной в удобном для человека формате, и, таким образом, в состоянии обеспечить точную, правильно сконструированную регистровую модель. Генераторы регистровых моделей используют свой формат описания регистров, при этом нет единого стандарта, каждый производитель программы генератора использует свой собственный формат. Генераторы регистровых моделей выходят за рамки библиотеки классов UVM и далее не рассматриваются.
На рисунке ниже показана диаграмма UVM классов регистровой модели.
Внутри тестового окружения регистровая модель используется, как зеркало, которое отображает внутри окружения состояния регистров тестируемого блока. Также регистровая модель предоставляет интерфейсы доступа к регистрам тестируемого блока как через физический интерфейс, с использованием транзакций чтения-записи (frontdoor доступ), так и напрямую к регистрам тестируемого блока, через иерархический путь внутри тестового окружения (backdoor доступ).
Таким образом, компоненты тестового окружения или сиквенсы, которые используют регистровую модель, должны оперировать с объектом регистровой модели, указатель, на который рекомендуется передавать через объект конфигурации с помощью базы конфигурации.
Для выполнения прямого доступа к регистрам цифровой модели тестируемого блока, для регистровой модели задается строковый параметр, который содержит прямой путь в иерархии RTL проекта к соответствующим регистрам или ячейкам памяти. Во время процесса моделирования симуляторы позволяют записывать и считывать значения этих регистров, используя этот путь, такой тип доступа называется backdoor-доступ к регистрам. Значения регистров хранящиеся в самой регистровой модели обновляются автоматически по завершению backdoor-доступа в регистры цифрового блока. Выполняется это посредством вызова метода predict(), который и обновляет значения в регистрах модели.
Регистровая модель поддерживает доступ к регистрам через физический интерфейс доступа к регистрам и ячейкам памяти в DUT, т.е. посредством выполнения транзакций чтения или записи на соответствующем физическом интерфейсе, такой тип доступа называют frontdoor, так как именно так происходит доступ к регистрам DUT в реальной цифровой системе. При его использовании методы регистровой модели генерирую регистровые транзакции чтения или записи, которые конвертируются в адаптере в транзакции для агента, управляющего соответствующим физическим интерфейсом, и отправляются на сиквенсер этого агента, а также в случае с операцией чтения, ответ, полученный от DUT, конвертируется обратно в регистровые транзакции. Такой подход автоматической конвертации при frontdoor-доступе, позволяет регистровой модели автоматически обновлять значения, проверять их корректность и предсказывать значения, которые должны быть вычитаны. Регистровую модель можно использовать в тестовом окружении в трех режимах предсказания получаемых значений:
Автоматическое предсказание или неявное предсказание - это самый простой алгоритм предсказания (рис 7.1). В этом режиме значения регистров, автоматически обновляются через вызов метода predict(), в момент вызова генерации транзакции записи с помощью вызова встроенных методов регистровой модели, к примеру write. А при чтении регистров, ожидается получить записанное ранее значение, с учетом типа доступа к регистру. Например, регистр может быть доступен только для чтения, то после транзакции записи, значение в регистре не должно измениться. Тип доступа к регистру настраивается при создании регистровой модели.
Для включения автоматического обновления регистровой модели при выполнении операции записи необходимо вызвать функцию set_auto_predict со значением 1. Например:
regmodel.default_map.set_auto_predict(1);
Эта настройка выполняется в фазе connect_phase класса UVM окружения. При использовании этого режима предсказания, регистровая модель "считает, что" она знает, чему равны значения регистров в DUT, потому что этот режим предсказания не собирает транзакции к регистрам проекта, которые происходят не от регистровой модели. Такое неявное предсказание хорошо подходит для сиквенсов, потому что просто выполняются операции чтения/записи, а контроль за правильностью читаемых данных происходит автоматически в регистровой модели без использования scoreboard.
Таким образом автоматическое предсказание самый простой режим использования регистровой модели в тестовом окружении, но недостаток такого метода в том что, он работает только, если запись и чтение инициируются через вызов методов read/write регистровой модели, внутри которых вызывается метод predict(). А бывают случаи, когда тестируемый блок является частью системы и сиквенсы запускаются для этой системы, в этом случает только есть возможность с помощью монитора отслеживать регистровые транзакции на физическом интерфейсе DUT, которые являются результатом работы другого RTL-блока. В этом случае в режиме автоматического предсказания значения регистровой модели не будут обновляться обновятся.
В режиме автоматического предсказания, если при выполнении регистровой транзакции записи будет получен статус UVM_NOT_OK от агента физического интерфейса, то ожидаемое значение также не будет обновлено.
(http://uvm-stage.mentor.com//w/images/thumb/a/af/Reg_auto_predict.gif/600px-Reg_auto_predict.gif)
Рис. 7.1. Структура окружения при использовании регистровой модели с неявным предсказанием
Явное предсказание является рекомендуемым режимом по умолчанию. В данном режиме генерация транзакций записи/чтения регистров DUT инициируется в сиквенсе с помощью соответствующих методов write/read регистровой модели, но при этом автоматическое обновление значений регистров в модели не происходит (функция автоматического предсказания отключается set_auto_predict(0)). Для обновления регистровой модели используется компонент окружения регистровый предиктор, который наследуется от класса uvm_reg_predictor. Предиктор параметризуется типом принимаемой транзакции, имеет порт bus_in, через который можно получать транзакции чтения/записи регистров или ячеек памяти DUT, собранные монитором на физическом интерфейсе DUT, и после вызывать метод predict(), который будет обновлять значения хранящиеся в регистровой модели. Компонент предиктор использует регистровый адаптер для конвертации транзакций, полученных от монитора агента физического интерфейса, в регистровые транзакции.
На рисунке ниже представлена общая схема взаимодействия компонентов UVM окружения при использовании регистровой модели в режиме явного предсказания.
(http://uvm-stage.mentor.com//w/images/thumb/d/de/Reg_explicit_prediction.gif/600px-Reg_explicit_prediction.gif)
Главное преимущество использования явного предсказания в том что, обновление значений происходит только тогда когда транзакция произошла на физическом интерфейсе DUT, в отличие от неявного, в котором как только транзакция готова к выполнению, значения регистровой модели обновляются. Также такой подход делает тестовое окружение пригодным для повторного использования без изменений кода, при моделировании системы на более высоком уровне иерархии (т.е. при переходе от верификации на уровне блоков к верификации на уровне системы), так как отсутствует привязка к запускаемым сиквенсам, достаточно только подключить к физическому интерфейсу DUT соответствующий монитор агента, который будет собирать транзакции записи/чтения регистров.
Если повторно использовать тестовое окружение блочного уровня с регистровой моделью в системах более высокого уровня, то окружение должно поддерживать опции конфигурации для режима пассивного предсказания, о котором будет сказано ниже.
Так же стоит обратить внимание, что если используется явное предсказание, то статус возвращаемый методом predict() игнорируется. Это означает, что если вернулся статус выполнения предсказания UVM_NOT_OK, то нужно отфильтровать самостоятельно транзакции, которые посылаются в предиктор, иначе значения регистров обновятся в любом случае. Такая проверка выполнятся в мониторе перед отправкой в порт bus_in или в самом предикторе после получения траназкции.
В режиме пассивного предсказания регистровая модель не принимает участия в формировании каких-либо воздействий на физическом интерфейсе доступа к регистрам DUT, но обновляет значения полей регистров, если монитор собрал транзакцию на шине. Т.е. в этом режиме не используется возможность генерации тестовых последовательностей с помощью методов read/write регистровой модели. На рисунке ниже показана схема взаимодействия компонентов тестового окружения при использовании регистровой модели в пассивном режиме.
(http://uvm-stage.mentor.com//w/images/thumb/c/c2/Passive_prediction.gif/600px-Passive_prediction.gif)
Для интеграции регистровой модели в UVM окружение необходимо выполнить следующие шаги.
Классы регистровой модели имеют следующую иерархическую структуру уровней, описанную в следующей таблице.
(Рисунки в таблице взяты со страницы https://verificationacademy.com/cookbook/registers/modelstructure)
Уровень | Описание | Схема |
Поля | Биты сгруппированы по функциональности в поля | |
Регистр | Группирует поля и биты с заданным смещением (адрес внутри регистра) | |
Память (Memory) | Представляет собой блок памяти с заданным диапазоном адресов | |
Блок (Block) | Группирует регистры, если это уровень, описывающий аппаратный блок. Или группирует другие блоки, для которых заданны одна или несколько карт адресов. Может также содержать память. | |
Карта адресов (Map) | Имя карты адресов, которая задает смещение и позволяет вычислить адрес регистра по его имени. Также содержит указатель на сиквенсер, который будет работать с этой картой адресов. |
Самый нижний уровень регистровой модели это поле регистра. Как самый нижний уровень, поля могут соответствовать битам или группам бит регистра. Каждое поле представляет собой объект класса uvm_reg_field. Поля размещаются внутри класса, описывающего регистр uvm_reg, и конфигурируются посредством вызова метода configure().
function void configure( uvm_reg parent, //регистр, которому принадлежит поле bit is_rand, // Можно ли рандомизировать // каждому биту внутри поля |
Когда поля созданы, они получают строковое имя, которое позволяет получить доступ к ним.
Регистрами считаются объекты класса, наследуемого от uvm_reg. Общие параметры и настройки задаются через конструктор класса.
// конструктор класса uvm_reg function new ( string name="", // Имя регистра |
Класс, описывающий регистры содержит метод build, который используется для создания и конфигурации полей регистра. Стоит отметить что регистровая модель это наследник класса uvm_object, и так как это не uvm_component, то вызов метода build не происходит автоматически в build-фазе. Например
// регистр ctrl //------------------------------------------------------------------ // в конструкторе задаем имя //------------------------------------------------------------------ |
Когда регистр добавляют в блок, это выполняется аналогично добавлению полей в регистр. Сначала регистры должны быть созданы и сконфигурированы. Прототип функции конфигурации представлен ниже
function void configure ( uvm_reg_block blk_parent, // Задается блок, который содержит регистр // позволяет задать регистровый файл, которому принадлежит регистр // доступа к регистрам цифрового блока, если все биты регистра имеют // одинаковый путь в иерархии проекта в базе симулятора. |
Регистровой памятью считаются объекты класса, наследуемого от класса uvm_mem. Внутри регистровой модели uvm_mem обрабатывается как регион, к которому есть доступ. В отличие от регистров, значения ячеек памяти в регистровой модели не сохраняются, с целью экономии оперативной памяти на рабочих станциях. Диапазон, к которому есть доступ, задается в конструкторе.
function new ( string name, // Имя блока памяти // сбора покрытия |
Пример реализации класса памяти размером 32'h2000:
// Массив памяти размером 32'h2000; |
Карта адресов регистровой модели служит для двух целей. Карта предоставляет информацию о смещених содержащихся в ней регистров, памяти или блоков. А также карта используется для того, чтобы определить агент, с которым будет работать регистровая модель, и то, где будут выполняться встроенные базовые последовательности, проверяющие регистровую модель, до тех пор пока модель не будет встроена в систему более высокого уровня.
(http://uvm-stage.mentor.com//w/images/8/8d/Reg_map_content.gif)
Содержимое карты адресов регистровой модели
Для добавления регистров или памяти в карту используются методы add_reg() или add_mem() соответственно, формат которых показан ниже.
function void add_reg ( uvm_reg rg, // Указатель на объект регистра для добавления // не будет доступа через карту // адресов и придется определить, как будет происходить к нему // frontdoor-доступ, т.е. задать следующий параметр // будет осуществлять frontdoor доступ к регистру (к примеру другая // карта адресов) |
function void add_mem ( uvm_mem mem, // Указатель на объект памяти // будет доступа через карту адресов и придется определить, как будет // происходить к ней frontdoor-доступ, т.е. задать следующий параметр // осуществлять frontdoor-доступ к памяти (к примеру другая карта // адресов) |
Внутри регистрового блока могут размещаться несколько карт адресов, каждая из которых может работать с различными агентами и задавать различные смещения.
Следующий уровень иерархии в UVM регистровой модели это регистровый блок. Этот класс может группировать регистры и карты памяти для моделей уровня IP-цифрового блока, а также может группировать другие регистровые блоки. Для того, чтобы определять адреса регистров и смещение адреса внутри блока используется объект карты адресов регистровой модели и для него задается базовый адрес, от которого будут вычисляться смещения. Объект карты адресов должен создаваться внутри блока с помощью метода create_map.
function uvm_reg_map create_map( string name, // Имя карты адресов // (в байтах ) // Пример использования метода create_map: |
В методе create_map параметр n_bytes задает ширину слова (шины), с которой будет оперировать карта адресов. Если ширина регистров больше чем ширина слов, с которыми работает карта, то нужно более чем один доступ к шине для записи/чтения регистра через шину.
Параметр byte_addressing определяет, как адрес будет меняться при последовательном доступе.
Например n_bytes=4 и byte_addressing=0, и мы осуществляем доступ к регистру шириной 64 бита, по нулевому смещению. В результате получится две транзакции на шине, с адресом 0 и с адресом 1. Если бы byte_addressing=1, то в этом случае было бы также две транзакции на шине с адресом 0 и 4.
В UVM 1.1 и выше значение по умолчанию byte_addressing равно 1.
Первая карта адресов регистровой модели, которая создается внутри блока назначается картой по умолчанию (default_map).
Далее приведен пример кода блока SPI регистровой модели.
class spi_reg_block extends uvm_reg_block; //объявляем карты адресов
rxtx0_reg.configure(this, null, ""); // Конфигурируем rxtx0_reg.build(); // Вызываем метод создания полей регистра |
Вызов метода lock_model() в конце функции build является завершающей стадией создания блока регистровой модели, которая гарантирует, что модель не будет изменена. После вызова этого метода невозможно будет добавить какие либо регистры или память в карту адресов. Снять блокировку после выполнения lock_model() нет возможности.
Приведенный выше пример можно использовать при верификации законченных блоков, но если блоки встраиваются в большую систему, то регистровые блоки можно комбинировать с другими блоками. Далее приведен пример как это реализуется.
package pss_reg_pkg; |
Рассмотрим как выглядит модель регистра в UVM регистровой модели. Класс поля uvm_reg_field является наименьшим (в иерархии) слоем в модели регистра, который представляет отдельные биты регистра. Класс uvm_reg_field использует некоторые свойства для сохранения множества значений поля регистра:
Таким образом, модель поля регистра включает четыре атрибута, что показано на следующем рисунке.
(http://i2.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_reg_field_properties.png) |
Рис. Атрибуты класса uvm_reg_field |
Обратим внимание, что среди этих свойств только свойство value является public. Остальные свойства являются локальными, таким образом мы не можем к ним обратиться напрямую вне данного класса. Далее рассматриваются методы, позволяющие изменять значения данных свойств.
Первое, что делается после создания класса uvm_reg_field - это его настройка. Ниже приведен пример конфигурации поля mode, которое имеет тип доступа на запись и чтение ("RW"), размер 8 бит и равно нулю после сброса.
mode = uvm_reg_field::type_id::create( "mode" ); mode.configure( .parent ( this ), .size ( 8 ), .lsb_pos ( 0 ), .access ( "RW" ), .volatile ( 0 ), .reset ( 0 ), .has_reset ( 1 ), .is_rand ( 1 ), .individually_accessible( 0 ) ); |
Если аргумент has_reset равен 1, то значение аргумента reset задаётся в свойстве m_reset как значение "HARD"-сброса. Если же значение has_reset равно 0, то значение аргумента reset игнорируется. Значение reset должно совпадать с состоянием соответствующего поля в регистре DUT. Если же необходимо изменить значение сброса после конфигурации, то можно использовать метод set_reset().
mode.set_reset( .value( 0 ), .kind( "HARD" ) ); // kind == "HARD" значение по умолчанию |
На следующем рисунке показана схема работы методов configure() и set_reset().
(http://i0.wp.com/cluelogic.com/wp-content/uploads/2013/02/jb16_configure.png) |
Рис. 7.2 Диаграмма работы методов configure() и set_reset() |
Метод reset() сбрасывает параметры полей регистра, если значение заданного типа сброса (m_reset[kind]) существует. По умолчанию тип сброса "HARD". Если m_reset[kind] не задан, то метод reset() ничего не выполняет. Стоит отметить, что этот метод сбрасывает только значения свойств регистровой модели и не влияет на значения регистров DUT.
mode.reset( .kind( "HARD" ) ); // kind == "HARD" значение по // умолчанию |
На рисунке ниже показана диаграмма работы метода reset().
(http://i0.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_reset.png) |
Рис. Диаграмма работы метода reset() |
Метод set() задает желаемое значение поля регистра. Метод set() не задает значение регистра в DUT, он только устанавливает значение свойств m_desired и value для соответствующего поря регистра. Для задания значения регистра в DUT используются методы write() и update(), которые будет описаны далее. Пример вызова метода set показан ниже.
mode.set( .value( 1 ) ); |
На следующем рисунке показана схема работы метода set().
(http://i2.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_set.png) |
Рис. Диаграмма работы метода set() |
Метод get() только возвращает значение свойства m_desired поля регистра, не выполняя чтение значения регистра в DUT. Для чтения значения из DUT используются методы read() и mirror(), которые будут описаны ниже. Также доступны методы get_reset() и get_mirrored_value для чтения значений свойств m_reset и m_mirrored соответственно. Метод get_reset() имеет один входной аргумент kind, который задает тип сброса, по умолчанию kind = "HARD". Если значение свойства reset[kind] не задано, то метод get_reset() возвращает значение свойства m_desired. Ниже показаны примеры вызовов описанных методов.
uvm_reg_data_t desired_value = mode.get(); uvm_reg_data_t mirrored_value = mode.get_mirrored_value(); |
На рисунке ниже показана схема работы различных get*() методов.
(http://i1.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_get.png) |
Действия методов get(), get_reset() и get_mirrored_value() |
Метод randomize() это метод языка SystemVerilog. Он генерирует случайное значение полей регистра. После генерации случайного значения поля value метод post_randomize() копирует значения value в m_desired. Важно, что метод pre_randomize() копирует значения переменной m_desired в value если rand_mode для поля value выключен.
assert( mode.randomize() ); |
(http://i2.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_randomize.png) |
Диаграмма работы метода randomize() |
Метод write() позволяет записать значение в регистр DUT.
uvm_status_e status; |
Метод write() запускает следующую последовательность действий в UVM окружении с использование регистровой модели в режиме явного предсказания:
Стоит обратить внимание на то, что, если во время создания и конфигурации регистра individually_accessible аргумент был равен 0, то будет перезаписан весь регистр, так как побитовый доступ отключен. В этом случае значение m_mirrored будет использоваться при перезаписи остальных полей.
(http://i2.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_write.png) |
Диаграмма работы метода write() |
Метод read() позволяет прочитать значение из регистра DUT.
uvm_status_e status; |
Метод read() запускает последовательность действий в UVM окружении с использование регистровой модели в режиме явного предсказания:
(http://i0.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_read.png) |
Рис. 7. Диаграмма работы метода read() |
Метод update() записывает значения в регистрах регистровой модели в DUT. Метод update() является методом uvm_reg класса. Класс uvm_reg_field не содержит метода update().
uvm_status_e status; |
Отличия от метода write() состоят в следующем.
(http://i2.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_before_update.png) |
Диаграмма отображает предварительную проверку внутри метода update() |
Метод update() использует внутри вызов метода write( .value( m_desired ) ). Так как вызов метода write() необходим для того, чтобы обновить значение переменной m_mirrored.
(http://i2.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_after_update.png) |
Диаграмма выполнения метода update() |
Метод mirror() выполняет чтение регистра из DUT.
uvm_status_e status; |
Отличие метода mirror() от метода read() заключаются в следующем.
Метод mirror() вызывает внутри метод do_read(). Это тот же метод, который вызывается внутри метода read(). Именно поэтому метод mirror() будет обновлять не только свойство m_mirrored, но и значения свойств value и m_desired.
(http://i1.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_mirror.png) |
Диаграмма работы метода mirror() |
Метод predict() обновляет значения переменной m_mirrored.
mode.predict( .value( 1 ) ); |
Метод predict() также обновляет переменной value и m_desired.
(http://i2.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_predict.png) |
Рис. Диаграмма работы метода predict() |
Методы write/read регистровой модели генерируют чтение и запись на физическом интерфейсе, используя общую регистровую транзакцию. Эта регистровая транзакция должна быть адаптирована для трансляции в транзакцию физического интерфейса и обратно. Для этого используется адаптер, который должен быть двунаправленным. Адаптер должен быть реализован путём наследования от базового класса uvm_reg_adapter.
(http://uvm-stage.mentor.com//w/images/thumb/d/d2/Reg_adapter.gif/600px-Reg_adapter.gif)
Общая регистровая транзакция реализуется как структура, чтобы минимизировать количество используемых ресурсов памяти. Структура определяется как тип uvm_reg_bus_op и содержит 6 полей:
Поле | Тип | Описание |
addr | uvm_reg_addr_t | Поле адреса (ширина 64 бита по умолчанию) |
data | uvm_reg_data_t | Поле хранящее данные, полученные при чтении или используемые для записи |
kind | uvm_access_e | Поле определяет тип транзакции (UVM_READ, UVM_WRITE) |
n_bits | unsigned int | Число бит, которое будет передаваться в данной транзакции |
byte_en | uvm_reg_byte_en_t | Поле используется в транзакциях, которые поддерживают сигналы разрешения передачи данных |
status | uvm_status_e | Поле хранит статус выполнения регистровой транзакции (UVM_IS_OK, UVM_IS_X, UVM_NOT_OK) |
Поля регистровой транзакции должны быть преобразованы в целевую транзакцию физического интерфейса, по которому выполняется доступ к регистрам DUT. Для этого используется класс uvm_reg_adapter, который содержит два метода: reg2bus() и bus2reg(). Эти методы нужно переопределить и реализовать в них прямое и обратное преобразования соответственно регистровой транзакции и транзакции соответствующего агента физического интерфейса. Класс адаптера также содержит два битовых свойства: supports_byte_enable и provides_responses, эти свойства должны быть установлены в соответствии с поддерживаемой функциональностью. Если шина не поддерживает сигналы разрешения для передаваемых битов, то supports_byte_enable устанавливается равным 0. Параметр provides_responses отвечает за ожидаемый в адаптере способ получения ответа из драйвера в сиквенсер.
В качестве примера рассмотрим APB интерфейс, где для кодирования операции используется транзакция apb_seq_item, содержащая три поля (addr, data, we), которые соответствуют адресу, данным и типу операции (чтение либо запись). Адрес и данные прямо соответствуют соответсвующим полям регистровой транзакции. Поле “we” назначается на поле “kind” регистровой транзакции. При преобразовании ответа с APB интерфейса в регистровую транзакцию, поле “status” всегда устанавливается в UVM_IS_OK, так как в данном примере APB агент не поддерживает бит статуса SLVERR и поэтому предполагается что операция на APB интерфейсе всегда проходит успешно.
Так как APB интерфейс не поддерживает сигналы разрешения для побайтовой записи/чтения, то support_byte_enable бит устанавливается в 0 в конструкторе APB адаптера.
Бит provedes_responses должен быть установлен в 1, если драйвер APB агента возвращает отдельную транзакцию с ответом (т.е. драйвер выполняет put(response), или item_done(response)). Этот бит используется регистровой моделью, чтобы определить ждать ли ответ или нет. Если этот бит установлен и драйвер не возвращает ответ, тогда генерация воздействий блокируется.
В примере APB драйвер использует метод item_done() без параметра, это значит, чтобы вернуть ответ, используется одна и та же транзакция как для запроса, так и для ответа, поэтому provides_responses бит должен быть установлен в 0 в конструкторе APB адаптера. Далее приведен пример описания класса APB адаптера (reg2apb_adapter).
class reg2apb_adapter extends uvm_reg_adapter; virtual function void bus2reg(uvm_sequence_item bus_item, |
Адаптер является важным звеном в цепи связей между методами доступа регистровой модели и драйвером. Для корректной работа адаптера необходимо, чтобы драйвер был реализован в соответствии с общепринятыми стандартами, т.е. получение и отправка транзакции осуществлялась с помощью либо методов get_next_item()/item_done(), либо методов get()/put(rsp). Атрибут класса адаптера provides_responses должен быть установлен в 1, если драйвер использует метод put(rsp), чтобы вернуть ответ сиквенсеру, либо в 0, если не использует.
Если значение provides_responses задано неверно, тогда, либо генерация воздействий блокируется, либо неправильный ответ при frontdoor-доступе будет возвращен сразу же, но активность на шине произойдёт позже.
Блокировка может произойти, если используется get_next_item()/item_done() для получения транзакции запроса и транзакции ответа и при этом установлен в единицу бит provides_responses, отвечающий за передачу ответа. Причина в том, что адаптер при включенном параметре provides_responses, будет ожидать ответа, которого не будет, и метод get_responce() будет зависать.
Возврат ответа будет происходить немедленно, если вы используете механизм get()/put() для передачи и получения транзакции запроса и ответа, и при этом у вас параметр provides_responses установлен в 0. В этом случае метод get() вызывается немедленно из драйвера без блокировки сиквенсера. Далее драйвер продолжает выполнять транзакции, и затем возвращает ответ, используя метод put(rsp), который в этом случае будет игнорироваться адаптером.
При реализации адаптера для уже существующего агента, следует убедиться, что операции чтения и записи, оперируют одинаковой моделью возврата ответов внутри агента между сиквенсером и драйвером. Для правильной работы адаптера, при заданном параметре provides_responses = 1, в драйвере должен возвращаться ответ как на команду чтения, так и на команду записи.
Чтобы использовать регистровую модель, нужно создать объект регистровой модели и разместить указатели на эту модель в подходящих объектах конфигурации, и затем связать между собой сиквенсер, аддаптер и карту адресов регистровой модели.
Объект регистровой модели должен быть создан в тесте и указатель на этот объект должен быть передан в остальную часть иерархии тестового окружения через базу конфигурации. В случае окружения, проверяющего отдельный компонент цифрового блока, нужно передавать указатель на полную модель. Однако, в случае окружения для подсистемы или окружения уровня системы на кристалле, общая регистровая модель будет собираться из блоков регистров для под-компонентов и указатели на под-блоки регистровой модели будут необходимы различным окружениям используемым в общей системе.
Например, в случае тестового окружения для одного блока реализующего только SPI протокол, объект конфигурации spi_env содержал бы указатель на регистровую модель SPI, которая была бы создана в тесте.
Следующий код представляет собой пример из базового класса для теста SPI блока, обратите внимание, что регистровая модель создается с использованием механизма фабрики, а затем вызывается метод build().
Важно: Метод build() регистровой модели не вызывается автоматически во время работы механизма создания компонент в build_phase(), так как тип регистровой модели не наследуются от uvm_component, и не является uvm компонентом. Если этот метод не будет вызван вручную, то не будут созданы и доступны к использованию все внутренние объекты, такие как регистры, регистровые поля и др. И при попытке обращения к ним будет получена ошибка о попытке обращения к несуществующему объекту (null object access).
// Базовый тест для SPI |
Для полного подключения нужно выполнить два действия.
Подключение должно выполняться в connect_phase фазе, для того, чтобы знать наверняка, что компоненты адаптера и предиктора уже созданы. Для каждой карты адресов регистровой модели должен быть подключен свой единственный адаптер и сиквенсер. В окружении уровня блока такое подключение нужно выполнять в классе наследуемом от uvm_env. Для окружения верхнего уровня подключение выполняется на самом верхнем уровне, например, в базовом тесте.
Например, регистровую модель, которая на верхнем уровне будет работать с APB интерфейсом, можно использовать для работы по APB интерфейсу, при наличии соответствующего регистрового адаптера, подключив к сиквенсеру агента, используя метод set_sequencer() класса карты адресов.
// Объявляем указатель на используемый адаптер reg2apb_adapter reg2apb; // случае будем использовать регистровую модель для подачи // тестовых воздействий if(m_cfg.spi_rm.get_parent() == null) begin |
По умолчанию используется явное предсказание для обновления значений в регистровой модели, каждый раз когда приходят транзакции чтения/записи. Явное предсказание требует использования компонента, наследуемого от класса uvm_reg_predictor.
(http://uvm-stage.mentor.com//w/images/9/9c/Reg_predictor.gif)
Компонент uvm_reg_predictor наследуется от uvm_subscriber и параметризируется типом анализируемой транзакции. Он содержит указатель на адаптер и карту адресов регистровой модели. Для его использования нужно выполнить следующие действия
Если к регистрам может осуществляться доступ по нескольким интерфейсам, то предикторы следует подключать ко всем мониторам, отслеживающим записи в регистры DUT.
// Объявялем предиктор: // конвертирования полученных от монитора транзакций в транзакции // регистровой модели apb2reg_predictor.adapter = reg2apb; |
В UVM библиотеку уже включены несколько стандартных тестовых сиквенсов для проверки регистров и памяти. Если регистровая модель уже интегрирована в тестовое окружение и подключена к DUT, то можно запускать стандартные тестовые сиквенсы для проверки правильного функционирования регистров и блоков памяти в DUT.
В стандартных тестовых сиквенсах по умолчанию проверяются все регистры во всех блоках, которые включены в регистровую модель. С помощью атрибута NO_REG_TESTS можно исключить регистр или целый блок из проверки, выполняемой стандартными тестовыми сиквенсами. Все стандартные регистровые тестовые сиквесы наследуются от класса uvm_reg_sequence. Список и краткое описание тестовых сиквенсов приведен ниже в таблице:
Имя сиквенс | Описание |
uvm_reg_hw_reset_seq | Читаются все регистры в блоках регистровой модели и сравниваются прочитанные значения со значениями по умолчанию, которые должны устанавливаться после сброса регистров |
uvm_reg_single_bit_bash_seq | Последовательно записываются значения 1 и 0 в каждый бит регистра, проверяются соответствующие установленные значения, основываясь на заданном типе доступа для полей регистра регистра |
uvm_reg_bit_bash_seq | Вызывается сиквенс uvm_reg_single_bit_bash_seq для всех регистров в блоках и подблоках регистровой модели |
uvm_reg_single_access_seq | Проводится запись регистра через физический интерфейс DUT с последующим контролем (чтением) с помощью back-door доступа. Затем проводится запись регистра через back-door доступ и чтение регистра через физический интерфейс |
uvm_reg_access_seq | Выполняется сиквенс uvm_reg_single_access_seq для всех регистров в блоках и подблоках регистровой модели |
uvm_mem_single_walk_seq | Записывается определенная последовательность данных в память и затем проверяется чтение записанных значений |
uvm_mem_walk_seq | Выполняется сиквенс uvm_mem_single_walk_seq для всех блоков памяти в регистровой модели |
uvm_mem_sing1e_access_seq | Проводится запись в память через физический интерфейс DUT с последующим контролем (чтением) с помощью back-door доступа. Затем проводится запись в память через back-door доступ и чтение из памяти через физический интерфейс |
uvm_mem_access_seq | Выполняется сиквенс uvm_mem_sing1e_access_seq для всех блоков памяти в регистровой модели |
uvm_reg_shared_access_seq | Для каждой карты адресов, через которую доступен регистр, проводится запись регистра с помощью одной карты (через один физический интерфейс), затем проводится чтение записанного значения через все другие карты адресов (другие физические интерфейсы) |
uvm_mem_shared_access_seq | Для каждой карты адресов, через которую доступна память, проводится запись ячеек памяти с помощью одной карты (через один физический интерфейс), затем проводится чтение записанных значений через все другие доступные карты адресов (другие физические интерфейсы) |
uvm_reg_mem_shared_access_seq | Выполняются сиквенс uvm_reg_shared_access_seq для всех регистров и сиквенс uvm_mem_shared_access_seq для памяти во всех доступных блоках регистровой модели |
uvm_reg_mem_built_in_seq | Вызываются все выбранные стандартные тестовые сиквенсы регистровой модели. По умолчанию все стандартные сиквенсы выбраны |
uvm_reg_mem_hdl_paths_seq | Проверяются на правильность все HDL пути, заданные для регистров и блоков памяти, которые описаны в регистровой модели |
Сиквенсы uvm_reg_single_access_seq и uvm_mem_single_access_seq требуют наличия настроенного backdoor доступа к регистрам/памяти DUT. Для выполнения следующих сиквенсов требуются наличие доступа к регистрам через несколько карт адресов: uvm_reg_shared_access_seq, uvm_mem_shared_access_seq, uvm_reg_mem_shared_access_seq.
С помощью атрибутов, описанных в таблице ниже, можно отключать выполнение стандартных тестовых сиквенсов регистровой модели.
Атрибут | Пропускаемые тестовые сиквенсы |
NO_REG_HW_RESET_TEST | uvm_reg_hw_reset_seq |
NO_REG_TEST | Все тесты |
NO_REG_BIT_BASH_TEST | uvm_reg_single_bit_bash_seq uvm_reg_bit_bash_seq |
NO_REG_ACCESS_TEST | uvm_reg_single_access_seq uvm_reg_access_seq |
NO_MEM_WALK_TEST | uvm_mem_single_walk_seq uvm_single_walk_seq |
NO_MEM_TESTS | Все *_mem и *_reg тестовые сиквенсы |
NO_MEM_ACCESS_TEST | uvm_mem_sing1e_access_seq uvm_mem_access_seq |
NO_REG_SHARED_ACCESS_TEST | uvm_reg_shared_access_seq |
NO_MEM_SHARED_ACCESS_TEST | uvm_mem_shared_access_seq |
Для исключения блока или регистра при выполнении теста необходимо задать соответствующий атрибут в uvm_resource_db, как показано в следующем примере.
uvm_resource_db#(bit) :: set ( { "REG::", regmodel.blk.get_full_name(), ".*" }, "NO_REG_TESTS", 1, this); |
В данном примере исключается блок blk регистровой модели regmodel при выполнении стандартных тестовых сиквенсов.
Далее показан пример использования (вызова) стандартной тестовой сиквенсы uvm_reg_hw_reset_test.
class simple_reset_test extends uvm_test; my_env m_env; . . . task main_phase(uvm phase phase); uvm_reg_hw_reset_test rst_test_seq = new(); phase.raise_objection (this, "Running RESET test"); rst_test_seq.model = m_env.regmodel; rst_test_seq.start(null) ; phase.drop_objection(this, "End of RESET test") ; endtask : main_phase endclass : simple_reset_test |
Одним из преимуществ использования регистровой модели, является возможность создания сиквенсов, в которых будет выполняться обращения к регистрам по их имени, без привязки к реальному адресу. Разработав такую сиквен ее можно повторно использовать в тестовом окружении, даже если карта адресов регистров меняется. Для этого необходимо создать и подключить регистровую модель, передать указатель на нее в сиквенс, а далее использовать команды изменения содержимого регистров read(), write(), set(), update() и т.д.
//Создаем собственную базовую сиквенс class spi_bus_base_seq extends uvm_sequence #(uvm_sequence_item); spi_reg_block spi_rm; // операций с регистрами if(!uvm_config_db #(spi_env_config)::get(null, get_full_name(), "spi_env_config", m_cfg)) begin spi_rm = m_cfg.spi_rm; // Наследуемся от базовой сиквенсы и реализуем сценарий class ctrl_set_seq extends spi_bus_base_seq; spi_rm.ctrl_reg.update(status, .path(UVM_FRONTDOOR), .parent(this)); // операций записи (реально на шине транзакции не произойдет) data = spi_rm.ctrl_reg.get(); |
1. UVM tutorial for candy lovers на сайте cluelogic.com. Ссылка http://cluelogic.com/2013/02/uvm-tutorial-for-candy-lovers-register-access-methods/
2. UVM Cookbook. Chapter "Register Abstraction Layer". Ссылка https://verificationacademy.com/cookbook/registers
3. SystemVeriloig Golden Reference Guide - A concise guide to SystemVeriloig IEEE Std 1800-2009 Version 5.0, August 2012, Doulos Ltd
Регистровая модель | Набор UVM классов (uvm_reg_field, uvm_reg, uvm_reg_block и др.), сконфигурированных в соответствии с иерархической структурой регистров и блоков в тестируемом блоке (DUT) |
Карта адресов | UVM класс uvm_reg_map, в котором задается смещение адресов регистров или элементов памяти входящих в uvm_reg_block |
Предиктор регистровой модели | Класс uvm_reg_predictor, предназначенный для обновления значений регистровой модели, используемой в режиме явного предсказания |
Регистровый адаптер | Класс uvm_reg_adapter, предназначенный для выполнения прямого и обратного преобразований регистровых транзакций в транзакции физического интерфейса |