nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

МКО через UART, часть 9: реализация CRC

В части 8 мы громко думали об этом, а теперь пора реализовать.

У нас уже есть приёмопередатчик с "встроенной" генерацией/проверкой CRC, но для него наш протокольный контроллер должен сформировать сигналы ComputeCRC и TransmitCRC, а также отреагировать на сигналы FrameError (очередное слово данных принять не удалось) и CRCerror (CRC не совпал, в сообщении где-то ошибка)...


Начнём с сигнала ComputeCRC. В состоянии sIdle = 002 должно быть ComputeCRC = 0. Тогда, даже если первое же принятое слово будет командным словом, адресованным нам, модуль CRC успеет обнулиться.

Далее, в состоянии sReceive = 012 (приём слов данных) нужно установить ComputeCRC = 1: будем подсчитывать CRC от поступающего сообщения. Но на последнем слове надо выставить ComputeCRC = 0, чтобы включилась сверка CRC из сообщения (идёт последним словом) с посчитанным только что.

В состоянии sReply = 102 (выдача ответного слова) нужно установить ComputeCRC = 0, чтобы по завершении передачи этого сообщения у нас снова был нулевой CRC, т.к по нашему протоколу, ответное слово в CRC не входит, туда входят только слова данных.

И наконец, в состоянии sTransmit = 112 повторяется история sReceive: держим ComputeCRC = 1, пока не останется последнее слово, на нём установим ComputeCRC = 0, чтобы CRC не видоизменилось от поступающих на вход данных.

Но ещё нужно "отключить" работу с CRC, если приходят команды управления (подадреса 00000 или 11111, см часть 6). Как это проще всего сделать - сообразим позднее.

Групповые сообщения (широковещательные, broadcast) логику нам не портят: в них возможен только приём информации, он в sReceive останется таким же, как был. Вывод будет сделан по последнему принятому слову, а что потом прыгаем сразу в sIdle, а не в sReply - не страшно.

Выражение для ComputeCRC сделаем таким:

