Всем привет, дорогие хабровчане! Сегодня я хочу поделиться своей «больной» идеей реализовать калькулятор на ПЛИС на основе конечного автомата. Почему больной? Потому что уж очень мудрёно получается: всё-таки реализация автоматов на ПЛИС – дорогая практика в смысле ресурсов. Почему хочу поделиться? Потому что вишенкой на торте в этом проекте является автоматическая генерация кода с помощью такого мощного средства, как HDL Coder в MATLAB, что в купе со Stateflow очень интересно смотрится: создание железного кода на основе графического составления графа системы – ни это ли верх мечтаний разработчика, которому необходимо реализовать сложнейший граф с кучей разных переходов и условий ?!
Итак, задачу перед собой я поставил следующую: у меня есть «китайский» кит с FPGA Spartan 6 на борту и старенький клавиатурный интерфейс PS/2. Я собираюсь залить проект калькулятора-автомата на ПЛИС вместе с выбранным интерфейсом и с клавиатуры осуществлять ввод данных. Вывод результата и текущего ввода будем наблюдать на 8-ми cемисегментных дисплеях, которые также имеются на отладочной плате.
В первой части мы познакомимся с пакетом Stateflow, как собиралась модель в SIMULINK и сгенерируем HDL-описание. Во второй части мы немного скорректируем проект для получения синтезируемого HDL кода.
![Рис. 1 Отладочная плата Рис. 1 Отладочная плата](https://habrastorage.org/getpro/habr/upload_files/dff/86c/37d/dff86c37dc56f8774a7e30c316979a21.jpg)
Немного ликбеза о конечных автоматах
Конечным автоматом называется модель устройства, имеющего один вход, один выход и обладающего конечным набором состояний. Переход в следующее состояние зависит от состояния, в котором находится устройство в данный момент, и от входного воздействия. Отсюда и деление на автоматы Мили и Мура. Автоматы Мура в отличие от автоматов Мили определяют следующее состояние, исходя только из текущего состояния, в то время как в автоматах Мили учитывается также и входное воздействие.
![Рис. 2 Синхронный автомат Мили Рис. 2 Синхронный автомат Мили](https://habrastorage.org/getpro/habr/upload_files/852/3ee/42d/8523ee42d9ddab526a2559cf0b602c0e.png)
![Рис. 3 Синхронный автомат Мура Рис. 3 Синхронный автомат Мура](https://habrastorage.org/getpro/habr/upload_files/f50/85c/1fb/f5085c1fb923c1b37e042ed95d19c0f7.png)
Блоки F и G являются строго комбинационными. Блок памяти состояний построен на последовательностной логике.
Описание автоматов осуществляется с помощью графов и таблиц. Графы используются для наглядности логики переходов, в то время как таблицы используются для описания таких переходов в ЭВМ. Автомат считается полным, если для каждой вершины определены переходы в любое из возможных состояний, описываемых моделью автомата. В противном случае автомат считается частичным.
Знакомство со Stateflow
Пакет прост и интуитивен, поэтому предлагаю начать наглядное знакомство. Итак, работа в данном пакете начинается с открытия Simulink и использования блочка chart (рис. 4). В данном блоке описывается вся внетабличная логика автомата как посредством самого инструмента Stateflow, так и Simulink вместе с MATLAB. Состояния характеризуются блоком State (рис. 5). Предусмотрено, что логика состояния может выполняться в 3-х разных режимах: при входе, при выходе из состояния, либо выполняться постоянно. Переход из состояния в состояние осуществляется посредством направленных графовых стрелок (рис. 6).
![Рис. 4 Основной блок описания логики автомата Рис. 4 Основной блок описания логики автомата](https://habrastorage.org/getpro/habr/upload_files/54a/298/c0a/54a298c0a0ebc93aef56138f216a757e.png)
![Рис. 5 Блок состояния Рис. 5 Блок состояния](https://habrastorage.org/getpro/habr/upload_files/4c0/3c8/da8/4c03c8da8efec03b713f9e3248331c64.png)
![Рис. 6 Переходы графа Рис. 6 Переходы графа](https://habrastorage.org/getpro/habr/upload_files/850/528/2f1/8505282f1f31cb5a726fd5a07c47235f.png)
Как говорилось выше, пакет Stateflow отличает конечные автоматы Мура и Мили. Соответственно исполненные структуры при генерации HDL кода также повторяются. Чтобы показать какую структуру мы хотим реализовать в «железе», мы должны собирать граф, как показано на рис. 7.
![Рис. 7 Различие структур при сборке конечного автомата Рис. 7 Различие структур при сборке конечного автомата](https://habrastorage.org/getpro/habr/upload_files/792/0f0/0b8/7920f00b8d25ff559ab0e75b7f19aa2a.png)
Также важно отметить, что можно задавать как последовательно выполняющиеся состояния, так и параллельно выполняющиеся процессы, что мы увидим далее, а сейчас давайте перейдём непосредственно к сборке модели.
Граф алгоритма и сборка нашего проекта
Собственно, перед началом сборки я долго думал о том, как мне работать с точкой: с одной стороны, я могу выводить только 4 знака после запятой на свои семисегментные дисплеи и при этом, потратив большой ресурс на разрядность числа – 50 разрядов точки – это величина, которая требовалась, чтобы получить чистую тысячную единицу. С другой стороны, самые простые калькуляторы могут давать намного больше цифр после запятой, чем 4, что потребовало бы от меня значительно усложнить модель и я бы отошел от простой идеи.
![Рис. 8 Модель в Simulink Рис. 8 Модель в Simulink](https://habrastorage.org/getpro/habr/upload_files/1e8/2f6/743/1e82f6743ef745d598c01f8a2f2b4b79.png)
Чтобы спроектировать и ввести в работу любое устройство, необходимо создать его сырую копию, т. е. прототип. Это может быть как натурный объект, так и его математическая модель. Легко догадаться, что проще и быстрее создать последнее, т. е. создать цифрового двойника. В моём случае необходимо было промоделировать ввод с клавиатуры, интерфейс ps/2, работу самого автомата и засветку семисегментных дисплеев.
Итого, в состав модели вошли:
Простейший контроллер ps/2 интерфейса – Controller_psD2;
Калькулятор на основе конечного автомата – Chart;
Семисегментная панель — controlLedPanal.
В теле автомата калькулятора можно найти 7 параллельных процессов (пунктирные границы блока state) рис. 9.
![Рис. 9 Структура автомата Рис. 9 Структура автомата](https://habrastorage.org/getpro/habr/upload_files/d2c/b32/1d6/d2cb321d60d485c23d9167376eb10589.png)
Важнейшими процессами в структуре, приведенной на рис. 9, являются:
evalBody – процесс, в котором описан граф переходов самого калькулятора (рис. 10).
LEDDISPLAY – процесс, в котором описан граф засветки семисегментных дисплеев (рис. 11).
numDecode – процесс, который проверяет код нажатой кнопки с клавиатуры и интерпретирует в цифру или арифметический знак.
interfaceProc – процесс, который собирает 8-битный кадр, приходящий от клавиатуры. Вызывает функцию собранную в Simulink (рис. 12).
![Рис. 10 Граф калькулятора Рис. 10 Граф калькулятора](https://habrastorage.org/getpro/habr/upload_files/86b/5da/878/86b5da8785660249d99a4b19cfe986f4.png)
![Рис. 11 Граф засветки семисегментных дисплеев Рис. 11 Граф засветки семисегментных дисплеев](https://habrastorage.org/getpro/habr/upload_files/ffd/4b1/1db/ffd4b11dba4839f8bcf07cd35ca602d7.png)
![Рис. 12 Блок сборки кадра, приходящего от клавиатуры на основе блока Simulink Function Рис. 12 Блок сборки кадра, приходящего от клавиатуры на основе блока Simulink Function](https://habrastorage.org/getpro/habr/upload_files/bbb/d5c/c5b/bbbd5cc5bd79ca558a3d9a9bc1dfa3f9.png)
Остальные процессы в той или иной степени отвечают за «сборку» цифр в число и обратно. Здесь же есть так называемые MATLAB Function, которые можно вызывать в теле конечного автомата, когда необходимо описать логику работы с помощью скрипта. И также для удобства используются Simulink Function, которые тоже используются внутри автомата.
Работа модели состоит в следующем: блок контроллера клавиатуры на вход конечного автомата последовательно отправляет 8-битный код символа нажатой клавиши, по линии dataIn, биты которого выставляются по переднему фронту тактового сигнала clockS1, который также поступает на вход конечного автомата (рис. 13).
![Рис. 13 Осциллограммы интерфейса ps/2 в Simulink. На входе dataIn выставлена единица. Рис. 13 Осциллограммы интерфейса ps/2 в Simulink. На входе dataIn выставлена единица.](https://habrastorage.org/getpro/habr/upload_files/445/f9a/fc3/445f9afc333c8ef44ca5388072257788.png)
Модель предполагает вводной и выводной регистр. В вводной регистр записывается очередная цифра, введенная с клавиатуры, а в выводной регистр записываются результаты арифметических операций и каждая вторая вводимая цифра. Последовательно введенный код декодируется в цифру, цифра записывается в вводной сдвиговый регистр и далее калькулятор ждёт следующей команды. Если приходит код цифры — всё повторяется, если приходит код арифметической операции, то из вводного регистра цифры сдвигаются в выводной регистр, а в процессе evalBody осуществляется переход в состояние операции, соответствующей пришедшему коду. В таком состоянии происходит комплектация числа из цифр, находящихся в выходном регистре с помощью MATLAB функции buildNum. Следующий шаг опять будет зависеть от кода: если это будет арифметический символ, то выполнится соответствующее действие.
![Рис. 14 Операция сложения Рис. 14 Операция сложения](https://habrastorage.org/getpro/habr/upload_files/c70/c23/b05/c70c23b05dad6d8bba234519d2fae570.png)
![Рис. 15 Результат сложения Рис. 15 Результат сложения](https://habrastorage.org/getpro/habr/upload_files/485/323/ddd/485323ddde0268050ef52b98ca91f12d.png)
Использование HDL Coder при генерации железного кода
Вот мы и добрались до генерации HDL кода. Сам по себе процесс сборки кода несложный, если знать некоторые тонкости:
При синтезе не используйте Simulink Function в теле автомата – так мне пришлось выносить интерфейсный блок.
Все вводимые переменные должны иметь тип fixed point и их разрядность должна быть задана. Не должно присутствовать переменных с типом double.
Помните, в HDL нет операции деления (/ – только нацело), извлечения корня и возведения в степень. Поэтому применение данных операторов либо вызовет ошибку, либо приведет к неверному результату.
Для реализации сложных арифметических действий в железе, пользуйтесь библиотекой HDLMathLib, которая содержит синтезируемые IP-ядра для таких действий (рис. 16).
В петлях обратной связи между блоками конечного автомата и Simulink блоками необходимо использовать задержку в 1 такт либо использовать разные скорости.
![Рис. 16 Содержимое библиотеки арифметических IP-ядер Рис. 16 Содержимое библиотеки арифметических IP-ядер](https://habrastorage.org/getpro/habr/upload_files/0c7/0f7/806/0c70f7806c52437db861af80fa7cae76.png)
Итак, для генерации кода я чуть-чуть подправил модель (рис. 17) – здесь вынесены из тела автомата блок формирования кадра и ядро деления, и уместил синтезируемую часть в подсистему (Subsystem). Теперь необходимо в Setting Parameters указать устройство, под которое производится генерация кода и под какой софт. Также во вкладке Report отмечаем все галочки для получения максимально подробного отчёта, даём команду на генерацию кода (рис. 18).
![Рис. 17 Изменения изначальной модели под генерацию кода Рис. 17 Изменения изначальной модели под генерацию кода](https://habrastorage.org/getpro/habr/upload_files/18e/566/59d/18e56659d322298e40adba94bb066462.png)
![Рис. 18 Генерируем код Рис. 18 Генерируем код](https://habrastorage.org/getpro/habr/upload_files/229/98e/34b/22998e34b1e9320a97c94b58e8d68c4c.png)
Результаты и выводы
Как результат мы получаем:
Подробнейший отчёт по процессу генерации;
HDL – код проекта;
Код, привязанный к комментариям и соответствующим частям модели;
Сгенерированный TestBench.
![Рис. 19 Часть отчёта: затраченные ресурсы Рис. 19 Часть отчёта: затраченные ресурсы](https://habrastorage.org/getpro/habr/upload_files/fe9/9f1/f0d/fe99f1f0d24ea1041ba7480adc7398fa.png)
![Рис. 20. Соответствие частей кода с элементами модели Рис. 20. Соответствие частей кода с элементами модели](https://habrastorage.org/getpro/habr/upload_files/9d3/df8/997/9d3df899739372aae33645e7083bbd65.png)
Итак, мы получили жирный в смысле ресурсов Verilog код калькулятора для ПЛИС и TestBench под него. Однако, это без этапа оптимизации кода в HDL workflow. Весь процесс был интуитивен и понятен, и самым сложным были только функции сборки числа и обратно. Также мы ушли от написания громоздкого TestBench и смогли создать цифровой двойник нашего устройства – начиная от интерфейса ввода и заканчивая выводом данных на семисегментные дисплеи. Во второй части мы задействуем HDL workflow, оптимизируем этим инструментом код и зальём код в железку! Всем спасибо за внимание!