Введение
Так получилось, стал я обладателем борды, на которой есть все, кроме JTAG. Это означало, что такого софта как SignalTAP у меня нет. Отлаживать не получится. Подымать карту в слепую то еще занятие. Это как копаться в черном ящике в темной комнате. Было принято решение, начать писать логи на карте и передавать их в каком нибудь формате через Ethernet на компьютер. На основе этого был получен некоторый опыт, описать который захотелось. Сильно особо не критикуйте, эту тему я изучаю полностью с нуля, давалось и дается fpga (как и схемотехника) очень тяжело, когда нет специализированного образования.
ETHERNET
Вообще ethernet — это технология передачи данных. Жестко стандартизирована по IEEE. Подстандартов сейчас очень много, технологии не стоят на месте. Наверное слышали 10Base-T (10Мбит/сек), 100Base-T, 1000Base-T(1Гигабит/сек), ну или как на нашей бирже 10GBase… Самая сложность реализации на железе этих стандартов — это реализация физического уровня, то есть как передаваемые вами байты в сеть преобразовывать в электрические сигналы, используя кодировку и тд. Если поднять с нуля 10Base-T не представляется сложным, делал это без проблем даже без специальных magnetic на разъеме RJ45, то скорости повыше это задачка уже так себе. Ну еще можно без каких либо сильных сложностей поднять 100Мбит.
Поэтому разработчиками всякого железа было в свое время придумано засунуть реализацию физического уровня Ethernet в какую нибудь дешевую микруху, которая на себя возьмет полностью реализацию всего. Наверняка вы слышали таких производителей, как Realtek, Marvell и др. Использование таких микрух достаточно оправдано, за небольшие деньги вынести в отдельный модуль решение массы задач. Это гораздо упрощает решение задачи + нет нагрузки на саму fpga, освобождаются ресурсы под другие задачи.
Мне повезло. На моей борде стоит такой чип от Marvell.
RGMII
Так как я уже ранее сказал, что любая эта тема является жестко стандартизированной, то и работа с такими чипами тоже описана стандартами, а если быть корректней, то протоколами. Для передачи данных на физический уровень Ethernet были разработаны различные протоколы. Ну например: MII, SGMII, RGMII и тд. Их описание вы легко прочитаете в инете. Ну и тут мне повезло, мой Marvell поддерживает RGMII. Это значит, что я могу работать с ним на fpga на пониженных частотах (125Mhz), это повысит надежность кода, алгоритма и повысит стабильность выполнения задачи.
Я решил максимум отладить код в ModelSim, а потом пробовать его в слепую запускать на fpga. Кстати говоря, раньше давно я не дооценивал ModelSim, любил SignalTAp и отлаживать сразу на бою в рабочем состоянии. Как потом показал опыт, качественно отлаженный код в ModelSim запускается с первого раза на карте. В том числе и 1000Base-T я запустил на карте с первого раза, после недельной отладки в ModelSim. Это очень сильно экономит время, так как компилирование и прошивка fpga — это вам не c++ закомпилить. Это минут 15-20.
Все что вам нужно знать для отправки сетевого пакета на физический уровень сети через протокол RGMII, так это шесть сигналов: tx_clk, tx_ctrl, tx_0, tx_1, tx_2, tx_3. Качественно их подготовив и подав на микруху Marvell (ну или Realtek), вы получите результат => отправка битов в витую пару. Их описание:
—
tx_clk. Это сигнал генератора на частоте 125Mhz. Грубо говоря, если мы переведем герцы в наносекунды, то получим, что каждые 8наносек (125Mhz = 8наносек), мы просто должны подавать электрический сигнал (логическую 1) на ножку чипа Marvell
—
tx_ctrl. Это сигнал управления передачи. Если на эту ножку подаем логическую 1, то мы говорим микрухе, что мы передаем в сеть биты. И она начинает работу и передавать биты. Если мы на эту ножку ничего не подаем (логический 0), то микруха молчит.
-
tx_0, tx_1, tx_2, tx_3. — Это как раз значения передаваемых битов. Соответственно, если на какую то ножку мы подаем электрический сигнал, то значения соответствующего бита = 1.
Легко понять, что у нас всего 4 ножки под значения битов. То есть передаем 4 бита, а байты в компьютере состоят из 8 битов. Вот тут и вступают правила RGMII. В любом описании в инете вы найдете нечто похожий текст, что по переднему фронту сигнала tx_clk мы передаем первые 4 бита, по заднему фронту вторые 4 бита. Неподготовленному бойцу может стать и не понятно. Попробую нарисовать это:
Если нарисовать алгоритм RGMII блок схемой:
Все в принципе просто. Особенно когда переваришь тонну материалов в интернете. Но есть нюансы. Может получиться такая ситуация, и практически 100% вероятность, что она получится, когда вы подаете на ногу тактовый сигнал tx_clk, а сами ноги бит tx0..tx3 чуточку запаздывают, на считанные наносекунды. Это может вызвано несколькими причинами. Самим FPGA и вашим кодом, либо схемой платы, ее пайкой и тд. То есть передача сигналов по самой плате передаются с задержкой. Обычно производители плат в документации составляют целую таблицу задержек тех или иных сигналов по плате (особенно актуально, когда вы подымаете sfp+ или pci-express на карте). В связи с этим, принято сдвигать тактовый сигнал на величину задержки, а еще актуальней просто сдвигать его на 90 градусов, если схематично, то сдвинуть его вправо, пометил его зеленым на рисунке
Тогда гарантированно при подаче тактового сигнала tx_clk, микруха Marvell считает именно нужные биты со своих ног tx0..tx3 и tx_ctrl.
Ethernet кадры и UDP
Вообще не уверен что это тема этого топика. Поэтому совсем немного. Сеть Ethernet так устроена, что данные передаются кадрами, cформированными по определенным правилам. Вы не можете заслать в кабель через микруху любую последовательность байт и ожидать результата. Для начала нужно собрать ethrnet кадр по спец правилам и начать его передавать в кабель.
1) Для начала в кабель нужно заслать преамбулу кадра. Это просто 7 байт с шестнадцатеричным значением 55 (1010101 в двоичном).
2) Далее нужно заслать 1 байт разделителя после преамублы. Его значение D5 в Hex.
3) Далее нужно заслать в кабель 6 байт MAC адреса получателя
4) Далее нужно заслать в кабель 6 байт MAC адреса отправителя. Будьте тут внимательны, вбивайте правильно значение, чтобы не намаяться с отладкой.
5) Если у вас сеть, как на Мос бирже колокейшен, то нужно заслать заголовок VLAN
6) Далее идут данные кадра (TCP или UDP пакет)
7) И все это дело заканчивается контрольной суммой кадра. Это 4 байта. Правильно высчитанное значение контрольной суммы очень важно. Если сумма не корректна, если у вас ошибка кода, то ваш кадр просто не дойдет до получателя. Намучаетесь много.
Лично мне помогали вот эти две ссылки. Я в ModelSim выводил значения засылаемых в кабель данных и проверял тут правильность сгенерированных кадров в hex.
packetor.com/
www.gasmi.net/hpd/
Честно говоря, мне лень описывать этот раздел. Я уже к этой строке потратил полтора часа подготовки текста и лень меня одолевает. Если вам интересно будет, задайте просто вопрос, я расскажу.
Контрольная сумма
Код или модуль контрольной суммы нужно написать так, чтобы она пересчитывалась online, по мере поступления очередного байта в кабель. Ведь у нас FPGA и мы должны использовать все преимущества параллельных расчетов. Поэтому код нужно составить так, чтобы к моменту посылки контрольной суммы в кабель, она уже была рассчитана иначе ее расчет потянет за собой задержки и вы просто можете не уложиться в заданный такт tx_clk. Если будет интересен ее код или ее алгоритм, задайте вопрос, я тоже покажу.
Организация логов
Тут все очень просто. Задача очевидная. Нам нужно сохранять значения каких то сигналов или значения переменных или значения регистров с заданной частотой. А отправлять эти значения с конкретной частотой 125Mhz (скорость, на которой работает RGMII Marvell). В моих потребностях логи я пишу с частотой 100Mhz (1 раз в 10 наносекунд). Очевидно, что эту задачу может решить асинхронный буфер FIFO. В Quartus есть такая мегафункция. Ее просто нужно подключить и настроить.
Формат логов у меня пока очень простой. Первые 2 байта я использую под порядковый номер лога + еще 1 байт под значения восьми сигналов (1 байт = 8 бит). Получается, в асинхронный FIFO я сохраняю каждые 10наносек 24 бита (3 байта по 8 бит). И вытаскиваю из этого буфера эти 24 бита каждые 8 наносек и отправляю в сеть.
Очень упрощенно это выглядит так:
Принимающая сторона
Тут надо признать, я немного намучался. Скорость логов была такова, что даже зависала сеть, не говоря уже о подвисании компьютера. Слегка решил проблему, когда увеличил размер посылаемого лога в притирку к MTU = 1500 байт. Этим самым мне удалось добиться разрыва между пакетами чуть больше 1мксек. До этого решения, у меня посылалось примерно до 3 пакетов в 1 мксек и у меня жутко висела вся сеть. Даже если свитч выдерживал нагрузку, то компьютер не справлялся.
Когда я поборол зависания путем уменьшение частоты пакетов, я столкнулся с проблемой визуализации как в SignalTAP. Все решения рисования графиков на c#, на .Net, Excel тупиковые. Они просто не справляются таким потоком данных и самое главное таким кол-ом набора данных. Но плавно пришло решение. Я просто погружал эти данные в ModelSim с помощью написанного мною TestBench.
В итоге связка получилась такая. Я на c# написал сниффер сети. Сырые сокеты не стал использовать, взял готовое решение для .NET PCAP (на нем работает и Wireshark). Сниффлю я все пакеты с MAC отправителя моей FPGA (там я просто прописал 000102030405), PAYLOAD (полезные) данные из UDP пакета я просто побайтно сохраняю в файл. И потом этот файл просто загружаю в ModelSim с помощью TestBench.
Выглядит это вот так:
По моему получилось супер. Тема логов очень актуальна. Гигабитный RJ45 очень выручает в этом плане. Мы не засоряем боевые каналы передачи данных (SFP+ 10G, PCI-E) где ходят торговые данные. + Визуализация отладки мне понравилась. Тормозов нет, все в виде осцилограммы. И самое главное в таком решении, нет ограничения по времени логов, как это в SignalTAP, где время собираемых логов ограниченно свободной памятью fpga. Получился некий скоростной логический анализатор. Буду рад услышать критику или ваши похожие решения.
ps. Кода в посте совсем не было. Ну пусть будет хоть кусок TestBench на Verilog =))
ps2. В поддержку Тимофея и его Смартлаб.
ps3. В настройках подсветки кода нет настройки Verilog (видно разработчики не ожидают тут фпгаашников), так что будем довольствоваться подсветкой c++
`timescale 1ns / 1ns
module main_test_bench_125_1;
/*
Формат лога в сетевом пакете:
struct {
uint16_t log_id; Два байта порядкого номера лога
uint8_t signal_values; 8 бит со значениями восьми сигналов
}
*/
//Сигналы для вывода на осцилограмму
reg clk_100Mhz; //Осцилограмма генератора 100Mhz
reg [15:0] log_row_id; //Первые 2 байта лога - порядковый номер
reg signal_1; //Первый бит третьего байта лога - значение первого сигнала в логе
reg signal_2; //Второй бит третьего байта лога - значение второго сигнала в логе
reg signal_3;
reg signal_4;
reg signal_5; //...
reg signal_6;
reg signal_7;
reg signal_8; //Восьмой бит третьего байта лога - значение восьмого сигнала в логе
//Вспомогательные переменные
integer file_log; //Указатель на файл с логами
integer scan_file;
initial
begin
//Пытаемся открыть файл с логами
file_log = $fopen("udp_dump.bin", "r");
if (file_log == 0)
begin
$display("Log file open FAILED");
$finish;
end
//Имитируем тактовый генератор 100Mhz
clk_100Mhz = 1'b0; //Начальное значение 0
#5; //Задержка сигнала 5нс (100Mhz = такт 10нс, 5нс - полу такт)
repeat(24999999)
begin
clk_100Mhz = 1'b1; //Ставим генератор в 1
#5 clk_100Mhz = 1'b0; //Ждем 5нс и ставим генератор в 0, фактически имитируем сигнал 1 в течении 5нс
#5; //Ждем 5нс и ставим генератор в 1, фактически имитируем сигнал 0 в течении 5нс, получаем 1 такт длительностью 10нс
end
clk_100Mhz = 1'b1;
#5;
end
//На каждый такт генератора читаем данные из файла и выводим их в сигналы осцилограммы
reg [7:0] byte_1;
reg [7:0] byte_2;
reg [7:0] byte_3;
always @(posedge clk_100Mhz)
begin
byte_1 = 0;
byte_2 = 0;
byte_3 = 0;
scan_file = $fscanf(file_log, "%c%c%c", byte_1, byte_2, byte_3); //Читаем 3 байта в 3 регистра по 8 бит
if (!$feof(file_log))
begin
log_row_id [15:8] = byte_1; //Назначаем прочитанные значения на выводы осцилограммы
log_row_id [7:0] = byte_2;
signal_1 = byte_3 [7];
signal_2 = byte_3 [6];
signal_3 = byte_3 [5];
signal_4 = byte_3 [4];
signal_5 = byte_3 [3];
signal_6 = byte_3 [2];
signal_7 = byte_3 [1];
signal_8 = byte_3 [0];
end
end
initial
#1000000000 $stop; //После 1млрд наносекунд (=1 сек), заканчиваем моделирование
endmodule
Не дешевле ли было раскошелиться на новую карточку ?
Или умельца привлечь, JTAG подпаять? Физически то он на всех Altera есть, просто на карточке может быть не подключен.
Проц не задействовали для передачи, чисто на конечных автоматах?
До JTAG не дошли руки (дошли, но не получилось). Просто устал. Есть мнение, что он закорочен до меня. Конкретно первая нога (tck). Прочитал глупый совет на форуме альтеры, начать подавать повышенное питалово от альтернативного источника (начал подавать 3.3 вольта) на эту ногу, закаротка начала по тихоньку пропадать, начало расти сопротивление. Но ничтожно медленно. А 5 вольт, как написано было в совете, я боюсь подавать, спалю альтеру, жалко будет.
Новые платы можно взять начиная с 1,5k$, зависит от класса. Просто нерационально убивать месяц работы на починку.
А начинал в свое время с циклона 4 SX, но на нем далеко не уедешь.
А если Gb Ethernet, то еще дешевле и вариантов море.
1) Находите в характеристиках микросхемы приемника (ваш PHY) параметры tsu, th;
2) Рассчитываете по ним tco, tcomin;
3) Прибавляет к ним разницу задержки в дорожках (для 125 МГц она ничтожна, можно игнорировать);
4) Задаете параметры в Quartus;
5) Quartus сам подбирает задержки во время Fitter и проверяет, что попал в Timing Analizer.
Так гораздо быстрее, чем подбирать руками. Кроме того гораздо надежнее, так как задержки все равно немного плавают от температуры и кристалла (в смысле на другой ПЛИС того же типа), и нередко «подобранные руками золотые задержки» перестают работать совершенно неожиданно.
При применении расчета даже если при разводке по кристаллу параметры сломаются, увидите предупреждение в отчете.
Когда подымал 10Base действительно перебрал методом тыка частоту.
Marvel's 88E1118R
Знал бы, что существует услуга рентген плат, воспользовался незамедлительно
А рентген мало поможет, во-первых скорее всего он внутри микросхемы, а не между проводниками, а во-вторых на многослойке рентгеном можно увидеть только закоротку припоем под корпусом.
Какие то ножки, особенно jtag, трансиверы и marvell звонил обычным мультиметром. Купил самые острые наконечники, заострил их наждаком еще больше и тыкал по плате. Альтера то BGA, с обратной стороны доступ есть.
Все остальное не получилось вызвонить (диоды, генераторы и тд). Перекрестился, подал на все 650 штук пользовательские пины 0, диоды загорелись. Ну а дальше дело техники. Сужал диапазон ножек в прошивке и вычислял конкретные ноги.
Генераторы тоже дело техники. Начал брать каждую ножку с функцией клока из Pin Planner, сделал через нее делитель частоты и выводил на диод. Грубо говоря заставлял моргать диод. Примерно после 19 прошивки я вычислил оба генератора. Секундомером замерил кол-во морганий и вычислил частоту, что подтвердило приблизительно маркировкой (как смог разглядеть через двойную лупу)
Ну вот как то так. Гораздо интересней вспоминать как я плату подымал и как мне помогали американцы =)). Я даже статейку на СЛ написал, но удалил, не понравилось. Сумбурный поток мыслей
Кстати, Тимофей примерно с год назад жаловался вслух, почему хабровцы не пишут у него.