Verilog State Machine Framework

FSM, Finite State Machine

Рискну предложить почтенной публике мое новейшее "изобретение": VSMF, Verilog State Machine Framework.
Я делаю его для одного из наших внутренних проектов. Пока это не полностью завершенная работа, но скорее демонстрация идеи.

VSMF - это такая даже не библиотека, а набор макросов для упрощенного описания машин состояния на языке Verilog HDL.

Не знаю кому как, но мне бывает трудно описать машину состояния (FSM, Finite State Machine), если состояний много и много разных условий переходов. Хоть я и поклонник Verilog, но здесь на мой взгляд он не очень удобен.

Думаю Вы и сами без труда можете нагуглить статьи типа How to write FSM in Verilog? или Verilog HDL Templates for State Machines

Информации по машинам состояния в интернете много. Примеров много. Но писать FSM все равно не легко.
Если логика у машины состояния сложная, то зачастую у разработчика возникает мысль: "а не вставить ли в ПЛИС какой нибудь софт процессор, чтоб потом на нормальном языке всю логику описать".

Другие идут еще дальше - делают транслятор с традиционных языков типа C/C++ в Verilog или VHDL.
Не плохая мысль, кстати.

Я довольно долго работал над этим вопросом и вот придумал VSMF.

Итак, машина состояний.
Один из способов представления машины состояний - это набор связанных регистров, в каждый момент времени только один регистр хранит бит "1", остальные в это же время хранят "0". Переход из одного состояния в другое состояние - это передача "единички" от одного регистра к другому. Получается одинокая "единичка" путешествует по регистрам состояния, как эстафетная палочка от одного бегуна к другому.

Существует несколько проблем при описании FSM на Verilog:

  1. желательно зараннее сразу продумать все возможные состояния в системе, по возможности желательно задать им имена;
  2. нужно сразу четко понимать направления переходов между состояниями;
  3. нужно сразу продумывать какие действия система производит в каждом из состояний.

Кажется логичным и правильным брать листок бумаги чертить кружочки со стрелочками и долго на это смотреть и обдумывать алгоритмы.

А теперь представьте себе программиста на языке C или Pascal. Они вообще когда нибудь задумываются сколько строчек кода будет в их программе? А ведь условно говоря каждая строчка кода - это как бы новое состояние, новое действие системы.

С самого начала моих изысканий мне хотелось придумать псевдо язык программирования, может быть даже чем-то похожий на язык бейсик. Мне хотелось, что бы мой псевдо язык программирования позволял построчное исполнение "программы" - условно говоря, одна строка программы - это одно состояние машины FSM. При этом хотелось бы использовать какие-то стандартные методы, не изобретать свой компилятор.

После нескольких неудачных попыток пришла мысль сделать набор специальных макросов Verilog. Макросы верилог действуют примерно так же, как и в других языках. Например, на языке C/C++ можно написать 

#define MAGIC_NUMBER 123

и везде, где в тексте программы встретится слово MAGIC_NUMBER оно будет заменено на 123. Так же и в Verilog HDL есть макросы, которые позволяют авто подстановку текста перед компиляцией. В верилог можно написать

`define MAGIC_NUMBER 123

в коде программы Verilog для макроподстановки нужно писать `MAGIC_NUMBER - везде этот макрос будет заменен на 123. Не буду здесь углубляться в тонкости синтаксиса (хотя они есть). Просто скажу, что макросы могут быть сложными, многострочными и принимать параметры. Воспользовавшись этими знаниями я написал несколько макросов Verilog для своего псевдо языка программирования Verilog State Machine Framework.

Вот сразу посмотрите на такой код:

