nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

"МКО через UART", часть 7 1/2 - проверка заголовков ПОПРОЩЕ

Что-то я не доволен тем, что получилось в прошлый раз. С одной стороны, работает, и есть определённая "гордость" в том, что мы на ходу, весьма малой кровью перевели 5-битный подадрес в 16-битный код Рида-Мюллера. С другой - радость омрачил квартус, выдав странный баг. Потом он ушёл, но "осадочек остался".
И ещё одна возможная проблема: в той логике сопоставления заголовков массивов подадресам содержался изъян в лице сообщения "СЫРЫЕ ДАННЫЕ ДЛЯ СТЕРЕОРЕЖИМА", которые один прибор передаёт другому. Поскольку разные подадреса на приём и на передачу, то и заголовки получались разными, а надо было выбрать какой-то один. И я, не сильно задумываясь, вписал тот, что соответствует подадресу при передаче. И тогда такая "жёсткая" логика его бы забраковала, проверяя подадрес приёма. Вообще, не поздно это дело поменять, там вообще в протоколе было обозначено, что сообщение содержит "наши служебные данные", которые мы вправе поменять, но обязательно поставим всех в известность.
Ну и в целом у меня осталось впечатление, что можно было сделать гораздо проще, если свою гордость пока что отодвинуть и просто свериться с содержимым оперативной памяти.

Ведь у нас всё содержимое памяти автоматически инициализируется нужными нам значениями при включении ПЛИС, почему бы этим не воспользоваться! В тех "сегментах", что мы отвели на ПРИЁМ, самым первым значением положим ПРАВИЛЬНЫЙ заголовок соответствующего массива.

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


У нас был следующий код для MemRdReq (запрос на чтение из памяти):
always @(posedge clk)
	MemRdReq <= start & (~TxBusy);


То есть, запросы шли исключительно когда наш передатчик получает очередное слово для отправки. Первый раз это происходит при передаче ответного слова, ровно тогда ("на будущее") мы запрашиваем первое слово данных, и так далее. При этом немножко запросов оказываются лишними, но для запросов чтения это допустимо! (это записывать "про запас" нельзя категорически)

Нужно, чтобы дополнительный запрос отправлялся при получении командного слова, или чуточку позже (на такт-другой). Например, вот так:
always @(posedge clk)
	MemRdReq <= start & (~TxBusy) | BeginMessage;


BeginMessage=1, если пришло командное слово, содержащее "наш" адрес, нормально. Уже к следующему такту сформируется и запрос MemRdReq, и адрес, по которому производить чтение.

Через несколько тактов придут запрошенные данные, вместе с сигналом MemReady=1. При этом в регистр TxMux "защёлкнется" содержимое.

Запрос на запись в память нужно чуть подправить, чтобы не записывалось первое слово данных (оно у нас всегда является заголовком, его записывать не надо, только проверить). Было так:
assign MemWrReq = DataValid & RXisData & State[0] & (~MessageError);


Запрос идёт ровно по получению очередного слова данных, если нет ошибки в сообщении, и мы находимся в состоянии sReceive или sTransmit (главное, чтобы не sIdle, иначе "чужие" слова начнём заталкивать). А нужно ещё исключить первое слово:
assign MemWrReq = DataValid & RXisData & State[0] & (~MessageError) & (~isFirstDataWord);


И последний штрих: когда приходит первое слово данных, сравниваем его со значением из памяти, которое было защёлкнуто в TxMux:
always @(posedge clk)
	MessageError <= isIdle? isCWerror : (isFirstDataWord & DataValid & RXisData)? MessageError | (D != TxMux) : MessageError;


Приведём весь код модуля целиком:

