Очередное изобретение велосипедов, или UART_TX и UART_RX на языке Verilog

Значит, сидел я на скучной паре по смехотехнике(ну дело в том, что вот уже 6-ая лекция, а я ничего нового не услышал — все эти регистры, счетчики, дешифраторы для меня давно не новы), и вот что-то захотелось творить! Ну так как под руку подвернулся мой нетбук с установленным Quartus, Notepad++ и Icarus-Verilog, решил я сделать свой велосипед со своими костылями вместо спиц, а именно свои приемник и передатчик UART.
Ну, первым был приемник, как не странно.
uart_rx
Что мы имеем:
1)вход rx, с которого мы принимаем наши данные
2)вход clk, на который подаем тактовую частоту, в 8 раз большую частоты передачи
3)выход данных. В процессе приема данные на нём, в принципе, не определены =)
4)и сигнал окончания приёма. Если 1 — значит, всё устаканилось и можно снять байт с выхода данных, а также принимать новые данные.

Вот он мой быдлокод:

module uart_rx (clk, rx, data, data_ready);

input 	wire 			clk;
input 	wire 			rx;
output 	reg 	[7:0] 		data;
output				data_ready;

reg rx_ff1, rx_ff2; // две защелки, для вычленения старта передачи 
		    // и вообще, защелки надо, чтобы всякие помехи не ловить

always @(posedge clk)
	begin
		rx_ff1 <= rx;
		rx_ff2 <= rx_ff1;
	end
// отлавливаем старт-бит
wire spad = ~rx_ff1 & rx_ff2;
// состояние приемника
reg receive;
// для корректной симуляции. Насколько знаю, квартус это пропускает
initial receive = 0;

//Ну тут понятно - если старт бит, то включаем режим приема,
//если приняли - выключаем
always @(posedge clk)
	if (spad) 
		receive <= 1'b1;
	else 
		if (data_ready)
			receive <= 1'b0;
//cигнал начала приема. Для инициализации счетчиков.
wire start = ~receive & spad;

//поскольку у нас clk в 8 раз быстрее rx, делаем делитель
reg [2:0] count_os;

always @(posedge clk)
	if (start)
		count_os <= 1'b0;
	else
		if(receive)
			count_os <= count_os + 1'b1;
//при переполнении счетчика-делителя выдираем бит из входных данных
wire get_bit = ~|count_os;
//счетчик принятых данных. Как примем 9 бит - можно останавливаться
reg [3:0] count_byte;
always @(posedge get_bit or posedge start)
	begin
		if (start)
			count_byte <= 0;
		else 
			count_byte <= count_byte + 4'b1;
	end
wire data_ready = (count_byte == 9);
//сдвигаем регистр данных на одну позицию вправо,
//и пишем принятый бит в старший бит
always @(negedge get_bit)
	if (!data_ready) data <= {rx_ff1, data[7:1]};
endmodule


Вот такое вот безобразие получилось.
Потом берем, пишем тестбенч:


