Pull to refresh

Верификация конечного автомата

Reading time7 min
Views12K
Всем привет! Эта статья будет посвящена верификации дизайна конечного автомата управления торговым устройством vending machine, описанного на языке Verilog (дизайн) и System Verilog (верификация).

Вообще в основе публикации лежит мой курсовой проект, который был оценен моим преподавателем по достоинству с предложением сделать публикацию на Хабре.

Основное на чем я хочу акцентировать внимание — это описания типичных блоков multilayer testbench и применение некоторых базовых конструкции языка SystemVerilog и верификации. В основе подхода, который я использовал лежит так называемая Open Verification Methodology (OVM) с изменениями, которые упрощали разработку проекта и были удобны персонально мне.

Итак, поехали!

Спецификация устройства и принцип его работы


Материал, что последует далее сложно назвать спецификацией устройства, но попытка была сделана. И вот что из этого получилось.

Верифицируемое устройство – конечный автомат Мура предназначение, которого: управление vending machine.

Интерфейс устройства: 7 входных сигналов / шин и 6 выходных сигналов / шин. Назначение сигналов, их разрядность и направление для удобства представлены в виде таблицы.

Название сигнала/шины Направление Разрядность Назначение
i_clk входной 1 Сигнал синхронизации
i_rst_n входной 1 Сигнал сброса с активным низким уровнем
i_money входной 4 Шина по которой передаются коды номиналов денежных единиц
i_money_valid входной 1 Сигнал валидности кода на шине i_money
i_product_code входной 4 Шина по которой передают коды товаров
i_buy входной 1 Сигнал подтверждения осуществления покупки
i_product_ready входной 1 Сигнал готовности продукта к выдаче (входной, потому что я принял, что приготовлением продукта занимается другое устройство)
o_product_code выходной 4 Код товара для выдачи покупателю
o_product_valid выходной 1 Сигнал валидности информации на шине o_product_code
o_busy выходной 1 Сигнал того, что конечный автомат занят обработкой текущего заказа
o_change_denomination_code выходной 4 Сдача, а точнее коды номиналов денежных единиц
o_change_valid выходной 1 Сигнал валидности на шине o_change_denomination_code
o_no_change выходной 1 Сигнал окончания выдачи сдачи

Всего у конечного автомата 4 состояния: CHOOSE_PRODUCT, ENTER_MONEY, GIVE_PRODUCT, GIVE_CHANGE.

Думаю, что из названий в принципе понятно что к чему.

Но если непонятно, тогда следует пояснить
CHOOSE_PRODUCT: В этом состоянии конечный автомат принимает код товара и ждет подтверждения покупки

ENTER_MONEY: Здесь ми кормим автомату денежку в виде кодов номиналов денежных единиц. Переход к следующему состоянию происходит тотчас после того, как в кошельке у автомата денег будет не менее чем нужно для покупки товара.

GIVE_PRODUCT: Тут мы считаем сдачу и передаем код продукта, который нужно приготовить «абстрактному устройству исполнителю». Переход к следующему состоянию происходит после получение соответствующего сигнала готовности продукта от «абстрактного исполнителя».

GIVE_CHENGE: Выдаём сдачу и переходим в режим ожидания, то есть СHOOSE_PRODUCT.

Также предлагаю Вашему внимаю какую ни какую диаграмму сего чуда:


Описание на Verilog можно найти здесь

Собственно верификация


Этот раздел я хотел бы разбить на две части. В первой я приведу структуру тестбенча, опишу каждый функциональный блок, из которого он состоит. Во второй речь пойдет о так называемом code and functional coverage и о assertions.

Структура тестбенча


Начнем с картинки, которая иллюстрирует структуру тестбенча.



Рассмотрим каждый блок по отдельности:

DUT (design under test) — этот блок и есть описанием устройства конечного автомата с одной небольшой доработкой в виде обёртки коротая позволяет блокам тестбенча взаимодействовать между собой с помощью interface.

Вообще, interface это такая конструкция языка SystemVerilog, которая позволяет группировать между собой сигналы и упростить роботу разработчика. С ней можно делать много прикольных вещей, о которых можно почитать в стандарте к языку и немного в этой статье.

Ссылочка на DUT

Assertions — здесь мы на уровне сигналов проверяем насколько соответствует поведение нашего дизайна спецификации устройства.

В этом нам помогают такие конструкции как: assert, property, sequence. Так же мы можем включать результаты проверки поведения нашей модели в определение functional coverage с помощью конструкции cover.

Ссылочка на Assertions

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

При его описании используют конструкцию Program. Почему так, сложно ответить, для меня это тоже пока открытий вопрос. Возможно это связано з регионами симуляции симулятора SystemVerilog, но это пока догадки.

Ссылочка на Environment

Внутри Environment живет много других сущностей, которые как раз и осуществляют генерацию стимулов для дизайна, реализуют различные сценарии верификации, осуществляют проверку правильности полученных данных на выходе и оценку code и functional coverage. Итак, перейдем к ним:

Sequencer — блок, где описаны сценарии, по которым будет происходить верификация. Эти описания достаточно высокоуровневые и опираются на методы, которые предоставляют Transactor. Примечательным здесь есть то, что рабочей лошадкой этого блока обычно является конструкция randsequence. Основная ее задача — это обеспечить удобный способ организации сценария в виде последовательности вызовов методов Transactor'а. Вот ссылка где хорошо объясняется как пользоваться randsequence.

А вот что получилось у меня (Sequencer)

Transactor — блок, который реализует методы, которые собственно и служат основой для сценариев. На этом этапе так же происходит генерация информации, которая будет передана в качестве входных сигналов нашему дизайну. И тут на сцену вступает мощь SystemVerilog.

В SystemVerilog можно сделать так, чтобы значения полей классов генерировались случайным образом. Это очень полезно поскольку позволяет значительно ускорить верификацию. Собственно, для того, чтобы сделать поле случайно генерируемым нужно использовать ключевое слово rand или randc.

Но это еще не все. Предположим, что вы создали переменную типа int, но вам надо чтобы значения, которые она принимала были определены строгим диапазоном (например, у вас на шине могут появляться только определенные адреса). И на этот счет у SystemVerilog есть для вас подарок: конструкция constraint, которая позволяет накладывать ограничения то, какими свойствами будут обладать ваши случайные переменные. Пример из проекта:

rand logic [ 3:0] product_code;
constraint c_product_code {
	product_code inside { [ 1 : 8 ] };
}

Здесь я создал переменную product_code которую ограничил интервалом [1;8] с помощью c_product_code.

Но есть одно, но. После создания экземпляра класса переменные rand и randc не инициализируются случайным значением. Это происходит когда вызывается встроенный метод экземпляра randomize().

Возвращаемся к Transactor'у. Использование rand полей, метода randomize() и его расширения randomize() with естественным образом происходит в Transactor'е. В отличии от randomize(), randomize() with позволяет при его вызове накладывать дополнительные ограничения на rand поля. Более детально об этом можно узнать из стандарта языка SystemVerilog и здесь.

И конечно Transactor

Driver — единственный кто здесь по-настоящему работает. Его задача превратить информацию, полученную от Transactor'а во входные сигналы дизайна. С ним все просто и понятно. Поэтому я расскажу немного о конструкции interface.

Это не те конструкции, которые можно встретить в обычных языках программирования. Тут interface это больше конструкция, которая позволяет сгруппировать сигналы независимо от их направления так, как удобно пользователю. В проекте всего три interface конструкции: dut_interface, vm_in_interface, vm_out_interface. Первый — это сигнал синхронизации и сброса, второй — вход vending_machine, третий — ее выход. Вот так, оно все выглядит.

Ну и конечно — Driver

IN Monitor и OUT Monitor — эти блоки считывают информацию, которая поступает в и выходит из DUT для дальнейшей проверки на правильность. Зачем считывать информацию коротая поступает в DUT, если она сохранена в Driver'e спросите вы? Все просто, чтобы избежать ошибок в роботе Driver и всех выше стоящих блоков.

IN и OUT Monitor

Checker — блок, что выполняет проверку соответствия эталонных данных которые высчитываются на базе данных полученных из IN Monitor и данных полученных из OUT Monitor.

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

И тут мы подошли к самому соку, как делать functional coverage. Для этого в SystemVerilog есть специальная конструкция covergroup. В каждой covergroup вы определяете так называемые coverpoint, в которых как раз и происходит привязка к конкретному сигналу или шине на которой и будет проверятся, все ли возможные вариации данных были получены дизайном и все ли возможные данные появились на его выходах.

Вообще доступ к результатам functional coverage происходит в runtime, поэтому существуют специальные функции, которые позволяют после оценить его, когда угодно.

Одна из них — это встроенная функция $get_coverage которая возвращает значение от 0 до 100 рассчитанное на основе всех cover — конструкций (cover, covergroup, coverpoint).

Кроме доступа в runtime, представление о functional coverage можно получить и в графических средах симуляции (могу ручаться за ModelSim так точно).

Перейдем к code coverage. Этот показатель дает нам понят насколько был использован код нами написанный и насколько полны наши тесты. Если по какой-то причине ваш code coverage не достигает приемлемого для вас уровня тогда есть 2 варианта: пишите тести лучше, или ваш код чрезмерный. В любом случае это надо будет исправлять. Правда стоит отдельно упомянуть что бывают случаи, когда как бы и тесты хороши и код дизайн хорош, но все равно code coverage нас не устраивает, тогда надо будет что-то исключать из проверки.

Вообще, что проверяет code coverage:

  • Statement — строки исполненные и не исполнение за время симуляции
  • Branches — проверяет выполнение конструкций if..else, case
  • Condition — проверяет логических условии результаты которых должны быть True или False
  • Toggle — проверяет логические переходы с 0 в 1 и наоборот

Эти, как и другие проверки (проверки конечных автоматов и так называемые FEC Condition) дают представление о code coverage проекта.

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

Ну и, конечно, чуть не забыл (Scoreboard/Functional Coverage)
Ну и ссылка на весь проект целиком

Ну что ж на этом все. Спасибо за внимание. Надеюсь эту статью вы найдете для себя в чем-то полезной.

До новых встреч.
Tags:
Hubs:
Total votes 30: ↑29 and ↓1+28
Comments13

Articles