Знакомство с ПЛИС. UART на Verilog

UPD: По просьбе пользователя andrey добавлены статьи по каждому из частей — приемнику и передатчику.


Данная статья посвящена вопросу создания универсального асинхронного приемопередатчика (УАПП) — в дальнейшем, ввиду распространенности его английского названия мы будем называть его UART. Статья содержит основные понятия о принципах работы UART, приводится исходный код и результаты работы непосредственно «прошитой» ПЛИС.

UART — является полнодуплексным последовательным приемопередатчиком. Полнодуплексный означает что прием и передача могут происходить одовременно и независимо друг от друга. Последовательный — означает что все данные передаются по одному каналу побайтово.

Структура передачи данных представлена на рисунке 1.

2015-06-19 13-59-40 Скриншот экрана
Рисунок 1 — Метод передачи данных

 

В тот момент, когда отсутствует передача данных — в канале сигнал находится на уровне логической «1». Стартовым битом служит нулевой бит, который оповещает о начале пакета. Следует обратить внимание, что данные передаются от младшего бита к старшему. После того, как один байт (байт состоит из 8 бит) передан выставляется уровень логической «1», что означает конец передаваемого пакета (байта).

Как видно, структура довольно проста.

Приступим к непосредственному описанию блока UART для ПЛИС. Для этого будет использовать язык Verilog. Но перед этим нужно примерно понять структуру приемопередатчика.

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

После приема и обработки осуществим передачу данных. Структура приемопередатчика приведена на рисунке 2.

2015-06-19 13-51-24 Скриншот экрана
Рисунок 2- Схема приемапередатчика UART

На схема блок CLK_smth — передает тактовые сигналы от генератора тактовых импульсов к блокам receive и transmit по витой паре. Данный блок генерируется CoreGen’ом (об этом позже).  RX и TX — соответственно входной и выходной порты. Блок receive принимает последовательные биты, преобразует в байт данных и по готовности байта выставляется флаг rdy и передает данные data[7:0]. Блок transmit принимает данные data[7:0]  по флагу rdy и обратно преобразует их в последовательный вид для выдачи на выход (TX_out).

Данный блок, как видно из схемы, состоит из трех подблоков — CLK_smth, receive и transmit.

Для того,чтобы они функционировали вместе и для их объединения создается специальный файл (top.v), который объединяет нужные порты.

Содержимое top.v (объединения блоков) 

[php]
//—————top.v
`timescale 1ns / 100ps
module UART_top(
input clk_p, // LVDS clocks… отсюда сигнал 200 МГц попадает на PLL
input clk_n, // LVDS clocks… отсюда сигнал 200 МГц попадает на PLL
input RX_in, // UART вход
output TX_out // UART выход
);
wire rdy;
wire [7:0] data;
wire TX_out;
wire clk_out;
clk_wiz_v3_6 CLK_smth(
// Clock in ports
.CLK_IN1_P(clk_p),
.CLK_IN1_N(clk_n),
// Clock out ports
.CLK_OUT1(clk_out)
);
// Core UART приемника
UART_receiver receive(
.RX(RX_in),
.CLK(clk_out),
.Rdy(rdy),
.DATA(data)
);
// Core UART передатчика
UART_transmitter transmit (
.DATA(data),
.CLK(clk_out),
.Rdy(rdy),
.TX(TX_out)
);
endmodule
[/php]

Содержимое receiver.v (приемника UART)

[php]
//———-receiver.v
module UART_receiver (
input RX,
input CLK,
output reg Rdy = 0,
output reg [7:0] DATA
);

reg starter =1’d0;
reg [7:0] count = 8’d0;
reg [2:0] want_to_know;
reg true_RX;
reg [7:0] shifter;
reg [7:0] start = 8’d0;
reg smth =1’d0;

always @( posedge CLK )
begin
if (RX==0)
begin
starter<=1’d1;
end

if (starter)
begin
count<=count+1;
if (count==57||count==114||count==171) // Три выборки от результата деления частоты CLK на скорость UART в бодах (CLK/Vuart) (в начале, середине и конце)
begin // В данном случае CLK — 20 МГц, Vuart = 115200 бод ( значения также необходимо заменить в файле transmitter.v )
want_to_know[2:1]<=want_to_know[1:0];
want_to_know[0]<=RX;
end

if (count==172)
begin
if (want_to_know[2]&&want_to_know[0]||want_to_know[1])
begin
true_RX<=1;
end
else
begin
true_RX<=0;
end
end

if (count==173) // результат от деления частоты CLK на скорость UART в бодах (CLK/Vuart)
begin
count<=0;
start<=start+1; if (start>0&&start<9)
begin
shifter[7:1]<=shifter[6:0];
shifter[0]<=true_RX;
end
else
if (start==9)
begin
DATA<=shifter;
starter<=0;
smth<=1’d1;
start<=0;
end
end
end

if (smth)
begin
Rdy<=1;
smth<=0;
shifter<=8’d0;
end
else
begin
Rdy<=0;
end
end
endmodule
[/php]

Содержимое transmitter.v (передатчика UART)

[php]
module UART_transmitter(
input [7:0] DATA,
input CLK,
input Rdy,
output reg TX =1
);
reg start_trans=1’d0;
reg [7:0] count = 8’d0;
reg [7:0] tr_count = 8’d0;
reg [7:0] shifter;
always @ (posedge CLK)
begin
if (Rdy)
begin
start_trans<=1’d1;
shifter<=DATA;
end
if (start_trans)
begin
tr_count<=tr_count+1;

if (count==0)
begin
if (tr_count<173) // результат от деления частоты CLK на скорость UART в бодах (CLK/Vuart)
begin
TX<=0;
end
else
begin
count<=1;
tr_count<=0;
end
end
else
begin
if (tr_count<173) // результат от деления частоты CLK на скорость UART в бодах (CLK/Vuart)
begin
TX<=shifter[7];
end

else
begin
shifter[7:1]<=shifter[6:0];
tr_count<=0;
count<=count+1;
end
end

if (count==9) // если нужен бит четности, то добавлять if (count==10) begin …. end
begin
TX<=1;
start_trans<=1’d0;
count<=0;
shifter<=8’d0;
end
end
end
endmodule

[/php]

Как уже говорилось CLK_smth — сгенерирована CoreGen’ом и ее код здесь приводить не имеет смысла.
Для тестирования воспользовались Putty, соединили ПК и прошитый ПЛИС через UART.

2015-04-30 18-04-49 Скриншот экрана (2)
Рисунок 3 — Параметры соединения ПК с ПЛИС по UART (номер виртуального COM порта и скорость UART)
2015-04-30 18-04-33 Скриншот экрана (2)
Рисунок 4 — Результаты приема и передачи данных с ПЛИС