UART приемник на VHDL

Все говорят что процессы в VHDL выполняются параллельно, а мне надо последовательно!
К примеру сварить кофе, на С это с начла в чашку насыпать кофе, потом залить кипяток. На VHDL кофе и кипяток одновременно, что ж так даже быстрее. А если мне надо пожарить картошку: почистить, порезать, пожарить. С C осталось все по-прежнему, а вот в VHDL появляются непонятки.

На помощь приходит конечный автомат.

Что из себя представляет конечный автомат? На Википедии написано так:
Конечный автомат — абстрактный автомат без выходного потока, число возможных состояний которого конечно. Результат работы автомата определяется по его конечному состоянию.
Даже не знаю кому будет понятно такое определение. Если проще то это некая система, имеющая различные состояния которые определяют текущие действия. (если взять пример с картошкой, то там будет 3 состояния: чистить, резать, жарить).

Есть два типа конечных автоматов, автомат Мура и автомат Мили… мы не будем о них, разбирать теорию не так интересно, чем посмотреть как это делается на практике.

Буду все показывать на примере uart приемника, там у нас такая последовательность: старт бит, прием 8 бит, стоп бит.

Работа программы в общем виде:
Определяем старт передачи, проверяем старт-бит, потом через определенные промежутки времени считываем биты данных, определяем стоп-бит.

Вся программа выглядит так:
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;

entity reciver is
	port ( rx : in std_logic;
			clk : in std_logic;
			data_out : out std_logic_vector (7 downto 0) );
end reciver;

architecture rx of reciver is
signal count : std_logic;
signal counter : integer range 0 to 15;
begin

