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

Всім привіт! Ця стаття буде присвячена верифікації дизайну кінцевого автомата управління торговим пристроєм 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 і даних отриманих з Monitor OUT.

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)
Ну і посилання на весь проект цілком

Ну що ж на цьому все. Дякую за увагу. Сподіваюся цю статтю ви знайдете для себе в чомусь корисною.

До нових зустрічей.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.