Всем привет! Эта статья будет посвящена верификации дизайна конечного автомата управления торговым устройством 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)
> Ну и ссылка на весь проект целиком

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

До новых встреч.
Поделиться с друзьями
-->

Комментарии (13)


  1. Verification_guy
    17.01.2017 21:39
    +2

    У вашем проекте не используется библиотека OVM, это просто объектно-ориентированный testbench. Но в целом очень не плохо. Не считая того, что эгоистичный аппарат не вернет мне деньги, если я внесу только половину стоимости


            ENTER_MONEY:
                if (wallet >= product_price) begin
                    next_state = GIVE_PRODUCT;
                end else begin
                    next_state = ENTER_MONEY;
                end


    1. ogvalt
      17.01.2017 21:43

      Соглашусь с вашим замечанием по поводу того, что библиотеку OVM я не использовал, а скажем так, черпал оттуда идеи и вдохновение.
      И да, с этим конечным автоматом не надо шутить, если уж начали, так идите до конца :)


  1. k_levin
    17.01.2017 21:45
    +2

    Хорошая статья, этой темы практически нет на хабре.

    Возникло несколько вопросов:
    1. Не думали портировать всё на UVM?
    2. Для чего необходимо разделять мониторы на два?
    3. Не думали засунуть ассершены в интерфейс?


    1. ogvalt
      17.01.2017 22:08

      Спасибо!

      1. Я здесь не использовал библиотеку OVM, я организовывал проект по схожей с методологией структуре и черпал оттуда основные идеи. Планирую ли я в будущем использовать ее или библиотеку UVM и портировать проект? Возможно.
      2. Для Scoreboard нужна информация на основе которой будет рассчитан эталонный выход дизайна, который будет сравниваться с реальным выходом в Checker. По — этому не плохо создать два монитора, один для входа, второй для выхода. Вообще мне разделения на два монитора кажется уместным, поскольку один общий монитор будет выглядеть довольно сложно в подобном проекте или в проекте посложнее.
      3. Как по мне всему свое место. Можно их засунуть и в интерфейс, но мне кажется гораздо удобней выделить для них отдельный файл, если их много или они большие. Если их один-два, тогда не вижу проблемы разместить их в интерфейсе.


      1. k_levin
        18.01.2017 00:14
        +2

        1. Вообще я не очень понимаю смысл делать объектно-ориентированный тестбенч без использования библиотеки OVM/UVM и т.к. многие базовые вещи в ней уже есть и не нужно делать свой велосипед. Ну в вашем случае всё получилось статичным, а не динамичным. Так не делают, потому что при сборке тестбенча из нескольких UVC вы не сможете это всё подключить. Ну и библиотеку не сможете использовать, т.к. там для этого есть специальный базовый класс ovm_env/uvm_env.

        2. Честно говоря не соглашусь. Потому что по сущности этих мониторов не будут отличаться друг от друга. А в более сложных интерфейсах у вас будет большое дублирование кода, т.к. операция записи от операции чтения как правило не очень сильно отличается. Если хочется отделить одни транзакции от других, то наверно проще поставить второй экземпляр scoreboard, но подключить его иначе (expected брать из модуля, observed из окружения).

        Касательно более сложных проектов. Вот пример OVM монитора и UVM монитора. На мой взгляд там все очень наглядно выглядит.

        3. В этом будет свой смысл, когда вы будете делать reuse готовых модулей(UVC). И вместо того, чтобы соединять в тестбенче интерфейс, DUT и модуль с ассершенами, вы будете подключать только интерфейс и DUT. Вероятность ошибки сильно снижается. В итоге вы получаете уже отлаженный интерфейс, который нужно лишь подключить и использовать.

        В любом случае успехов вам в освоении выбранной профессии!


        1. Verification_guy
          18.01.2017 11:37

          Универсальный — значит избыточный. UVM библиотека требует больших трудозатрат на поддержку сопутствующей инфраструктуры, компонент и т.д… Если у тебя маленькие, не частые проекты с отсутствием стандартных интерфейсов. То использование ОО-методологии(собственно описана в статье) верификации без UVM библиотеки, может занимать сильно меньше времени. Тем более что для неё требуется поддержка со стороны симулятора.
          Справедливости ради, в серьезных компаниях повсеместно используют UVM-инфраструктуру.


  1. master65
    17.01.2017 22:09
    +1

    А какие свойства автомата тестировались? Ссылка на assertions не работает (page not found)


    1. ogvalt
      17.01.2017 22:21

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


      1. master65
        18.01.2017 08:47

        Спасибо. Было бы неплохо


  1. JediPhilosopher
    18.01.2017 00:08
    +1

    В ИТМО Шалыто мощно задвигал за верификацию автоматов когда я там учился. У него на сайте много материалов по этой теме http://is.ifmo.ru/verification
    С одной стороны тема казалось довольно перспективной — верификация позволяет действительно достичь высоких гарантий качества, а верифицировать автоматы проще чем произвольный код. С другой — программы надо либо сразу делать в виде конечного автомата, либо как-то приводить к этому виду (были там дипломы посвященные этой теме). Но это сложно и неудобно для программ в общем виде.


    1. ogvalt
      18.01.2017 21:14

      Я правильно понимаю, Вы говорите об организации програмы в виде конечного автомата? Соглашусь с Вами, что програма тогда будет сильно усложнена, и как по мне не будет давать реального прироста в скорости верификации дизайнов цифровых схем


  1. urock
    18.01.2017 18:17

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


    1. ogvalt
      18.01.2017 21:19

      Спасибо!
      Я как то пробивал игратся с библиотекой MyHDL для Python. Действительно было проще, но скорость подкачивала и я решил двигатся по пути увеличения скорости роботы то-есть изучать System Verilog