assign ComputeCRC = State[0] & (WordsLeft[4:0] != 5'b0_0001);


WordsLeft - это выход 6-битного счётчика слов данных. Как всегда, мы чуточку схитрили, задействовав только младшие 5 бит, зная, что число слов варьируется от 0 до 32. Поэтому если младшие 5 бит: 0_0001, то заведомо это значит одно оставшееся слово, а не 33.

Далее, сигнал TransmitCRC. Когда TransmitCRC=1, передатчик вместо очередного слова данных из соответствующего мультиплексора (где выбирается либо из памяти, либо из часов реального времени, либо сформированное ответное слово) начинает бит за битом передавать из CRC. На работу приёмника данное значение никак не влияет: задействуется только самый крайний бит сдвигового регистра, в приёме не участвующий!

Поэтому в состоянии sIdle=002 нам пофиг на значение TransmitCRC, мы ждём командного слова, адресованного нам.

В состоянии sReceive=012 также пофиг на TransmitCRC, т.к принимаем слова данных.

В состоянии sReply=102 обязательно TransmitCRC=0 - мы передаём ответное слово.

В состоянии sTransmit=112 держится TransmitCRC=0, пока не дойдём до последнего слова данных, там ставим TransmitCRC=1.

Подытожим:
assign ComputeCRC = State[0] & (WordsLeft[4:0] == 5'b0_0001);


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

Займёмся теперь обработкой сигнала FrameError: он появляется, если приёмник получил нечто, не являющееся корректным словом. Сейчас, в случае UART, это если стоповый бит не пришёл в нужный момент (ожидали единицу на линии, а там вместо неё нолик), либо если один байт пришёл, а второй - нет. В такой ситуации имеет смысл из любого другого состояния вернуться в sIdle. Послать "ругательное" ответное слово становится опасно, т.к из-за проблем на линии мы уже не знаем с уверенностью, сколько слов было послано, и в какой именно момент передатчик ожидает нашего ответа.

Не мудрствуя лукаво, дополним конечный автомат:

always @(posedge clk)
	State <= 	FrameError? 	sIdle :
			isIdle? 	(BeginMessage? sReceive : sIdle):
			isReceive?	((DoWeNeedToTransmit | noWordsLeft)? (rBroadcast? sIdle : sReply) : sReceive):
			isReply?	sTransmit:
					(~DoWeNeedToTransmit | noWordsLeft | MessageError)? sIdle : sTransmit;


(добавилась верхняя строка с FrameError в правой части выражения)

Впрочем, меня опять терзают смутные сомнения... Допустим, нам передают полётное задание. Часть слов мы приняли, а потом случился этот FrameError. Насколько достоверны были предыдущие слова - мы не знаем и уже не узнаем, т.к до CRC дело не дошло. И пока что мы "так всё и оставляем" - какие-то слова уже записаны в память, какие-то нет. По логике вещей, не получив нашего ответа на сообщение, контроллер шины должен будет послать сообщение повторно, и вот тогда всё перезапишется как надо, ему придёт ответное слово - и продолжится нормальная работа.

Но может, всё-таки надо будет ввести некий регистр в нашем "протокольном контроллере", что "весь комплект исходных данных был успешно передан". Ладно, это ещё надо будет обсудить с заказчиками и закрепить в письменной форме...

И наконец, CRCerror. До тех пор, пока ComputeCRC=1, заведомо будет выполняться CRCerror=0. Он может "зажечься" в единичку только на последнем слове данных, но как на приёме, так и на передаче. Последнее зависит от того, что будет делать приёмник во время работы передатчика. На нашем полудуплексном UART с приёмника будут идти сплошные единицы, они скорее всего не совпадут со значением CRC - и будет выдана CRCerror = 1. Ну и хрен с ним! Пущай зажигается MessageError = 1, всё равно ответное слово уже было передано, а к следующему разу значение будет сформировано по-новой.

Сейчас MessageError формируется следующим образом:
always @(posedge clk)
	MessageError <= isIdle? isCWerror : (isFirstDataWord & DataValid & RXisData)? MessageError | (D != TxMux) : MessageError;


Перемудрил маленько. То же самое можно было записать вот так:
always @(posedge clk)
	MessageError <= isIdle? isCWerror : (isFirstDataWord & DataValid & RXisData & (D != TxMux)) | MessageError;


В состоянии sIdle проверяем корректность командного слова, затем проверяем заголовок сообщения. Теперь сюда очень легко, через "ИЛИ" добавляется и ошибка в CRC:

always @(posedge clk)
	MessageError <= isIdle? isCWerror : (isFirstDataWord & DataValid & RXisData & (D != TxMux)) | CRCerror | MessageError;


Собственно, и всё: теперь и при некорректном CRC во время приёма сообщения, ответное слово будет содержать бит "Ошибка в сообщении", что должно заставить контроллер шины (пока что, на макете, его роль выполняет компьютер) передать сообщение повторно.

Если бы нам "индивидуально" прислали команду управления "Синхронизация (с СД)", то мы бы сдуру послали "Ошибка в сообщении", так как на первом же (и единственном) слове данных полезли бы проверять CRC, и он бы не совпал. Но команда "Синхронизация (с СД)" будет групповой, отвечать на неё не надо, так что пущай у нас зажигается MessageError, он всё равно никуда не попадёт.

Приведём код "протокольного контроллера" целиком:

//протокольный контроллер МКО, конкретно под ВИПС.
//работает независимо от процессора, т.к последний иногда работает "в режиме реального времени",
//когда прерывание заставит его запороть кадр. А отключить прерывание - не ответим на запрос и нас выключат.

//предполагается самопальный DMA, где процессор имеет приоритет на доступ к памяти, но мы можем быть уверены, что имеются свободные такты,
//когда к памяти можем обратиться мы. Задержка на несколько тактов - не страшно, т.к за одно обращение получаем 16 бит,
//а на их отправку уходит 16 мкс. (один такт: 40 нс).

//если нужно прочитать значение из памяти, выставляем адрес Addr и шлём запрос MemRdReq. Когда придёт MemReady=1,
//можно взять результат по MemIn. Задержка непостоянна, но вряд ли превысит несколько тактов.

//если нужно записать в память, мы просто шлём MemWrReq, с адресом записи по Addr, и данными, взятыми напрямую с приёмника
//запись может пройти не мгновенно, но мы вполне уверены, что к следующему нашему запросу она уже пройдёт.
//запросы с нашей стороны будут идти не чаще чем раз в 20 мкс. 

//если приходит КУ "Синхронизация с СД", мы по приходу этого СД выдаём sync=1, чтобы скорректировать наше бортовое время.
//когда запрошены целевые данные, мы в нужный момент вместо адреса из памяти выдаём на передатчик значение TimeStamp
//из наших "часов реального времени". Там на выходе стоит время получения кадра, с которого мы измеряем параметры.

//адрес нашего оконечного устройства (ОУ) на шине МКО задаётся с помощью адресной заглушки, хотя можно временно изобразить её "внутри ПЛИС".
//адрес от 0 до 30 задаётся в OurAddr, а также выставляется AddrParity таким образом, чтобы сумма битов получалась нечётной.
//если адрес получился 31 (широковещательный), или если чётность не совпала, будем принимать только групповые сообщения.

//Мы реагируем на две КУ: "Синхронизация с СД" (подадрес 00000 либо 11111, "число слов" 10001, приём/передача = 0, приём), см выше
// и "Установка ОУ в исходное состояние" (подадрес 00000 либо 11111, "число слов" 01000, приём/передача = 1, передача) (пока не реализовано)

//а также на 10 различных сообщений. На приём (K=0):
//1 0001 - FT (Flight Task) - полётное задание
//1 0010 - LT (Long range Target) - параметры МДД
//0 0011 - ST (Short range Target) - параметры МБД
//1 0100 - AF (AFfine  matrix) - матрица аффинного преобразования
//0 0101 - RA (RAw data) - сырые данные с соседнего комплекта для стереорежима
//на передачу (K=1):
//0 0110 - DA (DAta) - целевая информация
//1 1000 - TM (TeleMetry) - телеметрическая информация
//0 1001 - IM (IMage)- передача изображения
//0 1010 - DU (DUmp) - передача дампа памяти
//0 1100 - RA (RAw data) - передать сырые данные на соседний комплект для стереорежима

//все прочие команды считаются некорректными, посылаем ОС с признаком "ошибка в сообщении",
//его же посылаем, если не совпадает заголовок сообщения, либо не совпадает CRC. 
//CRC всовываем последним словом при передаче автоматически. 

module MilStdRemoteTerminalController (input clk,
		//интерфейс с приёмником МКО
		input [15:0] D, input RXisData, input DataValid, input FrameError, input CRCerror,
		//интерфейс с передатчиком МКО
		output [15:0] Q, output TXisData, output start, input TxBusy,
		//управление CRC
		output ComputeCRC, output TransmitCRC, 
		//интерфейс с оперативной памятью (малого объёма, около 1000 слов)
		output [MemWidth-1:0] Addr, input [15:0] MemIn, input MemReady, output MemWrReq, output reg MemRdReq = 1'b0,
		//интерфейс с часами реал времени
		output sync, input [15:0] TimeStamp,
		//интерфейс с адресной заглушкой
		input [4:0] OurAddr, input AddrParity,
		
		//output [15:0] DHeader,
		output DError);
		
parameter MemWidth = 9;
		
reg OurAddrOK = 1'b0;
always @(posedge clk)
	OurAddrOK <= (^OurAddr^AddrParity);
	
wire [4:0] curWordAddr = D[15:11];		//адрес ОУ
wire curWordDoTransmit = D[10];			//признак "приём-передача" (1 значит мы должны передавать данные)
wire [4:0] curWordSubAddr = D[9:5];		//подадрес
wire [4:0] curWordWordsCount = D[4:0];	//число слов данных (0 означает 32), при подадресах 00000 или 11111 это "команды управления"

reg MessageError = 1'b0; //становится одним из флагов в ответном слове

localparam sIdle 	= 2'b00;
localparam sReceive 	= 2'b01;
localparam sReply 	= 2'b10;
localparam sTransmit	= 2'b11;
	
reg [1:0] State = sIdle;
wire isIdle 	= (State == sIdle);
wire isReceive 	= (State == sReceive);
wire isReply 	= (State == sReply);
wire isTransmit = (State == sTransmit);
	
reg DoWeNeedToTransmit = 1'b0;
always @(posedge clk) if (isIdle)
	DoWeNeedToTransmit <= curWordDoTransmit; 

wire isBroadcast = (curWordAddr == 5'b11111);	
reg rBroadcast = 1'b0;
always @(posedge clk) if (isIdle)
	rBroadcast <= isBroadcast;
	
wire isOurAddr = (OurAddr == curWordAddr)&OurAddrOK | isBroadcast;

wire BeginMessage = DataValid & (~RXisData) & isOurAddr;

wire isServiceCommand = (curWordSubAddr == 5'b1_1111) | (curWordSubAddr == 5'b0_0000);

wire [5:0] WordCount;

assign WordCount[4:0] = isServiceCommand? {4'b0, curWordWordsCount[4]} : curWordWordsCount;
//если команда управления, то либо 0 слов (для команд от 00000 до 01111), либо 1 слово (для команд от 10000 до 11111)
//в противном случае, берём число слов "как есть"
assign WordCount[5] = isServiceCommand? 1'b0 : (curWordWordsCount == 5'b00000);
//число слов "0" мы должны интерпретировать как 32

wire noWordsLeft;
wire MinusOneWord = DoWeNeedToTransmit? (isTransmit & (~TxBusy)) :
										DataValid & RXisData;
wire [5:0] WordsLeft;
lpm_counter WordCounter (
			.clock (clk),
			.cnt_en (MinusOneWord),
			.data (WordCount),
			.sload (isIdle),
			.Q (WordsLeft),
			.cout (noWordsLeft) );
	defparam
		WordCounter.lpm_direction = "DOWN",
		WordCounter.lpm_port_updown = "PORT_UNUSED",
		WordCounter.lpm_type = "LPM_COUNTER",
		WordCounter.lpm_width = 6;

always @(posedge clk)
	State <= 	FrameError? 	sIdle :
			isIdle? 	(BeginMessage? sReceive : sIdle):
			isReceive?	((DoWeNeedToTransmit | noWordsLeft)? (rBroadcast? sIdle : sReply) : sReceive):
			isReply?	sTransmit:
					(~DoWeNeedToTransmit | noWordsLeft | MessageError)? sIdle : sTransmit;
							
assign start 	= State[1];
assign TXisData = State[0];	

reg [3:0] HiMem = 4'b0000;

always @(posedge clk) if (isIdle)
	HiMem <= curWordSubAddr[3:0];
	
assign Addr[8:5] = HiMem;

wire [4:0] LoMem;
lpm_counter LoMemCounter (
			.clock (clk),
			.cnt_en (MinusOneWord),
			.sclr (isIdle),
			.Q (LoMem) );
	defparam
		LoMemCounter.lpm_direction = "UP",
		LoMemCounter.lpm_port_updown = "PORT_UNUSED",
		LoMemCounter.lpm_type = "LPM_COUNTER",
		LoMemCounter.lpm_width = 5;

assign Addr[4:0] = LoMem;		

wire ConnectRTC = (~HiMem[3]) & (LoMem == 5'b00010);

reg [15:0] TxMux = 1'b0;

always @(posedge clk) if (MemReady)
	TxMux <= ConnectRTC? TimeStamp : MemIn;
	
//assign Q = TxMux;

assign Q = State[0]? TxMux : {OurAddr, MessageError, 10'b00_0000_0000};

assign MemWrReq = DataValid & RXisData & State[0] & (~MessageError) & (~isFirstDataWord);

always @(posedge clk)
	MemRdReq <= start & (~TxBusy) | BeginMessage;

//проверка корректности пары "приём/передача" и подадреса

wire isSubAddrError;
VIPSsubaddrError ErrDetection (
				.subAddr (curWordSubAddr),
				.isError (isSubAddrError));
//соответствие приёма/передачи подадресу					
wire isTransmitReceiveError = ((curWordSubAddr[3] | curWordSubAddr[2] & curWordSubAddr[1]) ^ curWordDoTransmit) & (~isServiceCommand);
	
wire isCWerror = isSubAddrError | isTransmitReceiveError;

reg isSyncDWCommand = 1'b0;

always @(posedge clk) if (isIdle)
	isSyncDWCommand <= isServiceCommand & (curWordWordsCount == 5'b10001);

assign sync = DataValid & RXisData & State[0] & isSyncDWCommand;

/*
wire [15:0]ExpectedHeader;
//assign DHeader = ExpectedHeader;

ReedMuller5_16 HeaderCheck (.D ({^HiMem, HiMem}),
							.Q (ExpectedHeader));
*/							
reg isFirstDataWord = 1'b0;
always @(posedge clk) if (isIdle | DataValid & RXisData)
	isFirstDataWord <= isIdle;
//always @(posedge clk)
//	isFirstDataWord <= isIdle? 1'b1 : (DataValid & RXisData)? 1'b0 : isFirstDataWord;

always @(posedge clk)
	MessageError <= isIdle? isCWerror : (isFirstDataWord & DataValid & RXisData & (D != TxMux)) | CRCerror | MessageError;
	//MessageError <= isIdle? isCWerror : (isFirstDataWord & DataValid & RXisData)? MessageError | (D != TxMux) : MessageError;
//	MessageError <= isIdle? isCWerror : (isFirstDataWord & DataValid & RXisData)? MessageError | (D != ExpectedHeader) : MessageError;

//assign DError = MessageError;

assign ComputeCRC = State[0] & (WordsLeft[4:0] != 5'b0_0001);
assign TransmitCRC = State[0] & (WordsLeft[4:0] == 5'b0_0001);

endmodule



Жуткая штуковина... Синтезируется в 110 ЛЭ, предельная частота 63,69 МГц, меня это устраивает (пока работаю на 25 МГц).

Самое время проверить весь "информационный обмен" в сборе: протокольный контроллер + часы реального времени + память + полудуплексный приёмопередатчик + CRC. Сначала на симуляции, потом и в железе, в кои-то веки.
Tags: ПЛИС, работа, странные девайсы
Subscribe

  • Тестируем atan1 на QuatCore

    Пора уже перебираться на "железо" потихоньку. Решил начать с самого первого алгоритма, поскольку он уже был написан на ассемблере. В программу внёс…

  • Формулы приведения, что б их... (и atan на ТРЁХ умножениях)

    Формулу арктангенса на 4 умножениях ещё немножко оптимизировал с помощью алгоритма Ремеза: Ошибка уменьшилась с 4,9 до 4,65 угловой секунды, и…

  • Алгоритм Ремеза в экселе

    Вот и до него руки дошли, причина станет ясна в следующем посте. Изучать чужие библиотеки было лениво (в том же BOOSTе сам чёрт ногу сломит), писать…

  • atan на ЧЕТЫРЁХ умножениях

    Мишка такой человек — ему обязательно надо, чтоб от всего была польза. Когда у него бывают лишние деньги, он идёт в магазин и покупает какую-нибудь…

  • Ай да Пафнутий Львович!

    Решил ещё немного поковыряться со своим арктангенсом. Хотел применить алгоритм Ремеза, но начал с узлов Чебышёва. И для начала со своего "линейного…

  • atan(y/x) на двух умножениях!

    Чего-то никак меня не отпустит эта тема, всё кажется, что есть очень простой и эффективный метод, надо только его найти! Сейчас вот такое…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments