FPGA. UART-ресивер

Написав на языке Verilog модуль UART-трансмиттера, после успешного тестирования я приступил к разработке модуля UART-ресивера, в работе над которым выяснилось, что хотя Verilog позволяет мне как разработчику использовать в языковой конструкции always @ условие negedge (задний фронт импульса), на практике микросхема программируемой логики Cyclone устроена таким образом, что такая конструкция работает достаточно плохо, в отличие от языковой конструкции always @ (posedge).

Тактовый генератор бод-рейта

Модуль тактового генератора бод-рейта, согласно протоколу передачи данных UART 8N1, должен запуститься по заднему фронту на входной линии (RX), и задний фронт импульсов этого генератора должен выступать в качестве отсчёта для битов:

  • 0 -- стартовый бит

  • 1 -- данные[ 0 ]

  • 2 -- данные[ 1 ]

  • ...

  • 8 -- данные[ 7 ]

  • 9 -- стоповый бит

И по стоповому биту генератор можно остановить. Добавив в модуль clkdiv входной сигнал nResetIN, я получил модуль тактового генератора бод-рейта ClkDivEn (скачать):

module ClkDivEn(

input wire clkIN,

input wire nResetIN,

output reg clkOUT = 1'b0

);

parameter clkFreq = 48000000; // Input frequency, Hz

parameter outFreq = 2; // Output frequency, Hz


integer count;


always @ (posedge clkIN)

if (~nResetIN) begin

clkOUT <= 1'b0;

count <= 0;

end

else if (count == 0) begin

clkOUT <= ~clkOUT;

count <= (clkFreq / (2*outFreq)) - 1;

end

else

count <= count - 1;


endmodule

UART-ресивер

Сам модуль UART-ресивера 8N1 UartRX8N1 как конечный автомат получился сравнительно несложным (скачать):

module UartRX8N1 (

input wire clkIN,

input wire nResetIN,

input wire rxIN,

output reg [7:0] dataOUT,

output reg doneOUT

);

parameter clkFreq = 48000000;

parameter baudRate = 57600;


`define READY 0

`define BAUDSTART 1

`define STARTBIT 2

`define NOTSTARTBIT 3

`define NEXTDATABIT 4

`define DATABIT 5

`define PRESTOPBIT 6

`define STOPBIT 7

reg [2:0] state;

reg baudClkOn = 1'b0;

wire baudClkW;


defparam CDE.clkFreq = clkFreq;

defparam CDE.outFreq = baudRate;

ClkDivEn CDE(.clkIN(clkIN),

.nResetIN(baudClkOn),

.clkOUT(baudClkW));


reg [3:0] count;


always @ (posedge clkIN)

if (~nResetIN) begin

dataOUT <= 8'h00;

doneOUT <= 1'b0;

baudClkOn <= 1'b0;

count <= 4'b1111;

end

else case (state)

`READY:

if (~rxIN) begin

baudClkOn <= 1'b1;

state <= `BAUDSTART;

doneOUT <= 1'b0;

end

else

baudClkOn <= 1'b0;

`BAUDSTART:

if (baudClkW) begin

state <= `STARTBIT;

end

`STARTBIT:

if (~baudClkW) begin

if (~rxIN) begin

state <= `NEXTDATABIT;

count <= 4'b1111;

end

else

state <= `NOTSTARTBIT;

end

`NOTSTARTBIT:

if (rxIN)

state <= `READY;

`NEXTDATABIT:

if (baudClkW) begin

state <= `DATABIT;

count <= count + 1'b1;

end

`DATABIT:

if (~baudClkW) begin

dataOUT <= { rxIN, dataOUT[7:1] };

if (count == 4'b0111)

state <= `PRESTOPBIT;

else

state <= `NEXTDATABIT;

end

`PRESTOPBIT:

if (baudClkW)

state <= `STOPBIT;

`STOPBIT:

if (~baudClkW) begin

if (rxIN) begin

doneOUT <= 1'b1;

end

state <= `READY;

end

endcase


endmodule

Регистр данных

Чтобы сохранить принятый байт на время, пока принимается следующий байт, я добавил в схему модуль DataRegister, выходной сигнал resetOUT которого сигнализирует о том, что данные на выходе dataOUT[7..0] скопированы с входа dataIN[7..0] по переднему фронту импульса сигнала storeIN (скачать):

module DataRegister (

input wire clkIN,

input wire nResetIN,

input wire [7:0] dataIN,

input wire storeIN,

output reg [7:0] dataOUT,

output reg resetOUT

);

`define READY 0

`define RESETOUT 1

reg state;


always @ (posedge clkIN)

if (~nResetIN) begin

resetOUT <= 1'b0;

dataOUT <= 8'h0;

state <= `READY;

end

else case (state)

`READY: begin

resetOUT <= 1'b0;

if (storeIN) begin

dataOUT <= dataIN;

state <= `RESETOUT;

end

end

`RESETOUT: begin

resetOUT <= 1'b1;

if (~storeIN)

state <= `READY;

end

endcase


endmodule

Общая схема в итоге получилась следующей (скачать BDF):

Схема использования модулей UartRX8N1 и UartTX8N1 для приёма и отправки принятых данных

В данной схеме данные по линии UART_RX принимаются модулем UartRX8N1, и принятые данные передаются модулем UartTX8N1 по линии UART_TX.

Подключив плату FPGA к микросхеме-конвертеру USB-USART Arduino (FT232R), я воспользовался для проверки работы схемы командой:

screen /dev/ttyUSB0 57600

При этом схема работает как терминал в режиме ECHO ON.

Использование микросхемы конвертера USB-USART Arduino (FT232R) для соединения с микросхемами логического уровня LVTTL подробно описано в статье Осваиваем Olimexino-STM32.

Модули UartTX8N1 и Clk2ToSend, выполняющие передачу данных по протоколу UART 8N1, подробно описаны в статье FPGA. UART-трансмиттер.

Проблема синхронизации

Как показала практика, данная схема может зависать, если тактировать модули слишком высокой частотой осциллятора. Я тестировал её на чипе Cyclone III EP3C25E144C8N, и при этом наблюдались случайного типа зависания. Частота тактирования модулей была 48 МГц (реальный битрейт 57623 бод).

Тогда я попробовал добавить в схему ещё один модуль clkdiv, который использовал как основной сигнал CLK. На частоте 1 МГц (реальный битрейт 58823 бод) данные передавать не получилось совсем. Зато на частоте тактирования 12 МГц (реальный битрейт 57692 бод) всё заработало, и схема перестала зависать.

Как показали дальнейшие исследования, на частоте 5.760 МГц (реальный битрейт 57600), тоже происходят зависания. При этом, волшебная частота 12 МГц странным образом совпадает с внутренней частотой тактирования микросхемы FT232RL, выступающей в роли виртуального COM-порта. И как выяснилось далее, зависаний не происходит, даже если тактировать модуль UartTX8N1 с частотой 5.760 МГц, а остальные модули с частотой 12 МГц. То есть это не модуль трансмиттера подвисал, хотя я был уверен, что подвисает именно он.

Решение проблемы подсказали знающие товарищи. Суть её состоит в том, что последовательный сигнал, при передаче из одного временного домена в другой временной домен, становится асинхронным, и его нужно синхронизировать под другую тактовую частоту. Более подробно можно прочитать в статье Иосифа Каршенбойма Краткий курс HDL.Часть 11. Асинхронные частоты,пересечение клоковых доменови синхронизация.

Я применил простейшее решение - пропустить сигнал через два последовательных D-триггера, добавив в схему модуль UartRxSync (определён в файле uartrx8n1.v).

module UartRxSync (

input wire clkIN,

input wire rxIN,

output wire rxOUT

);

reg [1:0] sync;


assign rxOUT = sync[1];


always @ (posedge clkIN)

sync <= { sync[0], rxIN };


endmodule

Схема проекта (см. статью Управление светодиодами по UART):

FPGA. Схема проекта управления светодиодами по UART

Модуль pll1 (скачать) сгенерирован с помощью Megafunction Wizard (I/O -> ALT_PLL). Работа схемы проверена на частотах тактирования 12 МГц и 5.760 МГц. При этом на частоте 5.760 МГц терминальная программа даже не успевала отображать принимаемые данные, выдавая данные порциями. То есть чем точнее реальный битрейт, тем выше реальная скорость передачи.

Автор: Андрей Шаройко <vanyamboe@gmail.com>