Но тут мне пришлось освоить работу с шиной USB 3.0 через контроллер семейства FX3 фирмы Cypress. Пока свежи воспоминания, я решил задокументировать детали процесса, так как сделав всё по фирменным описаниям, можно либо не получить ничего, либо получить не совсем оптимальную систему. И снова вышел целый блок статей. В первой из них, мы рассмотрим, где взять «прошивку» для самого контроллера, как её собрать и залить в него. Также сделаем базовую «прошивку» для ПЛИС.
1 Зачем всё это
Во время практической работы с USB анализатором, я столкнулся с рядом трудностей.
Во-первых, передача данных через TCL-скрипт (которую мы начерно осваивали тут и начисто использовали тут) была худо-бедно приемлема для научных опытов, но для практической отладки USB-устройства категорически неприемлема. Причём малейшая доработка скрипта увеличивала время передачи данных. Иногда – вдвое. И получалось, скажем, десять минут вместо пяти… При том, что и пять-то минут были категорически неприемлемы. Надо было отказываться от передачи больших объёмов данных через JTAG.
Как уже отмечалось в одной из статей цикла, передача данных через выбранную нами микросхему FTDI по протоколу FT245-SYNC требует расходовать внутреннее ОЗУ ПЛИС, а его и так не хватает. И вообще не понравилась мне эта микросхема в реальной эксплуатации. Когда мы её закладывали, ставилась задача пользоваться только готовыми мостами с готовыми драйверами, чтобы ничего не дописывать. Эта задача была успешно выполнена, что позволило уложить разработку комплекса в разумные сроки. Сейчас, когда идёт его спокойная эксплуатация, вполне можно спокойно вести доработки для следующей ревизии аппаратуры. Итак, мост хочется заменить. Но на что? И вот тут всплывает «Во-вторых».
А во-вторых, когда я играл в USB-анализатор, память на плате быстро переполнялась. Давно ли мне хватало шестнадцати (даже пятнадцати с половиной) килобайт в БКшке? А тут, гляньте, 32 мегабайта мало. Но что есть, то есть (хотя, чтобы не гонять много через тормозной JTAG, я старался делать буфер на 2, 4, ну максимум 8 мегабайт).
И мы решили перейти на шину USB3. Её производительность такова, что вполне можно гнать поток от ULPI в PC без сохранения в промежуточное буферное ОЗУ. Соответственно, в PC можно складировать данные гораздо больших объёмов, чем 32М.
Хорошо. Меняем USB2 на USB3, но какой именно взять мост для этой шины? Мой знакомый из дружественной организации провёл большие исследования FTDI-шных мостов серии FT600 и получил мелкие плюхи, которые в итоге не давали работать в целом. Анализ форумов показал, что там всё очень критично к тюнингу временных диаграмм. Надо прописывать ряд ограничений для компилятора. И народ до сих пор ищет, как они должны выглядеть в идеале. Поэтому было решено пока что не рассматривать тот подход. Остаётся контроллер FX3 фирмы Cypress. Почему бы не влить в него типовую «прошивку» и не сказать, что теперь это мост, после чего забыть о том, что перед нами контроллер, требующий какого-то программирования? А что касается драйвера, там только с виду всё сложно, а так — всё просто и кроссплатформенно, но об этом я напишу в другой статье блока. Сегодня мы касаемся только аппаратуры.
2 Аппаратура
2.1 Плата с контроллером FX3
У меня в загашниках есть несколько отладочных плат на базе контроллера FX3. Самые первые непригодны для использования простыми советскими инженерами, так как разъём у них очень экзотический. Но есть и более дружественная макетка: CYUSB3KIT-003. Вот ссылка на сайт производителя: CYUSB3KIT-003 EZ-USB FX3 SuperSpeed Explorer Kit (cypress.com)
У неё обычные двухрядные разъёмы с шагом 2.54 мм. Фотография с сайта:
На той же странице есть ссылка на образ диска, содержащий среду разработки и SDK. Этот образ диска скачивается без регистрации. А вот если перейти по ссылке на страницу с SDK, то там для скачивания того же самого уже требуют зарегистрироваться, что я не люблю делать.
Также там есть ссылка на Application Note, содержащей информацию о том, как просто залить в контроллер типовое приложение, после чего он станет мостом.
AN65974 — Designing with the EZ-USB FX3 Slave FIFO Interface (cypress.com)
С виду – красота! Правда, если бы всё было так, как кажется, не было бы статьи. Но тем не менее, есть от чего плясать.
2.2 Плата с ПЛИС
Как я уже отмечал, для текущих опытов я использую китайский модуль AC608. Если вбить его имя на Ali Express, будет найдено некоторое количество продавцов, предлагающих его. Там стоит ПЛИС EP4CE10F17C8 семейства Cyclone IV. Этот модуль куплен давненько, ещё когда я только собрался серьёзно заняться анализатором. Сегодня бы я взял десятый Циклон, но что есть в наличии, то есть.
2.3 Соединяем
В документе 001-65974_AN65974_Designing_with_the_EZ-USB_FX3_Slave_FIFO_Interface.pdf есть замечательная таблица 2, в которой показано, как подключить систему в восьми, шестнадцати или тридцатидвухбитном режиме к ПЛИС. Для текущего варианта был выбран шестнадцатибитный режим. При разработке схемы сначала возникли небольшие трудности с чтением этой таблицы. Давайте сделаю так, чтобы они больше ни у кого не возникли. Вот пара строк из неё:
Их содержимое следует читать так: Сигнал CS – это нога GPIO17 контроллера. На шелкографии макетки она обозначена, как CTL0. Сигнал SLWR – это нога GPIO18, на шелкографии модуля она обозначена, как CTL1. Ну, и так далее. Смотрим сигналы в столбце выбранной разрядности, потом переводим взгляд влево и смотрим, куда они подключены. Причём скорее всего, вам придётся смотреть не первый, а второй столбец. Именно в этих терминах обозначены сигналы на схеме макетки и на шелкографии. Вот какая красивая там шелкография, конструктор – просто молодчина!
На частоте 100 МГц соединение плат проводами крайне противопоказано. Только плата! Схему нашего соединительного модуля в статье в виде рисунка приводить не вижу смысла, но на всякий случай, приложу её и файл с разводкой в раздаточные материалы, на случай если вдруг кто-то захочет повторить опыты именно с этим железом. Но в целом – получилась вот такая кувалда:
Как видно из фотографии, на ручке кувалды присутствует также модуль ULPI от WaveShare. Те, кто читают цикл давно, уже догадались, что перед нами очередная версия USB-анализатора. Поэтому сегодня мы будем заниматься прокачкой данных из ПЛИС в PC и только в этом направлении. Обратный поток нас сегодня не интересует.
3 Первое веселье с «прошивкой» для контроллера
3.1 Выбираем, что же мы будем прошивать
На самом деле, с виду, с «прошивкой» контроллера всё должно быть хорошо. Мало того, к примеру AN65974 прилагаются не только исходные, но и двоичные файлы. Теоретически, можно взять файл Fx3SlaveFifoSync.img и просто «прошить» его в макетку. Всё бы ничего, но из исходников видно, что это 32-битная сборка, а аппаратура у нас 16-битная…
/* 16/32 bit GPIF Configuration select */
/* Set CY_FX_SLFIFO_GPIF_16_32BIT_CONF_SELECT = 0 for 16 bit GPIF data bus.
* Set CY_FX_SLFIFO_GPIF_16_32BIT_CONF_SELECT = 1 for 32 bit GPIF data bus.
*/
#define CY_FX_SLFIFO_GPIF_16_32BIT_CONF_SELECT (1)
Что может быть проще? Устанавливаем среду разработки, импортируем туда проект, собираем… Получаем ошибку «Нечего собирать». Смотрим – видим, что не найдены элементы ToolChain (кстати, кто подскажет, как это называть по ГОСТ 2.105, то есть, не «тулчейн», а более по-русски, но чтобы слух не резало?). Немного поиска по Гуглю, и становится ясно, что просто перед нами очень древний проект, не совместимый с той средой разработки, которая лежит на сайте Cypress сегодня.
Отлично! Создаём новый проект в актуальной среде, подключаем к нему исходные файлы из того проекта, собираем… Получаем ошибки в заголовках. Немножко поиска по Гуглю, и становится ясно, что те исходники сделаны под старую версию SDK.
Хорошо. Осматриваемся в примерах, входящих в состав SDK. И выясняем, что пример Program Files (x86)CypressEZ-USB FX3 SDK1.3firmwareslavefifo_examplesslfifosync – современный аналог примера из Application Note. Ура? Подождите, приключения только начинаются. Но так или иначе, мы убедились, что исходно этот проект рассчитан на 16-битный обмен, и успешно собрали его. То есть, пока что мы нашли хороший пример и собрали его, не внося никаких правок (когда черновик статьи уже был залит на Хабр, выяснилось, что этот пример обладает серьёзным недостатком, но как его найти и устранить, мы рассмотрим в отдельном материале). Давайте я заодно расскажу, как его залить в контроллер.
3.2 Как залить получившуюся «прошивку» в контроллер
Чтобы залить проект в макетку, надо заставить контроллер запустить лоадер. Для этого устанавливаем джампер J4. На фото он специально жёлтого цвета (для контраста, так как фирменные все красные, причём для установки на позицию J4 тоже прилагается красный, в отдельном пакетике, комплект поставки у макетки – выше всяких похвал).
Итак, устанавливаем этот джампер, после чего либо передёргиваем питание, либо нажимаем кнопку Reset (она расположена рядом с разъёмом USB 3.0). Всё. Контроллер теперь находится в режиме загрузки.
Запускаем программу Control Center, которая установилась вместе со средой разработки. В ней выбираем контроллер в дереве (у меня дерево на экране состоит из корня одного устройства).
И выбираем либо пункт меню Program->FX3->RAM, чтобы запустить код в ОЗУ, либо Program->FX3->I2C EEPROM, чтобы прошить код в ПЗУ. Да, я не оговорился. Именно I2C EEPROM. Не SPI. Так надо!
На время опытов лучше лить просто в ОЗУ. Так быстрее. А опытов нам предстоит много. Потом, когда всё будет отлажено, тогда уже можно будет перейти на прошивку в ПЗУ. Чтобы код стартовал из ПЗУ, надо будет снять джампер J4. При заливке в ОЗУ аппаратуру можно не трогать, код запустится сам.
Хорошо. Первичная «прошивка» для FX3 сделана и даже залита в контроллер. В дереве появилось совсем другое устройство с двумя конечными точками типа Bulk:
Это наша система. Но для того, чтобы начать с нею работать, нам надо сделать «прошивку» для ПЛИС. Приступаем, для чего вчитываемся в документацию. И тут нас ждут первые доработки кода контроллера…
4 Насколько верна типовая прошивка для ПЛИС из примера?
4.1 Путанные результаты осмотра
Разработчики от Cypress заботливо положили примеры «прошивок» для Xilinx и Altera, причём сразу на двух языках – VHDL и Verilog. Красота! Но начав изучать файл AN65974FPGA Source filesfx3_slaveFIFO2b_alterartl_verilogslaveFIFO2b_streamIN.v, я понял, что понимаю не всё. Меня смущала нужность автомата и вообще флаги. Автомат в документе показан так:
Но суть его описана недостаточно понятно. Назначение флагов в исходниках описано так:
input flaga, //full flag
input flagb, //partial full flag
input flagc, //empty flag
input flagd, //empty partial flag
Про это что-то есть в документе, но очень путано. Логика мне не понятна нисколько. Более того, при описании, в документе ссылаются на функции CyU3PGpifSocketConfigure(), которые действительно используются в примере, идущем в составе AppNote… Который не собирается. И они отсутствуют в текущем примере. А почему они там отсутствуют? Открываем дизайн GPIF, идущий в составе AppNote. Видим там четыре флага (по паре Ready и Watermark для каналов 0 и 3):
А теперь – тот же дизайн из проекта, который собирается… Ой! А флагов всего два! Причём на самом деле, они равнозначные – Ready для выбранного канала (выбранный канал – это тот, номер которого установлен нашей ПЛИС на адресной шине).
Разумеется, специалисты, читая эти строки, уже усмехаются. Мол, не разобрался в контроллере, а всё туда же, статьи писать. Но вообще-то разобрался, просто нагнетаю обстановку. Однако в целом – беда в том, что вообще-то я и не собирался (да и не собираюсь) становиться гуру FX3. С FX2LP я 12 лет назад разбирался три месяца в режиме полного дня, и знаю его досконально. Процесс изучения программируемой логики, встроенной в семейство PSoC, я оформил в виде сразу двух циклов статей: авторского и переводного. А тут – нет времени, руководство требует срочно победить проблемы синтезируемого RISC-V, все силы брошены туда. Плюс – я уже старый и тормозной.
Использование блоков UDB контроллеров PSoC фирмы Cypress для уменьшения числа прерываний в 3D-принтере
Часть 2: Использование блоков UDB контроллеров PSoC фирмы Cypress для уменьшения числа прерываний в 3D-принтере
DMA: мифы и реальность
Использование Datapath Config Tool
Особенности формирования тактовых частот в PSoC 5LP
Источники вдохновения при разработке под UDB
То есть, я хотел просто взять типовой пример и заставить его выполнять типовые функции! Мне некогда вникать во все хитросплетения и проводить десятки и сотни опытов! Я хочу просто взять готовое и заставить его решать мои частные задачи для освоения совсем других вещей… Я не хочу тратить время и нервы, перенося куски из старых исходников в новые… Я не хочу понимать, куда вставить те функции, понимать, зачем этот хитрый автомат на 4 состояния, понимать, понимать, понимать…
Но кое-что понять придётся. Давайте почитаем документ, приложенный к AppNote. И там мы видим удивительные вещи.
4.2 Удивительная латентность
В документе есть очень подробное описание флагов. И там говорится про очень интересную латентность. Эта латентность также кочует из документа в документ. Собственно, для борьбы с нею и придуман тот самый таинственный флаг Watermark, сути работы с которым я изначально не понял. Вот эта латентность.
Рисунок очень общий. На нём тоже два флага, но это другие два флага. В коде используются флаги FLAGA и FLAGB, запрограммированные на функции Full и Watermark одного канала, а на этом рисунке (в документе, поясняющем код) — FLAGA и FLAGB как флаги Full для двух разных каналов, номера которых задаются на адресной шине. Как я долго въезжал в этот факт, кто бы знал! Поэтому отмечаю его для остальных.
Но из рисунка видно, что о том, что буфер переполнился, мы узнаем через три такта. Кто-то спросит, мол, а зачем нам знать через три такта, что буфер давно переполнился? Я просто присоединюсь к его вопросу. Ответа на него я не знаю.
Но знаю, что есть альтернативный флаг Watermark, про который сказано, что его нельзя использовать в одиночку, только в паре с основным флагом. Этот флаг можно запрограммировать на взведение, когда в буфере осталось N свободных элементов. И тогда, с поправкой на латентность, он взлетит как раз, когда буфер переполнен (правда, понятие «элемент» там забавное: они 32-битные, для 16-битных слов надо задаваемое значение делить на 2, а у нас латентность нечётная, но это мы изучим в следующей статье). Собственно, автомат в AppNote как раз по очереди и ждёт появления флагов. Правда, кто мешает объединить их по «И»? Но не суть. Суть в том, что у нас есть два варианта действий:
Либо открыть GPIF II Designer, в нём открыть проект Program Files (x86)CypressEZ-USB FX3 SDK1.3librarysync_slave_fifo_2bit.cydsnsync_slave_fifo_2bit.cyfx и поменять назначение флагов. Я сделал так:
Затем сгенерить заголовочный файл на языке С, выбрав Build ->Build Project:
И сравнивая содержимое получившегося файла и рабочего файла cyfxgpif_syncsf.h поправить его…
Либо поверить мне на слово, и просто взять тот файл cyfxgpif_syncsf.h, который я выдам в раздаточном материале. Я в нём уже сделал всё, что нужно…
Вот поэтому я и говорил, что пока проще заливать «прошивку» не в EEPROM, а в ОЗУ. Это не последняя правка кода. Мы ещё несколько раз будем его дорабатывать и заливать результаты в контроллер.
5 Проектируем систему для ПЛИС
5.1 Общий вид
Кто читал цикл про комплекс Redd, тот знает, что все системы в его рамках делаются на базе процессорных систем. Остальным советую посмотреть основы идеологии в статье «Разработка простейшей «прошивки» для ПЛИС, установленной в Redd, и отладка на примере теста памяти» (самая главная особенность – наша система является блоком верхнего уровня, вставляемым без каких-либо прослоек на машинном языке). Базовые принципы разработки своих модулей для системы описаны в статье «Разработка собственного ядра для встраивания в процессорную систему на базе ПЛИС«, мы будем работать с шиной AVALON-ST, о которой можно узнать из статьи «Первые опыты использования потокового протокола на примере связи ЦП и процессора в ПЛИС комплекса Redd«.
Сегодняшняя система будет весьма и весьма спартанской:
5.2 PLL
Примеры от Cypress работают на частоте 100 МГц. Отчего бы не попробовать эту частоту и нам? Но ULPI выдаёт данные на частоте 60 МГц. Для преобразования частот в систему добавлен блок PLL. Мы с ним работали уже достаточно много, так что подробности его настройки показывать нет смысла. Кто забыл детали – может заглянуть в статью «Ускорение программы для синтезированного процессора комплекса Redd без оптимизации: замена тактового генератора«, только сегодня входная частота – не 50, а 60 МГц, и сдвиг фазы не нужен.
Частота 60 МГц (фиолетовая линия) тактирует источник сигнала и входной порт FIFO. Частота 100 МГц тактирует выходной порт FIFO и интерфейсный блок, работающий с FX3.
5.3 Источник данных
Источник сигнала – таймер, с которым мы уже познакомились в статье «Практическая работа с ПЛИС в комплекте Redd. Осваиваем DMA для шины Avalon-ST и коммутацию между шинами Avalon-MM«. Он гонит постоянный поток данных. Если данные были утеряны, мы заметим разрыв в потоке. Правда, сегодня я чуть-чуть изменил его работу. Во-первых, мы гоним 16-битный поток данных, так что и таймер стал 16-битным. А во-вторых, сегодня нам важно, чтобы таймер работал на исходно пустую очередь буферного FIIFO. Поэтому я добавил правило: если приёмник данных от таймера не готов принимать поток, то после возобновления готовности, таймер ждёт 8192 такта. Ведь почему FIFO теряет готовность? Потому что на том конце не могут принять данные в момент, когда очередь уже переполнена. А если из блока FIFO пришёл сигнал о готовности, то это значит, что в очереди освободилось хотя бы одно свободное место. Переполнить очередь в этот момент – пара пустяков. Именно поэтому мы подождём… Через 8 килотактов, очередь, с высокой степенью вероятности, если не опустеет вовсе, то хотя бы будет почти пустой.
Итак, вот текст, реализующий такой модуль.
module Timer_ST (
input clk,
input reset,
input logic source_ready,
output logic source_valid,
output logic[15:0] source_data
);
logic [15:0] delay_cnt = 0;
always @ (posedge clk)
begin
// На том конце очередь переполнена
// Значит, когда она освободится - начнём
// слать данные не сразу...
if (source_ready == 0)
begin
delay_cnt <= 8192;
source_valid <= 0;
end else
begin
// Задержка истекла - начинаем слать
if (delay_cnt == 0)
begin
source_valid <= 1;
end else
// Иначе - просто ждём...
begin
delay_cnt <= delay_cnt - 1;
end
end
end
// Таймер же продолжает тикать всегда, чтобы
// на PC увидеть факты разрыва...
logic [15:0] counter = 0;
always @ (posedge clk, posedge reset)
if (reset == 1)
begin
counter <= 0;
end else
begin
counter <= counter + 1;
end
assign source_data [15:0] = counter [15:0];
endmodule
Вот – настройки шин блока для Platform Designer:
5.4 FIFO
Тут всё просто. 16 бит (два символа), 8192 элементов.
5.5 Интерфейс шины FX3
5.5.1 Где черпаем вдохновение
Как я уже сказал, мне категорически не понравился пример из Application Note. Я стал рыться по сети и нашёл довольно забавный проект Stream: GitHub — myriadrf/STREAM: FPGA development platform for high-performance RF and digital design
Там напрочь отсутствует исходный код для FX3, только двоичный файл, но зато есть файл fx3_tx.v (часть логики, спрятана в fx3.v), размером всего в экран. Этот файл преобразует шину AVALON_ST в FX3. Как раз то, что нам нужно!
Но там используется всего один флаг! Собственно, я перетянул ту логику к себе, изменив разрядность шины, добавив формирование константных сигналов и добавив второй флаг, объединив его по «И» с первым.
5.5.2 Описание получившегося файла
Собственно, у меня получился такой модуль.
module FX3_to_ST (
// ****************
// *** Шина FX3 ***
// ****************
input reset, //input reset active high
input clk, //input clp 100 Mhz
inout [15:0]fdata, //data bus
output [1:0]faddr, //output fifo address
output slrd, //output read select
output slwr, //output write select
output sloe, //output output enable select
output clk_out, //output clk 100 Mhz
output slcs, //output chip select
output pktend, //output pkt end
// Эти два флага - для передачи
input flaga, //full flag
input flagb, //partial full flag
// Эти два флага для приёма, сейчас не используются
input flagc, //empty flag
input flagd, //empty partial flag
// **********************
// *** Шина AVALON_ST ***
// **********************
output logic sink_ready,
input logic sink_valid,
input logic[15:0] sink_data
);
reg flagb_d;
always @(posedge clk)
begin
flagb_d <= flagb;
end
wire flagsab;
assign flagsab=flaga&flagb_d;
// Оригинальный пример задерживает данные на такт. Ну и мы так сделаем
reg [15:0] fx3_data;
// Этот сигнал тоже задержан на такт вместе с данными
reg fx3_wr;
assign clk_out = clk;
// Пока что никогда не читаем
assign slrd = 1'b1;
// Вот такая логика записи в FIFO
assign slwr = !fx3_wr;
// Всегда выбран нулевой канал
assign faddr = 2'd0;
// Мы никогда не принимаем данные, только посылаем
assign sloe = 1'b1;
// Но на всякий случай, шину данных занимаем только когда реально передаём
assign fdata = (fx3_wr) ? fx3_data : 16'dz;
// Канал всё время выбран
assign slcs = 1'b0;
// И пока что никогда не терминируется
assign pktend = 1'b1;
// Линия задержки сигнала FULL
reg [1:0] full = 0;
assign sink_ready = !full[1];
always @(posedge clk)
begin
if (reset) begin
fx3_wr <= 1'b0;
end
full <= {full[0],!flagsab};
fx3_data <= sink_data;
fx3_wr <= !full[1] & sink_valid;
end
endmodule
Защёлкивание данных требуется, чтобы на шине FX3 они были стабильны на протяжении всего цикла. Пусть шина AVALON_ST приходит к нам из ОЗУ, и никаких переходных процессов не будет… Я пробовал убрать защёлки. Сразу лезет бешеное количество битовых ошибок!
А, собственно, из оригинальной логики – всё. Если шина FX3 не готова принимать данные, мы сообщим об этом шине AVALON_ST. Если в шине AVALON_ST есть данные, а FX3 готова, мы выставляем строб записи…
Интерес представляет задержка на такт сигнала FLAGB. Детективную историю, как я выяснил, что он нужен, мы рассмотрим в следующей статье. А так – всё. На удивление простой модуль. Не то, что вариант из Application Note.
5.5.3 Упаковка модуля для использования в процессорной системе
Настройки блока для процессорной системы должны выглядеть так (не забываем поменять автоматически выбранную шину AVALON_MM на AVALON_ST, создать шину couduit, перетаскать туда большинство сигналов и вручную прописать им имена в пределах шины):
Уффф. Материалов получилось уже многовато, а практическая работа предстоит тоже немаленькая. Предлагаю сделать паузу и переварить материал, а к первым практическим опытам перейти в следующей статье.
6 Заключение
Статья является, по сути, рефератом, в котором перечислены ссылки на основные документы, позволяющие подключить контроллер FX3 к комплексу, похожему на Redd, а точнее – к ПЛИС Altera. В материалах выявлены нестыковки по версиям. Найден вариант, который в конце 2020 года, вроде как, собирается и работает. Система пока что не имеет практической ценности, так как ещё не проведена её отладка. Первый шаг к отладке будет сделан в следующей статье.
Материалы с описанием аппаратуры, можно скачать тут.
Демонстрационный проект, созданный при написании данной статьи, можно скачать тут. Код FX3 несколько отличается от того, который получится после правки согласно этой статье. Что и зачем там ещё поправлено, мы узнаем из следующей статьи. Возможно, даже из двух.