`xprogram(64);
`xstep;
`xstep;
`xstep;
`xstep;
....

И этот макро код будет развернут препроцессором компилятора Verilog HDL и будет представлять примерно такую схему:

xprogram VSMF

На самом деле схема будет немного сложнее, но для простоты понимания я нарисовал ее вот так.

Представьте себе, что макрос `xprogram() создает самый первый регистр машины состояний RS0, тот, который после сброса содержит начальную "единичку". Каждый следующий макрос `xstep автоматически(!) создает последовательно соединенные регистры RS1, RS2, RS3, RS4... В момент сброса сигналом reset все регистры состояния начиная с RS1 будут содержать ноль. После того, как сигнал reset будет снят, "единица" из регистра RS0 начнет свое путешествие по регистрам RS1, RS2, RS3 и так далее с каждым импульсом тактовой частоты. Таким образом, используя вот только эти два макроса уже можно получить несколько поочередных состояний и специально их именовать не нужно.

Теперь, между строк с макросами `xstep можно вписывать какие-то строки, описывающие полезные действия, выполняемые именно на этом шаге. Имя регистра текущего состояния описывается макросом `R. Каждая новый вызов макроса `xstep переопределяет макрос `R. Если хотите, то можно выполнить какое нибудь действие прямо на нужном шаге программы как-то вот так:

`xprogram(64);
`xstep;
`xstep;
`xstep;

reg A;
always @(posedge clk)
if( `R ) A <= .....

`xstep;
....

Здесь присвоение нового значения в регистр A будет происходить именно на шаге программы RS3. На мой взгляд это уже интересно и как-то облегчает жизнь разработчика, но хочется пойти дальше.

Я ввожу еще один макрос `xvar(name,numbits). Этот макрос объявляет специальную переменную для всей программы.
Почему нужна особая переменная, почему не использовать обычный reg языка Verilog? На самом деле обычный регистр reg вполне подходит, но проблема состоит в том, что присвоение разных значений в регистр при разных условиях требует мультиплексоров. Описание этих мультиплексоров (в Verilog они описываются с помощью if-else или case-endcase) - это не простое занятие при описании сложных машин состояний.

Мой макрос `xvar позволяет автоматизировать процесс присвоения значений в регистр во время разных состояний. То есть, мультиплекросы сами собой будут созданы макросом.

Ну и естественно, нужен еще макрос для присвоения значений в переменную. Это макрос `xset(var,new_value).
Теперь мой псевдо язык позволит написать что-то вот такое:

`xprogram(64);
`xvar(counter,16);
`xstep;
`xstep;
`xset(counter,0);
`xstep;
`xstep;
`xset(counter,counter+1);
`xstep;

Здесь в объявленную переменную "counter" присвоение идет из двух мест программы, из двух разных состояний.
В состоянии RS2 в counter будет записан ноль, а во время состояния RS4 значение counter будет увеличено на единицу.

Если не пользоваться макросами VSMF, придется писать примерно вот такой Verilog код:

reg [15:0]counter;
always @(posedge clk)
  if(RS2)
    counter <= 0;
  else
  if(RS4)
    counter <= counter+1;

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

Мне кажется, что описание программы макросами VSMF будет проще.

Хотелось бы отметить, что на одном шаге программы можно делать несколько присвоений в разные переменные, но нельзя делать несколько присвоений в один регистр. Ну вот такой у меня язык программирования. Вот так можно написать:

`xprogram(64);
  `xvar(counter,16);
  `xvar(xxx,8);