process (clk)
variable state : integer range 0 to 9 := 0;
variable data : std_logic_vector (7 downto 0);
variable rx_p : std_logic;
begin
if (clk = '1' and clk'event) then

if (rx_p = '1' and rx = '0') then	-- определяем старт
	count <= '1';
	rx_p := rx;
else rx_p := rx;
end if;

if (counter = 8) then
case (state) is
	when 0 =>
			if (rx = '0') then	-- проверяем старт-бит
				state := 1;	
			else state := 0;
				count <= '0';
			end if;
	when 1 => data(0) := rx;	-- запоминаем бит
				state := 2;
	when 2 => data(1) := rx;
				state := 3;
	when 3 => data(2) := rx;
				state := 4;
	when 4 => data(3) := rx;
				state := 5;
	when 5 => data(4) := rx;
				state := 6;
	when 6 => data(5) := rx;
				state := 7;
	when 7 => data(6) := rx;
				state := 8;
	when 8 => data(7) := rx;
				state := 9;
	when 9 => state := 0;
			count <= '0';
			if (rx = '1') then		-- проверяем стоп-бит
				data_out <= data;	-- выводим данные
			end if;
end case;
end if;

end if;
end process;

process (clk)
begin
if (clk = '1' and clk'event) then	
	if (count = '1') then
		counter <= counter + 1;		-- счечик времени через которое считывать бит
	else counter <= 0;
	end if;
end if;
end process;

end architecture;


Сначала разберу всю программу, про конечный автомат в самом конце.

Разберу ее по кусочкам.
if (rx_p = '1' and rx = '0') then	-- определяем старт
	count <= '1';
	rx_p := rx;
else rx_p := rx;
end if;


Поскольку у меня все синхронно (происходит по фронту CLK) определяем старт-бит так: есть переменная которая хранит предыдущее значение (которое было при предыдущем фронте CLK) равна 1, а сейчас 0, тогда это старт-бит. Конечно в последовательности принимаемых данных тоже будут переходы от 1 к 0, но здесь у нас просто изменяется пременная count на 1. Переменная count разрешает тактирование счетчика для отсчета времени, если мы принимаем данные, то счетчик работает, значение этой переменной и так 1, так что ничего страшного.

Идем дальше.
process (clk)
begin
if (clk = '1' and clk'event) then	
	if (count = '1') then
		counter <= counter + 1;		-- счечик времени через которое считывать бит
	else counter <= 0;
	end if;
end if;
end process;


По фронту тактового импульса увеличивается переменная counter если count равен 1, иначе сбрасывается в 0 и ничего не происходит. Считает до 15, по кругу.

Теперь про счетчик и считывание.
Счетчик начинает считать с 0, после того как переменна counter становится 1. Когда счетчик досчитывает до 8, происходит считывание данных:
if (counter = 8) then
case (state) is
	when 0 =>
			if (rx = '0') then	-- проверяем старт-бит
				state := 1;	
			else state := 0;	
				count <= '0';
			end if;
	when 1 => data(0) := rx;	-- запоминаем бит
				state := 2;


Частота передачи в 16 раз меньше частоты тактовых импульсов. После определения старт-бита, досчитываем до 8, тогда попадем в центр бита. Следующее считывание происходит когда счетчик снова досчитает до 8, т. е. пройдет 16 тактов.

Вот он, конечный автомат.
case (state) is
	when 0 =>
			if (rx = '0') then	-- проверяем старт-бит
				state := 1;	
			else state := 0;
				count <= '0';
			end if;
	when 1 => data(0) := rx;	-- запоминаем бит
				state := 2;
	when 2 => data(1) := rx;
				state := 3;
	when 3 => data(2) := rx;
				state := 4;
	when 4 => data(3) := rx;
				state := 5;
	when 5 => data(4) := rx;
				state := 6;
	when 6 => data(5) := rx;
				state := 7;
	when 7 => data(6) := rx;
				state := 8;
	when 8 => data(7) := rx;
				state := 9;
	when 9 => state := 0;
			count <= '0';
			if (rx = '1') then		-- проверяем стоп-бит
				data_out <= data;
			end if;
end case;


Переменная state определяет состояние автомата. Когда state = 0, проверяем наличие старт-бита, он должен быть равен 0.
when 0 =>
			if (rx = '0') then	-- проверяем старт-бит
				state := 1;	
			else state := 0;
				count <= '0';
			end if;


Если это не так, значит произошло ошибочное определения начала передачи, останавливаем счетчик. Если все правильно — state принимает значение 1, автомат переходит в следующее состояние.

Дальше по-очереди происходит считывание каждого бита данных.
when 1 => data(0) := rx;	-- запоминаем бит
		state := 2;
when 2 => data(1) := rx;
		state := 3;
when 3 => data(2) := rx;
		state := 4;
when 4 => data(3) := rx;
		state := 5;
when 5 => data(4) := rx;
		state := 6;
when 6 => data(5) := rx;
		state := 7;
when 7 => data(6) := rx;
		state := 8;
when 8 => data(7) := rx;
		state := 9;


Проверка стоп-бита
Если он не равен 1, тогда прием ошибочный и данные не выводятся
when 9 => state := 0;
			count <= '0';
			if (rx = '1') then		-- проверяем стоп-бит
				data_out <= data;	--выводим данные
			end if;


В конечных автоматах удобно использовать перечисляемые типы данных.
В нашем случае это выглядит так:
-- объявление переменных
type finit_state is (start, bit_0, bit_1, bit_2, bit_3, bit_4, bit_5, bit_6, bit_7, stop);
variable state : finit_state;  


case (state) is
	when start =>
		if (rx = '0') then	-- проверяем старт-бит
			state := bit_0;	
		else state := start;
			count <= '0';
		end if;
	when bit_0 => data(0) := rx;	-- запоминаем бит
		state := bit_1;

и так далее


Для лучшего понимания как это происходит в железе можете прочитать Схемотехническое проектирование для ПЛИС.

Все работает, проверенно. Вот проект в Квартусе под MAX II, там используется внутренний генератор на 5 МГц, частота делиться на 32, скорость приема 9600 бит/с.
uart.rar

Попытайтесь самостоятельно написать передатчик, он проще.

Файлы в топике: uart.zip

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

RSS свернуть / развернуть
пост дублируется… глюк?

можно перетащить в свой блог и удалить дубль…
0
Спасибо
0
Я в течении пяти минут пытался себе представить, как можно одновременно чистить, резать и жарить картошку, настолько зацепил меня этот пример, и самое удивительное ведь на ПЛИС это можно делать :)
0
Ну как-бы, если есть ковеер, то на нем можно одновременно все это делать. Но не с одной картошкой, а с потоком :)
0
Как раз после того как написал комент, подумал про конвейер и долго его себе представлял, как по нему двигается картошка, блин пора завязывать читать так много блогов по электронике :)
0
Писал в момент когда лень еще побеждала чувство голода))
0
Ну, в порядке шутки можно припомнить, как Мюнхгаузен утку шомполом подстрелил)
0
Кстати rx_p = '1' and rx = '0' не всегда прокатывает :)
Как раз на сигналах с меедленными фронтами типа uart'а оно иногда преспокойно переключается с 1 на 0, ни разу не задействовав это условие. Дружно читаем про метастабильность.