module MilStdRemoteTerminalController (input clk,
		//интерфейс с приёмником МКО
		input [15:0] D, input RXisData, input DataValid,
		//интерфейс с передатчиком МКО
		output [15:0] Q, output TXisData, output start, input TxBusy,
		//интерфейс с оперативной памятью (малого объёма, около 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);
		
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;
lpm_counter WordCounter (
			.clock (clk),
			.cnt_en (MinusOneWord),
			.data (WordCount),
			.sload (isIdle),
			.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 <= 	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;

reg isFirstDataWord = 1'b0;
always @(posedge clk) if (isIdle | DataValid & RXisData)
	isFirstDataWord <= isIdle;

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

endmodule


Синтезируется это безобразие в 105 ЛЭ. Напомним, что без проверки заголовка выходило 91 ЛЭ, а с проверкой через непосредственную генерацию Рида-Мюллера: 110 ЛЭ.

Выходит, старая версия добавляла 19 ЛЭ, а новая версия: 14 ЛЭ. Чтобы проверить равенство двух 16-битных значений, просто исходя из того, что компаратор обязан иметь 32 входа, получаем 8 ЛЭ на эту проверку. Плюс регистр isFirstDataWord, и плюс некоторое усложнение логики как MessageError, так и MemWrReq и MemRdReq. Пожалуй, соглашусь :)

Вся конструкция в сборе (протокольный контроллер, рассмотренный выше + адресная заглушка + "фиктивный" передатчик + часы реального времени) синтезируется в 161 ЛЭ, но после работы фиттера ВНЕЗАПНО разрастается до 177 ЛЭ, тогда как со старой версии проверки заголовков выходило 165 ЛЭ. Вот это называется сэкономил :)

Ну ладно, посмотрим, что из этого выходит:


Сообщение "Передать полётное задание". Как только получено командное слово, идёт запрос на чтение, приходит ответ: 0xDEAD. Это я так инициализировал память, чтобы потом увидеть, было ли записано то что надо? Поскольку 0xDEAD не совпадает с 0xAAAB, ни одного слова так и не записывается, и ответное слово отправляется с признаком "Ошибка в сообщении". Всё правильно.

Попробуем переправить заголовок сообщения на 0xDEAD:


Да, в этот раз запись происходит. Формируется ещё кривоватый, в пол-такта сигнал на MemWrReq, но к фронту тактового импульса уже исчезает. Да и появись он, перезапись корректного заголовка нам не повредит.

Пара запросов на получение данных от нас:


Ну да, лишний запрос в память, а в остальном работает как и прежде.

Пытаемся "накормить" телеметрию, вместо того, чтобы получить её:

Безуспешно, как и должно быть!

Групповое сообщение с заголовком, не совпадающим с нынешнем 0xDEAD:


Из-за того, что заголовок не совпал, формируется признак ошибки, но ответное слово не отправляется, поскольку групповое сообщение. И, как видно, запросов на запись в память тоже не идёт.

"Синхронизация (с СД)":


Ну, такое себе... Запрос в память всё равно идёт, и сравнение слова данных, вот только здесь заголовка нет, формируется ошибка, но никуда не идёт, поскольку сообщение групповое. И на формирование сигнала sync оно никакого влияния не оказывает. Сейчас мы, по сути, освободили адреса памяти 0x000 и 0x1E0, т.к ОДНО слово данных, идущее с командами управления, уж точно записано не будет из-за введённой нами задержки.

В общем-то, меня всё устраивает. Чуть красивее было бы не формировать ошибку в командах управления, мало ли, вдруг затребуют от нас КУ "Передать ОС", ну да ладно.


Любопытно! На этапе синтеза мы действительно сэкономили аж 4 ЛЭ, но потом приходит фиттер и впихивает лишних 16 ЛЭ, делая эту реализацию ещё толще, чем была! Конечно, фиттер вещь непредсказуемая, сегодня впихал, а завтра, в общей схеме (с процессором, DMA, видеопроцессором и пр.) - обойдётся, но здесь я могу его понять, схема стала более связной, чем была. Раньше приёмник и передатчик почти что жили своей жизнью, можно было их разнести по разные стороны, теперь же между ними вставился 16-битный компаратор, а эти ПЛИС, 5576ХС4Т (функциональный аналог Flex10k) имеют недостаточно мощные интерконнекторы, толстые шины ему тяжело даются.

Так что придётся оставить два варианта, а там уж выбрать по обстоятельствам. Зато буриданов осёл во мне доволен: рассмотрели оба варианта и всё съели!

На очереди по-прежнему генерация и проверка 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