FPGA. UART-трансмиттер

Познакомившись с конечными автоматами как методом программирования HDL-устройств, я сделал вывод, что пора переходить созданию так называемых SoC (System on Chip - система в чипе). Так называют HDL-устройства, основой которых является центральный процессор. Таким образом система на чипе является устройством, которое можно программировать, что несомненно гораздо удобнее, чем каждый раз менять архитектуру устройства на языке VHDL или Verilog.

Вполне очевидно, что для общения с таким устройством мне потребуется UART-интерфейс. Поэтому первым же делом я написал UART-трансмиттер, чтобы с его помощью передавать данные в терминальную программу, например screen.

UART-трансмиттер на Verilog

Я не стал усложнять себе задачу, поэтому написанный мною трансмиттер умеет только отправлять данные в асинхронном режиме в формате 8N1 с фиксированным бод-рейтом. Ниже приведён код модуля UartTX (скачать):

module UartTX(baudClkX2, nResetIN, dataIN, sendIN, txOUT, nBusyOUT);

input baudClkX2, nResetIN, sendIN;

input [7:0] dataIN;

output txOUT, nBusyOUT;


reg txOUT = 1'b1;

reg nBusyOUT = 1'b1;

reg [7:0] data;

reg [3:0] bitCnt;


`define READY 0

`define STARTBIT 1

`define SENDDATABIT 2

`define NEXTDATABIT 3

`define LASTDATABIT 4

`define STOPBIT 5

`define NEXTSTOPBIT 6

reg [3:0] state;


always @ (posedge baudClkX2)

if (~nResetIN) begin

txOUT <= 1'b1;

nBusyOUT <= 1'b0;

state <= `READY;

end

else case (state)

`READY:

if (~sendIN) begin

txOUT <= 1'b1;

nBusyOUT <= 1'b1;

state <= `READY;

end

else begin

txOUT <= 1'b0;

nBusyOUT <= 1'b0;

data <= dataIN;

bitCnt <= 0;

state <= `STARTBIT;

end

`STARTBIT: begin

state <= `SENDDATABIT;

end

`SENDDATABIT: begin

txOUT <= data[bitCnt];

if (bitCnt == 7)

state <= `LASTDATABIT;

else

state <= `NEXTDATABIT;

end

`NEXTDATABIT: begin

bitCnt <= bitCnt + 1;

state <= `SENDDATABIT;

end

`LASTDATABIT: begin

state <= `STOPBIT;

end

`STOPBIT: begin

txOUT <= 1'b1;

state <= `NEXTSTOPBIT;

end

`NEXTSTOPBIT: begin

if (sendIN) begin

state <= `NEXTSTOPBIT;

nBusyOUT <= 1'b1;

end

else

state <= `READY;

end

endcase


endmodule

Логика работы модуля довольно проста.

Модуль тактируется с частотой, в 2 раза превышающей заданный бод-рейт (baud rate).

По переднему фронту сигнала sendIN данные входе dataIN буферизируются в регистр data, на выводе txOUT устанавливается низкий уровень (стартовый бит).

Затем данные передаются побитно и при передаче стоп-бита делается проверка, чтобы на сигнале sendIN был низкий уровень.

Передний фронт вывода nBusyOUT сигнализирует о том, что данные переданы и модуль готов отправлять следующий байт данных.

Следующая диаграмма иллюстрирует модуль UartTX как конечный автомат:

Модуль UartTX как конечный автомат (state machine)

Объединяя модули в одно

Чтобы не изображать на схеме каждый раз делитель частоты clkdiv, который к тому же должен делить частоту не на частоту бод-рейта, я решил упаковать соединение этих двух модулей в единый модуль UartTx8N1 (скачать):

module UartTx8N1 (

input wire clkIN,

input wire nResetIN,

input wire [7:0] dataIN,

input wire sendIN,

output wire txOUT,

output wire nBusyOUT );

parameter clkFreq = 48000000;

parameter baudRate = 57600;


wire baudClk2x;


defparam CD.clkfreq = clkFreq;

defparam CD.outfreq = baudRate * 2;

clkdiv CD(.clkIN(clkIN),

.clkOUT(baudClk2x));


UartTX UTX(.baudClkX2(baudClk2x),

.nResetIN(nResetIN),

.dataIN(dataIN),

.sendIN(sendIN),

.txOUT(txOUT),

.nBusyOUT(nBusyOUT));


endmodule

Передаём таблицу ASCII

В качестве теста я решил подать на вход модуля UartTX таблицу ASCII-символов с кодами [32..126]. В качестве привода для отправки я подключил на вход sendIN модуля UartTX выход делителя частоты clkdiv (скачать) с частотой 2 Гц, чтобы 2 раза в секунду отправлялся очередной символ.

Вот исходный код модуля ASCIITable (скачать):

module ASCIITable(

input wire clkIN,

input wire nResetIN,

output reg [7:0] dataOUT = 8'h00

);


always @ (posedge clkIN)

if (~nResetIN) begin

dataOUT <= 8'h20;

end

else begin

if (dataOUT < 8'h7F)

dataOUT <= dataOUT + 1'b1;

else

dataOUT <= 8'h20;

end


endmodule

Блок-cхема устройства получилась следующая:

Блок-схема UART-трансмиттера таблицы ASCII

Особенности HDL-программирования параллельных шин

Модуль AsciiTable на приведённой выше схеме тактируется сигналом CLK2, и этот же сигнал подаётся как триггер отправки очередного символа на входе sendIN модуля UartTx8N1. Как несложно догадаться, модуль AsciiTable в такой схеме просто не должен успевать выдать на свой выход dataOUT очередной символ. На практике схема работает, но степень её "глючности" зависит от результатов компиляции, ведь компилятор все эти сигналы так или иначе оптимизирует.

Чтобы решить эту проблему, я добавил в схему модуль Clk2ToSend (скачать):

module Clk2ToSend (

input wire clkIN,

input wire nResetIN,

input wire clk2IN,

input wire nBusyIN,

output reg sendOUT

);


`define READY 0

`define SENDOUT 1

`define SENDOUT0 2

reg [1:0] state;


always @ (posedge clkIN)

if (~nResetIN) begin

state <= `READY;

sendOUT <= 1'b0;

end

else case (state)

`READY: begin

if (~nBusyIN)

sendOUT <= 1'b0;

else if (clk2IN)

state <= `SENDOUT;

end

`SENDOUT: begin

sendOUT <= 1'b1;

if (~nBusyIN)

state <= `SENDOUT0;

end

`SENDOUT0: begin

sendOUT <= 1'b0;

if (~clk2IN)

state <= `READY;

end

endcase


endmodule

Диаграмма модуля Clk2ToSend как конечного автомата:

Модуль Clk2ToSend как конечный автомат (state machine)

В результате блок-схема получилась следующая (скачать BDF):

Блок-схема UART-трансмиттера с модулями AsciiTable и Clk2ToSend

См. также

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