`timescale 1ns / 1ps

module test_uart_rx;
reg clk;
reg rx;
wire data_received;
wire [7:0] data_in;

uart_rx u_rx (clk, rx, data_in, data_received);

always
  #10 clk = ~clk;

initial
	begin
	
		clk = 0;
		rx	= 1;
		#205 rx = 0;
		#160 rx = 1;
		#160 rx = 0;
		#160 rx = 1;
		#160 rx = 0;
		#160 rx = 1;
		#160 rx = 0;
		#160 rx = 1;
		#160 rx = 0;
		#160 rx = 1;
		#578 rx = 0;
		#160 rx = 1;
		#160 rx = 1;
		#160 rx = 1;
		#160 rx = 0;
		#160 rx = 1;
		#160 rx = 1;
		#160 rx = 1;
		#160 rx = 0;
		#160 rx = 1;
	end
initial
		#10000 $finish;
initial 
	begin
		$dumpfile("out.vcd");
		$dumpvars();
	end

  
endmodule

Симулируем Икарусом (как? можно у марсоходовцев посмотреть) и видим, что байтики принимаются, как надо.
Второй модуль — передатчик.
Ну тут всё просто:

module uart_tx(clk, data, start, tx, ready);

input 	wire 			clk;
input 	wire 	[7:0] 	data;
input 	wire			start;
output	reg				tx;
output	wire			ready;

reg [7:0] data_tx;
reg [2:0] count;
reg [3:0] state;
initial count = 0;

//ну тут тоже сделано так, что передача в 8 раз медленнее клока
always @(posedge clk)
	count <= count + 1;
assign clock = ~|count;

//смена состояний конечного автомата
always @(posedge clk)
	case (state)
		4'b0000: tx <= 1'b1; // при простое держим единицу 
		4'b0001: tx <= 1'b0; // старт-бит
		4'b0010: tx <= data_tx[0];
		4'b0011: tx <= data_tx[1];
		4'b0100: tx <= data_tx[2];
		4'b0101: tx <= data_tx[3];
		4'b0110: tx <= data_tx[4];
		4'b0111: tx <= data_tx[5];
		4'b1000: tx <= data_tx[6];
		4'b1001: tx <= data_tx[7];
		4'b1010: tx <= 1'b1; //стоп-бит
		default: tx <= 1'b1; 
	endcase

assign ready = (state == 4'b0000);

initial state = 0;

always @(posedge clock)
	begin
		if (start & ready) // шоб во время передачи не пытались
			begin
				data_tx <= data; 
				state <= 4'b0001;
			end
		else
			if (state > 4'b1010) // усё передали
				state <= 4'b0000; 
			else 
				if (!ready & clock) 
					state <= state + 1'b1;
	end
endmodule

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

Вот так вот.
Кстати, в железе еще не опробовал. Симуляцию прошел, квартус синтезирует вообще без warning-ов (правда, передатчик еще не пробовал компилить).
А теперь, товарищи знатоки, к вам вопросы:
1) Какие тут есть очевидные косяки? С плис я не так давно знаком, так что пишу пока с не особым знанием дела
2) Как можно прикинуть, будет ли данный модуль, реализованный на EP4CE22F17C6 (установлен на моей de0-nano — вроде самый быстрый циклон из данной линейки) принимать 12 мбит uart с FTDI FT2232HL и где могут быть узкие места?

UPD.1:
Поменял вот что:

always @(posedge clk)
	if (start)
		count_os <= 4'b101;
	else
		if(receive)
			count_os <= count_os + 1'b1;

Это позволило попадать на середину бита rx.
А еще, в конце сделал по переднему фронту(ну тут разницы никакой, просто задний фронт — это вроде как лишний инвертор и лишняя задержка, да и вообще срабатывание по заднему фронту было в роли костыля, который теперь и не нужен):
always @(posedge get_bit)


Алсо, вот кусок времянки:


Отметил старт и стоп биты, всё что между — это передаваемый байт. Тут я передаю 01010101, что в хексе будет 55. Как видно, он выдает на выходе именно 55. Гы, да еще и готов, не дожидаясь стопбита. Алсо, там еще выведен get_bit, и, как видно, он попадает как раз на середину бита rx. То бишь, небольшие отклонения в скорости передачи ему не страшны

UPD.2
Сегодня из коробки с хламом отрыл выпотрошенный DCA-510, он же USB-COM на PL2303. C патченными CHAOS-ом дровами он поддерживает скорость 1625000 бит/с. Запаял я разъемчик, подключил к своей de0-nano, попихал байтики в терминал, светодиодики зажигаются те, что надо.
UPD.3
Понятно, что nobody cares, но надо бы апдейт бахнуть. Заметил я баг в передатчике. Там в одном месте вместо clock написал clk и передатчик не работал. Исправил. Через тот же dca-510 принимал байтЫ с плис.
Всё никак руки не дойдут перепилить с учетом всех замечаний приемник. Вроде и так работает, хотя я его не обкатывал ещё на все 100 ;-)

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

RSS свернуть / развернуть
Гм, а не проще передатчик было на базе сдвигового регистра (каковым он и является) сделать? Да и приемник тоже.
0
  • avatar
  • Vga
  • 10 октября 2011, 21:57
Передатчик — вполне можно, но я тут решил конечный автомат. Можно и регистр, чо. Но велосипед же с костылями, да и мне передатчик пока не нужен.
Насчет приемника — UART того, асинхронный. Был бы какой-нить SPI или другой синхронный последовательный интерфейс — тогда и правда просто регистр. А UART может в любой момент начать передачу. Поэтому модуль проверяет RX с частотой, в 8 раз превышающей частоту передачи. И как только засекает момент передачи, происходит как бы синхронизация(сброс счетчика count_os) и происходит прием. И там внутри есть сигнал get_bit, он-то выскакивает внутри битов uart. Надо вот допилить, чтобы оно попадало на середину бита uart, так лучше будет. Меньше чувствительность к неточным по длительности посылкам.
0
Допилил, теперь ваще лепота. Можно прикрутить бит чОткости и сделать параметризуемым, и всё.
0
По идее нужно просто выжидать стартбит (т.е. падение уровня на RX) и на этот момент синхронизировать клок выборки. А затем уже этим клоком двигать данные в регистр. Внутри кадра все равно синхронизации нету.

А так ИМХО более сложно и подозреваю (ибо ПЛИСы я щупал тока физически, «ути какой тараканчик многоногий») более требовательно к количеству элементов.

Я бы еще понял, если бы приемник реализовывал хитрые алгоритмы (как у AVR с их множественной выборкой каждого бита), а так…
0
Лень компилить, но сегодня что-то около 22 LE показало. При 22320 LE, думаю, это совсем немного ))
Да и тут, по сути, как раз то, что ты и описал. По стартбиту происходит синхронизация клока выборки, и этот клок двигает данные в регистр. Всё так и есть ))
0
Алсо, вот коллега реализовал:
0
не знаю, почему, но коммент запилился недопечатанным.
Ну так вот, коллега реализовал уже свой приемник UART. За исключением того, что он нарисовал на VHDL, он сделал 16х оверсемплинг, а не 8. И у него как раз принимаемые данные побитно распихивает конечный автомат. А у меня — сдвиговый регистр
0
Так и быть, перепилю передатчик под сдвиговый регистр
0
Приличные UAR всегда контролируют наличие стоп-бита. Тем более это важно на высоких скоростях.
также, приёмник обычно делает три информационных выборки и пропускает это дело через мажоритарную схему. и совсем продвинутые контролируют старт-бит на равенство нулю в тесение около 90% длительности бита. как-то так.
0
Спасибо за коммент! Подумаю, как сделать это всё.
0
Воспользовался вашим кодом, вопрос возник только по тактовым частотам,
как я посчитал для скорости 19200 тактовая должна быть 8х19200=153600Гц?
ну или напишите как вы выбираете тактовую для той или иной скорости?
да и насколько передатчик и приемник чувствительны к отличию тактовой от оптимальной?
0
да поигрался с передатчиком, не удобно работает загрузка данных для передачи, только по байтно, и пока байт не передался то следующий загрузить невозможно, да старт загрузки привязан к клоку 8х, что не удобно для двухбайтовых величин. Добавил доп. буффер и логику работы с ним. Теперь можно и в железо…
0
count_os <= 4'b101;
имелось ввиду 3'b101?
0
Есть одна проблемка в стандартных UART-ах — укороченный стоп-бит. У Texas Instruments есть документ — TL16C752C/TL16C754C/TL16C2752 Short STOP Bit Errata. Не увидел в приемнике борьбы с этим.
0
запилите лучше генератор частоты для уарта по алгоритму брезенхема.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.