
Всем привет. Пару недель назад я начал потихоньку изучать программирование под ПЛИС. Для этих целей мною была заказана у китайцев самая дешевая плата на основе Altera Max II EPM240T100C5N чипа. Установив Quartus v15, стал изучать Verilog стандарта 2001 года. Наморгавшись светодиодами решил попробовать реализовать какой-нибудь протокол передачи данных. Естественно им стал UART :) Посмотрев на чужие примеры в сети, мне не очень понравилось излишнее нагромождение логики, множество дополнительных счетчиков, а главное, проблемы с синхронизацией в приемнике и, как следствие, не стабильность работы на высоких скоростях. Конечно можно найти и качественные реализации, полностью конфигурируемые, да и вообще, с «идеальным кодом», но так не будет ни какого спортивного интереса.
И так, задача стояла реализовать максимально компактный, стабильный и простой 8-ми битный асинхронный приемопередатчик с 1-м стартовым и 1-м стоповым битом. Одним словом — классика. Но как оказалось задача не такая уж тривиальная, какой она была на первый взгляд. Реализовав приемник и передатчик буквально за один вечер, мне пришлось потратить еще два, что-бы заставить логику микросхемы не проглатывать, корректно принимать и отсылать поток байт, без ошибок.
Основываясь на критике и пожеланиях в комментариях, мною была проведена работа над ошибками, и в статье представлена уже вторая реализация данного модуля. Прием и отправка данных были переведены на сдвиговые регистры, добавлена мажоритарная схема на вход RX из трех элементов, избавился от блокирующих присваиваний в синхронных блоках и счетчики тактового сигнала UART считают от максимального значения к нулю.

Файлы проекта:
- Main
- UART
- UART_TX
- UART_RX
- RXMajority3Filter
Начнем с модуля UART_TX:
module UART_TX #
(
	parameter CLOCK_FREQUENCY = 50_000_000,
	parameter BAUD_RATE       = 9600
)
(
	input  clockIN,
	input  nTxResetIN,
	input  [7:0] txDataIN,
	input  txLoadIN,
	output wire txIdleOUT,
	output wire txReadyOUT,
	output wire txOUT
);
localparam HALF_BAUD_CLK_REG_VALUE = (CLOCK_FREQUENCY / BAUD_RATE / 2 - 1);
localparam HALF_BAUD_CLK_REG_SIZE  = $clog2(HALF_BAUD_CLK_REG_VALUE);
reg [HALF_BAUD_CLK_REG_SIZE-1:0] txClkCounter = 0;
reg txBaudClk       = 1'b0;
reg [9:0] txReg     = 10'h001;
reg [3:0] txCounter = 4'h0; 
assign txReadyOUT = !txCounter[3:1];
assign txIdleOUT  = txReadyOUT & (~txCounter[0]);
assign txOUT      = txReg[0];
always @(posedge clockIN) begin : tx_clock_generate
	if(txIdleOUT & (~txLoadIN)) begin
		txClkCounter <= 0;
		txBaudClk    <= 1'b0;
	end
	else if(txClkCounter == 0) begin
		txClkCounter <= HALF_BAUD_CLK_REG_VALUE;
		txBaudClk    <= ~txBaudClk;
	end
	else begin
		txClkCounter <= txClkCounter - 1'b1;
	end