`xstep;
`xstep;
  `xset(counter,counter+1);
  `xset(xxx,(xxx<<1) | 8`h01 );
`xstep;
`xstep;
...

К сожалению, я не смог придумать абсолютно элегантного способа представления и создания переменнных с помощью макросов Verilog. Макрос `xvar зараннее генерирует особый регистр для каждого(!) шага программы, а потом ообъединяет их логическим "ИЛИ". При этом появляется избыточное количество объявленных регистров с первоначальным значением ноль, и уже компилятор Verilog потом оптимизируя схему устройства выбрасывает всю избыточную логику. При этом, возникает необходимость зараннее объявлять максимально возможное число шагов в программе в макросе `xprogram(max_num_steps). Не очень удобно, но хоть так.

Идем дальше.
Для нормальных программ нужна возможность условного исполнения и условных переходов - читай переходов между состояниями.

С условным исполнением просто. Вводим макрос `xset_if( variable, value, condition ). В переменную variable будет присвоено значение value, если condition не равен нулю.

С условными переходами несколько сложнее.
Во-первых, нужно как-то обозначить место или состояние куда переходить. Макрос `xstep не именует состояние. Поэтому добавляю специальный макрос `xstate(label). Этот макрос не только создает регистр для хранения состояния, но и делает возможным переход в это состояние из других участков программы.

xstate

Макрос `xstate создает регистр у которого на входе данных есть многовходовый элемент ИЛИ для приема "единицы" состояния не только от предыдущего шага, но и из произвольного места в программе.

Макрос `xgoto(label) передает "единицу" не следующему шагу, а указанному в параметре состоянию.
Соответственно макрос `xgoto_if(label,condition) осуществляет условный переход к нужному состоянию в программе.

Давайте теперь сделаем очень простой проект для платы Марсоход3, который будет демонстрировать возможности псевдо языка VSMF. Опять будем использовать только кнопочки и светодиодики - исключительно с целью сделать простой и понятный проект.

project top module ALtera Quartus II
К модулю simple_sm идут сигналы тактовой частоты clk, сигнал сброса reset, сигналы от кнопочек. Из модуля simple_sm идет восьмибитная шина на светодиоды платы.

Код машины состояний выглядит вот так:

module simple_sm(
  input wire clk,
  input wire reset,
  input wire key0,
  input wire key1,
  output wire [7:0]leds
);

`include "vsmf.v"

`xprogram(64);
`xvar(leds_reg, 8);
`xvar(counter, 32);
`xstep;
`xstep;
`xstep;
  `xset(leds_reg,1);
`xstep;
`xstate(start);
  `xset( counter, 0 );
`xstate(label_cycle);
  `xset(counter, (counter + 1) );
`xstep;
  `xgoto_if(label_cycle,(counter<200000));
`xstep;
  `xgoto_if(shift_right,(key0==0));
`xstep;
  `xset(leds_reg, {leds_reg[6:0], leds_reg[7]} );
  `xgoto(start);
`xstate(shift_right);
  `xset(leds_reg, {leds_reg[0],leds_reg[7:1]} );
  `xgoto(start);

assign leds = leds_reg;

endmodule

Программа делает следующее: горит один светодиодик из восьми и перемещается по кругу влево. Если нажать кнопочку key0, то светодиодик начинает перемещаться вправо.

Чтобы пояснить программу я раскрасил ее в разные цвета.
Текст выделенный красным показывает внутренний цикл по счетчику counter. Здесь реализована обычная программная задержка, как ее обычно пишут в микроконтроллерах, всяких ардуинах и т.д. От этой задержки зависит как быстро будет моргать светодиодик.

Синим цветом показаны присвоения в переменную leds_reg из разных состояний FSM.

Так же обратите внимание, что переход на одну метку программы start может происходить из разных мест программы. У меня одно состояние без имени делает циклический сдвиг содержимого регистра leds_reg влево, а состояние shift_right сдвигает содержимое этого же регистра вправо.

Таким образом, использование макросов VSMF позволяет использовать Verilog HDL для описания последовательно исполняемых программ.

Надеюсь Verilog State Machine Framework покажется вам интересной идеей и вы сможете использовать его в своих проектах.

Здесь можно скачать весь проект для Altera Quartus II для платы Марсоход3: 

  Здесь в архиве есть все необходимые файлы.

Видеодемонстрация проекта в плате Марсоход3:

Снимал видео в довольно темном месте, чтоб хоть чуть чуть было видно бегающий огонек светодиода. Из-за этого видео немного шумит. Так получилось...

 


Добавить комментарий