nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

"Ошибка на единицу" при передаче CRC

В прошлый раз обнаружили нехорошую вещь: при передаче данных от нашего устройства, CRC начинает работать одним словом раньше, чем надо, и результат своей работы отсылает одним словом раньше.

Пора уже это исправить, чего-то задолбался я немного.


Как было сказано чуть раньше, у нас смешались две "концепции". Этот передатчик, как и другие передатчики до того, работали по принципу "Fire and forget", "выстрелил и забыл". Т.е, когда передатчик не занят, мы даём ему команду на передачу очередного слова данных - и переходим к другим делам, с нашей точки зрения слово уже передано.

Но сигналы для управления модулем CRC работали "напрямую", т.е всё то время, что длится передача, мы должны "сопровождать" её выдачей правильных значений.

Что-то одно из этого надо поменять. Будь у нас одиночный передатчик - наверное я бы поставил пару регистров внутрь него, TransmitCRC и ComputeCRC, и "защёлкивал" туда значения вместе с сигналом start и данными D. Вполне логично: мы как бы дали цельное задание, "запускай передачу слова данных. Сейчас это будет слово, переданное в D, в это время вычисляй CRC". Или "Запускай передачу CRC, которое вычислил ранее, а значение в D проигнорируй!".

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

Сейчас я бы хотел пойти обратным путём - в кои-то веки отказаться от этого "выстрелил и забыл". Концепция хороша для процессора, зачем ему полностью замирать и ждать, когда передатчик закончит работу - у него и так дела есть, проверить окончание цикла, прыгнуть в начало цикла, получить новое значение и пр. Но здесь у нас специализированный модуль, у которого ровно одна задача - управлять пересылкой данных!

Вспоминаем, как совсем недавно у нас работал модуль АЛУ. Он прямо на том же такте, когда ему давали команду, выполняющуюся дольше 1 такта, устанавливал SrcStallReq = 1, что сразу же останавливало весь процессор (ну разве что выборка из памяти успешно завершалась, специально ради этого мы заморочались с отдельными SrcStall и SrcDiscard).

Попробуем организовать здесь сходную логику. Вот состояние sReply (передача ответного слова). Сначала мы ничего не передавали, ждали окончания паузы. Но затем мы отправляли start=1, и на том же такте передвигались в sTransmit. А давайте условием перехода в sTransmit будет окончание передачи ответного слова. Когда-то, до возни с процессором, мы ж так и делали, у нас был отдельный выход передатчика, ready, с которого мы и получали единичный импульс. Можно его вернуть! Если пытаться обойтись одним лишь RW (1 для передачи, 0 для приёма), получается не очень, придётся какой-то дополнительный регистр вводить, который отличит ситуацию "ещё не отправили ответное слово, ждём окончания паузы" от "уже отправили слово".

Пожалуй, удобнее всего будет выдавать ready сразу, как только закончилась передача битов данных, и мы начали передавать стоповый бит (в случае настоящего МКО, это будет бит чётности). В этот момент значения ComputeCRC и TransmitCRC уже не важны, они своё дело сделали, а до следующего слова ещё остаётся как минимум 25 тактов, чтобы успеть сделать выборку из памяти, особенно когда память мы делим с процессором, у которого безусловный приоритет. Получается вот так:

assign ready = (State == sB8) & ce & isSecondByte & RW;	//передатчик отправил все биты данных, осталось лишь стоповый бит

Тот случай, когда приходится проверять все 4 бита State... Хотя можно будет попробовать и по-другому: задержать ce ещё на один такт, и тогда вместо (State == sB8) проверять попросту isStopState, это выход cout счётчика. Скорее всего, всё примерно одинаково получится...

С выходом передатчика ready, логика конечного автомата даже упростится слегка. Переход из sReply в sTransmit - именно по ready=1. Тем самым, мы гарантируем, что при передаче ответного слова computeCRC = 0, transmitCRC = 0, т.е пока CRC обнуляется, чтобы начать вычисления по первому слову ДАННЫХ:
always @(posedge clk) begin
	OneTickLeft <= ~State[1]? 1'b0 : ce_2us | OneTickLeft;
	State <= 	FrameError? 	sIdle :
			isIdle? 	(BeginMessage? sReceive : sIdle):
			isReceive?	((DoWeNeedToTransmit | noWordsLeft)? (rBroadcast? sIdle : sReply) : sReceive):
			isReply?	(TxReady? sTransmit: sReply):
					(~DoWeNeedToTransmit | noWordsLeft | MessageError)? sIdle : sTransmit;
end
							
assign start 	= State[1] & (OneTickLeft&ce_2us | State[0]);


Далее, идёт состояние sTransmit. Тут мы в своё время упростили себе задачу: даже если ничего передать не нужно, мы с чистой совестью переходили в sTransmit и на следующий же такт уходили из него, зная, что в sReply передатчик был "озадачен" и проигнорирует наш следующий запрос. Если же надо хоть одно слово передать - мы просто будем ждать, пока он "освободится". К счастью, так всё и останется сейчас: от прихода TxReady до окончания передачи ещё есть около 25 тактов, поэтому мы вполне успеем "смыться"!

И останется поменять управление счётчиком оставшихся слов. Сейчас он уменьшал своё значение на единичку по выражению isTransmit & (~TxBusy). Получалось, что значение уменьшается в тот момент, когда слово ТОЛЬКО НАЧАЛО ПЕРЕДАВАТЬСЯ. Если же заменить выражение на то же самое "ready", идущее с передатчика, после передачи 16 бит данных (но возможно не до конца переданы стоповый бит / бит чётности), то пока слово передаётся, счётчик слов будет оставаться на прежнем значении, поэтому сигналы ComputeCRC / TransmitCRC будут верны:

wire MinusOneWord = DoWeNeedToTransmit? State[0] & TxReady :
					DataValid & RXisData;


Когда у нас приёмник и передатчик в одном модуле и взаимно исключают друг друга (уж либо приём, либо передача), можно и условный оператор отсюда убрать, просто верхнюю и нижнюю строчку объединить по "ИЛИ":
wire MinusOneWord = State[0] & TxReady | DataValid & RXisData;


Когда будут переданы биты данных последнего слова, по сигналу ready счётчик сбросится в ноль - и к следующему такту вернёмся в состояние sIdle, здесь проблем не ожидается.

Ещё надо посмотреть, когда приходит сигнал на инкремент адреса памяти:
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;


Ровно тогда же, когда вычитается единичка из числа оставшихся слов.

И ещё рассмотрим запрос на чтение из памяти:
always @(posedge clk)
	MemRdReq <= start & (~TxBusy) | BeginMessage;


Заменяем start & (~TxBusy) попросту на TxReady:
always @(posedge clk)
	MemRdReq <= TxReady | BeginMessage;


Всё хорошо: между приходом ready и началом следующего слова пройдёт хотя бы 25 тактов, так что выборка памяти будет сделана, даже при совместном использовании памяти.

Как будто бы и всё, теперь неплохо бы ему заработать как положено!

По крайней мере, штуковина синтезируется, в те же самые 302 ЛЭ, что и раньше (это тестовая схема, т.е и то что мы проверяем, и ещё один полудуплексный UART с CRC в качестве "контроллера шины"), с предельной частотой 49,5 МГц, меня пока устраивает...

На приём всё работает правильно, что неудивительно, мы его практически не тронули. Смотрим на передачу, момент сразу как получили командное слово 0x3703 (передать 3 слова телеметрии):


Перешли в состояние sReply = 2, и отсчитав два импульса ce_2us (обведены синим), начали передавать ответное слово, но всё так же находясь в состоянии sReply. Переходим в sTransmit аккурат когда на линию передачи RT_line поступает стоповый бит. Ещё "полбита" спустя на нашем "контроллере шины" загорается BC_HasOutput = 1, и значение 0x3000 - это ответное слово "адрес 6, ошибок нет", всё правильно.

Смотрим дальше:


Было передано слово 0xFF00 (заголовок массива) и ещё одно, 0x0000. Всё это время было установлено ComputeCRC=1, TransmitCRC = 0. А вот к следующему слову (третьему, последнему) видим смену на ComputeCRC=0, TransmitCRC=1. Поехали дальше:


Да, передали 0xC6F3, это CRC от нашего сообщения. Если на crccalc.com ввести сообщение 00FF0000 (наше сообщение, только с другим endianness, из-за использования UART с младшими битами вперёд), выбрать HEX вместо ASCII и запустить CRC-16, то на строке CRC16/KERMIT мы увидим то самое 0xC6F3.

Действительно обошлись "малой кровью", не добавив ни одного ЛЭ.

Вот, кажется, и всё, протокол информационного обмена исполнен в полном объёме, на "жёсткой логике", без обращения к процессору.

Осталось всё это "в железе" посмотреть, заодно написав "программу рабочего места". Потом, если всё нормально, написать примитивный DMA. Дальше ожидаю раунд борьбы с фиттером, который может завалить трассировку всего проекта целиком, и второй раунд, когда в кои-то веки придётся все части программы свести в единое целое, так что и ROM и RAM подрастут, и ширина адресных шин вместе с ними.

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

Recent Posts from This Journal

  • Тестируем 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 

  • 1 comment