Мучаем 5576ХС4Т - часть 'hF - модуль передатчика МКО

Nov 15, 2018 03:18

Часть 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 - уравновешенный четверичный умножитель

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

А сейчас - потихоньку приступим к написанию модулей на verilog, которые в совокупности называют протокольным контроллером МКО.

Для начала - передатчик. Один модуль будет передавать отдельные слова, второй - управлять первым для передачи целых сообщений, определённым образом размещённых в памяти.




Ещё пару слов о протоколе МКО.



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

Устройства делятся на 3 класса:
- контроллер шины (КШ, Bus Controller, BC). Именно он начинает любой информационный обмен, благодаря чему коллизии исключены. В каждый момент времени контроллер шины должен быть ровно один. Если есть резервное устройство, оно должно сидеть в качестве оконечного устройства или монитора шины, и только заметив, что настала гробовая тишина (пока контроллер шины не скажет - остальные помалкивают) - взять управление на себя.

- оконечное устройство (ОУ, Remote Terminal, RT). Не самый удобный термин, раз за разом мучаюсь, пытаясь отделить просто устройства, сидящие на шине, и совсем-совсем оконечное устройство, в котором ещё и установлен "терминатор" (резистор 75 Ом). В общем-то, все остальные устройства, которые принимают активное участие в информационном обмене. Всего их может быть до 32 штук, а желательно 31, чтобы адрес 5'b11111 можно было использовать для групповых (широковещательных) сообщений. Каждому присваивается свой адрес.

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

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



Благодаря этому сигнал заведомо имеет нулевую постоянную составляющую, и никогда на линии не возникает длинного положительного или отрицательного импульса, какие бы данные мы ни передавали. Благодаря этому линию можно гальванически развязать с помощью трансформатора очень скромных габаритов: хватит ферритового колечка К10х6х3 с десятком витков на каждую обмотку. Промышленно выпускаемые ТИЛ6В для поверхностного монтажа - и того меньше, напоминая скорее микросхему, а не трансформатор:



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



Благодаря этому, его ни с чем не спутаешь, что очень полезно, поскольку стандарт требует, чтобы слова, относящиеся к одному сообщению, передавались вообще без пауз.

Слова бывают трёх разных типов:


- Командное слово - его передаёт контроллер шины. Его содержимое жестко определяется стандартом: в начале идёт положительный синхроимпульс, затем 5 бит адреса оконечного устройства, которому адресовано сообщение (адрес 31 может означать групповое сообщение, а может не означать - как договоришься), один бит - "приём/передача". Если здесь стоит единичка, значит мы запрашиваем данные у оконечного устройства, если нолик - сами передадим ему данные сразу после командного слова. Затем идёт 5 бит "подадреса", который, как правило означает не адрес как таковой, а собственно наименование команды - что именно мы просим у оконечного устройства. Следующие 5 бит - количество слов данных, которые должны отправить либо мы, либо должны отправить нам, в зависимости от бита "приём/передача". Количество слов данных - от 1 до 32. Значения от 1 до 31 интерпретируются "как есть", а все нули (нулевое значение) означает 32. Завершается слово битом чётности, который выставляется так, чтобы количество всех "единичек" в сообщении (16 бит данных + чётность) получалось нечётной.

Также есть специальные "подадреса" 0 и 31, которые означают служебные команды (Mode Codes) самого протокола МКО. Код команды в этом случае хранится в поле "количество слов данных". Все эти команды описаны в ГОСТ Р 52070-2003 и в Mil-Std1553B. Некоторые из них описаны однозначно (например, передать ОС, блокировать/разблокировать передатчик), другие допускают простор для толкования, например, "синхронизация", "начать самоконтроль ОУ" или "получить векторное слово".

- Ответное слово - это всегда первое слово в ответе оконечного устройства. Всегда (за исключением групповых сообщений), когда контроллер шины отправляет сообщение, устройство, которому оно адресовано, обязано с задержкой 2..12 мкс послать ответное слово, сопровождаемое (если надо) словами данных. Ответное слово также начинается с положительного синхроимпульса, за ним идёт собственный адрес (ведь у контроллера шины нет своего адреса, он Контроллер Шины, и этого достаточно) и различные биты состояния. Можно немножко башку сломать, пытаясь разобраться, в каком случае какие из них отсылать, и чаще всего никто не заморачивается и просят высылать все нули. Хотя определённая логика там прослеживается, про неё расскажем позже. Завершается слово битом чётности, работает так же, как в командном слове.

