Мой «Hello World!» на FPGA или очередная версия UART

    Мой Hello World! на FPGA или очередная версия UART

    Наконец-то у меня дошли руки до изучения ПЛИС. А то как-то неправильно получается: драйвера на железо под Linux пишу, микроконтроллеры программирую, схемы читаю (и немного проектирую), надо расти дальше.

    Так как мигать светодиодами мне показалось не интересно, то решил сделать простенькую вещь. А именно написать модули приемника и передатчика для UART, объединить их внутри FPGA (заодно понять как использовать IP Core), ну и протестировать это все на реальном железе.

    Сразу говорю, что сделать универсальное параметризированное ядро задачи не стояло. Это просто тестовый проект, на предмет «пощупать, что такое FPGA и как с ней общаться».

    Итак, начнем с приемника. Алгоритм достаточно хорошо описан, поэтому повторю здесь только основные его моменты.

    • Частоту семплирования сигнала RX выберем в четыре раза больше, чем требуемая скорость передачи по UART.
    • Условием начала приема будем считать переход входного сигнала RX из высокого уровня в низкий в случае, если в данный момент прием еще не ведется.
    • Условием достоверного опознания стартового бита будем считать удержание сигнала RX в состоянии низкого уровня на втором такте частоты семплирования. При этом мы практически попадаем на середину битового импульса, что позволит в дальнейшем производить семплирование импульсов через каждые 4 такта.
    • В случае ошибки в стартовом либо в стоповом битах устанавливаем сигнал ошибки error. На его основе формируем сигнал fastsync, который в дальнейшем будем использовать для быстрой синхронизации приемника.
    • После опознавания стартового бита начинаем последовательный прием битов данных, начиная с самого младшего. Принимаемые данные записываем в регистр со сдвигом вправо на один бит. Условием окончания приема будет являться обнаружение стартового бита в 0-й позиции сдвигового регистра.
    • Быстрая синхронизация приемника заключается в приведении его в исходное состояние после обнаружения ошибки при переходе сигнала RX в высокий уровень (это может быть как передача логической «1», так и передача стоп-бита либо состояние бездействия линии передачи).
    • Условием успешного завершения приема (корректных значений стартового и стопового битов) является сигнал complete. От него (при тактировании сигналом rdclk) формируется импульсный сигнал ready, который обозначает наличие валидных данных на шине rxdata.

    Сразу отмечу, что тактировать сигнал чтения ready от тактового сигнала clk я не хотел (неожиданно, да?), чтобы не завязавать скорость последующей обработки данных на скорость обмена по UART. Аналогичная реализация и в модуле передатчика (см. ниже). А тестовая связка модулей приемника и передатчика сделана на основе IP Core FIFO от Intel, причем с возможностью имитации различных скоростей для потребителя и генератора данных. Единственным ограничением является то, что тактовая частота производителя и потребителя данных должна быть не ниже тактовой частоты clk.

    Модуль приемника (Verilog)
    //
    // Блок приемника UART
    //
    // Данные rxdata валидны, когда ready==1 и error==0.
    // Сигнал ready устанавливается в 1 после завершения приема на один такт rdclk.
    //
    // Реализация:
    // После перехода сигнала rx в низкий уровень проверяем его неизменность в течении
    // 2-х тактов. Если уровень остался низким, значит начат прием стартового бита.
    // В этом случае делаем прием 8 битов данных и одного стоп-бита (всего 9 битов).
    // 2 Такта - это приблизительно середина бита, так что устойчивость должна быть
    // хорошей.
    //
    // Биты передаются начиная от младшего к старшему.
    // Лог. '0' передается низким уровнем сигнала, лог. '1' передается высоким уровнем сигнала
    // idle передается высоким уровнем сигнала (лог. '1')
    // start-бит передается низким уровнем сигнала (лог. '0')
    // stop-бит передается высоким уровнем сигнала (лог. '1')
    module uart_rx(
    	nreset,			// сигнал сброса (асинхронный, активный уровень 0)
    	clk, 				// тактовая частота UART, д.б. в четыре раза больше скорости обмена по UART
    	rx,				// входная линия UART
    	rdclk,			// тактирование чтения результата приема (rxdata, ready)
    	rxdata,			// принятые данные, значение валидно при ready==1
    	ready,			// индикатор валидности данных rxdata (активный уровень 1)
    	error,			// индикатор ошибки приема (активный уровень 1)
    	busy,				// индикатор занятости модуля (идет прием, активный уровень 1)
    	idle);			// индикатор свободной линии приемника (активный уровень 1)
    	
    input wire nreset;		// сигнал сброса (асинхронный, активный уровень 0)
    input wire clk; 			// тактовая частота, д.б. в четыре раза больше скорости обмена по UART
    input wire rx;				// входная линия UART
    input wire rdclk;			// тактирование чтения результата приема
    output wire[7:0] rxdata;
    output wire ready;
    output error;
    output busy;
    output idle;
    	
    	
    // Изменение сигнала завершения приема, тактируемое через rdclk
    reg[2:0] done = 3'b000;
    
    // Выходной сигнал готовности принятых данных, тактируемый rdclk
    //assign ready = (done == 2'b10) ? 1'b1 : 1'b0;
    assign ready = (done[1] && !done[0]) ? 1'b1 : 1'b0;
    
    // Признак наличия ошибки приема
    reg error = 1'b0;
    
    // Сигнал сброса логики приемника для быстрой синхронизации при ошибке
    // Если на текущем такте имеем ранее установленный сигнал error и высокий
    // уровень сигнала rx, возможно это пауза между передаваемыми байтами данных.
    wire fastsync = (error && rx);	
    	
    // Признак свободной линии приемика
    reg idle = 1'b1;	
    	
    // Принятые данные:
    // d[9] - стоповый бит, д.б. == 1
    // d[8:1] - данные
    // d[0] - стартовый бит, д.б. == 0
    reg[9:0] d = 10'b1xxxxxxxx1;
    
    // Статус приема. Завершение приема индицируется значением 2'b10
    wire[1:0] status = { d[9], d[0] };
    
    // Признак завершения приема.
    wire complete = (status == 2'b10) ? 1'b1 : 1'b0;
    
    // Принятый байт данных
    assign rxdata = d[8:1];
    
    // Признак занятости модуля
    reg busy = 0;
    // Счетчик тактовых импульсов до	семплирования линии rx
    reg[1:0] cnt;
    
    always @(posedge clk, negedge nreset)
    begin
    	if(!nreset) begin
    		rxreset();
    	end else begin
    		if(fastsync) begin
    			rxreset();
    		end else begin	
    			if(busy == 1'b1) begin
    				// Идет прием чего-то, проверяем необходимость семплинга rx
    				if(cnt == 2'd0) begin
    					// Записываем принятый бит
    					// в старший разряд данных со сдвигом предыдущего значения вправо
    					// (т.к. передача идет от младшего бита к старшему)
    					d <= { rx, d[9:1] };
    	
    					if(d[1] == 1'b0) begin
    						// На этом шаге стартовый бит попадет в последнюю позицию, прием завершен
    						busy <= 1'b0;
    	
    						// Проверяем корректность стопового бита
    						error <= (rx == 1'b1) ? 1'b0 : 1'b1;
    					end else begin
    						// Мы находимся в процессе приема
    						if(rx && (d == 10'b1111111111)) begin
    							// Слишком маленькая длительность стартового бита
    							busy <= 1'b0;
    							// Индицируем наличие ошибки
    							error <= 1'b1;
    						end else begin
    							// Нормальная процедура приема
    							// Кол-во тактов целого бита - подготовка к приему следующего бита
    							cnt <= 2'd3;
    						end
    					end			
    				end else begin
    					// Уменьшаем кол-во оставшихся до семплинга тактов
    					cnt <= cnt - 2'd1;
    				end
    			end else begin
    				// Модуль пока еще ничего не делает
    				if(!error) begin
    					// Нет сигнала ошибки, можно попытаться начать прием стартового бита
    					if(rx == 1'b0) begin
    						// Линия приемника в низком уровне и до этого приема не было - начинаем работу
    						busy <= 1'b1;
    						// Инициализируем буфер приема данных. Здесь критично записать все 1, т.к. окончание
    						// приема определяется состоянием d[0]==0
    						d <= 10'b1111111111;
    						// Проверять линию rx будем через 1/2 длительности бита
    						// 1-й такт - это текущее сэмплирование
    						// 2-й такт - это следующее сэмплирование (cnt будет 0)
    						cnt <= 2'd0;
    						// Т.к. мы потенциально начали прием, отмечаем линию как занятую
    						idle <= 1'b0;
    					end else begin
    						// Линия приемника свободна
    						idle <= 1'b1;
    					end
    				end
    			end
    		end
    	end
    end
    
    task rxreset;
    	begin
    		// Сброс признака ошибки
    		error <= 1'b0;
    		// Установка сигнала свободной линии (!?)
    		idle <= 1'b1;
    		// Сброс признака занятости модуля
    		busy <= 0;
    		// В принципе можно записать что-нибудь, лишь бы статус не попал в complete
    		d <= 10'b1xxxxxxxx1;
    	end
    endtask
    
    
    always @(negedge rdclk, negedge nreset)
    begin
    	if(!nreset) begin
    		done <= 3'b000;
    	end else begin
    		// По тактам чтения сохраняем состояние сигнала complete.
    		// Логика сигнала формирования сигнала ready формирует один импульс при
    		// изменение сигнала complete с 0 на 1 на один такт rdclk.
    		done <= { complete, done[2:1] };
    	end
    end
    
    endmodule
    


    Так как входной сигнал RX является асинхронным и (возможно) нестабильным, в главном модуле перед модулем приемника был подключен мажоритарный элемент. Элемент также написан на Verilog, но его код здесь приводить смысла нет. Вместо него красивая картинка синтезированного элемента.

    Синтезированная схема мажоритарного элемента
    Мажоритарный элемент

    Блок передатчика еще проще и, надеюсь, в дополнительных комментариях не нуждается.

    Модуль передатчика (Verilog, блокирующие и не блокирующие присваивания внутри always)
    //
    // Блок передатчика UART
    //
    // Сигналы:
    // clk - частота должна быть в 4 раза больше скорости передачи, скважность не важна
    // rdclk - тактирование обмена txdata, write, fetch. Частота д.б. выше clk
    // txdata - данные для передачи, управляются сигналами write/fetch
    // write - источник имеет данные для передачи (1=да)
    // fetch - модуль принял данные для передачи (1=да)
    // tx - линия передачи UART
    // idle - линия передачи свободна (1=да, информационный сигнал)
    //
    // Для FIFO нужно использовать режим dcfifo_component.lpm_showahead = "ON"
    module uart_tx(
    	nreset,			// сигнал сброса (асинхронный, активный уровень 0)
    	clk,				// тактовая частота UART, д.б. в четыре раза больше скорости обмена по UART
    	rdclk,			// тактирование подтверждения приема данных от поставщика
    	txdata,			// шина данных на передачу от поставщика
    	write,			// признак наличия данных на передачу (активный уровень 1)
    	idle,				// индикатор не активного передатчика (активный уровень 1)
    	fetch,			// подтверждение загрузки данных от поставщика, тактируется rdclk
    	tx);				// выходная линия UART
    
    input wire nreset;		// сигнал сброса (асинхронный, активный уровень 0)
    input wire clk;			// тактирование UART
    input wire rdclk;
    input wire[7:0] txdata;
    input wire write;
    output wire idle;
    output fetch;
    output tx;
    	
    // Состояние выходной линии
    reg tx = 1'b1;
    
    reg fetch = 1'b0;
    
    // Делитель частоты на 4
    reg[1:0] div4 = 2'd0;	
    	
    // Состояние машины:
    reg[3:0] s = 4'd10;	
    
    // Передатчик полностью свободен
    assign idle = (s == 4'd10);
    
    // Сдвиговый регистр данных
    reg[7:0] d;	
    
    // Признак передачи стартового бита в данном цикле
    reg sendstart;
    	
    // Признак возможности запроса новых данных на передачу
    reg canfetch;	
    
    // Признак завершения ввода новых данных, тактируется clk
    reg gotdata = 1'b0;
    
    // Для синхронизации clock domains
    reg[2:0] sync = 3'b000;
    
    // Запомненный по rdclk сигнал write
    reg wr = 1'b0;
    
    // При появлении запроса getdata==1 при наличии данных у внешнего источника
    // производится их запоминание в регистре nextdata и устанавливается признак
    // готовности gotdata==1. Кроме того, для внешнего источника данных формируется
    // сигнал подтверждени.
    // Сигнал gotdata снимается при снятии сигнала getdata.
    always @(posedge rdclk, negedge nreset)
    begin
    	if(!nreset) begin
    		wr <= 1'b0;
    		sync <= 3'b000;
    		// Сбрасываем сигнал подтверждения ввода данных
    		fetch <= 1'b0;
    	end else begin
    		// Запоминаем сигнал write
    		wr <= write;
    	
    		// Проверяем появление запроса новых данных для передачи
    		sync <= { gotdata, sync[2:1] };
    
    		// По положительному переходу сигнала gotdata устанавливаем
    		// сигнал подтверждения для источника данных. В остальных
    		// случаях сбрасываем его.
    		fetch <= (sync[1] && !sync[0]) ? 1'b1 : 1'b0;
    	end
    end
    	
    always @(posedge clk, negedge nreset)
    begin
    	if(!nreset) begin
    		// Установка передатчика в исходное состояние
    		div4 <= 2'd0;
    		s <= 4'd10;
    		gotdata <= 1'b0;
    	end else begin	
    		// Пока нет признака передачи стартового бита в этом цикле
    		sendstart = 1'b0;
    	
    		// Начальная установка признака запроса данных на передачу
    		canfetch = wr;
    		
    		if(div4 == 2'd0) begin
    			case(s)
    				4'd0:
    					begin
    						// Передача стартового бита будет инициирована ниже
    						sendstart = 1'b1;
    						
    						// Передатчик занят, нельзя запрашивать новые данные
    						canfetch = 1'b0;
    					end
    				4'd9:
    					begin
    						// Передача стопового бита
    						tx <= 1'b1;
    					end
    				4'd10:
    					begin
    						// Состояние idle, ничего не делаем
    					end
    				default:
    					begin
    						// Идет передача битов данных, текущий младший является выходом
    						tx <= d[0];
    						// Выполняем сдвиг данных вправо
    						d <= { 1'b0, d[7:1] };
    						// Передатчик занят, нельзя запрашивать новые данные
    						canfetch = 1'b0;
    					end
    			endcase		
    		end else begin
    			// Выдерживаем текущее состояние
    			div4 <= div4 - 2'd1;
    			
    			if(s < 4'd9) begin
    				// При выдерживании до состояния 9 прием новых данных невозможен!
    				canfetch = 1'b0;
    			end
    		end
    		
    		if(canfetch) begin
    			// Входные данные готовы, передаем их на обработку
    			d <= txdata;
    			
    			// Подтверждение взятия данных на обработку
    			gotdata <= 1'b1;
    	
    			if(idle /*s == 4'd10*/) begin
    				// Состояние idle - немедленно начинаем передачу стартового бита
    				sendstart = 1'b1;
    			end else begin
    				// На следующем шаге переходим к передаче стартового бита
    				s <= 4'd0;
    			end
    		end
    		
    		if(gotdata) begin
    			// Данные были приняты ранее, снимаем сигнал подтверждения
    			gotdata <= 1'b0;
    		end
    		
    		if(sendstart) begin
    			// На данном шаге начинаем передачу стартового бита
    			tx <= 1'b0;
    				
    			// Переходим к следующему состоянию
    			s <= 4'd1;
    	
    			// Длительность стартового бита
    			div4 <= 2'd3;
    		end else begin
    			if(div4 == 2'd0) begin
    				if(s < 4'd10) begin
    					// Последовательное изменение состояния на следующее
    					s <= s + 4'd1;
    					
    					// Время выдерживания состояния
    					div4 <= 2'd3;
    				end
    			end
    		end
    	end
    end
    
    endmodule
    


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

    Модуль передатчика (Verilog, идеологически правильный)
    //
    // Блок передатчика UART
    //
    // Сигналы:
    // clk - частота должна быть в 4 раза больше скорости передачи, скважность не важна
    // rdclk - тактирование обмена txdata, write, fetch. Частота д.б. выше clk
    // txdata - данные для передачи, управляются сигналами write/fetch
    // write - источник имеет данные для передачи (1=да)
    // fetch - модуль принял данные для передачи (1=да)
    // tx - линия передачи UART
    // idle - линия передачи свободна (1=да, информационный сигнал)
    //
    // Для FIFO нужно использовать режим dcfifo_component.lpm_showahead = "ON"
    module uart_tx(
    	nreset,			// сигнал сброса (асинхронный, активный уровень 0)
    	clk,				// тактовая частота UART, д.б. в четыре раза больше скорости обмена по UART
    	rdclk,			// тактирование подтверждения приема данных от поставщика
    	txdata,			// шина данных на передачу от поставщика
    	write,			// признак наличия данных на передачу (активный уровень 1)
    	idle,				// индикатор не активного передатчика (активный уровень 1)
    	fetch,			// подтверждение загрузки данных от поставщика, тактируется rdclk
    	tx);				// выходная линия UART
    
    input wire nreset;		// сигнал сброса (асинхронный, активный уровень 0)
    input wire clk;			// тактирование UART
    input wire rdclk;
    input wire[7:0] txdata;
    input wire write;
    output wire idle;
    output fetch;
    output tx;
    	
    // Состояние выходной линии
    reg tx = 1'b1;
    
    reg fetch = 1'b0;
    
    // Делитель частоты на 4
    reg[1:0] div4 = 2'd0;	
    	
    // Состояние машины:
    reg[3:0] s = 4'd10;	
    
    // Передатчик полностью свободен
    assign idle = (s == 4'd10);
    
    // Сдвиговый регистр данных
    reg[7:0] d;	
    
    // Признак передачи стартового бита в данном цикле
    reg sendstart;
    	
    // Признак возможности запроса новых данных на передачу
    reg canfetch;	
    
    // Признак завершения ввода новых данных, тактируется clk
    reg gotdata = 1'b0;
    
    // Для синхронизации clock domains
    reg[2:0] sync = 3'b000;
    
    // Запомненный по rdclk сигнал write
    reg wr = 1'b0;
    
    // При появлении запроса getdata==1 при наличии данных у внешнего источника
    // производится их запоминание в регистре nextdata и устанавливается признак
    // готовности gotdata==1. Кроме того, для внешнего источника данных формируется
    // сигнал подтверждени.
    // Сигнал gotdata снимается при снятии сигнала getdata.
    always @(posedge rdclk, negedge nreset)
    begin
    	if(!nreset) begin
    		wr <= 1'b0;
    		sync <= 3'b000;
    		// Сбрасываем сигнал подтверждения ввода данных
    		fetch <= 1'b0;
    	end else begin
    		// Запоминаем сигнал write
    		wr <= write;
    	
    		// Проверяем появление запроса новых данных для передачи
    		sync <= { gotdata, sync[2:1] };
    
    		// По положительному переходу сигнала gotdata устанавливаем
    		// сигнал подтверждения для источника данных. В остальных
    		// случаях сбрасываем его.
    		fetch <= (sync[1] && !sync[0]) ? 1'b1 : 1'b0;
    	end
    end
    	
    // Для улучшения стиля(?) выносим комбинаторную логику в отдельный always
    // Здесь у нас вычисляются сигналы sendstart и canfetch
    always @(*)
    begin
    	// Пока нет признака передачи стартового бита в этом цикле
    	sendstart = 1'b0;
    	
    	if(nreset) begin
    		// Начальная установка признака запроса данных на передачу
    		canfetch = wr;
    
    		if(div4 == 2'd0) begin
    			case(s)
    				4'd0:
    					begin
    						// Передача стартового бита будет инициирована ниже
    						sendstart = 1'b1;
    						
    						// Передатчик занят, нельзя запрашивать новые данные
    						canfetch = 1'b0;
    					end
    				4'd9:
    					begin
    						// Передача стопового бита
    					end
    				4'd10:
    					begin
    						// Состояние idle, ничего не делаем
    					end
    				default:
    					begin
    						// Идет передача битов данных
    						// Передатчик занят, нельзя запрашивать новые данные
    						canfetch = 1'b0;
    					end
    			endcase		
    		end else begin
    			if(s < 4'd9) begin
    				// При выдерживании до состояния 9 прием новых данных невозможен!
    				canfetch = 1'b0;
    			end
    		end
    		
    		if(canfetch && idle) begin
    			// Состояние idle - немедленно начинаем передачу стартового бита
    			sendstart = 1'b1;
    		end
    	end else begin
    		// В случае активного reset мы вообще работать не будем
    		canfetch = 1'b0;
    	end
    end	
    	
    always @(posedge clk, negedge nreset)
    begin
    	if(!nreset) begin
    		// Установка передатчика в исходное состояние
    		div4 <= 2'd0;
    		s <= 4'd10;
    		gotdata <= 1'b0;
    	end else begin
    		if(div4 == 2'd0) begin
    			case(s)
    				4'd0:
    					begin
    						// В зависимости от sendstart может быть инициирована передача стартового бита
    					end
    				4'd9:
    					begin
    						// Передача стопового бита
    						tx <= 1'b1;
    					end
    				4'd10:
    					begin
    						// Состояние idle, ничего не делаем
    					end
    				default:
    					begin
    						// Идет передача битов данных, текущий младший является выходом
    						tx <= d[0];
    						// Выполняем сдвиг данных вправо
    						d <= { 1'b0, d[7:1] };
    					end
    			endcase		
    		end else begin
    			// Выдерживаем текущее состояние
    			div4 <= div4 - 2'd1;
    		end
    		
    		if(canfetch) begin
    			// Входные данные готовы, передаем их на обработку
    			d <= txdata;
    			
    			// Подтверждение взятия данных на обработку
    			gotdata <= 1'b1;
    	
    			if(!idle /*s == 4'd10*/) begin
    				// На следующем шаге переходим к передаче стартового бита
    				s <= 4'd0;
    			end
    		end else begin
    			// Если данные были приняты ранее, снимаем сигнал подтверждения
    			gotdata <= 1'b0;
    		end
    		
    		if(sendstart) begin
    			// На данном шаге начинаем передачу стартового бита
    			tx <= 1'b0;
    				
    			// Переходим к следующему состоянию
    			s <= 4'd1;
    	
    			// Длительность стартового бита
    			div4 <= 2'd3;
    		end else begin
    			if((div4 == 2'd0) && (s < 4'd10)) begin
    				// Последовательное изменение состояния на следующее
    				s <= s + 4'd1;
    					
    				// Время выдерживания состояния
    				div4 <= 2'd3;
    			end
    		end
    	end
    end
    
    endmodule
    



    Для тестирования приемника и передатчика на коленке был написан главный модуль. На него прошу не ругаться, ошибки проектирования (внешний асинхронный сигнал nreset, отсутствие сброса FIFO и т.п.) я и сам знаю. Но для целей проверки функциональности они не существенны.

    Моя демонстрационная плата тактируется от источника сигнала 50Mhz. Поэтому в главном модуле я использовал PLL, на выходе C0 которого сформировал частоту для работы c UART (1.8432Mhz, реально 1.843198Mhz) и, по-приколу, сформировал частоту 300Mhz (выход c1 PLL) для тактирования имитации схемы обработки информации.

    Главный модуль (Verilog)
    //
    // Т.к. прием и передача данных через UART синхронизируется тактовой частотой UART,
    // а обработка данных синхронизируется тактовой частотой FPGA, то нужно использовать
    // для каждого модуля FIFO IP CORE типа DCFIFO.
    //
    //NB!
    // Не забываем в SDC-файле прописывать соответствующие внутренние частоты!
    // Иначе получаем зверские эффекты (типа часть внутри блока if выполнилась,
    // а часть нет).
    module uart(
    	input wire clk50mhz,	// тактовая частота 50Mhz
    	input wire nreset,	// инверсный сигнал сброса
    	input wire rx,			// входной сигнал UART
    	output wire tx,		// выходной сигнал UART
    	output wire overflow
    	);
    
    	
    // Тактовая частота 1.8432Mhz (реально 1.843198Mhz)	
    wire clk_1843200;
    // Тактовая частота 1.2288Mhz (реально 1.228799Mhz)
    //wire clk_1228800;
    
    // Внутренняя тактовая частота 300Mhz, сформированная PLL
    wire clk300mhz;
    
    // Синтезируем тактовые частоты для UART
    uart_pll pll50mhz(.inclk0(clk50mhz), 
    						.c0(clk_1843200) /*, .c1(clk_1228800)*/,
    						.c1(clk300mhz));
    	
    // Скорость UART 38400
    // Делитель (1843200/38400)/4 = 12 ('b1100). 
    
    // Скорость UART 57600
    // Делитель (1843200/57600)/4 = 8
    
    // Скорость UART 115200
    // Делитель (1843200/115200)/4 = 4
    
    // Скорость UART 230400
    // Делитель (1843200/230400)/4 = 2
    
    // Скорость UART 460800
    // Делитель (1843200/460800)/4 = 1 (т.е. делитель вообще не нужен!)
    
    // Тактовая частота для UART
    wire uart_baud4;
    // Подключаем его в схему
    // Значение делителя .data должно быть на 1 меньше требуемого делителя. Тогда
    // период сигнала uart_baud4 будет равен значению .clock/делитель
    // Длительность высокого уровня сигнала uart_baud4 будет равна одному такту .clock
    uart_osc uart_osc_1(.clock(clk_1843200), 
    						  .data(5'd2/*5'd4*//*5'd12*/-5'd1), 
    						  .sload(uart_baud4), 
    						  .cout(uart_baud4));
    //wire uart_baud4 = clk_1843200;
    	
    // Входной сигнал после мажоритарного фильтра	
    wire rxf;	
    // Подключаем мажоритарный фильтр на входной сигнал
    mfilter mfilter_rx(.clk(clk50mhz /*clk_1843200*/), 
    						 .in(rx), 
    						 .out(rxf)); 
    
    //wire rxf = rx;
    
    // Подключаем модуль приемника
    wire[7:0] rxdata;
    wire rxready;
    wire error;
    uart_rx uart_rx_1(.nreset(nreset), 
    						.clk(uart_baud4), 
    						.rx(rxf), 
    						.rdclk(clk300mhz /*clk50mhz*/ /*clk_1843200*/), 
    						.rxdata(rxdata), 
    						.ready(rxready), 
    						.error(error));
    
    wire[7:0] txdata;
    // Сигнал, индицирующий отсутствие данных, ожидающих передачи
    wire txnone;
    // Сигнал, индицирующий готовность передатчика принять новые данные
    wire fetch;
    
    wire full;
    	
    // Буферирование принятых данных
    // Запись тактируется сигналом uart_baud4
    // Чтение тактируется сигналом clk50mhz
    uart_fifo_rx uart_fifo_rx_1(.data(rxdata), 
    									 .rdclk(clk300mhz /*clk50mhz*/ /*clk_1843200*/ /*uart_baud4*/), 
    									 .rdreq(fetch), 
    									 .wrclk(clk300mhz /*clk50mhz*/ /*clk_1843200*/ /*uart_baud4*/), 
    									 .wrreq(rxready), 
    									 .rdempty(txnone), 
    									 .q(txdata), 
    									 .wrfull(full));
    
    assign overflow = ~error;
    	
    	
    uart_tx uart_tx_1(.nreset(nreset), 
    						.clk(uart_baud4), 
    						.rdclk(clk300mhz /*clk50mhz*/ /*clk_1843200*/), 
    						.txdata(txdata), 
    						.write(~txnone), 
    						.fetch(fetch), 
    						.tx(tx));
    
    	
    endmodule
    


    Для тестирования использовался генератор трафика testcom от Zelax. К сожалению, имеющийся у меня адаптер USB/UART отказался работать со скоростями выше 230400BPS, так что все тестирование проводилось на этой скорости.

    Результат тестирования с фильтрацией входного сигнала RX при помощи мажоритарного элемента
    Тестирование с предварительной фильтрацией сигнала RX
    Состояние сигналов, снятые при помощи Signal Tap
    Сигналы приемника UART при отсутствии ошибок

    А вот здесь мажоритарный элемент со входа был убран.
    А что, как мне еще было имитировать произвольные ошибки при проверке схемы быстрой синхронизации?
    Тестирование без предварительной фильтрации сигнала RX
    Состояние сигналов, снятые при помощи Signal Tap
    Сигналы при быстрой синхронизации приемника после обнаружения ошибки

    Примечание


    Извините, курсов по Quartus я не проходил и вопросы задавать было некому. На что сам наткнулся и о чем предупреждаю других начинающих ПЛИСоводов: обязательно создавайте в проекте SDC-файл и описывайте в нем тактовые частоты. Да, проект собирается и без него, правда возможно появление предупреждений, если синтезатор не смог определить временные характеристики тактирования. Я их сначала игнорировал, пока не убил полдня на определение проблемы, почему у меня в модуле приемника при выполнении кода

    if(rx == 1'b0) begin
      busy <= 1'b1;
      d <= 10'b1111111111;
      cnt <= 2'd0;
      idle <= 1'b0;
    end else begin
    

    сигналы busy и idle устанавливались правильно, а вот содержимое регистра d иногда не изменялось.

    Приложение: SDC-файл к проекту
    set_time_format -unit ns -decimal_places 3
    # Тактовая частота 50Mhz, (50/50 duty cycle)
    create_clock -name {clk50mhz} -period 20.000 -waveform { 0.000 10.000 } 
    
    ##############################################################################  Now that we have created the custom clocks which will be base clocks,#  derive_pll_clock is used to calculate all remaining clocks for PLLs
    derive_pll_clocks -create_base_clocks
    derive_clock_uncertainty
    
    # Сигналы от PLL софтина умеет считать сама?
    #		altpll_component.clk0_divide_by = 15625,
    #		altpll_component.clk0_duty_cycle = 50,
    #		altpll_component.clk0_multiply_by = 576,
    #		altpll_component.clk0_phase_shift = "0",
    #create_generated_clock -name clk_1843200 -source [get_ports {clk50mhz}] -divide_by 15625 -multiply_by 576 -duty_cycle 50 -phase 0 -offset 0
    
    # Для baudrate=38400
    # Сигнал активен в течении 1/4 цикла, т.е. duty=(1/4)*100=25%
    #create_generated_clock -name uart_baud4 -source [get_nets {pll50mhz|altpll_component|auto_generated|wire_pll1_clk[0]}] -divide_by 12 -duty_cycle 25 [get_nets {uart_osc_1|LPM_COUNTER_component|auto_generated|counter_reg_bit[0]}]
    
    # Для baudrate=230400
    # Сигнал активен в течении 1/4 цикла, т.е. duty=(1/4)*100=50%
    create_generated_clock -name uart_baud4 -source [get_nets {pll50mhz|altpll_component|auto_generated|wire_pll1_clk[0]}] -divide_by 2 -duty_cycle 25 [get_nets {uart_osc_1|LPM_COUNTER_component|auto_generated|counter_reg_bit[0]}]
    
    # Для baudrate=460800
    # Делитель равен 1, используется непосредственно выход PLL, поэтому описание дополнительной частоты не требуется.
    


    Большое человеческое спасибо всем, кто написал комментарии к статье! Из них я почерпнул очень много полезной, хотя иногда и несколько противоречивой информации. По-моему их ценность намного больше, чем описанная выше реализация алгоритма. И, несомненно, они будут полезны тем, кто тоже рискнет залезть в мир ПЛИСок.

    Список внешних ссылок

    1. Универсальный асинхронный приёмопередатчик (ВикипедиЯ)
    2. Мажоритарный элемент (ВикипедиЯ)
    Поделиться публикацией

    Похожие публикации

    Комментарии 36
      0
      Отличная статья — без воды и с достаточным количеством информации! Да и вообще плохих статей о fpga на хабре не пишут)) Я так понимаю вы тестировали сразу в железе не прибегая к симуляции?
        0
        Можете пояснить, как работает ваш Мажоритарный элемент? Т.е. от каких ошибок от избавляет.
          0
          Отвечу за автора. Мажоритарный элемент это вот что. Берутся 3, 5, 7 (короче говоря нечётное число) одноразрядных регистров и собираются в сдвиговый регистр, по которому гонится сигнал с входной линии. Если большинство разрядов равны 1, выход элемента равен 1. Если 0 — то 0. Такая штуковина спасает от «иголок» — коротких помех на входной линии. Представьте себе, что на линию подана 1. Но в силу каких-то причин (например наводок), на короткое время по ней проскальзывает 0. Этот 0 попадает в наш мажоритарный элемент. Но поскольку большинство его регистров установлено в 1, на выходе так 1 и останется. Т.е. мажоритарный элемент подавит эту помеху. Если например Вы работаете с длинными соплями, а источник сигнала не слишком мощный, такую защиту ставить необходимо. Сам это делал не далее как этой весной, работая с микроконтроллером STM8.
            0
            По описанию, это не иначе как медианный фильтр.
              0
              Да, фактически так. Я в своё время изобрел это сам. И таким вопросом не заморачивался. Давит помеху и ладно. Как Конфуций с кошкой :)))
          +2
          Блокирующие присваивания внутри тактируемого блока? Это грубая ошибка.

          Я про вот этот кусок, например:
          Заголовок спойлера
          always @(posedge clk, negedge nreset)
          begin
          	if(!nreset) begin
          		// Установка передатчика в исходное состояние
          		div4 <= 2'd0;
          		s <= 4'd10;
          		gotdata <= 1'b0;
          	end else begin	
          		// Пока нет признака передачи стартового бита в этом цикле
          		sendstart = 1'b0;
          	
          		// Начальная установка признака запроса данных на передачу
          		canfetch = wr;
          		
          		if(div4 == 2'd0) begin
          			case(s)
          				4'd0:
          					begin
          						// Передача стартового бита будет инициирована ниже
          						sendstart = 1'b1;  // ОШИБКА
          						
          						// Передатчик занят, нельзя запрашивать новые данные
          						canfetch = 1'b0;  // ОШИБКА
          



          Чтобы понять что не так, посмотрите доклады Каммингса (один из разработчиков стандарта SystemVerilog, а именно так называется язык с 2009 года).
          Или вот этот замечательный материал из университета Berkeley (сразу предупреждаю, их материал про конечные автоматы устарел — их следует писать в одном always блоке, а не в двух или трёх)
            0
            «Или вот этот замечательный материал из университета Berkeley (сразу предупреждаю, их материал про конечные автоматы устарел — их следует писать в одном always блоке, а не в двух или трёх)»

            Если не затруднит, можно ссылку на материал, подтвеждающий эти слова?
            Спасибо!
              0
              Кажется, я был неправ. Вычитал эту рекомендацию на форуме Xilinx (ссылка на рабочем компьютере, сейчас найти не смог), где утверждалось, что синтезатор Xilinx может не распознавать FSM как FSM если он разбит на несколько always-блоков. А сейчас в гайде Altera увидел вот такие слова:
              To ensure proper recognition and inference of state machines and to improve the quality of results, Altera recommends that you observe the following guidelines, which apply to both Verilog HDL and VHDL:
              • Assign default values to outputs derived from the state machine so that synthesis does not generate unwanted latches.
              • Separate the state machine logic from all arithmetic functions and data paths, including assigning output value

              А дальше идут примеры кода с двумя и тремя always-блоками.

              Что интересно: в гайде от Xilinx единственный пример FSM с одним always-блоком без объяснений почему.
              0
              Ни разу не ошибка, т.к. видно, что эти 2 регистра используются как локальные переменные внутри этого always — сначала присваиваются блокирующим образом, потом используются чуть ниже по коду, снаружи не используются и инфу между тактами не хранят. Для понятности их следовало, конечно же, объявить внутри begin/end, но и так сойдёт.
                +1
                «Локальные переменные», «используются ниже по коду» — вы вообще хоть строчку Verilog для реального устройства в жизни написали или думаете, что это такой язык программирования? Это не язык программирования, это язык описания аппаратуры.

                Я посмотрел код передатчика дальше. Всё ещё хуже — чтение сигнала, который присваивается в том же always блоке с помощью = (блокирующего присваивания) это состояние гонки, которое приводит к неопределённому поведению. Это непонимание фундаментальнейших основ Verilog и того как он синтезируется в RTL. Если это вообще работает, то автору крупно повезло.

                Нет никакого «ниже по коду», есть триггеры, которые защёлкиваются по фронту частоты и всё. И если сигнал clk идёт до триггера, который читает sendstart дольше, чем до того триггера, который sendstart выставляет, то условие if (sendstart) сработает на том же такте. А такое может быть, потому что синтезатор не обязан правильно разводить неправильный код.

                А вам рекомендую взять книжку Харрис «Цифровая схемотехника и архитектура компьютера» и прочитать что такое защёлкивание по фронту, а также прочесть те документы, ссылки на которые я привёл выше.
                  0
                  Во-первых, спасибо за полезные ссылки, код однозначно буду переделывать. Но появился вопрос:
                  Всё ещё хуже — чтение сигнала, который присваивается в том же always блоке с помощью = (блокирующего присваивания) это состояние гонки
                  . У меня sendstart сначала вычисляется и только потом читается. Последовательностей, которые Вы привели ниже в примерах (сигнал сначала читаем, а потом его же блокирующе модифицируем), у меня нет (то, что так делать нельзя, было интуитивно понятно). Просто во многих примерах в инете я видел применение блокирующих и неблокирующих присваиваний внутри одного always-блока, причем авторы утверждали, что синтезатор с этим успешно справляется. Да, в моей реализации все вычисляемые при помощи блокирующих присваиваний сигналы вполне можно вынести за always-блоки, просто формулы их вычислений повторят нынешние условия if/else и станут более громоздкими и сложными для понимания (человеком). Да, я читал статью от dsmv2014 и думал, что если мне понадобится симуляция, то я смогу воспользоваться этим хаком. Так все-таки вопрос к Вам, как профессионалу: синтезатору в моем случае все равно, где расположены блокирующие присваивания? Не симулятору, а именно синтезатору? А если не все равно, то почему именно? Можно разжевать в комментарии, можно отправить к источнику. В любом случае я буду весьма благодарен за совет.
                    +2
                    Во-первых я не знаю каким синтезатором вы пользуетесь. Есть Vivado, есть Quartis, есть ISE, есть Sinplify и много других. Полагаться на особенности реализации, которые прощают нарушения, лучше не стоит, лучше соблюдать стандарт Verilog / SystemVerilog. А ещё лучше читать руководства (их делает каждый серьёзный производитель синтезаторов) по кодингу — где есть список поддерживаемых конструкций HDL и рекомендации по стилю и различным паттернам.
                    Например: Vivado — см. гл. 4 Quartus — см. гл. 11,12

                    Ситуация с Verilog в рунете катастрофическая. В университетах (я имею в виду даже вузы типа МАИ) зачастую учат люди, которые ПЛИС никогда в руках не держали и рассказывают какую-то кашу из синтезируемого и несинтезируемого подмножества языков. Даже в статьях на Хабре много ошибок бывает.

                    Теперь к коду:
                    У меня sendstart сначала вычисляется и только потом читается.

                    Что такое «потом»? Код на Verilog не императивный, он декларативный. Его обманчивая похожесть на C/Java — его самый большой недостаток. Если внутри always-блока вы напишете несколько if на одном уровне вложенности, они все будут проверяться ПАРАЛЛЕЛЬНО и ОДНОВРЕМЕННО (с поправкой на время распространения фронта тактирующего сигнала). Поэтому установка canfetch внутри этого условия
                    if(div4 == 2'd0) begin
                    и проверка условия
                    if (canfetch)
                    происходят параллельно. Что самое неприятное: нельзя сказать, успеет он в том же такте проверить флаг canfetch или нет. Как разведётся.

                    У триггера есть внутреннее состояние и выход. Неблокирующее присваивание <= делает так, что внутреннее состояние запоминается СЕЙЧАС (из правой части присваивания), а на выход оно подаётся СО СЛЕДУЮЩИМ ФРОНТОМ тактирующего сигнала. Синтезаторы понимают какой частотой тактируется какой триггер и следят за тем, чтобы время распространения сигнала по всей схеме не превышало такта частоты, иначе будет большая красная ошибка на этапе имплементации с сообщением о том, что timings failed.

                    Время в тактируемом коде на Verilog двигается квантами. Это как конвеер с шаговым двигателем. Пришёл такт частоты — выполняются все нужные действия (синтезатор гарантирует корректность в случае тактируемых блоков и неблокирующих присваиваний). Затем всё замирает и вплоть до следующего такта НИЧЕГО НЕ МЕНЯЕТСЯ — триггеры держать свои сигналы на выходе (их в это время можно спокойно читать) и всё.

                    Все строчки, все инструкции внутри одного always-блока выполняются ПАРАЛЛЕЛЬНО и ПОТЕНЦИАЛЬНО ОДНОВРЕМЕННО. Поэтому писать вот так
                    
                    always @(posedge clk) begin
                        a = b;
                        b = c;
                    end
                    

                    значит создавать неопределённое поведение в случае тактируемого блока (никто не сможет сказать что будет в а к следующему такту), а такая конструкция
                    
                    always @(*) begin
                        a = a + 1;
                    end
                    

                    создаст то, что называется «комбинаторной петлёй», т.е. к схеме, которая будет работать с максимальной частотой в бесконечном цикле.

                    И вообще — тайминги в схемах могут меняться от температуры и напряжения питания, поэтому делать комбинаторные схемы следует в крайнем случае — когда по-другому просто нельзя. Иногда придётся прописывать много хитрых граничных условий (constraints), где указывать требования на времена распространения сигнала и пр. и пр.
                      0
                      Вот это доступно и полезно. Меня же никто этому не учил, спецов по ПЛИСам рядом нет. В принципиалке взаимосвязи видны, в новом для меня языке нет. В документации, естественно, тонкости упустил. Некоторые данные из интернета оказались в корне неправильные. Проект буду переделывать, это однозначно. В начало добавил предупреждение, что так делать нельзя. Статью оставил именно из-за комментариев. Ибо когда начинал, у меня таких подсказок под рукой не было.
                        0
                        Что такое «потом»? Код на Verilog не императивный, он декларативный. Его обманчивая похожесть на C/Java — его самый большой недостаток. Если внутри always-блока вы напишете несколько if на одном уровне вложенности, они все будут проверяться ПАРАЛЛЕЛЬНО и ОДНОВРЕМЕННО (с поправкой на время распространения фронта тактирующего сигнала).

                        Вообще то говоря это не совсем так. Verilog, так же как и VHDL содержат как параллельные так и последовательные конструкции. В целом описывается параллельная конструкция, но иногда для удобства описания применяются последовательные конструкции. VHDL имеет для этого более строгие понятия signal и variable, Verilog имеет блокирующие и неблокирующие присваивания.
                        В примере canfetch используется только внутри always блока причём только для вычисления значений по фронту clk. Это вполне нормальное применение. Синтезатор для этого примера обязан сделать большую комбинационную схему. Недостатком является как раз размер этой комбинационной схемы, но не способ описания.
                          0
                          И как эта большая комбинационная схема будет работать? Там параллельно выставляется значение canfetch и читается. Как я пойму что срабатывает в каком порядке? Считывает ли он выставленное значение canfetch в том же такте или на следующем?
                            +1
                            Синтезатор всё сделает в соответствии со стандартом языка, значение canfetch будет использоваться в том же самом такте. canfetch надо рассматривать как временную переменную и не использовать вне этого блока. Автор так и делает. Всё корректно. В VHDL в таком описании используется variable, её в принципе нельзя использовать за пределами process (который является аналогом always блока). Это снижает вероятность ошибки.

                            Будет ли работать эта большая комбинационная схема сообщит трассировщик, если частота небольшая, например 1 МГц, то вполне может заработать.
                              0
                              Вы согласны с тем, что такого кода следует избегать?
                                +1
                                Нет, не согласен. Просто надо чётко представлять во что выливается применение той или иной конструкции.
                          +1
                          Если внутри always-блока вы напишете несколько if на одном уровне вложенности, они все будут проверяться ПАРАЛЛЕЛЬНО и ОДНОВРЕМЕННО (с поправкой на время распространения фронта тактирующего сигнала).

                          Вы лжёте и вводите в заблуждение начинающих. Всё, что написано внутри одного begin/end, выполняется последовательно. То, что присваивается при помощи <=, откладывает присвоение в указанный reg на дельта-время. То, что при помощи = — присваивается сразу же. Такова семантика верилога, и когда вы говорите что это не так, не забывая перейти на личности — демонстрирует именно ваши познания. Пруф: staff.ustc.edu.cn/~songch/download/IEEE.1364-2005.pdf, пункт 9.8.

                          Согласно верилогу, это должно выполняться последовательно, следовательно и синтезаторы, понимая это, делают соответствующую логику, и никаких 'гонок' не происходит. В данном случае, sendstart становится одним из комбинационных сигналов, подающихся через комбинационную логику на вход реальных триггеров. И именно использование блокирующего присваивания reg'а внутри begin/end как 'локальной переменной' — вполне себе нормальная практика, иногда позволяющая сильно сократить объём кода и увеличить его человекопонятность. Естественно, те reg'и, которые синтезируются в реальные триггеры, не стоит присваивать при помощи =, так как как минимум сразу можно напороться при симуляции.
                            0
                            always @(posedge clk) begin
                                a = b;
                                b = c;
                            end
                            

                            поведение в данном случае полностью определённое и будет просинтезировано аналогично
                            always @(posedge clk) begin
                                a <= b;
                                b <= c;
                            end
                            

                            На выходе вы получите сдвиговый регистр a < — b < — c.

                            Если же строчки присвоения поменять местами, то поведение данных блоков будет отличаться — в случае с неблокирующими присваиваниями по-прежнему будет трёхбитовый сдвиговый регистр, а вот в случае с блокирующими — двухбитовый a < — c, при этом сигнал b скорее всего будет исключен в процессе оптимизации синтеза.

                            Но никаких неопределённостей в данных конструкциях нет, просто нужно понимать как именно они «отображаются» в реальную схему.
                              0
                              А вот в том, что идёт в реальную схему, никогда нельзя присваивать внешние (относительно текущего begin/end) регистры как = [в always @(posedge clk) по крайней мере]. Буквально вчера наткнулся: в тестбенче кто-то давно написал = вместо <=, в моём RTL это выглядело так, будто конкретный регистр присваивается значением другого из будущего (того, что сразу после posedge clk). Суть проста — когда есть множество posedge clk, то симулятор их выполняет вообще говоря в неопределённом порядке. И вот присваивание = из тестбенчевского кода выполнилось раньше моего <=. В результате мой код увидел значение 'из будущего'.
                                0
                                Честно говоря, почти ничего не понял из написанного. Блокирующее присваивание выполняется мгновенно, неблокирующее выполняется после дельта-задержки. Если все always-блоки выполняются в пределах одной дельта-задержки, то действительно возможно появление результата блокирующего присваивания ранее, чем это требовалось. Поэтому выше уже упоминалось, что с этой точки зрения разделение в VHDL на сигналы и переменные удобнее и адекватнее, чем "=" и "<=" в Verilog. Но наличие возможности выстрелить себе в ногу не является достаточным для «это нельзя делать, это всё неправильно, никогда так не пишите».
                          0
                          Вообще использование блокирующих присваиваний внутри always вполне себе допустимо, и даже допустимо чтение сигнала в том же блоке «ниже по коду», в котором он присваивается. Просто это выльется в более длинную цепочку логики, подобные операции будут выполнены в пределах одного такта. Пока это писал, увидел, что ниже вам то же самое уже написали ранее.
                      +2
                      В Verilog есть комбинаторные блоки и тактируемые.
                      Круглые скобки после always @ — это список чувствительности (при изменении сигналов, описанных там, сработает блок). Если там стоит posedge или negedge, то блок будет тактируемым (фронтом или спадом соответственно; если и то, и другое, то DDR). Если просто имена сигналов или *, то комбинаторным (и будет срабатывать при любом изменении перечисленных сигналов). Reg может быть как в комбинаторном, так и в тактируемом блоке, а сам тип логики он не определяет. С типом wire можно использовать только assign, но не = или <=. Вообще reg и wire это дурные путающие абстракции, поэтому в SystemVerilog ввели универсальный тип logic.
                      Тактируемые:
                      always @ (posedge clk) begin
                          a <= b;
                      end
                      

                      Внутри этих блоков можно использовать ТОЛЬКО <= без всяких но и если.

                      Комбинаторную логику (которая срабатывает «мгновенно», а на самом деле со скоростью распространения сигнала, которая зависит от разводки) можно описать двумя способами:
                      assign a = b & (c > 0); // здесь a должен быть wire
                      

                      ИЛИ
                      always @ (*) begin
                          a = b & (c > 0); // здесь a должен быть reg
                      end
                      

                      Здесь в списке чувствительности (в круглых скобках) можно записать значения сигналов через запятую, но лучше ставить *, тогда список чувствительности будет формироваться автоматически (туда будут включены все сигналы, от изменения значения которых может измениться состояние блока, т.е. все правые части присваиваний, условия при if и т.д.), а вручную его формировать ТОЛЬКО если нужно исключить какой-то сигнал оттуда.
                      В комбинаторных блоках ТОЛЬКО блокирующие присваивания.

                      Больше никаких конструкций в Verilog нет. <= в комбинаторных блоках или = в тактируемых — это или не имеющая смысла чепуха в первом случае или грубейшая ошибка во втором и так писать нельзя никогда.

                      P.S. Писать = внутри тактируемого блока можно. Если вам нужен генератор псевдослучайных чисел.
                        0
                        Как получить состояние гонки (и неопределённое поведение)?
                        А вот так:
                        always @(*) begin
                            a = a + 1;
                        end
                        

                        always @(posedge clk) begin
                            if (a) begin
                                a = 0;
                            end
                            else begin
                                a = 1;
                            end
                        end
                        
                          0
                          Во втором процессе нет состояния гонки и отсутствует неопределённое поведение, т.к. if проверяется лишь однажды. На выходе синтеза получится T-триггер.
                        0
                        Поздравляю! Много разработчиков на FPGA когда-то писали свой UART)))

                        Вы можете перестать занимать PLL, решить вопрос «чтобы не завязавать скорость последующей обработки данных на скорость обмена по UART» и убрать мажоритарную логику и FIFO для данных. И сделать более практичное применение. Просто тактируете модуль частотой обработки данных, а в его параметрах прописываете значение частоты и скорости по UART. Из частоты и скорости можно получить длину счётчика бита UART. Упал Rx, включился счётчик. Дребезг по входу уже будет неважен. Хотя по идее в нормальных условиях и при правильно спроектированной схеме его и не должно быть — сигнал с RS232 надо заводить на FPGA через буфер. Досчитал счётчик до середины — считали бит. Сбрасываем счётчик и отсчитываем бит. Читаем. И так — весь байт. Бита чётности у вас нет, значит потом будет стоп-бит, на середине которого можно проверить его уровень и сбросить счётчик, опять начав ожидать старт-бит — таким нехитрым образом на каждом байте будет осуществляться тактовая подстройка приёма в FPGA под передатчик в ПК.
                          0
                          PLL и FIFO в данном случае для имитации. А также я разбирался, как использовать IP Cores.
                          Предложенный алгоритм интересен, хотя его применение тоже имеет ограничения. Врядли он будет работать, если частота обработки данных не в разы превышает скорость обмена по UART.
                            0
                            Врядли он будет работать, если частота обработки данных не в разы превышает скорость обмена по UART.

                            Верно. Именно потому после написания кода нужно выводить ограничение на связку параметров CLOCK и Speed. Но обычно в ПЛИСе десятки мегагерц, на практике пока не сталкивался с ограничением.
                            Зато подобный подход может дать интересные плюшки. Например, можно отслеживать стабильность уровня Rx в заданном диапазоне около точки чтения бита, можно детектить гвозди на линии, можно отфильтровывать помехи, превышающие длительность половины бита UART и т.д. Прелесть в том, что всё это просто выводится в телеметрию модуля, анализируя которую потом можно сделать вывод о состоянии линии связи.
                              0
                              Имелось ввиду что UART может быть не только «компьютерным», где максимальная частота ограничивается сотнями килогерц. Никто не мешает связать асинхронно парочку ПЛИСов (хотя конечно лучше для этого использовать стандартные протоколы обмена).
                              А вот в случае внешнего интерфейса я согласен, ваш алгоритм достаточно полезный. Возьму себе на заметку.
                            0
                            Насчет убрать мажоритарную логику — не согласен. Особенно если сопли по которым гоняется связь длинные, а передатчик слабый.
                            0
                            Искренне поздравляю! В нашем полку прибыло! :)))
                            Я тоже начинал в своё время именно с uart. Более того, любой мой проект начинается с того, что я копирую в него uart. И в дальнейшем подключаю к нему всю отладку и тестирование проекта. Исключительно удобная штука.
                              0
                              Для первого HDL-кода выглядит вполне недурно.

                              По поводу частоты семплирования:
                              UART является асинхронным интерфейсом — без передачи синхросигнала и без восстановления частоты из данных. Скорость передачи данных по сути «оговаривается» заранее, определить её из потока данных довольно проблематично.
                              Но даже если оба устройства знают на какой частоте они обмениваются данными, данные частоты всё равно не будут идентичными. Во-первых, в каждом устройстве в общем случае будет свой источник тактирования (кварц, генератор, что угодно), имеющий внутренний дрифт. Во-вторых, часто невозможно получить целочисленное деление тактовой частоты для подходящего baud rate в UART: контроллер например работает на 24МГц, а чтобы получить 115200, нужно 24МГц разделить на 208.(3).
                              В общем к чему это я всё — семплировать входящие данные лучше на как можно большей частоте. К примеру, распространённым механизмом является работа приёмника на частоте x16: по заднему фронту входного сигнала (начало старт-бита) отсчитываем 8 тактов, попадая таким образом на середину бита; далее через каждые 16 тактов читаем значение входного бита. С точки зрения частотной ошибки мы можем себе позволить расхождение по частоте на ±8 периодов нашей частоты семплирования (т.к. точка семплирования в таком случае выпадет за пределы бита), и соответственно на ±1 период частоты семплирования на бит.
                              На первом такте при начале старт-бита мы заведомо не знаем попал ли наш отчёт ровно на задний фронт или нет, точность этого — как раз один период частоты семплирования. Соответственно при более низкой частоте (как х4 в вашем случае) стартовая ошибка может быть больше, а запас по отклонению частот — меньше.

                              По поводу использования блокирующих и неблокирующих присваиваний:
                              «Идеологически правильный» код потенциально может привести к формированию защёлок (latch) в синтезе: блок always @(*) комбинаторный, следовательно для каждого из сигналов, используемых в левой части присвоений, должны быть описаны все условия ветвления. В данном примере это обходится (возможно, по счастливой случайности) наличием блокирующих присваиваний сигналов перед ветвлениями, что по сути станет неявным присваиванием во всех ветвлениях, где это не описано явно. Примерно так развернутся эти присваивания:
                              always @(*)
                              begin
                              	if(nreset) begin
                              		if(div4 == 2'd0) begin
                              			case(s)
                              				4'd0: begin
                              						sendstart = 1'b1;
                              						canfetch = 1'b0;
                              					end
                              				4'd9: begin
                              						sendstart = 1'b0;
                              						canfetch = wr;
                              					end
                              				4'd10:
                              					begin
                              						sendstart = 1'b0;
                              						canfetch = wr;
                              					end
                              				default:
                              					begin
                              						sendstart = 1'b0;
                              						canfetch = 1'b0;
                              					end
                              			endcase		
                              		end else begin
                              			sendstart = 1'b0;
                              			if(s < 4'd9) begin
                              				canfetch = 1'b0;
                              			end
                              			else begin
                              				canfetch = wr;
                              			end
                              		end
                              		
                              		if(canfetch && idle) begin
                              			sendstart = 1'b1;
                              		end
                              		else begin
                              			sendstart = 1'b0;
                              		end
                              	end else begin
                              		sendstart = 1'b0;
                              		canfetch = 1'b0;
                              	end
                              end	
                              

                              Собственно, если бы присваивания были неблокирующими, ваш вариант скорее всего работал бы некорректно (не помню как в Verilog, но в VHDL повторное неблокирующее присваивание в блоке считается ошибкой).
                              Вне зависимости от того, блокирующие у нас присваивания или неблокирующие, пропуск одной из веток приведёт к тому, что синтезатор не будет знать, что ему делать с сигналом в данном условии, и примет решение создать элемент памяти. Но т.к. в блоке не указаны фронты (а то и вообще комбинаторный список чувствительности), будет установлена защёлка. Мета-пример подобной ситуации:
                              always @(*)
                              begin
                              	if(nreset) begin
                              		if(canfetch) begin
                              			sendstart = 1'b1;
                              		end
                              	end else begin
                              		sendstart = 1'b0;
                              	end
                              end	
                              

                              Что должно происходить с сигналом sendstart при nreset == 1 и canfetch == 0? Выходит, что он должен сохранить своё текущее состояние. Но для этого нужен элемент памяти, т.к. ситуация невозможна, если использовать только комбинаторные элементы.
                                0
                                (не помню как в Verilog, но в VHDL повторное неблокирующее присваивание в блоке считается ошибкой).

                                В VHDL повторное присваивание внутри процесса (аналог always) прекрасно работает. Выполняться будет последнее по тексту.
                                  0
                                  Значит и здесь тоже не помню; в любом случае это не очень красивый код, который будет хуже читаться.
                                0
                                Вот мне совершенно не нравиться «идеологически правильный» код передатчика. Как уже писали выше это потенциальный источник ошибок. На мой взгляд лучше использовать конструкцию always (@postedge clk) и внутри описывать нужную логику.

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                Самое читаемое