Published using Google Docs
Лекция 6. Регистровая модель (v.0.3)
Updated automatically every 5 minutes

Лекция 6. Регистровая модель (v.0.3)

Обзор UVM регистровой модели

Регистровая модель UVM (UVM register layer) определяет некоторые базовые классы, которые определяют абстрактные операции чтения/записи в регистры и ячейки памяти в DUT (design under test - тестируемый компонент). Использование регистровой модели позволяет существенно упростить и ускорить создание моделей регистровых файлов и блоков памяти, написание тестовых последовательностей для проверки правильности работы регистров в DUT, при этом тестовые последовательности не зависят от используемых физических интерфейсов доступа к регистрам (APB, AXI и др.), что значительно упрощает их повторное использование в новых проектах.

Регистровая модель обычно состоит из набора блоков (классов), иерархия которых повторяет иерархию блоков и регистров в DUT. Блоки регистровой модели могут содержать регистры, регистровые файлы и ячейки памяти.

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

Reg auto predict.gif

(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 окружения при использовании регистровой модели в режиме явного предсказания.

Reg explicit prediction.gif

(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 регистровой модели. На рисунке ниже показана схема взаимодействия компонентов тестового окружения при использовании регистровой модели в пассивном режиме.

Passive prediction.gif

(http://uvm-stage.mentor.com//w/images/thumb/c/c2/Passive_prediction.gif/600px-Passive_prediction.gif)

Алгоритм интеграции регистровой модели в UVM окружение

Для интеграции регистровой модели в UVM окружение необходимо выполнить следующие шаги.

  1. Описать регистровую модель, т.е. создать иерархическое описание регистров и ячеек памяти по спецификации DUT с помощью конфигурации соответствующих UVM классов регистровой модели. Для автоматизации этого этапа могут быть использованы специализированные программы генерации регистровой модели.
  2. Определить какая карта адресов (address map) в регистровой модели соответствует агенту, который работает с одним из физических интерфейсов, по которому идет чтение и запись регистров DUT.
  3. Создать класс регистрового адаптера, в котором  реализуются методы reg2bus и bus2reg для прямого и обратного преобразования регистровой транзакции (uvm_reg_bus_op) в транзакцию используемого физического интерфейса доступа к регистрам DUT.
  4. Декларировать и создать объект регистровой модели в базовом тесте и отправить, используя механизм базы конфигурации, указатель на него внутрь тестового окружения. Стоит отметить, что в тестовом окружении рекомендуется проверить получение объекта регистровой модели из базы конфигурации, и, если он не создан или не передан, то создать внутри свой или выдать сообщение об ошибке.
  5. В каждом окружении, которое содержит активный мастер агент управления физическим интерфейсом доступа к регистрам DUT, нужно соединить карту адресов регистровой модели, адаптер и сиквенсер. Если используется несколько карт адресов, то для каждой карты нужно сделать соответствующие подключения.
  6. В каждом окружении, которое содержит активный агент, нужно настроить используемый регистровый предиктор. Для этого нужно установить соответствующую карту адресов (в случае, если их несколько) и подключить порт bus_in, по которому будет происходить получение транзакций.

 


Классы регистровой модели

Классы регистровой модели имеют следующую иерархическую структуру уровней, описанную в следующей таблице.

(Рисунки в таблице взяты со страницы https://verificationacademy.com/cookbook/registers/modelstructure)

Уровень

Описание

Схема

Поля
(Fields)

Биты сгруппированы по функциональности в поля

Reg field.gif

Регистр
(Register)

Группирует поля и биты с заданным смещением (адрес внутри регистра)

Register.gif

Память

(Memory)

Представляет собой блок памяти с заданным диапазоном адресов

Reg mem.gif

Блок

(Block)

Группирует регистры, если это уровень, описывающий аппаратный блок. Или группирует другие блоки, для которых заданны одна или несколько карт адресов.

Может также содержать память.

Reg block.gif

Карта адресов

(Map)

Имя карты адресов, которая задает смещение и позволяет вычислить адрес регистра по его имени. Также содержит указатель на сиквенсер, который будет работать с этой картой адресов.

Reg map.gif

Поля регистра (класс uvm_reg_field)

Самый нижний уровень регистровой модели это поле регистра. Как самый нижний уровень, поля могут соответствовать битам или группам бит регистра. Каждое поле представляет собой объект класса  uvm_reg_field. Поля размещаются внутри класса, описывающего регистр uvm_reg, и конфигурируются посредством вызова метода  configure().

function void configure(

  uvm_reg        parent,    //регистр, которому принадлежит поле
 
int unsigned   size,      // Размер поля в битах
 
int unsigned   lsb_pos,   // Адрес младшего бита поля
 
string         access,    // Тип доступа к полю "RW", "RO", "WO" и др.
 
bit            volatile,  // Задает режим предсказания значения
 uvm_reg_data_t reset
,     // Значение после сброса
 
bit            has_reset, // Можно ли сбросить

  bit            is_rand,   // Можно ли рандомизировать
 
bit            individually_accessible); // доступен ли доступ к

                                           // каждому биту внутри поля

Когда поля созданы, они получают строковое имя, которое позволяет получить доступ к ним.

Регистры (класс uvm_reg)

Регистрами считаются объекты класса, наследуемого от uvm_reg. Общие параметры и настройки задаются через конструктор класса.

// конструктор класса uvm_reg

function new (

  string name="",      // Имя регистра
 
int unsigned n_bits, // Ширина регистра в битах
 
int has_coverage);   // Вкл./отключение сбора покрытия по регистру

Класс, описывающий регистры содержит метод build, который используется для создания и конфигурации полей регистра. Стоит отметить что регистровая модель это наследник класса uvm_object, и так как это не uvm_component, то вызов метода build не происходит автоматически в build-фазе. Например

   // регистр ctrl
 
class ctrl extends uvm_reg;
     `uvm_object_utils
(ctrl) // регистрируем макросом как объект
     
rand uvm_reg_field acs; //объявляем поля регистра
     
rand uvm_reg_field ie;
     
rand uvm_reg_field lsb;
     
rand uvm_reg_field tx_neg;
     
rand uvm_reg_field rx_neg;
     
rand uvm_reg_field go_bsy;
     uvm_reg_field reserved
;
     
rand uvm_reg_field char_len;

//------------------------------------------------------------------

// в конструкторе задаем имя
//------------------------------------------------------------------
     
function new(string name = "ctrl");
       
super.new(name, 14, UVM_NO_COVERAGE);
     
endfunction
//------------------------------------------------------------------
// описываем создание полей в методе build

//------------------------------------------------------------------
     
virtual function void build();
        acs
= uvm_reg_field::type_id::create("acs");
        ie
= uvm_reg_field::type_id::create("ie");
        lsb
= uvm_reg_field::type_id::create("lsb");
        tx_neg
= uvm_reg_field::type_id::create("tx_neg");
        rx_neg
= uvm_reg_field::type_id::create("rx_neg");
        go_bsy
= uvm_reg_field::type_id::create("go_bsy");
        reserved
= uvm_reg_field::type_id::create("reserved");
        char_len
= uvm_reg_field::type_id::create("char_len");
       // вызываем для каждого поля метод конфигурации
        acs.configure
(this, 1, 13, "RW", 0, 1'b0, 1, 1, 0);
        ie.configure
(this, 1, 12, "RW", 0, 1'b0, 1, 1, 0);
        lsb.configure
(this, 1, 11, "RW", 0, 1'b0, 1, 1, 0);
        tx_neg.configure
(this, 1, 10, "RW", 0, 1'b0, 1, 1, 0);
        rx_neg.configure
(this, 1, 9, "RW", 0, 1'b0, 1, 1, 0);
        go_bsy.configure
(this, 1, 8, "RW", 0, 1'b0, 1, 1, 0);
        reserved.configure
(this, 1, 7, "RO", 0, 1'b0, 1, 0, 0);
        char_len.configure
(this, 7, 0, "RW", 0, 7'b0000000, 1, 1, 0);
     
endfunction
 
endclass

Когда регистр добавляют в блок, это выполняется аналогично добавлению  полей в регистр. Сначала регистры должны быть созданы и сконфигурированы. Прототип функции конфигурации представлен ниже

function void configure (

  uvm_reg_block blk_parent, // Задается блок, который содержит регистр
 uvm_reg_file regfile_parent
= null, // По умолчанию не используется,

// позволяет задать регистровый файл, которому принадлежит регистр
 
string hdl_path = "");    // Используется для задания прямого

// доступа к регистрам цифрового блока, если все биты регистра имеют

// одинаковый путь в иерархии проекта в базе симулятора.

Регистровая память (класс uvm_mem)

Регистровой памятью считаются объекты класса, наследуемого от класса uvm_mem. Внутри регистровой модели uvm_mem обрабатывается как регион, к которому есть доступ. В отличие от регистров, значения ячеек памяти в регистровой модели не сохраняются, с целью экономии оперативной памяти на рабочих станциях. Диапазон, к которому есть доступ, задается в конструкторе.

function new (

  string           name,          // Имя блока памяти
 
longint unsigned size,          // Размер памяти
 
int unsigned     n_bits,        // Размер одного слова памяти
 
string           access = "RW", // Тип доступа "RW" или "RO"
 
int              has_coverage = UVM_NO_COVERAGE); // Вкл./выкл.

                                                    // сбора покрытия

Пример реализации класса памяти размером 32'h2000:

// Массив памяти размером 32'h2000;
class mem_1_model extends uvm_mem;
 `uvm_object_utils
(mem_1_model)

 
function new(string name = "mem_1_model");
   
super.new(name, 32'h2000, 32, "RW", UVM_NO_COVERAGE);
 
endfunction
endclass: mem_1_model

Карта адресов регистровой  модели (класс uvm_reg_mem)

Карта адресов регистровой модели служит для двух целей. Карта предоставляет информацию о смещених содержащихся в ней регистров, памяти или блоков. А также карта используется для того, чтобы определить агент, с которым будет работать регистровая модель, и то, где будут выполняться встроенные базовые последовательности, проверяющие регистровую модель, до тех пор пока модель не будет встроена в систему более высокого уровня.

Reg map content.gif

(http://uvm-stage.mentor.com//w/images/8/8d/Reg_map_content.gif)

Содержимое карты адресов регистровой модели

Для добавления регистров или памяти в карту используются методы add_reg() или add_mem() соответственно, формат которых показан ниже.

function void add_reg (

  uvm_reg        rg, // Указатель на объект регистра для добавления
 uvm_reg_addr_t offset
, // Смещение адреса добавляемого регистра
 
string         rights = "RW",  // Тип доступа к регистру
 
bit            unmapped = 0,   // Если установлен в 1, то к регистру

                                 // не будет доступа через карту

// адресов и придется определить, как будет происходить к нему

// frontdoor-доступ, т.е. задать следующий параметр
 uvm_reg_frontdoor frontdoor
= null); // Указатель на объект, который

// будет осуществлять frontdoor доступ к регистру (к примеру другая

//  карта адресов)

function void add_mem (

  uvm_mem        mem,           // Указатель на объект памяти
 uvm_reg_addr_t offset
,        // Смещение адреса памяти
 
string         rights = "RW", // Тип доступа к памяти
 
bit            unmapped=0,    // Если установлен в 1, то к памяти не

  // будет доступа через карту адресов и придется определить, как будет

  // происходить к ней frontdoor-доступ, т.е. задать следующий параметр
 uvm_reg_frontdoor frontdoor
=null);// Указатель на объект, который будет

// осуществлять frontdoor-доступ к памяти (к примеру другая карта

// адресов)

Внутри регистрового блока могут размещаться несколько карт адресов, каждая из которых может работать с различными агентами и задавать различные смещения.

Регистровый блок (класс uvm_reg_block)

Следующий уровень иерархии в UVM регистровой модели это регистровый блок. Этот класс может группировать регистры и карты памяти для моделей уровня IP-цифрового блока, а также может группировать другие регистровые блоки. Для того, чтобы определять адреса регистров и смещение адреса внутри блока используется объект карты адресов регистровой модели и для него задается базовый адрес, от которого будут вычисляться смещения. Объект карты адресов должен создаваться внутри блока с помощью метода create_map.

function uvm_reg_map create_map(

  string name,               // Имя карты адресов
 uvm_reg_addr_t base_addr
,  // Базовый адрес для карты памяти
 
int unsigned n_bytes,      // Ширина данных доступных через карту

                             // (в байтах )
 uvm_endianness_e endian
,   // Порядок байт внутри карты
 
bit byte_addressing=1);    // Поддерживается ли побайтовая адресация  

// Пример использования метода create_map:
AHB_map
= create_map("AHB_map", 'h0, 4, UVM_LITTLE_ENDIAN);

В методе 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;
     `uvm_object_utils
(spi_reg_block)
//объявляем регистры
     
rand rxtx0 rxtx0_reg;
     
rand rxtx1 rxtx1_reg;
     
rand rxtx2 rxtx2_reg;
     
rand rxtx3 rxtx3_reg;
     
rand ctrl ctrl_reg;
     
rand divider divider_reg;
     
rand ss ss_reg;

//объявляем карты адресов
     uvm_reg_map APB_map
;
   
     
function new(string name = "spi_reg_block");
       
super.new(name, UVM_NO_COVERAGE);
     
endfunction

//Описываем метод build
     
virtual function void build();


        rxtx0_reg
= rxtx0::type_id::create("rxtx0"); // создаем

         rxtx0_reg.configure(this, null, "");         // Конфигурируем

         rxtx0_reg.build(); // Вызываем метод создания полей регистра

        rxtx1_reg
= rxtx1::type_id::create("rxtx1");
        rxtx1_reg.configure
(this, null, "");
        rxtx1_reg.build
();

        rxtx2_reg
= rxtx2::type_id::create("rxtx2");
        rxtx2_reg.configure
(this, null, "");
        rxtx2_reg.build
();

        rxtx3_reg
= rxtx3::type_id::create("rxtx3");
        rxtx3_reg.configure
(this, null, "");
        rxtx3_reg.build
();

        ctrl_reg
= ctrl::type_id::create("ctrl");
        ctrl_reg.configure
(this, null, "");
        ctrl_reg.build
();

        divider_reg
= divider::type_id::create("divider");
        divider_reg.configure
(this, null, "");
        divider_reg.build
();

        ss_reg
= ss::type_id::create("ss");
        ss_reg.configure
(this, null, "");
        ss_reg.build
();

//Создаем объект карты адресов регистровой модели
        APB_map
= create_map("APB_map", 'h0, 4, UVM_LITTLE_ENDIAN);
//Добавляем ргистры в карту адресов
        APB_map.add_reg
(rxtx0_reg, 32'h00000000, "RW");
        APB_map.add_reg
(rxtx1_reg, 32'h00000004, "RW");
        APB_map.add_reg
(rxtx2_reg, 32'h00000008, "RW");
        APB_map.add_reg
(rxtx3_reg, 32'h0000000c, "RW");
        APB_map.add_reg
(ctrl_reg, 32'h00000010, "RW");
        APB_map.add_reg
(divider_reg, 32'h00000014, "RW");
        APB_map.add_reg
(ss_reg, 32'h00000018, "RW");
//Вызываем метод финализации
        lock_model
();
     
endfunction
 
endclass

Вызов метода lock_model() в конце функции build является завершающей стадией создания блока регистровой модели, которая гарантирует, что модель не будет изменена. После вызова этого метода невозможно будет добавить какие либо регистры или память в карту адресов. Снять блокировку после выполнения lock_model() нет возможности.

Приведенный выше пример можно использовать при верификации законченных блоков, но если блоки встраиваются в большую систему, то регистровые блоки можно комбинировать с другими блоками. Далее приведен пример как это реализуется.

package pss_reg_pkg;

import uvm_pkg::*;
`include "uvm_macros.svh"

import spi_reg_pkg::*;
import gpio_reg_pkg::*;

class pss_reg_block extends uvm_reg_block;

`uvm_object_utils
(pss_reg_block)

function new(string name = "pss_reg_block");
 
super.new(name);
endfunction

rand spi_reg_block spi;
rand gpio_reg_block gpio;

function void build();
 AHB_map
= create_map("AHB_map", 0, 4, UVM_LITTLE_ENDIAN);

 spi
= spi_reg_block::type_id::create("spi");
 spi.configure
(this);
 spi.build
();
 AHB_map.add_submap
(this.spi.default_map, 0);

 gpio
= gpio_reg_block::type_id::create("gpio");
 gpio.configure
(this);
 gpio.build
();
 AHB_map.add_submap
(this.gpio.default_map, 32'h100);

 lock_model
();
endfunction: build
endclass: pss_reg_block
endpackage: pss_reg_pkg

Хранение и обработка полей регистровой модели

Рассмотрим как выглядит модель регистра в UVM регистровой модели. Класс поля uvm_reg_field является наименьшим (в иерархии) слоем в модели регистра, который представляет отдельные биты регистра. Класс uvm_reg_field использует некоторые свойства для сохранения множества значений поля регистра:

Таким образом, модель поля регистра включает четыре атрибута, что показано на следующем рисунке.

Properties of uvm_reg_field

(http://i2.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_reg_field_properties.png)

Рис. Атрибуты класса uvm_reg_field

Обратим внимание, что среди этих свойств только свойство value является public. Остальные свойства являются локальными, таким образом мы не можем к ним обратиться напрямую вне данного класса. Далее рассматриваются методы, позволяющие изменять значения данных свойств.

Метод configure()

Первое, что делается после создания класса 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()

Метод  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() задает желаемое значение поля регистра. Метод set() не задает значение регистра в DUT, он только устанавливает значение свойств m_desired и value для соответствующего поря регистра. Для задания значения регистра в DUT используются методы write() и update(), которые будет описаны далее. Пример вызова метода set показан ниже.

mode.set( .value( 1 ) );

На следующем рисунке показана схема работы метода set().

How the set() method works

(http://i2.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_set.png)

Рис. Диаграмма работы метода set()

Метод get()

Метод 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 reset_value    
= mode.get_reset( .kind( "HARD" ) );

uvm_reg_data_t mirrored_value = mode.get_mirrored_value();

На рисунке ниже показана схема работы различных get*() методов.

How the get(), get_reset(), and get_mirrored_value() methods work

(http://i1.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_get.png)

Действия методов get(), get_reset() и get_mirrored_value()

Метод randomize()

Метод randomize() это метод языка SystemVerilog. Он генерирует случайное значение полей регистра. После генерации случайного значения поля value метод post_randomize() копирует значения value в m_desired. Важно, что метод pre_randomize() копирует значения переменной m_desired в value если rand_mode для поля value выключен.

assert( mode.randomize() );

How the randomize() method works

(http://i2.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_randomize.png)

Диаграмма работы метода  randomize()

Метод write()

Метод  write() позволяет записать значение в регистр DUT.

uvm_status_e status;
mode.write( .status( status ), .value( 1 ) );

Метод write() запускает следующую последовательность действий в UVM окружении с использование регистровой модели в режиме явного предсказания:

  1. Создается объект uvm_reg_item, который будет использоваться для регистровой транзакции записи
  2. Адаптер uvm_reg_adapter конвертирует регистровую транзакцию записи в транзакцию (bus_req) агента соответствующего физического интерфейса
  3. Драйвер uvm_driver выполняет эту транзакцию, отправляя данные по физическому интерфейсу в DUT.
  4. Монитор uvm_monitor собирает транзакцию произошедшую на физическом интерфейсе
  5. Предиктор uvm_reg_predictor получает от монитора транзакцию и запрашивает у адаптера uvm_reg_adapter конвертацию транзакции в действие с регистром.
  6. Действие с регистром конвертируется в регистровую транзакцию uvm_reg_item.
  7. Регистровая транзакция uvm_reg_item используется для обновления (предсказания) значений свойств value, m_mirrored и m_desired.

Стоит обратить внимание на то, что, если во время создания и конфигурации регистра individually_accessible аргумент был равен 0, то будет перезаписан весь регистр, так как побитовый доступ отключен. В этом случае значение m_mirrored будет использоваться при перезаписи остальных полей.

How the write() method works

(http://i2.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_write.png)

Диаграмма работы метода write()

Метод read()

Метод  read() позволяет прочитать значение из регистра DUT.

uvm_status_e   status;
uvm_reg_data_t value;
mode.read( .status( status ), .value( value ) );

Метод read() запускает последовательность действий в UVM окружении с использование регистровой модели в режиме явного предсказания:

  1. Создается объект uvm_reg_item, который будет использоваться для регистровой транзакции чтения.
  2. Адаптер uvm_reg_adapter конвертирует регистровую транзакцию чтения в транзакцию чтения (bus_req) агента соответствующего физического интерфейса.
  3. Драйвер uvm_driver выполняет транзакцию чтения (bus_req), отправляя данные по физическому интерфейсу в DUT и получая ответ.
  4. Регистровый адаптер uvm_reg_apapter конвертирует транзакцию физического интерфейса с прочитанными данными в регистровую транзакцию.
  5. Метод read() возвращает прочитанные данные.
  6. В это же время монитор uvm_monitor собирает транзакцию, произошедшую на физическом интерфейсе.
  7. Предиктор uvm_reg_predictor получает от монитора транзакцию и вызывает метод конвертации из адаптера uvm_reg_adapter, чтобы конвертировать полученную от монитора транзакцию в соответствующую регистровую операцию.
  8. Регистровая операция конвертируется в регистровую транзакцию uvm_reg_item.
  9. Регистровая транзакция uvm_reg_item используется для обновления значений свойств value, m_mirrored и m_desired.

How the read() method works

(http://i0.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_read.png)

Рис. 7. Диаграмма работы метода read()

Метод update()

Метод update() записывает значения в регистрах регистровой модели в DUT. Метод update() является методом uvm_reg класса. Класс uvm_reg_field не содержит метода update().

uvm_status_e status;
jb_recipe_reg.update
( .status( status ) );

Отличия от метода write() состоят в следующем.

Before the update() is executed

(http://i2.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_before_update.png)

Диаграмма отображает предварительную проверку внутри метода update()

Метод update() использует внутри  вызов метода write( .value( m_desired ) ). Так как вызов метода write() необходим для того, чтобы обновить значение переменной m_mirrored.

After the update() is executed

(http://i2.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_after_update.png)

Диаграмма выполнения метода update()

Метод mirror()

Метод mirror() выполняет чтение регистра из DUT.

uvm_status_e status;
mode.mirror( .status( status ), .check( UVM_CHECK ) );

Отличие метода mirror() от метода read() заключаются в следующем.

Метод mirror() вызывает внутри метод do_read(). Это тот же метод, который вызывается внутри метода read(). Именно поэтому метод mirror() будет обновлять не только свойство m_mirrored, но и значения свойств value и m_desired.

How the mirror() method works

(http://i1.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_mirror.png)

Диаграмма работы метода mirror()

Метод predict()

Метод predict() обновляет значения переменной m_mirrored.

mode.predict( .value( 1 ) );

 

Метод predict() также обновляет переменной value и m_desired.

How the predict() method works

(http://i2.wp.com/cluelogic.com/wp-content/uploads/2013/01/jb16_predict.png)

Рис. Диаграмма работы метода predict()

Класс адаптера (класс uvm_reg_adapter)

Методы write/read регистровой модели генерируют чтение и запись на физическом интерфейсе, используя общую регистровую транзакцию. Эта регистровая транзакция должна быть адаптирована для трансляции в транзакцию физического интерфейса и обратно. Для этого используется адаптер, который должен быть двунаправленным. Адаптер должен быть реализован путём наследования от базового класса uvm_reg_adapter.

Reg adapter.gif

(http://uvm-stage.mentor.com//w/images/thumb/d/d2/Reg_adapter.gif/600px-Reg_adapter.gif)

Регистровая транзакция (uvm_reg_bus_op)

Общая регистровая транзакция реализуется как структура, чтобы минимизировать количество используемых ресурсов памяти. Структура определяется как тип 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)

Методы reg2bus и bus2reg адаптера

Поля регистровой транзакции должны быть преобразованы в целевую транзакцию физического интерфейса, по которому выполняется доступ к регистрам 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;
 `uvm_object_utils
(reg2apb_adapter)
 
function new(string name = "reg2apb_adapter");
     
super.new(name);
     supports_byte_enable
= 0;
     provides_responses
= 0;
 
endfunction
 
virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
   apb_seq_item apb
= apb_seq_item::type_id::create("apb");
   apb.we
= (rw.kind == UVM_READ) ? 0 : 1;
   apb.addr
= rw.addr;
   apb.
data = rw.data;
   
return apb;
 
endfunction: reg2bus

  virtual function void bus2reg(uvm_sequence_item bus_item,
                               
ref uvm_reg_bus_op rw);
   apb_seq_item apb
;
   
if (!$cast(apb, bus_item)) begin
     `uvm_fatal
("NOT_APB_TYPE","Provided bus_item is not of the correct type")
     
return;
   
end
   rw.kind
= apb.we ? UVM_WRITE : UVM_READ;
   rw.addr
= apb.addr;
   rw.
data = apb.data;
   rw.
status = UVM_IS_OK;
 
endfunction: bus2reg
endclass: reg2apb_adapter

Настройка адаптера для получения ответа

Адаптер является важным звеном в цепи связей между методами доступа регистровой модели и драйвером. Для корректной работа адаптера необходимо, чтобы драйвер был реализован в соответствии с общепринятыми стандартами, т.е. получение и отправка транзакции осуществлялась с помощью либо методов 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
// Создаем окружение, затем объект конфигурации, включающий конфигурации под-блоков и указателей на объекты интерфейсов
function void spi_test_base::build_phase(uvm_phase phase);
  m_env_cfg
= spi_env_config::type_id::create("m_env_cfg");
// Активируем все типы встроенного функционального покрытия для регистровой модели
 uvm_reg
::include_coverage("*", UVM_CVR_ALL); 
 
// Создаем обьект регистровой модели
 spi_rm
= spi_reg_block::type_id::create("spi_rm");
 
// Вызываем обьект построения всех внутренних обьектов модели
 spi_rm.build
();
 
//Передаем указатель на регистровую модель в объект конфигурации
 m_env_cfg.spi_rm
= spi_rm;
endfunction: build_phase

Подключение объекта регистровой модели

Для полного подключения нужно выполнить два действия.

  1. Подключение карты адресов регистровой модели к сиквенсеру и адаптеру.
  2. Подключение предиктора (передача ему указателя на карту адресов регистровой модели и адаптера, а также подключение порта получения транзакций)  

Подключение карты адресов регистровой модели к сиквенсеру и адаптеру

Подключение должно выполняться в connect_phase фазе, для того, чтобы знать наверняка, что компоненты адаптера и предиктора уже созданы. Для каждой карты адресов регистровой модели должен быть подключен свой единственный адаптер и сиквенсер. В окружении уровня блока такое подключение нужно выполнять в классе наследуемом от uvm_env. Для окружения верхнего уровня подключение выполняется на самом верхнем уровне, например, в базовом тесте.

Например, регистровую модель, которая на верхнем уровне будет работать с APB интерфейсом, можно использовать для работы по APB интерфейсу, при наличии соответствующего регистрового адаптера, подключив к сиквенсеру агента, используя метод set_sequencer() класса карты адресов.

// Объявляем указатель на используемый адаптер

reg2apb_adapter reg2apb;
function void spi_env::connect_phase(uvm_phase phase);
 
if(m_cfg.m_apb_agent_cfg.active == UVM_ACTIVE) begin
   reg2apb
= reg2apb_adapter::type_id::create("reg2apb");
   
// подключение регистровой модели к сиквенсеру и адаптеру
   
// Если у нас тестовое окружение верхнего уровня, только в этом

    // случае будем использовать регистровую модель для подачи

    // тестовых воздействий

    if(m_cfg.spi_rm.get_parent() == null) begin
     m_cfg.spi_rm.APB_map.set_sequencer
(m_apb_agent.m_sequencer, reg2apb);
   
end
 
end
 
//Далее следует код подключения предиктора
endfunction: connect_phase

Подключение предиктора

По умолчанию используется явное предсказание для обновления значений в регистровой модели, каждый раз когда приходят транзакции чтения/записи. Явное предсказание требует использования компонента, наследуемого от класса  uvm_reg_predictor.

Reg predictor.gif

(http://uvm-stage.mentor.com//w/images/9/9c/Reg_predictor.gif)

Компонент uvm_reg_predictor наследуется от uvm_subscriber и параметризируется типом анализируемой транзакции. Он содержит указатель на адаптер и карту адресов регистровой модели. Для его использования нужно выполнить следующие действия

  1. Определить указатель на предиктор, параметризировав его типом транзакции.
  2. Создать предиктор в компоненте uvm_env в build фазе.
  3. Назначить карту адресов регистровой модели.
  4. Назначить адаптер соответствующего физического интерфейса.
  5. Подключить порт bus_in в connect_phase фазе к монитору соответствующего физического интерфейса.

Если к регистрам может осуществляться доступ по нескольким интерфейсам, то предикторы следует подключать ко всем мониторам, отслеживающим записи в регистры DUT.

// Объявялем предиктор:
uvm_reg_predictor
#(apb_seq_item) apb2reg_predictor;

function void spi_env::build_phase(uvm_phase phase);
 
if(!uvm_config_db #(spi_env_config)::get(this, "", "spi_env_config", m_cfg)) begin
   `uvm_error
("build_phase", "SPI env configuration object not found")
 
end
 m_cfg
= spi_env_config::get_config(this);
 
if(m_cfg.has_apb_agent) begin
   set_config_object
("m_apb_agent*", "apb_agent_config", m_cfg.m_apb_agent_cfg, 0);
   m_apb_agent
= apb_agent::type_id::create("m_apb_agent", this);
   
// Создаем компонент предиктора
apb2reg_predictor
= uvm_reg_predictor #(apb_seq_item)::type_id::create ("apb2reg_predictor", this);
 
end
endfunction:build_phase

function void spi_env::connect_phase(uvm_phase phase);
 
// задаем карту адресов, с которой будет работать предиктор
 apb2reg_predictor.map
= m_cfg.spi_rm.APB_map;
 
// задаем адаптер, который будет использовать предиктор для

  // конвертирования полученных от монитора транзакций в транзакции

  // регистровой модели

  apb2reg_predictor.adapter = reg2apb;
 
// подключаем порт монитора к порту предиктора
 m_apb_agent.ap.connect
(apb2reg_predictor.bus_in);
endfunction : connect_phase


Встроенные тесты

В 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);
`uvm_object_utils
(spi_bus_base_seq)

// объявляем указатель на регистровую модель

spi_reg_block spi_rm;
// объявляем указатель на объект конфигурации
spi_env_config m_cfg
;
// Объявляем атрибуты, которые будем использовать для

// операций с регистрами
rand  uvm_reg_data_t data; // Для сохранения данных
uvm_status_e
status;       // Для сохранения статуса

function new(string name = "spi_bus_base_seq");
 
super.new(name);
endfunction

task body;
// Получаем указатель на объект конфигурации

  if(!uvm_config_db #(spi_env_config)::get(null, get_full_name(), "spi_env_config", m_cfg)) begin
   `uvm_error
("body", "Could not find spi_env_config")
 
end
// Из объекта конфиграции получаем указатель на регистровую модель

  spi_rm = m_cfg.spi_rm;
endtask: body
endclass: spi_bus_base_seq

// Наследуемся от базовой сиквенсы и реализуем сценарий

class ctrl_set_seq extends spi_bus_base_seq;

`uvm_object_utils
(ctrl_set_seq)

function new(string name = "ctrl_set_seq");
 
super.new(name);
endfunction

bit int_enable = 0;

task body;
 
super.body;
 
// Вызываем метод randomize, который позволяет генерировать случайное значение атрибута value, для регистра ctrl_reg
 
if(!spi_rm.ctrl_reg.randomize() with {char_len.value inside {0, 1, [31:33], [63:65], [95:97], 126, 127};}) begin
   `uvm_error
("body", "Control register randomization failed")
 
end
 
// Устанавливаем в нашей регистровой модели значение поля ie из регистра ctrl_reg, равное значению переменной int_enable
 spi_rm.ctrl_reg.ie.
set(int_enable);
 
//Устанавливаем в нашей регистровой модели значение поля go_bsy из регистра ctrl_reg, равное 0
 spi_rm.ctrl_reg.go_bsy.
set(0);
 
// Инициируем команду обновления регистров в DUT согласно нашей текущей регистровой модели (выполняем команды записи), по средствам транзакций на шине.

  spi_rm.ctrl_reg.update(status, .path(UVM_FRONTDOOR), .parent(this));
 
// В поле data помещаем значение регистра ctrl_reg после проведения

  // операций записи (реально на шине транзакции не произойдет)

  data = spi_rm.ctrl_reg.get();
endtask: body
endclass: ctrl_set_seq

Литература

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