- Слово данных - оно начинается с ОТРИЦАТЕЛЬНОГО синхроимпульса (см рисунок), а за ним уже следуют произвольные 16 бит данных и бит контроля чётности.



Это пока всё, что нам нужно знать, чтобы начать работу над модулями передатчика.

Передатчик слов
Будем действовать по аналогии с передатчиком UART, когда один модуль передаёт конкретный байт, а для формирования сообщения им управляет второй модуль.

Правда, здесь мы доверим передатчику слов ещё одну важную функцию - он сам, по завершению первого слова, перевернёт синхроимпульс, чтобы обозначить, что за командным/ответным словом идут именно слова данных. Это на самом деле не всегда так: стандартами предусмотрены довольно упоротые варианты передачи данных, когда контроллер шины отправляет первое командное слово одному оконечному устройству (ОУ), чтобы оно приготовилось принять данные, но потом вместо самих данных отправляет второе командное слово второму оконечному устройству, на передачу данных! В ответ на это, второе оконечное устройство присылает ответное слово ("вас понял"), сопровождаемое словами данных, а по окончанию этой передачи должно откликнуться ответным словом первое оконечное устройство ("данные принял!"). Это так называемый формат 3 передачи данных (ОУ-ОУ), используется ли он в реальности - не знаю, пока что не натыкался. Он показывает, зачем нужно делать синхроимпульсы разной полярности - первое ОУ, получив команду на приём данных, должно "настроится" на приём именно слов данных, и начисто проигнорировать второе командное слово, которое адресовано вовсе не ему! Будь все синхроимпульсы одинаковыми, эти посылки стали бы неотличимыми.

Но мы пока сделаем вид, что такого формата не существует :) Говоря языком учебников, разработать более универсальный передатчик, поддерживающий и этот формат, оставим читателю в качестве несложного самостоятельного упражнения :) (подсказка: передатчик слов даже упростится от этого, правда, передатчик сообщений раздуется)

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

