nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

CRC и полудуплексный UART

Апологеты "модели OSI" скажут, что генерация и проверка CRC (Cyclic Redundancy Codes) - это уже канальный уровень, а не физический, и мешать их между собой не стоит. Дескать, физический уровень - это когда говоришь "передай вот этот байт" - и самому передатчику должно быть пофиг, что это за байт такой, частью чего он является. А всей остальной системе должно быть пофиг, как именно этот байт передаётся - хоть оптикой, хоть медяшкой, хоть по радио.

Но если уж я заранее знаю, что у меня будет на физическом уровне (поначалу, для "обкатки", RS485, позже - МКО, он же ГОСТ Р 52070-2003, он же Mil-Std 1553), и у меня была возможность, исходя из этого, придумать конкретный вид применяемого CRC, то более "тесное" проникновение одного в другое позволяет заметно упростить проект.

Смысл в том, что наиболее простая реализация CRC - когда мы подаём по одному биту за раз, поэтому если уж во время передачи данных они всё равно должны "сериализоваться" - то почему бы этот поток бит и не пустить на CRC. Да и мультиплексировать один бит (поставить ли его из "обычного" сдвигового регистра, или из CRC) получается не так затратно, чем 8-битную шину, а тем более 16-битную.

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

Для начала раскомментируем выражение isDataBit:
assign isDataBit = (State[3] | State[0])&(~isStopState)&ce;	


И ещё разок смотрим работу передатчика:



Убеждаемся, что isDataBit=1 выдаётся ровно 8 раз, причём во время передачи он выдаётся в последний такт передачи данного бита:


В начале, когда мы отправляем данные пакета, байт за байтом, и в то же самое время подсчитываем CRC - всё хорошо. Ко входу CRC как раз идёт сигнал, отправляемый передатчику, и только когда там идут биты данных, мы выдаём isDataBit=1, чтобы каждый бит "защёлкнулся", но строго один раз.

Но когда приходит время отправить вычисленное значение CRC, возникает проблема. Первый бит (b0) передаётся как надо, но в тот момент, когда должен защёлкнуться следующий, с выхода CRC продолжает идти первый бит, и лишь СЛЕДУЮЩИМ ТАКТОМ - второй (b1). То есть, импульсы isDataBit идут чуть позже, чем хотелось бы. Попробую сделать так: использовать для isDataBit "комбинаторный сигнал" comb_ce, а уже для защёлкивания очередного бита - "регистровый" сигнал ce:

	wire comb_ce = (FD & Limit) == Limit;
	reg ce = 1'b0;
	always @(posedge clk)
		ce <= comb_ce;
	assign isDataBit = (State[3] | State[0])&(~isStopState)&comb_ce;


Посмотрим, во сколько ЛЭ это выльется и не снизит ли быстродействие. Если так, снизим лимит ещё на единичку и поставим два регистра подряд, первый будет формировать импульс для isDataBit, а уже второй: ce. Пока выходит так:



Предельная частота пока не снизилась, она и не могла, поскольку выходные сигналы "не считаются" - они по другой графе идут.

И давайте сразу допишем код мультиплексора внутри полудуплексного UART, позволяющего вместо байта по входу D передать CRC, идущий по одноимённому входу, бит за битом. Вместо строки

SR[0] <= (startTX & isIdle)? 1'b0 : SR[1];


запишем:
SR[0] <= (startTX & isIdle)? 1'b0 : isCRC? CRC_bit : SR[1];


После всех изменений, этот приёмопередатчик стал занимать 35 ЛЭ, до этого было 32 ЛЭ. Это при тактовой частоте 25 МГц и скорости передачи 921 600 бод, с отключёнными выходами FrameError и LineBreak, так и не придумали толком, куда их присандалить. Хотя можно было бы LineBreak на прерывание - сообщить каким-то образом что линия оборвана. А FrameError - для статистики, считать процент забракованных байтов. Но попозже.

Теперь давайте всё-таки запишем в виде верилога модуль вычисления CRC. Нечто у меня лежит:
//-----------------------------------------------------------------------------
// Copyright (C) 2009 OutputLogic.com 
// This source file may be used and distributed without restriction 
// provided that this copyright statement is not removed from the file 
// and that any derivative work contains the original copyright notice 
// and the associated disclaimer. 
// 
// THIS SOURCE FILE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS 
// OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED	
// WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 
//-----------------------------------------------------------------------------
// CRC module for data[0:0] ,   crc[15:0]=1+x^5+x^12+x^16;
//-----------------------------------------------------------------------------
module CRC_for_VIPS(
  input D,
  input crc_en,
  output [15:0] crc_out,
  input rst,
  input clk);

  reg [15:0] SR;	//shift reg

  assign crc_out = SR;
  
  wire FB = D ^ SR[15];

  always @(posedge clk) begin
	if (rst) begin
		SR <= {16{1'b0}};
	end
	else if (crc_en) begin
		SR[0]  <= FB;
		SR[1]  <= SR[0];
		SR[2]  <= SR[1];
		SR[3]  <= SR[2];
		SR[4]  <= SR[3];
		SR[5]  <= SR[4] ^ FB;
		SR[6]  <= SR[5];
		SR[7]  <= SR[6];
		SR[8]  <= SR[7];
		SR[9]  <= SR[8];
		SR[10] <= SR[9];
		SR[11] <= SR[10];
		SR[12] <= SR[11] ^ FB;
		SR[13] <= SR[12];
		SR[14] <= SR[13];
		SR[15] <= SR[14];
	end

  end // always

endmodule // crc


Это код, сгенерированный на сайте http://outputlogic.com/?page_id=321 но потом "причёсанный" ручками. Асинхронный сброс заменён на синхронный, изменены названия "проводов" и регистров. Там был c_lsfr (combinatorial linear-shift feedback register) и r_lsfr (register...), я комбинаторные части формирую "на месте", регистр обозвал просто и незамысловато: SR (shift register).

Но нынешний вход CRC_EN хочу переименовать в clk_en (clock enable) - именно на этот вход будут поступать импульсы isDataBit, и только по ним будет происходить хоть что-нибудь.

А вместо общего сброса поставим как раз-таки crc_en. Когда crc_en=1, схема будет работать как этот самый сдвиговый регистр с обратными связями, обновляя значение CRC. А когда crc_en=0, он превращается в самый обычный сдвиговый регистр, на вход которого заталкиваются нули. Таким способом можно его обнулить, и этим же способом - вытолкать по одному биту значение CRC, которое он посчитал.

16-битный выход crc_out становится отладочным, а для работы вытаскиваем однобитный выход Q = crc_out[15].

И наконец, добавим "RS-триггер" CRC_ERROR, который устанавливается в ноль (сбрасывается), когда crc_en = 1 (т.е когда мы продолжаем формировать CRC, то ли для выдачи на линию связи, то ли для сверки с тем, что получим с линии связи), и устанавливается в единицу, если D ^ SR[15] = 1, т.е вход (CRC, переданное в конце пакета) не совпал с выходом (CRC, посчитанным "только что", на месте). Если хотя бы один бит из 16 не совпадёт, в конце работы у нас выйдет CRC_ERROR = 1.

В конечном итоге, этот модуль выглядит так:
module CRC_for_VIPS(
  input clk,
  input D,
  input clk_en,
  input crc_en,
  output [15:0] crc_out,
  output Q,
  output reg crc_error = 1'b0  );

  reg [15:0] SR;	//shift reg

  assign crc_out = SR;
  assign Q = SR[15];
  
  wire FB = (D ^ SR[15]) & crc_en;

  always @(posedge clk)	if (clk_en) begin
		SR[0]  <= FB;
		SR[1]  <= SR[0];
		SR[2]  <= SR[1];
		SR[3]  <= SR[2];
		SR[4]  <= SR[3];
		SR[5]  <= SR[4] ^ FB;
		SR[6]  <= SR[5];
		SR[7]  <= SR[6];
		SR[8]  <= SR[7];
		SR[9]  <= SR[8];
		SR[10] <= SR[9];
		SR[11] <= SR[10];
		SR[12] <= SR[11] ^ FB;
		SR[13] <= SR[12];
		SR[14] <= SR[13];
		SR[15] <= SR[14];
		crc_error <= crc_en? 1'b0 : (D ^ SR[15]) | crc_error;
	end

endmodule // crc


Синтезируется в 19 ЛЭ, и в 20 ЛЭ после фиттера. Нормально.

Что ж, давайте проверим совместную работу передатчика и модуля CRC:


Нижний приёмопередатчик - это как бы "принимающая сторона", чтобы убедиться, что посланное нами будет воспринято адекватно.

Появился дополнительный управляющий сигнал, crc_en. Пока что можно было с помощью инвертора из него получить isCRC для приёмопередатчика. Т.е когда crc_en=1, это означает: передаём данные, по ним вычисляем CRC. Когда crc_en=0, передаём посчитанный CRC, а он должен прекратить вычисляться, и просто вытолкнуться "как есть".

Опробуем передачу сначала байта 0x42, затем 0xAA, затем два раза передачу CRC, чтобы все 16 бит прошли.

Смотрим на первый байт:


Значение 0x42 было успешно передано, а затем принято. Тем временем сформировался CRC = 0x6886. Совпадает с X-Modem, но это случайное совпадаение: 0x42 является палиндромом, хоть слева направо передавай по битам, хоть справа налево - выйдет то же самое (0100_0010).

Смотрим дальше, на второй байт 0xAA:


Также он успешно передан, и CRC обновляется до 0x61FE. Такой результат, увы, в онлайн-калькуляторе нам не дают. Мы-то ожидали, что биты будут поступать в CRC от старшего к младшему (в МКО так и будет), но UART передаёт от младшего к старшему, а отображаются они "как есть". Так что для проверки надо "ручками" реверсировать 0xAA = 1010_1010. Это будет 0101_0101 = 0x55. Если в калькуляторе вбить последовательность 4255 в HEX, то CRC по методу X-Modem будет тот самый 0x61FE.

Другой вариант: наш CRC 0x61FE = 0110_0001_1111_1110 реверсировать, это даст 0111_1111_1000_0110 = 0x7F86. Это значение мы найдём в онлайн калькуляторе, если введём исходную последовательность 42AA и посмотрим на графу KERMIT. Хорошо, CRC вычисляется "предсказуемо".

Теперь посмотрим, как он будет передаваться по линии связи:


Приёмник получил значение 0x86. В этом есть смысл - это как раз младший байт CRC16/KERMIT. Так вышло, поскольку CRC у нас начал передаваться от старшего бита к младшему, а приёмник их воспринимает от младшего к старшему. Сойдёт...

И наконец, пытаемся передать второй байт CRC:


После получения 8 бит данных, на выходе защёлкнулось правильное значение 0x7F, но затем "что-то пошло не так". Сигнал HasOutput так и не сформировался, и можно отследить, почему: стоповый бит не был единичным, как это должно быть, ну и дальше мы продолжили передавать нолик.

Довольно быстро становится ясно, в чём же дело: при передаче данных, в сдвиговый регистр UART вдвигаются единицы, поэтому как только все данные будут переданы, мы "автоматом" получим стоповый бит и единичное значение на линии после передачи. Но когда isCRC=1, мы продолжили защёлкивать значения из модуля CRC, а там в сдвиговый регистр вдвигались сплошные нули, вот они и выползли. Нам и на первом байте повезло, что следующий на очереди бит был единичным, иначе мы бы и первый байт нормально не приняли!

Как ни обидно, однако мы должны выследить два последних состояния, sB8 = 1_110 и sStop = 1_111, и именно на них присоединить SR[0] назад к "родному" сдвиговому регистру, к SR[1]. Если бы речь шла только о sStop, мы бы воспользовались выходом cout счётчика. Так что нужно "честно" проверить 3 старших бита, чтобы все были единичными:
SR[0] <= (startTX & isIdle)? 1'b0 : (isCRC & (State[3:1] != 3'b111))? CRC_bit : SR[1];


Можно ещё было вот так записать:
SR[0] <= (startTX & isIdle)? 1'b0 : (isCRC & ~&State[3:1])? CRC_bit : SR[1];


Или, чтобы уж точно никто не догадался, вот так:
SR[0] <= (startTX & isIdle)? 1'b0 : (isCRC &~& State[3:1])? CRC_bit : SR[1];


Несчастный сотрудник, которому доведётся разбираться в этом коде, решит, что это какой-то очень специфический оператор &~&, полезет в мануалы, перероет всё, но ничего не найдёт. Но оно, сцуко, синтезируется и правильно выполняется :)

На самом деле, самый правый & имеет название "reduction &", то что я раньше назвал "горизонтальным &", такова была терминология в инструкциях SSE, AVX и иже с ними. Например, &State[3:1] - это то же самое, что State[3]&State[2]&State[1]. Результатом становится один бит. От него берётся инверсия с помощью ~, и наконец делается & между результатом и левым операндом, isCRC :)

В любом случае, теперь оно работает правильно:



Ладно, начало неплохое, при передаче CRC ведёт себя прилично. Воспользовались "недокументированным оператором верилога" &~& и ужаснулись возможностям по обфускации кода, которые он открывает :)

Теперь осталось посмотреть работу CRC на приёме, а потом сделать "схему", работающую и на приём, и на передачу.
Tags: ПЛИС, работа, странные девайсы
Subscribe

  • Лестница для самых жадных

    В эти выходные побывал на даче, после 3-недельной "самоизоляции". Забавно, как будто зима началась! Особенно грязные галоши остались на улице, в…

  • Возвращаемся к макету

    Очень давно макетом видеоизмерителя параметров сближения не занимался: сначала "громко думал" по поводу измерения его положения на аппарате, а потом…

  • Минутка живописи

    В процессе разгребания содержимого квартиры (после нескольких ремонтов) дошёл, наконец, и до картин. В кои-то веки их повесил. Куда их вешать -…

  • Костыль ноутбуку и кабели Франкенштейна (это не я!)

    Продолжаю разгребать хлам, накопившийся за много лет, раз уж всё равно на карантине сижу. Довольно продуктивный день: починил ноутбук (грубо, но…

  • Атомный файлсервер, убитые петухи и харакири андроида

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

  • Ещё про яркий светильник и ИБП

    Всё-таки применил лежащее дома барахло наиболее простым образом: А ещё наконец-то замерял температуру теплоотвода яркого светильника на кухне,…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 6 comments