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 как конечный автомат:
Объединяя модули в одно
Чтобы не изображать на схеме каждый раз делитель частоты 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хема устройства получилась следующая:
Особенности 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 как конечного автомата:
В результате блок-схема получилась следующая (скачать BDF):
См. также
Автор: Андрей Шаройко <vanyamboe@gmail.com>