Класс, содержащий пакет данных, с помощью последовательности которых можно описать поведение устройства в среде верификации также называют транзакцией. Пакет UVM предоставляет набор классов для работы как с одиночными транзакциями, так и с последовательностями транзакций (сиквенсами), позволяет контролировать время жизни, реализует интерфейсы, позволяющие управлять множеством последовательностей. Иерархия классов представлена на рисунке 6.1.
Рис. 6.1 Иерархия классов для работы с транзакциями
(uvm-1.1d/docs/html/images/uvm_ref_sequence.gif)
Класс uvm_transaction содержит методы и переменные позволяющие хранить и контролировать время создания, выполнения, удаления транзакции, а также методы позволяющие логировать эту информацию и записывать транзакции для последующего их отображения и анализа на временных диаграммах.
Основные методы перечислены в следующей таблице.
accept_tr | Метод позволяет указать, что транзакция была получена принимающим компонентом. Обычно <uvm_driver> вызывает метод uvm_component::access_tr, который в свою очередь вызывает метод из транзакции. Происходит это в тот момент, когда возвращается транзакция из методов get_next_item(), get(), peek(), вызванных драйвером из uvm_driver::seq_item_port. В некоторых протоколах транзакция не запускается на выполнение немедленно после получения. Например, при работе шины после получения транзакции ее выполнение может быть заблокировано до тех пор пока принимающее устройство не будет готово к приему. Поэтому эта функция выполняет следующие действия:
|
do_accept_tr | Функция позволяет реализовать пользовательскую обработку транзакции. Вызывается автоматически в методе accept_tr. Для корректной работы необходимо в функции первым вызывать метод super.do_accept_tr |
begin_tr | Вызов этой функции указывает на то, что начато выполнение транзакции и она не является потомком другой транзакции. Компонент, получивший транзакцию чаще всего и начинает ее выполнение. Обычно драйвер через метод uvm_component::begin_tr, вызывает его перед фактическим выполнением транзакции. Транзакция, исполняемая драйвером, всегда должна принадлежать сиквенсе. В этом случае метод begin_tr вызывается у предка, в данном случае у сиквенса, а затем выполняется метод begin_child_tr, который уже запускает метод из транзакции. Функция выполняет следующие действия:
Возвращаемое значение представляет собой указатель на транзакцию, если активирована запись транзакции в базу. Это значит, что наличие ненулевого указателя будет зависеть от конкретной реализации этого метода. |
begin_child_tr | Запуск функции указывает на то, что транзакция запускается внутри сиквенсы или другой транзакции и был передан указатель на вызвавшую транзакцию. Обычно выполняющий транзакцию компонент, вызывает ее через базовый метод uvm_component::begin_child_tr, сигнализируя о том, что транзакция уже запущена на выполнение. Если указатель родителя равен 0, то функция работает аналогично begin_tr. |
do_begin_tr | Функция позволяющая реализовать пользовательскую обработку. Для корректной работы необходимо вызывать super.do_begin_tr. |
end_tr | Вызов функции сигнализирует о том что выполнение транзакции закончено. Обычно выполняющий транзакцию компонент вызывает эту функцию. Для корректного вызова этой функции вручную нужно учитывать, что перед ее вызовом должны отработать функции begin_tr и begin_child_tr. Обычно драйвер вызывает метод uvm_component::end_tr, внутри которого вызывается метод из транзакции, после получения подтверждения, что транзакция закончилась. Также драйвер в транзакцию всегда сохраняет указатель, вызвавшей ее сиквенсы. В этом случае метод begin_tr передает этот указатель в метод begin_child_tr. Функция выполняет следующие действия:
|
do_end_tr | Функция позволяющая реализовать пользовательскую обработку. Для корректной работы необходимо вызывать super.do_end_tr. |
get_tr_handle | Функция возвращает указатель объкта из которого была вызвана транзакция на выполнение. Этот указатель используется в методах begin_child_tr и begin_tr для начала записи транзакции. |
disable_recording | Выключает запись потока транзакций. Этот метод не имеет эффекта на метод записи компонентов. |
enable_recording | Включает поток записи транзакций. Интерпретация этого метода зависит от симулятора и реализации этой функций записи в нем. По умолчанию должен быть реализован аргумент командной строки позволяющий активировать запись. Если запись в базу включена то она происходит когда транзакция стартует и когда транзакция заканчивается |
is_recording_enabled | Возвращает 1, если запись транзакций включена, |
is_active | Возвращает 1, если выполнение транзакции было начато, но еще не закончено. Возвращает 0, если транзакция еще не была запущена на выполнение. |
get_event_pool | Возвращает пул событий связанных с этой транзакцией. По умолчанию пул содержит следующие события: begin, accespt, end. В этот список могут быть добавлены события для производных объектов. Пул событий имеет тип общий тип используемый для пулов в UVM <uvm_pool #(T)>, а именно uvm_pool#(uvm_event). |
set_initiator | Устанавливает указатель на компонент, который был инициатором запуска этой транзакции. Инициатором может быть компонент, который запустил транзакцию. Разработчик транзакции может определять, какой компонент будет считаться инициатором |
get_initiator | Возвращает указатель на компонент, который был инициатором выполнения транзакции и был установлен с помощью метода set_initiator. |
get_accept_time | Возвращают значения времени доступа, начала и конца транзакции соответственно. |
get_begin_time | |
get_end_time | |
set_transaction_id | Устанавливает идентификационный номер для транзакции. Если не установлен через этот метод, то по умолчанию значение равно -1. При использовании сиквенса для генерации последовательности транзакций, номер назначаетсяа автоматически и позволяет сиквенсеру получать ответ на запросы с заданным идентификационным номером, тем самым можно установить соответствие между транзакциями |
get_transaction_id | Возвращает идентификационный номер транзакции. |
Все перечисленные методы вызываются автоматически при использовании конструкций start_item/finish_item (или `uvm_do* macro), вызываемых внутри сиквенсы, наследуемой от uvm_sequence#(REQ,RSP). А именно будут автоматически назначаться события begin_event, end_events через вызов описанных выше методов. Для назначения разрешений драйверу заполнять поля времени жизни транзакции, симуляторы поддерживают параметр командной строки +define+UVM_DISABLE_AUTO_ITEM_RECORDING, который нужно задавать при компиляции uvm_pkg. Также пользователь самостоятельно может добавить функционал, выполняющий некоторые действия на основе событий расположенных в event_pool.
Для реализации конвейерного протокола, драйвер может сделать пометку в транзакции, что она выполнена (вызов метода finish_item() или `uvm_do макроса) до того как ее передача-выполнение реально завершится на интерфейсе, в этом случае сиквенс может блокировать выполнение до того момента, пока не получит подтверждение о выполнении транзакции через получение ответа. Например:
task uvm_execute(item, ...); |
Простой драйвер с поддержкой конвейера может содержать фазы адреса и данных его реализация представлена на в примере ниже:
task run(); |
Для создания собственных транзакций следует использовать класс uvm_sequence_item, наследуемый от uvm_transaction. Этот класс реализует дополнительные интерфейсы, позволяющие работать с сиквенсером, а также методы вывода сообщений. Этот класс является также базовым для uvm_sequence, поэтому в нем заложены методы позволяющие также работать с сиквенсами. Эти методы перечислены в следующей таблице.
get_sequence_id | Внутренний метод, который не предназначен для пользовательского кода. Переменная sequence_id это не просто целое число, запрос с использованием этой функции на получение номера является сигналом для пользователя о том, что созданная сиквенс маркирована и к ней можно получить доступ по этому номеру. Этот номер используется для того, чтобы сиквенсер мог отправить ответ обратно в сиквенс транзакцию - ответ на конкретную транзакцию запрос. Этот метод вызывается автоматически через сиквенсер, в тот момент когда инициируется выполнение сиквенсы на сиквенсере. sequence_id является уникальным номером до тех пор пока сиквенс не завершится или не будет прервана методом kill. Однако допускается существование нескольких сиквенсов с одинаковым идентификационным номером. Если запустить сиквенс снова после того как она выполнилась и уже получила уникальный номер, то будет назначен новый уникальный номер. В свою очередь переменная transaction_id также назначается автоматически внутри сиквенса каждый раз когда созданная транзакция отправляется в сиквенсер. Значение по умолчанию переменой transaction_id равно -1. Если пользователь самостоятельно выставит значение переменной transaction_id то оно будет сохранено и использоваться в дальнейшем для идентификации транзакции. Ответ передаваемый обратно из драйвера в сиквенсер, а из него в сиквенс основывается на значении переменной sequence_id. В свою очередь сиквенсы могут использовать transaction_id для получения соответствующей транзакции ответа на запрос отправленный ранее. |
set_item_context | Метод помещает указатели сиквенсы и сиквенсера в транзакции которые выполняются в них |
set_use_sequence_info | Этот метод позволяет установить и получить значение бита use_sequence_info. Этот бит контролирует печать, копирование или запись информации сиквенса. По умолчанию значение этого бита равно 0, и это означает что информация о сиквенсе нигде не храниться и не используется. Если установить этот бит то информация о сиквенсе будет храниться и передаваться при копировании и печати этой сиквенсы |
get_use_sequence_info | |
set_id_info | Метод позволяет скопировать sequence_id и transaction_id из другой сиквенсы или транзакции в текущую. Главное назначение этого метода в том, чтобы пользователь мог отправить транзакцию ответа, в этом случае необходима скопировать данные о уникальных номерах сиквенса и транзакции из транзакции запроса в транзакцию ответа с помощью этого метода. |
set_sequencer | Метод позволяет установить сиквенсер по умолчанию на котором будет выполняться сиквенс или все ее внутренние таски. Вызов этого метода происходит автоматически если мы запускаем сиквенс через метод start(указатель_на_сиквенсер). Если мы хотим выполнять таски описанные внутри сиквенсы по своему усмотрению, то нужно с помощью этого метода указать на каком сиквенсере они будут выполняться а затем вызывать задачи(tasks) из сиквенсы. Этот метод выполняется немедленно без ожидания каких либо событий, поэтому не следует в ходе выполнения какой либо сиквенсы изменять сиквенсер на другой. |
get_sequencer | Возвращает указатель на сиквенсер, который установлен по умолчанию для сиквенсы. |
set_parent_sequence | Методы позволяет установить указатель на сиквенс, которой принадлежит транзакция. По умолчанию транзакция не принадлежит сиквенсе и имеет нулевой указатель. |
get_parent_sequence | Методы позволяет получить указатель на сиквенс, которой принадлежит транзакция. Если транзакция не создавалась/запускалась в сиквенсе, то будет возвращен нулевой указатель. |
set_depth | Метод позволяет установить вложенность сиквенсы. По умолчанию вложенность вычисляется автоматически. Однако пользователь может её изменить. |
get_depth | Возвращает значение вложенности сиквенсы. Если в сиквенсе есть подсиквенс, то ее вложенность будет равно 1, а подсиквенсы 2. Если в подсиквенсе запускается еще одна сиквенс то ее вложенность будет равно 3, и т.д. |
is_item | Метод может быть вызван как в сиквенсе так и в транзакции и позволяет отличить одно от другого, если значение 1 то транзакция, если 0 то сиквенс. |
get_root_sequence_name | Предоставляет имя сиквенсы самого верхнего уровня. |
get_root_sequence | Предоставляет указатель на сиквенс самого верхнего уровня. |
get_sequence_path | Возвращает строку представляющую собой полное иерархическое имя сиквены, имя каждой вложенной сиквенсы отделяется символом “.”. |
uvm_report | Транзакция и сиквенс будут использовать сиквенсер для вывода сообщений, если сиквенсер не установлен, то используется глобальный объект вывода сообщений. Вызов этих методов осуществляется через методы вывода сообщений класса uvm_component или uvm_report_object. |
uvm_report_info | |
uvm_report_warning | |
uvm_report_error | |
uvm_report_fatal |
Рекомендуется классы транзакций, используемых в среде верификации, наследовать от класса uvm_sequence_item. Также рекомендуется регистрировать транзакцию с помощью макросов регистрации `uvm_object_utils, и все поля транзакции с помощью макросов `uvm_field_*. Эти макросы регистрируют объект для дальнейшего использования его в механизме фабрики (подмена типа объекта без изменения кода), а также реализуют все функции копирования, сравнения, клонирования и вывода для переменных класса.
Существует возможность в макросах для регистрации полей, указать какие методы в макросе не будут реализованы. Для этого в макрос регистрации поля передается параметр. Каждому значению параметра соответствует бит вектор, у которого установлен только один бит, кроме UVM_ALL_ON, UVM_ALL_ON. Поэтому допускается выбор одновременно нескольких параметров, и задаются они через знак “побитового или” - “|”. В следующей таблице представлено описание параметров, которые можно использовать при регистрации атрибутов класса транзакции.
UVM_ALL_ON | Включает реализацию всех доступных в макросе методов |
UVM_DEFAULT | В версии пакета UVM-1.1d работает аналгично UVM_ALL_ON, но в дальнейшем если набор функций расширится то значение по умолчанию останется на текущем значении, а UVM_ON_ALL будет расширен |
UVM_NOCOPY | Отключает реализацию метода копирования |
UVM_NOCOMPARE | Отключает реализацию метода сравнения |
UVM_NOPRINT | Отключает реализацию метода печати в лог |
UVM_NOPACK | Отключает реализацию метода упаковки |
Ниже представлен пример использования регистрации в фабрике при описании класса транзакции.
`ifndef APB_ITEM__SV `define APB_ITEM__SV class apb_item extends uvm_sequence_item; typedef enum {READ, WRITE} kind_e; rand bit [31:0] addr; rand logic [31:0] data; rand kind_e kind; rand bit [3:0] strb; `uvm_object_utils_begin(apb_item) `uvm_field_int(addr, UVM_ALL_ON | UVM_NOPACK| UVM_NOCOMPARE); `uvm_field_int(data, UVM_ALL_ON | UVM_NOPACK); `uvm_field_int(strb, UVM_ALL_ON | UVM_NOPACK); `uvm_field_enum(kind_e,kind, UVM_ALL_ON | UVM_NOPACK); `uvm_object_utils_end
function new (string name = "apb_item"); super.new(name); endfunction function string convert2string(); return $sformatf("kind=%s addr=%0h data=%0h",kind,addr,data); endfunction endclass: apb_item `endif // APB_ITEM |
Сиквенс представляет собой класс, наследуемый от uvm_sequence, в котором реализован механизм, позволяющий генерировать наборы транзакций, отправлять их на выполнение через сиквенсер в драйвер, а также управлять процессом выполнения этих транзакций. В свою очередь uvm_sequence наследуется от uvm_sequence_base.
Рассмотрим интерфейсы, реализованные в этих классах.
Методы создания и управления запуском | |
is_item | Возвращает 1, если на основе этого класса создана транзакция, или 0, если сиквенс. Так как, по умолчанию, этот класс используется как базовый для сиквенсы, то функция всегда возвращает 0. |
get_sequence_state | Возвращает состояние сиквенса в виде перечислимого типа uvm_sequence_state_enum UVM_CREATED UVM_PRE_START UVM_PRE_BODY UVM_BODY UVM_ENDED UVM_POST_BODY UVM_POST_START UVM_STOPPED UVM_FINISHED по значениям которого можно отслеживать фазы выполнения сиквенсы. |
wait_for_sequence_state | Метод позволяет блокировать поток выполнения до тех пор, пока сиквенс не перейдет в заданное состояние, если сиквенс уже в одном из указанных состояний то управление возвращается немедленно. wait_for_sequence_state(STOPPED|FINISHED); |
start | virtual task start ( uvm_sequencer_base sequencer, uvm_sequence_base parent_sequence = null, int this_priority = -1, bit call_pre_post = 1 ) Основной метод запуска сиквенса на выполнение, он блокирует поток выполнения до тех пор, пока сиквенс не будет выполнена. Передаваемый параметр sequencer представляет собой указатель на сиквенсер, на котором требуется запустить созданную сиквенс, при этом сиквенс и сиквенсер должны быть совместимы. Если parent_sequence равен null, то эта сиквенс считается корневой, в противном случае сиквенс считается принадлежащей другой сиквенсе. Методы pre_do, mid_do, post_do по умолчанию вызываются автоматически в процессе выполнения сиквенсы. По умолчанию параметр priority равен параметру родительской сиквенсы и равен 100. Другие приоритеты могут быть заданы используя аргумент this_priority. Чем больше число тем больше приоритет. Параметр call_pre_post отвечает за вызов методов сиквенсы pre_body, post_body. По умолчанию его значение равно 1, поэтому, если не задать обратного методы будут вызываться по умолчанию также. |
pre_start | Метод, позволяет реализовать пользовательский функционал, который будет вызваться перед выполнением pre_body. Не следует вызывать его вручную. |
pre_body | Метод, позволяет реализовать пользовательский функционал, который будет вызываться перед выполнением pre_body. Этот метод будет выполняться только, если сиквенс запускается через метод start. Если start вызывается с параметром call_pre_post равным 0, то метод pre_body не будет вызван. Этот метод не должен вызываться пользователем вручную. |
pre_do | Это пользовательская задача, реализующий пользовательский функционал. Задача запускается для сиквенсы родителя, если какая-либо сиквенс выполнила запрос wait_for_grant() и сиквенсер выбрал ее на выполнение. Или если выполняется транзакция, то перед ее рандомизацией. Не смотря на то, что pre_do это задача, при ее реализации не рекомендуется реализовывать действия с потреблением времени моделирования, это может привести к непредсказуемому поведению драйвера. Этот метод не должен вызываться пользователем вручную. |
mid_do | Функция позволяет реализовать пользовательскую обработку, причем ее вызов происходит автоматически, после того как транзакция была рандомизирована, но перед тем как отправлена в драйвер. Метод не должен вызываться пользователем вручную. |
body | Эта задача, в которой размещается весь основный код пользовательского алгоритма генерации транзакции. Метод не следует вызывать вручную. |
post_do | Функция реализуется пользователем и вызывается автоматически после того как драйвер сообщит о том что транзакция завершена, используя item_done или put метод. Метод не следует вызывать вручную. |
post_body | Метод реализуется пользователем и вызывается автоматически после того как выполнился метод body, в том случае если для запуска сиквенсы использовался метод start(). Если start(...) был вызван с параметром call_pre_post = 0, то post_body не будет вызываться. Задачу не следует вызывать вручную. |
post_start | Метод реализуется пользователем и вызывается автоматически после того как выполнится post_body. Метод не следует вызывать вручную. |
starting_phase | Переменная содержит указатель на объект фазы, если он не нулевой то его значение будет определять фазу в которой была запущена сиквенс. Переменная starting_phase устанавливается автоматически, если мы используем метод запуска сиквенса через назначение сиквенсы по умолчанию. Если мы не используем назначение сиквенсы по умолчанию, то значение переменной будет назначено в методе uvm_sequencer_base::start_phase_sequence, и будет соответствовать фазе сиквенсера. В свою очередь uvm_sequencer_base::start_phase_sequence запускается с помощью задач обработки фаз компонент (uvm_task_phase.svh) Эта переменная позволяет управлять процессом моделирования. Обычно приходится управлять объектами выполнения вручную, но можно добавить в сиквенс проверку и включение объекта выполнения автоматически. virtual task user_sequence::body(); starting_phase.raise_objection(this, "user_seq not finished"); if (starting_phase != null) starting_phase.drop_objection(this, "user_seq finished"); |
Методы для управления приоритетом запуска | |
set_priority | Приоритет последовательности может изменять в различные моменты времени. Если приоритет был изменен то сиквенсер будет использовать его в дальнейшем при запуске сиквенсов. Значение по умолчаниб для сиквенса равно 100. Чем выше значение тем выше приоритет выполнения у сиквенсы. |
get_priority | Функция позволяет получить текущий приоритет сиквенсы |
is_relevant | По умолчанию функция возвращает значение 1, которое указывает что сиквенс всегда актуальна. Пользователь может переопределить поведение функции, для того чтобы указывать сиквенсеру что после выполнения функции сиквенс больше не актуальна. Перед запуском сиквенсер каждый раз запрашивает состояние сиквенсы и приоритет, если сиквенс скажет что она не актуальна то сиквенсер не будет ее больше запускать пока она снова не станет актуальной в будущем. Если все сиквенсы которые может опрасить сиквенсер не актуальны то вызывается задача wait_for_relevant для всех сиквенсов и выполняется подсчет и опрос приоритетов по новому. Каждый кто реализует функцию is_relevant() должен реализовывать метод wait_for_relevan()t, который позволит сиквенсеру приостановить выполнение и подождать пока не появиться актуальных сиквенсов |
wait_for_relevant | Этот метод вызывается сиквенсером, если все запрошенные сиквенсы не актуальны. Когда wait_for_relevant возвращает поток управления сиквенсер выполняет операцию арбитража заново. Возвращения потока выполнения не гарантирует того, что появились актуальные сиквенсы, хотя в идеале так должно быть. Этот метод представляет задержку опроса сиквенсов в бесконечном цикле.
Если метод is_relevant реализован таким образом, что он может возвращать статус “не актуально”, (по умолчанию метод всегда говорит, что сиквенса актуальна), то сиквенса должна обязана иметь реализацию wait_for_relevant |
lock | Функция выполняет запрос на блокировку и перехват потока выполнения у сиквенсера. Если сиквенсер задан как null, то запрос блокировки будет выполнен на текущем сиквенсере, по умолчанию. Этот запрос обрабатывается аналогично, остальным запросам. Запрос lock получит поток выполнения после того как все запросы вызванные перед этим выполняться, и после никакой другой заброс lock или grab не сможет получить доступ к потоку выполнения до тех пор пока блокировка не будет снята или запущенная сиквенс выполнится. |
grab | Выполняет запрос на блокировку на заданном сиквенсере. Если не передавать аргументы, запрос блокировки будет выполнен на текущем сиквенсере по умолчанию. Запрос grab поставит в сиквенс на выполнение перед всеми другими сиквенсами расположенными в очереди приоритетов. Т.е. при следующей проверка статуса сиквенса, самой приоритетной будет вызвавшая этот запрос, и она без ожидания завершения предыдущих сиквенсов начнет подавать транзакции. Это основное отличие от запроса lock. После завершения поток выполнения будет снова передан для определения приоритета выполнения. Запрос grab получит поток выполнения для сиквенсы, если никакие другие сиквенсы не выполняют в этот момент grab или lock сиквенсера. |
unlock | Удаляет все запросы lock или grab выполненные из сиквенсы для сиквенсера. Если сиквенсер null, запрос будет выполнен на сиквенсере по умолчанию. |
ungrab | |
is_blocked | Функция возвращает бит который указывает на то является ли сиквенс заблокированной в настоящий момент из-за того что был сделан запрос lock или grab из другой сиквенсы. Если возвращает 1, то сиквенс заблокирована, иначе нет. Если сиквенс не заблокирована, то любая другая сиквенс может прервать и заблокировать ее выполнение. |
has_lock | Возвращает 1 если в сиквенс был вызван запрос lock, иначе 0. Если сиквенс вызвала lock, дочерние сиквенсы могут также вызывать запрос lock самостоятельно, в этом случае дочерняя сиквенс будет иметь статус блокированной для выполнения ее на сиквенсере. |
kill | Эта функция прервет выполнение сиквенсы и снимет все блокировки с сиквенсера по умолчанию, если во время выполнения сиквенсы такие были. Сиквенсер перейдет в состояние STOPPED,и методы post_body() и post_start() исполняться не будут. Если сиквенс выполнила lock или grab запрос на сиквенсер отличный от сиквенсера по умолчанию, следует позаботиться, чтобы обязательно выполняется отмена регистрации сиквенса на сиквенсере, с помощью метода сиквенсера unregister_sequence(). |
do_kill | Функцию можно переопределить и реализовать в ней дополнительный функционал. Вызов будет происходить автоматически в тот момент, когда выполнение сиквенсы прерывается функциями sequence.kill() или sequencer.stop_sequences() (вызов последней более эффективен). |
create_item | Позволяет создать и инициализировать транзакцию или сиквенс с использованием фабрики. Также позволяет ассоциировать создаваемую транзакцию с сиквенсером на котором ее в дальнейшем будут запускать. |
start_item | Метода start_item, finish_item вместе запускают обработку транзакции. Если транзакция не создавалась перед этим с использованием create_item, то в этом методе она будет назначена на выполнение на сиквенсер по умолчанию, указатель на который храниться в переменной m_sequencer. Рекомендуется рандомизацию транзакции делать между вызовами функций start_item, finish_item, это гарантирует, что транзакция сгенерируется перед отправлением ее на драйвер. |
finish_item | Метод сигнализиует о том что транзакция создана и заполнена. Между вызовами методов start_item/finish_item не должно быть дельта-циклов или задержек с потреблением времени, допускается любая обработка транзакции или ее генерация без потребления времени. |
wait_for_grant | Эта задача отправляет запрос на разрешение выполнения сиквенсы на сиквенсере. Если параметр item_priority не установлен, то будет использовано текущее значение приоритета сиквенсы. Если был выполнен запрос lock_request, в тот момент когда сиквенсер выполнял другую транзакцию, то блокировка будет выполнена немедленно перед завершением запроса на разрешение. (Важно что блокировка сиквенсы может быть выполнена и без того что сиквенс получила разрешение на выполнение, при условии что параметр is_relevant не установлен. ) Когда задача заканчивает выполнение, сиквенсер выбирает сиквенс для выполнения, и она должна послать send_request без какой либо задержки симуляционного времени, даже без дельта цикла. Вызов метода send_request отправит драйверу следующую транзакцию. |
send_request | Эту функцию допускается вызывать только после wait_for_grant. Ее вызов отправит запрошенную транзакцию сиквенсеру, который пришлет ее драйверу. Если бит, отвечающий за рандомизацию установлен в 1, то перед тем как отправить транзакцию драйверу, она будет рандомизирована. |
wait_for_item_done | Внутри сиквенса допускается по необходимости пользователя вызывать задачу wait_for_item_done. Эта задача блокирует выполнение внутри сиквенса до тех пор пока драйвер не вызовет item_done или put. Если параметр transaction_id не установлен, то вызов этого метода вернет выполнение, когда драйвер вышлет подтверждение для соответствующей транзакции. Стоит обратить внимание что если transaction_id был задан и драйвер уже выполнил item_done или put, для этой транзакции, то задача продолжит выполнение, пропустив предыдущее уведомление. |
Методы для получения ответа | |
use_response_handler | function void use_response_handler(bit enable) Если вызвать метод с входным параметром равным 1, то ответ будет высылаться через вызов метода response_handler, иначе ответ должен быть извлечен с помощью метода get_response. По умолчанию ответ передаётся через вызов метода get_response, альтернативой этому методу является вызов функции response_handler. |
get_use_response_handler | Возвращает значение use_response_handler бита. |
response_handler | Если бит use_response_handler имеет значение 1, то эта задача вызывается сиквенсером для получения ответа и передачи его в сиквенс. |
set_response_queue_error_report_disabled | По умолчанию флаг отвечающий за выдачу сообщений об ошибке при переполнении включен. Если много ответов было выслано в сиквенсер, и при этом ответы никто не забирает, то очередь ответов при достижении заданного значения считается переполненной и будет выдано предупреждение что ответов получено больше чем запрошено. Если установить бит в 0, то генерация ошибки будет отключена. |
get_response_queue_error_report_disabled | Функция позволяет получить значение бита отвечающего за генерацию ошибки при переполнении очереди ответов. |
set_response_queue_depth | Позволяет установить размер очереди ответов, по умолчанию она имеет величину равную 8. Если установить значение равное -1, то это будет означать что очередь ответов не имеет ограничения на глубину и не будет проверятся. |
get_response_queue_depth | Функция позволяет получить текущее значение глубины очереди ответов |
clear_response_queue | Функция очищает очередь ответов, для сиквенсы из которой была вызвана. |
Методы класса uvm_sequence | |
send_request | Этот метод позволяет отправить транзакцию запроса в сиквенсер, который переправит ее драйверу. Если бит рандомизации включен, то транзакция перед отправкой в драйвер будет рандомизирована. Эту функцию допускается вызывать только после выполнения задачи uvm_sequence_base::wait_for_grand |
get_current_item | Функция возвращает транзакцию которая в текущий момент выполняется на сиквенсере. Если сиквенсер не выполняет в текущий момент никаких транзакций, метод вернет null. Транзакция считается выполняющейся на сиквенсере, в моменты времени после вызова метода get_next_item() или peek() до момента вызова метода get() или item_done(). Стоит обратить внимание что драйвер который вызывает только метод get() никогда не будет иметь текущей исполняемой транзакции, так как элемент будет считаться завершенным сразу же как он был получен. |
get_response | Метод который по умолчанию должен использоваться в сикевенсах для получения ответа. Если transaction_id не задан при вызове этого метода, эта задача вернет следующий ответ отправленный в сиквенс. Если нет доступных ответов в очереди, то задача заблокирует поток выполнения до тех пор пока не появится в очереди транзакция ответа. Если при вызове задачи указан transaction_id, задача заблокирует поток выполнения до тех пор пока в очереди ответов не появится транзакция с заданным id. По умолчанию размер очереди ответов равен 8. Поэтому для корректной работы по умолчанию задержка вызова метода получения ответа не должна превышать 8 транзакций, иначе будет генерироваться ошибка переполнения очереди ответов, и последующие ответы будут теряться. Генерацию ошибки и потерю транзакций можно избежать вызвав метод set_response_queue_error_report_disabled, и при этом изменив размер очереди с помощью метода set_response_queue_depth. |
Для запуска сиквенсы на выполнение, нужно указать сиквенсер, на котором она будет выполняться, и вызвать метод стартующий ее выполнение. Или запустить используя макрос.
sub_seq.randomize(...); // optional |
После вызова этого метода из сиквенсы будет назначен сиквенсер, а также выполнены методы сиквенса в следующем порядке:
sub_seq.pre_start() (task) |
Последовательный запуск перечисленных выше методов следует учитывать и использовать при проектировании сиквенсов. Обычно в методах pre_body и post_body проверятся создан и активен ли объект выполнения, и если нет, то активируется. Это позволяет сиквенсе самой инициировать выполнение и гарантирует, что она выполнится до конца независимо от внешнего управления.
Далее рассматриваются UVM макросы, которые позволяют запустить сиквенс на сиквенсере по умолчанию. При этом нет необходимости создавать сиквенс (объект класса) с помощью вызова конструктора new, т.к. в зависимости от макроса объект сиквенса будет создан и рандомизирован внутри макроса.
`uvm_create | `uvm_create(SEQ_OR_ITEM) Макрос содержит код, который создает транзакцию или сиквенс с использованием фабрики. Макрос делает немедленную обработку без потребления времени. После выполнения макроса, пользователь может устанавливать значения транзакции вручную, также устанавливать режим рандомизации, включать или выключать ограничения. |
`uvm_do | `uvm_do(SEQ_OR_ITEM) Макрос получает на вход аргумент являющийся uvm_sequence_item null-указателем или указателем, который хранит ссылку на уже созданный объект. Макрос пересоздает объект с указанным в аргументе именем с помощью `uvm_create. Если переданный указатель/объект представляет собой тип транзакции (uvm_sequence_item), то она будет запущена на сиквенсере с помощью конструкции start_item()/randomize()/finish_item, если в макрос передается сиквенс (класс производный от uvm_sequence_base), то будет вызван метод uvm_sequence_base::start(), при этом методы pre и post в сиквенсах будут отключены Для транзакции будут производиться следующие действия `uvm_create(item) Для сиквенсы следующие действия `uvm_create(sub_seq) |
`uvm_do_pri | `uvm_do_pri(SEQ_OR_ITEM, PRIORITY) Макрос работает аналогично макросу `uvm_do за исключением, что транзакция или сиквенс исполняются с заданным приоритетом. Величина приоритета находится в диапазоне значений от 0 до 100, это значение определяет, какая из запущенных одновременно в параллель сиквенсов получает управление при запуске на сиквенсере. |
`uvm_do_with | `uvm_do_with(SEQ_OR_ITEM, CONSTRAINTS) Макрос работает аналогично макросу `uvm_do только позволяет во втором аргументе добавить ограничения на генерацию переменных внутри сиквенсы. Ограничения задаются в фигурных скобках “{}” и будут добавлен при вызове функции randomize() with CONSTRAINTS; |
`uvm_do_pri_with | `uvm_do_pri_with(SEQ_OR_ITEM, PRIORITY, CONSTRAINTS) Макрос работает аналогично макросу `uvm_do_pri только позволяет добавить в третьем аргументе ограничения на генерацию переменных перед выполнением. |
Также в UVM реализованы макросы, которые используются для запуска сиквенсов и позволяют указать сиквенсер, на котором они будут запущены. Эти макросы также создадут и рандомизируют объект сиквенса.
`uvm_create_on | `uvm_create_on(SEQ_OR_ITEM, SEQR) `uvm_do_on(SEQ_OR_ITEM, SEQR) `uvm_do_on_pri(SEQ_OR_ITEM, SEQR, PRIORITY) `uvm_do_on_with(SEQ_OR_ITEM, SEQR, CONSTRAINTS) `uvm_do_on_pri_with(SEQ_OR_ITEM, SEQR, PRIORITY, CONSTRAINTS) Эти макросы работает также как и аналогичные макросы из предыдущей таблицы, только позволяет вдобавок к существующему функционалу указать вторым параметром сиквенсер, на котором нужно запустить выполнение транзакции или сиквенса. |
`uvm_do_on | |
`uvm_do_on_pri | |
`uvm_do_on_with | |
`uvm_do_on_pri_with |
В UVM также реализованы макросы, которые используются для запуска сиквенсов, которые уже созданы, т.е. сиквенc не будет создаваться перед запуском.
`uvm_send | `uvm_send(SEQ_OR_ITEM) Макрос аналогичен макросу `uvm_do только без создания и рандомизации объекта. |
`uvm_send_pri | `uvm_send_pri(SEQ_OR_ITEM, PRIORITY) Макрос аналогичен макросу `uvm_send, вдобавок позволяет задать приоритет запускаемой транзакции или сиквенса. |
`uvm_rand_send | `uvm_rand_send(SEQ_OR_ITEM) Макрос аналогичен макросу `uvm_do только без создания объекта |
`uvm_rand_send_pri | `uvm_rand_send_pri(SEQ_OR_ITEM, PRIORITY) Макрос аналогичен макросу `uvm_rand_send, вдобавок позволяет задать приоритет запускаемой транзакции или сиквенса. |
`uvm_rand_send_with | `uvm_rand_send_with(SEQ_OR_ITEM, CONSTRAINTS) макрос аналогичен макросу `uvm_rand_send с той лишь разницей, что этот макрос позволяет добавить дополнительные ограничения, которые будут добавлены при вызове функции randomize () with CONSTRAiNT. |
`uvm_rand_send_pri_with | `uvm_rand_send_pri_with(SEQ_OR_ITEM, PRIORITY, CONSTRAINTS) Макрос выполняет аналогичные `uvm_rand_send_pri макросу действия с той лишь разницей что этот макрос позволяет добавить дополнительные ограничения, которые будут добавлены при вызове функции randomize () with CONSTRAINT. |
Также в UVM при разработке сиквенсов которые возможно запускать только на определённых сиквенсерах, реализован макрос, который позволяет установить сиквенсер по умолчанию. Это в свою очередь позволит получить доступ к переменным сиквенсера.
`uvm_declare_p_sequencer | `uvm_declare_p_sequencer(SEQUENCER) Этот макрос используется для задания переменной p_sequencer внутри сиквенсы и переопределения метода m_set_p_sequencer. Что в свою очередь при попытке запустить сиквенс на отличном от указанного сиквенсере (не являющимся расширением указанного) приведет к ошибке и сообщению о том что сиквенс не предназначена для этого сиквенсера. Такой макрос удобно использовать, если в сиквенсе используются поля сиквенсера (порты, переменные, указатель на компонент, и т.д.) Пример использования этого макроса приведен ниже. class mysequence extends uvm_sequence#(mydata); |
В UVM реализован механизм запуска сиквенс на сиквенсере, для этого достаточно после создания сиквенсы передать указатель на нее через UVM базу конфигурации uvm_config_db. Если это выполнить, то сиквенсер в заданной фазе автоматически проверит назначена ли сиквенс и выполнит все сопутствующие действия - запустит ее на выполнение. Сделать это можно двумя способами.
myseq_t myseq = new("myseq"); myseq.randomize() with { ... }; uvm_config_db #(uvm_sequence_base)::set(null, "top.agent.myseqr.main_phase", "default_sequence", myseq); |
Конфигурация с использованием типа транзакции короче, но есть еще один способ с использованием механизма фабрики.
uvm_config_db #(uvm_object_wrapper)::set(null, "top.agent.myseqr.main_phase", "default_sequence", myseq_type::type_id::get()); |
Также можно использовать uvm_resource_db для настройки сиквенсы выполняемой по умолчанию.
myseq_t myseq = new("myseq"); myseq.randomize() with { ... }; uvm_resource_db #(uvm_sequence_base)::set({get_full_name(), ".myseqr.main_phase", "default_sequence", myseq, this); |
Или еще один способ.
uvm_resource_db #(uvm_object_wrapper)::set({get_full_name(), ".myseqr.main_phase", "default_sequence", myseq_t::type_id::get(), this ); |
Назначение сиквенсы по умолчанию удобно использовать для запуска различных сиквенсов из одного теста (см. Переопределение в тесте запускаемых сиквенсов).
Допустим интерфейс содержит сигналы, задающие стробы записи и требуется их проверить. В таком случае нужно генерировать последовательность транзакций содержащих все возможные состояния. Можно создавать множество случайных транзакций общим количеством примерно 1/6 от всего числа возможных состояний.
Если требуется перебрать случайным образом все значения без повторений, то нужно объявить проверяемые поля как тип randc и выполнить рандомизацию поля n-е число раз, где n - число возможных состояний это поля.
Класс сиквенса параметризуется типом используемой транзакции. Внутри сиквенсы объявляется объект транзакции. Регистрируется класс с использованием `uvm_object_util макроса, а также объекты транзакции, это позволит использовать механизм фабрики для подмены сиквенсы, а также позволит реализовать методы копирования, сравнения и печати для сиквенсы.
В методе body() создается транзакция с помощью механизма фабрики (используется метод create), далее вызывается встроенный метод генерации случайных значений для всех переменных объявленных как rand, randc, без дополнительных ограничений. Далее выполняется метод start_item(item), передающий транзакцию драйверу. Драйвер же ожидает вызова этого метода, и после начинает обработку транзакции. Далее в сиквенсе вызывается метод finish_item(), который блокирует выполнение сиквенса и ждет выполнения функции item_done() внутри драйвера. После того как драйвер вызывает item_done(), сиквенс продолжает выполнение. Диаграмма общения драйвер-сиквенсер-сиквенс в данном случае выглядит следующим образом, как показано на рисунке.
http://uvm-stage.mentor.com//w/images/thumb/9/9d/Get_next_item_item_done_uml.gif/500px-Get_next_item_item_done_uml.gif
В следующем примере создается 20 случайных транзакций, для генерации значений полей транзакции можно использовать “лямбда” выражения, которые позволяют добавить ограничения или выбрать требуемые значения.
class apb_base_seq extends uvm_sequence#(apb_item); apb_item item; `uvm_object_utils_begin(apb_base_seq) `uvm_object_field (item, UVM_ALL_ON | UVM_NOPACK); `uvm_object_utils_end function new(string name="apb_base_seq"); super.new(name); endfunction endclass : apb_base_seq class apb_rand_seq extends apb_base_seq; `uvm_object_utils(apb_rand_seq)
function new(string name="apb_rand_seq"); super.new(name); endfunction
virtual task body(); repeat (20) begin item = apb_item::type_id::create("item"); item.randomize(); start_item(item); finish_item(item); //`uvm_do(req) end endtask
endclass : apb_rand_seq |
Если требуется проверить несколько различных последовательностей транзакций, то удобно описать несколько отдельных методов внутри класса сиквесы и далее в методе body() вызвать их в нужном порядке. Данный подход демонстрируется в следующем примере.
class apb_incr_seq extends apb_base_seq; `uvm_object_utils(apb_incr_seq)
int addr_i; int strb_i; int data_i; function new(string name="apb_incr_seq"); super.new(name); endfunction // генерация данных в порядке уменьшения virtual task decr_data(); for(int i=0; i < (1<<4); i++) begin item = apb_item::type_id::create("item"); item.randomize() with { data == data_i; }; start_item(item); finish_item(item); data -= 1000; end endtask // генерация поля strb в порядке возрастания virtual task incr_strb(); for(int i=0; i < (1<<4); i++) begin item = apb_item::type_id::create("item"); item.randomize() with { strb == strb_i; }; start_item(item); finish_item(item); strb_i++; end endtask //генерация поля addr в порядке возрастания virtual task incr_addr(); for(int i=0; i < (1<<32); i++) begin item = apb_item::type_id::create("item"); item.randomize() with { addr == addr_i; }; start_item(item); finish_item(item); addr_i++; end endtask // генерация поля strb и addr virtual task strb_addr(); for(int i=0; i < (1<<32); i++) begin item = apb_item::type_id::create("item"); item.randomize() with { strb inside {4’b1111, 4’b1110,4’b1100,4’b1000 }; addr == addr_i; data[31] == 1; data[7:0] <20; data[7:0] >2; }; start_item(item); finish_item(item); addr_i++; end endtask //Главное тело сиквенса, к котором вызываются таски virtual task body(); strb_addr (); incr_addr (); incr_strb (); decr_data (); endtask endclass : apb_incr_seq |
Для примера выше тело метода run_phase драйвера будет следующим.
task run_phase( uvm_phase phase ); |
Сиквенсы с обратной связью используются в агентах, реализующих функционал пассивных блоков или блоков, работающих в режиме ведомого (slave). В этом случае драйвер получает по интерфейсу запрос с какими-то параметрами, затем этот запрос попадает в сиквенс, где транзакция запроса должна быть заполнена и отправлена на выполнение на драйвер, как ответ на транзакцию запроса. Для получения транзакции ответа от драйвера к сиквенсу реализован метод get_responce(rsp), который позволяет забрать транзакцию ответа. К примеру мы хотим вычитать по интерфейсу значение, инвертировать его и записать в следующую транзакцию, и так для всех ячеек памяти. В этом случае получим следующий код.
class apb_rand_seq extends apb_base_seq; apb_item resp_item; bit [31:0] rdata_i; `uvm_object_utils(apb_rand_seq)
function new(string name="apb_rand_seq"); super.new(name); endfunction
virtual task body(); // запускаем пустую транзакцию для получения первого ответа с данными for(int i=0; i < (1<<32); i++) begin item = apb_item::type_id::create("item"); item.randomize() with { kind == READ; addr == addr_i; }; start_item(item); finish_item(item); get_responce(resp_item); // получаем ответ от драйвера содержащий данные rdata_i = resp_item.read; // сохраняет значение rdata_i = ~ rdata_i; // инвертируем // создаем и отправляем транзакцию на запись item = apb_item::type_id::create("item"); item.randomize() with { kind == WRITE; strb inside {4’b1111, 4’b1110,4’b1100,4’b1000 }; addr == addr_i; read == read_i; }; start_item(item); finish_item(item); addr_i++; end endtask endclass : apb_rand_seq |
В случае ожидания транзакции ответа, важно не забывать что драйверу следует выслать ответ используя в данном случае метод put(), иначе выполнение сиквенсы будет заблокировано.
// |
В этом случае диаграмма общения сиквенсер-драйвер выглядит следующим образом.
http://uvm-stage.mentor.com//w/images/1/16/Get_put_uml.gif
Встречаются задачи, в которых необходимо запускать однообразные сиквенсы, отличающиеся одним или несколькими параметрами. В этом случае удобно сделать сиквенс, которую можно параметризовать. Также удобно часть параметров определить в сиквенсе как rand, и перед запуском рандомизировать сиквенс. В этом случае можно проверить сценарии, в которых сиквеннсы запускаются последовательно со случайными параметрами. При этом существует возможность задать ограничения, как в сиквенсе при ее определили, так и при ее рандомизации.
Далее приведен пример сиквенс со случайными параметрами.
class apb_rand_seq extends apb_base_seq; apb_item resp_item; rand rw_type rw_l; rand length_l;
`uvm_object_utils(apb_rand_seq)
function new(string name="apb_rand_seq"); super.new(name); endfunction
virtual task body(); for(int i=0; i < length_l; i++) begin item = apb_item::type_id::create("item"); item.randomize() with { kind == rw_l; }; start_item(item); finish_item(item); end endtask endclass : apb_rand_seq |
Далее показан тест для запуска сиквенса без дополнительных ограничений на параметры.
class run_item_test extends apb_base_test; typedef apb_rand_seq RUN_SEQ; RUN_SEQ seq0; `uvm_component_utils(run_item_test) function new(string name, uvm_component parent = null); super.new(name, parent); endfunction task main_phase(uvm_phase phase); phase.raise_objection(this); `uvm_info ("TB/TRACE/RUN_ITEM","start sequence", UVM_NONE) repeat (100) begin seq0 = RUN_SEQ::type_id::create("seq0"); seq0.randomize(); seq0.start(env.apb.sqr); end `uvm_info ("TB/TRACE/RUN_ITEM","finish sequence", UVM_NONE) #1000; phase.drop_objection(this); endtask endclass |
Далее показан пример теста для запуска сиквенса с дополнительными ограничениями для случайных параметров.
class run_item_rand_test extends apb_base_test; typedef apb_rand_seq RUN_SEQ; RUN_SEQ seq0; `uvm_component_utils(run_item_rand_test) function new(string name, uvm_component parent = null); super.new(name, parent); endfunction task main_phase(uvm_phase phase); phase.raise_objection(this); `uvm_info ("TB/TRACE/RUN_ITEM","start sequence", UVM_NONE) repeat (100) begin seq0 = RUN_SEQ::type_id::create("seq0"); seq0.randomize() with { length_l > 10; length_l < 100; rw_l dist { READ := 10 , WRITE := 90 }; }; seq0.start(env.apb.sqr); end `uvm_info ("TB/TRACE/RUN_ITEM","finish sequence", UVM_NONE) #1000; phase.drop_objection(this); endtask endclass |
В UVM библиотеке для компонента uvm_sequencer реализована возможность запуска нескольких сиквенсов одновременно в параллель. В этом случае сиквенсер анализирует очередь сиквенсов и запускает их в зависимости от приоритета, заданного для каждой сиквенсы. По умолчанию приоритет сиквенсов будет одинаковый и можно наблюдать равномерный запуск каждой из запущенных сиквенсов.
class run_item_prior_test extends apb_base_test; typedef apb_rand_seq RUN_SEQ; RUN_SEQ seq0; RUN_SEQ seq_er_inj; `uvm_component_utils(run_item_prior_test) function new(string name, uvm_component parent = null); super.new(name, parent); endfunction task main_phase(uvm_phase phase); phase.raise_objection(this); `uvm_info ("TB/TRACE/RUN_ITEM","start sequence", UVM_NONE) seq0 = RUN_SEQ::type_id::create("seq0"); seq0.randomize() with { length_l > 10; length_l < 100; rw_l dist { READ := 10 , WRITE := 90 }; }; seq_er_inj = RUN_SEQ::type_id::create("seq_er_inj"); seq_er_inj.randomize(); fork seq0.start(.sequencer(env.apb.sqr), .this_priority(90)); seq_er_inj.start(.sequencer(env.apb.sqr), .this_priority(10)); join `uvm_info ("TB/TRACE/RUN_ITEM","finish sequence", UVM_NONE) #1000; phase.drop_objection(this); endtask endclass |
Во время выполнения одной сиквенсы на сиквенсере, существует возможность принудительно запустить другую сиквен. К примеру во время выполнения транзакции проверяющей штатный режим работы устройства, требуется запустить сиквенс генерирующую ошибочные воздействия, для проверки устройства на предмет зависании или обработки исключительных ситуаций. Для этого в сиквенсе и в сиквенсере реализованы методы lock\unlcok, grab\ungrab.
class apb_error_inj_seq extends apb_base_seq; apb_item resp_item; rand rw_type rw_l; rand length_l;
`uvm_object_utils(apb_error_inj_seq )
function new(string name="apb_rand_seq"); super.new(name); endfunction
virtual task body(); grab(); // перехватываем поток управления у всех остальных сиквенсов запущенных на m_sequenser item = apb_item::type_id::create("item"); item.randomize() with { error_inj == 1; }; start_item(item); finish_item(item); ungrab();// возвращаем поток управления у всех остальных сиквенсов запущенных на m_sequenser endclass : apb_error_inj_seq |
Также существует возможность изменить алгоритм управления приоритетами для сиквенсера. Для этого сиквенсер имеет интерфейс set_arbitration() и user_priority_arbitration(). Метод set_arbitration() позволяет выбрать возможный алгоритм из уже реализованных, по умолчанию используется SEQ_ARB_FIFO алгоритм.
SEQ_ARB_FIFO | Запросы получают приоритет в порядке FIFO, т.е. ID следующей сиквенсы на выполнение будет находится в avail_sequences[0]. |
SEQ_ARB_WEIGHTED | Запросы получают приоритет случайным образом согласно весовым значениям приоритета, заданного при старте. |
SEQ_ARB_RANDOM | Запросы получают приоритет случайным образом. |
SEQ_ARB_STRICT_FIFO | Запросы с самым большим приоритетов имеют приоритет выполнения и выполняются в порядке FIFO |
SEQ_ARB_STRICT_RANDOM | Запросы с самым высоким приоритетом получают приоритет выполнения в строгом случайном порядке |
SEQ_ARB_USER | Алгоритм приоритета передаётся пользовательской функции, user_priority_arbitration. В этой функции определяет очередность выполнения сиквенсов. |
Функция user_priority_arbitration() позволяет пользователю определить собственный алгоритм выбора сиквенсов. На вход функции подается очередь содержащая идентификаторы сиквенсов, и она возвращает идентификатор сиквенсы, которая должна выполняться следующей.
В тестовом окружении, которое проверяет цифровой блок, содержащий более чем один физический интерфейс (например, APB, AXI, PCIe и пр.), встает необходимость синхронизации запусков несколько сиквенсов на разных агентах в определенной последовательности.
Например, изначально нужно запустить сиквенс, которая генерирует сигналы сброса по всем интерфейсам. Затем запустить сиквенс, которая записывает конфигурацию регистров по APB шине, затем запустить сиквенс, выполняющую проверку подключенного устройства по третьему интерфейсу. И только после этих сиквенсов можно проверять обмен данными этого устройства. Получается, каждый раз нужно запускать три различных типа сиквенсов на разных сиквенсерах, в этом случае придется весь код дублировать в большинстве тестах.
Поэтому в UVM методологии верификации предлагается использовать сиквенс, которая будет представлять собой объект обвертку для других сиквенсов. Она будет содержать указатели на все сиквенсеры, на которых нужно запускать сиквенсы, а также в ее методе body() будет реализован алгоритм запуска и синхронизации работы всех сиквенсов.
Запускать эту сиквенс можно из теста без сиквенсера, в этом случае она будет запускаться на сиквенсере null.
// Для удобства можно объявить тип виртуальной сиквенсы reset_base_seq reset_seq; axi_base_seq read_seq; axi_base_seq write_seq; `uvm_object_utils(init_vseq)
function new(string name="init_vseq"); super.new(name); endfunction
cfg_seq = new(); reset_seq = new(); read_seq = new(); write_seq = new(); cfg_seq.randomize(); reset_seq.randomize(); read_seq.randomize() with { rw == READ; }; cfg_seq.randomize() with { rw == WRITE; };
if (axi_sqr == null) `uvm_fatal(“BASE_VSEQ”,”AXI_SQR is not set”) //проверяем перед запуском что указатели на сиквенсеры реальные fork read_seq.start(axi_sqr, this); write_seq.start(axi_sqr, this); join |
Далее показан пример теста, в котором происходит инициализация и запуск виртуальной сиквенсы vseq без сиквенсера.
class run_vseq_test extends apb_base_test; typedef init_vseq RUN_SEQ; RUN_SEQ vseq;
`uvm_component_utils(run_vseq_test) function new(string name, uvm_component parent = null); super.new(name, parent); endfunction task main_phase(uvm_phase phase); phase.raise_objection(this); `uvm_info ("TB/TRACE/RUN_ITEM","start sequence", UVM_NONE) vseq = RUN_SEQ::type_id::create("vseq"); //задаем сиквенсеры vseq.axi_sqr = env.subenv1.axi_agent.sequencer; vseq.start(null); `uvm_info ("TB/TRACE/RUN_ITEM","finish sequence", UVM_NONE) #1000; phase.drop_objection(this); endtask endclass |
Также виртуальную сиквенс можно запускать на виртуальном сиквенсере. Виртуальный сиквенср это компонент, который также наследуется от uvm_sequencer и содержит в своем теле указатели на сиквенсеры, на которых будет запускаться сиквенсы.
Необходимость в виртуальном сиквенсере возникает в том случае, если для генерации новых воздействий или для реализации алгоритма запуска транзакций необходимо анализировать проходящие в тестовом окружении транзакции от мониторов различных интерфейсов. В этом случае придется подключить порты компонентов, к портам мониторов и наблюдать с их помощью за транзакциями. А для корректной работы с портами (объявление, создание, подключение) они должны создаваться с использованием стандартных фаз, реализованных в uvm_component. Поэтому использование виртуальной сиквенсы без сиквенсера не приемлемо.
Для назначения передачи сиквенсеров для внутренних сиквенсов, удобно использовать переопределённый метод start для виртуального сиквенса. В этом случае нет необходимости в тесте передавать каждый раз сиквенсеры, достаточно это сделать один раз при создании виртуального сиквенсера.
Далее показан пример виртуальной сиквенсы, предназначенной для запуска на виртуальном сиквенсере.
// Для удобства можно объявить тип виртуальной сиквенсы reset_base_seq reset_seq; axi_base_seq read_seq; axi_base_seq write_seq; `uvm_object_utils(init_vseq)
function new(string name="init_vseq"); super.new(name); endfunction virtual task start (uvm_sequencer_base sequencer, uvm_sequence_base parent_sequence = null, int this_priority = -1, bit call_pre_post = 1) axi_sqr = sequencer.axi_sqr; super.start(sequencer,parent_sequence,this_priority, call_pre_post); endtask : start cfg_seq = new(); reset_seq = new(); read_seq = new(); write_seq = new(); cfg_seq.randomize(); reset_seq.randomize(); read_seq.randomize() with { rw == READ; }; cfg_seq.randomize() with { rw == WRITE; };
if (axi_sqr == null) `uvm_fatal(“BASE_VSEQ”,”AXI_SQR is not set”) ....//проверяем перед запуском что указатели на сиквенсеры реальные fork read_seq.start(axi_sqr, this); write_seq.start(axi_sqr, this); join |
Пример запуска виртуальной сиквенсы на виртуальном сиквенсере из теста
class run_vseq_test extends apb_base_test; typedef init_vseq RUN_SEQ; typedef init_vsqr VSQR; RUN_SEQ vseq; VSQR vsqr; `uvm_component_utils(run_vseq_test) function new(string name, uvm_component parent = null); super.new(name, parent); endfunction : new function void build_phase(uvm_phase phase); function void connect_phase( uvm_phase phase ); task main_phase(uvm_phase phase); phase.raise_objection(this); `uvm_info ("TB/TRACE/RUN_ITEM","start sequence", UVM_NONE) vseq = RUN_SEQ::type_id::create("vseq"); vseq.start(vsqr); `uvm_info ("TB/TRACE/RUN_ITEM","finish sequence", UVM_NONE) #1000; phase.drop_objection(this); endtask : main_phase endclass : run_vseq_test |
Выше уже были рассмотрены примеры тестов, в которых запускаются сиквенсы. Для запуска теста в среде верификации используется задача run_test(). Вызов этой задачи осуществляется из модуля в блоке initial. Так как при создании компонентов теста используется механизм фабрики, то это позволяет симулятору при запуске анализировать параметр командной строки +UVM_TESTNAME и запускать класс с указанным в этой строке именем. Если задать такое имя теста, которого не было зарегистрировано с помощью фабрики, то при запуске будет выведено сообщение об ошибке.
Можно реализовать один базовый тест, который в зависимости от имени будет подменять сиквенс по умолчанию на соответствующую имени сиквенс. Для этого используется механизм подмены типов с помощью фабрики. (см. запуск сиквенсы по умолчанию). Аналогичный механизм уже реализован для самого класса uvm_test, поэтому используемый подход выбирается в зависимости от требуемой задачи. Все тесты будут представлять собой однообразный запуск сиквенсов, а изменяться будет лишь их имя, то данный метод позволит уменьшить число файлов входящих в тестовое окружение.
`ifndef TX_BE_LENGTH2_TEST_SV `define TX_BE_LENGTH2_TEST_SV class tx_be_length2_test extends tx_test_base; `uvm_component_utils(tx_be_length2_test) string test_name; myseq_t myseq; function new(string name="tx_be_length2_test", uvm_component parent = null); super.new(name,parent); $value$plusargs("UVM_TESTNAME=%s" , test_name); endfunction : new
virtual function void build_phase(uvm_phase phase); `uvm_info("build_phase", "Entry", UVM_HIGH) super.build_phase(phase);
set_inst_override_by_name("myseq", myseq_t::get_type_name(), test_name_to_vseq_name(test_name)); myseq = myseq_t::type_id::create("myseq");
uvm_config_db #(uvm_sequence_base)::set( null, "top.agent.myseqr.main_phase", "default_sequence", myseq); `uvm_info("build_phase","Exit",UVM_HIGH) endfunction virtual function string test_name_to_vseq_name(string inline); ... endfunction endclass: tx_be_length2_test `endif // TX_BE_LENGTH2_TEST_SV |
Кроме имени теста удобно использовать другие параметры командной строки для параметризации тестов, сиквенсов или режимов работы окружения. Этот подход позволяет изменять конфигурации без пере-компиляции тестового окружения. Параметры могут быть различных типов: строковый (%s), битовый (%b), численные (%d, %h). Для проверки значения параметра, заданного в командной строке при запуске моделирования, используется функция $value$plusargs.
$value$plusargs("ИМЯ_ПАРАМЕТРА=%b", целевая_переменная); |
Далее показан пример базового теста, в котором используются значения различных параметров, передаваемых из командной строки.
class base_test extends uvm_test;
int s0_tr_num = 10; bit xor_enable = 0; bit data_type = 1; bit delay_en = 1; bit perf_mode = 0; int error_num = 1; int aport_num = 0; bit [2:0] cbm_rw_cfg = 0; bit pl_mode = 1; bit [1:0] rw_mode = 3; `uvm_component_utils_begin(base_test) `uvm_component_utils_end extern function void get_plusarg(); endclass : base_test function void base_test::get_plusarg(); $value$plusargs("CBM_XOR_EN=%b" , xor_enable); $value$plusargs("CBM_S0_NBURST=%d" , s0_tr_num); $value$plusargs("CBM_DTYPE=%b" , data_type); $value$plusargs("CBM_DELAY=%b" , delay_en); $value$plusargs("CBM_PERF_MODE=%b" , perf_mode); $value$plusargs("CBM_ERROR_INJ=%d" , error_num); $value$plusargs("CBM_APORT_NUM=%d" , aport_num); $value$plusargs("CBM_RW_CFG=%b" , cbm_rw_cfg); $value$plusargs("CBM_PL_MODE=%b" , pl_mode); $value$plusargs("CBM_RW_MODE=%b" , rw_mode); endfunction : get_plusarg function base_test::new(string name, uvm_component parent); super.new(name, parent); get_plusarg(); endfunction : new |
Класс предоставляет интерфейсы для работы с аргументами командной строки, которые были переданы симулятору. Класс предназначен для использования как статический, но это не обязательное требование. Генерация структуры данных, которые заполняются аргументами командной строки, происходит в момент создания объекта класса. Глобальная переменная uvm_cmdline_proc создается в нулевой момент времени и может использоваться для доступа к информации переданной в командной строке.
Класс uvm_cmdline_processor также предоставляет поддержку установок различных UVM переменных из командной строки, например, таких как задание чувствительности вывода сообщений для компонентов и различные строковые и численные параметры. Далее в таблице описаны реализованные параметры и интерфейсы для работы с ними.
get_inst | Возвращает указатель на статический объект класса обработки параметров командной строки |
get_args | Функция возвращает очередь со всеми аргументами командной строки, которые были заданы во время запуска моделирования. Стоит обратить внимание на то, что элемент очереди с номером 0 будет всегда хранить имя исполняемого файла, который начал симуляцию. |
get_plusargs | Эта функция возвращает очередь, содержащую все аргументы со знаком “+”, плюс-аргументы, которые были переданы во время запуска моделирования. Плюс-аргументы могут использоваться разработчиками симулятора или пользователем по их усмотрению. Плюс-аргументы никогда не имеют дополнительных аргументов, т.е. если в командной строке есть один плюс-аргумент и для него задано значение, то следующий за ним аргумент будет новым независимым аргументом, а никак не расширением предыдущего. |
get_uvmargs | Эта функция возвращает очередь со всеми uvm аргументами которые были переданы в командной строке при запуске моделирования. Все uvm аргументы передаются с помощью ключа +\- и все они имеют в названии в начале три буквы “UVM”. |
get_arg_matches | Эта функция заполняет очередь аргументами, которые соответствуют заданному выражению и возвращает количество таких аргументов. Если входное выражение задано между символами “//”, это выражение рассматривается как встроенное регулярное выражение для поиска имени аргумента, которое содержит заданную строку. |
string myargs[$] void'(uvm_cmdline_proc.get_arg_matches("+foo",myargs)); //совпадает с условием +foo, +foobar //не совпадает +barfoo void'(uvm_cmdline_proc.get_arg_matches("/foo/",myargs)); //подходит +foo, +foobar, foo.sv, barfoo, etc.
//Подходит matches foo.sv и foo123.sv, //Не подходит barfoo.sv. end | |
get_arg_value | Эта функция находит первый аргумент, который совпадает с заданным именем. Эта функция аналогична системной функции $value$plusargs, но не поддерживает строку формата ввода данных. Возвращает количество найденных аргументов с заданным именем и возвращает значение первого найденного подходящего аргументы. |
get_arg_values | Эта функция находит все аргументы, которые совпали с заданным именем и возвращает значения найденных аргументов в виде списка. Число найденных значений можно получить вызвав функцию получения размера списка найденных аргументов size() Например: Строка запуска: ‘+foo=1,yes,on +foo=5,no,off’ а в окружении исполняется следующий код: string foo_values[$] void'(uvm_cmdline_proc.get_arg_values end То очередь значений будет содержать два найденных параметра: ”1,yes,on” ”5,no,off” При обработке переданных значений лучше всего пользователю разделить полученные строки используя uvm_split_string() функцию |
get_tool_name | Функция возвращает название симулятора. Разработчик среды моделирования реализует эту функцию. |
get_tool_version | Функция возвращает информацию о симуляторе. Разработчик среды моделирования реализует эту функцию. |
В следущей таблице перечислены основные плюс-параметры, используемые в пакете UVM.
+UVM_DUMP_CMDLINE_ARGS | Параметр позволяет пользователю сохранять все аргументы командной строки используя механизм вывода сообщений. Для вывода параметров будет использоваться формат tree. |
+UVM_TESTNAME | Параметр позволяет задать пользователю какой тест или какой компонент верхнего уровня должен быть создан и запущен в UVM фазах. Если задано несколько аналогичных параметров, то будет использоваться только первый, и будет выдано предупреждение. <sim_command> +UVM_TESTNAME=read_modify_write_test |
+UVM_VERBOSITY | Параметр позволяет задать порог чувствительности для всех компонентов при выводе сообщений с помощью макроса `uvm_info. Если задано несколько аналогичных параметров, то будет использоваться только первый, и будет выдано предупреждение. <sim_command> +UVM_VERBOSITY=UVM_HIGH |
Для управления временем выполнения теста в UVM реализован класс uvm_objection. Этот класс содержит методы, которые указывают симулятору, что тестовое окружение еще работает или оно уже закончило свое выполнение. В этом классе реализован метод set_drain_time, который позволяет задать время, которое симулятор будет продолжать работать после того как подаст сигнал о завершении последний объект класса uvm_objection.
Можно использовать функцию set_timeout класса uvm_root, которая позволяет задать время, после превышения которого тест принудительно завершиться.
Термин | Значение |
Транзакция (transaction) | Класс, содержащий набор различных данных, с помощью последовательности которых можно описать модель поведения устройства в среде верификации |
Сиквенс (sequence) | Класс, генерирующий последовательность (набор) транзакций |
Сиквенсер (sequencer) | UVM компонент выполняющий арбитраж запущенных сиквенсов. |