module MyOwn1553_TXD( input clk,
input [15:0] data,
input start,
output TXP,
output TXN,
output reg AlmostReady = 1'b0);

clk - тактовая частота,
data - информативные 16 бит слова для передачи. Синхроимпульс и чётность мы уж сами сообразим!
start - единичный импульс для запуска передатчика.
TXP - положительный выход передатчика,
TXN - отрицательный выход передатчика. Как правило (и у нас будет именно так!), положительный и отрицательный выходы будут управлять двумя выходными транзисторами, включенными в две половинки обмотки трансформатора ("тянитолкай", push-pull). Во время передачи данных эти выходы будут в противофазе, а во время ожидания - оба сидеть в "нуле".
AlmostReady - единичный импульс, который выдаётся, когда 16 бит уже переданы, и сдвиговый регистр уже можно загружать повторно, а в данный момент идёт передача бита чётности. Потому и называется "почти готово". Это понадобилось, поскольку стандарт запрещает делать паузы между словами, они должны идти одно за другим. Вот мы и даём управляющей логике немножко тактов, чтобы вовремя подсуетиться. И если в течение передачи бита чётности мы получим запускающий импульс, мы начнём передавать следующее слово, уже с отрицательным синхроимпульсом (как слово данных). Если же запускающий импульс не придёт, и мы перескочим в режим ожидания, то в следующий раз начнём с положительным синхроимпульсом, зная, что любое сообщение начинается с командного слова или слова данных.

Теперь опишем наш конечный автомат. Как всегда, у передатчика он мало отличается от счётчика, и именно так мы его здесь исполним. Застреваем в состоянии sIdle, перескакиваем из него в sStart по приходу запускающего импульса, а оттуда идём последовательно: передача синхроимпульсов, передача 16 бит данных, передача бита чётности. Наконец, оттуда мы либо возвращаемся в режим ожидания, либо снова перескакиваем в sStart.

Реализуем его в виде отдельного модуля на основе lpm_counter, а пока опишем "окружение", куда он вставится:

//
//State machine
//
localparam sIdle = 6'b000000;
//big gap which we never reach
localparam sStart = 6'b011000; //begin of sync pulse
localparam sB2 = 6'b011001; //second half-bit of sync pulse
localparam sSwapSync = 6'b011010; //third half-bit, after which we change polarity
//3 states corresponding to second sync pulse here
localparam sFirstDataBit = 6'b011110;
localparam sParityBit = 6'b111110;

wire [5:0] State;

wire isIdle = (State[5:4] == 1'b0); //2 senior bits are enough to check this
wire TimeToSwapSync = (~State[5]) & (State[2:0] == 3'b010) & ce;
wire TimeForData = (~State[5]) & (State[2:0] == 3'b101) & ce; //also swap sync once again if this is data word
wire TimeForParity = (State == 6'b111101) & ce; //no shortcuts it seems

reg DoDataBits = 1'b0; //could be done combinatoric, but probably better so
reg DoParity = 1'b0; //could be done combinatoric, but probably better so

wire isLastState;

По сути, у нас здесь 41 состояние - ожидание, плюс передача 40 "половинок" бит. sIdle стоит первым (все нули), чтобы не было проблем с инициализацией. Остальные отнесены на 24 позиции, чтобы окончание передачи соответствовало значению 63 (все единицы), что очень удобно - мы опять полезно используем выход Carry Out, и кроме того, из него мы "автоматом" вернёмся в режим ожидания.

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

А чтобы не генерировать страшных комбинаторных выражений наподобие (State > 6'b011101) && (State < 6'b111110), соответствующих моменту, когда мы передаём данные, мы ввели пару регистров: DoDataBits, включающийся, когда мы достигаем значения 6'b011101 и готовимся переключиться дальше (т.е уже пришел новый импульс ce), и выключающийся, когда мы достигаем значения 111101 и готовы переключиться дальше. Точно так же DoParity включается по значению 111101 и отключается по сигналу CarryOut, который мы здесь назвали IsLastState.

Логические выражения isIdle, TimeToSwapSync и TimeForData удалось чуть упростить, зная, что состояний 6'b000001 .. 6'b010111 (от 1 до 23) мы никогда не достигаем. (а если даже в результате тяжелой заряженной частицы или метастабильного состояния мы туда попали, то сможем выбраться "своим ходом").

Описываем модуль счётчика для нашего регистра состояния:

module MilStd1553TXstateCounter (input clk, input ce, input sset, output [5:0] Q, output isLastState);

lpm_counter counter (
.clock (clk),
.cnt_en (ce),
.sset (sset),
.q (Q),
.cout (isLastState) );
defparam
counter.lpm_direction = "UP",
counter.lpm_port_updown = "PORT_UNUSED",
counter.lpm_type = "LPM_COUNTER",
counter.lpm_width = 6,
counter.lpm_svalue = 6'b011000;
endmodule

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

Добавляем этот модуль к себе:

MilStd1553TXstateCounter FSM ( .clk (clk),
.ce (ce),
.sset ((isIdle & start) | (isLastState & ce & DoRestart)),
.Q (State),
.isLastState (isLastState));

Именно здесь мы описали логику запуска: либо мы были в режиме ожидания и получили запускающий импульс на входе, либо уже целиком передали всё и помним, что запускающий импульс приходил, значит, данные уже "защёлкнуты" и можно действовать. Упростить это выражение проблематично, поскольку очень уж специфический момент нам нужно выбрать для перезапуска!

Опишем поведение регистров DoDataBits и DoParity в always-блоке:

DoDataBits <= TimeForData? 1'b1 : TimeForParity? 1'b0 : DoDataBits;
DoParity <= TimeForParity? 1'b1 : (isLastState & ce)? 1'b0 : DoParity;

По сути, они работают как старые добрые RS-триггеры: один сигнал защёлкивает в них единичку, другой - нолик.

Вводим регистры DoRestart и isDataWord:

reg DoRestart;

reg isDataWord;

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

isDataWord устанавливается в единичку на пару с DoRestart, но вот сбрасывается только при переходе в режим ожидания. Именно этот регистр подсказывает нам полярность синхроимпульса.

Внутри always-блока лежит следующий код для них:

DoRestart <= ~DoParity? 1'b0 : start? 1'b1 : DoRestart;
isDataWord <= isIdle? 1'b0 : DoRestart? 1'b1 : isDataWord;

Опять по сути RS-триггеры.

Не забудем и про делитель частоты:

//
//Frequency divider
//
parameter CLKfreq = 80_000_000;
localparam Mil1553freq = 2_000_000; //2 MHz, twice the actual, to handle manchester code

localparam Limit = CLKfreq / Mil1553freq - 1;
localparam DividerBits = `CLOG2(CLKfreq / Mil1553freq);

reg [DividerBits - 1 : 0] FreqDiv;
wire ce = ((FreqDiv & Limit) == Limit);

В этот раз мы даже не стали выводить наружу скорость передачи данных, зная её заранее - 1 МГц. Мы же делим до 2 МГц, для реализации фазоманипулированного кода.

В always-блок следующий код для делителя частоты:

FreqDiv <= (isIdle | ce)? 1'b0 : FreqDiv + 1;

То есть, как и раньше, наш счётчик остановлен, пока не придёт запускающий импульс, и мы можем начать передачу сразу же. Есть варианты исполнения, когда частота 2 МГц генерируется "сама по себе" (как, например, в микроконтроллерах), и первым делом при запуске мы должны дождаться фронта этого сигнала, и начинать уже оттуда.

Для генерации синхроимпульсов мы вводим регистр Sync:

reg Sync;

(удивительно, правда?)

Вот код для него в always-блоке:

Sync <= isIdle? 1'b1 : ce & (TimeToSwapSync | (TimeForData & isDataWord))? ~Sync : Sync;

В режиме ожидания он сбрасывается в единицу - ведь именно с единицы и должна начинаться передача.

Переключаться он согласен только в моменты времени "на границе между битами", т.е по приходу импульса ce. Он всегда переключается в середине 2-го бита, а ещё мы его "возвращаем" назад между словами данных. То есть, при передаче первого слова он как сбросился с единицы в ноль, пусть там и остаётся, пока не переключится с ноля в единицу уже при передаче синхроимпульса в слове данных. Но вот к следующему слову данных он должен опять вернуться в ноль, что мы и делаем вторым условием.

Следующая наша забота - передача 16 бит данных, для чего служит сдвиговый регистр:

reg [15:0] SR;

Код для него внутри always-блока:

SR <= start? data :
(DoDataBits & State[0] & ce)? SR << 1:
SR;

По приходу стартового импульса мы защёлкиваем новые данные. Когда мы начали передавать данные (уже прошёл синхроимпульс), то на каждой второй половинке бита мы сдвигаем данные влево. Мне привычнее отсчитывать биты справа налево, а здесь, в отличие от UART, данные передаются старшим битом вперёд, поэтому мы каждый раз извлекаем как раз-таки старший бит 15, и данные сдвигаются влево, чтобы на его место вставал следующий.

Осталось описать всего один регистр, бит чётности:

reg ParityBit; //we very much don't want it combinatoric! (that's combining all 16 bits at once, horrible solution, though it's just 4 extra LE)

И его код внутри always-блока:

ParityBit <= TimeForData? 1'b1 : (DoDataBits & State[0] & ce)? ParityBit ^ SR[15] : ParityBit;

Итак, непосредственно после синхроимпульса мы устанавливаем бит чётности в единицу. Дальше, в момент передачи каждого из битов мы переворачиваем бит чётности, если передаётся единичный бит, т.е производится операция "исключающее ИЛИ", XOR. Как результат, у нас выходят все 16 бит, объединённые по XOR, и ещё с инверсией, чтобы вместе с битом чётности мы всегда получали нечётный результат.

Мы описали весь код, определяющий "внутреннее состояние" схемы. Осталось только сформировать выходы.

Вот соответствующий код:

wire OutputSig = DoDataBits? (SR[15] ^ State[0]) : DoParity? (ParityBit ^ State[0]) : Sync;

assign TXP = isIdle? 1'b0 : OutputSig;
assign TXN = isIdle? 1'b0 : ~OutputSig;

OutputSig определяет сигнал на положительном выходе на всём протяжении передачи данных. Когда это 16 "информативных" бит, мы передаём старший бит сдвигового регистра, объединённый по XOR с младшим битом счётчика, в точности повторяя принцип построения Манчестерского кода, как показано на рисунке выше. Точно также, при передачи бита чётности, мы делаем Манчестерский код из него, ну а если это не передача данных и не чётность, значит, методом исключения, надо передавать синхроимпульс. Он у нас уже готов, на блюдечке с голубой каёмочкой.

Остаётся только выдать OutputSig противофазно на TXP и TXN при передачи данных, и нули в режиме ожидания.

Вишенка на торте: выход AlmostReady:

AlmostReady <= TimeForParity;

Мы сделали его регистром, чтобы можно было соединить AlmostReady напрямую (комбинаторно) с нашим входом start, и оно сработало как надо. Дело в том, что TimeForParity срабатывает одним тактом раньше, чем схема начинает проверять поступление стартового импульса. Кроме того, осциллограмма (в самом верху поста) становится красивой, без "комбинаторного мусора".

Соберём этого монстра воедино:

`include "math.v"

module MyOwn1553_TXD( input clk,
input [15:0] data,
input start,
output TXP,
output TXN,
output reg AlmostReady = 1'b0);
//
//State machine
//
localparam sIdle = 6'b000000;
//big gap which we never reach
localparam sStart = 6'b011000; //begin of sync pulse
//2 other states corresponding to first sync pulse
localparam sSwapSync = 6'b011011; //when it changes polarity
//2 other states corresponding to second sync pulse
localparam sFirstDataBit = 6'b011110;
localparam sParityBit = 6'b111110;

wire [5:0] State;

wire isIdle = (State[5:4] == 1'b0);
wire TimeToSwapSync = (~State[5]) & (State[2:0] == 3'b010) & ce;
wire TimeForData = (~State[5]) & (State[2:0] == 3'b101) & ce;
wire TimeForParity = (State == 6'b111101) & ce;

reg DoDataBits = 1'b0;
reg DoParity = 1'b0;
wire isLastState;
MilStd1553TXstateCounter FSM ( .clk (clk),
.ce (ce),
.sset ((isIdle & start) | (isLastState & ce & DoRestart)),
.Q (State),
.isLastState (isLastState));
//
//Frequency divider
//
parameter CLKfreq = 80_000_000;
localparam Mil1553freq = 2_000_000; //2 MHz, twice the actual, to handle manchester code
localparam Limit = CLKfreq / Mil1553freq - 1;
localparam DividerBits = `CLOG2(CLKfreq / Mil1553freq);
reg [DividerBits - 1 : 0] FreqDiv;
wire ce = ((FreqDiv & Limit) == Limit);

reg DoRestart;
reg isDataWord;
reg Sync;
reg [15:0] SR;
reg ParityBit;

always @(posedge clk) begin
FreqDiv <= (isIdle | ce)? 1'b0 : FreqDiv + 1;
DoRestart <= ~DoParity? 1'b0 : start? 1'b1 : DoRestart;
isDataWord <= isIdle? 1'b0 : DoRestart? 1'b1 : isDataWord;
Sync <= isIdle? 1'b1 : ce & (TimeToSwapSync | (TimeForData & isDataWord))? ~Sync : Sync;
DoDataBits <= TimeForData? 1'b1 : TimeForParity? 1'b0 : DoDataBits;
SR <=start? data :
(DoDataBits & State[0] & ce)? SR << 1:
SR;
DoParity <= TimeForParity? 1'b1 : (isLastState & ce)? 1'b0 : DoParity;
ParityBit <= TimeForData? 1'b1 : (DoDataBits & State[0] & ce)? ParityBit ^ SR[15] : ParityBit;
AlmostReady <= TimeForParity;
end
wire OutputSig = DoDataBits? (SR[15] ^ State[0]) : DoParity? (ParityBit ^ State[0]) : Sync;
assign TXP = isIdle? 1'b0 : OutputSig;
assign TXN = isIdle? 1'b0 : ~OutputSig;
endmodule

Синтезируется эта штука в 54 логические ячейки (LE). Учитывая, что одних лишь регистров у нас на 35 бит:
- 16 бит сдвиговый регистр для данных
- 6 бит регистр состояния
- 6 бит регистр делителя частоты (с 80 МГц в 2 МГц)
- Мелочь пузатая (по 1 биту): DoRestart, isDataWord, Sync, DoDataBits, DoParity, DoParityBit, AlmostReady, итого ещё 7.

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

Вот как бы и всё. На скриншоте в самом начале поста показано, как оно работает. Чтобы получить его, я добавлял дополнительные выходы DState, DDoRestart и пр. (D значит Debug), а когда убедился, что всё работает как надо - удалил их. К сожалению, не знаю, как можно заставить Quartus показать "внутренние цепи" модуля во время симуляции, ну да ладно, мы не гордые.

Традиционная надежда: если знаете более компактный код, и если он не составляет военной или коммерческой тайны, пришлите его, пожалуйста, с меня пиво и орешки, а можно тортик.

странные девайсы, ПЛИС, работа

Previous post Next post
Up