end
always @(posedge txBaudClk or negedge nTxResetIN) begin : tx_transmit
	if(~nTxResetIN) begin
		txCounter <= 4'h0;
		txReg[0]  <= 1'b1;
	end
	else if(~txReadyOUT) begin
		txReg     <= {1'b0, txReg[9:1]};
		txCounter <= txCounter - 1'b1;
	end
	else if(txLoadIN) begin
		txReg     <= {1'b1, txDataIN[7:0], 1'b0};
		txCounter <= 4'hA;
	end
	else begin
		txCounter <= 4'h0;
	end
end
endmodule
Разберем все по порядку:
module UART_TX #
(
	parameter CLOCK_FREQUENCY = 50_000_000,
	parameter BAUD_RATE       = 9600
)
(
	input  clockIN,
	input  nTxResetIN,
	input  [7:0] txDataIN,
	input  txLoadIN,
	output wire txIdleOUT,
	output wire txReadyOUT,
	output wire txOUT
);
Параметры CLOCK_FREQUENCY и BAUD_RATE это частота кварцевого резонатора и частота UART передатчика соответственно.
Входящие порты:
clockIN — порт тактового сигнала с кварцевого резонатора.
nTxResetIN — порт сброса по отрицательному фронту.
txDataIN — восьмибитная шина данных.
txLoadIN — порт начала передачи данных.
Исходящие порты:
txIdleOUT — порт «простоя» передатчика, выставляется в лог. 1 при полном завершении цикла передачи байта данных, если на порту txLoadIN не будет присутствовать лог. 1.
txReadyOUT — порт, лог. 1 на котором, будет означать что стоповый бит был отправлен, и можно загружать новые данные.
txOUT — порт последовательной передачи исходящих данных, который нужно назначить на ножку ПЛИС.
localparam HALF_BAUD_CLK_REG_VALUE = (CLOCK_FREQUENCY / BAUD_RATE / 2 - 1);
localparam HALF_BAUD_CLK_REG_SIZE  = $clog2(HALF_BAUD_CLK_REG_VALUE);
reg [HALF_BAUD_CLK_REG_SIZE-1:0] txClkCounter = 0;
reg txBaudClk       = 1'b0;
reg [9:0] txReg     = 10'h001;
reg [3:0] txCounter = 4'h0; 
assign txReadyOUT = !txCounter[3:1];
assign txIdleOUT  = txReadyOUT & (~txCounter[0]);
assign txOUT      = txReg[0];
Локальный параметр HALF_BAUD_CLK_REG_VALUE — значение счетчика-делителя частоты полупериода тактового сигнала UART. Вычисляется по формуле CLOCK_FREQUENCY / BAUD_RATE / 2 — 1.
Локальный параметр HALF_BAUD_CLK_REG_SIZE — разрядность этого самого счетчика. Вычисляется чудесной функцией $clog2 — логарифмом по основанию 2 от значения параметра HALF_BAUD_CLK_REG_VALUE.
Регистры reg:
txClkCounter — счетчик-делитель частоты тактового сигнала.
txBaudClk — тактовый сигнал для передатчика.
txReg — сдвиговый регистр в который будут записываться байт данных, стартовый и стоповый бит.
txCounter — счетчик количества отправленных бит.
Провода wire:
txReadyOUT назначен непрерывным соединением через логическое отрицание на 4-й, 3-й и 2-й бит регистра txCounter Принимает состояние лог. 1 при достижении счетчиком txCounter значения 1 или 0.
txIdleOUT назначен непрерывным соединением на txReadyOUT и через логический примитив AND на инвертированный 1-й бит регистра txCounter. Принимает состояние лог. 1 при достижении счетчиком txCounter значения 0.
txOUT назначен непрерывным соединением на 1-й бит регистра txReg
Передача данных:
always @(posedge txBaudClk or negedge nTxResetIN) begin : tx_transmit
	if(~nTxResetIN) begin
		txCounter <= 4'h0;
		txReg[0]  <= 1'b1;
	end
	else if(~txReadyOUT) begin
		txReg     <= {1'b0, txReg[9:1]};
		txCounter <= txCounter - 1'b1;
	end
	else if(txLoadIN) begin
		txReg     <= {1'b1, txDataIN[7:0], 1'b0};
		txCounter <= 4'hA;
	end
	else begin
		txCounter <= 4'h0;
	end
end
По отрицательному фронту на порту nTxResetIN, который проверяется в первом условии, на первом бите регистра txReg выставляется лог. 1, а регистр txCounter принимает значение 0, что дает на выходах txIdleOUT и txReadyOUT и txOUT лог. 1.
В противном случае по положительному фронту на порту txBaudClk проверяется значение сигнала порта txReadyOUT, и, при лог. 0, содержимое регистра txReg сдвигается в сторону младших бит, а счетчик txCounter уменьшается на единицу, и при достижении значения 1 на выходе txReadyOUT будет установлена лог. 1.
Иначе по положительному фронту на порту txBaudClk проверяется сигнал порта txLoadIN, и, при лог. 1, в регистр txReg попадают значение со входа txDataIN стартовый и стоповый бит, счетчик txCounter принимает значение 10 (4'hA), что даст отрицательный фронт на выходах txIdleOUT и txReadyOUT и txOUT — что будет сигнализировать начало передачи данных (стартовый бит).
Иначе регистр txCounter принимает значение 0, и на выходе txIdleOUT появляется лог. 1.
Стоит отметить что по данной логике при лог. 1 на txLoadIN данные будут постоянно забираться со входа txDataIN в регистр txReg и последовательно передаваться на выход txOUT. Т.е. для прекращения передачи пакета данных, нужно сбросить txLoadIN в лог. 0 до того, как будет полностью передан стоповый бит. Лучший способ — это сброс txLoadIN по отрицательному фронту на порту txReadyOUT. Прервать процесс передачи байта данных логическим нулем на txLoadIN нельзя. Для этого можно использовать nTxResetIN.
Формирование тактового сигнала передатчика:
always @(posedge clockIN) begin : tx_clock_generate
	if(txIdleOUT & (~txLoadIN)) begin
		txClkCounter <= 0;
		txBaudClk    <= 1'b0;
	end
	else if(txClkCounter == 0) begin
		txClkCounter <= HALF_BAUD_CLK_REG_VALUE;
		txBaudClk    <= ~txBaudClk;
	end
	else begin
		txClkCounter <= txClkCounter - 1'b1;
	end
end
По положительному фронту тактового сигнала на порту clockIN в первом условии проверяется лог. 1 на txIdleOUT и лог. 0 на txLoadIN, и при выполнении условия регистр txClkCounter сбрасывается в 0, и на тактовом сигнале txBaudClk устанавливается лог. 0. Т.е. тем самым мы гарантируем что при лог. 1 на txDataIN передатчик начнет передачу данных уже по следующему положительному фронту clockIN.
В противном случае txClkCounter проверяется на достижение значения 0, и при выполнении условия в txClkCounter будет записано значение параметра HALF_BAUD_CLK_REG_VALUE а txBaudClk инвертирует свое состояние.
Иначе txClkCounter уменьшит свое значение на 1.
Временная диаграмма сигналов модуля UART_TX:

Модуль UART_RX:
module UART_RX #
(
	parameter CLOCK_FREQUENCY = 50_000_000,
	parameter BAUD_RATE       = 9600
)
(
	input  clockIN,
	input  nRxResetIN,
	input  rxIN, 
	output wire rxIdleOUT,
	output wire rxReadyOUT,
	output wire [7:0] rxDataOUT
);
localparam HALF_BAUD_CLK_REG_VALUE = (CLOCK_FREQUENCY / BAUD_RATE / 2 - 1);
localparam HALF_BAUD_CLK_REG_SIZE  = $clog2(HALF_BAUD_CLK_REG_VALUE);
reg [HALF_BAUD_CLK_REG_SIZE-1:0] rxClkCounter = 0;
reg rxBaudClk = 1'b0;
reg [9:0] rxReg = 10'h000;
wire rx;
assign rxIdleOUT      = ~rxReg[0];
assign rxReadyOUT     = rxReg[9] & rxIdleOUT;
assign rxDataOUT[7:0] = rxReg[8:1];
RXMajority3Filter rxFilter
(
	.clockIN(clockIN),
	.rxIN(rxIN),
	.rxOUT(rx)
);
always @(posedge clockIN) begin : rx_clock_generate
	if(rx & rxIdleOUT) begin
		rxClkCounter <= HALF_BAUD_CLK_REG_VALUE;
		rxBaudClk    <= 0;
	end
	else if(rxClkCounter == 0) begin
		rxClkCounter <= HALF_BAUD_CLK_REG_VALUE;
		rxBaudClk    <= ~rxBaudClk;
	end
	else begin
		rxClkCounter <= rxClkCounter - 1'b1;
	end
end
always @(posedge rxBaudClk or negedge nRxResetIN) begin : rx_receive
	if(~nRxResetIN) begin
		rxReg <= 10'h000;
	end
	else if(~rxIdleOUT) begin
		rxReg <= {rx, rxReg[9:1]};
	end
	else if(~rx) begin
		rxReg <= 10'h1FF;
	end
end
endmodule
module UART_RX #
(
	parameter CLOCK_FREQUENCY = 50_000_000,
	parameter BAUD_RATE       = 9600
)
(
	input  clockIN,
	input  nRxResetIN,
	input  rxIN, 
	output wire rxIdleOUT,
	output wire rxReadyOUT,
	output wire [7:0] rxDataOUT
);
Во многом похож на модуль UART_TX.
Входящие порты:
clockIN и nRxResetIN имеют те-же значения что и в модуле UART_RX
rxIN — входящий порт последовательной передачи данных, который нужно назначить на ножку ПЛИС.
Исходящие порты:
rxIdleOUT — порт «простоя» приемника, выставляется в лог. 1 при полном завершении цикла приема байта данных.
rxReadyOUT — порт готовности приемника. При переходе в лог. 1 показывает, что был принят байт данных, который завершился стоповым битом (лог. 1). Переходит в состояние лог. 0 при лог. 0 на порту nRxResetIN или при начале приема следующего байта данных.
rxDataOUT — восьмибитная шина принятых данных.
localparam HALF_BAUD_CLK_REG_VALUE = (CLOCK_FREQUENCY / BAUD_RATE / 2 - 1);
localparam HALF_BAUD_CLK_REG_SIZE  = $clog2(HALF_BAUD_CLK_REG_VALUE);
reg [HALF_BAUD_CLK_REG_SIZE-1:0] rxClkCounter = 0;
reg rxBaudClk = 1'b0;
reg [9:0] rxReg = 10'h000;
wire rx;
assign rxIdleOUT      = ~rxReg[0];
assign rxReadyOUT     = rxReg[9] & rxIdleOUT;
assign rxDataOUT[7:0] = rxReg[8:1];
Регистры reg:
rxClkCounter — счетчик-делитель частоты тактового сигнала.
rxBaudClk — тактовый сигнал для приемника.
rxReg — сдвиговый регистр, который хранит 8 бит принятых данных, стартовый и стоповый бит.
Провода wire:
rx — сигнал входящих последовательных данных, пропущенный через мажоритарную логику модуля RXMajority3Filter.
rxIdleOUT непрерывно назначен на на инвертированный 1-й бит регистра rxReg. Принимает лог. 1 при окончании приема данных, когда в регистр rxReg[0] будет записан стоповый бит.
rxReadyOUT непрерывно назначен на 10-й бит регистра rxReg и rxIdleOUT через логический примитив AND. Принимает лог. 1 если прием данных был завершен и в регистре rxReg 10-й бит принял значение лог. 1 (стоповый бит).
rxDataOUT назначен с 9 по 1 биты регистра rxReg.
Прием данных:
always @(posedge rxBaudClk or negedge nRxResetIN) begin : rx_receive
	if(~nRxResetIN) begin
		rxReg <= 10'h000;
	end
	else if(~rxIdleOUT) begin
		rxReg <= {rx, rxReg[9:1]};
	end
	else if(~rx) begin
		rxReg <= 10'h1FF;
	end
end
По отрицательному фронту на порту nRxResetIN, будет выполнено первое условие, и rxReg сбросится в 0, что установит лог. 0 на порту rxReadyOUT и лог. 1 на rxIdleOUT.
В противном случае при лог. 0 на порту rxIdleOUT содержимое регистра rxReg сдвигается в сторону младших бит, а в старший бит будет записано текущее состояние сигнала rx.
Иначе лог. 0 сигнала rx будет означать начало передачи данных (стартовый бит), и в регистр rxReg во все биты кроме старшего (стартовый бит) будут записаны единицы (10'h1FF).
Формирование тактового сигнала приемника:
always @(posedge clockIN) begin : rx_clock_generate
	if(rx & rxIdleOUT) begin
		rxClkCounter <= HALF_BAUD_CLK_REG_VALUE;
		rxBaudClk    <= 0;
	end
	else if(rxClkCounter == 0) begin
		rxClkCounter <= HALF_BAUD_CLK_REG_VALUE;
		rxBaudClk    <= ~rxBaudClk;
	end
	else begin
		rxClkCounter <= rxClkCounter - 1'b1;
	end
end
Назначение второго и третьего условия идентично условию из модуля UART_TX — формирование тактового сигнала для приемника.
В первом-же условии проверяются лог. 1 сигнала rx и лог. 1 сигнала rxIdleOUT, и при выполнении условия в txClkCounter будет записано значение параметра HALF_BAUD_CLK_REG_VALUE, а на rxBaudClk будет установлен лог. 0.
Т.е. при появлении лог. 0 (стартовый бит) на порту rx, счетчик отсчитает половину периода тактового сигнала приемника, и только после этого будет начат прием данных.
Временная диаграмма сигналов модуля UART_RX:

Модуль RXMajority3Filter:
module RXMajority3Filter
(
	input clockIN,
	input rxIN,
	output wire rxOUT
);
reg [2:0] rxLock = 3'b111;
assign rxOUT = (rxLock[0] & rxLock[1]) | (rxLock[0] & rxLock[2]) | (rxLock[1] & rxLock[2]);
always @(posedge clockIN) begin
	rxLock <= {rxIN, rxLock[2:1]};
end
endmodule
Представляет собой реализацию мажоритарного элемента на сдвиговом регистре из трех элементов.
Модуль UART:
module UART #
(
	parameter CLOCK_FREQUENCY = 50_000_000,
	parameter BAUD_RATE       = 9600
)
(
	input  clockIN,
	
	input  nTxResetIN,
	input  [7:0] txDataIN,
	input  txLoadIN,
	output wire txIdleOUT,
	output wire txReadyOUT,
	output wire txOUT,
	
	input  nRxResetIN,
	input  rxIN, 
	output wire rxIdleOUT,
	output wire rxReadyOUT,
	output wire [7:0] rxDataOUT
);
defparam  uart_tx.CLOCK_FREQUENCY = CLOCK_FREQUENCY;
defparam  uart_tx.BAUD_RATE       = BAUD_RATE;
UART_TX uart_tx
(
	.clockIN(clockIN),
	.nTxResetIN(nTxResetIN),
	.txDataIN(txDataIN),
	.txLoadIN(txLoadIN),
	.txIdleOUT(txIdleOUT),
	.txReadyOUT(txReadyOUT),
	.txOUT(txOUT)
);
defparam  uart_rx.CLOCK_FREQUENCY = CLOCK_FREQUENCY;
defparam  uart_rx.BAUD_RATE       = BAUD_RATE;
UART_RX uart_rx
(
	.clockIN(clockIN),
	.nRxResetIN(nRxResetIN),
	.rxIN(rxIN), 
	.rxIdleOUT(rxIdleOUT),
	.rxReadyOUT(rxReadyOUT),
	.rxDataOUT(rxDataOUT)
);
endmodule
Просто объединяет два модуля UART_RX и UART_TX в единое целое, пробрасывая входящие и исходящие сигналы, и значения параметров частоты кварцевого резонатора и частоты UART передатчика.
И собственно модуль верхнего уровня Main:
module Main
(
	input  wire clockIN,
	input  wire uartRxIN,
	output wire uartTxOUT
);
defparam uart.CLOCK_FREQUENCY = 50_000_000;
defparam uart.BAUD_RATE       = 921600;
reg [7:0] txData;
reg txLoad  = 1'b0;
wire txReset = 1'b1;
wire rxReset = 1'b1;
wire [7:0] rxData;
wire txIdle;
wire txReady;
wire rxIdle;
wire rxReady;
UART uart
(
	.clockIN(clockIN),
	
	.nTxResetIN(txReset),
	.txDataIN(txData),
	.txLoadIN(txLoad),
	.txIdleOUT(txIdle),
	.txReadyOUT(txReady),
	.txOUT(uartTxOUT),
	
	.nRxResetIN(rxReset),
	.rxIN(uartRxIN), 
	.rxIdleOUT(rxIdle),
	.rxReadyOUT(rxReady),
	.rxDataOUT(rxData)
);
always @(posedge rxReady or negedge txReady) begin
	if(~txReady)
		txLoad <= 1'b0;
	else if(rxReady) begin
		txLoad <= 1'b1;
		txData <= rxData;
	end
end
endmodule
Является по сути простым «эхо» тестом.
По положительному фронту на порту rxReady входящие данные будут записаны в регистр txData, который назначен на вход txDataIN передатчика, и регистр txLoad, который назначен на вход передатчика txLoadIN будет выставлен в лог. 1, для начала передачи.
По отрицательному фронту на порту txReady, регистр txLoad примет значение лог. 0.
Данный модуль был протестирован на плате с Altera Max II EPM240T100C5N чипом и кварцевым резонатором с частотой 50 мегагерц, со скоростью UART в 921600 baud (максимальная скорость, которую поддерживает мой USB-UART переходник).
По стандарту, для приемника, частота сэмплирования стартового бита должна быть минимум в 16 раз больше частоты UART. Так что для стабильной работы модуля при 921600 baud rate, частота кварцевого резонатора должна быть не ниже 921600 * 16 = 14'745'600 герц. Например пойдет кристалл на 16 мегагерц.
Также желательно поставить подтягивающий резистор на вход приемника.
Как обычно, любые советы по оптимизации и улучшении приветствуются.
Скачать обновленные файлы можно тут.
Комментарии (30)
 - Hypnotriod26.02.2016 14:21+1- Ну и раз уж статью делайте, то где поддержка parity bit, flow control, приёмный и передающий буфера?:) 
 Ну так хотелось сделать все как можно проще :) Что-бы влезало в 240 ячеек Max II EPM240, и осталось место под полезную логику. Ну flow control и буфера можно внешне прикрутить, по надобности. Parity bit не делал :)
 На счет rxIN. Нужно поставить пару-тройку регистров, как-то так?
 
 - reg [1:0] rxLock = 2'b11; wire rx = (|rxLock); always @(posedge clockIN) begin rxLock = rxLock << 1; rxLock[0] = rxIN; end
 - О ужос. 
 А чего, неужто все так плохо? - holyglory27.02.2016 07:36- Да, все оч плохо. 
 Ну в смыслп я бы такую статью не публиковал.
 - if(rxIN & rxIdleOUT) begin rxClkCounter <= 0;
 
 здесь вы по сути описываете конечный автомат, а rxIN берете прямо со входа. Соответственно, когда фронт rxIN придет очень близко к фронту клока, rxClkCounter потенциально может принять рандомное значение. На симуляциив RTL вы этого не заметите, а вот в железе будет глючить. Ну или можете поэксперементировать с моделированием нет листа с подключенным Back annotation
 
 - if(~nRxResetIN) begin rxReg[8] <= 1'b0; rxIndex <= 4'h9; end else if(~rxIdleOUT) begin rxReg[rxIndex] = rxIN; rxIndex = rxIndex + 1'b1; end
 
 Здесь лучше подойдёт сдвиговый регистр:
 
 `
 reg [8:0] rxShReg;
 
 always @(posedge clk ...)
 …
 if (clr)
 rxShReg <= #T 9'h1FF;
 else
 if (rx_do_shift)
 rxShReg <= #T {rxShReg[7:0], rx_in};
 `
 
 Если в него же задвигать стартовый бит, то по нулю в txShReg[8] будете ловить конец приёма.
 rx_in лучше сделать выходом маожоритарной схемы ((a&b)|(a&c)|(b&c)), где a,b,c — входной сдвиговы регистр. Тогда отфильтруете возможные глитчи на длинных проводах, или косяки, связанные с длинными фронтами.
 
 С отправкой такая же шляпа:
 - txPin = txData[txIndex];
 у вас здесь вместо изящненького сдвигового регистра, мультиплексор на 8 входов и еще один регистр на выходе. Ничего страшного, если вы пишите UART для макса, но становится страшно за код, который вы родите в более серьезном проекте :) - Hypnotriod27.02.2016 15:14- Все приму к сведению. По поводу сдвигового регистра согласен полностью, нужно переписать. 
 Но, в этом блоке как раз и решается проблема с захватом стартового бита:
 
 - if(rxIN & rxIdleOUT) begin rxClkCounter <= 0; rxBaudClk <= 0; end
 Т.е. если мы захватили стартовый бит, мы будем инкрементировать счетчик делителя для клока как минимум 8 раз при лог 0 на rxIN, пока не стартует прием. Если мы поймаем глитч, а потом линия rxIN вернется в лог. 1, то rxClkCounter сбросится в 0, и будет ждать нового спада на линии rxIN, что-бы опять посчитать эти 8 клоков перед начало захвате. По моему скромному мнению это решение на много эффективнее, т.к. попросту экономит три регистра. Но опять-же я могу ошибаться.
 
 
 - 32bit_me26.02.2016 14:56+1- А чего, неужто все так плохо? 
 Ничего хорошего. Пользуйтесь в синхронных always только неблокирующими присваиваниями, иначе будут образовываться защёлки (latch), что ведёт к куче негативных последствий. - Hypnotriod26.02.2016 15:10- Можно поподробней, какие могут быть негативные последствия при данной реализации?  - 32bit_me26.02.2016 15:15+1- Защёлки приводят к разным труднопрогнозируемым багам (glitches и т.п.), поэтому лучше просто так не делать вообще никогда. Синтезатор должен, кстати, выдавать предупреждение об этом. Вы в Quartus II делали?  - Hypnotriod26.02.2016 15:20- Да, в квартусе. Предупреждений не было по этому поводу. Просто я не вижу другого варианта, в данной ситуации.  - 32bit_me26.02.2016 15:34- В любой ситуации можно сделать схему без защёлок. 
 Во-первых: output wire — это не есть хорошо. Выходы настоятельно рекомендуется делать reg-ами. - Hypnotriod26.02.2016 15:45- Так он там и есть регистр, вернее назначен на регистр, иначе и работать не будет. А есть ссылка на литературу, где рекомендуется использовать на выходе именно регистр? 
 Просто я так себе представляю, что модуль — это интерфейс. И про реализацию его пользователь знать не должен. Какие выходы на какие регистры назначены, и через какие лог. примитивы, и т.п. - 32bit_me26.02.2016 15:50- Очень рекомендую: Evgeni Stavinov "100 Power Tips for FPGA Designers"  - YuriPanchul26.02.2016 21:35+2- Я лично знаю Ставинова в реале. 
 
 При этом вы перепутали: проблема c блокирующим присваиванием в синхронных always-блоках вовсе не latch (он возникнуть в них не может), а race condition — зависимость результатов от порядка симуляции always-блоков:
 
 Вот иллюстрация проблемы с race conditions:
 
 http://www.silicon-russia.com/2015/03/19/intro-rtl-fpga-verilog-midterm-1-1/
 
 - module dut ( input clk, input [7:0] d, output logic [7:0] q ); logic [7:0] r; always @(posedge clk) // ПЛОХО! Результат симуляции зависит от того, какой блок симулируется раньше в event queue r = d; always @(posedge clk) q = r; endmodule
 Вот иллюстрация проблемы с latch:
 
 - always @ (posedge clock) if (a) b <= c; // Получается D-flip-flop (D-триггер) always @ (a or c) if (a) b = c; // Получается latch (защелка)
 Вот правила:
 
 7.2 Which rule for signal assignment is violated in the following code?
 
 a) Synchronous sequential logic: use always @(posedge clk) or always_ff @(posedge clk)
 and nonblocking assignments (<=)
 
 - always_ff @ (posedge clk) q <= d; // nonblocking
 b) Simple combinational logic: use continuous assignments (assign.)
 
 - assign y = a & b;
 c) More complicated combinational logic: use always @* or always_comb and blocking assignments (=)
 
 d) Assign a signal in only one always statement or continuous assignment statement
 
 e) This code does not violate any rules for signal assignment
 
 Вообще вот все варианты
 
 http://silicon-russia.com/exams_and_quizes/2015_08_08_quiz_4_ff/2015_08_08_quiz_4_ff.html
 
 В output wire ничего плохого нет, оно может идти с более нижнего уровня иерархии или быть подсоединено к регистру.
 
  - UA3MQJ26.02.2016 21:22- Да практически всегда на выходе должен быть регистр. Иначе, если это комбинационная схема, то результат на её выходе будет с задержкой. 
 Почитайте немного, должно проясниться немного, как было у меня:
 https://habrahabr.ru/post/254869/
 https://habrahabr.ru/company/metrotek/blog/235037/
 
 Вообще, тут передача получается из одного устройства в другое. Эти устройства не имеют единого клока, так что работать ИМХО надо как при передаче сигнала между двумя клоковыми доменами.
 http://www.fpga4fun.com/CrossClockDomain.html
 То есть по своей тактовой частоте записывать в регистр. А уже с данными на его выходе работать. - YuriPanchul26.02.2016 21:40- Да практически всегда на выходе должен быть регистр. Иначе, если это комбинационная схема, то результат на её выходе будет с задержкой 
 Синтаксис "output wire" вовсе не означает, что это выход от комбинационной схемы.
 
 Например этот вывод может идти от более нижнего уровня иерархии или быть подсоединенным к регистру:
 
 …
 input i,
 output wire o
 );
 
 reg r;
 
 always @ (posedge clock)
 r <= i;
 
 assign o = r; // Так будет работать и вывод будет из регистра
 
 Задержка с delta-cycle — не реальная задержка - UA3MQJ26.02.2016 22:13- Синтаксис «output wire» вовсе не означает, что это выход от комбинационной схемы 
 Я имел в виду именно в физическом смысле. Более того, у меня чаще wire на выходе. Я не знаю, какой кодстайл принят у профессионалов, но у меня часто выходы wire а внутри уже логика, результат которой <= на регистры, а их assign наружу. Если модуль больше средних размеров, то это мне помогает обозначить порты модуля и дальше их уже не трогать (не менять с wire на reg или обратно, в процессе доработок). То есть получается, что если я что-то и меняю, то только касаемо внутренностей модуля.
 
 Тут я еще один момент заметил — Hypnotriod:- Просто я так себе представляю, что модуль — это интерфейс. И про реализацию его пользователь знать не должен. Какие выходы на какие регистры назначены, и через какие лог. примитивы, и т.п. 
 Я ж, как новичек, тоже столкнулся с этим вопросом. Ведь можно написать чисто комбинационный модуль, без регистров. А потом, к примеру, результат защелкивать в регистр на верхнем уровне. И если совсем не знать, что внутри модуля, то может оказаться, что в модуле все же есть регистр, и запись его выхода в регистр на более высоком уровне может оказаться не нужной и синтезатор сократит лишний регистр. И произойдет то, что я не понимаю, что случилось. Помогло систематическое использование RTL Viewer и чтение книжек.
 
 Поэтому, если модуль большой, и пользователь действительно не должен знать, как он внутри работает, то внутри я ставлю регистры на выходе и после этого никакой комбинаторики. Иначе уже синтезатор начинает ругаться, причем по-делу (забыл ошибку). Исключение у меня составляют небольшие вспомогательные модули. Они входят в крупные модули (с целью сократить код), в схему комбинаторики перед записью в выходные регистры. Но они, как правило, занимают не больше пол экрана, и нет никакой проблемы в них зайти и понять, что там регистров нет.
 
  - Hypnotriod26.02.2016 22:28- Это все хорошо, но rxReadyOUT даст лог. 1 после того, как мы примем стоповый бит, т.е. принятые данные уже давно на своих местах. И не важно, завязан он на комбинаторной логике или на выходе регистра. Вы напишите конкретную строчку, и скажите почему она, по вашему мнению, не будет работать. А то так мы рассыждаем на абстрактные темы. 
 
 
 
 
 
 
 
 - UA3MQJ26.02.2016 21:25- Спасибо за статью. Скоро как раз надо будет делать небольшой приемник RS232 на ПЛИС MAX ii 240. Пригодится. То, что есть параметризация — это хорошо, это правильно. А проблемы, я думаю, быстро исправите.  - Hypnotriod26.02.2016 22:12- Надо было назвать статью UART TTL. Т.к. на стандарт RS232 я и не целился. У меня нет возможности его тестировать, попросту некуда подключать :) Тут скорее пойдет для подключения ПЛИС к тому-же МК.  - UA3MQJ26.02.2016 22:14- В моем случае будет вообще прием MIDI сообщений :)  - Hypnotriod26.02.2016 22:46- Довольно много логики будет :) Там и системные сообщения, и realtime сообщения, и running status с note off при нулевой velocity. Я делал синтезатор под МК, то хорошо наигрался. Другое дело принимать MIDI сообщения по USB, там все просто, за исключением самого USB :) 
 
 
 
 - Shamrel27.02.2016 10:08+1- Господа, товарищи, ребята! Если цель статьи потренировать свои навыки в проектировании на плис, то вопросов нет. +1. Но если вы делаете реальный проект, то зачем изобретать велосипед? Есть замечательный ресурс opencores.org/, там есть испытанные проверенные реализации от самых простых, до навороченных. Мне же импонирует (вставляю в свои проекты) реализация от 
 www.fpga4fun.com - YuriPanchul27.02.2016 11:22+3- Я с вами не согласен. У блока на opencores может быть неподходящий для вас интерфейс, блок может быть плохо оптимизирован, иметь отнюдь не индустриальное качество. Для небольшого блока иногда проще написать свой, чем приспособить и верифицировать чужой. 
 
 - Shamrel27.02.2016 15:16- Тем не менее, это модули, на которые смотрела не одна сотня глаз. Если речь идет о "начинающем" разработчике, то сомневаюсь, что потребность есть в "индустриальном качестве". К тому же, учиться лучше на чужих ошибках.  - Hypnotriod27.02.2016 15:26- Так я и не спорю. Есть решения на github которые уже несколько лет поддерживаются и усовершенствуются. Я не буду отрицать что данная реализация далеко не "идеальна" :) Как-раз моя задача это знакомство с ПЛИС. 
 
 
           
 
holyglory
О ужос.
rxIN счелкать на входе надо, прежде чем отправлять его на вход конечного автомата. А вообще принято triple majority делать.
В делителе считать лучше вниз до нуля. Тогда, когда вы сделаете возможность задавать baud rate регистром, он меньше жрать будет.
Сдивговые регистры тоже красивее смотрятся. Ну и по старт-биту в старшем разряде сдвигового регистра можно определять конец приёма. С передачей счётчик нужен, так уж и быть.
Ну и раз уж статью делайте, то где поддержка parity bit, flow control, приёмный и передающий буфера?:)
odiszapc
Сеньор принципал верилог девелопер детектед.