Русский
Русский
English
Статистика
Реклама

Fpga

Перевод Уловка для обновления содержимого инициализации ОЗУ в битовых потоках ПЛИС Intel

17.05.2021 16:18:04 | Автор: admin

Применение программных ЦПУ ускоряет процесс разработки ПЛИС за счет избежания этапов синтеза и размещения с трассировкой, которые сменяются компиляцией прошивки и обновлением потока битов. Для быстрого же обновления битовых потоков можно использовать либо официальную технику, которая не работает с вендорными ПЛИС, либо обходной путь. Обе этих техники мы и рассмотрим в данной статье.

План статьи



Введение


Логические элементы ПЛИС настраиваются с помощью потоков битов: таблицы поиска логических элементов получают содержимое, определяющее их поведение, переключатели в комплексных сетях создают нужную топологию маршрутизации, триггеры подключаются к правильным сетевым часам и т.д.

Большинство ПЛИС позволяют блочной ОЗУ получать в процессе настройки ненулевое содержимое.

Данная функция очень важна, если ПЛИС оборудована процессором с программным ядром, потому что это самый простой способ получать загрузочный код сразу после включения питания.

В собственных проектах я зачастую использую такие мини-ЦПУ при реализации контроллеров для всевозможных низкоскоростных протоколов вроде I2C, SPI, Ethernet PHY MDIO и т.д. В этих случаях все прошивки предварительно подготавливаются в блочной ОЗУ. Я практически никогда не применяю внешние флеш-накопители для хранения прошивки просто потому, что обычно использую не настолько большой объем Си кода.

Применение программных ЦПУ вместо аппаратных ускоряет циклы разработки: если вы можете избежать синтеза, а также размещения и трассировки, заменив эти шаги на компиляцию прошивки и обновление потока битов, то вам удастся сэкономить минуты на этапах проектирования мелких схем или даже часы при разработке крупных.

И здесь возникает логичный вопрос:

Как можно быстро обновлять битовые потоки новым содержимым ОЗУ, не проходя через повторный синтез с последующим размещением и трассировкой?

В этой статье я опишу две техники:

  • Официальную, которая требует ручного инстанцирования модели ОЗУ на ПЛИС Intel и использования HEX или MIF файла.
  • Уловку, которая позволит задействовать в Verilog выведенную ОЗУ, инициализированную с помощью $readmem(...).

В первой технике используется примитив Intel altsyncram, и она не работает с вендорными ПЛИС. Вторая же техника делает РТЛ (резисторно-транзисторной логики) проекта совместимой с различными семействами ПЛИС.

Я также приведу пример, в котором обе техники реализуются на минимальной, но при этом полезной системе ЦПУ.

Общий способ вывода инициализированной ОЗУ


Стандартный способ добавления ОЗУ в проект ПЛИС приблизительно выглядит так:

localparam mem_size_bytes   = 2048;    // $clog2 требуется Verilog-2005 или новее...    localparam mem_addr_bits    = $clog2(mem_size_bytes);       reg [7:0] mem[0:mem_size_bytes-1];    wire                     mem_wr;    reg [mem_addr_bits-1:0]  mem_addr;    reg [7:0]                mem_rdata;    reg [7:0]                mem_wdata;    always @(posedge clk)        if (mem_wr) begin            mem[mem_addr]   <= mem_wdata;            mem_rdata       <= mem_wdata;       // Это требуется некоторым   //ОЗУ на ПЛИС Intel...        end        else            mem_rdata  <= mem[mem_addr];

Любой компетентный инструмент синтеза ПЛИС выведет из этого кода блочную ОЗУ размером 2Кб.

Если вам нужно, чтобы содержимое ОЗУ инициализировалось после конфигурации, просто добавьте:

initial begin        $readmemh("mem_init_file.hex", mem);    end

Этот метод работает для симуляции и, опять же, большинство компетентных инструментов ПЛИС будут синтезировать поток битов, инициализирующий блочную ОЗУ с содержимым после конфигурирования. (Хотя разработчикам микросхем ASIC это совсем не понравится).

Здесь важно отметить, что в течение всего процесса, начиная с РТЛ и до потока битов, инструмент ПЛИС в ходе РТЛ-анализа и синтеза будет обрабатывать инструкцию $readmemh(). Эти шаги происходят в начале всего процесса создания потока битов. В результате простейшая реализация потребует перезапуска процесса в момент изменения содержимого mem_init_file.hex. По крайней мере это точно касается Quartus (Открытые инструменты синтеза ICE40/ECP5 и размещения с трассировкой не подпадают в категорию простейшей реализации).

Должен быть способ получше

Ручное инстанцирование блочной ОЗУ


Вместо того, чтобы оставлять процесс вывода ОЗУ из поведенческих блоков Verilog инструментам синтеза, можно явно инстанцировать примитивы ОЗУ в своем коде. Это окажется полезным по ряду причин.

Одна из них относится к случаям, когда нужно использовать очень специфичные возможности блочной ОЗУ конкретной ПЛИС.

Типичный пример это, когда я хочу убедиться, что в блочной ОЗУ есть триггеры на входе (адрес, данные для записи, разрешение записи) и на выходе (данные для чтения).

Это увеличивает задержку чтения ОЗУ с одного до двух циклов, что во многих проектах не станет проблемой и может привести к существенному повышению тактовой частоты.

В коде ниже прописана ступень конвейера на выходе ОЗУ, которая затем используется в качестве операнда умножителя:

reg [7:0] mem[0:mem_size_bytes-1];    always @(posedge clk)        if (mem_wr_p0) begin            mem[mem_addr_p0] <= mem_wdata_p0;            mem_rdata_p1     <= mem_wdata_p1;        end        else            mem_rdata_p1     <= mem[mem_addr_p0];    // Дополнительная ступень конвейера для разрыва тракта синхронизации   //между ОЗУ и входом умножителя    always @(posedge clk)        mem_rdata_p2    <= mem_rdata_p1;    always @(posedge clk)        result_p3       <= some_other_data_p2 * mem_rdata_p2;



Для подобных случаев выход ОЗУ ПЛИС может оказаться бессистемным, так как инструмент синтеза имеет 2 варианта реализации для регистра rd_data_p2.

Он может использовать выход FF ОЗУ так:



Либо использовать вход FF блока DSP так:



Когда мне в таких ситуациях требуется тонкий контроль, я инстанцирую ОЗУ вручную с помощью примитивной ячейки ОЗУ Intel. Ранее приведенный поведенческий код РТЛ обретает частично структурированную форму:



Выделенная строка здесь ключевая: использование REGISTERED активирует вывод FF ОЗУ и добавляет ступень конвейера.

Новый код нельзя использовать с ПЛИС других вендоров, и даже его эмулирование становится затруднительным, так как модели симуляции Intel обычно зашифрованы и работают только с коммерческими инструментами симуляции Verilog вроде ModelSim или VCS.

Предлагаемый мной обходной путь состоит в применении ifdef, которая выбирает поведенческие блоки для симуляции, и altsyncram для синтеза.

Примитив altsyncram также содержит параметр init_file:

 altsyncram #(        .operation_mode    ("SINGLE_PORT"),        .width_a           (8),        .width_ad_a        (mem_addr_bits),        .outdata_reg_a     ("REGISTERED"),        .init_file         ("mem_init_file.mif")    // <<<<<<<<<<    )    u_mem(        ...

MIF означает файл инициализации памяти и представляет проприетарный текстовый формат файлов Intel. Я преобразую двоичные файлы в MIF с помощью собственного скрипта create_mif.rb.

ОЗУ, выведенные Verilog, можно использовать с файлами MIF:

    (* ram_init_file = "mem_init_file.mif" *) reg [7:0] mem[0:mem_size_bytes-1];

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

Быстрое обновление потока битов после изменения файла MIF


Красота использования altsyncram и файла MIF в том, что можно легко обновлять поток битов и изменять файл MIF, не начиная все сначала.

Достаточно выполнить следующие шаги:

  • заменить содержимое файла MIF;
  • Quartus GUI: Processing -> Update Memory Initialization file.

Так вы загрузите обновленный файл во внутреннюю проектную базу данных Quartus.



  • Quartus Gui: Processing -> Start -> Start Assembler.

Так вы создадите поток битов из внутренней проектной базы данных Quartus.



Вместо GUI для проделывания двух перечисленных шагов Quartus я задействую Makefile:

QUARTUS_DIR = /home/tom/altera/13.0sp1/quartus/bin/DESIGN_NAME = my_designupdate_ram: sw $(QUARTUS_DIR)/quartus_cdb $(MY_DESIGN) -c $(MY_DESIGN) --update_mif$(QUARTUS_DIR)/quartus_asm --read_settings_files=on --write_settings_files=off $(MY_DESIGN) -c $(MY_DESIGN)sw:cd ../sw && make

Правило sw пересобирает последнюю версию прошивки и создает новый файл MIF. quartus_cdb обновляет проектную базу данных, а quartus_asm создает новый битовый поток.

Быстрое обновление битового потока для общих случаев Verilog


Чтобы обновить выведенную ОЗУ, которая была инициализирована с помощью $readmemh(), нужно самим взломать проектную базу данных Quartus. Это легче, чем кажется, потому что Quartus использует в БД формат файлов MIF.

Шаги для обновления выведенной ОЗУ:

  • найти в базе данных файл MIF, используемый для ОЗУ;

Я для этого вывожу все находящиеся в БД файлы MIF:

  cd quartus/db  ll *.mif

  • cоздать файл MIF для выведенной ОЗУ;

В Makefile для своей прошивки я всегда сразу собираю HEX файл (для использования $readmemh()) и файл MIF.

  • скопировать ваш файл MIF поверх аналогичного файла во внутренней БД;
  • проделать два ранее описанных шага Quartus.

Так выглядит Makefile:

QUARTUS_DIR = /home/tom/altera/13.0sp1/quartus/bin/DESIGN_NAME = my_designDB_MEM_MIF  = $(wildcard ./db/*mem*.mif)SRC_MEM_MIF = ../sw/mem_init_file.mifupdate_ram: sw $(DB_MEM_MIF)$(QUARTUS_DIR)/quartus_cdb $(MY_DESIGN) -c $(MY_DESIGN) --update_mif$(QUARTUS_DIR)/quartus_asm --read_settings_files=on --write_settings_files=off $(MY_DESIGN) -c $(MY_DESIGN)$(DB_MEM_MIF): (SRC_MEM_MIF)cp $< $@sw:cd ../sw && make

Самое главное здесь это выбор верного файла MIF в базе данных. Имя этого файла будет соответствовать иерархическому размещению памяти в проекте, но при этом также обладать случайным hex-суффиксом.

Когда у вас несколько ОЗУ, которые должны обновляться подобным образом, нужно с осторожностью выбирать шаблон, но на деле это не сложно.

Мини ЦПУ: пример конкретного дизайна


Чтобы продемонстрировать только что описанные принципы, я создал небольшой, но в то же время нетривиальный пример, в котором есть ЦПУ VexRiscv, двухпортовая ОЗУ для хранения инструкций ЦПУ и данных, а также периферийные регистры для управления светодиодами и считывания кнопки. Соответствующий GitHub-репозиторий находится здесь.

Этот пример я протестировал на своей плате Arrow DECA FPGA, но его также легко портировать на другие платы ПЛИС Intel.

В нем есть есть `define для выбора между общим выводом ОЗУ и инстанцированием altsyncram .

Makefile в каталоге ./quartus_max10_deca показывает, как обновлять 4 ОЗУ, содержащих эту прошивку.

Если у вас плата DECA, попробуйте:

  • скомпилировать прошивку в каталоге ./sw;
  • создать битовый поток;
  • проверить, вращаются ли светодиоды в одном направлении;
  • изменить define в прошивке, чтобы светодиоды стали вращаться в противоположном направлении;
  • выполнить make update_ram в каталоге ./quartus_max10_deca, чтобы обновить битовый поток без повторной компиляции.

Если у вас другая плата ПЛИС на базе Intel, то скопируйте каталог ./quartus_max10_deca и доработайте. Пул-реквесты я принимать готов.

Заключение


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


Подробнее..

PAL, GAL и путешествие в цифровое ретро

06.01.2021 02:13:18 | Автор: admin
Идея сделать цифровые логические микросхемы с изменяемой структурой была всегда. Почему? Достаточно посмотреть на толстенный каталог чипов серии TTL 74xx (или советской К155), чтобы такая идея самозародилась. В СССР почти у каждого инженера и радиолюбителя был справочник В.Л. Шило Популярные цифровые микросхемы, который вышел каким-то невероятным тиражом. Но всё равно, хотелось иметь некий универсальный кристалл, из которого можно сделать все остальные микросхемы (ну хорошо, не все, но многие).

Конечно же, полупроводниковая промышленность тоже была не прочь удовлетворить такой спрос. Поэтому, начиная с конца 1960-х, на рынке каждый год появлялось огромное количество подобных устройств из класса PLD (Programmable Logical Device), самых разнообразных архитектур. Это было интереснейшее время! На рынке постоянно появлялось что-нибудь новенькое. Здесь были и различные ULA и БМК и EPROM-устройства на базе пережигаемых перемычек (82Sxxx) и PLA, у которых программировались оба слоя: И и ИЛИ (привет нашим К556РТ1 и К556РТ2) и т.д. Обзор этих ретро-технологий тема для отдельной статьи. Мы же сделаем лишь обзор того, что выстрелило и стало мэйнстримом.

Сразу хочется попросить извинения у опытных электронщиков и разработчиков FPGA. Эта статья нисколько не имеет цели вторгнуться на их территорию, а только дает исторический обзор как это было.

А в конце мы попробуем соединить теорию с какой-то осмысленной практикой, поглядеть, как это применяется в реальной жизни. Светодиод, например, зажечь или там даже семисегментный индикатор

MMI


Так вот, в далекие 1970-е годы компания-производитель ПЗУ Monolithic Memories Inc. (MMI) выпустила на рынок очередное семейство программируемых чипов. Семейство ВНЕЗАПНО оказалось настолько удачным, что в 1978 году MMI зарегистрировала торговую марку PAL (Programmable Array Logic) и стала лицензировать технологию таким гигантам, как Texas Instruments (серия TIBPAL), National Semiconductor, Philips (PLUS), AMD (AMPAL) и другим.


Даже в СССР не отставали и выпустили клоны, серию К1556.


В чем же секрет? Спроектированное инженерами и для инженеров семейство MMI PAL представляло собой практически чистое воплощение идеи ДНФ (Дизъюнктивная Нормальная Форма).

Здесь надо сделать логическое отступление и взглянуть, что же это такое, ДНФ и откуда оно взялось.

Матлогика


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

Лирическое отступление: есть огромное искушение ничего не писать, а просто посоветовать взять и прочитать недавно переведенный учебник Харрис&Харрис Цифровая схемотехника и архитектура компьютера, а конкретно главу 2 Проектирование комбинационной логики. Это отличная книга! Там даже есть параграф про PAL (п.5.6 Матрицы логических элементов), но такое ощущение, что его безжалостно сократили в очередной редакции.

А для тех, кто захотел канонично, от печки, разобраться в предмете поглубже (бывает!) можно порекомендовать книги 60-х годов по проектированию ЭВМ. Например, книгу Синтез цифровых автоматов В.М. Глушкова (тот самый, который ОГАС!) (М. Государственное издательство физико-математической литературы, 1962г). Книга настолько олдовая, что фамилия Карно записывается как Карнаут. Или можно предложить книгу Синтез схем электронных цифровых машин (Е.Н.Вавилов, Г.П.Портной М. Советское радио 1963).

В современных отечественных учебниках, чем дальше, тем больше этот раздел ужимается.
Например учебник Угрюмов Е.П. Цифровая схемотехника издания 2007 года еще содержал главу про PAL, а издания 2010 уже нет. В настоящее время данная наука почти полностью перекочевала в пыльные бумажные методички кафедр по специальности 230101. Вдобавок, попытка дать студентам этот материал наталкивается на стойкое сопротивление: разговоры про основы воспринимаются учащимися как too old то есть как совершенно ненужный, устаревший хлам. Даже тут, на Хабре есть несколько статей на данную тему, и комментарии наполнены стонами Да-да! Нас тоже зачем-то насиловали этим весь первый курс!.

Так вот, автор сбрасывает себя обязанность писать про пресловутые основы. Предполагается, что читатель, который заглянул на огонек, всё-таки понимает, что такое булева алгебра, что такое И, ИЛИ, НЕ, знает теорему де Моргана и умеет читать схемы на логических элементах.

ПРИМЕЧАНИЕ: Мне тут подсказывают, что в современном курсе цифровой электроники эта дисциплина (Дискретная математика) и Цифровая схемотехника чаще всего разделены на два предмета и учебники у них разные. А Харрис&Харрис в попытке впихнуть все в одну книгу породил монстра на 1600 страниц. Ну OK.

Ах да, ДНФ. В старых книжках говорится (объясняется почему), что при достаточно сложном логическом выражении его лучше всего представить (и это можно сделать для любого выражения!) в виде дизъюнктивной нормальной формы (sum of product, SOP). Несмотря на страшное название, это очень просто. Возьмем логическую функцию Y от трех переменных, A, B, C. Пусть она принимает значение истина тогда и только тогда, когда все три входные переменные равны нулю или же все они равны единице. В ДНФ это запишется крайне просто:



Это так называемая алгебраическая форма. Операция ИЛИ тут записывается как +, а операция И как умножение и, как в обычной алгебре, опускается. Так получилось, что существует множество форматов записи (синтаксисов) булевых выражений. Вот в другой записи:



Си-шное выражение для bool переменных:



Или даже так (синтаксис PALASM):



То есть ДНФ (в алгебраической форме) выглядит как цепочка сложений т.е. многочлен (логическое ИЛИ) из произведений (логическое И) входных переменных (sum of product), причем только тех комбинаций, для которых должна получаться истина. Интуитивно это довольно понятно: Истина, когда это И это, ИЛИ же когда то И то. Как выше упоминалось, в форме ДНФ можно представить любое логическое выражение (и помним, что мы не углубляемся в СДНФ, КНФ, СКНФ, критерий Поста и т.д.).

Оптимизация, оптимизация


Другая, ныне забытая дисциплина при изучении данной науки это методы оптимизации логических выражений. Современному разработчику практически не надо заботиться об оптимизации, за него все сделает умный компилятор. Да, напрямую к PAL оптимизация выражений отношения не имеет, но чтобы понять дух эпохи, давайте посмотрим.

На самом деле, методов оптимизации существует довольно много. Самый простой алгебраический, когда преобразуется выражение по законам булевой алгебры (она слегка похожа на обычную, но чуть другая). Можно еще упомянуть теорему Квайна (и метод Квайна Мак-Класки), метод получения тупиковых форм, метод испытания членов, метод импликантных матриц и т.д. Отдельно где-то парит полином Жегалкина на базисе Исключающее-ИЛИ. Методов много. Самыми удобными оказались диаграммы Вейча или, в усовершенствованном виде карты Карно.

Для изучения механизма работы карты Карно можно, опять же, отправить к учебнику Харрис&Харрис глава 2.7. Но очень уж изящное решение! Кроме того, карты Карно иногда спрашивают на собеседованиях. Карты Карно предназначены для визуального безкомпьютерного упрощения выражений с количеством переменных до 6.

Давайте посмотрим на типичную карту Карно, например на 4 переменных:


Самое важное (и в чем состоит суть изобретения Мориса Карно) это то, что соседние клеточки по вертикали и горизонтали отличаются значением ровно одной переменной. А если в соседних ячейках стоят 1 то эту переменную можно исключить. Причем края карты Карно подразумеваются склеенными. Так что наша функция ужимается до



Если попытаться изобразить в 3D карту Карно для 4-х переменных, ее можно будет представить как развертку такого угловатого тора, имеющего 16 граней (нарисовано в OpenSCAD ).


Возвращаясь к PAL


Обогатившись такими знаниями, давайте вернемся к нашим PAL-кам и посмотрим на их внутреннее устройство. И опять же, хорошим подспорьем тут будет учебник Харрис&Харрис Цифровая схемотехника и архитектура компьютера, а конкретно параграф про PAL (п.5.6 Матрицы логических элементов).

При разработке PAL применяется своеобразная графическая запись, первоначально, видимо, предназначенная для ручного кодирования (до появления PALASM).

Давайте возьмем одну ячейку PAL, самого классического PAL16L8:


Итак, сверху вниз идут колонки входные переменные. Из них: 10 шт. это самые настоящие входы, то есть ножки микросхемы, а 6 шт. это возвраты внутри чипа, что позволяет создавать более сложные многоступенчатые выражения. Понятно, что каждая из этих 16 переменных существует в прямой и инверсной форме, итого мы видим 16*2=32 колонки. Слева мы видим ввод одной переменной в матрицу, в прямом и инверсном виде (вывод 2).

Все эти вертикальные переменные одновременно поступают на элементы И, которые могут использовать некоторые их них (а могут не использовать). Схема примерно такая (тут нарисованы две входные переменные и два элемента И):


Мы видим плавкие перемычки (fuse, F1..F4 и F5..F8), которые задают, какие переменные подключены к И. Именно эти fuse и программируются и задают логическую функцию PAL. Картинка получается довольно громоздкая, поэтому применяют такую сокращенную графическую запись (крестики это подключенные входы):


Вернемся к схеме ячейки PAL:


Как видите, в каждой строке (горизонтали) можно собрать функцию И из 16 (32, учитывая инверсию) возможных входов (переменных). То есть такая вот строка это на самом деле 32-входовый элемент И (программируемый). Чуть позже мы посмотрим, как эти соединения выглядят в прошивке.

Далее по схеме мы видим, что в PAL16L8 имеется 7 штук таких И-шек, объединенных по ИЛИ. Структура у ИЛИ у PAL фиксированная (в отличии от PLA и других архитектур) и не программируется. Кстати, обратите внимание, можно использовать меньшее количество ИЛИ (просто не задавая функций у И в ячейке), а вот больше нельзя!

Как несложно заметить, такая конструкция и является физической реализацией ДНФ или SOP (Sum Of Product то есть сумма произведений). Бинго!

Такие устройства стали называться SPLD (Simple PLD) в противовес CPLD (Complex PLD) и FPGA. Про CPLD кстати, имеется отдельная статья.

Такая простая и элегантная конструкция быстро стала чрезвычайно популярной и стала быстро вытеснять остальные семейства. Вот почему такие гранды, как AMD, Philips, National Semiconductor лицензировали ее.

Популярность PAL-ок пришлась на середину 80-х и начало 90-х. Многие производители чипов, прямо в Datasheet-ах публиковали листинги на PALASM для подключения своих микросхем к другим чипам и к разнообразным микропроцессорным шинам. Некоторые платы начала 90-х представляли собой россыпи PAL-ок, на которых были реализованы сложнейшие схемы. Это позволяло быстро выйти на рынок, до начала производства заказных микросхем LSI. (Погуглите, ну скажем, картинки материнской платы EISA i486 Everex Step Mega Cube или Intel iSBC 386. Все узкие микросхемы с бумажками это PAL ).

Сейчас эту нишу занимают CPLD и FPGA.

Компания MMI выпускала книжки, содержащие массу примеров применения своих микросхем, которые сопровождались забавными рисунками (извините, утащил).


Или даже так, ужас-ужас (Текст: В этой конструкции одна PAL16R8 заменяет 15 TTL микросхем малой и средней степени интеграции ):


Семейство PAL было довольно обширное. Существовали разновидности на разное количество ячеек и входов. Были разновидности с триггерами, что позволяло делать на них синхронные схемы и даже маленькие конечные автоматы (например PAL16R8). Существовали универсальные (Versatile) разновидности, в которых можно выбирать комбинаторную логику или триггера (PAL16V8). Существовали чуть более крупные PAL-ки в 24-выводном корпусе, типа PAL22V10. Микросхемы могли быть упакованы в различные корпуса и могли быть разной скорости, от -5 до -25 наносекунд. Наконец, существовали низкопотребляющие варианты и варианты в CMOS исполнении (например Texas Instrument TICPAL или Cypress Semiconductor PALC).


PALASM


Невероятный успех микросхемам PAL фирмы MMI принёс еще один компонент, без которого покорение рынка было бы немыслимо. Это утилита PALASM. С ее помощью создание прошивки для PAL стало делом настолько легким и простым, что любой инженер с нормальной подготовкой, понимал идею практически сразу. В PALASM вводится названия пинов и логические выражения в человеческой алгебраической форме (которые обычно выглядят как ДНФ), после чего PALASM выдает файл прошивки для программатора (JEDEC). Грубо говоря, компания MMI сделала тот шаг, который когда-то в 1950-е прошли обычные компьютеры, при переходе от ручного выписывания двоичных кодов к более понятной записи выражений машинного языка (ассемблера). А сам PALASM первых версий, в свою очередь, был написан на языке FORTRAN IV и распространялся в исходных текстах, что позволяло запускать его на любой тогдашней машине, где был компилятор, вплоть до ранних персоналок под CP/M и DOS. Да, да, когда-то FORTRAN был общим системным языком для переноса программ

Позже вышел PALASM2, синтаксис немного изменился, оброс разнообразными возможностями, появилось некоторое подобие макросов, оптимизатор, поддержка конечных автоматов и даже симулятор. Компания MMI тоже претерпела изменения, ее активы были поглощены AMD, потом выделены в отдельную компанию Vantis, которую позже купила Lattice Semiconductor. Сам входной язык PALASM послужил прообразом множества подобных и более продвинутых языков, его следы есть в ABEL, CUPL и в VHDL (а вообще-то он сам вырос из FORTRAN-а ).

Наиболее развитый PALASM это версия PALASM4 v1.5a от AMD 1992 года. Понятно, что такая ретро-программа требует для запуска ретро-DOS. Но к счастью, этот вопрос давно решен. Тут нам поможет прекрасная утилита DOSBOX, позволяющая запускать DOS-программы даже на современном Windows 10 64-бит. Хотя DOSBOX вообще-то, предназначен для запуска ретро-игр, но и не-игровые DOS-утилиты неплохо в нем живут и PALASM не исключение. Компания AMD сделала широкий жест и отпустила PALASM в Public Domain, так что пользоваться им можно совершенно легально.

Тут можно найти инструкцию, как поставить PALASM на DOSBOX и также скачать саму программу. Конфигурационный файл DOSBOX хранится в профиле пользователя
C:\Users\%USERNAME\AppData\Local\DOSBox\ dosbox-0.74.conf
Лучше дополнить секцию [autoexec]:

......[autoexec]# Lines in this section will be run at startup.# You can put your MOUNT lines here.@echo offmount c c:\DOSBOXset PALASM=C:\PALASMset PATH=%PATH%;C:\PALASMC:

Из под Linux тоже можно запустить PALASM например с помощью того же DOSBOX или похожего DOSEMU.

Из PAL в GAL


При всём своём удобстве PAL-ы имели и некоторые проблемы. Одна из таких проблем состоит в том, что PAL это однократно программируемое устройство (OTP) с плавкими титано-вольфрамовыми перемычками (Ti-W fuse). При появлении исправлений и замене прошивки приходилось старую микросхему просто выкидывать. Так что многие производители стали предлагать стираемые PAL-ки. История тут длинная, можно, например, вспомнить УФ-стираемые чипы с окошечком, или тот факт, что очень хотелось сохранить совместимость с парком программаторов и алгоритмами прошивки оригинальных PAL (серии PALCE и PEEL от International CMOS Technology (ICT) Corporation) и т.д.

Наконец фирма Lattice Semiconductor в 1985 г. выпустила семейство GAL, которое можно считать своеобразной вершиной PAL-строения. Подобно исходным MMI PAL-ам c буковкой V ( Versatile) (например PAL16V8) чипы GAL (соответственно название будет GAL16V8) могут принимать прошивки ВСЕХ (ну почти) моделей PAL, а вдобавок чип GAL электрически стираемый и многократно прошиваемый. Устройства GAL практически вытеснили все остальные SPLD, за исключением, пожалуй Atmel (нынче Microchip) серии ATF (аналогичный чип у них будет называться ATF16V8).


Для переноса JEDEC файлов (прошивок) старых PAL фирма Lattice Semiconductor выпустила утилиту PALTOGAL (тоже бесплатную и тоже под DOS).

ПРМЕЧАНИЕ: Фирма Lattice Semicronductor рекомендует использовать для разработки ABEL (пакет ispLEVER), а фирма Atmel CUPL (WinCUPL или ProChip Designer). National Semiconductor предлагала свой Opal JR. Но мы в ретро-целях останемся верны PALASM.

ПРИМЕЧАНИЕ2: (для совсем нердов) У GAL все же есть некоторые мелкие отличия, связанные с тем, что MMI PAL это ТТЛШ микросхема, а GAL CMOS.

И конечно же, для прошивки микросхем GAL (и ATF) потребуется специальный программатор. Существуют и PAL-ы, прошиваемые по JTAG, но это редкость (Lattice ispGAL). Хотя программатор для GAL можно сделать самостоятельно (ищется по именам GALBLAST и ATFBLAST), всё же лучше приобрести готовый. Например, TL866 (известный еще как Minipro), которыми забит Aliexpress:


Сами чипы GAL или ATF можно приобрести на том же Aliexpress. Цена за десяток может доходить до 5$ и менее. Не стоит ожидать чудес, чипы будут б/у (помним, что их можно и нужно(!) стереть) и могут иметь следы пайки и маркировку краской или лазером от погибших неведомых устройств, откуда их вытащили трудолюбивые китайцы. Lattice GAL сняты с производства в 2011, но запасов из старой техники хватит еще на пару десятков лет. ATF еще выпускаются.

7-SEG LED


Если помните, в начале статьи мы собирались зажечь светодиод. А точнее не один, а целых семь! Да- да, речь пойдет о 7-сегментном индикаторе, а точнее о реализации банального дешифратора HEX-to-7SEG.


Почему-то так сложилось, что готовых микросхем с такой функцией не так уж и много. Есть десятичные BCD дешифраторы (т.е. без шестнадцатеричных символов ABCDEF), например 7446/7447/7448/7449 и 74246/74247/74248/74249). Полных HEX дешифраторов не так много например Motorola MC14495 Hexadecimal-to-Seven Segment Driver или Fairchild DM9368.

Но цены на PAL (GAL) упали настолько, что сделать дешифратор на SPLD дешевле и быстрее! Вот и давайте для практики его и сделаем, включая шестнадцатеричные цифры ABCDEF (Нет, ЕГГОГ мы декодировать не будем ). Отличное приложение наших олдскульных знаний по матлогике и старым программируемым микросхемам.


Построение такого дешифратора является каноническим примером комбинационной логики и типовой лабораторной работой. В учебнике Харрис&Харрис Цифровая схемотехника и архитектура компьютера пункт 2.7.2 Логическая минимизация на картах Карно содержит пример 2.10 построения такого дешифратора, но только BCD.

На Википедии есть статья, в которой даже приведена нужная нам таблица перекодировки для полного HEX.

Вот эта же самая таблица, только здесь зажигание нужного светодиода отмечено красными клеточками. Как видите, никакой регулярности, только таблица, только хардкор!


Давайте для примера рассмотрим сегмент D (нижний). Как мы видим, он загорается 11 раз из 16 возможных входных комбинаций (вертикаль d). Если выписать ДНФ, то формула будет такой:



Но здесь есть проблема! Как мы писали выше, матрица обычных PAL (например в PAL16L8) имеет только 7 ИЛИ (сложений), а у нас 11. Надо или применять более сложный чип или выражение придется оптимизировать.

ПРИМЕЧАНИЕ: В PALASM4 есть встроенный оптимизатор. Вероятно, он смог бы упростить это выражение, только надо эту оптимизацию включить. Но для сохранения ретро-духа пройдем этот этап вручную.

Давайте оптимизируем это выражение по карте Карно. Можно сделать это вручную и получить удовольствие, а можно воспользоваться сайтами, где на JavaScript реализованы эти алгоритмы, например:


На втором сайте в качестве переменных можно вводить: D3,D2,D1,D0. Далее, вводятся номера единичных минитермов (фактически, это входные числа, для которых загорается сегмент D), у нас это 0,2,3,5,6,8,9,11,12,13,14 (0,2,3,5,6,8,9,B,C,D,E) проверяем по красным клеточкам.


Как видите, наше выражение для сегмента D превращается в короткое:


Тут всего 5 штук ИЛИ вместо 11 так что выражение влезает в ячейку PAL. Проделаем это для всех сегментов.

Получается такой файл PALASM 7SEG.PDS
;PALASM Design Description;-------------- Declaration Segment ------------TITLE 7-SEG LED decoderPATTERN 7SEG.PDSREVISION AAUTHOR ALECVCOMPANY HABRDATE 01/01/90CHIP DECODER PAL16L8;-------------- PIN Declarations ---------------;PINS  1  2  3  4  5  6  7  8  9  10      NC D0 D1 D2 D3 NC NC NC NC GND;PINS 11 12 13 14 15 16 17 18 19  20      NC NC G  F  E  D  C  B  A VCC;--------------- Boolean Equation Segment ------EQUATIONS/A = /D0*/D2 + /D0*D3 + D1*D2 + D1*/D2*/D3 + D0*D2*/D3 + /D1*/D2*D3/B = /D2*/D3 + /D0*/D2 + /D0*/D1*/D3 + D0*D1*/D3 + D0*/D1*D3/C = D0*/D1 + D0*/D2 + /D1*/D2 + D2*/D3 + /D2*D3/D = D2*/D1*D0 + /D3*/D2*/D0 + /D2*D1*D0 + D2*D1*/D0 + D3*/D1/E = /D0*/D2 + D2*D3 + /D0*D1 + D1*D3/F = /D0*/D1 + /D2*D3 + D1*D3 + /D0*D2 + /D1*D2*/D3/G = D1*/D2 + D0*D3 + /D2*D3 + /D0*D1 + /D1*D2*/D3;------------- Simulation Segment -------------SIMULATIONTRACE_ON A B C D E F GSETF /D3 /D2 /D1 /D0    ; 0SETF /D3 /D2 /D1  D0    ; 1SETF /D3 /D2  D1 /D0    ; 2SETF /D3 /D2  D1  D0    ; 3SETF /D3  D2 /D1 /D0    ; 4SETF /D3  D2 /D1  D0    ; 5SETF /D3  D2  D1 /D0    ; 6SETF /D3  D2  D1  D0    ; 7SETF  D3 /D2 /D1 /D0    ; 8SETF  D3 /D2 /D1  D0    ; 9SETF  D3 /D2  D1 /D0    ; ASETF  D3 /D2  D1  D0    ; BSETF  D3  D2 /D1 /D0    ; CSETF  D3  D2 /D1  D0    ; DSETF  D3  D2  D1 /D0    ; ESETF  D3  D2  D1  D0    ; FTRACE_OFF

Откомпилируем файл .PDS в файл .JED с помощью PALASM:


Очень интересно, как PALASM разложил наши выражения на плавкие перемычки. Для этого можно заглянут в файл Fuse plot (рисунок перемычек).

Fuse plot файл 7SEG.XPT
PALASM4  PAL ASSEMBLER   - MARKET RELEASE 1.5a (8-20-92) (C) - COPYRIGHT ADVANCED MICRO DEVICES INC., 1992TITLE   :7-SEG LED decoder        AUTHOR :ALECV                    PATTERN :7SEG.PDS                 COMPANY:HABR                     REVISION:A                        DATE   :01/01/90                 PAL16L8DECODER                   11  1111  1111  2222  2222  2233       0123  4567  8901  2345  6789  0123  4567  8901  0    ----  ----  ----  ----  ----  ----  ----  ----    1    -X--  ----  -X--  ----  ----  ----  ----  ----    2    -X--  ----  ----  X---  ----  ----  ----  ----    3    ----  X---  X---  ----  ----  ----  ----  ----    4    ----  X---  -X--  -X--  ----  ----  ----  ----    5    X---  ----  X---  -X--  ----  ----  ----  ----    6    ----  -X--  -X--  X---  ----  ----  ----  ----    7    XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    8    ----  ----  ----  ----  ----  ----  ----  ----    9    ----  ----  -X--  -X--  ----  ----  ----  ----    10   -X--  ----  -X--  ----  ----  ----  ----  ----    11   -X--  -X--  ----  -X--  ----  ----  ----  ----    12   X---  X---  ----  -X--  ----  ----  ----  ----    13   X---  -X--  ----  X---  ----  ----  ----  ----    14   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    15   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    16   ----  ----  ----  ----  ----  ----  ----  ----    17   X---  -X--  ----  ----  ----  ----  ----  ----    18   X---  ----  -X--  ----  ----  ----  ----  ----    19   ----  -X--  -X--  ----  ----  ----  ----  ----    20   ----  ----  X---  -X--  ----  ----  ----  ----    21   ----  ----  -X--  X---  ----  ----  ----  ----    22   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    23   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    24   ----  ----  ----  ----  ----  ----  ----  ----    25   X---  -X--  X---  ----  ----  ----  ----  ----    26   -X--  ----  -X--  -X--  ----  ----  ----  ----    27   X---  X---  -X--  ----  ----  ----  ----  ----    28   -X--  X---  X---  ----  ----  ----  ----  ----    29   ----  -X--  ----  X---  ----  ----  ----  ----    30   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    31   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    32   ----  ----  ----  ----  ----  ----  ----  ----    33   -X--  ----  -X--  ----  ----  ----  ----  ----    34   ----  ----  X---  X---  ----  ----  ----  ----    35   -X--  X---  ----  ----  ----  ----  ----  ----    36   ----  X---  ----  X---  ----  ----  ----  ----    37   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    38   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    39   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    40   ----  ----  ----  ----  ----  ----  ----  ----    41   -X--  -X--  ----  ----  ----  ----  ----  ----    42   ----  ----  -X--  X---  ----  ----  ----  ----    43   ----  X---  ----  X---  ----  ----  ----  ----    44   -X--  ----  X---  ----  ----  ----  ----  ----    45   ----  -X--  X---  -X--  ----  ----  ----  ----    46   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    47   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    48   ----  ----  ----  ----  ----  ----  ----  ----    49   ----  X---  -X--  ----  ----  ----  ----  ----    50   X---  ----  ----  X---  ----  ----  ----  ----    51   ----  ----  -X--  X---  ----  ----  ----  ----    52   -X--  X---  ----  ----  ----  ----  ----  ----    53   ----  -X--  X---  -X--  ----  ----  ----  ----    54   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    55   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    56   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    57   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    58   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    59   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    60   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    61   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    62   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX    63   XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX  XXXX     SUMMARY -------      TOTAL FUSES BLOWN    = 1262

Этот файл лучше рассматривать одновременно с документацией на PAL16L8. Ее можно найти, например, на сайте Texas Istruments: pal16r8am.pdf страница 5.

Сегмент D у нас выведен на 16-й вывод микросхемы. Если мы посмотрим схему PAL, то за него отвечают перемычки, начиная с номера 768. Как несложно догадаться 768 / 32 = 24 это 24-я строка. Сама она отвечает за управление выходом и поэтому пустая. Логические выражения начинаются с перемычки 800, то есть строки 25. Если посмотреть на схему, то в 25-й строке собираются по И: прямой вход D0 (вывод микросхемы 2), инверсия D1 (вывод 3) и прямой вход D2 (пин 4). Это в точности соответствует первому минитерму в формуле для сегмента D! Остальные минитермы из выражения также транслируются в перемычки и в конце объединяются по ИЛИ. Так что PALASM, не мудрствуя лукаво, просто переводит наши ДНФ-выражения в прошивку один в один.

Именно таким способом проектировали прошивки до появления PALASM, в самой старой документации MMI как раз описан этот способ. Надо отметить, что другие семейства, например PLA, не получили вообще или получили утилиты класса PALASM довольно поздно (например ICT PEEL Array PLACE) и не стали такими популярными.

Для программирования GAL полученный файл 7SEG.JED отконвертируем утилитой PALTOGAL:

PALTOGAL C2 R 7SEG.JED 7SEG_GAL.JED

При подключении индикатора к FPGA (на VHDL и Verilog) или к микроконтроллеру логические функции раскладывать нет необходимости и просто используют таблицу. Можно погуглить или посмотреть на Youtube, поиск Семисегментный индикатор.

Практика


Давайте соберем модель декодера семисегментного индикатора (и сдадим курсовик, ха-ха). В качестве генератора импульсов применим обычную микросхемку NE555 (К1006ВИ1). Для наблюдения нам нужен период примерно 1 секунда. Для делителя на 16 в коде 1-2-4-8 можно применить что-нибудь типа К155/К555 ИЕ5 или ИЕ7 (совсем круто было бы сделать счетчик тоже на GAL, но это в следующий раз). В коробочке нашлась К555ИЕ7 (SN74LS193), значит так тому и быть. На вывод R подадим ноль, на выводы V и -1 единицу, на вывод +1 тактовые импульсы с NE555. Счетчик начал считать. Возьмем семисегментный индикатор с общим анодом и нашу GAL. Перед этим сотрем её и прошьем JEDEC файлом. Индикатор попался LTS-4801WC, OK. Выводы счетчика Q0,Q1,Q2,Q3 соединим со входами D0,D1,D2,D3 GAL, а выходы GAL с катодами индикатора через 7 резисторов на 330 Ом.

Получается как-то так:


Заключение


Итак, мы окунулись в историю программируемых логических микросхем (PLD). Мы увидели, что за конструкцией PAL скрывается Древняя Могучая Теория. Выяснилось, что даже сейчас, вполне еще можно применять эти устройства в небольших самоделках. Мы рассмотрели вполне боевой workflow, как это сделать. И да, PAL и GAL совместимы с олдовой теплой 5V TTL электроникой.

Конечно, в такой короткой статье невозможно отразить все аспекты. Вот кратко, про что мы не рассказали:

Не рассмотрели работу симулятора. В PALASM (2 и 4) встроен довольно мощный симулятор, практически язык программирования, с циклами, условными операторами и т.д.

Не рассмотрели построение на PALASM конечных автоматов. Как-нибудь в другой раз.

Не рассмотрели тонкости и ограничения оптимизации, но зато сделали оптимизацию вручную, по карте Карно. Это исключительно в ретро-целях! Сейчас так никто уже не делает (как и сами разработи на PAL).

Не ответили на вопрос: можно ли вводить в PALASM выражения не в ДНФ? Конечно можно. Если выражение синтаксически корректно, PALASM скорее всего его поймет. Но внутри все равно преобразует в ДНФ для воплощения в перемычках.

Не рассмотрели вопрос программирования (прошивки) исходных MMI PAL. Проблема здесь в том, что это однократно программируемые микросхемы и сейчас довольно сложно найти чистые PAL. И их не стереть. А программировались они специальным программатором (например, российский Стерх это умеет). Изначально у MMI был даже метод прошивать PAL как ПЗУ 512x4 на старых программаторах с помощью специальной personality card у первых PAL было ровно 2048 перемычек, но ныне этот способ утрачен.

И еще много чего.
Подробнее..

Maple BUS в ореховой скорлупе или Периферия SEGA Dreamcast, как сделать

01.02.2021 10:05:55 | Автор: admin

И сразу к делу!

Протокол Maple BUS симметричный, то есть имея одну хорошую реализацию например HOST'а эту же реализацию можно использовать и как DEVICE. Проще, - можно читать джойстик, а можно им прикинуться.

Описание протокола (аппаратная часть).

Интерфейс Maple BUS двух-проводный. SDCKA/SDCKB, каждая из линий на определенных этапах выполняет роль как "передающая данные" и так и "защелкивающая данные".

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

Всего паттернов 5 видов:

START - указывает на начало передачи данных (4-ре клока SDCKB в то время пока SDCKA в низком уровне).

Пакет всегда должен заканчиваться паттерном END (2-ва клока SDCKA пока SDCKB в низком уровне):

Occupancy паттерн - указывает на старт режима прослушивания шины (8-мь клоков SDCKB пока SDCKA в низком уровне). Переход линии HI->LO SDCKA после получения этого паттерна указывает на начало режима, LO->HI указывает на завершение. Этот режим используется для взаимодействия со световым пистолетом (Light GUN - Func. FT7):

RESET - аппаратный перезапуск устройства (14-ть клоков SDCKB пока SDCKA в низком уровне, только для DEVICE).

Теперь рассмотрим как по шине передаются данные.

Биты данных передаются фазами. В четной фазе линия данных - SDCKB, а клок - SDCKA, в нечетной наоборот (этот фрагмент тоже назовем паттерном :) ).

Величина таймаута на ответ от устройства после запроса хоста 1мс:

Помним, что например к джойстику можно подключать VMU, вибропак, микрофон...

Устройства подключаемые непосредственно к Maple BUS называются Device, а устройства подключаемые к Device называются Expansion Device, общение между Device и Expansion Device осуществляется средствами протокола LM-Bus. Expansion устройств можно подключить до 5-ти, хотя я не видел ни одного устройства в котором это было реализовано, а в чипах (например 315-6211-AB) "выведено наружу" только под 2-ва (хотя в программной части протокола под идентификацию EXP-DEV выделено пять бит, но тут честно говоря нужно уточнить, VMU например содержит память и LCD дисплей, это уже два Exp. устройства).

LM-BUS это что то типа суррогата Maple BUS, то есть шина на которую DEVICE напрямую переключает шину Maple BUS согласно тому какой Exp. DEVICE выбран HOST'ом.

LM-BUS тема отдельного разговора, отвлекаться не буду, перейдем к программной реализации протокола.

Программная часть протокола.

Как я уже писал выше данные передаются пакетами, рассмотрим пакет поближе:

  • COMMAND - команда, может принимать значения от 0x01 до 0xFE (см. возможные значения в коде ниже "maplebus.h").

maplebus commands
//HOST#defineDeviceRequest0x01#defineAllStatusRequest0x02#defineDeviceReset0x03#defineDeviceKill0x04#defineGetCondition0x09#defineGetMediaInfo0x0A#defineBlockRead0x0B#defineBlockWrite0x0C#defineGetLastError0x0D#defineSetCondition0x0E#defineFT4Control0x0F#defineARControl0x10#defineTransmitAgain0xFC//Device#defineDeviceStatus0x05#defineDeviceAllStatus0x06#defineDeviceReply0x07#defineDataTransfer0x08#defineARError0xF9#defineLCDError0xFA#defineFileError0xFB#defineTransmitAgain0xFC#defineCommandUnknown0xFD#defineFunctionTypeUnknown0xFE
  • DEST. AP - адрес назначения пакета (для какого устройства пакет).

  • ORIG. AP - от кого пакет.

Для AP справедлива следующая таблица:

PO[1:0] - Номер порта (A - 00, B - 01, C - 10, D - 11).

D/E - (1 - Device, 0 - Expansion Device или PORT).

LM[4:0] - (1 - Exp. DEVICE подключено, 0 - Слот Exp. пуст).

  • DATA SIZE - размер данных в пакете в 32-х битных чанках.

  • DATA - Состав пакета.

  • CRC - побайтный XOR всех данных включая COMMAND, AP, DATA SIZE, DATA.

"Общение" между HOST и DEVICE начинается с запроса DeviceRequest, в нем хост указывает какой порт он опрашивает, устройство, первый раз после включения или сброса "увидев" номер порта присваивает его себе (A/B/C/D).

Отвечать на данный запрос любое устройство обязано статусом (DeviceStatus answer):

Device ID - содержит функциональные возможности периферии (Device ID содержит блок FT, состав включенных битов в этом блоке определяет функции которые поддерживает устройство и FD - параметры поддерживаемых функций).

Device Functions
/*Device functions*/#define CONTROLLER    MAKE_DWORD(0x00000001)      //FT0 : Controller Function#define STORAGE            MAKE_DWORD(0x00000002)      //FT1 : Storage Function#define LCD                    MAKE_DWORD(0x00000004)      //FT2 : B/W LCD Function#define TIMER                MAKE_DWORD(0x00000008)      //FT3 : Timer Function#define AUDIO_INPUT    MAKE_DWORD(0x00000010)      //FT4 : Audio input device Function#define AR_GUN            MAKE_DWORD(0x00000020)      //FT5 : AR-Gun Function#define KEYBOARD        MAKE_DWORD(0x00000040)      //FT6 : Keyboard#define GUN                    MAKE_DWORD((unsigned int)0x00000080)        //FT7 : Light-Gun Function#define VIBRATION        MAKE_DWORD((unsigned int)0x00000100)        //FT8 : Vibration Function#define MOUSE                MAKE_DWORD((unsigned int)0x00000200)        //FT9 : Pointing Function#define EXMEDIA            MAKE_DWORD((unsigned int)0x00000400)        //FT10 : Exchange Media Function#define CAMERA            MAKE_DWORD((unsigned int)0x00000800)        //FT11 : Camera Device Functio

Destination code - указывает на целевой регион использования устройства.

Product name - название устройства (например {'D','r','e','a','m','c','a','s','t',' ','C','o','n','t','r','o','l','l','e','r', ' ',' ',' ',' ',' ',' ',' ',' ',' ',' '} - 30 байт).

License - кому принадлежит лицензия ( {'P','r','o','d','u','c','e','d',' ','B','y',' ','o','r',' ','U','n','d','e','r',' ','L','i','c','e','n','s','e',' ','F','r','o','m',' ','S','E','G','A',' ','E','N','T','E','R','P','R','I','S','E','S',',','L','T','D','.',' ',' ',' ',' ',' ',} -60 байт ).

Min./Max. current - соотв. минимальное и максимальное потребление устройства (1мА = 10 единиц, 43мА => 0x1AE).

Далее в пакете может идти "свободный статус устройства" (на изображении не указано, так как этот кусочек не обязателен), для джойстика он выглядит так: 40 байт "Version 1.000,1998/05/11,315-6125-AB Analog Module: The 4th Edition. 05/08".

То, какие команды применимы к устройству нам показывает блок Device ID.

К FT0, CONTROLLER, применима команда GetCondition - получить состояние кнопок/триггеров и аналоговых стиков геймпада. То в каких битах расположены какие значения указано всё в том же блоке Device ID. В частном случае, для геймпада Device ID будет выглядеть так:

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

Ra/La/Da/Ua - Право/Лево/Вниз/Вверх (цифровой "крестик").

Start/A/B/X/Y - соотв кнопки.

A1, A2 - аналоговые курки

A3 и A4 - положение "стика".

Вот собственно и всё что нужно знать для реализации контроллера.

Реализация (аппаратная часть)

В общем то можно взять линии SDCKA и SDCKB и прикрутить на прерывания микроконтроллера и чисто программно реализовать, однако если МК медленный, то успевать не будет, и это не самое главное, во первых "плотность данных" для разных устройств разная и если например джойстик работать будет на одной программной реализации, то не факт что будет работать вибропак или карта памяти, во вторых в программной реализации определение ошибок внутри фрейма как и скажем команда "аппаратный сброс" не реализуемы, поэтому правильнее будет сделать аппаратный кусочек приемника, а отправлять и микроконтроллером можно.

Возьмем CPLD попроще (EPM3032) и реализуем xMAPLE:

SDCKA/SDCKB - вход линий Maple BUS.

GCLK - внешний CLK 16-48MHz.

INHTxD - сигнал блокировки работы приемника, 1 - игнорировать события на шине, 0 - нормальное функционирование.

RxD - идет прием пакета.

nSTRCV- начат прием пакета (Rising Edge).

nDLatch - Негативный импульс для "защелки данных" (сигнализирует о том что на линии данных Q[7..0] присутствует следующий полученный байт).

Q[7..0] - шина данных.

EOP - получен паттерн END (конец приема пакета).

FERR - обнаружена ошибка при приеме пакета.

nRST - подключается напрямую к микроконтроллеру - если получен RESET паттерн, - 0.

И общий вид:

Пишем это на верилог'е (3 файлика, надеюсь догадаетесь как это соединять):

SMAPLE.v
module SMAPLE(input GCLK,//MCU Generated 16MHz clock inputinput INHTxD,//Inhibit Input Data (User Can disable XMAPLE Detect Signals While MCU Transmit DATA)input SDCKAi,//Data/Clock A Lineinput SDCKBi,//Data/Clock B Lineoutput RxD,//Receive on progress (While receive is 1)output [7:0]Q// Output data bus (MCU can read valid data on this //port in time 200uS after data latch Negative Pulse received),output nSTRCV,//Receive start, negative pulse - Outputoutput OCPYi,//Occupancy packet received - Outputoutput nRST,//Reset packet received - Outputoutput FERR,//Frame error - Outputoutput EOPi,//End Of Packed received - Outputoutput nDLatch//New Data latched on BUS (Negative Pulse));/*Control Register*/reg rRxD = 0;assign RxD = rRxD;reg rFERR = 0;assign FERR = rFERR;wire nWE;assign nDLatch = (EOPi & nWE);wire iFERR;/* Align Data Packet */reg rENA = 1'b0;reg rENB = 1'b0;always @(posedge GCLK or negedge nRST) beginif(!nRST) beginrENA <= 1'b0;rENB <= 1'b0;end else beginrENA <= SDCKAi;rENB <= SDCKBi;endendalways @(posedge GCLK or negedge nRST) beginif(!nRST) beginrFERR <= 0;rRxD <= 0;end else beginif(!EOPi)// && !INHTxD) rRxD <=0 ;else beginif(!iFERR) rFERR <= 1;if(!nSTRCV) beginrFERR <= 0;rRxD <= ~INHTxD;endendendendline_monitor line_monitor(.GCLK(GCLK),//Global Clock - Input.SDCKA(SDCKAi|INHTxD),//CLOCK/DATA Line A disabled by data transmit - Input.SDCKB(SDCKBi|INHTxD),//CLOCK/DATA Line B disabled by data transmit - Input.RxDr(RxD),//Data Receive in progress - Input.RxD(nSTRCV),//Receive start, negative pulse - Output.OCPY(OCPYi),//Occupancy packet received - Output.RESET(nRST),//Reset packet received - Output.FERR(iFERR),//Frame error - Output.EOP(EOPi),//End Of Packed received - Output.ENA(rENA),//CLOCK For Line B.ENB(rENB)//CLOCK For Line A );/*Receive Maple Frame*/maple_receive maple_receive(.SDCKA(SDCKAi),//CLOCK/DATA Line A disabled by data transmit - Input.SDCKB(SDCKBi),//CLOCK/DATA Line B disabled by data transmit - Input.ENA(rENA),//CLOCK For Line B.ENB(rENB),//CLOCK For Line A .RCV(RxD),//Receive in progress, 1 - receive - Input.Dout(Q[7:0]), //Received data byte - Output.nWE(nWE),//Write Latch - Output.RxDi(nSTRCV),//Receive start, negative pulse - Input.INHTxD(INHTxD)//Inhibit Input Data (User Can disable XMAPLE Detect Signals While MCU Transmit DATA));endmodule
line_monitor.v
module line_monitor(input GCLK,input SDCKA,input SDCKB,input  RxDr,//Data Receive in progress - Inputoutput RxD,output OCPY,output RESET,output FERR,output EOP,input ENA,input ENB);reg [3:0] countA = 0;reg [2:0] countB = 0;reg [3:0] pcount = 0;reg rEOP = 1'b1;assign EOP = rEOP;assign RxD = (pcount == 4'h4) ? 1'b0 : 1'b1;assign OCPY = (pcount == 4'h8) ? 1'b0 : 1'b1;assign RESET = (pcount == 4'hE)? 1'b0 : 1'b1; //Output reset signal does not need to check for FERRassign FERR = (!((RxD & OCPY & RESET) && pcount[3:1])) | (!RxDr & !rEOP);//assign EOP = (eopcount == 3'h2) ? 1'b0 : 1'b1;always @(posedge SDCKA) pcount <= countA;always @(posedge SDCKB) rEOP <= !(countB == 3'h2);//Patterns//PATTERN Counter Managingalways @(posedge ENA or negedge ENB) beginif (ENA) begincountA <= 0;endelse begincountA <= countA + 4'h1;endend //EOP Counter Managingalways @(posedge ENB or negedge ENA) beginif (ENB) begin countB <= 0;endelse begin countB <= countB + 3'h1;endend //synopsys translate_off//synopsys translate_onendmodule
maple_receive.v
module maple_receive(input SDCKA,//CLOCK/DATA Line Ainput SDCKB,//CLOCK/DATA Line Binput ENA,//CLOCKinput ENB,//CLOCKinput RCV,//Receive in progress, 1 - validoutput [7:0]Dout, //received data outputoutput nWE,input RxDi,input INHTxD//Inhibit Input Data (User Can disable XMAPLE Detect Signals While MCU Transmit DATA));reg [3:0] dataA = 4'h0;reg [3:0] dataB = 4'h0;reg [1:0]countB = 2'b00;reg rLastBitCounted = 1'b1;//B LINE//Dout[1] = SDCKA Means Major version 1.//Dout[0] = SDCKB Means Minor version .0//And version result = 1.0assign Dout[1] = !INHTxD ? dataB[0] : SDCKA;assign Dout[3] = dataB[1];assign Dout[5] = dataB[2];assign Dout[7] = dataB[3];//A LINEassign Dout[0] = !INHTxD ? dataA[0] : SDCKB;assign Dout[2] = dataA[1];assign Dout[4] = dataA[2];assign Dout[6] = dataA[3];assign nWE = (dtaLock);always @(negedge ENA)begindataB[3:1] <= dataB[2:0];dataB[0] <= SDCKB;if(RCV) begincountB <= countB + 2'b1;end else begincountB <= 2'b11;endendalways @(negedge ENB)begindataA[3:1] <= dataA[2:0];dataA[0] <= SDCKA;rLastBitCounted <= !countB[0] | !countB[1];endwire dtaLock = rLastBitCounted;endmodule

Чтобы не "развлекаться с проводочками" накидал Eval Board.

Общий вид по блокам:

Полная схема модуля.

Gerber фалы.

Внешний вид:

И посадочное место под Eval...

Gerber файлы.

И соединяем всё это вместе:

Реализация устройства.

Железки есть, схемы есть, переходим к реализации.

Для начала заделаем небольшой код чтобы чтобы геймпад Dreamcast прикидывался геймпадом XBOX360 (поскольку я заботливо "выкусил хэндшейк" с XBOX360, данная реализация на приставке работать не будет только на ПК).

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

Верхняя часть (GERBER), нижняя часть (GERBER).

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

Для этой цели можно к примеру разобрать разъем SD вот как-то так:

Прикинем как должен работать алгоритм... не буду останавливаться на том как работает USB HID, опишу общую схему опроса устройств на шине MAPLE.

И собственно архив с исходниками.

Компилируем определив константы:

  • USE_STDPERIPH_DRIVER - использовать стандартную библиотеку периферии от ST.

  • STM32F10X_MD - чип Medium Density.

  • MAPLE_HOST - библиотека MAPLE работает в режиме HOST.

  • USB_HID - собрать целевое HID устройство.

Прошиваем, подключаемся:

и видим вот такую картину (не забываем что необходимо поставить Microsoft Xbox 360 Accessories, а ещё помним что геймпад у нас работает в режиме XInput... кому лень разбираться, можно скачать уже откомпилированную прошивку):

А теперь развернем xMAPLE в обратную сторону и...

Подключим мышь от ПК к DREAMCAST.

Мышь, - FT9 : Pointing Function.

Что нам нужно, DeviceID и состав команды GetCondition, чтоб собирать пакет с данными.

Mouse DeviceID:

Стандартная мышь Dreamcast содержит 3 кнопки: A,B,W, дельты смещения по осям X/Y: AC1,AC2 (ball) и смещение "колеса": AC3 (wheel).

AC1,AC2,AC3 - десяти-битные величины плюс флаг переполнения.

Вот так выглядит пакет данных:

AOV2, AOV1, AOV0 - флаги переполнения для AC3, AC2, AC1 соответственно.

Для удобства накидаем схемку адаптера PS/2 для нашей борды:

...разводим, получаем gerber'ы...

И с завода нам приезжает вот это:

Ну и чтобы совсем удобно, накидаем вот такую схему, если брать провод от оригинального пада, то просто подключаемся к разъему и УРА.

"Рисуем" gerber'ы и получаем вот такой переходник:

Собираем весь этот "огород" вместе:

Компилируем прошивку (ниже архив с исходниками) не забывая объявить константы препроцессора:

  • USE_STDPERIPH_DRIVER - использовать стандартную библиотеку периферии от ST.

  • STM32F10X_MD - чип Medium Density.

  • MAPLE_DEVICE - библиотека MAPLE работает в режиме DEVICE.

  • EN_MOUSE - собрать целевое HID устройство.

  • MOUSE_CALLBACK - обработать функцию чтения мыши в процессе ожидания запроса от HOST.

  • EXTI9_5_CALLBACK - передавать в код пользователя системные прерывания EXTI5-EXTI9 библиотеки MAPLE_BUS.

(исходники, скомпилированный HEX).

К слову, если вместо EN_MOUSE в данных исходниках определить константу EN_CONTROLLER, то мы получим довольно забавную штуку, переходник превращающий PS/2 мышь в контроллер DREAMCAST, собственно специально сделал, потому как мышью в меню DREAMCAST управлять нельзя. Поэтому чтобы наглядно увидеть работоспособность исходников и оборудования в целом не запуская скажем "HALF LIFE для проверки" можно прошить откомпилированный код с константой EN_CONTROLLER и управлять внутри меню мышкой PS/2.

Прошиваем, подключаемся к DREAMCAST и оно работает!!!

Вот собственно и всё что хотел поведать. Однако я не рассказал о (надеюсь ещё расскажу :) ):

  • Как работать с VibroPAK.

  • Как реализовать Memory Unit (хотя на борде расширения PS/2 SPI EEPROM память можно установить и работать с ней).

  • И у меня остались комплекты печатных плат и трём желающим "попробовать свои силы" могу отправить комплекты печатных плат за стоимость почты.

Удачного дня! Отличного настроения и взаимопонимания!!!

Подробнее..

Реализация процессорной архитектуры из книги Чарльза Петцольда Код. Тайный язык информатики

06.02.2021 16:23:57 | Автор: admin

О книге

Наверное многие, из тех, кто увлекается изучением того, как работает компьютер на самом низком уровне читали такие книги как: Таненбаум "Архитектура компьютера" или Харрис, Харрис "Цифровая схемотехника и архитектура компьютера", которые безусловного являются объемлющими трудами и хорошими книгами для обучения. Но если вы не являетесь инженером, но всё равно хотите погрузиться в мир цифровых вычислений и более глубоко понять то, а как же работает компьютер. В этом вам сможет помочь книга Чальза Петцольда "Код. Тайный язык информатики", которая начинает свой рассказ от причин, по которым людям понадобился обмен информацией и её обработка , с какими проблемами при этом столкнулись и заканчивает рассказам об устройстве реальных операционных систем и процессоров и их архитектуру. При этом автором был отлично подобран уровень абстракции, которого он придерживался при написании. В каких-то моментах подробно описывая работу элементарных частей процессора, а в других рассказывая простыми словами о сложных вещах.

Описание архитектуры

Так в главе 17 "Автоматизация" автор, начиная с описания с того, как автоматизировать суммирование данных с промежуточным сохранением состояния между операциями, переходит к реализации архитектуры с набором команд, достаточным для исполнения практически любых вычислений. Эта архитектура отличается от используемых тем, что размерности шины адреса, шины данных, и машинного слова имеют разные размерности. Обращение к памяти является является побайтовой, что является достаточно обычным. Машинное слово фиксированной длины little-endian состоит из 12 бит или 3 байта, разделённых на две части. Младший байт слова содержит код операции содержит номер команды, старшие два - её аргумент. Из-за ограничений тракта данных на один такт процессора тратится 4 такта счётчика, из которых 3 уходят на чтение команды и 1 на исполнение вычислений. Архитектурой описывается один программно доступный регистр общего назначения, в котором сохраняется результаты выполнения операций с АЛУ, а также 2 регистра-флага, являющихся аналогами C и Z регистра CPSR архитектуры ARM и означающими то, имел ли результат последней операции, выполненной с помощью АЛУ, бит переноса или равнялся нулю соответственно. Архитектура описывает 12 команд, которые можно логически разделить на 4 группы:

  • Операции взаимодействия с внутренним регистром

  • Загрузить

  • Сохранить

  • Арифметические операции

  • Сложить

  • Вычесть

  • Сложить с переносом

  • Вычесть с переносом

  • Операции перехода (условного и безусловного)

  • Перейти

  • Перейти если 0

  • Перейти если перенос

  • Перейти если не 0

  • Перейти если не перенос

  • Операция останова

  • Остановить

Так как количество операций, которые могут быть закодированными с помощью 8 бит равняется 2^8=256, то для расширения архитектура оставляет 256-12=244 вариантов команд, к которым могут быть добавлены, например различные часто используемые арифметические операции

  • Побитовое И (AND)

  • Побитовое ИЛИ (OR)

  • Побитовое НЕ (NOT)

  • Побитовое исключающее ИЛИ (XOR)

  • Логический сдвиг влево

  • Логический сдвиг вправо

  • Арифметический сдвиг

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

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

Наверное самой важной частью системы является интерфейс взаимодействия с человеком. Так в главе 22 "Операционная система" Петцольд демонстрирует панель прямого доступа к памяти. Доступ к памяти осуществляется с помощью перехвата тракта адреса и остановки выполнения текущей программы. Адресация предполагается с помощью ручного ввода значения переключением тумблеров в состояние логического 0 или 1, содержимое ячейки сразу отображается с помощью световых индикаторов. Изменение значения ячейки осуществляется с помощью тумблеров данных и возможно только если активирован тумблер, разрешающий запись. Активация тумблер сброс отвечает за обнуление счётчика команд.

Реализация

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

Микроархитектура

Схема микроархитектуры, предлагаемая автором.

Для реализации микроархитектуры использовалась САПР для ПЛИС Quartus II 13.0sp1, предоставляющая широкие возможности для разработки и отладки ПЛИС. Несмотря на возможность описания архитектуры с помощью специализированных языков таких как VHDL и Verilog, для большей наглядности всё проектирование осуществлялось исключительно в графическом режиме. Широко использовались возможности встроенных функций таких как:

  • lpm_mux (сокр. от multiplexer)

  • lpm_decode

  • lpm_counter

  • lpm_ff (сокр. от flip-flop)

  • lpmaddsub

  • lpm_constant

На начальных этапах разработки тестирование проводилось в программе ModelSim, поставляемой в пакете программ для разработки. На финальных этапах отладка и тестирование производилось непосредственно на самой исполняющей плате с помощью встроенной утилиты In-System Memory Content Editor для изменения состояния входящих данных.

В качестве исполняющей платы была выбрана Cyclone II EP2C5 Mini Dev Board на основе чипа ПЛИС EP2C5T144C8, обладающий встроенным кварцевым генератором на 50 МГц, более 80 контактами интерфейса ввода/вывода общего назначения и разъёмом JTAG для прошивки и отладки. Техническим ограничением стал существенно меньший объем оперативной памяти, доступный для использования, вследствие чего для адресации используются только 13 младших бит в отличие от 16 бит, описанных в архитектуре. Однако это не вносит никаких критических изменений в работу процессора и может быть исправлено заменой исполняющей платы на более производительный аналог.

Внешний вид

Это изображение сформировало в голове довольно запоминающийся образ и с первого взгляда реализовать такой пульт достаточно не сложно. Для его реализации потребуется 16 + 8 + 3 = 27 двухпозиционных тумблеров, 8 светодиодов и непосредственно листовой материал на котором будет всё размещаться.

Использовались микротумблеры MTS-102 ON-ON. В нижнем положении соединяющие верхнюю ножку со средней, в верхнем - нижнюю со средней. Это было удобно, так как по всем нижним можно было пустить высокое напряжении, верхние подключить к земле и снимать значение со средних.

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

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

Большой проблемой стало нанесение надписей, для этого в Autodesk Fusion 360 была разработана схема расположения надписей на фронтальной панели и с помощью плоттера вырезан шаблон для покраски.

Для питание используется разъем GX16 5P, но для удобства использования впаянный в шнур с USB для удобства использования с обычными блоками питания.

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

Внешний вид в целом соответствует взятому за основу изображению. Для лучшей читаемости нумерация на тумблерах изменена с десятичной записи на шестнадцатеричную.

Внутреннее пространство

Как уже было сказано выше в качестве вычислительного модуля была выбрана плата Cyclone II EP2C5 Mini Dev Board на основе чипа ПЛИС EP2C5T144C8. Для подключения тумблеров и светодиодов к плате было решено не использовать пайку и использовать провода, используемые для макетного моделирования. Однако другая сторона распаяна к тумблерам или светодиодам, к которым относятся.

Так как хотелось сделать коробку ещё и автономной, решил внедрить плату от powerbank'a, которая включает в себя плату регулировки заряда литиевых аккумуляторов и повышения напряжения до 5 вольт, также к ней припаян бокс для аккумулятора размера 18650.

Для удобства крепления вычислительной платы и платы питания внутри корпуса в САПР Autodesk Fusion 360 были разработаны крепления, приклеенные к тыльной стороне.

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

Итог

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

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

Подробнее..

Новостной дайджест событий из мира FPGAПЛИС 007 (2020_12)

31.12.2020 12:04:32 | Автор: admin

Здравствуйте друзья.


Продолжаем публиковать новости из мира FPGA/ПЛИС.


В 2020 опубликовано 238 новостей, запущены FPGA стримы и проведена первая отечественная FPGA конференция. Подборка за декабрьи ссылки на все упоминания под катом.




Ссылки:


  • Краткий 90-минутный видео-обзор прошедших событий в FPGA отрасли здесь
  • Материалы прошедшей FPGA конференции и ссылки на материалы здесьи отчет на хабре
  • Записи FPGA стримов здесь


PS: Стримы проводят ребята вот с этого проекта. Подкидывайте им идеи для стримов или присоединяйтесь в качестве гостя, если надумаете рассказать что-то интересное из мира FPGA/ПЛИС



FPGA декабрь новости



Зимний хакатон от QuickSilicon


Выполнение MicroBlaze приложений на PSU DDR в Vitis


Инструменты FPGA с открытым исходным кодом и поддержка Renode для MCU Core-V


Решаем проблему разбиения чисел на PYNQ


Следующий уровень светодиодного стрима


Небольшой бесплатный курс по VHDL на Udemy


Модельное проектирование ПЛИС и ASIC в контексте функциональной безопасности


Школа FPGA/SoC для применения в атомной промышленности и связанной с ней приборостроении


UVM обновилась согласно стандарту IEEE 1800.2-2020


Доступны материалы конференции Synopsys Verification Day 2020


Silexica запускает первый коммерческий плагин для Vitis HLS


Реализация интерпретатора CHIP-8 на Verilog


Установка Cocotb на Windows 10 для повышения производительности проверки проектов ПЛИС


ECPIX-5 современная отладочная плата на Lattice ECP5


Релиз Sigasi Studio 4.10


Глубокое обучение на FPGA


Конкурс от QuickLogic совместно с SensiML


Qomu MCU + eFPGA Development Kit, который помещается внутри USB-порта


Вебинар: Верификация с использованием OSVVM


Светодиодно-ленточный релакс FPGA стрим сегодня в 20:00 Мск


Ускорения отладки RTL для ПЛИС


О разработке на Плис в соответствии с DO254


Самоконфигурируемая трехмерная мультиПЛИСовая адаптивная платформа


Microchips Анонсировала доступность PolarFire для космического применения


Опубликованы материалы конференции Nokia и Intel


FPGA конференция в Сколково


Intel Open FPGA Stack Простая разработка пользовательских платформ


Изучаем Vivado Methodology Report


Все воркшопы Адама Тейлора в этом году


HBM2 и тензорные блоки ключевые особенности Intel FPGA


Запись субботнего стрима GoWin первое знакомство доступна на Youtube


Обзор научных работ, связанных с FPGA


Компания Samsung разработала прототип голографического дисплея с использованием FPGA


Повышение производительности разработки с Vivado и SystemVerilog


Новые возможности VHDL2019


FPGA конференция и хакатон от Intel и Nokia


Саммит разработчиков oneAPI 2020


Вебинар: Accelerating Data Channels to 112 Gbps PAM4: A Case Study in Real-World FPGA Implementation


Освоение DPC++ для программирования гетерогенных систем с использованием C++ и SYCL


Реализация глубоких нейронных сетей на ПЛИС


Делаем UART на HLS


QuickLogic присоединяется к партнерской программе Samsung SAFE IP



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


Подробнее..
Категории: Новости , Дайджест , Intel , Fpga , Gowin , Плис , Rtl , Lattice , Risc-v , Xilinx , Hdl , Migen , Litex

Генерация клока на примитиве LUT

14.04.2021 10:21:24 | Автор: admin

Введение

В прошлой статье я попытался с генерировать тактирующий сигнал в ПЛИС на разных примитивах. Из всех рассмотренных вариантов генерации сигнала, самым адекватным получился клок с генерированный на блоках LUT.

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

Исходные данные.

В свое время приобрел с Китая плату EBAZ4205, с ПЛИС ZYNQ7010.

Как оказалось, некоторая часть компонентов была выпаяна(или не впаяна): диод по питанию, тактирующий кварц для ПЛИСовой части, пины подключения программатора и UART, слот под SD-карту. Изначально я планировал использовать встроенный XADC для определения температуры кристалла, но по закону подлости он так же оказался не подключенным. На рисунке я отметил пины которые требуются для работы встроенного XADC. Пины по питанию J9 и J10 замкнуты сами на себя через конденсатор, а пины VREF вовсе не подключены.

Таким образом температуру померить внутренним XADC не получится. А это значит что набор для эксперимента дополняется мультиметром с термопарой, струбциной и парой проводов).

Эксперимент.

Первая часть. С прошлого раза я понял, что замкнуть на себя цепочку лутов просто так не получится, придется писать различные ограничения, поэтому было решено пустить эту цепочку через внешние пины ПЛИС. Для того чтобы оценить влияние внешнего проводника(да и просто ради эксперимента), была набросана такая схема:

Сигнал CLK выдает 100 МГц и тактирует свой счетчик. Пины in_pin, out_pin снаружи замкнуты сами на себя проводником, а внутри соединены через инверсию. Длину этого проводника буду менять. И так, вот результаты этого этапа:

73.118 МГц

14 см

55.914 МГц

49 см

41.840 МГц (прямой провод)

92 см

51.417 МГц (катушка с сердечником)

38.710 МГц (катушка без сердечника)

37.830 МГц

170 см

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

 Синий провод и есть проводник длиной 14 сантиметров, его я заменял на другие провода различной длины. Синий провод и есть проводник длиной 14 сантиметров, его я заменял на другие провода различной длины.

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

А вот и результаты, при внешнем проводе 14 см( +1 лут - это тот самый инвертирующий лут):

4+1 лут. Базовая частота 100 МГц

68.230 МГц

100+1 лут. Базовая частота 100 МГц

13.881 МГц

1000 + 1 лут. Базовая частота 100 МГц

1.466 МГц

1000 + 1 лут. Базовая частота 10 МГц

Подключил провод 170 см. результат не сильно изменился

1.496 МГц

1.476 МГц(с проводом 170 см)

5000 + 1 лут. Базовая частота 10 МГц

0.274 МГц

11000 + 1лут Базовая частота 5 МГц.

Не получилось собрать. Ругался на длинную цепочку лутов.

8000 + 1 лут. Базовая частота 5 МГц.

Собралось, но чипскоп ни как не загружал данные из ПЛИС.

8000 + 1 лут.Базовая частота 10 МГц.

0.166 МГц

*Базовая частота эта та частота относительно которой я считал частоту, она же была частотой для ChipScope

Третья часть. И так, из проведенных предыдущих экспериментов, можно определиться с оптимальными параметра для третьего эксперимента, чтобы оценить зависимость частоты от температуры. Я возьму 8000 лутов и внешний провод 14 см. Доработав проект, получаю вот такую схему:

Блок gen_trig генерирует сигнал каждую секунду, так же сигнал trig потребуется в чипскопе для сбора статистики. Блок cnt_lut считает такты с генерированные цепочкой лутов, по сигналу rst(непонятно почему он считает его инверсным) сбрасывается счетчик. А вот как заполнен кристалл данным проектом. Желтым отмечены все 8000 лутов, маджентой отмечены блоки чипскопа. Так же отметил где находятся пины на внешний проводник.

А вот как выглядит собраный макет эксперимента:

На ПЛИС положил термопару, капнул термопасты, накрыл пленкой и прижал струбциной через несколько слоев картона. Картон нужен чтобы струбцина не стала радиатором. Кристалл максимум греется до 47 градусов при 17 минутах работы. Поэтому чипскоп настраиваю на захват 1024 окна по два отсчета, на каждый сигнал триггера, который предусмотрен в проекте. И так, результат эксперимента:

На графике видим изменение частоты каждую секунду. Частота меняется от 165217 Гц до 165589 Гц, при изменении температуры 25-46 градусов. В целом хотелось бы нагреть кристалл до 85 градусов.

Первое, что меня удивило, так это повышение частоты с повышением температуры. Я ожидал, что частота станет меньше, либо в вообще меняться не будет, но я ни как не ожидал, что частота поднимется вместе с температурой.

Заключение.

В статье продемонстрировал результат трех экспериментов, по которым можно сделать следующие выводы:

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

  2. Зависимость частоты от количества лутов так же не линейна. Причем слишком уж сильно не линейна.

Тут пара графиков зависимостей частоты от количества лутов(ось Y - значение частоты).

Сырые данные последнего графика если кому надо:

5

68,23

6

52,33

21

32,285

41

24,326

56

19,663

71

15,657

86

16,056

96

13,277

101

13,881

121

10,499

141

10,104

161

6,443

181

7,268

201

6,331

301

4,484

401

3,315

501

2,791

601

2,49

701

2,042

801

1,753

901

1,629

1001

1,496

1401

1,029

1801

0,797

2201

0,655

2601

0,537

3. Зависимость частоты от температуры оценить трудно, так как не получается однозначно сопоставить значение частоты и температуре. Для этого нужен XADC.

Подробнее..

Как прошла наша вторая конференция FPGA разработчиков FPGA-Systems 2021.1?

12.05.2021 10:23:58 | Автор: admin

ПЛИС-культ привет, FPGA хаб!

FPGA-движ, хоть и не быстро, но набирает свои обороты и проведение уже второй за полгода встречи ПЛИСоводов, прошедшей в формате конференции, это подтверждает. Мы пересмотрели формат конференции, учли приобретенный ранее опыт организации / проведения мероприятия и также постарались прислушаться к вашим пожеланиям и отзывам. В итоге у нас получилось: два типа докладов (суммарно их было 9 ), хакатон и 3 викторины с призами.

Чудесный "аквариум"Чудесный "аквариум"

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

Последняя суббота апреля

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

Учитывая все выше написанное, вторая конференция состоялась 24 апреля 2021 года в ИЦ "Сколково".

Зальные проблемы

С учетом посещаемости того года нам выделили зал на 50 человек. Мы должны были собраться в ИЦ "Сколково" - Амальтея - Капсула номер 3. Предварительно в пятницу 23 апреля я провел без малого 4 часа в подготовке оборудования, настройке трансляции, проверке звука, понимания какие кнопки на пульте надо было крутить, чтобы сделать небольшой тюнинг во время прямого эфира и тд (особенно учитывая опыт по трансляции с первой конференции). Зная, что звук будет являться главной проблемой, я 10 раз все перепроверил, провел тестовую трансляцию с парой человек из комунти и со спокойной душой поехал домой.

Уже часов в 7 вечера раздается звонок, содержимое которого было примерно следующее - "Мих, тут что-то напутали с расписанием и ваш зал уже на завтра сдали другим". Сказать, что я прифигел - ни чего не сказать. С утра конференция, а нас лишают уже подготовленного / настроенного зала, за который мы договорились несколько месяцев назад. Приглашения уже были разосланы, подтверждения от участников получены, а тут вот такое...

Поскольку зал нам выдали бесплатно, да еще и в субботу, то мы были там полностью на птичьих правах. Соответственно, я начал расспрашивать за любое другое свободное помещение, в которое бы поместилось 50-60 человек. Несказанно повезло, что была свободна капсула 1 - но я ее в глаза не видел, не знал какое там оборудование, как им пользоваться, но вариантов не было. Пришлось согласиться и выехать с утра пораньше в Сколково, что бы хоть как-то настроиться "на скорую руку".

Огромное спасибо Петру Рогачеву, он проторчал в ту пятницу до самого позднего вечера в Сколково, чтобы к утру наше помещение было полностью готово. Так сказать он перенес настройки одной капсулы в другую.

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

Спонсоры

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

Программа конференции

Программа конференции была действительно насыщенной: 5 докладов по 50-60 минут, 4 доклада по 20-30 минут, 3 викторины минут по 10. Между докладами 10 минут перерыв и один большой перерыв на час. При этом вся конференция продолжалась более 9 часов.

Вместе с программой я выложу ссылки записи / нарезки докладов и ссылки на скачивание презентаций. Все видео собраны в отдельный плейлист на YouTube. Там же вы найдёте и полную 9-ти часовую запись с таймкодами

Программа:

10:00 Открытие конференции

10:10 - 11:00 Beyond HLS. Задачи архитектора, которые может решать экосистема разработки / Буровский Павел

11:10 - 11:20 Возможности отечественного САПР проектирования электроники Delta Design / Никита Малышев

11:20 - 12:10 Роль лексического и синтаксического анализа в маршруте разработки проекта ПЛИС/ Варганов Артем

12:10 - 12:20 Викторина #1

12:30 - 12:50 Платформа для on-line обучения схемотехнике на интерактивных схемах / Гнитеев Николай

13:00 - 13:50 Скриптовая среда для работы с периферией ПЛИС с использованием boundary-scan + демо / Иванов Алексей

13:50 - 14:00 Викторина #2

14:00 - 15:00 перерыв

15:00 - 15:20 Искусство отладки FPGA / Дыдыкин Сергей

15:30 - 16:20 Генерация HDL кода из моделей MATLAB/Simulink / Шидловский Дмитрий

16:20 - 16:30 Викторина #3

16:40 - 17:00 Опыт создания виртуальной лаборатории для проектирования на ПЛИС в МИЭМ НИУ ВШЭ / Романов Александр

17:10 - 18:00 Реализация криптоалгоритмов на ПЛИС / Мурзинов Дмитрий

18:10 - 18:30 Результаты хакатона, награждение победителей

18:30 - 19:00 Итоги конференции

Вопросы викторин

Во время мероприятия мы провели три небольших викторины среди участников как онлайн трансляции, так и участников в зале. Для её проведения я использовал телеграм бота QuizBot, а сами викторины публиковались в ранее созданном отдельном канале конференции. Желающие проверить свои FPGA знания на простеньких вопросах первой викторины и свои знания по истории ПЛИС могут пройти викторины самостоятельно: ссылка на первую викторину и ссылка на вторую викторину. Пока ответить правильно на все вопросы второй викторины ни кому не удалось :-)

Хакатон

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

География очной аудитории

Учитывая ограниченность предоставленного помещения, в Сколково в этом году приехали почти 60 человек: Воронеж, Рязань, Калуга, Ярославль, Дубна, Москва, Санкт-Петербург, Нижний-Новгород, Саров, Челябинск, Петрозаводск, Зеленоград, Долгопрудный, Раменское.

Онлайн трансляцию смотрели участники из России, Польши, Беларуси, Украины, Казахстана, Эстонии.

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

Опрос

После мероприятия мы решили провести небольшой анонимный опрос, который бы показал интересы FPGA разработчиков в плане используемых языков, направлений разработки и общего впечатления о мероприятии. Если вы хотите дополнить статистику опроса, то это можно сделать по ссылке. Опрос строго анонимный

PS в конце статьи есть также хабр-опрос

Когда следующая FPGA конференция?

Следующая наша встреча состоится 27 ноября 2021 года. Учитывая текущий опыт, мы решили уже сейчас открыть предварительную регистрацию участников, чтобы примерно знать к началу ноября, какое именно помещение нам стоит поискать для мероприятия.

Мы опять встретимся в Москве, но если найдется хотя бы 5 докладчиков в Санкт-Петербурге, то мы проведем встречу и в северной столице тоже. Все зависит от вас ;-)

Ссылка для предварительной регистрации на третью конференцию FPGA разработчиков FPGA-Systems 2021.2

PS: ищем ещё варианты с местом проведения? Мб кто-то прочитает и скажет "ого да я могу предложить этим ребятам хороший вариант"

--

В заключении хотел бы выразить благодарность помогаторам, докладчикам, участникам конференции и FPGA комьюнити за поддержку. Без вас бы это мероприятие не состоялось.

Подробнее..

Прокачиваем скрипты симуляции HDL с помощью Python и PyTest

17.01.2021 20:13:46 | Автор: admin

Все делают это. Ну ладно, не все, но большинство. Пишут скрипты, чтобы симулировать свои проекты на Verilog, SystemVerilog и VHDL. Однако, написание и поддержка таких скриптов часто бывает довольно непроста для типично используемых Bash/Makefile/Tcl. Особенно, если необходимо не только открывать GUI для одного тестбенча и смотреть в диаграммы, но и запускать пачки параметризированных тестов для различных блоков, контролировать результат, параллелизировать их выполнение и т.д. Оказалось, что всё это можно закрыть довольно прозрачным и легко поддерживаемым кодом на Python, что мне даже обидно становится от того, как я страдал ранее и сколько странного bash-кода родил.

Конечно, я не первый кто задумывается о подобном. Уже даже существует целый фреймворк VUnit. Однако, как показывает практика и опросы в профильных чатах, такие фреймворки используются нечасто. Вероятно потому, что они предъявляют требования к внутренней структуре самих тестбенчей, с чем наверное можно мириться только на новых проектах, без обширной кодовой базы. Ну и вообще, куда ж без таких вещей как "у нас так не принято" и "not invented here".

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

Задачи

Попробую перечислить большинство задач, которые так или иначе возникают в процессе разработки и эволюции HDL проекта. От более простых к более сложным:

  • Одиночный запуск. Запустить симуляцию выбранного тестбенча, посмотреть временные диаграммы, внести коррективы, повторить.

  • Одиночный запуск без GUI. То же самое, но без временных диаграмм, оценить результат по выводу в консоль.

  • Параметризированный запуск. Запуск симуляции с GUI или без, но с параметрами (дефайнами), передаваемыми в скрипт из консоли.

  • Запуск с пре-/постпроцессингом. Например, для теста должны быть подготовлены данные. Или сам тест порождает данные, которые должны быть проверены вне HDL.

  • Массовый запуск. Прогнать симуляцию всех существующих тестбенчей.

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

  • Параллельные запуски. Тесты могут легко идти минуты/часы и последовательное исполнение может занимать слишком много времени. Скрипты должны позволять параллельное исполнение нескольких тестбенчей.

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

  • Поддержка CI. Массовый запуск должен сочетаться в том числе с выбранной стратегией CI (прогон всех тестов после каждого пуша, "ночные сборки" и т.д.).

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

Идея

Все симуляторы запускаются примерно одинаково в большинстве случаев:

  • собираем список всех исходников (опционально делим на несколько списков по языку);

  • собираем список всех директорий для поиска исходников (нужно для include);

  • собираем список всех дефайнов;

  • сообщаем имя библиотеки, куда всё будем компилировать (или нескольких);

  • сообщаем имя верхнего модуля (обычно это имя тестбенча);

  • передаём это всё симулятору в виде ключей, файлов со списками и т.д.

А что если написать модуль на Python, в котором обернуть нужные симуляторы в один класс Simulator, вынести общие вещи в атрибуты и реализовать метод run(), который запустит симуляцию с помощью выбранного симулятора? В целом, именно это я и сделал для Icarus Verilog, Modelsim и Vivado Simulator, используя модуль subprocessпод капотом. Также я добавил класс CliArgs, основанный на модуле argparse, чтобы иметь возможность управлять запуском из консоли. Ну и написал некоторое количество вспомогательных функций, которые пригодятся в процессе. Получился файл sim.py.

Фактически, я постарался свести всё к тому, что в новом проекте нужно всего-лишь закинуть этот файл, создать рядом еще один скрипт на Python, импортировать необходимое из sim.py и начать работу.

Тестовый проект

Для демонстрации я вытянул модуль пошагового вычисления квадратного корня из одного старого проекта, чтобы тестовый дизайн был хоть чуточку сложнее счётчика или сумматора. Код основан на публикации An FPGA Implementation of a Fixed-Point Square Root Operation.

Репозиторий проекта pyhdlsim на GitHub.

Иерархия проекта проста:

$ tree -a -I .git. .github    workflows # Github Actions        icarus-test.yml # запуск всех тестов в Icarus Verilog после каждого пуша на github        modelsim-test.yml # запуск всех тестов в Modelsim после каждого пуша на github .gitignore LICENSE.txt README.md sim # скрипты для запуска симуляции    conftest.py    sim.py    test_sqrt.py src # исходники     beh # поведенчесие описания и модели        sqrt.py     rtl # синтезируемый HDL код        sqrt.v     tb # HDL код тестбенчей         tb_sqrt.sv

Сам тестбенч tb_sqrt.sv тоже довольно примитивен: подготавливается массив входных значений, вычисляются "идеальные" значения с помощью $sqrt(), входные значения проталкиваются в модуль корня, выходные значения сохраняются в массив, происходит сравнение ожидаемых значений и фактических.

В принципе, краткое описание есть в самом репозитории и на этом можно закругляться, однако, думаю что будет гораздо нагляднее, если показать весь путь написания тестового окружения (будем считать что весь HDL и файл sim.py уже написаны). Всё действо будет происходить внутри папки sim. Осторожно, букв много впереди ожидает.

Одиночный запуск

Создадим файл test_sqrt.py для запуска тестбенча.

#!/usr/bin/env python3from sim import Simulatorsim = Simulator(name='icarus', gui=True, cwd='work')sim.incdirs += ["../src/tb", "../src/rtl", sim.cwd]sim.sources += ["../src/rtl/sqrt.v", "../src/tb/tb_sqrt.sv"]sim.top = "tb_sqrt"sim.setup()sim.run()

Тест будем прогонять в Icarus с открытием GTKWave для просмотра диаграмм. Пути до исходников задаются относительно самого скрипта. Задавать директории поиска инклудов для данного проекта не обязательно, и сделано лишь для демонстрации. Чтобы не загрязнять директорию со скриптами - с помощью sim.setup() будет создана рабочая папка work (а если она существовала, то она будет удалена и создана заново) внутри которой симулятор и будет запущен (sim.run()).

Делаем скрипт исполняемым и запускаем:

chmod +x test_sqrt.py./test_sqrt.py

Симуляция должна пройти успешно и должно появиться окно GTKWave.

Одиночный запуск без GUI

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

#!/usr/bin/env python3from sim import Simulator, CliArgsdef test(tmpdir, defines, simtool, gui):    sim = Simulator(name=simtool, gui=gui, cwd=tmpdir)    sim.incdirs += ["../src/tb", "../src/rtl", sim.cwd]    sim.sources += ["../src/rtl/sqrt.v", "../src/tb/tb_sqrt.sv"]    sim.defines += defines    sim.top = "tb_sqrt"    sim.setup()    sim.run()if __name__ == '__main__':    # run script with key -h to see help    args = CliArgs(default_test="test").parse()    test(tmpdir='work', simtool=args.simtool, gui=args.gui, defines=args.defines)

Посмотрим что нам доступно:

$ ./test_sqrt.py -husage: test_sqrt.py [-h] [-t <name>] [-s <name>] [-b] [-d <def> [<def> ...]]optional arguments:  -h, --help            show this help message and exit  -t <name>             test <name>; default is 'test'  -s <name>             simulation tool <name>; default is 'icarus'  -b                    enable batch mode (no GUI)  -d <def> [<def> ...]  define <name>; option can be used multiple times

Теперь мы можем запустить тест в консольном режиме:

$ ./test_sqrt.py -bRun Icarus (cwd=/space/projects/pyhdlsim/simtmp/work)TOP_NAME=tb_sqrt SIMiverilog -I /space/projects/pyhdlsim/src/tb -I /space/projects/pyhdlsim/src/rtl -I /space/projects/pyhdlsim/simtmp/work -D TOP_NAME=tb_sqrt -D SIM -g2005-sv -s tb_sqrt -o worklib.vvp /space/projects/pyhdlsim/src/rtl/sqrt.v /space/projects/pyhdlsim/src/tb/tb_sqrt.svvvp worklib.vvp -lxt2LXT2 info: dumpfile dump.vcd opened for output.Test started. Will push 8 words to DUT.!@# TEST PASSED #@!

Или запустить в другом симуляторе:

# как в консоли./test_sqrt.py -s modelsim -b# так и с GUI./test_sqrt.py -s modelsim

Параметризированный запуск

Также теперь можно контролировать дефайны из консоли, и, например, увеличить количество подаваемых данных:

$ ./test_sqrt.py -b -d ITER_N=42Run Icarus (cwd=/space/projects/pyhdlsim/simtmp/work)TOP_NAME=tb_sqrt SIMiverilog -I /space/projects/pyhdlsim/src/tb -I /space/projects/pyhdlsim/src/rtl -I /space/projects/pyhdlsim/simtmp/work -D TOP_NAME=tb_sqrt -D SIM -g2005-sv -s tb_sqrt -o worklib.vvp /space/projects/pyhdlsim/src/rtl/sqrt.v /space/projects/pyhdlsim/src/tb/tb_sqrt.svvvp worklib.vvp -lxt2LXT2 info: dumpfile dump.vcd opened for output.Test started. Will push 42 words to DUT.!@# TEST PASSED #@!

Запуск с пре-/постпроцессингом

Часто бывает так, что сгенерировать данные для теста невозможно внутри тестбенча и должны быть применены внешние генераторы. Сделаем еще один тест, где будем сверять работу модуля на Verilog с идеальной моделью, написанной на Python. Алгоритм работы уже был представлен выше - просто перепишем его на Python, не забывая проверить что он на самом деле работает. Результатом будет файл src/beh/sqrt.py. Оттуда нам нужна будет лишь одна функция nrsqrt().

Переименуем старый тест, который ориентируется на данные, полученные внутри тестбенча, в test_sv. И создадим новый test_py, который будет готовить данные с помощью функции nrsqrt().

#!/usr/bin/env python3from sim import Simulator, CliArgs, path_join, write_memfileimport randomimport syssys.path.append('../src/beh')from sqrt import nrsqrtdef create_sim(cwd, simtool, gui, defines):    sim = Simulator(name=simtool, gui=gui, cwd=cwd)    sim.incdirs += ["../src/tb", "../src/rtl", cwd]    sim.sources += ["../src/rtl/sqrt.v", "../src/tb/tb_sqrt.sv"]    sim.defines += defines    sim.top = "tb_sqrt"    return simdef test_sv(tmpdir, defines, simtool, gui):    sim = create_sim(tmpdir, simtool, gui, defines)    sim.setup()    sim.run()def test_py(tmpdir, defines, simtool, gui=False, pytest_run=True):    # prepare simulator    sim = create_sim(tmpdir, simtool, gui, defines)    sim.setup()    # prepare model data    try:        din_width = int(sim.get_define('DIN_W'))    except TypeError:        din_width = 32    iterations = 100    stimuli = [random.randrange(2 ** din_width) for _ in range(iterations)]    golden = [nrsqrt(d, din_width) for d in stimuli]    write_memfile(path_join(tmpdir, 'stimuli.mem'), stimuli)    write_memfile(path_join(tmpdir, 'golden.mem'), golden)    sim.defines += ['ITER_N=%d' % iterations]    sim.defines += ['PYMODEL', 'PYMODEL_STIMULI="stimuli.mem"', 'PYMODEL_GOLDEN="golden.mem"']    # run simulation    sim.run()if __name__ == '__main__':    args = CliArgs(default_test="test_sv").parse()    try:        globals()[args.test](tmpdir='work', simtool=args.simtool, gui=args.gui, defines=args.defines)    except KeyError:        print("There is no test with name '%s'!" % args.test)

Теперь, когда тестов несколько, можно воспользоваться ключом выбора теста:

# аргумент должен совпадать с именем функции./test_sqrt.py -t test_py

Аналогичным образом можно организовать и постпроцессинг внутри запускающего скрипта при желании.

Массовый запуск

Сейчас у нас уже есть 2 теста, а ведь завтра может быть и 202, а значит уже можно переживать о том, что нужен способ как их все прогнать за раз. И вот тут на сцене появляется pytest.

Нано-ликбез по pytest.

  • Начиная с директории запуска, pytest рекурсивно ищёт всё начинающееся на test* и исполняет: модули, функции, классы, методы.

  • Тест считается выполненным, если не возникло исключений (типичным является использование assert для контроля).

  • Для формирования тестового окружения используются фикстуры (fixtures). Например, что подставлять в тест test_a(a) в качестве аргумента при выполнении как раз определяется фикстурой.

  • Можно создать дополнительный файл conftest.py, в котором разместить код для более тонкого контроля выполнения тестов и их окружения.

Типичные сценарии запуска:

  • pytest - рекурсивный поиск и исполнение всех тестов, начиная с текущей директории;

  • pytest -v - выполнить тесты и показать болеe подробную информацию о ходе выполнении тестов;

  • pytest -rP - выполнить тесты и показать вывод в stdout тех тестов, что завершились успешно;

  • pytest test_sqrt.py::test_sv - выполнить указанный тест.

Для того чтобы адаптировать текущий скрипт под pytest нужно совсем немного. Импортируем сам pytest. Добавим пару фикстур для таких аргументов теста как simtool и defines. Значение, возвращаемое фикстурами будет использовано в качестве аргумента во всех тестах. Два других аргумента gui и pytest_run снабжаем значениями по умолчанию. Фактически их тоже можно было сделать фикстурами, но т.к. для запуска pytest они не должны принимать никакое другое значение, то сделал так.

Да, кстати, появился аргумент pytest_run который сообщает о том, выполняется ли сейчас pytest, или просто тест запущен отдельно из консоли.

С аргументом tmpdir я схитрил - это имя стандартной фикстуры, которая возвращает путь к временной папке, уникальной для каждого теста. Т.е. сами тесты будут прогонятся где-то во временных директориях и не засорять содержимое sim.

Имена тестов тоже изменять не нужно - они будут найдены pytest, т.к. имеют префикс test_.

Решение о том, прошел тест или нет принимается по состоянию аттрибута is_passed симулятора. Он возвращает истину, если увидел ключевую фразу !@# TEST PASSED #@! в stdout. Очевидно, если компиляция была неудачной, или тест завершился с ошибкой, то этой фразы выведено не будет. Это самый простой способ оценить результат, но возможности для его кастомизации здесь ограничены лишь фантазией. Можно получить stdout через sim.stdout и искать там что угодно.

#!/usr/bin/env python3import pytestfrom sim import Simulator, CliArgs, path_join, write_memfileimport randomimport syssys.path.append('../src/beh')from sqrt import nrsqrt@pytest.fixture()def defines():    return []@pytest.fixturedef simtool():    return 'icarus'def create_sim(cwd, simtool, gui, defines):    sim = Simulator(name=simtool, gui=gui, cwd=cwd, passed_marker='!@# TEST PASSED #@!')    sim.incdirs += ["../src/tb", "../src/rtl", cwd]    sim.sources += ["../src/rtl/sqrt.v", "../src/tb/tb_sqrt.sv"]    sim.defines += defines    sim.top = "tb_sqrt"    return simdef test_sv(tmpdir, defines, simtool, gui=False, pytest_run=True):    sim = create_sim(tmpdir, simtool, gui, defines)    sim.setup()    sim.run()    if pytest_run:        assert sim.is_passeddef test_py(tmpdir, defines, simtool, gui=False, pytest_run=True):    # prepare simulator    sim = create_sim(tmpdir, simtool, gui, defines)    sim.setup()    # prepare model data    try:        din_width = int(sim.get_define('DIN_W'))    except TypeError:        din_width = 32    iterations = 100    stimuli = [random.randrange(2 ** din_width) for _ in range(iterations)]    golden = [nrsqrt(d, din_width) for d in stimuli]    write_memfile(path_join(tmpdir, 'stimuli.mem'), stimuli)    write_memfile(path_join(tmpdir, 'golden.mem'), golden)    sim.defines += ['ITER_N=%d' % iterations]    sim.defines += ['PYMODEL', 'PYMODEL_STIMULI="stimuli.mem"', 'PYMODEL_GOLDEN="golden.mem"']    # run simulation    sim.run()    if pytest_run:        assert sim.is_passedif __name__ == '__main__':    args = CliArgs(default_test="test_sv").parse()    try:        globals()[args.test](tmpdir='work', simtool=args.simtool, gui=args.gui, defines=args.defines, pytest_run=False)    except KeyError:        print("There is no test with name '%s'!" % args.test)

Прогоним все тесты несколько раз:

$ pytest========== test session starts ===========platform linux -- Python 3.8.5, pytest-6.2.1, py-1.10.0, pluggy-0.13.1rootdir: /space/projects/misc/habr-publications/pyhdlsim/pyhdlsim/simtmpplugins: xdist-2.2.0, forked-1.3.0collected 2 itemstest_sqrt.py ..                    [100%]=========== 2 passed in 0.08s ============$ pytest -v========== test session starts ===========platform linux -- Python 3.8.5, pytest-6.2.1, py-1.10.0, pluggy-0.13.1 -- /usr/bin/python3cachedir: .pytest_cacherootdir: /space/projects/misc/habr-publications/pyhdlsim/pyhdlsim/simtmpplugins: xdist-2.2.0, forked-1.3.0collected 2 itemstest_sqrt.py::test_sv PASSED       [ 50%]test_sqrt.py::test_py PASSED       [100%]=========== 2 passed in 0.08s ============

Массовый параметризированный запуск

Неплохо было бы прогнать тесты для разной величины ширины данных. Очевидно, что руками запускать тесты N раз, каждый раз анализируя глазами логи в поисках фразы об успешном завершении, довольно утомительно. Нужно использовать pytest.

Модификация будет минимальной, нужно лишь обновить фикстуру defines:

# заменим это@pytest.fixture()def defines():    return []# на это@pytest.fixture(params=[[], ['DIN_W=16'], ['DIN_W=18'], ['DIN_W=25'], ['DIN_W=32']])def defines(request):    return request.param

Теперь фикстура может принимать одно из 5 значений. Запустим тесты:

$ pytest -v================== test session starts ==================platform linux -- Python 3.8.5, pytest-6.2.1, py-1.10.0, pluggy-0.13.1 -- /usr/bin/python3cachedir: .pytest_cacherootdir: /space/projects/misc/habr-publications/pyhdlsim/pyhdlsim/simtmpplugins: xdist-2.2.0, forked-1.3.0collected 10 itemstest_sqrt.py::test_sv[defines0] PASSED            [ 10%]test_sqrt.py::test_sv[defines1] PASSED            [ 20%]test_sqrt.py::test_sv[defines2] PASSED            [ 30%]test_sqrt.py::test_sv[defines3] PASSED            [ 40%]test_sqrt.py::test_sv[defines4] PASSED            [ 50%]test_sqrt.py::test_py[defines0] PASSED            [ 60%]test_sqrt.py::test_py[defines1] PASSED            [ 70%]test_sqrt.py::test_py[defines2] PASSED            [ 80%]test_sqrt.py::test_py[defines3] PASSED            [ 90%]test_sqrt.py::test_py[defines4] PASSED            [100%]================== 10 passed in 0.28s ===================

Как видим, теперь каждый тест запустился по 5 раз с разным дефайном.

Параллельные запуски

Тут тоже всё довольно просто и работает почти из коробки. Ставим один плагин:

python3 -m pip install pytest-xdist

И теперь можем запускать тесты в несколько параллельных потоков, например, в 4:

# можно также использовать значение auto, pytest задействует все доступные ядраpytest -n 4

Для того, чтобы проверить работу этого механизма добавим еще один длительный тест:

def test_slow(tmpdir, defines, simtool, gui=False, pytest_run=True):    sim = create_sim(tmpdir, simtool, gui, defines)    sim.defines += ['ITER_N=500000']    sim.setup()    sim.run()    if pytest_run:        assert sim.is_passed

Запустим последовательное и параллельное исполнение (тестов теперь стало 3*5=15):

$ pytest=================== test session starts ====================platform linux -- Python 3.8.5, pytest-6.2.1, py-1.10.0, pluggy-0.13.1rootdir: /space/projects/misc/habr-publications/pyhdlsim/pyhdlsim/simtmpplugins: xdist-2.2.0, forked-1.3.0collected 15 itemstest_sqrt.py ...............                         [100%]============== 15 passed in 242.74s (0:04:02) ==============$ pytest -n auto=================== test session starts ====================platform linux -- Python 3.8.5, pytest-6.2.1, py-1.10.0, pluggy-0.13.1rootdir: /space/projects/misc/habr-publications/pyhdlsim/pyhdlsim/simtmpplugins: xdist-2.2.0, forked-1.3.0gw0 [15] / gw1 [15] / gw2 [15] / gw3 [15]...............                                      [100%]============== 15 passed in 145.66s (0:02:25) ==============

Результат, как говорится, видно невооруженным взглядом.

Поддержка нескольких симуляторов

Ранее уже было показано, что при выполнении теста без pytest можно было выбрать симулятор с помощью ключа -s. Теперь же добавим выбор симулятора для pytest. Очевидно, нужно что-то сделать с фикстурой simtool.

Тут нам пригодится знание о существовании файла conftest.py, необходимого для кастомизации запусков pytest. Создадим такой файл рядом с sim.py и добавим туда следующий код:

def pytest_addoption(parser):    parser.addoption("--sim", action="store", default="icarus")

В файле теста test_sqrt.py обновим фикстуру simtool:

@pytest.fixturedef simtool(pytestconfig):    return pytestconfig.getoption("sim")

Теперь можно прогнать все тесты в другом симуляторе:

pytest --sim modelsim -n auto

Поддержка CI. Github Actions + (Modelsim | Icarus)

Ну и бонусом будет часть о непрерывной интеграции (CI). В репозиторий добавлены два файла .github/workflows/icarus-test.yml и .github/workflows/modelsim-test.yml. Это так называемые Github Actions - по определенному событию будет выполнено их содержимое внутри виртуального окружения, предоставляемого Github. В данном случае, после каждого пуша будут прогнаны все тесты в двух симуляторах.

В Icarus Verilog:

- name: Install dependencies  run: |    python -m pip install --upgrade pip    pip install pytest pytest-xdist    sudo apt-get install iverilog- name: Test code  working-directory: ./sim  run: |    pytest -n auto

И в Modelsim Intel Starter Pack:

- name: Install dependencies  run: |    python -m pip install --upgrade pip      pip install pytest pytest-xdist      sudo dpkg --add-architecture i386      sudo apt update      sudo apt install -y libc6:i386 libxtst6:i386 libncurses5:i386 libxft2:i386 libstdc++6:i386 libc6-dev-i386 lib32z1 libqt5xml5 liblzma-dev    wget https://download.altera.com/akdlm/software/acdsinst/20.1std/711/ib_installers/ModelSimSetup-20.1.0.711-linux.run        chmod +x ModelSimSetup-20.1.0.711-linux.run    ./ModelSimSetup-20.1.0.711-linux.run --mode unattended --accept_eula 1 --installdir $HOME/ModelSim-20.1.0 --unattendedmodeui none    echo "$HOME/ModelSim-20.1.0/modelsim_ase/bin" >> $GITHUB_PATH- name: Test code  working-directory: ./sim  run: |    pytest -n auto --sim modelsim

Тут кстати очень порадовала последняя версия Modelsim. Они наконец-то починили её! Каждый кто хоть раз устанавливал его на Ubuntu/Fedora поймёт о чём я (вот, например, инструкция для Quartus+Modelsim 19.1 и Fedora 29).

Ну и сравнение времени выполнения после очередного пуша в репозиторий:

Даже не смотря на то, что скачивание 1.3GB установочника Modelsim и его распаковка занимают некоторое время (которое тем не менее, очень мало!), он оказывается в итоге ещё и быстрее моментально развертываемого Icarus.

Тут конечно можно пойти дальше и подготовить Docker-образ с Modelsim, чтобы не качать его каждый раз сайта, но я, пожалуй, здесь остановлюсь.

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

Все финальные версии скриптов лежат в репозитории pyhdlsim на GitHub.

Подробнее..
Категории: Python , Fpga , Verilog , Vhdl , Modelsim , Pytest , Vivado , Simulation , Systemverilog , Icarus

Учимся работать с USB-устройством и испытываем систему, сделанную на базе контроллера FX3

11.01.2021 14:11:20 | Автор: admin
В двух предыдущих статьях мы сделали USB 3.0 систему на базе контроллера FX3. Пришла пора научиться работать с нею из своих программ для PC. Ну, и попутно понять, насколько получившаяся система пригодна для практического применения. Действительно ли ширины канала хватает на весь поток? И не теряются ли единичные байты из потока? Кто хоть немного поработал тестировщиком, не поверит в то, что если система в принципе работает, значит, работает и в деталях. А я на этой должности проработал лет пять, не меньше, поэтому привык проверять всё на практике. В общем, приступаем.




1 Теория о методах доступа к USB


1.1 Windows


Когда новое, доселе неизвестное системе USB-устройство впервые вставлено в ЭВМ, работающую под управлением Windows, оно отображается в диспетчере устройств с жёлтым знаком вопроса. Это связано с тем, что Windows обязательно нужен драйвер для работы с ним. Давайте я немного порассуждаю про эти драйверы, но не с научной точки зрения, а как инженер, раскрыв чисто практические аспекты, но несколько упростив теорию. Потому что мне не нужно, чтобы все уснули. Мне нужно, чтобы суть была понятна, так что ряд скучных деталей придётся опустить.

1.1.1 Драйверы, работающие на функциональном уровне


Что такое USB-устройство? Это набор конечных точек. Но прикладному программисту, если честно, эти точки не интересны. Давным-давно, ещё в прошлом тысячелетии, когда последовательные порты делались на микросхеме UART16550, под неё был сделан функциональный драйвер для Windows, и все прикладные программисты привыкли работать с абстракциями именно этого драйвера. И с этой привычкой трудно спорить. Представим на минутку, что с переходником USB-COM придётся работать в USB-шном стиле, на уровне конечных точек. Есть идеология CDC: две конечных точки туда-обратно, одна точка статуса в режиме прерываний и определённый набор команд, подаваемых через конечную точку EP0. Это всё описано в стандартах, относящихся к USB.

Всё? Нет, некоторым этого мало! Prolific сделала свой набор команд для точки EP0, не совместимый с CDC. А FTDI свой. Он не совместим ни с CDC, ни с Prolific. Так что, если бы прикладной программист работал бы с переходниками USB-COM на уровне конечных точек, ему пришлось бы нелегко. К счастью, Prolific и FTDI предоставляют функциональные драйверы для своих чипов, а для CDC функциональный драйвер разработала сама Microsoft и прилагает его в составе Windows. Поэтому прикладной программист понятия не имеет, как работать с конкретным переходником (помню, мы целый NDA лет 15 назад с FTDI подписывали, чтобы получить от них руководство по их командам, причём я сразу же им послал информацию об ошибке в документе, так как пока работали бюрократы, я через дизассемблер всё сам уже успел изучить, так что сразу нашёл несовпадение их описания с моим). На прикладном уровне драйверы всех упомянутых производителей дают интерфейс такой же, как и при работе со старым добрым UART16550.

То же касается и USB-накопителей. Мало кто знает, что физически там две конечных точки. Почти никому не интересно, как там надо гонять пакеты, чтобы посылать команды и данные. Как разруливать ошибки, знает ещё меньше людей. Все работают через драйвер usbstor.sys, предоставляемый Microsoft, который обеспечивает работу точно так же, как и с дисками, подключёнными через локальную шину материнской платы.
Удобно? Однозначно! Но вот мы сделали своё устройство, не совместимое по логике работы ни с каким из стандартных. И что нам теперь, всенепременно писать для него персональный драйвер?

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

1.1.2 CyUSB


С этим драйвером я познакомился раньше других. Было это 12 лет назад. Вот с него и начну рассказ. Правда, скажу лишь вскользь. Фирма Cypress, выпуская замечательные контроллеры FX2LP, просто обязана была сделать драйвер, работающий на уровне конечных точек, чтобы облегчить жизнь своим клиентам. И она его сделала. Кому интересно ищите по слову CyAPI. Это DLL-ка, предоставляющая ряд функций для прикладного программиста. Я как системщик старался обойтись без DLL, поэтому сделал себе библиотеку, работающую с драйвером на уровне IOCTL запросов.

Главный недостаток данной библиотеки заключается в её лицензионном соглашении. Её можно использовать только с контроллерами Cypress. А чтобы всё было убедительнее, начиная с некоторых версий, драйвер стал просто зависать, если он работает с контроллерами других производителей. По крайней мере, старые версии с AT90USB работали, а более свежие нет. Поэтому я решил, что не буду пользоваться данным драйвером. Даже написал свой Но вскоре обнаружился замечательный готовый вариант от Microsoft, и я перешёл на него.

1.1.3 WinUSB


Этот драйвер уходит своими корнями в инфраструктуру UMDF. Когда фирма Microsoft работала над возможностью запускать драйверы на пользовательском уровне защиты, они сделали специальный драйвер-прослойку WinUSB. Но через этот же драйвер можно работать и из обычных прикладных программ, а не только из UMDF-драйверов. Кому интересно вбейте в поиск WinUSB API. Через эту функциональность можно делать то же самое, что и через CyUSB, но с контроллерами любых производителей.

Сам драйвер входит в состав Windows, поэтому в Win7 его можно было вообще ставить без каких-либо проблем с подписыванием. Можно было создать по инструкции от Microsoft или найти и скачать готовый inf файл, поменять в нём VID/PID на свои и установить. К сожалению, начиная с WIN8, обязательно надо подписывать не только сам драйвер, но и INF файл. Однако никто не мешает поправить VID/PID у устройства на тот, который будет найден. Вот у меня есть вот такой подписанный inf файл.
Посмотреть inf файл.
;; Android WinUsb driver installation.;[Version]Signature           = "$Windows NT$"Class               = AndroidUsbDeviceClassClassGuid           = {3F966BD9-FA04-4ec5-991C-D326973B5128}Provider            = %ProviderName%DriverVer           = 06/06/2017,2.0.0010.00003CatalogFile.NTx86   = MT16_x86.catCatalogFile.NTamd64 = MT16_x64.cat;; This section seems to be required for WinUsb driver installation.; If this section is removed the installer will report an error; "Required section not found in INF file".;[ClassInstall32]Addreg = AndroidWinUsbClassReg[AndroidWinUsbClassReg]HKR,,,0,%ClassName%HKR,,Icon,,"-26"[Manufacturer]%ProviderName% = Google, NTx86, NTamd64[Google.NTx86]%CompositeAdbInterface1%     = USB_Install, USB\VID_1234&PID_0001%CompositeAdbInterface2%     = USB_Install, USB\VID_1234&PID_0002%CompositeAdbInterface3%     = USB_Install, USB\VID_1234&PID_0003%CompositeAdbInterface5%     = USB_Install, USB\VID_1234&PID_0005%OldBox%          = USB_Install, USB\VID_4844&PID_8816%HIDkey%          = USB_Install, USB\VID_A1A2&PID_2001[Google.NTamd64]%CompositeAdbInterface1%     = USB_Install, USB\VID_1234&PID_0001%CompositeAdbInterface2%     = USB_Install, USB\VID_1234&PID_0002%CompositeAdbInterface3%     = USB_Install, USB\VID_1234&PID_0003%CompositeAdbInterface5%     = USB_Install, USB\VID_1234&PID_0005%OldBox%          = USB_Install, USB\VID_4844&PID_8816%HIDkey%          = USB_Install, USB\VID_A1A2&PID_2001[USB_Install]Include = winusb.infNeeds   = WINUSB.NT[USB_Install.Services]Include     = winusb.infAddService  = WinUSB,0x00000002,WinUSB_ServiceInstall[WinUSB_ServiceInstall]DisplayName     = %WinUSB_SvcDesc%ServiceType     = 1StartType       = 3ErrorControl    = 1ServiceBinary   = %12%\WinUSB.sys[USB_Install.Wdf]KmdfService = WINUSB, WinUSB_Install[WinUSB_Install]KmdfLibraryVersion  = 1.7[USB_Install.HW]AddReg  = Dev_AddReg[Dev_AddReg]HKR,,DeviceInterfaceGUIDs,0x10000,"{F72FE0D4-CBCB-407d-8814-9ED673D0DD6B}"[USB_Install.CoInstallers]AddReg    = CoInstallers_AddRegCopyFiles = CoInstallers_CopyFiles[CoInstallers_AddReg]HKR,,CoInstallers32,0x00010000,"WdfCoInstaller01009.dll,WdfCoInstaller","WinUSBCoInstaller2.dll"[CoInstallers_CopyFiles]WinUSBCoInstaller2.dllWdfCoInstaller01009.dll[DestinationDirs]CoInstallers_CopyFiles=11[SourceDisksNames]1 = %DISK_NAME%,,,\i3862 = %DISK_NAME%,,,\amd64[SourceDisksFiles.x86]WinUSBCoInstaller2.dll  = 1WdfCoInstaller01009.dll = 1[SourceDisksFiles.amd64]WinUSBCoInstaller2.dll  = 2WdfCoInstaller01009.dll = 2[Strings]ProviderName                = "GM Software"CompositeAdbInterface1      = "MT16 Device"CompositeAdbInterface2      = "MT16 Ports 1-5"CompositeAdbInterface3      = "MT16 Ports 12-16"CompositeAdbInterface5      = "TBR Flasher"OldBox          = "Old MT16 Box"HIDkey          = "HID Dongle via WinUSB"WinUSB_SvcDesc              = "MT16 COM Ports"DISK_NAME                   = "MT16 Driver Installation Directory"ClassName                   = "MT16 USB Devices"


Раньше таких inf файлов на просторах сети было много. Андроид-телефоны через них подключались. Сейчас надо искать по слову WinUSB.sys внутри. Ну, или ServiceBinary = %12%\WinUSB.sys.
Я поправил Прошивку для FX3 вот так:


То же самое текстом.
const uint8_t CyFxUSB30DeviceDscr[] __attribute__ ((aligned (32))) ={    0x12,                           /* Descriptor size */    CY_U3P_USB_DEVICE_DESCR,        /* Device descriptor type */    0x00,0x03,                      /* USB 3.0 */    0x00,                           /* Device class */    0x00,                           /* Device sub-class */    0x00,                           /* Device protocol */    0x09,                           /* Maxpacket size for EP0 : 2^9 */#ifdef CY_VID_PID    0xB4,0x04,                      /* Vendor ID */    0xF1,0x00,                      /* Product ID */#else    0x34,0x12,                      /* Vendor ID */    0x05,0x00,                      /* Product ID */#endif


И теперь могу собирать варианты хоть под CyUSB, хоть под WinUSB с привязкой к имеющемуся у меня inf файлу. А так можно перевести ОС в режим, не требующий подписывания драйверов, хоть это и не очень удобно.

1.1.4 Библиотека libusb


Вариант с WinUSB отличный, но не кроссплатформенный. Насколько мне известно, под Linux нет точно такого же API, который предоставляет Microsoft для Windows. Кроссплатформенный вариант это использование библиотеки libusb. Причём под Windows эта библиотека по умолчанию опирается на всё тот же драйвер WinUSB. Нашли драйвер, накатили его на устройство. Скачали библиотеку, начали через неё работать с этим драйвером. Надо будет через неё же можно будет работать и под Linux. Замечательно? Да. Особенно если мы разработали полностью своё устройство. Но, увы, я просто обязан указать недостаток данного метода для общего случая.

Когда мы устанавливаем на устройство драйвер WinUSB, мы убираем оригинальный драйвер. В рамках данного блока статей мы теряем возможность общаться с нашим устройством при помощи утилиты ControlCenter. Ну, и я не упоминал в статьях про утилиту Streamer, позволяющую измерять скорость устройства В общем, ею мы тоже не сможем пользоваться, если заменим штатный драйвер на WinUSB.

В рамках другой задачи мне надо было экспериментировать с аудиоустройствами из своего приложения. При этом я не хотел постигать все тонкости работы с ними. Я хотел только некоторые команды подавать, а чтобы всё остальное за меня делала сама операционка. Но если бы я посадил устройства на WinUSB, ОС бы потеряла контроль над ними и не могла бы оказывать мне всемерное содействие.

Можно ли работать из прикладной программы с устройствами, не пересаживая их на специальный драйвер? В принципе, да (правда, это очень аккуратное утверждение). Давайте я покажу этот метод Правда, в конце сильно раскритикую его.

1.1.5 Драйвер UsbDk


Библиотека libusb существует в двух версиях. Версия 0.1 и версия 1.0. Обе версии в настоящее время развиваются, создавая некоторую путаницу. И вот версия 1.0 может работать не только через драйвер WinUSB, но и через драйвер UsbDk. Последний является фильтр-драйвером. Что такое фильтр-драйверы? Вспоминаем детство, сказку о царе Салтане:



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

Очень схематично, опустив ряд неважных подробностей, это можно показать так:



Вот тут мы видим, что фильтр-драйвер UsbDK подсел на пути пакетов к нашему устройству (на самом деле, он подсел на пути ко всем USB-устройствам, так как прицепился к драйверу класса USB):



Если при открытии библиотеки libusb 1.0 сказать:
    libusb_init(&m_ctx);    libusb_set_option(m_ctx, LIBUSB_OPTION_USE_USBDK);

то она будет использовать в качестве основы не WinUSB, а UsbDk. Ура? Не совсем. Приведу минимум две проблемы, создаваемые при использовании данного пути.

Первая проблема организационная. Если мы запустили программу, поработали, вышли, то всё будет хорошо. Но вот мы запустили программу, начали работать, а потом почему-то прервались. Да хоть у меня стояла точка останова, исполнение прервалось, я осмотрел переменные, увидел, что программа работает неверно, и завершил её. Могу я так сделать? Дело-то житейское при разработке программы. Или просто зациклилась она. В общем, мы её прервали. Снова запускаем устройство уже не открывается. Смотрим код ошибки он очень интересный.
Driver file operation error. DeviceIoControl failed (Cannot create a file when that file already exists.  Error code = 183)Cannot Open USB Device

И всё. Выдёргивать-вставлять USB-кабель бесполезно. Только полная перезагрузка Windows спасёт Отца Русской Демократии. Когда перезапускаешься третий-четвёртый раз за час это начинает несколько раздражать. Поиск в сети не дал никаких результатов. Попытка бегло осмотреть исходники UsbDk тоже, а на детальные разбирательства нет времени. Может, кто в комментариях чего подскажет

Но на самом деле, эта проблема раздражает, но не является фатальной. Вторая проблема более серьёзная. Вот я поставил VirtualBox. Я просто запустил виртуальную машину и хочу подключить к ней, скажем, бластер. И что получаю?



Аналогично любое другое устройство.

Поиск по сети даёт много вариантов типа: У меня всё заработало после того, как я потёр заячьей лапкой по бубну из кожи тушканчика, спрыснутому кровью семидесятидвухлетней девственницы, полученной Что там дальше в рецепте сейчас уже не помню Тем более, мне он не помог Более осмысленные рекомендации требуют сносить фильтр-драйверы USB, пока не полегчает. Проблема уходит, когда сносишь именно UsbDK. Ну, значится, так тому и быть. Хотя для экспериментов с аудио, других приемлемых вариантов я не нашёл. Так что драйвер я снёс, но дистрибутив оставил. Пригодится. Именно поэтому описываю эту методику. Ну, и вдруг кто в комментариях подскажет, как обходить эти две проблемы. Тогда станет совсем здорово.

1.1.6 Итого
Итого, сегодня мы будем работать через библиотеку libusb, посадив устройство на драйвер WinUSB. Да, мы потеряем возможность работать с устройством через стандартные приложения от Cypress. Зато всё будет стабильно и хорошо.

1.2 Linux


1.2.1 Драйвер


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

Некоторые устройства уже поддержаны в Линуксе по классу, либо по VID/PID. Вот, скажем, я подключил макетную плату ICE40 с ПЛИС Latice и подал сначала команду lsusb, чтобы увидеть USB устройства, а затем уже полюбившуюся нам по прошлым статьям команду:
ls l /dev/serial/by-path
чтобы увидеть, что мост фирмы FTDI сам прикинулся двумя последовательными портами.



С такими устройствами, если ничего не предпринимать, можно работать только на функциональном уровне. Однако, если функциональный драйвер не подключился, как уверяет начальник, в отличие от Windows такие устройства не станут неизвестными (а потому недоступными). Напротив, с ними можно сразу и без какой-либо подготовки работать через библиотеку libusb. А разработанное нами устройство относится к таковым. Поэтому никакой подготовки для начала работы не требуется. Надеюсь, начальник меня не обманул.

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

1.2.2 Отключение требований прав администратора при работе с USB-устройствами


Главная особенность USB-устройств в Linux состоит в том, что по умолчанию для доступа к ним надо обладать правами администратора. То есть, для сборки прошивки для ПЛИС ICE40 (к теме статьи они не относятся, но по проекту я сейчас их осваиваю, причём под Linux, так что скриншоты готовить проще для них) мне достаточно набрать make, а вот если для прошивки я наберу make prog, то получу такое сообщение:



Не хватает прав. Надо набирать sudo make prog.

Чтобы понизить требуемые права, надо прописать правила. А чтобы быстро выяснить, как их прописать, я обычно даю такой запрос Гуглю:
Usb blaster Linux

Ссылок будет много, все они разной степени шаманства. Вот более-менее подробная ссылка:
Using USB Blaster / USB Blaster II under Linux | Documentation | RocketBoards.org

Что сразу бросается в глаза: имена файлов с правилами для Debian и для Ubuntu разные. /etc/udev/rules.d/92-usbblaster.rules и /etc/udev/rules.d/51-usbblaster.rules, соответственно.

Всё тот же начальник, а по совместительству любитель Линукса, объяснил, что если в нескольких файлах есть правила для одного и того же устройства, число задаёт приоритет разбора файлов. Собственно, документ также говорит, что число 92 базируется на том, что в системе имеется файл /lib/udev/rules.d/91-permissions.rules.

Вообще, мне стало интересно, и я вбил в Гугля строку поиска
/etc/udev/rules.d/

Он нашёл мне много статей про udev. Некоторые более теоретические. Некоторые слишком практические. Пересказывать их не вижу смысла. При желании вы можете погуглить сами. Дам только ссылку на статью, которая мне кажется хорошо сбалансированной по теории и практике:
Igorka: Знакомство с udev в ubuntu

Итак. Иду в каталог /etc/udev/rules.d. Вижу файл 70-snap.snapd.rules, в котором есть правило, относящееся ко всем FTDI чипам:

FTDI-based serial adapters
#   FTDI does USB to serial converter ICs; and it's very likely that they'll#   never do modems themselves, so it should be safe to add a rule only based#   on the vendor Id.ATTRS{idVendor}=="0403", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"

Не понимая до конца, что творю, запускаю не просто mc, а sudo mc и в его редакторе создаю файл /etc/udev/rules.d /71-ice40.rules со следующим содержимым:
#FTDI Programmer for laticeSUBSYSTEM=="usb", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6010", MODE="0666"

Закрываю терминал с админскими правами, открываю новый, с правами обычными. Отключаю-включаю устройство, и Вуаля! Оно уже доступно без прав администратора!



Поэтому можете просто повторить мои шаманства, можете почитать теорию по ссылкам, после чего у вас всё получится уже с пониманием физики процесса. А так в рамках данной статьи я буду работать только через Windows, но через кроссплатформенную библиотеку libusb версии 1.0 (не путать с 0.1). Поэтому в Линуксе прописывать правила для устройства FX3 не буду.

2 Практика


Ох, и огромный сегодня получился теоретический раздел! Но наконец, мы владеем достаточным количеством знаний, чтобы приступить к опытам. Напомню, я буду работать в ОС Windows, пользуясь библиотекой libusb 1.0, опираясь на драйвер WinUSB. Я сейчас осваиваю работу с кроссплатформенной библиотекой Qt, поэтому буду разрабатывать код под неё.

2.1 Добавляем libusb в проект


Я скачал библиотеку libusb и распаковал её в каталог своего проекта. Чтобы подключить её к проекту, в файл *.pro пришлось добавить блок:
win32:{LIBS +=  D:/Work/AS/2020/teensy/Qt/UsbSpeedTest/LibUSB/MinGW64/static/libusb-1.0.a}

Кто сказал, что абсолютные пути зло? Золотые слова! Я тоже так считаю. Но я уйму времени убил на эксперименты с относительными путями в этом месте. Ничего не получалось. Поэтому пока сделал так. Если кто-то знает тайну, как в проект добавляются относительные пути, да ещё так, чтобы работали, буду премного благодарен за разъяснения. А пока отмечаю, что такой вариант работает, но однозначно является злом, так как он работает только у меня и только, пока я не соберусь перенести проект куда-то. Собственно, всё. Библиотека добавлена и готова к работе.

2.2 Класс для доступа к библиотеке


Обычно примеры для статей я пишу в слитном стиле. Но сегодняшний код получился несколько запутанным, поэтому пришлось вынести работу с платой FX3 в примитивный класс. Вот его интерфейсная часть:
#ifndef CUSBTESTER_H#define CUSBTESTER_H#include "./LibUSB/libusb.h"class CUsbTester{public:    CUsbTester();    ~CUsbTester();    virtual bool ConnectToDevice();    virtual void DisconnectFromDevice();    libusb_device_handle* m_hUsb;protected:    libusb_context * m_ctx = NULL;};#endif // CUSBTESTER_H

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

В конструкторе происходит инициализация и тонкая настройка библиотеки:
//#define CY_VID_PIDCUsbTester::CUsbTester(){    libusb_init(&m_ctx);#ifdef CY_VID_PID    libusb_set_option(m_ctx, LIBUSB_OPTION_USE_USBDK);#endif    libusb_set_option(m_ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_DEBUG);    m_hUsb = 0;}

Как видим, возможна работа библиотеки через драйвер CyUSB и фильтр UsbDk, но сейчас она отключена. И мы включаем расширенную диагностику работы библиотеки. Все сообщения о проблемах будут сыпаться в окно отладочного вывода. Помните, я показывал сообщение об ошибке при работе UsbDk? Без этой опции никто бы ничего не узнал. Ошибка и ошибка.

Деструктор, соответственно, всё деинициализирует. Класс тестовый, заботиться о наличии дополнительных пользователей библиотекой нет смысла. Так что просто деинициализируем и всё.
CUsbTester::~CUsbTester(){    DisconnectFromDevice();    libusb_exit(m_ctx);}

Подключение к устройству интересно лишь тем, что может работать по разным парам VID/PID. Как я уже говорил, я могу собирать прошивку под штатные VID/PID от Cypress, если идёт работа через UsbDk, и под те, на которые у меня нашёлся inf-файл, устанавливающий драйвер WinUSB. В остальном, ничем не примечательные типовые вызовы функции библиотеки libusb:
bool CUsbTester::ConnectToDevice(){#ifdef CY_VID_PID    m_hUsb = libusb_open_device_with_vid_pid(m_ctx,0x4b4,0xf1);#else    m_hUsb = libusb_open_device_with_vid_pid(m_ctx,0x1234,0x0005);#endif    if (m_hUsb == 0)    {        qDebug()<<"Cannot Open USB Device";        return false;    }    int res = libusb_claim_interface(m_hUsb,0);    if (res != 0)    {        qDebug()<<"Cannot claim interface - "<<libusb_error_name(res);    }    return true;}

Ну, и отключаемся от устройства тоже типовым методом:
void CUsbTester::DisconnectFromDevice(){    if (m_hUsb != 0)    {        libusb_release_interface(m_hUsb,0);        libusb_close(m_hUsb);        m_hUsb = 0;    }}

Собственно, всё. Функцию чтения из устройства я буду вызывать напрямую. Это противоречит принципам проектирования классов, но для опытов сойдёт. Я просто заметил, что код инициализации сильно разросся и отделил его от основного кода, вынеся в класс. А чтение оно в одну строку выполняется, его отделять было не нужно.

2.3 Тестовая программа


Программа состоит из функции main() и тестовой функции.

Тестовая функция делится на две части. Первая часть измеряет скорость передачи данных. Вторая проверяет то, что данные из счётчика приходят с инкрементом. При обнаружении разрыва сведения об этом выдаются в консоль отладочного вывода. Собственно, вот текст функции:
bool TestStep(CUsbTester& tester,uint16_t* pData, const int bytesCnt){    bool bResult = true;    qDebug() << "Testing...";    QElapsedTimer timer;    timer.start();    int actualLength;    int res = libusb_bulk_transfer(tester.m_hUsb,0x81,(uint8_t*)pData,bytesCnt,&actualLength,10000);    if (res != 0)    {        qDebug() << libusb_error_name(res);        return false;    }    quint64 after = timer.elapsed();    qDebug() << "Read Result = " << res;    qDebug() << "Actual Length = " << actualLength;    qDebug() << "Time = " << after;    double speed = bytesCnt/after;    qDebug() << speed << "Bytes / Sec";    uint16_t prevData = pData[0];    for (int i=1;i<bytesCnt/2;i++)    {        if (pData[i] != ((prevData + 1)&0xffff))        {            qDebug() << Qt::hex << i << " : " << prevData << ", " << pData[i];        }        prevData = pData[i];    }    return bResult;}

В будущем я планирую вызывать эту функцию многократно, но пока функция main() вызывает её один раз:
int main(int argc, char *argv[]){    QCoreApplication a(argc, argv);    CUsbTester tester;    if (tester.ConnectToDevice())    {        QByteArray ar1;        ar1.resize(128*1024*1024);        for (int i=0;i<1;i++)        {            TestStep (tester,(uint16_t*)ar1.constData(),ar1.size());        }        tester.DisconnectFromDevice();    }    return a.exec();}

Собственно, и вся программа.

2.4 Результат тестового прогона


2.4.1 Скорость


При тестовом прогоне мы видим, что скорость близка к требуемой. Напомню, что источник гонит 16-битные данные на скорости 60 МГц, так что нам требуется поток 120 миллионов байт в секунду. Когда на временных диаграммах наблюдался огромный дефицит колбасы, скорость была всего 30 Мегабайт в секунду. Даже меньше, чем практически достижимая скорость на шине USB 2.0. Так что текущие результаты вполне приемлемые.



Правда, чтобы окончательно успокоиться, я решил немного поэкспериментировать со скоростью. Чаще всего она была равна 119 0XX XXX байт в секунду. Изредка 119 4XX XXX. Ну, и совсем редко промежуточные значения, похожие на указанные выше. Сначала я решил, что это могут быть ошибки округления. У нас идёт округление показаний таймера, и возникают округления при делении. Тогда я переписал вычисление скорости так (перешёл на наносекунды и стал умножать перед делением, чтобы минимизировать ошибку округления):
    QElapsedTimer timer;    int actualLength;    timer.start();    int res = libusb_bulk_transfer(tester.m_hUsb,0x81,(uint8_t*)pData,bytesCnt,&actualLength,10000);    quint64 after = timer.nsecsElapsed();    if (res != 0)    {        qDebug() << libusb_error_name(res);        return false;    }    qDebug() << "Read Result = " << res;    qDebug() << "Actual Length = " << actualLength;    qDebug() << "Time = " << after;    quint64 size = bytesCnt;    size *= 1000000000;    quint64 speed = size/after;    qDebug() << speed << "Bytes / Sec";

Результат ничуть не изменился. Следующее подозрение было, что источник имеет некоторую ошибку кварцевого резонатора и шлёт данные чуть медленнее, чем надо. Но другая плата даёт точно такие же показания! Значит, дело не в экземпляре. Мало того, осциллограф (правда, весьма дешёвый китайский) говорит, что тактовая частота нисколько не занижена, даже чуть завышена.

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

120 мегабайт в секунду Округлим до ста. Это 1 мегабайт в 10 миллисекунд. А у нас просадка как раз на 1 мегабайт. Неужели начало и конец прокачки съедают так много времени? Как бы проверить хотя бы начерно? Я решил переписать на пробу код под прямой вызов WinUSB API. Полностью объяснять я его не стану, там устройство открывается хитро, через SetupDi API. Я просто воспользовался готовыми своими старыми классами, поэтому всё было сделано под Visual Studio. Но вместо страшных километровых текстов ожидания завершения библиотеки WinUSB, моя функция чтения выглядит вполне канонически:
int CWinUSBConnector::ReadViaBulk (void* pData,int count,int timeOut){DWORD dwRead;ResetEvent (m_hEvRead);if ((!WinUsb_ReadPipe (m_hUsbDrive,m_nReadEP,(BYTE*)pData,count,&dwRead,&m_ovRead)) && (GetLastError() != ERROR_IO_PENDING)){return -1;}if (WaitForSingleObject(m_hEvRead,timeOut)!=WAIT_OBJECT_0){WinUsb_AbortPipe (m_hUsbDrive,m_nReadEP);return -2;} else{WinUsb_GetOverlappedResult (m_hUsbDrive,&m_ovRead,&dwRead,FALSE);}return dwRead;}

А тест теперь выглядит так:
#include <iostream>#include <windows.h>#include "./USB/WinUSBConnector.h"CWinUSBConnector m_connector;int main(){    std::cout << "Hello World!\n";if (m_connector.Open() != 0){std::cerr << "Cannot Open Drive\n";return 1;}LARGE_INTEGER Frequency;QueryPerformanceFrequency(&Frequency);static const UINT64 cnt = 128 * 1024 * 1024;BYTE* pData = new BYTE [cnt];LARGE_INTEGER StartingTime, EndingTime, ElapsedMicroseconds;QueryPerformanceCounter(&StartingTime);UINT64 realSize = m_connector.ReadViaBulk(pData, (int)cnt, 10000);QueryPerformanceCounter(&EndingTime);if (realSize != cnt){std::cerr << "Cannot Read Data\n";return 2;}ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart;ElapsedMicroseconds.QuadPart *= 1000000;ElapsedMicroseconds.QuadPart /= Frequency.QuadPart;UINT64 bytesPerSeconds = (cnt*1000000LL) / ElapsedMicroseconds.QuadPart;std::cout << "Test Finished! " << bytesPerSeconds << " Bytes  per second \n";delete[] pData;return 0;}

Теперь скорость стабильно находится в районе 119 2XX XXX байт в секунду. Вот какой красивый результат я подловил для красного словца:



А вот результат нескольких прогонов подряд:



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

2.4.2 Пропуски в показаниях счётчика


Но на тему разрывов у нас тоже имеется пара строк в выводе основной тестовой программы:



Имеется два нарушения последовательности счёта. Неужели мы нарвались на выпадение данных? К счастью, всё в порядке. Множественные прогоны говорят, что проблемы всегда в одном и том же месте. И мы с таким уже сталкивались в одной из старых статей. У нас есть две точки буферизации данных: буфер контроллера и FIFO в ПЛИС. Готовы ли мы принимать данные или нет, они с выхода счётчика будут заливаться в буфер FX3. Когда тот переполнится, заливка остановится. Кроме буфера FX3, есть ещё FIFO в ПЛИС. Вот у нас и имеется две ёмкости, которые заполнились, но счётчик продолжил работу. При чтении мы увидели разрывы в счёте.

Соответственно, мы наблюдаем явление переходных процессов. При реальной работе надо будет настроить источник так, чтобы он не заполнял буфер данными, пока мы не готовы их принимать (собственно, в статье про голову USB-Анализатора мы уже делали такую функцию через добавление бита Go), а пока просто будем игнорировать ошибки в этой области. Считаем, что это не баги, а фичи. Меняем проверку разрывов на такую:


То же самое текстом.
    int from = 0xc001;    uint16_t prevData = pData[from];    for (int i=from+1;i<bytesCnt/2;i++)    {        if (pData[i] != ((prevData + 1)&0xffff))        {            qDebug() << Qt::hex << i << " : " << prevData << ", " << pData[i];        }        prevData = pData[i];    }


Делаем массовый прогон для такого варианта И тут я понял, кто даёт задержку Отладочный вывод!!! Вот так всё выглядит:



Масса вспомогательных текстов, выдаваемых библиотекой. Ну и отлично. Закомментируем в конструкторе класса, обслуживающего libusb, строку:

// libusb_set_option(m_ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_DEBUG);

Получаем красоту, ничем не отличимую от того, что мы получали при прямом вызове WinUSB API (жаль только, что не идеальный результат):



Но главное нет сообщений про разрывы в показаниях счётчика (известные точки разрывов мы игнорируем).

А что там нам про таймауты промежуточные говорили? Попробуем читать не 128, а 32 мегабайта! Получаем уже скорость вплоть до 119 8XX XXX мегабайт в секунду!



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

3 Заключение


Мы познакомились с теоретическими особенностями работы с USB устройствами в ОС Windows и Linux, а также получили практический навык работы с кроссплатформенной библиотекой libusb. Начерно, разработанное USB 3.0 устройство на базе контроллера FX обеспечивает требуемый поток данных для приёма данных из микросхемы ULPI без промежуточного их сохранения в SDRAM. В потоке не выявлено разрывов, то есть, протокол передачи с виду устойчивый. Автор утверждает, что выявил принципиальную проблему в системе, но выносит её обсуждение в следующую статью.
Подробнее..

Добавляем поддержку Vendor-команд к USB3.0 устройству на базе FX3

02.02.2021 12:18:59 | Автор: admin
В предыдущих статьях мы сделали достаточно интересную железку, состоящую из контроллера FX3 и ПЛИС Cyclone IV. Мы научились гонять через шину USB 3.0 потоки данных с достаточно высокой скоростью (я доказал, что поток 120 МБ/с из ULPI будет проходить через эту систему без искажений и потерь). Всё хорошо, но система, которая просто гонит данные, не имеет смысла. Любую систему надо настраивать. То есть, хочешь не хочешь, а кроме скоростных данных надо слать не очень спешные команды.

У шины USB для передачи команд предназначена конечная точка EP0. Сегодня мы потренируемся дорабатывать прошивку FX3 так, чтобы она обрабатывала команды от PC, а также транслировала их через GPIO в сторону ПЛИС. Кстати, именно здесь проявляется преимущество контроллера над готовым мостом. Что меня в текущей реализации Redd сильно удручает я не могу посылать никаких команд. Их можно только упаковать в основной поток. В случае же с контроллером что хочу, то и делаю. Начинаем творить, что хотим



Предыдущие статьи цикла:
  1. Начинаем опыты с интерфейсом USB 3.0 через контроллер семейства FX3 фирмы Cypress
  2. Дорабатываем прошивку USB 3.0, используя анализатор SignalTap, встроенный в среду разработки Quartus
  3. Учимся работать с USB-устройством и испытываем систему, сделанную на базе контроллера FX3
  4. Боремся с таймаутами при использовании USB 3.0 через контроллер FX3, возникающими при определенных условиях

Введение


Осматривая исходники типовой прошивки, я нашёл знакомое имя функции в файле cyfxgpiftousb.c. Функцию зовут:
/* Callback to handle the USB setup requests. */CyBool_tCyFxApplnUSBSetupCB (        uint32_t setupdat0, /* SETUP Data 0 */        uint32_t setupdat1  /* SETUP Data 1 */    )

Имея за плечами опыт работы с кучкой USB-контроллеров, начиная от прямого предка нашего (это был FX2LP), через STM32 и далее со всеми остановками, я уже нутром чую, что нужная нам функциональность начинается здесь. Собственно, код этой функции как раз разбирает команды группы STANDARD Request. Осталось добавить туда свою группу VENDOR COMMANDS. Жаль только, что все команды, которые уже имеются в готовой функции, не передают данных. Они ограничиваются работой с полями wData и wIndex, Мне этого недостаточно. Я хочу передавать в ПЛИС байт и два 32-битных слова (команда, адрес, данные), либо передавать байт и DWORD, после чего принимать DWORD (передали команду и адрес, приняли данные). То есть, без фазы данных точно не обойтись. Начинаем разбираться, где черпать вдохновение и добавлять желаемую функциональность.

Участок в зоне ответственности шины USB


Итак. Добавить фазу данных. Гуглю по слову:
CyU3PUsbAckSetup

И первая же ссылка ответила на все мои вопросы. На всякий случай вот она.

В том коде данные гоняют и туда, и обратно. Хорошо. Начнём с малого. Сначала вставляем только прогон данных через USB, без их передачи в ПЛИС. Будем для самоконтроля отправлять данные в UART, а при приёме, чтобы не тратить время на сложный вспомогательный код, просто будем заполнять память константами 00, 01 02 03

Добавляем в конец функции CyFxApplnUSBSetupCB() такой блок:
    if (bType == CY_U3P_USB_VENDOR_RQT)    {    // Cut size if needif (wLength > sizeof(ep0_buffer)){wLength = sizeof (ep0_buffer);}    // Need send data to PC    if (bReqType & 0x80)    {    int i;    for (i=0;i<wLength;i++)    {    ep0_buffer [i] = (uint8_t) i;    }    CyU3PUsbSendEP0Data (wLength, ep0_buffer);            isHandled = CyTrue;    } else    {    CyU3PUsbGetEP0Data (wLength, ep0_buffer, NULL);    ep0_buffer [wLength] = 0;// Null terminated String            CyU3PDebugPrint (4, (char*)ep0_buffer);    CyU3PUsbAckSetup();            isHandled = CyTrue;    }    }

Волшебная константа 0x80 согласен, что некрасивая, но не нашлось ничего подходящего в заголовках в районе изучаемого участка, а дальше искать не хотелось. Но, наверное, все помнят, что именно старший бит задаёт направление. Мало того, я в терминологии USB вечно путаюсь, что значит IN, что значит OUT. Я просто запомнил, что, когда есть 0x80 данные бегут в PC. Остальное, вроде, всё красиво и понятно получилось, даже не требует комментариев.

Чтобы не писать своей тестовой программы, проверять я сегодня буду в сниффере BusHound. Если в нём дважды щёлкнуть по устройству, то появляется очень полезный диалог. Вот тут щёлкаем:



И вот такую красоту получаем:



Я заполнил тип команды 0xC0 (Vendor Specific, данные из устройства в PC). Код команды я сделал равным 23 просто так, чисто во время экспериментов. Сейчас туда можно вписать всё, что угодно, в функции это поле не проверяется. Не проверяются и поля Value и Index. А вот когда я вбил поле Length, у меня внизу появился дамп. Всё готово к посылке команды. Нажимаем Run, получаем:



Всё верно. Функция CyFxApplnUSBSetupCB() посылает из FX3 в USB инкрементирующиеся байты, мы их видим. Теперь пробуем передавать. Подключаем UART (как это сделать я рассказывал в одной из предыдущих статей), запускаем терминал. Меняем тип запроса на 0x40 (Vendos Specific Command, данные из PC в устройство). Заполняем поля данных ASCII символами:



Жмём Run получаем:



Прекрасно! Эта часть готова! Переходим к работе с аппаратурой.

Работа с GPIO


Грустная теория


В том же примере, который я нашёл на github, идёт и работа с GPIO. Вот как красиво выглядит это в пользовательской части:
CyU3PGpioSetValue (FPGA_SOFT_RESET,  !((ep0_buffer[0] & GPIO_FPGA_SOFT_RESET) > 0)); CyU3PGpioSetValue (FMC_POWER_GOOD_OUT, ((ep0_buffer[0] & GPIO_FMC_POWER_GOOD_OUT) > 0));

Красиво? Ну, конечно же, красиво! Но впору вспомнить, что я писал в одной из статей про нашу ОСРВ МАКС.

Я там рассказывал, что операторы new и delete по факту раскрываются в огромный кусок кода с непредсказуемым временем исполнения. Примерно так и тут. Функция CyU3PGpioSetValue() раскрывается в такую громаду, что я спрячу её под кат.
Смотреть текст функции CyU3PGpioSetValue().
CyU3PReturnStatus_tCyU3PGpioSetValue (                   uint8_t  gpioId,                   CyBool_t value){    uint32_t regVal;    uvint32_t *regPtr;    if (!glIsGpioActive)    {        return CY_U3P_ERROR_NOT_STARTED;    }    /* Check for parameter validity. */    if (!CyU3PIsGpioValid(gpioId))    {        return CY_U3P_ERROR_BAD_ARGUMENT;    }    if (CyU3PIsGpioSimpleIOConfigured(gpioId))    {        regPtr = &GPIO->lpp_gpio_simple[gpioId];    }    else if (CyU3PIsGpioComplexIOConfigured(gpioId))    {        regPtr = &GPIO->lpp_gpio_pin[gpioId % 8].status;    }    else    {        return CY_U3P_ERROR_NOT_CONFIGURED;    }           regVal = (*regPtr & ~CY_U3P_LPP_GPIO_INTR);    if (!(regVal & CY_U3P_LPP_GPIO_ENABLE))    {        return CY_U3P_ERROR_NOT_CONFIGURED;    }    if (value)    {        regVal |= CY_U3P_LPP_GPIO_OUT_VALUE;    }    else    {        regVal &= ~CY_U3P_LPP_GPIO_OUT_VALUE;    }    *regPtr = regVal;    regVal = *regPtr;    return CY_U3P_SUCCESS;}


Какое будет максимальное быстродействие у кода, вызывающего эту функцию в цикле, мне страшно подумать. У неё есть более компактный аналог, но и его я предпочту спрятать под кат.
Более компактный аналог.
CyU3PReturnStatus_tCyU3PGpioSimpleSetValue (                         uint8_t  gpioId,                         CyBool_t value){    uint32_t regVal;    if (!glIsGpioActive)    {        return CY_U3P_ERROR_NOT_STARTED;    }    /* Check for parameter validity. */    if (!CyU3PIsGpioValid(gpioId))    {        return CY_U3P_ERROR_BAD_ARGUMENT;    }    regVal = (GPIO->lpp_gpio_simple[gpioId] &        ~(CY_U3P_LPP_GPIO_INTR | CY_U3P_LPP_GPIO_OUT_VALUE));    if (value)    {        regVal |= CY_U3P_LPP_GPIO_OUT_VALUE;    }    GPIO->lpp_gpio_simple[gpioId] = regVal;    return CY_U3P_SUCCESS;}


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

Чуть более оптимистичная теория


Чтобы не хранить маску записанных в порт данных, а также обеспечить себе максимальную потокобезопасность, мы можем воспользоваться аппаратурой, дающей независимый доступ к каждому биту порта. Вдохновение мы будем искать в разделе 9.2 GPIO Register Interface документа FX3_Programmers_Manual.pdf.

Вот так выглядит блок GPIO:



Мы видим, что кроме классического двоичного представления, есть такое, где каждой линии (а их в контроллере 61 штука) соответствует собственное 32-разрядное слово. Формат его такой:



Собственно, всё ясно. Так как я собираюсь работать с конкретными линиями GPIO, я вполне могу обращаться к битам IN_VALUE и OUT_VALUE в этих регистрах. Больше мне ничего и не надо. Ну, и настройку направления можно произвести здесь же.

С какими линиями мы работаем


Хорошо. Как нам достукиваться до линий, понятно. А как они адресуются? Что за 61 линия GPIO, о которых говорится в документации? С чем предстоит работать мне? Плату для меня разводил знакомый, которому я поставил очень простую задачу: несколько свободных линий от FX3 завести на ПЛИС. Так как конкретные номера не были мною обозначены, он взял те, которые захотел. Вот участок ПЛИС, к которому подходят линии GPIO, именованные в той нотации, какая задана на шелкографии около разъёма макетки:



Я собираюсь программно реализовать шину SPI, значит, мне надо 4 линии (выбор кристалла, тактовый сигнал и данные туда-обратно). Возьмём линии от DQ24 до DQ27 по принципу А почему бы и нет?. В одной из прошлых статей, я уже показывал таблицу, при помощи которой мы можем быстро сопоставить эти имена с реальными линиями GPIO. Смотрим в неё:



Значит, нас интересуют линии GPIO 41, 42, 43 и 44. Вот с ними я и буду работать.

Инициализация GPIO


Все, кто хорошо знаком с архитектурой ARM, знают, что любые порты надо инициализировать. Как это сделать в нашем случае? Мы работаем с демонстрационным приложением, так что часть работы уже сделана за нас. Доработаем кое-что из готового кода. В функции main(), есть такой участок:
io_cfg.isDQ32Bit = CyTrue;
io_cfg.useUart = CyTrue;
io_cfg.useI2C = CyFalse;
io_cfg.useI2S = CyFalse;
io_cfg.useSpi = CyFalse;
io_cfg.lppMode = CY_U3P_IO_MATRIX_LPP_DEFAULT;

/* No GPIOs are enabled. */
io_cfg.gpioSimpleEn[0] = 0;
io_cfg.gpioSimpleEn[1] = 0;
io_cfg.gpioComplexEn[0] = 0;
io_cfg.gpioComplexEn[1] = 0;
status = CyU3PDeviceConfigureIOMatrix (&io_cfg);

Поправим его так:



То же самое текстом.
    io_cfg.isDQ32Bit = CyFalse;    io_cfg.useUart   = CyTrue;    io_cfg.useI2C    = CyFalse;    io_cfg.useI2S    = CyFalse;    io_cfg.useSpi    = CyFalse;    io_cfg.lppMode   = CY_U3P_IO_MATRIX_LPP_UART_ONLY;    /* No GPIOs are enabled. */    io_cfg.gpioSimpleEn[0]  = 0;    io_cfg.gpioSimpleEn[1]  = (1<<9)|(1<<10)|(1<<11)|(1<<12);    io_cfg.gpioComplexEn[0] = 0;    io_cfg.gpioComplexEn[1] = 0;    status = CyU3PDeviceConfigureIOMatrix (&io_cfg);


Биты 9, 10, 11 и 12 в коде это биты старшего слова. Поэтому физически они соответствуют битам GPIO 9+32=41, 10+32=42, 11+32=43 и 12+32=44. Тем самым, с которыми я собираюсь работать.
Зададим ещё им направления. Скажем, я раскидаю их так:

Бит Цепь Направление
41 SS OUT
42 CLK OUT
43 MOSI OUT
44 MOSI IN


Объявим для этого следующие макросы:
#define MY_BIT_SS    41#define MY_BIT_CLK   42#define MY_BIT_MOSI  43#define MY_BIT_MISO  44

А в функцию CyFxApplnInit() добавим такой код:
    CyU3PGpioClock_t     gpioClock;    gpioClock.fastClkDiv = 2;    gpioClock.slowClkDiv = 16;    gpioClock.simpleDiv  = CY_U3P_GPIO_SIMPLE_DIV_BY_2;    gpioClock.clkSrc     = CY_U3P_SYS_CLK;    gpioClock.halfDiv    = 0;    apiRetStatus = CyU3PGpioInit (&gpioClock, NULL);    if (apiRetStatus != CY_U3P_SUCCESS)    {        CyU3PDebugPrint (4, "GPIO Init failed, error code = %d\r\n", apiRetStatus);        CyFxAppErrorHandler (apiRetStatus);    }    GPIO->lpp_gpio_simple[MY_BIT_SS] = CY_U3P_LPP_GPIO_OUT_VALUE | CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE;    GPIO->lpp_gpio_simple[MY_BIT_CLK] = CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE;    GPIO->lpp_gpio_simple[MY_BIT_MOSI] = CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE;    GPIO->lpp_gpio_simple[MY_BIT_MISO] = CY_U3P_LPP_GPIO_INPUT_EN | CY_U3P_LPP_GPIO_ENABLE;

Всё, блок GPIO инициализирован, направления заданы. А линия SS ещё и взведена в единицу. Можно начинать пользоваться GPIO для реализации функциональности.

Участок в зоне ответственности аппаратуры


Запись в SPI я сделаю в виде макросов взвести в 1 и Сбросить в 0 (увы, именно макросов, перед нами же код на чистых Сях, в плюсах я бы сделал на шаблонных функциях) и одной функции, которая обращается к ним. Получилось так:
#define SET_IO_BIT(nBit) GPIO->lpp_gpio_simple[nBit] |= CY_U3P_LPP_GPIO_OUT_VALUE#define CLR_IO_BIT(nBit) GPIO->lpp_gpio_simple[nBit] &= ~CY_U3P_LPP_GPIO_OUT_VALUEvoid SPI_Write (unsigned int data, int nBits){   while (nBits)   {   if (data&1)   {   SET_IO_BIT (MY_BIT_MOSI);   } else   {   CLR_IO_BIT (MY_BIT_MOSI);   }   SET_IO_BIT (MY_BIT_CLK);   data >>= 1;   nBits -= 1;   CLR_IO_BIT (MY_BIT_CLK);   }}

Соответственно, вместо вывода в UART в ранее написанном обработчике USB-команд, я сделаю вывод в SPI, но по очень хитрому алгоритму. Сначала байт USB-команды. Затем слова wData и wIndex, и потом DWORD, пришедший в фазе данных. При такой солянке сборной, удобнее всё передавать младшим битом вперёд (именно так работает функция SPI_Write()).

Чтение я пока делать не буду. Сейчас проверяется сама идея. Чтобы проверить чтение, надо делать прошивку и для ПЛИС, а запись я могу проконтролировать и при помощи осциллографа.

В результате, код обработчика Vendor-команды трансформируется следующим образом:
    // Need send data to PC    if (bReqType & 0x80)    {    int i;    for (i=0;i<wLength;i++)    {    ep0_buffer [i] = (uint8_t) i;    }    CyU3PUsbSendEP0Data (wLength, (uint8_t*)ep0_buffer);            isHandled = CyTrue;    } else    {    CyU3PUsbGetEP0Data (wLength, (uint8_t*)ep0_buffer, NULL);    ep0_buffer [wLength] = 0;// Null terminated String            CyU3PDebugPrint (4, (char*)ep0_buffer);            CLR_IO_BIT(MY_BIT_SS);            SPI_Write(bRequest,8);            SPI_Write(wValue,16);            SPI_Write(wIndex,16);            SPI_Write(ep0_buffer[0],32);            SET_IO_BIT(MY_BIT_SS);    CyU3PUsbAckSetup();            isHandled = CyTrue;    }

Итого


Итого, даём такой запрос:



И получаем такой результат:



Немного оптимизации


Видно, что данные передаются младшим битом вперёд, хорошо видны байт 0x23 и начало байта 0x55. Всё верно. Правда, частота, конечно, не ахти (её можно разглядеть, если кликнуть по рисунку и посмотреть его в увеличенном виде). Примерно 1.2 мегагерца. В целом, меня сейчас это сильно не беспокоит, но здесь скорее важен сам принцип. Не люблю, когда всё совсем медленно, и всё тут! Смотрим, во что превратилась функция записи, в этом нам поможет файл GpifToUsb.lst:
40003404 <SPI_Write>:40003404:ea00000d b40003440 <SPI_Write+0x3c>40003408:e59f303c ldrr3, [pc, #60]; 4000344c <SPI_Write+0x48>4000340c:e3100001 tstr0, #140003410:e59321ac ldrr2, [r3, #428]; 0x1ac40003414:e1a000a0 lsrr0, r0, #140003418:13822001 orrner2, r2, #14000341c:03c22001 biceqr2, r2, #140003420:e58321ac strr2, [r3, #428]; 0x1ac40003424:e59321a8 ldrr2, [r3, #424]; 0x1a840003428:e2411001 subr1, r1, #14000342c:e3822001 orrr2, r2, #140003430:e58321a8 strr2, [r3, #424]; 0x1a840003434:e59321a8 ldrr2, [r3, #424]; 0x1a840003438:e3c22001 bicr2, r2, #14000343c:e58321a8 strr2, [r3, #424]; 0x1a840003440:e3510000 cmpr1, #040003444:1affffef bne40003408 <SPI_Write+0x4>40003448:e12fff1e bxlr4000344c:e0001000 .word0xe0001000

16 строк. Вполне компактно Я уже много раз писал, что не собираюсь становиться гуру FX3. Поэтому решил не вчитываться в километры документов, а поиграть с кодом на практике. Само собой, несколько часов опытов я опущу, и приведу только итоговый результат. Так что немножко младшим учеником старшего помощника второго заместителя гуру побыть пришлось Но так или иначе. Я изучил вопрос настройки тактирования GPIO и пришёл к выводу, что оно вполне оптимальное.

Но напишем такой тестовый блок кода (первый макрос роняет значение в порту, второй взводит, а дальше идёт чреда взлётов и падений):
#define DOWN GPIO->lpp_gpio_simple[MY_BIT_CLK] = CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE#define UP GPIO->lpp_gpio_simple[MY_BIT_CLK] = CY_U3P_LPP_GPIO_OUT_VALUE | CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;

Ему соответствует участок ассемблерного кода, оптимизировать который в целом, невозможно. Он идеален:
400036c4:e58421a8 strr2, [r4, #424]; 0x1a8400036c8:e58431a8 strr3, [r4, #424]; 0x1a8400036cc:e58421a8 strr2, [r4, #424]; 0x1a8400036d0:e58431a8 strr3, [r4, #424]; 0x1a8400036d4:e58421a8 strr2, [r4, #424]; 0x1a8400036d8:e58431a8 strr3, [r4, #424]; 0x1a8

Результат прогона (получаем меандр с частотой 12.5 МГц):



А теперь заменим запись констант с прямой записи на чтение модификацию запись, как это реализовано в моих макросах для SPI:
#define UP GPIO->lpp_gpio_simple[MY_BIT_CLK] |= CY_U3P_LPP_GPIO_OUT_VALUE#define DOWN GPIO->lpp_gpio_simple[MY_BIT_CLK] &= ~CY_U3P_LPP_GPIO_OUT_VALUE    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;

В ассемблерном коде покажу только одну итерацию вверх-вниз

400036e4: e59431a8 ldr r3, [r4, #424]; 0x1a8
400036e8: e3c33001 bic r3, r3, #1
400036ec: e58431a8 str r3, [r4, #424]; 0x1a8
400036f0: e59431a8 ldr r3, [r4, #424]; 0x1a8
400036f4: e3833001 orr r3, r3, #1
400036f8: e58431a8 str r3, [r4, #424]; 0x1a8

Вместо пары строк получаем шесть. Частота упадёт втрое? Делаем прогон



12.5/1.9=6.6
Более, чем в шесть раз частота упала! Получается, что чтение из порта довольно медленная операция. Значит, чуть переписываем мои макросы записи в порт, убирая из них операции чтения:
#define SET_IO_BIT(nBit) GPIO->lpp_gpio_simple[nBit] = CY_U3P_LPP_GPIO_OUT_VALUE | CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE#define CLR_IO_BIT(nBit) GPIO->lpp_gpio_simple[nBit] = CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE

Делаем прогон записи в SPI



4 мегагерца. Ну вот. Не особо напрягаясь, разогнали систему почти вчетверо. Меня не покидает ощущение, что всё можно разогнать ещё сильнее, но оставим это на потом. Сейчас особо это не требуется.

Заключение


Мы освоили механизм добавления VENDOR команд в USB-устройство на базе FX3. При этом мы испытали работу с командами, передающими данные через конечную точку EP0 в обоих направлениях. Также мы освоили работу с GPIO у этого контроллера. Теперь, кроме скоростной передачи через конечные точки типа BULK и GPIF, мы можем передавать команды в свою прошивку ПЛИС.

А для чего я хочу это применять, будет рассказано в следующей статье.
Подробнее..

Делаем блок SPI to AVALON_MM для USB-устройства на базе FX3

24.02.2021 16:18:54 | Автор: admin
В предыдущей статье мы научились подавать Vendor команды в устройство USB3.0 на базе контроллера FX3 и реализовали программную шину SPI. Сегодня мы продолжим начатое и сделаем компонент SPI to Avalon_MM. Может возникнуть вопрос: мы же уже умеем работать с шиной Avalon_MM через JTAG средствами TCL-скриптов, зачем нам что-то ещё?

Дело в том, что когда мы работаем на чистом TCL, как делали это здесь и здесь, всё замечательно. Но для задач, гоняющих десятки или даже сотни мегабайт, этот вариант слишком медленный. Поэтому мы вынуждены добавить программу на С++, работающую через USB 3.0.

Вариант с TCL-сервером, к которому обращается плюсовая программа, рассмотренный в этой статье, требует сложной ручной подготовки при каждом запуске. Надо обязательно запустить среду исполнения (для Windows и Linux они разные), запустить серверный скрипт, а затем в программе синхронизировать работу с данными по USB и с командами через TCP. Не люблю такие сложности. Оставим те варианты под случаи, когда они не создают трудностей. Здесь же у нас есть USB-устройство, мы всё равно с ним работаем, вот и будем обращаться к шине AVALON_MM через него. Приступаем.



Предыдущие статьи цикла:

  1. Начинаем опыты с интерфейсом USB 3.0 через контроллер семейства FX3 фирмы Cypress
  2. Дорабатываем прошивку USB 3.0, используя анализатор SignalTap, встроенный в среду разработки Quartus
  3. Учимся работать с USB-устройством и испытываем систему, сделанную на базе контроллера FX3
  4. Боремся с таймаутами при использовании USB 3.0 через контроллер FX3, возникающими при определенных условиях
  5. Добавляем поддержку Vendor-команд к USB3.0 устройству на базе FX3

Введение


Перед тем как заняться разработкой своего блока, я попытался найти что-то готовое. Да, работа с TCL как напрямую, так и через сеть создаёт ряд неудобств для пользователя. Но нельзя ли достучаться до JTAG-адаптера напрямую? Ну, или хотя бы подключиться к JTAG-серверу, как это делают штатные компоненты системы? Я мучил Гугля всё более и более мудрёными запросами. Увы. Есть вариант, когда сервер реализуется на самой плате (только в нашем варианте с платы выкинут процессор, не на чем его там запустить), но нет примеров, как подключиться к JTAG-серверу, запущенному на PC. Были статьи про существующий сервер для проекта Марсоход, запускаемый на Малине, но насколько я понял, там надо подменять DLL. Было ещё несколько статей с явно нужными ключевыми словами, но все они были удалены, а в кэше Гугля лежало что-то, совершенно нечитаемое.

Я даже выдвинул гипотезу, что дело в ядрах с ограниченными правами использования. Тех, которые работают только тогда, когда подключены к PC с запущенным сервером. Возможно, кто поймёт принцип управления JTAG-сервером, тот сможет их всколоть, а правообладатели этого не хотят, поэтому тщательно скрывают протоколы. У нас нет задачи что-то всколоть, а у меня нет желания писать статью, которую быстро удалят. Поэтому я решил просто сделать свой блок. Какой? Решение выплывает из моей текущей рабочей загрузки. Я играю в среду Litex. Там используется шина AXI-Lite или Wishbone. Я работаю со второй из них и вижу в системе массу переходников. Там есть и SPI to Wishbone, и UART to Wishbone и всё, что угодно to Wishbone. Поэтому я и решил сделать переходник SPI to Avalon-MM.

Где черпаем вдохновение


Если вбить Гуглю запрос Avalon Memory-Mapped Master Templates, то мы попадём вот сюда:

Avalon Memory-Mapped Master Templates (intel.com)

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



Мы должны реализовать участок Control Logic и FIFO, после чего всё заработает само. Сначала я гипнотизировал эти примеры, мечтая пойти именно по этому пути. Первое, что не давало покоя: мастер чтения и мастер записи разные блоки, каждый из которых подключается к шине AVALON. Соединить их не так просто. Затем, начав практические опыты, я понял, что иерархия там тоже получается не самая лучшая. Мне бы пришлось сделать очень много транзитных верёвок. Система становилась слишком сложной, а выше я уже писал, что не люблю сложных систем.

Тогда я внимательно посмотрел на исходные тексты и понял, что на самом деле там реализуются действия, сложность которых не выше, чем у блоков AVALON_MM Slave, которые мы уже делаем, как семечки щёлкаем. Там нет ничего страшного. Нет никаких линий GNT, характерных для некоторых взрослых шин. Вообще ничего нет. Знай себе, ставь стробы и удерживай их, пока не получил подтверждения, что данные прокачались. Всё! Остальное за нас сделает логика, являющаяся внешней по отношению к нашему блоку (она спрятана от нас где-то внутри System Interconnect Fabric).

Некоторые небольшие трудности возникли бы при отладке пакетных передач, но я же не собираюсь их гонять! У меня очень медленная шина SPI (в прошлой статье мы видели, что на ней частота тактовых сигналов не превышает 4 МГц, а данных по ней пробегает 8 + 32 + 32 = 72 бита, итого предельная частота следования данных 55,(5) КГц). Так что получили запрос прогнали одно слово по Avalon, отпустили шину. Ждём следующий запрос. Не нужны тут пакеты!

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

Главный автомат


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



На этом рисунке линия называется не SS (Slave Select), а CS (Chip Select). Но мы видим, что её высокий уровень можно трактовать как сброс шины. Это очень удобно. Не надо бояться, что произойдёт рассинхронизация. Мы почти уверены, что перед первым битом этот сигнал перейдёт из единицы в ноль. В своём коде для FX3 я сделаю так, чтобы быть уверенным не почти, а стопроцентно.

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



Когда значение bit_cnt достигло сорока (из-за особенностей языков Verilog, да и VHDL тоже, в коде используется константа 39), мы выходим на рабочий участок. Команд может быть две: чтение и запись. За это отвечает нулевой бит команды (из-за той же особенности языка в коде проверяется первый). Вот так выглядит обработчик этого состояния на SystemVerilog:

Смотерть текст.
always_ff @(posedge master_clk, posedge spi_ss)begin      // Это эквивалентно сбросу со стороны SPI      if (spi_ss == 1)      begin          bit_cnt <= 0;          state <= idle;      end else      begin          master_write <= 0;          master_read <= 0;          case (state)            idle: begin                 // Ура! У нас очередной перепад SCK!                 if ((spi_sck_d == 0) && (spi_sck_reg == 1))                 begin                    regCmdAndAddr <= {spi_mosi_reg,regCmdAndAddr[39:1]};                    bit_cnt <= bit_cnt + 1;                    // Особенность машинного языка - анализируем не новое,                     // а предыдущее значение                    if (bit_cnt == 39)                    begin                       // Пишем                       if (regCmdAndAddr[1])                       begin                           state <= write1;                       end else                       // Читаем                       begin                           state <= read1;                       end                    end                 end            end


Чтобы хоть как-то оправдать восьмибитный регистр команды, я использую старшие 4 бита как BYTE_ENABLE для шины AVALON_MM, чтобы дать возможность писать не только по 32 бита. Для этого в конце текста есть такая строка:

assign master_byteenable = regCmdAndAddr [7:4];assign master_address = regCmdAndAddr [39:8];

Вторая строка в этой паре подключает наш регистр адреса к линиям адреса шины AVALON_MM.

Теперь пройдёмся по ветви чтения. Сначала надо считать данные из шины. Я исхожу из предположения, что шина SPI крайне медленная, поэтому не ввожу на неё никакого сигнала готовности. Считаем, что данные из AVALON_MM придут так быстро, что в SPI не успеет убежать ни одного лишнего бита. Нам потребуется два состояния. В первом мы взведём строб чтения и будем удерживать его, пока нам не придёт подтверждения, что данные пришли. Тогда мы защёлкнем эти данные и будем выдавать их в SPI на протяжении тридцати двух тактов. Собственно, всё. Потом мы на всякий случай сбросим счётчик битов (а вдруг начнётся передача новой команды без снятия SS?) и вновь перейдём в состояние idle, где будем копить новую команду. Состояние read1 выделено особым цветом, так как оно не зависит от положительного перепада на линии SCK шины SPI. Оно привязано только к тактовому сигналу шины AVALON_MM.



Вот так реализованы эти состояния в автомате.

Cмотреть текст.
            read1: begin                  master_read <= 1;                  if (master_readdatavalid)                  begin                      state <= read2;                      regData <= master_readdata;                  end            end            read2: begin                 if ((spi_sck_d == 0) && (spi_sck_reg == 1))                 begin                    regData <= {1'b0,regData[31:1]};                    bit_cnt <= bit_cnt + 1;                    // Особенность машинного языка - анализируем не новое,                     // а предыдущее значение                    if (bit_cnt == 71)                    begin                       bit_cnt <= 0;                       state <= idle;                    end                 end            end


И вот так привязан выход регистра сдвига к сигналу MOSI шины SPI:

assign spi_miso = regData [0];

Ветка записи с точностью до наоборот. Сначала в состоянии write1 мы принимаем 32 бита данных, затем выставляем строб записи и висим в состоянии write2 (также не привязанным к SCK, поэтому имеющем особый цвет), пока нам не сообщат, что наши данные ушли.



Получаем такой код для реализации состояний.

Смотреть текст.
            // При записи, надо сначала допринимать данные из SPI            write1: begin                 // Ура! У нас очередной перепад SCK!                 if ((spi_sck_d == 0) && (spi_sck_reg == 1))                 begin                    regData <= {spi_mosi_reg,regData[31:1]};                    bit_cnt <= bit_cnt + 1;                    // Особенность машинного языка - анализируем не новое,                     // а предыдущее значение                    if (bit_cnt == 71)                    begin                       state <= write2;                    end                 end            end            // Всё заполнено. Держим строб записи.             write2: begin               master_write <= 1;               // Если шина нас услышала - ну и прекрасно. Вышли               if (master_waitrequest == 0)               begin                   bit_cnt <= 0;                   state <= idle;               end            end


и такую строку для статического проецирования регистра данных на линии данных AVALON_MM:

assign master_writedata = regData;

Остальной текст модуля необходимая технологическая мишура. Нам надо продискретизировать линии SPI по тактовой частоте шины AVALON_MM, кроме того, надо получить задержанный на один авалоновский такт сигнал SCK шины SPI, чтобы иметь возможность ловить его перепад. Все эти действия мы имеем право выполнять только если уверены, что шина SPI достаточно медленная относительно AVALON_MM. Именно поэтому в прошлой статье я занимался оптимизацией, но не гнался за бешеными показателями.

reg spi_sck_reg, spi_sck_d;reg spi_mosi_reg;always @ (posedge master_clk)begin     spi_sck_reg <= spi_sck;     spi_mosi_reg <= spi_mosi;     spi_sck_d <= spi_sck_reg;end

Cобственно, всё. Модуль готов. Вот его полный текст для справки.

Смотреть полный текст модуля.
module spitoAvalon_mm (input           master_clk,input           master_reset,output  [31:0]    master_address,output  reg       master_write=0,output  [3:0]     master_byteenable,output  [31:0]    master_writedata,output reg        master_read,input             master_readdatavalid,input [31:0]    master_readdata,input             master_waitrequest,input           spi_sck,input           spi_mosi,output          spi_miso,input           spi_ss);// Чтобы не заводить SPI_SCK на линию GCK,// мы ориентируемся на то, что шина - медленная, поэтому// просто ловим перепады по основной тактовой частоте// Да и вообще, отдискретизируем SPI по тактовой. Во избежание...reg spi_sck_reg, spi_sck_d;reg spi_mosi_reg;always @ (posedge master_clk)begin     spi_sck_reg <= spi_sck;     spi_mosi_reg <= spi_mosi;     spi_sck_d <= spi_sck_reg;end// Число битов, принятых из SPIreg [7:0] bit_cnt;// Регистр команд/адреса. Итого 8 + 32 = 40 битreg [39:0] regCmdAndAddr = 0;reg [31:0] regData = 0;enum {idle,        read1, read2,       write1,write2     } state = idle;always_ff @(posedge master_clk, posedge spi_ss)begin      // Это эквивалентно сбросу со стороны SPI      if (spi_ss == 1)      begin          bit_cnt <= 0;          state <= idle;      end else      begin          master_write <= 0;          master_read <= 0;          case (state)            idle: begin                 // Ура! У нас очередной перепад SCK!                 if ((spi_sck_d == 0) && (spi_sck_reg == 1))                 begin                    regCmdAndAddr <= {spi_mosi_reg,regCmdAndAddr[39:1]};                    bit_cnt <= bit_cnt + 1;                    // Особенность машинного языка - анализируем не новое,                     // а предыдущее значение                    if (bit_cnt == 39)                    begin                       // Пишем                       if (regCmdAndAddr[1])                       begin                           state <= write1;                       end else                       // Читаем                       begin                           state <= read1;                       end                    end                 end            end            // При записи, надо сначала допринимать данные из SPI            write1: begin                 // Ура! У нас очередной перепад SCK!                 if ((spi_sck_d == 0) && (spi_sck_reg == 1))                 begin                    regData <= {spi_mosi_reg,regData[31:1]};                    bit_cnt <= bit_cnt + 1;                    // Особенность машинного языка - анализируем не новое,                     // а предыдущее значение                    if (bit_cnt == 71)                    begin                       state <= write2;                    end                 end            end            // Всё заполнено. Держим строб записи.             write2: begin               master_write <= 1;               // Если шина нас услышала - ну и прекрасно. Вышли               if (master_waitrequest == 0)               begin                   bit_cnt <= 0;                   state <= idle;               end            end            // При чтении - наоборот, сначала считали данные            // Для медленной шины, надо бы ещё готовность SPI добавить            // но в этом примере, мы ею пренебрежём            read1: begin                  master_read <= 1;                  if (master_readdatavalid)                  begin                      state <= read2;                      regData <= master_readdata;                  end            end            read2: begin                 if ((spi_sck_d == 0) && (spi_sck_reg == 1))                 begin                    regData <= {1'b0,regData[31:1]};                    bit_cnt <= bit_cnt + 1;                    // Особенность машинного языка - анализируем не новое,                     // а предыдущее значение                    if (bit_cnt == 71)                    begin                       bit_cnt <= 0;                       state <= idle;                    end                 end            end            default:  begin              state <= idle;            end          endcase                endendassign master_byteenable = regCmdAndAddr [7:4];assign master_address = regCmdAndAddr [39:8];assign master_writedata = regData;assign spi_miso = regData [0];endmodule


Можно внедрять его в проект. Как это делается мы детально рассматривали тут и потом постоянно занимались этим в цикле статей про Redd. Но сегодня править придётся намного больше вещей, чем обычно.

Внедряем модуль в проект


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

Смотреть текст Квартуса.
# TCL File Generated by Component Editor 17.1# Wed Dec 30 02:23:32 MSK 2020# DO NOT MODIFY# # SpiToAvalonMM "SpiToAvalonMM" v1.0#  2020.12.30.02:23:32# # # # request TCL package from ACDS 16.1# package require -exact qsys 16.1# # module SpiToAvalonMM# set_module_property DESCRIPTION ""set_module_property NAME SpiToAvalonMMset_module_property VERSION 1.0set_module_property INTERNAL falseset_module_property OPAQUE_ADDRESS_MAP trueset_module_property AUTHOR ""set_module_property DISPLAY_NAME SpiToAvalonMMset_module_property INSTANTIATE_IN_SYSTEM_MODULE trueset_module_property EDITABLE trueset_module_property REPORT_TO_TALKBACK falseset_module_property ALLOW_GREYBOX_GENERATION falseset_module_property REPORT_HIERARCHY false# # file sets# add_fileset QUARTUS_SYNTH QUARTUS_SYNTH "" ""set_fileset_property QUARTUS_SYNTH TOP_LEVEL spitoAvalon_mmset_fileset_property QUARTUS_SYNTH ENABLE_RELATIVE_INCLUDE_PATHS falseset_fileset_property QUARTUS_SYNTH ENABLE_FILE_OVERWRITE_MODE falseadd_fileset_file spitoAvalon_mm.sv SYSTEM_VERILOG PATH MyCores/spitoAvalon_mm.sv TOP_LEVEL_FILE# # parameters# # # display items# # # connection point conduit_end# add_interface conduit_end conduit endset_interface_property conduit_end associatedClock ""set_interface_property conduit_end associatedReset ""set_interface_property conduit_end ENABLED trueset_interface_property conduit_end EXPORT_OF ""set_interface_property conduit_end PORT_NAME_MAP ""set_interface_property conduit_end CMSIS_SVD_VARIABLES ""set_interface_property conduit_end SVD_ADDRESS_GROUP ""add_interface_port conduit_end spi_ss spi_ss Input 1add_interface_port conduit_end spi_sck spi_sck Input 1add_interface_port conduit_end spi_mosi spi_mosi Input 1add_interface_port conduit_end spi_miso spi_miso Output 1# # connection point avalon_master# add_interface avalon_master avalon startset_interface_property avalon_master addressUnits SYMBOLSset_interface_property avalon_master associatedClock clock_sinkset_interface_property avalon_master associatedReset reset_sinkset_interface_property avalon_master bitsPerSymbol 8set_interface_property avalon_master burstOnBurstBoundariesOnly falseset_interface_property avalon_master burstcountUnits WORDSset_interface_property avalon_master doStreamReads falseset_interface_property avalon_master doStreamWrites falseset_interface_property avalon_master holdTime 0set_interface_property avalon_master linewrapBursts falseset_interface_property avalon_master maximumPendingReadTransactions 0set_interface_property avalon_master maximumPendingWriteTransactions 0set_interface_property avalon_master readLatency 0set_interface_property avalon_master readWaitTime 0set_interface_property avalon_master setupTime 0set_interface_property avalon_master timingUnits Cyclesset_interface_property avalon_master writeWaitTime 0set_interface_property avalon_master ENABLED trueset_interface_property avalon_master EXPORT_OF ""set_interface_property avalon_master PORT_NAME_MAP ""set_interface_property avalon_master CMSIS_SVD_VARIABLES ""set_interface_property avalon_master SVD_ADDRESS_GROUP ""add_interface_port avalon_master master_address address Output 32add_interface_port avalon_master master_write write Output 1add_interface_port avalon_master master_byteenable byteenable Output 4add_interface_port avalon_master master_writedata writedata Output 32add_interface_port avalon_master master_read read Output 1add_interface_port avalon_master master_readdatavalid readdatavalid Input 1add_interface_port avalon_master master_readdata readdata Input 32add_interface_port avalon_master master_waitrequest waitrequest Input 1# # connection point clock_sink# add_interface clock_sink clock endset_interface_property clock_sink clockRate 0set_interface_property clock_sink ENABLED trueset_interface_property clock_sink EXPORT_OF ""set_interface_property clock_sink PORT_NAME_MAP ""set_interface_property clock_sink CMSIS_SVD_VARIABLES ""set_interface_property clock_sink SVD_ADDRESS_GROUP ""add_interface_port clock_sink master_clk clk Input 1# # connection point reset_sink# add_interface reset_sink reset endset_interface_property reset_sink associatedClock clock_sinkset_interface_property reset_sink synchronousEdges DEASSERTset_interface_property reset_sink ENABLED trueset_interface_property reset_sink EXPORT_OF ""set_interface_property reset_sink PORT_NAME_MAP ""set_interface_property reset_sink CMSIS_SVD_VARIABLES ""set_interface_property reset_sink SVD_ADDRESS_GROUP ""add_interface_port reset_sink master_reset reset Input 1


Итак. У меня в проекте традиционно имеется каталог MyCores. Туда я кладу СистемВерилоговский файл spitoAvalon_mm.sv.



А теперь на уровень проекта кладу файл SpiToAvalonMM_hw.tcl.



Прекрасно. Открываем Platform Designer и видим, что наш компонент сам запрыгнул в перечень доступных!



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



Обратите внимание, что всем линиям шины SPI пришлось дать осмысленные имена.

Теперь посмотрим настройки шины AVALON_MM



Я выставил адресацию с точностью до байта. Можно было бы переключить точность до слова, но раз у меня есть линии BYTE_ENABLE, то можно же сделать работу с байтами и WORDами. Так что до байта. И какая-то из латентностей, уже не помню какая, была установлена в единицу. Я заменил на ноль. Собственно, на рисунке выше все задержки и латентности равны нулю, так что не перепутаете. Единицу будет хорошо видно.

Добавляем тестовую систему


Собственно, как проверить шину? А давайте через неё подключим небольшую ОЗУшку и попробуем писать и читать данные. Добавляем в систему, доставшуюся нам в наследство от прошлых статей, два элемента: мост SpiToAvalonMM и On Chip Memory. Но ОЗУ мы настроим чуть более сложно, чем обычно. Дело в том, что при отладке зеркальных систем можно получить, что запись и чтение работают. Но если допущена идеологическая ошибка в обоих направлениях, оно неверно запишется, так же неверно считается, но считанные данные совпадут с записанными, и мы увидим ложную работу. Поэтому желательно, чтобы в ОЗУ были заранее известные данные. Сначала мы убедимся, что они читаются верно, а уже затем начнём проводить пару запись-чтение, чтобы проверить уже работу записи при заведомо работающем чтении. Для этого мы взводим флажок Enable non-default initialization file и начинаем разбираться, куда положить и как сделать файл onchip_mem.hex.



Кладём его на тот же уровень, где живут файлы *.qpf и *.qsf. А вот с содержимым придётся немного повеселиться. Генератор такого файла входит в состав NIOS II EDS, то есть, в состав Квартуса. Но увы, это elf2hex. А нам бы bin2hex. Я нашёл замечательный проект SRecord 1.64 (sourceforge.net), который умеет преобразовывать любые файлы в любые. Замечательный проект! Он стоит того, чтобы попасть в записные книжки разработчиков железа Но в классическом HEXе адресация идёт байтами, а Квартус хочет, чтобы шла DWORD-ами, поэтому пришлось писать генератор hex файла самому. Учитывая, что надо было экономить время на разработку, он получился таким.

Смотреть текст.
void SpiToAvalonDemo::on_m_btnCreateMemInitFile_clicked(){    // Create an output stream    QFile file ("onchip_mem.hex");    if (!file.open(QIODevice::WriteOnly))    {     return;    }    QTextStream out (&file);    uint32_t initData [4096/sizeof(uint32_t)];    memset (initData,0,sizeof(initData));    initData [0x00] = 0x12345678;    initData [0x01] = 0x11111111;    initData [0x02] = 0x22222222;    initData [0x03] = 0x33333333;    initData [0x04] = 0x44444444;    initData [0x05] = 0x55555555;    initData [0x06] = 0x66666666;    initData [0x07] = 0x77777777;    initData [0x08] = 0xffffffff;    // Адрес всегда нулевой    out << ":020000020000FC\r\n";    for (size_t i=0;i<sizeof(initData)/sizeof(initData[0]);i+=8)    {        uint8_t cs = 0x20;        out <<":20";        cs += (uint8_t) (i/0x100);        cs += (uint8_t) (i/0x1);        out << QString ("%1").arg(i,4,16,QChar('0'));        out << "00";        uint8_t* ptr = (uint8_t*)(initData+i);        for (int j=0;j<32;j++)        {            cs += ptr[j];            out << QString ("%1").arg(ptr[j],2,16,QChar('0'));        }        cs = -1 * cs;        out << QString ("%1\r\n").arg(cs,2,16,QChar('0'));    }    // EOF    out << ":00000001FF\r\n";//    out.writeRawData((const char*)initData,sizeof(initData));    file.close();}


Просто для справки: вот так выглядят вновь добавленные блоки в процессорной системе:



Conduit выход spi экспортирован наружу, единственная шина AVALON_MM соединяет наш мастер со slave-входом ОЗУ. А больше я даже не знаю, что сказать. Обычно у нас системы были позабористей. Тут всё просто.

Не забываем назначить новые выводы


Не забываем, что у нас появились новые выводы (SPI). Дело в том, что когда я повторял свои подвиги при написании этого текста, я реально забыл это сделать, и очень удивлялся, что постоянно читается FFFFFF, хотя система уже была проверена при черновой подготовке и не должна была дурить. Так что не забываем! Для моей аппаратуры получилось так:



Дорабатываем код FX3


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

#define GET_IO_BIT(nBit) ((GPIO->lpp_gpio_invalue1 >> (nBit-32))&1)

Из оптимизации там только то, что я заранее знаю, что нужные нам биты находятся в диапазоне 32-63, поэтому сразу обращаюсь к регистру GPIO->lpp_gpio_invalue1, не тратя такты процессора на проверку.

Само чтение тоже особо ничем не приметно. Но кто будет вглядываться в текст, тот заметит, что последовательность действий отличается от той, которая напрашивается сама собой. Я просто постарался раскидать работу, чтобы и в положительном, и в отрицательном полупериоде сигнала SCK задержку вносили какие-то полезные команды. Было бы обидно всё полезное расположить в одной половинке, а в другую добавлять бесполезную задержку при помощи NOPов. И так всё медленно работает!

Смотреть текст.
unsigned int SPI_Read (int nBits){   unsigned int data = 0;   SET_IO_BIT (MY_BIT_MOSI);   while (nBits)   {   data >>= 1;   nBits -= 1;   CLR_IO_BIT (MY_BIT_CLK);   data |= (GET_IO_BIT (MY_BIT_MISO) << 31);   SET_IO_BIT (MY_BIT_CLK);   }   CLR_IO_BIT (MY_BIT_CLK);   return data;}


И, наконец, обработчик Vendor-команды в итоге стал таким:

Смотреть текст.
    if (bType == CY_U3P_USB_VENDOR_RQT)    {    // Cut size if needif (wLength > sizeof(ep0_buffer)){wLength = sizeof (ep0_buffer);}    // Need send data to PC    if (bReqType & 0x80)    {            CLR_IO_BIT(MY_BIT_SS);            SPI_Write(bRequest,8);            SPI_Write(wValue,16);            SPI_Write(wIndex,16);            ep0_buffer [0] = SPI_Read (32);            SET_IO_BIT(MY_BIT_SS);    CyU3PUsbSendEP0Data (wLength, (uint8_t*)ep0_buffer);            isHandled = CyTrue;    } else    {    CyU3PUsbGetEP0Data (wLength, (uint8_t*)ep0_buffer, NULL);    ep0_buffer [wLength] = 0;// Null terminated String            CyU3PDebugPrint (4, (char*)ep0_buffer);            CLR_IO_BIT(MY_BIT_SS);            SPI_Write(bRequest,8);            SPI_Write(wValue,16);            SPI_Write(wIndex,16);            SPI_Write(ep0_buffer[0],32);            SET_IO_BIT(MY_BIT_SS);    CyU3PUsbAckSetup();            isHandled = CyTrue;    }    }


Черновая проверка


Как и в прошлый раз, начерно всё проверяем через какую-нибудь подавалку USB-команд. Лично я предпочитаю BusHound. Как через него выполнять подобные проверки, было рассказано в предыдущей статье. Вот я читаю адрес 0. Длину всегда задаю равную четырём. Читается 12345678, как раз то, что было записано в файле onchip_mem.hex.



С адреса 4 читается 11111111



Ну, и так далее. Теперь пробуем записать. Скажем, по адресу 0x40 значение 0x87654321.



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

Добавляем код для программной работы


Убедившись, что всё работает верно, я добавил в класс, общающийся с библиотекой LibUSB, две функции. Если быть совсем точным, то я создал класс CAvalonViaFX3, унаследовав его от уже известного по одной из прошлых статей CUsbTester, а уже в него добавил эти две функции. Но это уже детали реализации, для нас сейчас важен сам код. Вот он:

bool CAvalonViaFX3::WriteDword(uint32_t addr, uint32_t data){    int res = libusb_control_transfer(m_hUsb,                            0x40,0xf1,                            (uint16_t)addr,(uint16_t)(addr>>16),                            (unsigned char*)&data,4,100);    return (res == 4);}bool CAvalonViaFX3::ReadDword(uint32_t addr, uint32_t& data){    int res = libusb_control_transfer(m_hUsb,                            0xc0,0xf0,                            (uint16_t)addr,(uint16_t)(addr>>16),                            (unsigned char*)&data,4,100);    return (res == 4);}

Ну, и код, тестирующий память, размером 4 килобайта, выглядит так:

static const int memSize = 4096;void SpiToAvalonDemo::on_m_btnMemoryTest_clicked(){    QRandomGenerator genWrite (1234);    uint32_t data [memSize/sizeof(uint32_t)];    for (size_t i=0;i<memSize/sizeof(uint32_t);i++)    {        data[i] = genWrite.generate();    }    for (int i=0;i<memSize;i+=sizeof(uint32_t))    {        if (!m_tester.WriteDword(i,data[i/sizeof(uint32_t)]))        {            QString msg = QString ("Write Error at Addr: 0x%1\n").arg(i,8,16,QChar('0'));            qDebug() << msg;            return;        }    }    for (int i=0;i<memSize;i+=sizeof(uint32_t))    {        uint32_t rd;        if (!m_tester.ReadDword(i,rd))        {            QString msg = QString ("Read Error at Addr: 0x%1\n").arg(i,8,16,QChar('0'));            qDebug() << msg;            return;        }        if (rd!=data[i/sizeof(uint32_t)])        {            QString msg = QString ("Miscompare at Addr: 0x%1 Written: %2 Read: %3\n").arg(i,8,16,QChar('0')).                    arg(data[i],8,16,QChar('0')).arg(rd,8,16,QChar('0'));            qDebug() << msg;            return;        }    }    qDebug()<<"Test Finished\n";}

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

Заключение


Мы освоили методику разработки мастеров, читающих и пишущих в шину AVALON_MM. Набивая руку, мы сделали переходник SPI в AVALON_MM и проверили его работоспособность. При работе с контроллером FX3 это позволит обращаться (с не очень высокой производительностью) к шине без использования каких-либо сторонних средств, так как раньше пришлось бы работать с TCL-командами или скриптами.

Материалы, получившиеся при написании статьи, можно скачать тут.
Подробнее..

Асинхронная работа с libusb 1.0

09.03.2021 16:05:49 | Автор: admin
Несколько статей назад мы рассмотрели методику работы с USB-устройством при помощи библиотеки libusb. Данные в устройстве у нас формировались по таймеру, поэтому мы были не просто уверены, что рано или поздно они придут к нам, но даже могли предсказать, через какой срок это произойдёт. Однако в анализаторе (который является конечной целью разработки) данные идут непредсказуемо. Будут данные или нет зависит от поведения объекта контроля.

Поэтому, во-первых, было бы полезно видеть, какой их объём уже прошёл в буфер, чтобы представлять, работает система или нет. Ну, и во-вторых, если данных не предвидится, а всё интересное уже попало к нам в память, надо иметь возможность прекратить приём и начать разбор того, что уже накопилось. Ни то, ни другое невозможно при использовании функций, которые были рассмотрены в той статье. По крайней мере, со стороны PC. Без читов, добавленных в прошивку ПЛИС.

Сегодня мы научимся обращаться к библиотеке libusb асинхронным методом. Это позволит и грубо отслеживать объём уже пришедших данных, и прерывать работу в любой момент, и даже повысить общую производительность системы. Причём всё это будет сделано только за счёт вызова штатных функций libusb. Код для FX3 и ПЛИС мы для этого дорабатывать не будем. Итак, приступаем.



Предыдущие статьи цикла:
  1. Начинаем опыты с интерфейсом USB 3.0 через контроллер семейства FX3 фирмы Cypress
  2. Дорабатываем прошивку USB 3.0, используя анализатор SignalTap, встроенный в среду разработки Quartus
  3. Учимся работать с USB-устройством и испытываем систему, сделанную на базе контроллера FX3
  4. Боремся с таймаутами при использовании USB 3.0 через контроллер FX3, возникающими при определенных условиях
  5. Добавляем поддержку Vendor-команд к USB3.0 устройству на базе FX3
  6. Делаем блок SPI to AVALON_MM для USB-устройства на базе FX3

1 Зачем всё это


Напомню, что в предыдущей статье про libusb я читал данные из FX3 так:
int res = libusb_bulk_transfer(tester.m_hUsb,0x81,(uint8_t*)pData,bytesCnt,&actualLength,10000);

И всё. До истечения таймаута (а для анализатора он может быть огромным) текущий поток будет исполняться внутри данной функции. Управления мне никто не вернёт. Мы будем ждать, ждать и ждать, как бедняга на вступительном рисунке, отмеченном крестиком.

В противовес такой работе (её ещё называют синхронной), в любой уважающей себя USB-библиотеке должна быть ещё и асинхронная. Там мы вызываем функцию чтения, и управление немедленно возвращается нам. Что будет дальше, зависит от конкретной библиотеки. Где-то в нужный момент будет переведён в активное состояние объект Событие. В libusb будет вызвана Callback-функция. Короче, нам поступит сообщение о том, что наш запрос выполнен, как на вступительном рисунке с галочкой.

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

Скоро мы рассмотрим, как это работает, но сначала я объясню, почему не стал рассказывать об асинхронной работе немедленно после синхронной. Дело в том, что при проверке асинхронной работы, мы должны провоцировать ситуации с таймаутами. А значит, наш таймер должен не молотить от начала жизни и до бесконечности, а выдавать строго дозированные порции данных. Чтобы это делать, мы должны управлять им. Собственно, в предыдущих двух статьях мы и осваивали этот процесс. Теперь мы вполне можем посылать запросы на шину AVALON_MM. Поэтому, подключив таймер к этой шине, мы сможем управлять и им. Приступаем.

2 Дорабатываем таймер


Да, я обещал, что мы будем работать только средствами libusb. Но этот раздел ничему не противоречит. Он показывает не работу, а доработку средств тестирования. Итак, верилоговский модуль таймера, формирующего воздействия, несколько изменился. В него была добавлена шина AVALON_MM. Из самого счётчика убраны все особенности поведения. Раньше после переполнения приёмника он стартовал не сразу. Теперь всё просто. При старте он стоит. По шине AVALON_MM попросили сделать N тиков перешлёт ровно столько слов, сколько попросили. Переслал снова стоит.

Таким образом, наша программа сможет вырабатывать строго заданное количество 16-разрядных слов, которые уйдут в шину AVALON_ST, дальше в FIFO, а из него в FX3 и USB 3.0.



Под катом получившийся Verilog код таймера.
Смотреть код таймера.
module Timer_ST (  input              clk,  input              reset,  input [2:0]   avalon_mm_address,  input [31:0]  avalon_mm_data_in,  input         avalon_mm_wr,  input  logic       source_ready,  output logic       source_valid,  output logic[15:0] source_data);    logic [31:0] cnt = 0;    logic [31:0] counter = 0;    always @ (posedge clk)    begin         // На том конце очередь переполнена        // Значит, когда она освободится - начнём        // слать данные не сразу...        if (reset == 1)        begin            counter <= 0;            cnt <= 0;        end else        begin            if (avalon_mm_wr)            begin                cnt <= avalon_mm_data_in;               counter <= 0;            end else            begin               counter <= counter + 1;               if ((source_ready==1)&&(cnt != 0))               begin                  cnt <= cnt - 1;               end             end        end    end    assign source_valid = (source_ready!=0) && (cnt != 0);    assign source_data [15:0] = counter [15:0];endmodule


3 Первый эксперимент


3.1 Подготовка


Сегодня мы будем черпать вдохновение на соответствующей странице документации библиотеки libusb.
Чтобы сделать простейший асинхронный запрос, надо проделать следующий путь:
  1. Выделить память для структуры libusb_transfer, для чего имеется функция libusb_alloc_transfer();
  2. Заполнить поля созданной структуры. Причём не надо мучиться с ручным заполнением. В зависимости от типа транзакции мы можем использовать функции libusb_fill_control_setup(), libusb_fill_control_transfer(), libusb_fill_bulk_transfer(), libusb_fill_interrupt_transfer() или libusb_fill_iso_transfer(). Есть ещё заполнение потоковой структуры, но врать не буду, я с нею не разобрался.
  3. На шаге 2 заполняется указатель на функцию обратного вызова. Эту функцию надо написать.
  4. Запустить передачу в работу, вызвав libusb_submit_transfer().

3.2 Работа


Собственно, после вызова функции libusb_submit_transfer() хост начнёт попытку инициировать обмен с устройством. Когда процесс завершится (либо возникнет таймаут или ещё какая нештатная ситуация вплоть до вытаскивания устройства из разъёма), будет вызвана функция обратного вызова.

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



То есть, после отработки libusb_submit_transfer(), Callback-функция, получив управление, под конец своей работы снова вызывает libusb_submit_transfer() и завершается. Через некоторое время она снова получает управление, снова вызывает libusb_submit_transfer() и т.д.

При этом функция libusb_submit_transfer() возвращает управление немедленно, а следующий вызов CallBаck-функции произойдёт через существенный промежуток времени. Таким образом, всё это время основная программа может продолжать своё выполнение. Мало того, CallBack-функция по своей сути похожа на обработчик прерывания микроконтроллера. Она совершенно не изменяет ход работы основной программы. Появились данные функция была вызвана. Функция отработала управление вернулось в то место, где программа была в момент прихода данных. А как эта функция взаимодействует с основной программой всё в руках программиста.

3.3 Очень важный момент


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

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



3.4 Окончание работы


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

3.5 Практический пример


Если честно, то без доработок описанный выше механизм неприемлем для USB3.0. По этой шине данные летят широким потоком, а мы надолго прерываем приём. Пока мы войдём в функцию обратного вызова, пока она отработает, пока запустит приём новых данных Медленно всё это! Поэтому практический пример я покажу не для USB3, а для изохронных передач USB2. Вдохновение я черпал в файле \libusb-1.0.23\examples\sam3u_benchmark.c, который шёл в комплекте с библиотекой. Тот код можно считать эталонным, но я покажу свою перепевку (которая может что-то не учитывать).

Вот такую я сделал функцию обратного вызова. Она не выполняет никаких полезных действий, я просто ставил там точки останова и изучал в отладчике структуры пришедших данных. Ну, и статистику она мне строит. Тест мне был нужен, чтобы убедиться, что я понимаю принципы работы. В конце, как я уже и говорил, вызывается libusb_submit_transfer() для запуска приёма новой посылки. Объёмы изохронного трафика от микрофона (а ловил я именно их) таковы, что заботиться о скорости мне не требовалось. Там буквально килобайты в секунду идут.
Смотреть код.
void LIBUSB_CALL CIsoWriteTest::cb_xfr(struct libusb_transfer *xfr){    CIsoWriteTest* pForm = (CIsoWriteTest*) xfr->user_data;    if (xfr->status == LIBUSB_TRANSFER_COMPLETED)    {        uint minBlockSize = 100000;        uint maxBlockSize = 0;        uint total = 0;        int nBlocks = 0;        for (int i=0;i<xfr->num_iso_packets;i++)        {            if (xfr->iso_packet_desc[i].status == LIBUSB_TRANSFER_COMPLETED)            {                if (xfr->iso_packet_desc[i].actual_length > maxBlockSize)                {                    maxBlockSize = xfr->iso_packet_desc[i].actual_length;                }                if (xfr->iso_packet_desc[i].actual_length < minBlockSize)                {                    minBlockSize = xfr->iso_packet_desc[i].actual_length;                }                nBlocks += 1;                total += xfr->iso_packet_desc[i].actual_length;            }        }        pForm->ui->m_lblSize->setText(QString ("%1 Byttes transfered in %2 blocks").arg(total).arg(nBlocks));        pForm->ui->m_lblMinBlockSize->setText(QString ("Min Block Size = %1 bytes").arg(minBlockSize));        pForm->ui->m_lblMaxBlockSize->setText(QString ("Max Block Size = %1 bytes").arg(maxBlockSize));        pForm->ui->m_lblBytesPerSec->setText("***");    } else    {        pForm->ui->m_lblSize->setText(DecodeTransferStatus(xfr->status));        pForm->ui->m_lblMinBlockSize->setText("***");        pForm->ui->m_lblMaxBlockSize->setText("***");        pForm->ui->m_lblBytesPerSec->setText("***");    }    if (libusb_submit_transfer(xfr) < 0)    {        // todo Catch Errors    }}

Теперь, как запускается процесс. Вот я создаю структуру:
    // Allocate transfer    m_xfr = libusb_alloc_transfer(TEST_NUM_PACKETS);    if (m_xfr == 0)    {        QMessageBox::critical (this,"Error","Cannot Allocate Transfer");        return;    }


Вот я создаю собственный буфер данных (можно его создать и средствами библиотеки, но я предпочитаю выделять память так, чтобы она самоосвободилась в деструкторе класса), после чего заполняю ранее созданную структуру, передав ей указатели на буфер и функцию обратного вызова:
    m_buffer.resize(m_epParams.epMmaxPacketSize * TEST_NUM_PACKETS);    libusb_fill_iso_transfer(m_xfr, m_epParams.hDev, m_epParams.nEndPoint,(unsigned char*) m_buffer.constData(),            m_buffer.size(), TEST_NUM_PACKETS, cb_xfr, this, 1000);    libusb_set_iso_packet_lengths(m_xfr, m_epParams.epMmaxPacketSize);

где
#define TEST_NUM_PACKETS 128

Дальше я запускаю процесс ожидания и приёма посылки:
    int res = libusb_submit_transfer(m_xfr);    if (res != 0)    {        QMessageBox::critical(this,"Error",libusb_error_name(res));    }

И не забываю запустить отдельный поток, который выполняет роль мотора:
    m_thread.start();

сам поток прост, так как я просто изучал принципы работы и совершенно не задумывался о функциональности, так что он совершенно не обрабатывает ошибочные ситуации:
void CMonitorIsoTransactionThread::run(){    while (!isInterruptionRequested())    {        libusb_handle_events(NULL);    }}

4 Пользуемся очередью запросов


Как решить главную проблему, описанную выше (большой временной участок, когда не идёт приём), мне подсказал VelocidadAbsurda в комментариях к этой статье. Надо просто создать несколько структур типа libusb_transfer и запустить много операций обмена. Они встанут в очередь. Как только выполнится одна в игру вступит следующая. Пока мы находимся в обработчике, система не будет простаивать. Во время экспериментов я запускал 16 передач одновременно. Эту работу я рассмотрю уже на реальном примере для целевой шины USB3.

4.1 Работа с буфером


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



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



4.2 Функция обратного вызова


Снова начнём обсуждение кода с функции обратного вызова. Давайте пока не будем обсуждать ветку LIBUSB_TRANSFER_CANCELLED в ней. Она нас отвлечёт. Разберём основной ход.
Как я уже говорил, эта функция как-то должна взаимодействовать с основной программой. Для этого нужна кучка переменных. Я предпочёл объединить их в единую структуру:
    struct asyncParams    {        // Указатель на большой буфер, в который        // мы принимаем массив. Он может быть размером        // в сотни мегабайт        uint8_t* pData;        // Каждая транзакция принимает маленький кусочек.        // Например, 64 килобайта. В этой переменной лежит        // текущее смещение. Поставили транзакцию в очередь -         // сдвинулись в буфере. Короче, это указатель         // запрошенной части буфера.        int dataOffset;        // Размер буфера в байтах. Полезен, чтобы знать,        // когда следует прекращать работу. Если смещение        // дошло до этой величины - функция обратного вызова        // перестанет создавать новые запросы на передачу        int dataSizeInBytes;        // Размер одной передачи        int transferLen;        // Используется для отображения заполненности буфера,        // увеличивается по факту прихода новой порции данных.        // То есть, это указатель фактически заполненного буфера        int actualTranfered;    };

И функция, пользующаяся этой структурой, выглядит так:
void SpiToAvalonDemo::ReadDataTranfserCallback(libusb_transfer *transfer){    SpiToAvalonDemo* pClass = (SpiToAvalonDemo*) transfer->user_data;    switch (transfer->status )    {    case LIBUSB_TRANSFER_COMPLETED:        // Отметили, что принят очередной блок данных        pClass->m_asyncParams.actualTranfered += transfer->length;        // Если буфер принят ещё не весь        if (pClass->m_asyncParams.dataOffset < pClass->m_asyncParams.dataSizeInBytes)        {            // Новая пачка ляжет вот сюда            transfer->buffer = pClass->m_asyncParams.pData+pClass->m_asyncParams.dataOffset;            // Сдвигаем указатель на следующий блок в буфере            pClass->m_asyncParams.dataOffset += pClass->m_asyncParams.transferLen;            // Запустили передачу            libusb_submit_transfer(transfer);        }        break;    case LIBUSB_TRANSFER_CANCELLED:    {        pClass->m_cancelCnt -= 1;    }        break;    default:        break;    }}

Она простая. Есть буфер, вдоль которого мы скользим, принимая фрагменты потока данных. Например, буфер 120 мегабайт, а фрагменты по 64 килобайта. У нас есть указатель головы буфера. И задача функции просто посмотреть, не достигла ли голова конца буфера. Нет тогда установили новый указатель в структуре libusb_transfer. Прочие её поля не трогаем, они же уже заполнены в основной программе. Затем сдвигаем голову и проворачиваем колесо, вызвав libusb_submit_transfer(). Всё!

4.3 Запуск процесса и моторная часть


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

Собственно, в ней мы видим почти то же самое, что и в изохронном примере, только структуры libusb_transfer создаются и обрабатываются оптом.

Сначала я заполняю поля структуры, которая используется для связи с функцией обратного вызова:
    m_asyncParams.pData = (uint8_t*) pData;    m_asyncParams.dataOffset = 0;    m_asyncParams.dataSizeInBytes = bytesCnt;    m_asyncParams.transferLen = transferSize;    m_asyncParams.actualTranfered = 0;

Теперь создаём нужное количество структур libusb_transfer, которые указывают на функцию обратного вызова и на участок буфера. При этом не забываем сдвигать указатель на буфер:
    for (int i=0;i<nTransfersInParallel;i++)    {        m_transfers[i] = libusb_alloc_transfer(0);        libusb_fill_bulk_transfer (m_transfers[i],m_tester.m_hUsb,0x81,                                   m_asyncParams.pData+m_asyncParams.dataOffset,transferSize,ReadDataTranfserCallback,                                   this,60000);        m_asyncParams.dataOffset += transferSize;    }

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

Дальше я активирую все передачи, ставя их в очередь (которая существует где-то в недрах библиотеки). Я вынес это в отдельный цикл не столько для наглядности, сколько из-за того, что я измеряю производительность теста при помощи таймера, и здесь он уже тикает. А время инициализации этим таймером не учитывалось.
    QElapsedTimer timer;    timer.start();    // Separated loop for more careful time checking    for (int i=0;i<nTransfersInParallel;i++)    {        libusb_submit_transfer(m_transfers[i]);    }

Теперь возникает следующая ситуация. По мере прихода данных будет проворачиваться рабочее колесо. И функция обратного вызова всё время будет увеличивать поле m_asyncParams.actualTranfered. Когда все данные придут, оно станет равно m_asyncParams.dataSizeInBytes. Поэтому моторная часть у меня выглядит так:
   while (m_asyncParams.actualTranfered<m_asyncParams.dataSizeInBytes)   {      libusb_handle_events(m_tester.m_ctx);   }

Я по-прежнему игнорирую ошибки. Это тестовый пример. А так ошибка возникнет, даже если устройство выдернут из разъёма. Но обработчики ошибок сильно усложнят понимание статьи, так что я просто ещё раз напоминаю тем, кто захочет работать на практике, что мои примеры надо рассматривать творчески.
Дальше я вычисляю и вывожу скорость (код можно посмотреть в полном варианте функции), а затем начинаю освобождать ресурсы.
    for (int i=0;i<nTransfersInParallel;i++)    {        libusb_free_transfer(m_transfers[i]);    }

Собственно, всё!

Для справки, полный текст функции под катом.
bool SpiToAvalonDemo::AsyncStep(uint16_t *pData, const int bytesCnt, const int transferSize, const int nTransfersInParallel){    QElapsedTimer timer;    m_asyncParams.pData = (uint8_t*) pData;    m_asyncParams.dataOffset = 0;    m_asyncParams.dataSizeInBytes = bytesCnt;    m_asyncParams.transferLen = transferSize;    m_asyncParams.actualTranfered = 0;    // Allocate Transfers    for (int i=0;i<nTransfersInParallel;i++)    {        m_transfers[i] = libusb_alloc_transfer(0);        libusb_fill_bulk_transfer (m_transfers[i],m_tester.m_hUsb,0x81,                                   m_asyncParams.pData+m_asyncParams.dataOffset,transferSize,ReadDataTranfserCallback,                                   this,60000);        m_asyncParams.dataOffset += transferSize;    }    timer.start();    // Separated loop for more careful time checking    for (int i=0;i<nTransfersInParallel;i++)    {        libusb_submit_transfer(m_transfers[i]);    }    while (m_asyncParams.actualTranfered<m_asyncParams.dataSizeInBytes)    {        libusb_handle_events(m_tester.m_ctx);    }    quint64 after = timer.nsecsElapsed();    quint64 size = bytesCnt;    size *= 1000000000;    quint64 speed = size/after;    qDebug() << nTransfersInParallel << "," << transferSize << "," << speed;    int from = 0xc001;    uint16_t prevData = pData[from];    for (int i=from+1;i<bytesCnt/2;i++)    {        if (pData[i] != ((prevData + 1)&0xffff))        {            qDebug() << Qt::hex << i << " : " << prevData << ", " << pData[i];        }        prevData = pData[i];    }    // Release Resources    for (int i=0;i<nTransfersInParallel;i++)    {        libusb_free_transfer(m_transfers[i]);    }    return true;}


4.4 Пример вызова тестовой функции


Тестовая функция завязана исключительно на библиотеку libusb. Вся подготовка аппаратуры и выделение буферов вынесены из неё. Давайте рассмотрим один пример, как эта функция вызывается. Так я тестирую зависимость скорости передачи от числа передач, поставленных в очередь.
void SpiToAvalonDemo::on_m_btnAsync128M_clicked(){    static const int len = 128 * 1024 * 1024;    // Set Up Timer to 128 megabytes    QByteArray ar1;    ar1.resize(len);    // Заполнили буфера FX3, иначе подвиснет    m_tester.WriteDword(0,0x10000);    for (int i=1;i<16;i++)    {        // Set Transfer Size for timer        m_tester.WriteDword(0,len/2);        AsyncStep ((uint16_t*)ar1.constData(),len,65536,i);    }}

При прогоне этого теста я получил очень интересный результат. При некоторых прогонах я вижу ошибки в принятых буферах:



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

Как можно спровоцировать просадки скорости. Просто уводим курсор мыши в область панели задач и начинаем водить по ней. При этом возникают уменьшенные окошки



Вот их формирование часто и приводит к задержкам. Причём машина у меня не самая слабая:



Но вот что вижу, то вижу. Получается, что очень критичные к производительности системы (напомню, у меня конечная цель анализатор без собственной буферной памяти, только с небольшой очередью FIFO, выдающий данные со скоростью 120 миллионов байт в секунду) можно делать только для себя. С собой договориться всегда можно. А для внешних Заказчиков не получится. Есть у меня знакомый, который любит, чтобы на одной машине работала и технологическая программа, и редактор текстов, и игрушка, причём всё одновременно, и на не самой мощной машине

Однако, когда количество одновременно запрошенных передач близко к шестнадцати, всё более-менее хорошо. Так что повторю, с собой лично договориться о том, что при измерениях не двигать особо мышку, я смогу. Но анализатор и измеряемая программа явно должны быть запущены на разных машинах. Ну, или всё-таки надо будет ставить в ПЛИСовую часть большое буферное ОЗУ

5 Лирическое отступление о размере одной посылки


В далёком 2009-м году участвовал я в разработке программатора ПЗУ на базе FX2LP. Там требовалось выжать из скорости максимум. И вот тогда, после ряда опытов, я выяснил, что хоть физически по шине USB2 бегают пакеты, не больше, чем 512 байт, всё равно надо запрашивать сразу большие объёмы данных. Это связано с тем, что кроме размера пакета, есть ещё разбиение временной оси на кадры. В пределах одного кадра устройство может обменяться более, чем одним пакетом, если это запланировано хостом. Но если запрос пришёл слишком поздно, он может быть вписан в планы на следующую пятилетку следующий кадр. Чтобы избежать этого, надо сразу просить много. Тогда планы будут свёрстаны с учётом наших потребностей.

С тех пор я упоминаю об этом к месту и не к месту. Соответствующий график для USB2, реализованной средствами ПЛИС, я публиковал в своей статье про логические ограничения производительности шин. Сегодня я просто обязан построить такой же график для шины USB3, работающей через FX3.
Графики строил я примерно так.
void SpiToAvalonDemo::on_m_btnAsync128M_clicked(){    static const int len = 128 * 1024 * 1024;    // Set Up Timer to 128 megabytes    QByteArray ar1;    ar1.resize(len);    // Заполнили буфера FX3, иначе подвиснет    m_tester.WriteDword(0,0x10000);    qDebug()<<"";    qDebug()<<"";    qDebug()<<"";//    for (int i=1024;i<512*1024;i*=2)    for (int i=1024;i<32*1024;i+=1024)    {        // Set Transfer Size for timer        m_tester.WriteDword(0,len);        AsyncStep ((uint16_t*)ar1.constData(),len-128*1024,i,1);    }    qDebug()<<"";    qDebug()<<"";    qDebug()<<"";    //    for (int i=1024;i<512*1024;i*=2)    for (int i=1024;i<32*1024;i+=1024)    {        // Set Transfer Size for timer        m_tester.WriteDword(0,len);        AsyncStep ((uint16_t*)ar1.constData(),len-128*1024,i,2);    }    qDebug()<<"";    qDebug()<<"";    qDebug()<<"";    //    for (int i=1024;i<512*1024;i*=2)    for (int i=1024;i<32*1024;i+=1024)    {        // Set Transfer Size for timer        m_tester.WriteDword(0,len);        AsyncStep ((uint16_t*)ar1.constData(),len-128*1024,i,4);    }}


Видно, что во время опытов я экспериментировал с размерами, так как каждый замер идёт долго, но хотелось снять разные варианты.

Самый интересный из полученных графиков, выглядит так (первый столбец размер транзакции в байтах, второй получившаяся скорость передачи в байтах в секунду):



В общем, не стоит работать блоками меньше, чем 8 килобайт. Правда, я меньше 64 килобайт обычно и не использую.

Вот здесь я попробовал разное число транзакций в очереди (одну, две и четыре). Если честно, то в таблице видно, что чем больше транзакций, тем лучше, но на графике разница особенно и не видна. То есть, она несущественна. И мы уже знаем, что меньше восьми лучше не запускать. Я обычно меньше шестнадцати не запускаю.



Отдельно хочется отметить, что скорость передачи возросла по сравнению с аналогичной, полученной при синхронной работе. То, что скорость чуть превышает 120 миллионов байт в секунду, не является признаком ошибки измерений. Я отмечал в предыдущих статьях, что по осциллографу видно, что ULPI выдаёт тактовую частоту чуть выше, чем 60 МГц.

6 Как я провоцировал остановки шины


Коротко покажу, как я провоцировал остановки шины для постскриптума к этой статье. Чтобы их спровоцировать, просто необходимы как управление таймером, так и возможность подкинуть тактов таймера, когда передача данных уже началась. В общем, без асинхронности никак. Вот так выглядел тестовый код. Я сначала генерил набор тактов, а затем ещё чуть-чуть. Но мы помним, что проход запроса через программную шину SPI крайне медленный. Поэтому однозначно результаты предыдущей работы таймера ушли, и шина остановилась. Возникла большая пауза в передаче. И тут-то я досылал финальные байтики, которые приходили при уже упавшем флаге flagb. Подробнее о теории в той статье (см. выше).

Для этой функции я впервые вынес моторную функциональность в обособленное место:
void SpiToAvalonDemo::UsbLoop(uint32_t timeIn_ms){    timeval tv;    tv.tv_sec = 0;    tv.tv_usec = 500000;    QElapsedTimer timer;    timer.start();    while (timer.elapsed()<timeIn_ms)    {        libusb_handle_events_timeout(m_tester.m_ctx,&tv);    }}

И ключевой участок тестового кода выглядит так:
    UsbLoop (500);    m_tester.WriteDword(0,len/4);    UsbLoop (500);    m_tester.WriteDword(0,(len/4)-2);    UsbLoop (1500);    m_tester.WriteDword(0,2);

А вся функция так.
void SpiToAvalonDemo::on_m_btnWithLatencyProblem_clicked(){    static const int len = 0x10000;    // Set Up Timer to 128 megabytes    QByteArray ar1;    ar1.resize(0x100000);    m_asyncParams.pData = (uint8_t*)ar1.constData();    m_asyncParams.dataOffset = 0;    m_asyncParams.dataSizeInBytes = len;    m_asyncParams.transferLen = len;    m_asyncParams.actualTranfered = 0;    libusb_transfer* transfer = libusb_alloc_transfer(0);    libusb_fill_bulk_transfer (transfer,m_tester.m_hUsb,0x81,                               m_asyncParams.pData+m_asyncParams.dataOffset,len,ReadDataTranfserCallback,                               this,60000);    m_asyncParams.dataOffset += len;    libusb_submit_transfer(transfer);    UsbLoop (500);    m_tester.WriteDword(0,len/4);    UsbLoop (500);    m_tester.WriteDword(0,(len/4)-2);    UsbLoop (1500);    m_tester.WriteDword(0,2);    while (m_asyncParams.actualTranfered<m_asyncParams.dataSizeInBytes)    {        libusb_handle_events(m_tester.m_ctx);    }    libusb_free_transfer(transfer);}


Как этот тест изменил Verilog код преобразователя AVALON_ST в FX3, я уже рассказывал. Собственно, практически полностью изменил.

7 Отображаем текущее состояние и добавляем остановку процесса


Теперь рассмотрим самый полный, но и самый сложный пример. Попросим тестовую аппаратуру в ПЛИС сформировать данные так, чтобы процесс не смог завершиться. При этом программа будет отображать нам текущее заполнение буфера и давать возможность прервать процесс.

7.1 Таймер, отображающий текущий процент и отлавливающий факт успешного завершения работы


Функцию обратного вызова я оставил ту же самую. Но так как сейчас у нас всё должно быть весьма асинхронно, я добавил в класс виртуальную функцию обработки таймера. В Qt это делается именно так. Собственно, функция проста, а её логика нам знакома. Мы анализируем поле, в котором функция обратного вызова сохраняет объём фактически прокачанных данных. Если ещё не 100% отображаем значение и выходим. Если 100% останавливаем процесс.
void SpiToAvalonDemo::timerEvent(QTimerEvent *event){    Q_UNUSED(event)    static int prevPercent = 0;    int percent = (int)(((int64_t)m_asyncParams.actualTranfered * 100LL)/(int64_t)m_asyncParams.dataSizeInBytes);    if (prevPercent != percent)    {        ui->m_progressForCancel->setValue(percent);        prevPercent = percent;    }    if (percent == 100)    {        StopProcess();    }}

7.2 Функция завершения работы


Так как остановка процесса возможна не только автоматически, но ещё и по кнопке Cancel, она вынесена в отдельную функцию, чтобы её могли вызывать обе ветки. Там мы посылаем запрос на остановку моторного потока, дожидаемся фактической остановки, удаляем все структуры libusb_transfer и останавливаем таймер. Всё!
void SpiToAvalonDemo::StopProcess(){    // Всё, больше мотор не нужен! Глушим его!    m_transactionsThread.requestInterruption();    while (m_transactionsThread.isRunning())    {        QThread::msleep(10);    }    // Освободили память    for (int i=0;i<m_dataTranfersInParallel;i++)    {        libusb_free_transfer(m_transfers[i]);    }    ui->m_progressForCancel->setValue(0);    // Таймер, собственно, тоже стал не нужен    killTimer(m_timeId);}

7.3 Моторный поток


Раз уж зашла речь о моторном потоке, то рассмотрим его подробнее. Здесь нужен поток и только поток. Реализуем его, согласно правилам Qt. Каждые 500 миллисекунд (если точнее, то 500 000 микросекунд) библиотека возвращает нам управление, чтобы мы могли проверить, не пора ли завершить работу с устройством. Я по-прежнему не обрабатываю критические ошибки в угоду читаемости:
void CTransactionThread::run(){    timeval tv;    tv.tv_sec = 0;    tv.tv_usec = 500000;    while (!isInterruptionRequested())    {        libusb_handle_events_timeout(m_libusb_ctx,&tv);    }}

7.4 Кнопка Cancel


Кнопка Cancel, согласно теории, должна завершить незавершённые передачи. Звучит красиво, но на практике, мне пришлось посидеть. Я гарантирую, что всё сработает в Windows, но не удивлюсь, если в других ОС поведение будет чуть иным. Дело в том, что в любой момент времени, все передачи имеют состояние Успешно завершена. Идёт ожидание, не идёт Завершена и всё тут. Поэтому я просто пытаюсь загасить все передачи. Если передача действительно завершена, функция вернёт ошибку. Если же она в процессе работы функция отмены завершится успешно. И вот тогда я увеличиваю счётчик активных транзакций на единицу:
    m_cancelCnt = 0;    for (int i=0;i<m_dataTranfersInParallel;i++)    {        int res = libusb_cancel_transfer(m_transfers[i]);        if (res >= 0)        {            m_cancelCnt += 1;        }    }

Этот счётчик не простой. Он атомарно доступный. Я объявил его так:
    QAtomicInt m_cancelCnt;

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

data += 1;

обычно распадается минимум на три ассемблерные команды:
1) загрузка из памяти в регистр,
2) увеличение регистра,
3) выгрузка регистра в память (обычно потому что на PDP-11 можно было обойтись одной ассемблерной командой, но её времена уже прошли).

В теории поток может быть прерван в любой момент. И возможна ситуация, когда в регистр попало некое значение, после чего работа потока была прервана. Другой поток изменил значение в памяти. Управление вернулось. Но у нас в регистре по-прежнему лежит нечто, и наш поток понятия не имеет о том, что произошло в другом. Поэтому мы завершим инкремент старого значения и положим в память его результат. Данные в памяти станут неверными. Вот чтобы этого избежать, и используются атомарные операции. Они гарантированно завершатся перед прерыванием потока. Так как переменная m_cancelCnt модифицируется в функции обратного вызова, правила активации которой нам неизвестны, я и применил атомарный тип библиотеки Qt для этой переменной.


Помните, в функции обратного вызова я предлагал отложить рассмотрение участка на потом? Пришло время сделать это!
    case LIBUSB_TRANSFER_CANCELLED:    {        pClass->m_cancelCnt -= 1;    }

Когда мы запросили отмену транзакции нам всё равно вызовут соответствующую ей Callback-функцию. И, согласно документации на библиотеку, мы должны дождаться фактического завершения всех транзакций. Тут мы уже всего дождались и отмечаем это. А вот как основной поток ждёт того, что все транзакции успешно завершились:
    // Ждём фактической отмены пересылки    while (m_cancelCnt != 0)    {        QThread::msleep(100);    }

Ну, а потом завершаем работу, вызвав уже рассмотренную ранее функцию:
    // Остановили работу.    StopProcess();

Для справки, полный текст функции, обрабатывающей нажатие кнопки Cancel.
void SpiToAvalonDemo::on_m_btnCancel_clicked(){    m_cancelCnt = 0;    for (int i=0;i<m_dataTranfersInParallel;i++)    {        int res = libusb_cancel_transfer(m_transfers[i]);        if (res >= 0)        {            m_cancelCnt += 1;        }    }    // Ждём фактической отмены пересылки    while (m_cancelCnt != 0)    {        QThread::msleep(100);    }    // Остановили работу.    StopProcess();}


7.5 Запускаем процесс


Уффф. С инфраструктурой разобрались. Теперь запуск. Я сделал возможность задавать, сколько слов недосылать таймеру, чтобы можно было провоцировать различные ситуации. Например, висим с одной недокачанной передачей, висим, когда все передачи недокачаны и т.п.



С учётом этого старт работы выглядит так.

Уже известное нам заполнение параметров и выделение буфера:
    m_analyzerData.resize(120*1024*1024);    m_asyncParams.pData = (uint8_t*)m_analyzerData.constData();    m_asyncParams.dataOffset = 0;    m_asyncParams.dataSizeInBytes = m_analyzerData.size()*sizeof(uint16_t);    m_asyncParams.transferLen = 0x20000;    m_asyncParams.actualTranfered = 0;    for (int i=0;i<m_dataTranfersInParallel;i++)    {        m_transfers[i] = libusb_alloc_transfer(0);    }

Уже приевшееся выделение памяти для структур libusb_transfer. Но в нём кое-что поменялось. Я заменил значение таймаута. Давайте сначала посмотрим на код, а потом я порассуждаю про эти таймауты.
    for (int i=0;i<m_dataTranfersInParallel;i++)    {        libusb_fill_bulk_transfer (m_transfers[i],m_tester.m_hUsb,0x81,                                   m_asyncParams.pData+m_asyncParams.dataOffset,m_asyncParams.transferLen,                                   ReadDataTranfserCallback,this,0x7fffffff);        m_asyncParams.dataOffset += m_asyncParams.transferLen;    }

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

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

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

Возвращаемся к коду. Классический запуск передач:
    for (int i=0;i<m_dataTranfersInParallel;i++)    {        libusb_submit_transfer(m_transfers[i]);    }

Запускаем моторный поток. Ради интереса, я задал ему высокий приоритет:
    m_transactionsThread.m_libusb_ctx =  m_tester.m_ctx;    m_transactionsThread.start(QThread::HighestPriority);

Запуск таймера, отображающего процесс:
    m_timeId = startTimer(100);

И только сейчас запуск ПЛИСового таймера, который формирует нам данные. С учётом того, что он нам недошлёт:
    uint64_t left = ui->m_txtWordsLeft->text().toInt(0,16);    m_tester.WriteDword(0,m_analyzerData.size()-left);

Для справки, полная функция.
void SpiToAvalonDemo::on_m_btnCancelTest_clicked(){    m_analyzerData.resize(120*1024*1024);    m_asyncParams.pData = (uint8_t*)m_analyzerData.constData();    m_asyncParams.dataOffset = 0;    m_asyncParams.dataSizeInBytes = m_analyzerData.size()*sizeof(uint16_t);    m_asyncParams.transferLen = 0x20000;    m_asyncParams.actualTranfered = 0;    for (int i=0;i<m_dataTranfersInParallel;i++)    {        m_transfers[i] = libusb_alloc_transfer(0);    }    for (int i=0;i<m_dataTranfersInParallel;i++)    {        libusb_fill_bulk_transfer (m_transfers[i],m_tester.m_hUsb,0x81,                                   m_asyncParams.pData+m_asyncParams.dataOffset,m_asyncParams.transferLen,                                   ReadDataTranfserCallback,this,0x7fffffff);        m_asyncParams.dataOffset += m_asyncParams.transferLen;    }    for (int i=0;i<m_dataTranfersInParallel;i++)    {        libusb_submit_transfer(m_transfers[i]);    }    m_transactionsThread.m_libusb_ctx =  m_tester.m_ctx;    m_transactionsThread.start(QThread::HighestPriority);    m_timeId = startTimer(100);    uint64_t left = ui->m_txtWordsLeft->text().toInt(0,16);    m_tester.WriteDword(0,m_analyzerData.size()-left);}


7.6 Практические опыты


На самом деле, в целом, всё достаточно скучно. Если задать недосылать 0, то всё отработает и автоматически остановится. Мы просто увидим, как бежит прогресс.

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



Но давайте я поставлю точку останова вот сюда:


То же самое текстом.
void SpiToAvalonDemo::on_m_btnCancel_clicked(){    m_cancelCnt = 0;    for (int i=0;i<m_dataTranfersInParallel;i++)    {        int res = libusb_cancel_transfer(m_transfers[i]);        if (res >= 0)        {            m_cancelCnt += 1;        }    }    // Ждём фактической отмены пересылки    while (m_cancelCnt != 0)    {        QThread::msleep(100);    }


и нажму на Cancel. О-па! Одна структура перешла в состояние CANCELLED:



И я проверял, один раз будет вызвана CallBack-функция. Причём часть данных будет отмечена, как переданная:



Так что всё в порядке. Выбранный вариант поведения работает. Но я проверял только в Windows.

А теперь мы недошлём существенно больше. Не 0x800, а 0x800800 слов. Теперь всё остановится на отметке 93%.



И в точке останова получим такую картинку:



Все транзакции были активны, но так и не дождались своего завершения.

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

8 Заключение


В статье было показано, как можно работать с библиотекой libusb 1.0 через асинхронные запросы. Показаны основные моменты, без которых ничего не заработает. Раскрыта работа множеством транзакций, помещённых в очередь.

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

Рассмотрен также типовой метод отображения процесса приёма данных и его прерывания в произвольный момент.

Материалы, получившиеся при написании данной статьи, можно скачать тут.

P.S. Собственно, это вся теория, которую я хотел рассказать про работу с USB через FX3. Само собой, это всё только верхушка айсберга, но повторяю вновь и вновь: я не собираюсь становиться гуру в работе с этим контроллером. Я хотел взять типовой пример и сделать мост я сделал это. Всё! Остальное мне пока не нужно.

Но само собой, надо раскрыть интригу сезона: получился анализатор или нет. Получился! Мелочи, которые позволили собрать все прежние наработки в кучу и итоговые файлы, я оформлю в следующей статье. Она и станет заключительной в цикле. Но выйдет она через одну публикацию. А следующей будет выложена статья, где я расскажу не о своих, а о чужих наработках. Побуду корреспондентом в среде разработчиков, делающих сервис All Hardware. Перескажу с их слов, как можно пробрасывать UART из Линукса по сети (правда, примеры я там написал свои, так как все слова предпочитаю перепроверять).

Та статья написана ещё в прошлом году, но было желание пустить её уже после цикла про FX3. Но ситуация изменилась. Компания-разработчик сервиса All-hardware, который предоставляет бесплатный доступ к отладочным платам, объявила конкурс на разработку прошивок для плат, размещенных в сервисе, поэтому сейчас самое лучшее время для публикации статьи про сервис, возможно она будет полезна участникам. Конкурс продлится до 9 апреля. Но лучше не затягивать со стартом, вот она и вклинится вне очереди.
Подробнее..

Intel eASIC N5X структурный ASIC для 5G и AI

16.12.2020 10:16:09 | Автор: admin


В 2018 году Intel приобрела eASIC разработчика структурных ASIC, интересных и перспективных устройств, представляющих собой нечто среднее между классическим ASIC и FPGA. Как и в случае со всем известным производителем FPGA, компанией Altera, миграция eASIC в The Intel Company была постепенной предыдущие наработки шаг за шагом заменялись на более современные, унифицированные с другими продуктами Intel. И вот мы фиксируем знаковое событие: новая линейка eASIC N5X совместима с FPGA компании, то есть имеет полное право называться Intel eASIC N5X. Давайте посмотрим на характеристики этой линейки, а заодно вспомним, что представляет из себя структурный ASIC.

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

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

Применительно к серии N5X преимущества eASIC выглядят следующим образом: на 50% потребляется меньше энергии по сравнению с FPGA и на те же 50% сокращается время разработки по сравнению с ASIC на основе ячеек. Помимо всего прочего, совместимость процессорного блока с FPGA Agilex означает, что:
  • eASIC имеет 4-ядерный ARM процессор, контроллер памяти и большой набор интерфейсов ввода-вывода (подробнее в табличке ниже);
  • eASIC оснащен блоком аппаратного шифрования и аутентификации Secure Data Manager;
  • при проектировании eASIC можно пользоваться мощным пакетом FPGA-разработки Intel Quartus Prime.

Серия N5X будет выпускаться в 5 вариантах с различным количеством внутренних элементов, размер упаковки от 27x27 до 50x50 мм, корпус от FC676 до FC2397. Максимальные характеристики серии приведены в таблице.
Техпроцесс 16 нм
Эквивал. вентилей ASIC До 88 млн
Память До 229 Мб
Регистры До 158 тыс. 128-битных файлов
Трансиверы 32 Гб/с, до 80
Процессор 4-ядерный, 64-бит ARM Cortex-A53, до 1.5 ГГц, кеш 1 Мб
Память DDR4/LPDDR4/LPDDR4x 3200
Интерфейсы IO USB 2.0x2, 1G eMac* x3, UART x2, SPI x4, I2C x5, GPIO x1114
Безопасность AES-256/SHA-256, ECDSA 256/384 boot code authentication, Vendor authenticated boot (VAB), Secured data object storage (SDOS)
Выпуск eASIC N5X это часть стратегии Intel, направленной на создание эффективных инструментов для различных профилей нагрузки. В мире больше нет универсальных средств на все случаи жизни; такие области, как AI, облачные вычисления, системы передачи данных, IoT, требуют разнообразных и эффективных инструментов как для ЦОД, так и для периферии. Intel сейчас предлагает самый широкий ассортимент вычислительных мощностей под любую задачу.
Подробнее..

Скоростной АЦП с нуля. 16 бит за 10 лет

01.12.2020 12:23:05 | Автор: admin
Чего стоит разработать быстродействующий аналого-цифровой преобразователь, почти не имея опыта? Насколько сильно наше отставание в этой области? Есть ли в этой нише шанс найти коммерческое применение своей продукции и отщипнуть хоть кусочек рынка у гигантов мира сего? Выпуская в свет новый 16-битный 80 МГц АЦП, хотим порассуждать на эти темы и рассказать о самой микросхеме и опыте её создания.
image


Введение


2010 год. Тогда многие этим увлекались. Тема быстрых АЦП вдруг стала популярной. Кто-то раньше, кто-то позже, но сразу несколько российских компаний принялись вести разработки в этой области. Не стали исключением и мы. Словно нужно было дождаться, когда рассеется дым горящих вокруг Москвы торфяников, чтобы увидеть, что ниша отечественных скоростных аналого-цифровых преобразователей совершенно пуста. Отставание было гигантским, в несколько поколений. Из наших тогда можно было достать только старые добрые микросхемы серии 1108ПВ 10-14 разрядные АЦП с быстродействием 0,3-1,3 МГц, разработанные еще в советской Риге. Самым крутым считался вильнюсский биполярный 1107ПВ3, тоже родом из 80-х, который имел разрядность 6 бит и мог работать со скоростями до 100 МГц. В это же время западные микросхемы на таких скоростях достигали уже 16 бит! А при меньшей разрядности могли работать на нескольких сотнях мегагерц.

Столь привлекательным казалось попытаться наверстать отставание и заполнить этот вакуум отечественного сегмента АЦП. Было очевидно: кто первый создаст что-то более-менее современное, у того будет шанс монополизировать в дальнейшем весь сегмент. Ввязавшись в гонку тогда, мы смутно догадывались, что путь предстоит неблизкий, но никто не предполагал, что первый верстовой столб на нём будет стоять на отметке в 10 лет


Смог в Зеленограде 2010 г. Фото с сайта Graker.ru

Что за зверь?


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

Наверное, каждый человек, сам того не подозревая, ежедневно имеет дело с АЦП. Электроника окружает нас повсюду, и, если речь идёт о современном устройстве хоть чуточку сложнее штепсельной вилки, в нём наверняка трудится этот девайс. А уж такая привычная нам техника, как смартфоны, видеокамеры, аудиопроигрыватели, игровые станции, и пр. буквально напичканы ими. Аналого-цифровые преобразователи в их составе выполняют разную работу и имеют присущую этой работе архитектуру: это может быть SAR, Delta-sigma, Pipeline, Folded-interpolated, Flash, Dual-slope и т.д. Такое разнообразие видов обусловлено тем, что не существует оптимальной архитектуры для всех типов приложений. С точки зрения исполнения АЦП могут быть встроены в системы-на-кристалле или реализованы в виде отдельных микросхем.


В системах радиосвязи, радиолокации, телекоммуникации зачастую используются быстродействующие АЦП. Быстродействующими считаются преобразователи с частотой выборки более 10 Мвыб/c. Как правило, они имеют архитектуру Flash, Folded-Interpolated или pipeline, хотя в последнее время стали появляться и быстрые SAR.
У любого АЦП довольно много различных параметров. Для высокоскоростных преобразователей ввиду специфики их применения особенно важны динамические SFDR, SNR, IMD. Подробнее об этих и других параметрах можно прочитать здесь.

Первые шаги


Вернемся обратно в 2010 год. Какими наивными мы были! Сейчас уже невозможно сдержать улыбку, просматривая отчёты и презентации, что мы делали тогда. Только с аспирантской скамьи, мы строили честолюбивые планы, как через пару-тройку лет сделаем преобразователь, не менее быстрый и не менее точный, чем у них Ведь опыт разработки быстродействующих АЦП уже был. В нашем портфолио лежал аж 14-битный 100 МГц преобразователь! (Не миландровский.) Правда работал он так:

Вид кристалла и спектр после первой попытки

На выходе этого преобразователя вместо синусоиды был изрезанный резкими провалами меандр. Представляете, два года работы и такое фиаско! SNR 17 дБ вместо расчётных 68. Тем не менее никто не унывал, потому что такие провалы не редки в микроэлектронике. Такова уж специфика, что за каждой схемой, как бы хорошо она не работала на модели, скрывается вопрос а в железе будет работать? Ответить на этот вопрос, и то не наверняка, можно только с опытом.
Итак, мы перевернули страницу и принялись заново разрабатывать 14-разрядный 100 МГц АЦП. Вскоре параллельно с нами начала работать другая, более опытная команда, перешедшая к нам со своими разработками из другой компании. Мы недоумевали тогда, зачем двум командам решать, пусть и разными способами, но одну и ту же задачу? Зачем эта внутренняя конкуренция? Оказывается этим, сами того не подозревая, мы копировали в миниатюре великих мира сего

А как там у них?


Нам было любопытно, как развивалось направление быстрых АЦП у лидеров сегмента. Для примера мы взяли компанию Analog Devices, которая еще в 2010 году удерживала 48% рынка преобразователей, что больше, чем доля 8 последующих конкурентов вместе взятая. Проанализировав и сопоставив официальные даташиты и научные публикации, мы составили следующий таймлайн:



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

Не разбирая всех причин такого успеха компании, отметим лишь две наиболее важные для нас, инженеров. Во-первых, это полувековое эволюционное развитие, позволившее накопить внушительный коллективный опыт. Во-вторых, большие финансовые и ресурсные вложения, свидетельствующие о приоритетности этого направления в компании. Над быстродействующими АЦП одновременно работают два подразделения, в Вилмингтоне и Гринсборо (США), причем в разработке каждой микросхемы может быть задействовано до нескольких десятков инженеров. Основной костяк каждой группы это инженеры, которые много лет занимаются исключительно этой тематикой. Иногда у обеих команд получались близкие по характеристикам преобразователи, хоть и шли они каждый своим путём. Зачастую и в этом случае обе микросхемы выводились на рынок. Супербыстрые преобразователи последнего поколения оказались неподъёмными для какого-то одного подразделения, поэтому обе команды вынуждены были объединить усилия.

Долгая дорога в дебрях


Отметка мы здесь на таймлайне вверху демонстрирует, что в области быстродействующих АЦП наше отставание от передовых разработок на сегодня составляет около 10 лет. И это после десятилетней работы! Наверняка кому-нибудь понадобилось бы меньше времени, но именно столько мы потратили, чтобы пробраться сквозь дебри разработки и получить результат, за который не стыдно.

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

Очень скоро выяснилось, что при таких скоростях на параметры влияет не только качество схемотехники самой микросхемы, но и того, что её окружает корпуса и печатной платы. Нужно было учиться разрабатывать платы для таких приложений: ведь сначала не получалось даже повторить демо-плату ADI так, чтобы параметры их же АЦП соответствовали даташиту. Индуктивности использовавшегося корпуса тоже пагубно отражались на характеристиках, поэтому пришлось разработать новый корпус с так называемым донным контактом (exposed pad), чтобы увеличить количество выводов земли.

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


Таймлайн тестовых образцов в ходе разработки микросхемы

За время, что мы работали над этой микросхемой, было сделано 5 запусков. Будучи fabless компанией, каждый запуск обходился нам в копеечку, которую, к тому же, приходилось доставать из своего кармана (из кармана компании, а не из брюк инженеров), так как этот проект не связан с ОКР-ами и финансируется из собственных средств. Помимо цены есть ещё один минус для мелких fabless компаний. Ожидание кристаллов после запуска иногда затягивается до полугода, чем напрочь выбивает из рабочего ритма.
В 2014 году мы готовы были выводить имеющуюся разработку в свет, руководствуясь принципом на безрыбье и рак рыба. Микросхема была откровенно сырая, плохо калибровалась, поэтому хорошо, что к этому моменту вторая наша команда АЦП-шников сделала более хорошую микросхему её и стали производить под именем 5101HB015. Чтобы попробовать превзойти этот АЦП, нам пришлось перейти на новую архитектуру и даже другую фабрику.

И вот, наконец, новая микросхема увидит свет!

Коммерческий рынок. Почему высокоскоростные АЦП?


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

A. Выход на мировой рынок
Наверняка, многие знают: чтобы сделать коммерчески выгодный продукт в микроэлектронике, необходим крупный рынок сбыта. Это связано с окупаемостью R&D, измерительного оборудования, запуска тестовых кристаллов и т.д. Влияет на цену микросхемы и тот факт, что фабрика даёт скидку на пластины при больших объёмах производства. В суровых реалиях российского приборостроения сложно сделать схему, которая бы обеспечила высокий спрос. Тем более, когда существуют такие гиганты как ST, TI, ADI, ну и китайские аналоги любых микросхем, которые можно купить за 3 копейки.

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

С высокоскоростными преобразователями всё иначе. На рынке существуют 3 основные компании, которые развивают направление высокоскоростных АЦП: TI, ADI и Maxim Integrated (последние две объявили о слиянии). Поэтому данный рынок сильно монополизирован. Цены на преобразователи с частотой дискретизации 80 Мвыб/c находятся в районе 80 долларов, что подразумевает серьезную наценку. На habr есть статья, в которой хорошо освещена проблема монополии в микроэлектронике.

Б. Ограничение поставки в Россию и Китай
С каждым годом вступают все более жесткие ограничения на поставки ЭКБ в Россию и Китай. Высокоскоростные точные преобразователи попадают в категорию ограничений. Даже потребители из европейских стран при заказе таких микросхем должны заполнять документацию, связанную с экспортным контролем продукции двойного назначения. Этот аспект тормозит развитие коммерческих устройств, которые могли бы достичь лучших параметров.
Фото с сайтов Mouser, Arrow

В. Улучшение качества собственных продуктов РЭА
На данный момент у нас разрабатывается система ADAS для помощи водителю. Для обработки данных с радара может использоваться новый АЦП, что позволит существенно поднять точностные параметры системы, а также уменьшить стоимость аппаратуры.


Обобщив все эти пункты, мы решили создать коммерческий вариант микросхемы (называться будет MDRA1A16FI), цена которой будет ниже, чем у зарубежных аналогов. Образцы в металлокерамическом корпусе можем предоставить всем заинтересованным уже сейчас, а в пластиковом корпусе QFN-48 в начале 2021 года. Кому интересно, здесь можно оставить заявку на получение образцов. Пластиковый корпус существенно меньше металлокерамического всего 7x7x0.85 мм против 11x11x2 мм, и, следовательно, легче и дешевле.

Что в итоге получилось




Теперь, наконец, о самой микросхеме что в итоге получилось. Микросхема, получившая название 5101HB045, представляет собой 16-разрядный АЦП с частотой дискретизации 80 Мвыб/c. Её характеристики следующие:

Разрядность, бит $N$ 16
Напряжение питания, В $V_{dd}$ 1.8
Полная шкала, В (п-п) $V_{FS}$ 2
Частота преобразования, МГц $f_{s}$ 80
Соотношение сигнал/шум, dBFS (при $f_{IN}$=10/75МГц) $SNR$ 75.0 / 73.1
Динамический диапазон, свободный от гармоник, dBc
(при $f_{IN}$=10/75МГц)
$SFDR$ 94 / 83
Интермодуляционные искажения 3-го порядка, dBc (при $f_{IN}$~10/75МГц) $IMD3$ -92 / -80
Интермодуляционные искажения 2-го порядка, dBc (при $f_{IN}$~10/75МГц) $IMD2$ -105 / -83
Интегральная нелинейность, LSB $INL$ 2.7
Дифференциальная нелинейность, LSB $DNL$ 0.75
Джиттер, пс $T_{j}$ 0.30
Full Power Bandwidth, МГц $BW$ 688
Рассеиваемая мощность (полная, в КМОП режиме), Вт $P_{sup}$ 0.56


Спектр, интегральная и дифференциальная нелинейность

Структурная схема преобразователя:



Микросхема представляет собой одноканальный АЦП конвейерного типа с разрядностью 16 бит. Процесс преобразования происходит в несколько этапов:
  1. Входной аналоговый дифференциальный сигнал подаётся через выводы VINP/VINN на входное устройство выборки-хранения (УВХ).
  2. Сигнал выборки, сохраненный на емкости УВХ, обрабатывается ядром 16-битного АЦП.
  3. Система цифровой постобработки получает цифровой эквивалент обрабатываемой выборки, осуществляет цифровую коррекцию и кодировку в нужный формат (двоичный код со смещением, дополнительный код, код Грея).
  4. Итоговый результат выдается на параллельную шину по LVDS или CMOS интерфейсу.


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



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

Стало лучше?


Во многом, да, если сравнивать с тем же 5101НВ015:
  • Здесь нет интерливинга. Это означает, что в выходном спектре данного АЦП не будут присутствовать искажения, характерные для преобразователей с интерливингом, что является существенным плюсом при построении, например, SDR систем.
  • Проще пользоваться. Не нужно тратить время на калибровку при запуске или перенастройке микросхемы. Все калибровочные коэффициенты уже записаны во внутреннюю память при производстве.
  • Расширенный список доступных интерфейсов вывода данных: LVDS/DDR (с двойной скоростью) и параллельный КМОП 1,8 / 2,5 / 3,3 В.
  • Улучшенные динамические показатели SNR [69.5 -> 75] и SFDR [81->94]
  • Хорошая статическая линейность: INL 3 (14 бит) -> 2,5 (16 бит), т.е. более, чем в 4 раза лучше.
  • Можно подключить внешний источник опорного напряжения 1,25 В и поднять SNR до 76,7 дБ.
  • Улучшенный софт для отладочного комплекта.


Что не лучше так это скорость преобразования (80 вместо 125 МГц) и потребление (560 против 115 мВт).

Отладочный комплект и софт


Для того, чтобы попробовать этот АЦП в действии, и при этом не заниматься проектированием печатной платы, мы разработали специальный отладочный комплект. В него входят две платы аналоговая (собственно АЦП и обвязка) и цифровая (сбор выходных данных). Обе платы соединяются разъемами и питаются от одного стандартного 5 В блока питания.



Используя этот отладочный комплект, можно легко и быстро проверять свои решения при разработке аппаратуры. Достаточно подключить к разъёму входной сигнал, всё остальное сделает комплект. На плате присутствует источник тактового сигнала и даже источник внешнего опорного напряжения, чтобы проверить, как работает система при опоре 1,25 В. При желании можно подать свой собственный тактовый сигнал через соответствующий разъем.
Самостоятельно собирать и обрабатывать выходные отсчёты с АЦП тоже не нужно. Мы написали новый софт быстрый и автономный. Предыдущая версия требовала, к примеру, предустанавливать matlab-библиотеки, что достаточно неудобно. Программа умеет конфигурировать АЦП, снимать и выгружать с него данные, строить спектр и вычислять по нему характеристики. Данное ПО поставляется в составе отладочного комплекта. Так же, его можно скачать здесь. Кому интересны комплекты, вот ссылка.

Скриншоты отладочного ПО

Работа над ошибками


Да, мы сделали большой рывок, но часть характеристик всё еще не дотягивает до параметров импортных микросхем. Сравним основные характеристики существующих 16 разрядных АЦП с частотой дискретизации 80 Мвыб/c.

Сравнение параметров
5101НВ045 AD9265 ADS5481 ADS5562 MAX19586 AD9266 LTC2163
Производитель МИЛАНДР ADI TI TI Maxim ADI Linear T. (ADI)
Разрядность, бит 16 16 16 16 16 16 16
Скорость, Мвыб/c 80 80 80 80 80 80 80
Интерливинг нет нет нет нет нет нет нет
Входной буфер нет нет да нет да нет нет
Один источник питания да (1.8В) да (1.8В) нет (5; 3В) да (3.3В) нет (1.8; 3.3В) да (1.8В) да (1.8В)
Дизеринг нет да да нет нет нет нет
Делитель тактовой частоты да да нет нет нет да нет
Входной размах 2 В(п-п) 2 В(п-п) 3 В(п-п) 3.56 В(п-п) 2.56 В(п-п) 2 В(п-п) 2 В(п-п)
SFDR @ 10 MHz тип 94 (>88) тип 88 тип 98 тип 85 (>75) тип 96 тип 94 тип 90
SFDR @ 70 MHz тип 83 (>72) (>92) тип 93 н/д тип 84 (>80) тип 93 тип 89 (>82)
SNR @ 10 MHz тип 75 (>74) тип 80 тип 81 тип 83.8 (>79) тип 80 тип 77.6 тип 77.1
SNR @ 70 MHz тип 73.1 (>71) (> 78.7) тип 80.1 н/д тип 79.2 (>77.5) тип 76.6 тип 76.9 (>75.3)
Потребляемая мощность, Вт 0.563 0.308 2.15 0.865 1.11 0.124 0.188
Совместимость одновременно с 1,8/2,5/3,3 В ПЛИС да нет нет нет нет да нет



Недостатком нашей микросхемы является деградация линейности и ухудшение шума при работе в т.н. undersampling режиме (информацию об этом добавили в предыдущую публикацию), т.е. когда полоса входного сигнала находится во второй и выше зоне Найквиста. Это требуется, например, в приложениях с непосредственной дискретизацией ПЧ. Эта деградация происходит из-за относительно высокого собственного джиттера и нелинейности входного УВХ, которая начинает проявляться примерно с 60 МГц.

Зависимость динамических характеристик от частоты входного сигнала

Если, однако, вы обрабатываете сигналы с частотой до 60-70 МГц, то по динамическим параметрам 5101HB045 смотрится в этой таблице, на наш взгляд, вполне достойно.

Что будет дальше?


Поделимся планами по развитию наших высокоскоростных АЦП. Мы полны решимости сделать еще один цикл (надеемся, он пройдёт быстрее, чем за 10 лет) и дотянуть характеристики нашей микросхемы до уровня аналогов. Если конкретно, то:
  1. поднять SNR до 78 дБ, частоту дискретизации до 125 Мвыб/с.
  2. Уменьшить собственный джиттер, увеличить линейность УВХ, и улучшить таким образом SFDR и SNR на высоких частотах входного сигнала.
  3. Добавить дизеринг для работы с маленькими сигналами (более подробно можно узнать об этом в статье на habr).
Приоритетом для себя мы определили высокую линейность и низкий шум.

Вторая наша группа разработчиков приоритетом для себя видит высокую скорость преобразования. В её планах:
  1. В течение 2021 года вывести на рынок АЦП скоростью до 400 Мвыб/с в двух версиях: со встроенным входным буфером и без. Данный АЦП будет также работать в двухканальном режиме с частотой выборки до 200 Мвыб/с каждый.
  2. Этот же АЦП в двухканальном режиме оснастить последовательным выходным интерфейсом Serial LVDS. У схемы с таким интерфейсом существенно меньше выводов. Значит трассировка платы, снятие и обработка данных сильно упрощается. Это заметное преимущество, особенно для многоканальных АЦП.

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

Заключение


Итак, за 10 лет мы существенно продвинулись, но впереди ещё много работы. Тяжело догонять столь быстро и давно развивающиеся компании, особенно когда в R&D схем вкладывают большие ресурсы. Как уже упоминалось, направление скоростных АЦП у нас финансируется за счет собственных средств, и они, увы, сильно ограничены. Но все мы видим большое будущее в этом направлении. Будем действовать, руководствуясь нашим негласным девизом: Таких гигантов как ADI в АЦП-строении нам не переплюнуть, но как следует плюнуть в этом направлении уже хорошо.
Подробнее..

Google бесплатно изготовит чип на техпроцессе 130нм Skywater Апрель-Июнь 2021

23.04.2021 20:10:29 | Автор: admin

Если вы прочитали мою статью, то вы слышали про технологию Skywater 130nm. Google сделала анонс второй программы Multi-project-wafer, и вы можете произвести свою микросхему за бесплатно. С несколькими оговорками

  • Проект должен быть по технологии 130нм Skywater

  • Проект должен быть доступен всем

  • Проект должен находиться под лицензией из списка одобренных

  • Проект должен быть готов к июню 18 числа 2021-го года.

Несмотря на то, что 130нм очень древняя технология почти 20 летней давности, тем не менее с ней можно разработать самые разнообразные и интересные проекты.

Меня поправили в коментариях

130 нм мэйнстрим в части микроконтроллеров, многих видов автомобильных и высоковольтных силовых микросхем, а вовсе не древняя технология. Не надо все мерить по процессорам для песональных компьютеров, современные 130 нм так же похожи на 130 нм из Pentium III, как Tesla 3 похожа на Ford T. И там, и там четыре колеса, на этом сходство заканчивается.

Для изготовления микросхем Google и её партнер efabless выбрали компанию Skywater Technology Foundry, ранее известную под названием Cypress Semiconductor. Производство первых микросхем будет окончено в декабре.

Ранее бывшую собственным производством Cypress и выделенную в независимую компанию. Сама Cypress продолжает существовать в fabless режиме.

Кхм, меня попровляют в личных сообщениях, спасибо @amartology.

Технология имеет слой для полупроводников и 5 металлических слоёв. Заказчики проектов получат 50 экземпляров в корпусе WCLSP 6x10.

Из проектов прошлого года мне понравился вот этот. Это проект передатчика-приёмника для любительских спутников AMSAT.

Для того чтобы вы смогли разработать свою микросхему гугл также выпустила Process Development Kit доступный по ссылке вот тут. Важно сказать, чтобы гугл вашу микросхему вообще одобрил она должна быть собранна по проекту Caravel user project.

Если у вас есть готовый проект написанный на Verilog, его можно собрать используя OpenLANE.

Подробнее..

Zynq. Передача данных между процессорным модулем и программируемой логикой

27.12.2020 18:22:59 | Автор: admin
Как и обещал в предыдущей статье (Что такое Zynq? Краткий обзор), поговорим о передаче данных между процессорным модулем и программируемой логикой. В предыдущей статье упоминалось четыре способа передачи данных, в статье будут рассмотрены два способа, которые нашли большее применение. Подробности под катом. Осторожно, много картинок!

Содержание


1 Общие сведения
2 Передача данных в режиме PIO
2.1 Аппаратная часть
2.2 Программная часть
2.3 Результаты
3 Передача данных в режиме DMA
3.1 Аппаратная часть
3.2 Программная часть
3.3 Результаты
4 Заключение
5 Используемые источники

1 Общие сведения


В общем случае, передача данных между процессорным модулем и программируемой логикой возможна в двух режимах:

  • PIO, используется порт GP.
  • DMA, используется порт HP.

2 Передача данных в режиме PIO


В режиме PIO процессорный модуль работает с программируемой логикой как с набором регистров. Чтобы записать или прочитать определенный объем данных, нужно постоянное участие процессорного модуля. В режиме PIO инициатором всех транзакций является процессорный модуль. Подключение программируемой логики предполагает использование порта GP, где Master это процессорный модуль, Slave программируемая логика.


Структура проекта при использовании PIO

2.1 Аппаратная часть


  1. Создаем проект для Zybo в Vivado, тип микросхемы xc7z010clg400-1.
  2. Создаем block design. Во Flow Navigator => Create Block Design => имя ProcessingSystem => OK.
  3. Используя кнопку + на поле или сочетания клавиш Ctrl + I добавим процессорное ядро.

  4. Подключим необходимые выводы, нажав кнопку Run Block Automation => OK.
  5. Импортируем настройки процессорного модуля. Для этого выполним двойной клик на Zynq7 Processing System => Import XPS Setting => Укажем путь к файлу => OK => OK.
  6. Создаем периферийное ядро, которое будет реализовывать доступ к регистрам в программируемой логике. Tools => Create and Package New IP => Next => Create a new AXI4 peripheral => Next => Задаем имя ядра, например PIO_registers и указываем путь к каталогу для сохранения => Next => В этом окне можно выбрать количество регистров (4 хватит), тип интерфейса, в данном случае это Lite => Next => Add IP to the repository => Finish.

  7. После этих действий, созданное ядро появится в списке доступных для работы в IP каталоге. Можно его увидеть, если выбрать в Flow Navigator => IP Catalog.

  8. Добавим созданное ядро на рабочую область. Ctrl + I => PIO_registers.

  9. Отредактируем созданное ядро, добавив в него работу с перемычками и светодиодами. Для этого на блоке PIO_registers правой кнопкой мыши => Edit in IP Packager => OK. Откроется новое окно Vivado с созданным ядром.
  10. В файле PIO_registers_v1_0.vhd добавим входные и выходные порты и подключим их к внутреннему модулю:

    iSwitches: instd_logic_vector( 3 downto 0);oLeds: outstd_logic_vector( 3 downto 0);...iSwitches=> iSwitches,oLeds=> oLeds,
    

  11. В файле PIO_registers_v1_0_S_AXI.vhd добавим входные и выходные порты:

    iSwitches: instd_logic_vector( 3 downto 0);oLeds: outstd_logic_vector( 3 downto 0);
    

  12. Опишем обработку входов и выходов:

    signalSwitchesReg: std_logic_vector(31 downto 0);...process (SwitchesReg, slv_reg1, slv_reg2, slv_reg3, axi_araddr, S_AXI_ARESETN, slv_reg_rden)variable loc_addr :std_logic_vector(OPT_MEM_ADDR_BITS downto 0);begin    -- Address decoding for reading registers    loc_addr := axi_araddr(ADDR_LSB + OPT_MEM_ADDR_BITS downto ADDR_LSB);    case loc_addr is      when b"00" =>        reg_data_out <= SwitchesReg;      when b"01" =>        reg_data_out <= slv_reg1;      when b"10" =>        reg_data_out <= slv_reg2;      when b"11" =>        reg_data_out <= slv_reg3;      when others =>        reg_data_out  <= (others => '0');    end case;end process;process (S_AXI_ACLK) beginif (rising_edge(S_AXI_ACLK)) thenif (S_AXI_ARESETN = '0') thenSwitchesReg <= (others => '0');elseSwitchesReg( 3 downto 0) <= iSwitches;end if;end if;end process;process (S_AXI_ACLK) beginif (rising_edge(S_AXI_ACLK)) thenif (S_AXI_ARESETN = '0') thenoLeds <= (others => '0');elseoLeds <= slv_reg1( 3 downto 0);end if;end if;end process;
    

  13. Сохраняем vhd файлы, открываем вкладку Package IP PIO_registers. Обновим ядро с учетом изменённых файлов. Вкладка Compatibility меняем Life Cycle на Production. Вкладка File Groups => Merge changes from File Group Wizard. Вкладка Customization Parameters => Merge changes from Customization Parameters Wizard. Вкладка Review and Package => Re-Package IP => Yes. Vivado с созданным ядром закроется.
  14. В окне Block Design выбираем Report IP Status, в появившимся внизу окне выбираем Upgrade Selected => OK => Skip => OK.

  15. Подключим созданное ядро к процессору. Для этого нажимаем Run Connection Automation => OK.

  16. Подключим порты созданного ядра к портам block designa. Для этого выделяем порт, нажимаем правую кнопку => Make External.

  17. Переименуем полученные порты для удобства подключения iSwitches_0 => iSwitches. oLeds_0 => oLeds.

  18. Проверим полученный дизайн => Tools => Validate Design => Ok.
  19. File => Save Block Design.
  20. Переключаемся из режима работы с block design в режим работы с проектом, нажав во Flow Navigator => Project Manager.
  21. Смотрим, как описаны порты у полученного block designa. Для этого выбраем файл ProcessingSystem.bd, правой кнопкой => View Instantiation Template.

  22. Создаем vhd файл top-уровня и подключаем в нем полученный block design. File => Add Sources => Add or create design sources => Next => Create File => вводим имя файла и указываем путь до каталога => OK => Finish => OK => Yes.
  23. Заполняем файл:

    entity PioTransfer isport(DDR_addr: inout std_logic_vector(14 downto 0 );DDR_ba: inout std_logic_vector( 2 downto 0 );DDR_cas_n: inout std_logic;DDR_ck_n: inout std_logic;DDR_ck_p: inout std_logic;DDR_cke: inout std_logic;DDR_cs_n: inout std_logic;DDR_dm: inout std_logic_vector( 3 downto 0 );DDR_dq: inout std_logic_vector(31 downto 0 );DDR_dqs_n: inout std_logic_vector( 3 downto 0 );DDR_dqs_p: inout std_logic_vector( 3 downto 0 );DDR_odt: inout std_logic;DDR_ras_n: inout std_logic;DDR_reset_n: inout std_logic;DDR_we_n: inout std_logic;FIXED_IO_ddr_vrn: inout std_logic;FIXED_IO_ddr_vrp: inout std_logic;FIXED_IO_mio: inout std_logic_vector( 53 downto 0 );FIXED_IO_ps_clk: inout std_logic;FIXED_IO_ps_porb: inout std_logic;FIXED_IO_ps_srstb: inout std_logic;-- ControliSwitches: instd_logic_vector( 3 downto 0 );oLeds: outstd_logic_vector( 3 downto 0 ) );end PioTransfer;architecture Behavioral of PioTransfer isbeginPS : entity WORK.ProcessingSystemport map(DDR_addr=> DDR_addr,DDR_ba=> DDR_ba,DDR_cas_n=> DDR_cas_n,DDR_ck_n=> DDR_ck_n,DDR_ck_p=> DDR_ck_p,DDR_cke=> DDR_cke,DDR_cs_n=> DDR_cs_n,DDR_dm=> DDR_dm,DDR_dq=> DDR_dq,DDR_dqs_n=> DDR_dqs_n,DDR_dqs_p=> DDR_dqs_p,DDR_odt=> DDR_odt,DDR_ras_n=> DDR_ras_n,DDR_reset_n=> DDR_reset_n,DDR_we_n=> DDR_we_n,FIXED_IO_ddr_vrn=> FIXED_IO_ddr_vrn,FIXED_IO_ddr_vrp=> FIXED_IO_ddr_vrp,FIXED_IO_mio=> FIXED_IO_mio,FIXED_IO_ps_clk=> FIXED_IO_ps_clk,FIXED_IO_ps_porb=> FIXED_IO_ps_porb,FIXED_IO_ps_srstb=> FIXED_IO_ps_srstb,-- ControliSwitches=> iSwitches,oLeds=> oLeds );end Behavioral;
    

  24. Добавляем используемые входы и выходы в файл пользовательских ограничений. File => Add sources => Add or create constrains => Next => Create File => вводим имя файла и указываем путь до каталога => OK => Finish.

  25. Заполняем файл согласно принципиальной схеме:

    #Switchesset_property PACKAGE_PIN G15 [get_ports {iSwitches[0]}]set_property PACKAGE_PIN P15 [get_ports {iSwitches[1]}]set_property PACKAGE_PIN W13 [get_ports {iSwitches[2]}]set_property PACKAGE_PIN T16 [get_ports {iSwitches[3]}]set_property IOSTANDARD LVCMOS33 [get_ports {iSwitches[*]}]#LEDs#IO_L23P_T3_35set_property PACKAGE_PIN M14 [get_ports {oLeds[0]}]set_property PACKAGE_PIN M15 [get_ports {oLeds[1]}]set_property PACKAGE_PIN G14 [get_ports {oLeds[2]}]set_property PACKAGE_PIN D18 [get_ports {oLeds[3]}]set_property IOSTANDARD LVCMOS33 [get_ports {oLeds[*]}] 
    

  26. Соберем проект. Для этого во Flow Navigator => Generate Bitstream => OK. Появившиеся окно, что создание прошивки успешно завершено, закрываем.
  27. Экспортируем полученные файлы для разработки приложения на процессорном модуле. Для этого выбираем File => Export => Export Hardware => вводим имя файла и указываем путь до каталога => OK. Получаем на выходе файл .xsa


2.2 Программная часть


Теперь нужно написать приложение, работающее на процессорном модуле, которое будет читать данные из программируемой логики и писать данные в программируемую логику. Необходимо запустить среду разработки Vitis и создать приложение по шаблону Hello World, пример этого показан в предыдущей статье[1].

Адрес созданного ядра для обращения со стороны процессорного модуля можно посмотреть в Vivado. В Flow Navigator => Open Block Design => Вкладка Address Editor. В данном случае адрес равен 0x43C0_0000. По этому адресу расположен регистр, в котором хранится признак, в каком состоянии находятся переключатели. Соответственно, по адресу 0x43C0_0004 расположен регистр, который подключен к светодиодам.

В Vitis откроем файл helloworld.c и заполним:

int main(){init_platform();u32 Status = 0x00;u32 Command = 0x00;xil_printf("Hello World\n\r");while (1){Status = Xil_In32(0x43C00000);xil_printf("Status %x\n\r", Status);if (Status == 0x01 || Status == 0x02 || Status == 0x04 || Status == 0x08){Command = 0x01;}else if (Status == 0x03 || Status == 0x5 || Status == 0x06 || Status == 0x9 || Status == 0xA || Status == 0x0C){Command = 0x03;}else if (Status == 0x7 || Status ==  0x0B || Status == 0x0D || Status == 0x0E){Command = 0x7;}else if (Status == 0x0F){Command = 0x0F;}else{Command = 0x00;}xil_printf("Command %x\n\r", Command);Xil_Out32(0x43C00004, Command);usleep(1000000);}cleanup_platform();return 0;} 

Где функция Xil_In32 используется для чтения 4х байт данных из программируемой логики, а Xil_Out32 соответственно для записи 4х байт данных в программируемую логику.

2.3 Результаты


Собираем приложение, создаем файл прошивки и заливаем в плату. Описано в предыдущей статье[1].

Запускаем, смотрим в мониторе com-порта:

Xilinx First Stage Boot Loader Release 2019.2Dec  9 2020-15:16:52Silicon Version 3.1Boot mode is QSPISUCCESSFUL_HANDOFFFSBL Status = 0x1Hello WorldStatus 0Command 0Status 8Command 1Status CCommand 3Status DCommand 7Status FCommand F

Все работает корректно.

Таким образом, для доступа в режиме PIO к программируемой логике, необходимо в программируемой логике реализовать один из интерфейсов связи с процессорным модулем, где инициатором является процессорный модуль. Такой интерфейс представлен только портом GP.

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

Когда шина Axi-Lite работает на частоте 100 МГц, пауза между запросами составляет в среднем 23 такта. Изменим частоту работы шины до 200 МГц. Пауза между запросами становится равной в среднем 33 такта.

Итого, 4 байта данных передаются на частоте 100 МГц 23 такта. Скорость составляет: 32/(23*10нс)= 139 130 434 байт/с 135 869 Кбайт/с 132 Мбайт/с.
Итого, 4 байта данных передаются на частоте 200 МГц 33 такта. Скорость составляет 32/(33*5нс)= 193 939 393 байт/с 189 393 Кбайт/с 184 Мбайт/с.
Таким образом, можно достичь скорости в 184 Мбайт/с, но при постоянном участии процессорного модуля.

Проект: github.com/Finnetrib/PioTransfer

3 Передача данных в режиме DMA


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

В Zynq возможно использование нескольких ip-ядер, реализующих функции DMA. В данной статье будет рассмотрено ядро AXI DMA [2].

В AXI DMA есть два канала MM2S и S2MM. Канал MM2S (Memory-mapped to stream) используется для передачи данных из процессорного модуля в программируемую логику. Канал S2MM (Stream to memory-mapped) используется для передачи данных из программируемой логики в процессорный модуль. Каналы работают независимо друг от друга.

AXI DMA подразумевает два варианта использования:

  • Direct Register Mode
  • Scatter / Gather Mode

В Direct Register Mode используется один набор регистров, который позволяет передать один буфер из программируемой логики в процессорный модуль и наоборот. Например, для передачи буфера данных из программируемой логики в процессорный модуль, необходимо заполнить поля адреса и размера буфера и запустить DMA. В результате DMA заполнит один буфер в процессорном модуле и остановится.

В Scatter / Gather Mode используется список дескрипторов. DMA обрабатывает буфер, описанный в дескрипторе, и переходит к обработке буфера, описанного в следующем дескрипторе.

3.1 Аппаратная часть



Структура проекта при использовании DMA

Рассмотрим вариант, когда список дескрипторов хранится в программируемой логике. У блока DMA есть порт управления, который подключается к порту GP процессорного модуля. Также имеется порт HP, используемый для обращения к ОЗУ процессорного модуля. Список дескрипторов хранится в памяти дескрипторов. Доступ к памяти дескрипторов возможен как из DMA, так и из процессорного модуля. Процессорный модуль заполняет дескрипторы, DMA вычитывает дескрипторы.

  1. Создаем block design. Во Flow Navigator => Create Block Design => имя ProcessingSystem => OK.
  2. Используя кнопку + на поле или сочетания клавиш Ctrl + I добавим процессорное ядро.
  3. Подключим необходимые выводы, нажав кнопку Run Block Automation => OK.
  4. Импортируем настройки процессорного модуля. Для этого выполним двойной клик на Zynq7 Processing System => Import XPS Setting => Укажем путь к файлу => OK => OK
  5. Добавим на поле одно ядро AXI Direct Memory Access, два ядра AXI BRAM Controller, одно ядро Block Memory Generator.

  6. Двойной клик на ядре AXI Direct Memory Access, настроим ядро. Галка Enable Scatter Gather Engine включит режим работы по списком дескрипторов, оставляем. Галка Enable Control / Status Stream используется для работы совместно с ядром AXI Ethernet, снимаем. Поле With of Buffer Length Register задает количество разрядов, используемых при обработке размера буфера. Запишем в поле число 20, что даст максимальный размер буфера в 2^20 = 1 048 576 байт. Поле Address With задает разрядность адреса. Значения в 32 будет достаточно. Галки Enable Read Channel и Enable Write Channel позволяют включить каждый из каналов независимо друг от друга. Переключатель Enable Single AXI4 Data interface позволяет сократить количество линий связи на диаграмме, поэтому включим его. Нажимаем OK после настройки.

  7. Двойной клик на ядре AXI BRAM Controller. В поле Number of BRAM Interfaces выбираем значение 1. Нажимаем OK после настройки.

  8. Повторяем для второго ядра AXI BRAM Controller.
  9. Двойной клик на ядре Block Memory Generator. В поле Memory Type выбираем True Dual Port RAM. Нажимаем OK после настройки.

  10. Подключим полученный блок памяти к контроллерам памяти. Для этого нажимаем Run Connection Automation => Галку на axi_bram_ctrl_0 BRAM_PORTA => Галку на axi_bram_ctrl_1 BRAM_PORTA => OK.

  11. Подключим нулевой контроллер памяти к процессорному модулю. Для этого нажимаем Run Connection Automation => Галку на axi_bram_ctrl_0 S_AXI => Выбрать Master Interface /processing_system7_0/M_AXI_GP0 => OK. Таким образом, процессорный модуль получит доступ к памяти дескрипторов.

  12. Подключим первый контроллер памяти к ядру DMA. Для этого нажимаем Run Connection Automation => Галку на axi_bram_ctrl_1 S_AXI => Выбрать Master Interface /axi_dma_0/M_AXI_SG => OK. Таким образом, DMA контроллер также получит доступ к памяти дескрипторов.

  13. Подключим управление DMA к процессорному модулю. Для этого нажимаем Run Connection Automation => Галку на axi_dma_0 S_AXI_LITE => OK.

  14. Включим в процессорном модуле порт для доступа к оперативной памяти процессорного модуля HP и прерывания из программируемой логики. Для этого двойной клик на ядре Zynq7 Processing System => Вкладка PS-PL Configuration => Развернем HP Slave AXI Interface => Галку на S AXI HP0 Interface.


    Вкладка Interrupts => Развернем Fabric Interrupts => Развернуть PL-PS Interrupts Ports => Галку на Fabric Interrupts => Галку на IRQ_F2P => OK.

  15. Подключим DMA к порту для доступа к оперативной памяти. Для этого нажимаем Run Connection Automation => Галку на processing_system7_0 S_AXI_HP0 => Выбираем Master Interface /axi_dma_0/M_AXI => OK.

  16. Подключим прерывания от DMA к процессорному модулю. Для этого добавим на поле блок Concat через кнопку + или сочетание клавиш Ctrl + I.
  17. Наведем курсор на вывод mm2s_introut DMA, курсор примет форму карандаша. Кликаем на вывод mm2s_introut и не отпуская кнопку мыши тянем линию к входу In0 блока Concat. Доводим до блока, после появления зеленой галочки, отпускаем.

  18. Повторяем также для вывода s2mm_introut, который подключим к входу In1 блока Concat.
  19. Выход dout блока Concat таким же образом подключим к входу IRQ_F2P ядра Zynq7 Processing System.
  20. DMA подключено. Теперь необходимо подключить вход и выход DMA для данных. Так как обработка данных планируется за пределами Block Design, вытащим наружу тактовый сигнал и сигнал сброса. Для этого правый клик на свободном месте поля и выбираем Create Port или сочетание клавиш Ctrl + K. Заполняем имя порта, направление и тип => OK.

  21. Наведем курсор мыши на созданный порт и после появления карандаша подключим порт к выводу FCLK_CLK0 ядра Zynq7 Processing System.
  22. Подключим сигнал сброса. Для этого выделяем вывод peripheral_reset ядра Processor System Reset => правой кнопкой мыши => Make External.
  23. Клик на полученный порт, зададим новое имя порта и укажем, на каком тактовом сигнале необходимо обрабатывать данный сигнал.

  24. Подключим вход данных DMA. Для этого выделим порт S_AXIS_S2MM блока AXI Direct Memory Access => правой кнопкой мыши => Make External.
  25. Клик на полученный порт, зададим новое имя порта и укажем, на каком тактовом сигнале необходимо обрабатывать данный сигнал.

  26. Подключим выход данных DMA. Для этого выделим порт M_AXIS_MM2S блока AXI Direct Memory Access => правой кнопкой мыши => Make External.
  27. Клик на полученный порт, зададим новое имя порта и укажем, на каком тактовом сигнале необходимо обрабатывать данный сигнал.

  28. Подключим входы тактового сигнала для портов S_AXIS_S2MM и M_AXIS_MM2S у блока AXI Direct Memory Access. Для этого нажимаем Run Connection Automation => Галку на m_axi_mm2s_aclk и m_axi_s2mm_aclk => OK
  29. Теперь необходимо изменить адресное пространство так, чтобы при обращении к блоку памяти от процессорного модуля и от блока DMA адреса были одинаковы. Заодно и увеличим размер памяти для хранения дескрипторов. Вкладка Address Editor => processing_system7_0 / Data / axi_bram_ctrl_0 => Offset Address 0x4000_0000 => Range 32K. Далее axi_dma_0 / Data_SG / axi_bram_ctrl_1 => Offset Address 0x4000_0000 => Range 32K.

  30. Tools => Validate Design => OK. Полученная схема:

  31. File => Save Block Design.
  32. Переключаемся из режима работы с block design в режим работы с проектом, нажав во Flow Navigator => Project Manager.
  33. Смотрим, как описаны порты у полученного block designa. Для этого выбираем файл ProcessingSystem.bd, правой кнопкой => View Instantiation Template.
  34. Создаем vhd файл top-уровня и подключаем в нем полученный block design. File => Add Sources => Add or create design sources => Next => Create File => вводим имя файла и указываем путь до каталога => OK => Finish => OK => Yes.


  35. Заполняем файл:
    entity DmaTransfer isport(DDR_addr: inout std_logic_vector(14 downto 0);DDR_ba: inout std_logic_vector( 2 downto 0);DDR_cas_n: inout std_logic;DDR_ck_n: inout std_logic;DDR_ck_p: inout std_logic;DDR_cke: inout std_logic;DDR_cs_n: inout std_logic;DDR_dm: inout std_logic_vector( 3 downto 0);DDR_dq: inout std_logic_vector(31 downto 0);DDR_dqs_n: inout std_logic_vector( 3 downto 0);DDR_dqs_p: inout std_logic_vector( 3 downto 0);DDR_odt: inout std_logic;DDR_ras_n: inout std_logic;DDR_reset_n: inout std_logic;DDR_we_n: inout std_logic;FIXED_IO_ddr_vrn: inout std_logic;FIXED_IO_ddr_vrp: inout std_logic;FIXED_IO_mio: inout std_logic_vector(53 downto 0);FIXED_IO_ps_clk: inout std_logic;FIXED_IO_ps_porb: inout std_logic;FIXED_IO_ps_srstb: inout std_logic );end DmaTransfer;architecture Behavioral of DmaTransfer issignalRxData: std_logic_vector(31 downto 0);signalRxKeep: std_logic_vector( 3 downto 0);signalRxLast: std_logic;signalRxValid: std_logic;signalRxReady: std_logic;signalTxData: std_logic_vector(31 downto 0);signalTxKeep: std_logic_vector( 3 downto 0);signalTxLast: std_logic;signalTxValid: std_logic;signalTxReady: std_logic;signalclk: std_logic;signalrst: std_logic;signalFifoDataW: std_logic_vector(36 downto 0);signalFifoWrite: std_logic;signalFifoRead: std_logic;signalFifoDataR: std_logic_vector(36 downto 0);signalFifoEmpty: std_logic;signalFifoFull: std_logic;beginPS : entity WORK.ProcessingSystemport map(DDR_addr=> DDR_addr,DDR_ba=> DDR_ba,DDR_cas_n=> DDR_cas_n,DDR_ck_n=> DDR_ck_n,DDR_ck_p=> DDR_ck_p,DDR_cke=> DDR_cke,DDR_cs_n=> DDR_cs_n,DDR_dm=> DDR_dm,DDR_dq=> DDR_dq,DDR_dqs_n=> DDR_dqs_n,DDR_dqs_p=> DDR_dqs_p,DDR_odt=> DDR_odt,DDR_ras_n=> DDR_ras_n,DDR_reset_n=> DDR_reset_n,DDR_we_n=> DDR_we_n,FIXED_IO_ddr_vrn=> FIXED_IO_ddr_vrn,FIXED_IO_ddr_vrp=> FIXED_IO_ddr_vrp,FIXED_IO_mio=> FIXED_IO_mio,FIXED_IO_ps_clk=> FIXED_IO_ps_clk,FIXED_IO_ps_porb=> FIXED_IO_ps_porb,FIXED_IO_ps_srstb=> FIXED_IO_ps_srstb,-- Dma ChanneliDmaRx_tdata=> RxData,iDmaRx_tkeep=> RxKeep,iDmaRx_tlast=> RxLast,iDmaRx_tready=> RxReady,iDmaRx_tvalid=> RxValid,oDmaTx_tdata=> TxData,oDmaTx_tkeep=> TxKeep,oDmaTx_tlast=> TxLast,oDmaTx_tready=> TxReady,oDmaTx_tvalid=> TxValid,-- SystemoZynqClk=> clk,oZynqRst(0)=> rst );FifoDataW(31 downto  0) <= not TxData;FifoDataW(35 downto 32) <= TxKeep;FifoDataW(    36) <= TxLast;FifoWrite <= TxValid and not FifoFull;TxReady <= not FifoFull;EchFifo : entity WORK.SyncFifoBram37x1024port map(clk=> clk,srst=> rst,din=> FifoDataW,wr_en=> FifoWrite,rd_en=> FifoRead,dout=> FifoDataR,full=> open,empty=> FifoEmpty,prog_full=> FifoFull );RxData <= FifoDataR(31 downto  0);RxKeep <= FifoDataR(35 downto 32);RxLast <= FifoDataR(36);RxValid <= not FifoEmpty;FifoRead <= RxReady;end Behavioral; 
    
  36. Соберем проект. Для этого во Flow Navigator => Generate Bitstream => OK. Появившиеся окно, что создание прошивки успешно завершено, закрываем.
  37. Экспортируем полученные файлы для разработки приложения на процессоре. Для этого выбираем File => Export => Export Hardware => вводим имя файла и указываем путь до каталога => OK. Получаем на выходе файл .xsa



3.2 Программная часть


Теперь нужно написать приложение, работающее на процессорном модуле. Необходимо запустить среду разработки Vitis и создать приложение по шаблону Hello World, пример этого показан в предыдущей статье.

Формат дескрипторов для Axi DMA описан в документе на ядро [2]. Дескриптор имеет размер 52 байта, однако, адрес, по которому расположен дескриптор, должен быть выровнен на 64 байта.

Кратко по формату дескриптора:

  • NXTDESC адрес следующего дескриптора;
  • NXTDESC_MSB старшие 32 бита адреса следующего дескриптора;
  • BUFFER_ADDRESS адрес буфера;
  • BUFFER_ADDRESS_MSB старшие 32 бита адреса буфера;
  • RESERVED не используется;
  • RESERVED не используется;
  • CONTROL задает размер буфера, признаки начала и конца пакета;
  • STATUS показывает, сколько байт принято/передано, обработан/не обработан;
  • APP0 используется для работы с каналом Control/Status Stream;
  • APP1 используется для работы с каналом Control/Status Stream;
  • APP2 используется для работы с каналом Control/Status Stream;
  • APP3 используется для работы с каналом Control/Status Stream;
  • APP4 используется для работы с каналом Control/Status Stream.

Адреса в программируемой логике для обращения со стороны процессорного модуля можно посмотреть в Vivado. В Flow Navigator => Open Block Design => Вкладка Address Editor. В данном случае адрес DMA равен 0x4040_0000. Адрес начала области памяти для дескрипторов равен 0x4000_0000.

  1. В Vitis откроем файл helloworld.c и подключим следующие библиотеки

    #include <xil_io.h>#include "sleep.h"#include "xil_cache.h"#include "xil_mem.h"
    
  2. Каждый дескриптор должен начинаться с адреса, кратного 64 байтам. Следовательно, в 32Кбайта поместятся 32 768 / 64 = 512 дескрипторов. По 256 на прием и 256 на передачу.

    #define DESC_COUNT 256... /** Descriptors for receive */struct SGDesc RxDesc[DESC_COUNT];/** Descriptors for transmit */struct SGDesc TxDesc[DESC_COUNT];
    
  3. Выключим работу кэша, чтобы не думать, когда его сбросить.

    /** Flush Cache */Xil_DCacheFlush();/** Disable Cache */Xil_DCacheDisable();
    

  4. Заполним буферы, которые будут передаваться в программируемую логику.

    for (u16 desc = 0; desc < DESC_COUNT; desc++){for (u32 i = 0; i < BUFFER_SIZE; i++){TxBuffer[desc][i] = desc + i;}}
    
  5. Заполним список дескрипторов для передачи.

    for (u16 i = 0; i < DESC_COUNT; i++){TxDesc[i].NXTDESC = &TxDesc[i];TxDesc[i].NXTDESC_MSB = 0x0;TxDesc[i].BUFFER_ADDRESS = &TxBuffer[i][0];TxDesc[i].BUFFER_ADDRESS_MSB = 0x0;TxDesc[i].RESERVED0 = 0x0;TxDesc[i].RESERVED1 = 0x0;TxDesc[i].CONTROL = 0xC000000 + sizeof(TxBuffer[i]);TxDesc[i].STATUS = 0x0;TxDesc[i].APP0 = 0x0;TxDesc[i].APP1 = 0x0;TxDesc[i].APP2 = 0x0;TxDesc[i].APP3 = 0x0;TxDesc[i].APP4 = 0x0;}
    
  6. Скопируем дескрипторы передачи в память дескрипторов, которая расположена в программируемой логике.

    DescAddr = 0x40000000;for (u16 i = 0; i < DESC_COUNT; i++){Xil_MemCpy(DescAddr, &TxDesc[i], sizeof(TxDesc[i]));DescAddr += 0x40;}
    
  7. Запишем указатель на следующий элемент в списке дескрипторов.
    /** Write pointer to next pointer */DescAddr = 0x40000000;for (u16 i = 0; i < DESC_COUNT - 1; i++){Xil_Out32(DescAddr, DescAddr + 0x40);DescAddr += 0x40;}/** Write pointer for last descriptor */Xil_Out32(DescAddr, DescAddr);
    
  8. Повторим для списка дескрипторов приема.

    /** Fill descriptor to receive */for (u16 i = 0; i < DESC_COUNT; i++){RxDesc[i].NXTDESC = &RxDesc[i];RxDesc[i].NXTDESC_MSB = 0x0;RxDesc[i].BUFFER_ADDRESS = &RxBuffer[i][0];RxDesc[i].BUFFER_ADDRESS_MSB = 0x0;RxDesc[i].RESERVED0 = 0x0;RxDesc[i].RESERVED1 = 0x0;RxDesc[i].CONTROL = sizeof(RxBuffer[i]);RxDesc[i].STATUS = 0x0;RxDesc[i].APP0 = 0x0;RxDesc[i].APP1 = 0x0;RxDesc[i].APP2 = 0x0;RxDesc[i].APP3 = 0x0;RxDesc[i].APP4 = 0x0;}/** Copy receive descriptor for memory of descriptors */DescAddr = 0x40000000 + 0x4000;for (u16 i = 0; i < DESC_COUNT; i++){Xil_MemCpy(DescAddr, &RxDesc[i], sizeof(RxDesc[i]));DescAddr += 0x40;}/** Write pointer to next pointer */DescAddr = 0x40000000 + 0x4000;for (u16 i = 0; i < DESC_COUNT - 1; i++){Xil_Out32(DescAddr, DescAddr + 0x40);DescAddr += 0x40;}/** Write pointer for last descriptor */Xil_Out32(DescAddr, DescAddr); 
    
  9. Запустим DMA на передачу. DMA начинает обрабатывать данные после записи в регистр значения хвоста списка дескрипторов.

    /** Reset DMA and setup *//** MM2S */Xil_Out32(0x40400000, 0x0001dfe6);Xil_Out32(0x40400000, 0x0001dfe2);/** S2MM */Xil_Out32(0x40400030, 0x0001dfe6);Xil_Out32(0x40400030, 0x0001dfe2);/** PL => PS */Xil_Out32(0x4040003c, 0x00000000);Xil_Out32(0x40400038, 0x40004000);Xil_Out32(0x40400030, 0x0001dfe3);Xil_Out32(0x40400044, 0x00000000);Xil_Out32(0x40400040, 0x40007FC0);/** PS => PL */Xil_Out32(0x4040000C, 0x00000000);Xil_Out32(0x40400008, 0x40000000);Xil_Out32(0x40400000, 0x0001dfe3);Xil_Out32(0x40400014, 0x00000000);Xil_Out32(0x40400010, 0x40003FC0); 
    
  10. Подождем, пока будет обработан последний дескриптор на приеме и посчитаем время обработки. Конечно, можно использовать прерывания, но для тестовой задачи это излишне.

    /** Wait ready in last descriptor */while (1){status = Xil_In32(0x40003FDC);if ((status & 0x80000000) == 0x80000000){break;}else{countWait++;usleep(100);}}xil_printf("Time %x \n\r", countWait);
    

3.3 Результаты


Собираем приложение, создаем файл прошивки и заливаем в плату. Описано в предыдущей статье[1].

Запускаем, смотрим в мониторе com-порта:

Xilinx First Stage Boot LoaderRelease 2019.2  Dec 16 2020-15:11:44Silicon Version 3.1Boot mode is QSPISUCCESSFUL_HANDOFFFSBL Status = 0x1Hello WorldTime 10F

Таким образом, для обмена данными между процессорным модулем и программируемой логикой, в программируемой логике необходимо реализовать один из интерфейсов связи с процессорным модулем, где инициатором является программируемая логика. Такие интерфейсы представлены портами GP, HP, ACP. В предыдущей статье [1] они все были рассмотрены.

Посчитаем скорость передачи данных: (256 раз * 102400 байт) / (271 * 100 мкс) 967 321 033 байт/с 944 649 Кбайт/с 922 Мбайт/с.
Битовая скорость 7 738 568 264 бит/с.
Теоретическая скорость составляет 32 бита * 250 МГц = 8 000 000 000 бит/с.

Также, существует возможность хранить дескрипторы не в памяти программируемой логики, а в оперативной памяти, подключенной к процессорному модулю. В таком случае порт M_AXI_SG подключается к порту HP Zynq.

Рассмотрим первый вариант, когда для доступа DMA к данными и к дескрипторам в оперативной памяти процессорного модуля используются разные порты HP. Модифицируем прошивку в программируемой логике, чтобы получилась следующая схема:


Доступ к данными и дескрипторами через разные порты

Исходный код приложения приводить не будем. Отличия лишь в том, что дескрипторы не нужно копировать в память программируемой логики. Однако, необходимо учесть условие, чтобы адрес каждого дескриптора был выровнен на 64 байта.

После запуска приложения, в мониторе com-порта увидим, что время выполнения копирования буфера данных не поменялось, также 271 * 100 мкс.

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


Доступ к данными и дескрипторами через один и тот же порт

Исходный код приложения не поменялся относительно предыдущего варианта.
После запуска приложения, в мониторе com-порта увидим новое время выполнения операции копирования буфера: 398 * 100 мкс.

В результате, скорость обработки составит: (256 раз * 102400 байт) / (398 * 100 мкс) 658653266 байт/с 643216 Кбайт/с 628 Мбайт/с.
Битовая скорость 5269226128 бит/с.

Проект: github.com/Finnetrib/DmaTransfer

4 Заключение


В этой статье мы рассмотрели два реализации обмена данными между процессорным модулем и программируемой логикой. Режим PIO прост в реализации и позволяет получить скорость до 184 Мбайт/с, режим DMA несколько посложнее, но и скорость выше до 628 Мбайт/с.

5 Используемые источники


  1. habr.com/ru/post/508292
  2. www.xilinx.com/support/documentation/ip_documentation/axi_dma/v7_1/pg021_axi_dma.pdf
Подробнее..

Новостной дайджест событий из мира FPGAПЛИС 0010 (2021_03) конференция FPGA разработчиков

07.04.2021 18:21:37 | Автор: admin

ПЛИСкульт привет, FPGA хаб!

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





Вебинары


  • Ускорение процесса проектирования печатных плат с использованием ПЛИСУскорение процесса проектирования печатных плат с использованием ПЛИС ::

    Мощность, гибкость и непосредственная доступность ПЛИС привела к тому, что они получили широкое распространение при разработке проектов печатных плат. Очевидно, что благодаря более быстрому выходу на рынок за счет достоинств и высокой производительности, ПЛИС стали перспективной альтернативой по сравнению с более ресурсоемкими IC/ASIC решениями.

  • Xilinx Versal ACAP на русскомXilinx Versal ACAP на русском ::

    Хочу напомнить, что 25-марта-2021 стартует 2-дневный онлайн семинар на русском по Xilinx Versal ACAP. Часть лекций читают участники нашего чата: Дмитрий Cмехов (@dsmv2011) и Михаил Коробков (KeisN13)

  • Увеличение продуктивности верификации RTL кода в Matlab и SimulinkУвеличение продуктивности верификации RTL кода в Matlab и Simulink ::

    Проверка RTL кода остается серьезной проблемой в FPGA и ASIC проектах. На этом вебинаремы увидим, как инженеры с различными навыками могут использовать инструменты MATLAB / Simulink для повышения эффективности и сокращения времени верификации.

  • Plug &amp; Play FPGA программирование для всехPlug & Play FPGA программирование для всех ::

    IDE, учебные пособия и примеры кода широко доступны для микроконтроллеров. К сожалению, начать изучать технологию FPGA зачастую оказывается намного сложнее.

    Миссия VHDPlus изменить представление о проектирование на FPGA. Это инновационная среда разработки, обеспечивающая более высокий уровень абстракции, ч

  • Вебинар по формальной верификации регистров ввода/выводаВебинар по формальной верификации регистров ввода/вывода ::

    Даже небольшой компонент, описанный при помощи RTL, может содержать в себе сотни регистров управления и статуса. Как убедиться, что все они работают правильно? Об этом в предстоящем вебинаре от Siemens.

  • Вебинар по работе с SystemC и MatchLibВебинар по работе с SystemC и MatchLib ::

    Вебинар будет посвящен использованию библиотеки MatchLib SystemC с открытым исходным кодом, первоначально разработанной NVIDIA, для быстрого моделирования и синтеза ускорителя машинного обучения

  • Вебинар по использованию Intel OneAPI от компании BittwareВебинар по использованию Intel OneAPI от компании Bittware ::

    Компании Intelи Bittware проведут вебинар по использованию инструментария InteloneAPI для FPGA.

    В качестве примера будет рассмотрена реализация алгоритма 2D FFT, которая будет запущена на FPGA ускорителе520N-MXот Bittware.

  • Врываемся в FPGA с Basys3 - воркшоп в двух частяхВрываемся в FPGA с Basys3 воркшоп в двух частях ::

    Адам Тейлор приглашает на двух дневный воркшоп по основам проектирования на FPGA с использованием инструментов компании Xilinx. Узнайте, как создать встроенную систему на основе FPGA, разрабатывая забавную игру, похожую на Pong / Breakout. На этом воркшопе нам расскажут обо всем, что нужно знать об экосистеме Xilinx, а также рассмотрят все возможности потрясающей платы Digilent Basys3.

  • Начало работы с платформой Xilinx Versal ACAPНачало работы с платформой Xilinx Versal ACAP ::

    Компания Xilinx приглашает вас присоединиться к бесплатному двухдневному онлайн-тренингу, организованному совместно Xilinx Customer Training и авторизованными поставщиками обучения Xilinx.


Новинки


  • Разработчикам FPGA для бортовых систем: Aldec добавляет 60+ новых правил проверки RTL-кодаРазработчикам FPGA для бортовых систем: Aldec добавляет 60+ новых правил проверки RTL-кода ::

    Компания Aldec, Inc., один из лидеров в разработке ПО для моделировании VHDL/Verilog и аппаратной верификации проектов FPGA и ASIC, добавила более 60 новых правил HDL в модуль DO-254 программы ALINT-PRO (предназначенной для углубленной проверки качества исходного кода RTL) и внесла несколько улучшений в возможности

  • AXI interconnect IP от компании TrueStreamAXI interconnect IP от компании TrueStream ::

    Компания Truestream анонсировала IP ядро, выполняющее функцию AXI Intercinnect. Как следует из документации на IP это интерконнект позволяет решать задачи подключения/арбитражаN-to-1. при различных частотных доменах ведущих и ведомого устройства.

  • Xilinx возвращается в борьбу за Cost-optimized рынокXilinx возвращается в борьбу за Cost-optimized рынок ::

    Кевин Моррис опубликовал на порталеeejournal.com аналитическую заметку, приуроченную к анонсу компанией Xilinx выхода новых кристаллов семейства UltraScale+ Artix UltraScale+ и Zynq ZU1.

  • Анонс Artix UltraScale+ и ZU1Анонс Artix UltraScale+ и ZU1 ::

    Компания Xilinx анонсировала пополнение своей линейки UltraScale+ в направлении cost-optimized сегмента. Основные технические документы уже обновлены и можно ознакомиться стехническими деталями новинок.



Статьи


  • ЦОС на FPGA: простой КИХ фильтр на VeriogЦОС на FPGA: простой КИХ фильтр на Veriog ::

    В нашем телеграм чате в последнее время актуальной стала тема цифровой обработки сигналов на ПЛИС.Whitney Knitter c портала hackster.io как будто бы читая наш чат решила написать руководство по разработке простого КИХ фильтра на FPGA с использованием языка Verilog.

  • 10 Ошибок при проектировании на FPGA10 Ошибок при проектировании на FPGA ::

    На порталеhttps://hardwarebee.com/ появилась небольшая заметка, сосредоточенная на 10 ошибках, которые допускают FPGA разработчики при проектировании.

  • QuickLogic переоткрывает проектирование на FPGAQuickLogic переоткрывает проектирование на FPGA ::

    Еще одна крайне интересная заметка от Кевина Морриса на портале eejournal.com Здесь Кевин обсуждает одну из опенсорс инициативных компаний QuickLogic одного из немногих производителей, который идет по пути проектирования с открытым исходным и производящего FPGA со встроенным аппаратным контроллером Arm Cortex-M4 и со сверхнизким потреблениемQuickLogic EOS S3.

  • Тренинг по ChiselТренинг по Chisel ::

    Вы когда-нибудь слышали про chisel? Если вы читаете наш телеграм чат то наверное да. А вот планировали ли вы развиваться в этом направлении и пытаться его изучать?

  • Так что же такое FPGA?Так что же такое FPGA? ::

    На портале HardwareBee.com появилась небольшая заметка на тему прояснения того, что же такое FPGA? В статье обсуждается отличие FPGA от ASIC, преимущества FPGA, различия между FPGA и CPLD, приложения и архитектура ПЛИС.

  • Реализация тройного модульного резервирования (TMR) на MicroBlazeРеализация тройного модульного резервирования (TMR) на MicroBlaze ::

    Небольшое руководство о том, как разместить 3 софт-процессора MicroBlaze на отладочной платеNexys 4 DDR FPGA (Xilinx Artix 7 FPGA) и затем реализовать тройное модульное резервирование для управления GPIO, используя IP-блок Triple Modular Redundancy (TMR)

  • Использование интегрированного логического анализатора (ila) и виртуального ввода-вывода (vio)Использование интегрированного логического анализатора (ila) и виртуального ввода-вывода (vio) ::

    На портале vhdlwhiz.com появилось новое большое руководство по работе с инструментами отладки Vivado: интегрированными логическими анализаторами (ILA) и блоком виртуальных портов ввода/вывода (VIO).

  • Курсы по RISC-V от The Linux FoundationКурсы по RISC-V от The Linux Foundation ::

    Вышло два новых БЕСПЛАТНХ курса отRISC-V International&&The Linux Foundation

  • На русском::

  • Введение в EDA PlaygroundВведение в EDA Playground ::

    Данная записка создана в помощь новичкам в изучении сайта www.edaplayground.com, с помощью которого можно проводить моделирование модулей, запускать различные симуляторы и многое другое.

  • Что нового в VHDL 2019?Что нового в VHDL 2019? ::

    Эта статья, взята из доклада VHDL 2018: New and Noteworthy. С этим докладом выступил наш коллега Ливен Лемиенгре на DVCON 2018. Поскольку новый стандарт VHDL был утвержден и опубликован в 2019 году, все ссылки на 2018 год в этой статье были заменены на 2019 год

  • Асинхронная работа с libusb 1.0Асинхронная работа с libusb 1.0 ::

    Несколько статей назад мы рассмотрели методику работы с USB-устройством при помощи библиотеки libusb. Данные в устройстве у нас формировались по таймеру, поэтому мы были не просто уверены, что рано или поздно они придут к нам, но даже могли предсказать, через какой срок это произойдёт. Однако в анализаторе (который является конечной целью разработки) данные идут непредсказуемо. Будут данные или нет зависит от поведения объекта контроля.

  • Прикоснемся к магии или как я вступил в ряды MISTического обществаПрикоснемся к магии или как я вступил в ряды MISTического общества ::

    Ниже предложен рецепт приготовления деликатеса, позволяющего Вам попробовать различные 8 и 16-битные приставки и компьютеры. Основное же блюдо для меня miniMIG Amiga core с графикой OCS/AGA/RTG и CPU до 68020 в 20 раз быстрее стандартной A600.


Стримы




Обучающие видео




Вакансии



Конференция FPGA разработчиков



Осталось совсем чуть-чуть до нашей второй встречи FPGA разработчиков, которая пройдет 24 апреля 2021 в формате конференции. Еще можно успеть зарегистрироваться:

  • Онлайн и офлайн участие
  • 5 полноформатных докладов
  • 3 доклада карапули узнайте больше о форматездесь
  • хакатон + викторины с призами



Предыдущие выпуски новостного дайджеста


Вы можете помочь в наполении контентом следующего номера новостного дайджеста по FPGA/ПЛИС. Присылайте ссылки на актуальные материалы в личку или оставляйте их в комментариях.



Подробнее..
Категории: Intel , Fpga , Плис , Altera , Quartus , Xilinx , Vivado

Перевод SuperRT чип для рейтрейсинга на Super Nintendo

17.12.2020 12:23:08 | Автор: admin
image

В продолжение темы, представляем вашему вниманию перевод оригинала статьи от Бена Картера.

Ссылки на видео по этой статье:


У меня наконец-то появились результаты работы над проектом, которым я занимался в свободное время примерно около года.

Его идея возникла, когда я пытался придумать интересный проект для изучения Verilog и проектирования FPGA. Мне пришла в голову мысль о создании простого трассировщика лучей (частично я вдохновлялся успехами моего до ужаса умного друга, создавшего собственный GPU). Немного позже (наверно, потому, что мой мозг ненавидит меня и наслаждается придумыванием глупых заданий) всё это превратилось в вопрос: а не будет ли интересно заставить SNES выполнять рейтрейсинг?. Так родилась идея чипа SuperRT.

Я хотел попробовать создать нечто, напоминающее чип Super FX, используемый в таких играх, как Star Fox. SNES в них выполняет игровую логику и передаёт описание сцены чипу в картридже, который занимается генерированием графики. Я намеренно ограничил себя использованием в конструкции единого самодельного чипа, а не ядра ARM на плате DE10 или любых других внешних вычислительных ресурсов.

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


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


С показанной здесь Super Nintendo (строго говоря, это Super Famicom) снята крышка, чтобы было место для подключения проводов, но во всём остальном она совершенно не модифицирована. К ней подключена печатная плата с копией ужасной игры Pachinko, которую я приобрёл за 100 йен в местном секонд-хенде. ROM игры извлечён и заменён на кабельную муфту. Далее она проходит через набор схем сдвига уровня для преобразования 5 вольт SNES в 3,3 В, а затем подключается к плату разработки DE10-Nano FPGA с Cyclone V FPGA. Платы схем сдвига уровня совершенно ужасны, а их сборка превратилась в кошмар из-за обязательных интегральных схем, которые продаются только в корпусах с поверхностным монтажом. Однако со своей работой они справляются.


Чип SuperRT создаёт сцену при помощи специализированного языка команд, исполняемых одним из трёх блоков паралелльного выполнения кода чипа (по сути, это специализированные процессоры CISC) для расчётов тестов пересечения лучей. Описание сцены позволяет создавать объекты при помощи подмножества операций CSG: в качестве базовых строительных блоков используются сферы и плоскости, а при помощи операций OR, AND и вычитания они применяются для построения нужной геометрии. AABB тоже поддерживаются и в основном используются для тестов усечения (при желании их тоже можно рендерить, но они обладают более низкой точностью позиционирования по сравнению с другими примитивами, поэтому не особо полезны, за исключением задач отладки).


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


Цвет луча для каждого пикселя вычисляется движком лучей, обрабатывающим весь цикл жизни луча; он использует модуль движка исполнения для выполнения управляющей программы, которая описывает сцену столько раз, сколько необходимо для вычисления результатов луча. Сама управляющая программа загружается со SNES и хранится в локальном буфере ОЗУ на 4 КБ анимация реализуется записью в этот буфер модифицированных команд. Дизассемблированный буфер команд выглядит следующим образом:

0000 Start0001 Plane 0, -1, 0, Dist=-20002 SphereSub OH 2, 1, 5, Rad=50003 SphereSub OH 4, 1, 4, Rad=40004 SphereSub OH 5, 1, 9, Rad=90005 SphereSub OH 2, 1, 2, Rad=20006 SphereSub OH -0.5, 1, 2, Rad=20007 RegisterHitNoReset 0, 248, 0, Reflectiveness=00008 Checkerboard ORH 48, 152, 48, Reflectiveness=00009 ResetHitState0010 Plane 0, -1, 0, Dist=-2.1501460011 RegisterHit 0, 0, 248, Reflectiveness=1530012 AABB 4, -2.5, 11,    8, 3.5, 130013 ResetHitStateAndJump NH 440014 Origin 6, 2, 120015 Plane -0.2929688, 0, -0.9570313, Dist=0.24975590016 PlaneAnd OH 0.2919922, 0, 0.9560547, Dist=0.250017 PlaneAnd OH 0, 1, 0, Dist=10018 PlaneAnd OH 0, -1, 0, Dist=40019 PlaneAnd OH -0.9570313, 0, 0.2919922, Dist=-10020 PlaneAnd OH 0.9560547, 0, -0.2929688, Dist=1.4997560021 RegisterHit 248, 0, 0, Reflectiveness=0

Каждый движок исполнения это процессорный модуль с 14-тактным конвейером, и обычно за такт завершается выполнение одной команды, поэтому каждый модуль исполнения может вычислять примерно по 50 миллионов пересечений сфер, плоскостей или AABB. Исключением является то, что операциям ветвления нужно очищать весь конвейер, а следовательно, они тратят 16 тактов (14 тактов на очистку конвейера + 2 тактов задержки на получение команды). Чтобы по возможности избегать этого, используется система прогнозирования ветвления к счастью, часто пространственная связность соседних лучей приводит к высокому уровню совпадения прогнозов.


Пересечения в движке исполнения вычисляются двумя конвейерами один обрабатывает AABB, другой сферы и плоскости. Система в целом работает исключительно с 32-битной целочисленной математикой в формате фиксированной запятой 18.14; если известно, что значения находятся в интервале 1, то используется 16-битный (2.14) формат, а конвейер вычисления пересечений сфер/плоскостей имеет два дополнительных специализированных математических блока, вычисляющих операции обратных значений и квадратного корня.


При рендеринге кадра модуль преобразования PPU превращает буфер кадра в формат, который при помощи DMA можно передать напрямую во VRAM консоли SNES для отображения, ужав его до 256 цветов и заменив его на битовые плоскости тайлов символов. Экран имеет разрешение 200x160, то есть полный кадр занимает ровно 32000 байт данных изображений, которые из-за ограничений пропускной способности передаются во VRAM как два фрагмента по 16000 байт в следующих друг за другом кадрах. Следовательно, полное изображение можно обновлять только раз в два кадра, что ограничивает максимальную частоту кадров 30FPS. Однако тестовая сцена работает с частотой ближе к 20FPS (в основном из-за узких мест на стороне логики SNES).

Большое спасибо участникам этого треда на SNESdev за множество полезных идей о DMA полноэкранного чипа расширения, благодаря которым я придумал описанное мной решение.


В этом чипе также реализовано множество других базовых функций есть интерфейс с шиной картриджа SNES, а также небольшой ROM для программ, содержащий 32 КБ кода для SNES (он ограничен тем, что плата интерфейса пока подключена только к линиям адресной шины A консоли SNES, а поэтому доступное адресное пространство составляет всего 64 КБ, из которых 32 КБ используются для регистров ввода-вывода с отображением в память, применяемых для связи с чипом SuperRT). Также присутствует блок ускорения операций умножения, позволяющий SNES быстро выполнять операции умножения 16x16 бит.


Для отладки я использовал интерфейс HDMI платы DE10, выводя данные на второй монитор, а также геймпад Megadrive, подключённый к контактам GPIO для управления системой отладки. Однако из-за ограниченных ресурсов при включении всех трёх ядер движка лучей отладку приходится отключать.

Вот краткий обзор системы, в будущем я планирую опубликовать новые статьи с более подробным описанием работы отдельных компонентов. Однако если у вас есть вопросы или мысли, то свяжитесь со мной, и я постараюсь ответить!

Огромное спасибо Мэтту, Джеймин, Рику и всем тем, кто помогал советами, вдохновением и поддержкой!





На правах рекламы


Надёжный сервер в аренду и правильный выбор тарифного плана позволят меньше отвлекаться на неприятные уведомления мониторинга всё будет работать без сбоев и с очень высоким uptime!



Подробнее..

Прикоснемся к магии или как я вступил в ряды MISTического общества

27.03.2021 00:13:02 | Автор: admin

Ниже предложен рецепт приготовления деликатеса, позволяющего Вам попробывать различные 8 и 16-битные приставки и компьютеры. Основное же блюдо для меня miniMIG эмулятор Амига с графикой OCS/AGA/RTG и CPU до 68020 в 20 раз быстрее стандартной A600.


В свои студенческие годы после ZX-Spectrum (Пентагон-128 с дисководом) я прикоснулся к волшебному миру Амига, сначала A600, затем A1200, аксели от 030-40MHz до PPC603+040. И вот, совсем недавно я узнал о Apollo Vampire (цена конечно кусачая) решил поискать что-нибудь подобное. Нашел несколько вариантов, но самый интересный, на мой взгляд, проект MIST доделанный Павлом Рябцовым. Проштудировал ветку Сборка, настройка платы MiST v 1.31 , заказал на сайте CHIPkin две печатные платы (как оказалось потом правильное решение, попеременно возникали проблемы то с одним, то с другоим экземпляром) и детали, которые были в наличии. Остальное было куплено на Али и "Чип и Дип", по мере прибытия деталей, начал сборку, о чем и хочу рассказать вам. Если Вы являетесь таким же OLD-фагом старых компьютеров, как и я, пожалуйста, приготовьтесь к прочтению.


1. Собираем цепь питания и индикации. Устанавливаем на плату силовые микросхемы IC1 LM3940IS-3.3, IC2 1117-2.5V, IC4 1117-1.2V, электролиты C4, C6, C7, C10, C19, C22, также можете накидать парочку блокировочных конденсаторов на 100n. Припаяем цепь индикации красный LED3, зеленый LED2, желтый LED1 светодиоды и токоограничивающие резисторы R3, R7, R45 на 220 Ом к ним. Питание я подавал на con jack DC +5V уж больно microUSB по мне хлипенькие, кнопку поставил 6 контактную, хотя видел и вариант с угловой на два. Не забудьте припаять 0 резистор R1 с обратной стороны платы или кинуть "соплю" на контакты. При подключении питания и включении кнопки должен засветиться зеленый светодиод. Проверьте напряжения со стабилизаторов 3.3V, 2.5V, 1.2V. Вот что у меня получилось :



2. Запаиваем IC7 AT91SAM7S256 и все что нужно для её запуска транзистор T1 IRLML6402, резисторы R20, R46-R50, R72, R76, R78, R80, R82-R85, конденсаторы C33, C42-C48, C57-C64. В конце припаяем кварцевый резонатор Q3 18.432MHz, кнопки S2-S4, двойной dip переключатель S5, разъем SV3 для вывода отладочной информации, гнездо ARM JTAG SV4 и перемычку JP1. Разъем microUSB я подобрать не смог, может вам повезет больше, припаял провода от сломанного кабеля.



Теперь можно приступить к программированию ARM, для этого установим SAM-BA v2.18 (for Windows) качаем с сайта www.microchip.com. Первое включение на 5сек провести с замкнутой перемычкой JP1, в это время должен проинициализироваться USB загрузчик и при повторном включении без перемычки в диспетчере устройств появится новый USBSerial/COMxx порт. Запустите программу SAM-BA, выберите нужный COM порт, тип процессора и жмите Connect.



В следующем окне сначала выбираем Erase all flash жмем Execute, затем выберите файл прошивки поновее и нажмите кнопку Send File. Подробнее читайте мануал HowToInstallTheFirmware, там же найдете о программировании через JTAG.



В следующем окне сначала выбираем Erase all flash жмем Execute, затем выберите файл прошивки поновее и нажмите кнопку Send File. Подробнее читайте мануал HowToInstallTheFirmware, там же найдете о программировании через JTAG.


Все вышеперечисленное у меня получилось лишь на второй плате с МК rev. D, заказанным из магазина "Чип и Дип", первую плату с контроллером rev. C заказанным с Алика я тоже потом завел, замкнув вывод ERASE на 5сек. Возможно контроллер был б.у. поэтому и потребовались подобные фокусы.


3. Пора паять обвязку SD карты резисторы R77, R79, R81, дроссель L1, заодно контроллер USB U1 MAX3421E, резистор R65, конденсаторы C36, C40, C41 и резонатор Q2 12MHz. Не забываем припаивать конденсаторы на 100nF, вот что у меня получилось :



Подключите к SV3 USB-COM адаптер типа такого :



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


Minimig by Dennis van Weeren
ARM Controller by Jakub Bednarski
Version ATH201126
SDHC card detected
spiclk: 24 MHz
usb_init
max3421e_init()
Chip revision: 13
busprobe()
usb_reset_state
...

У меня упорно выдавался "Chip revision: ff" на второй плате, пока я не прозвонил все ноги. Оказалось не пропаял вторую ногу ARM к земле.


4. Раз все нормально, распаиваем USB HUB IC6 TUSB2046B, резисторы R51, R53, R55, R56, R66-R75, конденсаторы C35, C37, C38, C39, C49-C56. Обратите внимание, что номинал C38, C39 100pF, в BOM листе с сайта zx-pk.com ошибка, они там указаны 100nF, с такой ёмкостью USB HUB работать не будет. Также вместо резисторов R57-R64 в моем варианте платы место под резисторные сборки на 4 резистора по 15КОм с первым общим. Исправленный список деталей смотрите в полезных ссылках. В конце установим кварц Q1 6MHz, термопредохранитель R54 на 1.1A и двухэтажные USB гнезда :



5. Теперь, самое интересное, FPGA IC5 EP3C25E144. Здесь поаккуратней, на одной плате у меня был непропай с нее на CAS ОЗУ, пришлось любоваться графическими артефактами, пока не прозвонил все ноги. Необходимый минимум для ее запуска генератор OSC1 на 27MHz, дроссель L1, резисторы R4-R6, R8-R10, R12-R15, как обычно конденсаторы по питанию на 100nF.



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



Если FPGA рабочая и не перемаркировка, в дебажном выводе читаем лог про FPGA:


FPGA bitstream file opened, file size = 385575
[************************]
FPGA bitstream loaded
FPGA configured in 1918 ms
ident = a3
Identified MiST core
...

Код ident зависит от корки, для TurboGFX он равен a4, главное чтобы не было ошибок.


6. Теперь можно запаять память IC3 MT48LC16M16A2, ее заказал с префиксом 75D, так как увидел на схеме такую строчку :
SDRAM model is MT48LC16M16A2, speed variant -75 (tAC=6ns, tOH=3ns)
У одного человека увидел SDRAM H57V2562GTR-75C, но в нашей деревне ее найти оказалось сложнее, а мой вариант :



7. Распаиваем видео DAC однопроцентные резисторы R22-R42 и на синхро R16, R17 и конечно X2 узкий VGA разъем. С первой платой я возился неделю, почему нет видео, хотя по логам все ОК, оказалось пожалел пасты на строчный резистор.


8. Допаиваем оставшиеся блокировочные конденсаторы и аудиотракт резисторы R18, R19, R43, R44 и конденсаторы C28, C29, C31, C34 и линейный выход X1. Наушники тоже работают, домашняя специфика позволяет слушать только через Sennheiser, Infinity пока простаивают без дела. Насчет конденсаторов C30, C32 прочитал на форуме что без них "звук повеселее", на одной плате я их не запаял, действительно стало побольше высоких частот. Однозначно сказать не могу, решать Вам, попробуйте оба варианта.


9. Так как Midi пользоваться точно не буду, а на часы сразу детали не пришли, осталось припаять термопредохранитель R52 на 0.2A и разъемы X3,X5 папа DR9 для оригинальных джойстика и мыши (еще не проверял). В итоге получилось так :



Осталось залить амижную корку с ROM и другими необходимыми файлами и полюбоваться на картинку :



Последний раз подобное я видел около 20 лет назад, смахнул скупую слезу и пошел качать образы HDD для эмулятора. Использовать для каждого девайса свою флешку неудобно, я пользуюсь Sorgelig's startup menu для выбора систем через core менеджер. С ним у меня не заработали TurboGFX и Амижная, но под последнюю я и планировал отдельную карту. Очень порадовал ZX, игру Target Renegade с General Sound я чуть-ли не прошел до конца.



Если у Вас ошибка при загрузке FPGA, советую припаять разъем JTAG SV1 и через ByteBlaster с помощью Quartus 13 прошить последовательно уроки с 1 по 4, подробнее почитайте раздел SOC в MIST developer tutorials, возможно это наведет Вас на путь истинный. Обязательно переведите ползунок 1 переключателя S5 в ON перед подключением.


Полезные ссылки :


  1. Обещанная ссылка на исправленный BOM list
  2. Сайт разработчиков MiST FPGA retro gaming .
  3. Статья FPGA MiST Guide на сайте arekuse.net.
  4. Архив SD Amiga на сайте MEGA.

И самое главное, ради чего затевалось данная публикация. Есть у меня идея заменить Atmail на STM32F4, автоматически выкинутся часы и наверное MAX3421E. В будущем можно поставить Cyclone 4 пожирнее. Если у Вас есть мысли по этому поводу, милости просим.

Подробнее..

8 битный компьютер Sprinter Спринтер

19.06.2021 12:05:20 | Автор: admin

Памяти Ивана Петровича Макарченко
(1966-2013)
От поклонников его творчества, и по совместительству моих собеседников

Привет Хабра люди! Это моя первая статья на Хабре.

Я эпизодически интересовался компьютерами из серии сделай сам, те которые пользователь может спаять самостоятельно. Интересовался спектрумом и его многочисленными клонами, смотрел современные видео самостоятельных сборок спектрума на ютубе. Совершенно случайно, через знакомого я узнал, о редком, забытом 8 битном компьютере Спринтер, чья разработка внезапно ожила в последнее время. И который, возможно собрать своими руками.

Информации о данном проекте в интернете чрезвычайно мало, есть темы на форуме http://winglion.ru/ . Основные свои представления о Спринтере, я почерпнул из личного общения и telegram, поэтому возможно в моем посте будут содержаться фактические ошибки, или же простое недопонимание. Простой поиск по Хабру показал, что на нем нет статей по компьютеру Спринтер / Sprinter, есть только упоминания о нем в статьях посвященных zx spectrum. Моя статья это попытка закрыть этот пробел. Надеюсь, эта статья будет полезной для популяризации компьютера Sprinter, так и интересной для любителей ретро техники. Хотя, она может и выглядит довольно сумбурной.

Персональный компьютер Спринтер, достаточно современный 8 -битный компьютер, чья окончательная версия была представлена фирмой Петерсплюс/Peters Plus 7 августа 2000 г. Хотя рабочий прототип был впервые представлен на компьютерной выставке enlight'96, в 1996 году. Его производство было остановлено в 2003 году. 1 февраля 2007 Иван Мак - Иван Макарченко- winglion (разработчик компьютера Спринтер) объявил о том, что фирма Петерсплюс отдала ему все наработки, по - этому компьютеру, и теперь он готов со временем открыть проект. После его смерти, поддержкой, восстановлением проекта, и дальнейшей разработкой данного компьютера занимается группа энтузиастов.

Sprinter от Петерсплюс известен как, sp2000, был компьютер Sp97, который был прототипом.

Как мне стало известно из открытых источников, Спринтер в 90-х разрабатывался как полноценный, дешевый заменитель дорогого IBM PC совместимого пк.

Реклама на сайте Петерсплюс сообщала, что персональный компьютер Sprinter является универсальным компьютером на базе 8-ми битного процессора Z-80 корпорации Zilog. Архитектура компьютера основана на перепрограммируемой логической матрице (PLD) корпорации Altera. Так же указывалось, что: использование PLD позволяет программно изменять архитектуру компьютера без замены каких-либо компонентов платы компьютера. Компьютер Sprinter имеет две конфигурации для различных режимов работы. Конфигурация Sprinter является главной конфигурацией, обеспечивающей наиболее интересные возможности компьютера по выводу графики и звука. Конфигурация ZX Spectrum является первой конфигурацией служащей для совместимости с другими популярными компьютерами. Вы можете использовать режимы нескольких клонов компьютера Spectrum, не покидая ваш Sprinter. Компьютер Sprinter использует стандартную современную периферию. Это позволяет вам использовать доступный принтер для печати документов и рисунков, проигрыватель компакт дисков (CD-ROM) для прослушивания аудио CD, винчестер для хранения ваших файлов.

После объявления Иваном Макарченко о публикации прошивки компьютера Спринтер началось так называемое второе пришествие Спринтера.

После смерти Ивана Макарченко , спустя несколько лет спонтанно собралась Sprinter Team 24.11.2020. Создана группа в telegram: https://t.me/zx_sprinter, с очень лояльными для новых пользователей правилами: запрещены мат и личные оскорбления. Репозиторий исходный кодов, расположен по адресу: https://gitlab.com/sprinter-computer. Создан сайт http://sprinter8.com/, только он похоже пока не наполнен. Полноценный , наполненный сайт находится по адресу http://sprinter8.net/ . Официальные форумы: http://www.nedopc.org/forum/viewforum.php?f=60 https://zx-pk.ru/forums/121-sprinter.html.

Именно с 2020 года начинается так называемое Третье Пришествие Спринтера. На данный момент, платформа спринтера начала развиваться заново, выходят свежие ревизии материнских плат, добавляются новые возможности в архитектуру компьютера. Идут оживленные дискуссии о дальнейшей линии развития компьютера Спринтер, идеологии платформы, его будущей аудитории, вопросы ретро-гейминга, восстановление редких, и казалось бы утерянных игр (Thunder in the Deep). Довольно активно ведутся стримы с трансляциями запусков игр, и разработки нового программного обеспечения.

На сегодняшний день этот компьютер обладает следующими характеристиками: микроконтроллер z84c15 с частотой 21 mzh. Z84C15 микроконтроллер с ядром z80 и несколькими интегрированными периферийными чипами от Zilog.

Стандартные графические режимы:

  • 640х256 4 палитры по 16 цветов из 16 млн., две страницы ( по умолчанию в странице 0 установлена палитра 0, а в странице 1 палитра 1).

  • 320х256 4 палитры 256 цветов из 16млн., две страницы ( по умолчанию в странице 0 установлена палитра 0, а в странице 1 палитра 1).

Текстовые режимы:

  • 80х32 текстовый режим,

  • 40х32 текстовый режим (он нигде не используется).

Так же присутствует ZX Spectrum графический режим, он может иметь тайминги Пентагона или тайминги Скорпиона.

Расширенные графические режимы появившиеся буквально недавно:

  • 352*280 точек, 4 палитры по 256 цветов из16 мл. цветов, 2 страницы,

  • 704*280 точек 4 палитры по 16 цветов из 16 млн. 2 страницы.

Экспериментальный режим 368*288 точек, 4 палитры по 256 цветов из 16 мл. цветов, 1 страница - может быть полностью видимым только при подключении через скандаблеры или некоторые адаптеры SCART-HDMI.

Соотношение сторон монитора 5:4.

Поддерживаются разъемы IDE, FDD, присутствует шина ISA-8. Возможно подключение ISA-8 карт расширений. Для использования уже существующих карт расширений на базе шины ISA-8, будет достаточно написать драйвера. Оперативная память SIMM , установлен 4 mb оперативной SIMM памяти. Имеется поддержка клавиатуры PS/2 и com мыши. Compact Flash поддерживается через IDE переходник. Аудио ЦАП 16 бит.

Имеется аудио разъем. Недавно был сделан полноценный com разъем, теперь стало возможным организовать полноценную связь между компьютерами и подключить множество периферии по rs232.

Ведется разработка оборудования предназначенного для выхода в интернет. Ведется довольно активная разработка софта: обновлен файловый менеджер FlexNavigator по сведениям из телеграмма, написана масса дополнительных полезных утилит, создан редактор для разработки программ для zx spectrum. Разработку программного обеспечения и его тестирования можно вести прямо на спринтере, без использования ПК.

Спринтер можно оценивать как самобытный компьютер основанный на процессоре Zilog Z80/Z84 с шиной ISA-8, который в качестве дополнительной возможности, и в силу своей перепрограммируемой архитектуры основанной на применении PLD, может выступать как zx spectrum, с поддержкой таймингов пентагона и скорпиона.

У компьютера Sprinter собственная DOS подобная операционная система Estex DSS 1.60 с поддержкой FAT16 файловой системы, имеется собственный набор софта.

Интересные Факты:

  • Иван Макарченко является автором и разработчиком серии софт процессоров серии EQUINOX.

  • Иван Макарченко вынашивал идею использования в гипотетическом компьютере Sprinter II стекового софт-процессора, собственной разработки.

  • Под Спринтер существует минимум 3 Forth машины.

  • Спринтеров произвели около 115 штук.

  • По легенде начала 21 века Вячеслав Медноногов обещал поддержать софтом, если спринтеров реализуют 300 штук.

  • Иван Макарченко сделал программу управления конфигурациями компьютера Спринтер на языке Forth / Форт.

  • Пик популярности Спринтера пришёлся на 2002 год.

  • У Sprinter есть собственный маскот - Тхундер - герой игры Thunder in the deep.

  • Иван Макарченко увлекался физикой и писал фантастику.

Видео по Спринтеру

Иван Макарченко

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru