nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

Мучаем 5576ХС4Т - часть 'h10 - передатчик сообщений МКО

Часть 0 - покупаем, паяем, ставим драйвера и софт
Часть 1 - что это вообще за зверь?
Часть 2 - наша первая схема!
Часть 3 - кнопочки и лампочки
Часть 4 - делитель частоты
Часть 5 - подавление дребезга кнопки
Часть 6 - заканчиваем кнопочки и лампочки
Часть 7 - счетчики и жаба
Часть 8 - передатчик UART
Часть 9 - Hello, wolf!
Часть 'hA - приёмник UART
Часть 'hB - UART и жаба
Часть 'hC - полудуплексный UART
Часть 'hD - МКО (МКИО, Mil-Std 1553) для бедных, введение
Часть 'hE - приёмопередатчик МКО "из подручных материалов" (в процессе)
Часть 'hF - модуль передатчика МКО
Часть 'h10 - передатчик сообщений МКО
Часть 'h20 - работа с АЦП ADC124s051
Часть 'h21 - преобразование двоичного кода в двоично-десятичный (BCD)
Часть 'h22 - Bin2Bcd с последовательной выдачей данных
Часть 'h23 - перемножитель беззнаковых чисел с округлением
Часть 'h24 - перемножитель беззнаковых чисел, реализация
Часть 'h25 - передаём показания АЦП на компьютер
Часть 'h26 - работа над ошибками (быстрый UART)
Часть 'h27 - PNG и коды коррекции ошибок CRC32
Часть 'h28 - передатчик изображения PNG
Часть 'h29 - принимаем с ПЛИС изображение PNG
Часть 'h2A - ZLIB и коды коррекции ошибок Adler32
Часть 'h2B - ускоряем Adler32
Часть 'h2C - формирователь потока Zlib
Часть 'h2D - передаём сгенерированное PNG-изображение
Часть 'h2E - делим отрезок на равные части
Часть 'h2F - знаковые умножители, тысячи их!
Часть 'h30 - вычислитель множества Мандельброта
Часть 'h31 - ускоренные сумматоры
Часть 'h32 - ускоренные счётчики (делаем часы)
Часть 'h33 - ускоряем ВСЁ
Часть 'h34 - ускоренные перемножители
Часть 'h35 - умножители совсем просто
Часть 'h36 - уравновешенный четверичный умножитель


В прошлый раз мы написали код для модуля передатчика слов МКО, который автоматически менял полярность синхроимпульса: в первом слове он положительный, во всех последующих (передающихся без паузы) - отрицательный, что соответствует наиболее ходовой ситуации - сначала идёт командное/ответное слово, затем слова данных.

Сейчас дополним его модулем для передачи целых сообщений, пока что применительно к контроллеру шины.

По ходу дела задействуем ключевые слова generate / endgenerate языка verilog. Как оказывается, Quartus II их понимает и позволяет применять в модулях для синтеза.

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



Когда мы передавали строки по UART, мы гнали байт за байтом, пока не встречали нулевой байт - он давал нам подсказку, что строка закончена. Это так называемый символьный режим работы UART, удобный тем, что не требует написания специализированного софта, открываешь терминал и "общаешься".

В случае контроллера шины МКО, нулевое слово как таковое не нужно - если продолжить делать вид, что формата 3 (ОУ-ОУ) не существует, т.е командное слово всегда ровно одно, то нам вполне хватит информации, сидящей в первом, командном слове, чтобы понять, сколько ещё слов требуется передать.

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

Вот заголовок нашего модуля "контроля передачи":

module MilStdTxMessageControl (	input clk,
                                input [AddrWidth - 1 : 0] InitAddr,
                                input start,
                                input AlmostReady,
                                input [15:0] Data,
				output reg [AddrWidth - 1 : 0] OutAddr,
                                output StartTx);

parameter AddrWidth = 8;


clk - совершенно загадочный вход :)
InitAddr - начальный адрес сообщения в памяти. Здесь мы подразумеваем 16-битную адресацию, что при работе с МКО очень удобно. Кроме того, по умолчанию мы взяли ширину адресной шины 8 бит, что позволяет задействовать в точности один блок памяти ПЛИС размером 512 байт (у 5576ХС4Т их 24 штуки),
start - единичный импульс запуска. Именно в момент его прихода мы "защёлкиваем" содержимое InitAddr и начинаем работу,
AlmostReady - через этот вход мы получаем отмашку от передатчика слов МКО, что он почти что передал предыдущее слово и уже готов ознкомиться со следующим,
Data - сюда поступает 16-битное слово из памяти. Нам оно нужно, чтобы определить длину сообщения,
OutAddr - шина адреса, поступающая в блок памяти, через неё мы запрашиваем определённое слово,
StartTx - через этот выход мы запускаем передатчик слов МКО.

Для начала реализуем комбинаторную логику, которая из 16 бит командного слова определяет, сколько же слов нам надо передать?

Задача на удивление разветвлённая!

Код, рассматривающий все ситуации, описанные в стандарте, выглядит так:

  //wire [4:0] RTaddr = Data[15:11]; //not needed so far (will be later)
  wire DoWeReceiveData = Data[10]; 
  wire [4:0] SubAddr = Data[9:5];
	
  wire [5:0] RawWordCount = {(Data[4:0] == 1'b0) , Data[4:0]};

  wire IsModeCommand = (SubAddr == 5'b00000) | (SubAddr == 5'b11111);

  wire [WordCounterWidth - 1 : 0] WordCountToTransmit = DoWeReceiveData? 1'b0 :IsModeCommand? Data[4] : RawWordCount;


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

Но число слов данных мы записываем наконец-то в виде 6 бит, и преобразуем особый случай, когда нулевое значение на самом деле значит 32 слова. Снова применяем фигурные скобки, означающие конкатенацию - сначала старший бит, потом все остальные.

Итак, если должны принимать слова данных (бит "приём/передача" равен единице), то сразу говорим - кроме командного слова, нам ничего передавать и не нужно!

В противном случае, казалось бы, передаём столько, сколько написано в поле "число слов данных". Не тут-то было! Не забываем и про служебные команды МКО (mode code), которые кодируются подадресом 00000 или 11111, а в поле "число слов данных" вместо этого размещается код команды. Команды, код которых начинается с нуля, не требуют слова данных, а те, чей код начинается с единицы - передают ровно одно слово данных.

Вот теперь весь наш код должен обрести смысл! Но до чего же он громоздкий, для столь пустяковой задачи! Хочешь-не хочешь, а должен проанализировать практически ВСЕ биты, входящие в командное слово. Если оформить этот код в виде отдельного модуля, он займёт 11 логических ячеек.

Может, и ничего, но мы же ОЧЕНЬ жадные!!! А что, если мы половины этих возможностей стандарта вообще использовать не будем? К примеру, я хочу подключить к своей ПЛИС изделие ВОБИС (http://optolink.ru/ru/products/three_axis_fog/vobis), изучил ТУ на него и понял: более 1 слова данных отправлять на него никогда не требуется! Основная команда - запрос состояния - не имеет слов данных. В команде включения имитации (эта команда была введена для того, чтобы во время испытаний можно было измерить полосу пропускания измерительного канала) передаётся ровно одно слово данных, в служебных командах "блокировать/разблокировать передатчик" - ни одного.

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

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

Директивы препроцессора, очередные `define, `ifdef и пр. использовать не хочется - некрасиво.

Для того, чтобы похожего эффекта достичь, не прибегая к препроцессору, генерить различный код в зависимости от выставленных параметров - и нужна директива generate. Вот как оно примерно выглядит:

parameter DoBotherWithModeCode = 1;
	
parameter MaxWordCount = 32; //simplifies logic and counter a little.

localparam WordCounterWidth = `CLOG2(MaxWordCount + 1);

generate
  if (MaxWordCount == 32) begin
    wire [5:0] RawWordCount = {(Data[4:0] == 1'b0) , Data[4:0]};
  end
  else begin
    wire [WordCounterWidth - 1 : 0] RawWordCount = Data[WordCounterWidth - 1 : 0];
  end
  if (DoBotherWithModeCode == 1) begin
    wire [WordCounterWidth - 1 : 0] WordCountToTransmit = DoWeReceiveData? 1'b0 :IsModeCommand? Data[4] : RawWordCount;
  end
  else begin
    wire [WordCounterWidth - 1 : 0] WordCountToTransmit = DoWeReceiveData? 1'b0 : RawWordCount;
  end
endgenerate


Внутри generate можно объявлять регистры и провода, и даже вставлять модули (разные, в зависимости от условий).

Мы сделали так: только если мы зададим, что возможны все 32 слова данных, будет обработан отдельный случай с нулевым значением. И только если мы собираемся пользоваться служебными командами, мы будем их рассматривать.

Понятно, здесь это было не так уж принципиально, однако в целом в протоколе МКО есть очень много отдельных мест, каждое по отдельности вроде не так сложно, однако если попытаться реализоваться ВСЕ возможности контроллера шины, оконечного устройства и монитора шины В ОДНОМ УСТРОЙСТВЕ, оно довольно хорошо расползётся, как по размеру, так и по сложности работы с ним. За примерами далеко ходить не надо - микросхема 1895ВА2Т, протокольный контроллер МКО, имеет руководство по эксплуатации на 291 страницу, где языком наподобие моего здесь, только ещё более "сухого", с признаками бездумного перевода с английского, описываются все конфигурационные регистры, эффекты, которые они оказывают на работу, и допустимые их комбинации.

Чтобы не создать монстра, мы и будем пытаться делать все "навороты" опциональными.

Приведём теперь весь код нашего модуля MilStdTxMessageControl:
`include "math.v"

module MilStdTxMessageControl (input clk,
                               input [AddrWidth - 1 : 0] InitAddr,
                               input start,
                               input AlmostReady,
                               input [15:0] Data,
			       output reg [AddrWidth - 1 : 0] OutAddr,
                               output StartTx);
								
  parameter AddrWidth = 8;
	
  parameter DoBotherWithModeCode = 1;
	
  parameter MaxWordCount = 32; //simplifies logic and counter a little.
	
  localparam WordCounterWidth = `CLOG2(MaxWordCount + 1);
	
  //wire [4:0] RTaddr = Data[15:11]; //not needed so far (will be later)
  wire DoWeReceiveData = Data[10]; 
  wire [4:0] SubAddr = Data[9:5];
	
  wire IsModeCommand = (SubAddr == 5'b00000) | (SubAddr == 5'b11111);
	
  generate
    if (MaxWordCount == 32) begin
      wire [5:0] RawWordCount = {(Data[4:0] == 1'b0) , Data[4:0]};
    end
    else begin
      wire [WordCounterWidth - 1 : 0] RawWordCount = Data[WordCounterWidth - 1 : 0];
    end
    if (DoBotherWithModeCode == 1) begin
      wire [WordCounterWidth - 1 : 0] WordCountToTransmit = DoWeReceiveData? 1'b0 :IsModeCommand? Data[4] : RawWordCount;
    end
    else begin
      wire [WordCounterWidth - 1 : 0] WordCountToTransmit = DoWeReceiveData? 1'b0 : RawWordCount;
    end
  endgenerate
	
  localparam sIdle = 2'b00;
  localparam sFetch = 2'b01;
  localparam sSetup = 2'b10;
  localparam sTransmit = 2'b11;
	
  reg [1:0] State = sIdle;
	
  reg [WordCounterWidth - 1 : 0] WordsLeft;
	
  wire IsOver = (WordsLeft == 0);

  always @(posedge clk) begin
    OutAddr <= start? InitAddr : StartTx? OutAddr + 1'b1 : OutAddr;
    WordsLeft <= (State == sSetup)? WordCountToTransmit : StartTx? WordsLeft - 1'b1 : WordsLeft;
    State <= 	(State == sIdle)?  start : //either stay at idle, or go to Fetch
		(State == sFetch)? sSetup :
		(State == sSetup)? sTransmit :
		IsOver? 	   sIdle : sTransmit;
		
  end
  assign StartTx = State[1] & (~State[0] | AlmostReady);												
endmodule


Здесь мы подразумеваем память с задержкой в один такт: мы выдаём адрес, только к следующему такту он защёлкивается, и к середине следующего такта (когда все значения на входах регистров должны установиться) мы получаем запрошенные данные. Как было показано в главе 9 - hello, Wolf, на частоте 80 МГц это вполне реализуемо. (совсем без защёлок - не срабатывает, а с одной защёлкой - вполне себе).

Происходит следующее. Сначала мы находимся в режиме ожидания, ничего не происходит. Но приходит стартовый импульс и начальный адрес InitAddr - и мы защёлкиваем этот адрес в регистр OutAddr и переходим в режим sFetch. В нём ничего не происходит, поскольку только появился адрес на входе блока памяти, мы просто ждём ответа. Сразу за Fetch мы переходим в режим sSetup - сразу же, комбинаторно, мы подаём команду на старт передатчика (поскольку уж одно слово, командное, нам передать точно надо!). Кроме того, у нас появляется комбинаторный сигнал WordCountToTransmit - сколько ЕЩЁ слов надо передать. Именно находясь в режиме sSetup, мы защёлкиваем это количество в регистр (счетчик) WordsLeft, и переходим в режим sTransmit.

Именно в режиме sTransmit мы будем сигналы AlmostReady переправлять напрямую в StartTx, тем же тактом, при условии, что надо передать ещё хотя бы одно слово. Вместе с этим импульсом StartTx мы прибавляем единичку к адресу (это происходило и первый раз, при передачи командного слова) и вычитаем единичку из счётчика оставшихся слов. Когда он становится нулём, у нас "зажигается" признак IsOver, который переведёт нас назад в режим ожидания, так что очередной импульс AlmostReady уже не пройдёт на вход, и передача прекратится, где и должна.

Наконец, собираем полную схему передатчика и памяти:



Память создаём через Mega-wizard, 256 x 16.

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

Если выставить параметры DoBotherWithModeCode = 1, MaxWordCount = 32 (т.е полная поддержка всех вариантов), эта штуковина синтезируется в 86 логических ячеек,
а при DoBotherWithModeCode = 0, MaxWordCount = 1 (либо ни одного слова, либо одно - как при нашей работе с ВОБИС) - в 69.


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

Recent Posts from This Journal

  • Так есть ли толк в ковариационной матрице?

    Задался этим вопросом применительно к своему прибору чуть более 2 недель назад. Рыл носом землю с попеременным успехом ( раз, два, три, четыре),…

  • Big Data, чтоб их ... (4)

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

  • Потёмкинская деревня - 2

    В ноябре 2020 года нужно было сделать скриншот несуществующей программы рабочего места под несуществующий прибор, чтобы добавить его в документацию.…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 6 comments