И ещё, в VHDL, вообще-то, специально для конечных автоматов есть перечислимые типы.

PS И уберите нафиг переменные, у вас по смыслу это всё сигналы.

PPS Или уж тогда пользуйтесь тем, что это у вас переменные и пишите что-нибудь типа
data(state - 1) := rx;
state := state + 1;
0
Не понятно что вы имели ввиду про (rx_p and rx), они будут как D-триггер, сначала защелкивается rx_p с каким-то значением, потом считывается rx с входа и это все по фронтам тактовых импульсов. Тем более вход ПЛИС можно сконфигурировать как вход с триггером Шмитта.

Изначально у меня программа такая была
when 2 => data (n_bit) := rx;
	n_bit := n_bit + 1;
	if (n_bit = 0) then
		state := 3;	
	end if;

Хотел показать как выполнять последовательные действия на VHDL

Про перечисляемые типы сейчас добавлю.
0
А какие ПЛИСины умеют входы с триггером Шмитта?
У спартана, на котором я имел головную боль на эту тему есть только
> LVCMOS25/33 and LVTTL standards have about 100 mV of hysteresis on inputs.
чего не хватало даже на то чтобы нормально воспринимать фронты, пришедшие с FT232R (при тактовой у спартана 40 МГц).
В итоге я делал по классике — ставил на входе два-три D-триггера в ряд.
0
Я видел на Atmel-ловских CPLD.
0
Вот с ними я больше всего и работал, конкретно в MAX II 3.3V Schmit Trigger Input

А тактовая этого модуля в 16 раз больше скорости юарта, что не так уж и много.
0
Тем более здесь после улавнивание одного переходя с 1 на 0 происходит проверка через 8 тактов, а что было на протяжении этих 8 тактов ничего не меняет.
0
У меня проблема была как раз в том, что он не всегда ловил фронт, когда я написал так же, как написано у вас. То есть спокойно пропускал стартовый бит, а реагировал уже потом на какой-нибудь переход между битами с 1 на 0.
0
Интересно, как-нибудь поэкспериментирую с частотами побольше. Я еще ничего не делал где надо было больше нескольких мегагерц.
0
Ну вот, а я тоже про уарт пишу… не успел =( Ну ладно.
0
Вы писали что вам больше Verilog понравился, так опишите на Верилоге. Тому кто еще не выбрал на чем писать будет интересно сравнить.
0
Как вариант можете передатчик описать :)
0
Ну я, кстати, таки передатчик на Верилоге и описал =) но и приёмник хотел тоже добавить. Впрочем, всё равно лишним не будет.
0
Попытайтесь самостоятельно написать передатчик, он проще.
Я тоже про RS232 на ПЛИС написал. Дарю всем желающим: http://www.chipovod.ru/plis/rs232-plis/
0
Посмотрел твой бложек, понравилась статья про DDS генератор.
0
Интересные статьи. Добавил себе в закладки. Респект вобщем.
0
Хороший пост.
Попытайтесь самостоятельно написать передатчик, он проще
Из той же оперы. Полный VHDL UART acvarif.info/prvhdl/prvhdl7.html
Проверен на скорости 115200
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.