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

Плис

Перевод Уловка для обновления содержимого инициализации ОЗУ в битовых потоках ПЛИС 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 и доработайте. Пул-реквесты я принимать готов.

Заключение


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


Подробнее..

Новостной дайджест событий из мира 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

Из песочницы Генерация клока в ПЛИС на примитивах

05.07.2020 14:08:58 | Автор: admin
Читая даташиты на ПЛИС, можно находить таблички об их рабочих частотах

Хотя нет, история начинается еще с 2015 года, когда я познакомился с ПЛИС. В своих первых простеньких работах я формировал нужный мне клок из счетчика и запитывал от него всю логику(естественно при условии что клок мне нужен медленнее чем подавался на ПЛИС, например UART и SPI). Естественно за такое меня гоняли, но у меня была простая отмазка но ведь работает же!, и действительно все работало. С тех пор у меня в голове закралась мысль а откуда вообще можно взять тактирующий сигнал?.

Вариантов источников взять клок не много. Либо взять из некого ClockWizard основанный на PLL или MMCM, либо сформировать из счетчика, либо сразу с ножки так сказать single ended. А что, если взять тактовый сигнал сформированный примитивом ПЛИС?

В рамках этой статьи я решил рассмотреть три варианта: мультиплексор(MUXF7), таблица истинности(LUT1) и замкнуть ножки ПЛИС сами на себя.

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

image

В случае с LUT замыкаем выход на вход и задаем инвертирующую таблицу истинности. При подаче 1 выдавать ноль, а при 0 выдавать единицу.

image

В случае с GPIO там все просто, выходному сигналу присваиваем инверсию входного:
assign s2 = ~s1;

Цель эксперимента: сгенерировать частоту тремя способами и замерить ее.
Измерять частоту будем за счет счетчиков. Будет 4 счетчика: три на каждый вариант и один счетчик базовый, относительно которого все будет считаться. А смотреть эти счетчики будем через ChipScope.

А вот собственно весь код модуля:
module gen_clk(    input clk_base,    input s1, //gpio    output s2 //gpio    );//счетчик на входных-выходных контактахassign s2 = ~s1;wire clk_gpio = s1;reg [31:0] cnt_gpio = 0; (* MARK_DEBUG="true" *) reg [31:0] cnt_gpio_buf = 0;always@(posedge clk_gpio)begin     if(cnt_gpio[2:0]==3'd0) cnt_gpio_buf<=cnt_gpio;     cnt_gpio <= cnt_gpio + 1'b1;end//счетчик на мультиплексореwire clk_mux;MUXF7 MUXF7_inst(    .O(clk_mux),    .I0(1'b1),    .I1(1'b0),    .S(clk_mux));reg [31:0] cnt_mux = 0; (* MARK_DEBUG="true" *) reg [31:0] cnt_mux_buf = 0;always@(posedge clk_mux)begin     if(cnt_mux[2:0]==3'd0) cnt_mux_buf<=cnt_mux;     cnt_mux <= cnt_mux + 1'b1;end//счетчик на одном лутеwire clk_lut;LUT1#(    .INIT(2'b01))LUT1_inst(    .O(clk_lut),    .I0(clk_lut));reg [31:0] cnt_lut = 0; (* MARK_DEBUG="true" *) reg [31:0] cnt_lut_buf = 0;always@(posedge clk_lut)begin     if(cnt_lut[2:0]==3'd0) cnt_lut_buf<=cnt_lut;     cnt_lut <= cnt_lut + 1'b1;end//базовый счетчик относительно которого будем считать     (* MARK_DEBUG="true" *) reg [31:0] cnt_base = 'd0;        always@(posedge clk_base)begin    cnt_base <= cnt_base + 1'b1;end       endmodule


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

image


Практическая часть

В моем распоряжении есть три платы:

  1. KC705 Evaluation Kit

    image

  2. ML507 Evaluation Kit

    image

  3. Китайская плата Spartan-6 XC6SLX16

    image

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


И так теперь собственно результаты


Kintex-7:

Так как проект начинал делать под него, то и проект был написан не сразу целиком, а поэтапно. Сначала подключил один LUT добавил сигналы в отладку и стал смотреть.

Базовый счетчик тактируется на 200 МГц, поэтому посчитать частоту клока сгенерированного на луте не сложно, во сколько раз больше дельта счетчика лута дельты базового счетчика за одно и тоже время, во столько раз больше его частота. В данном случае: получается частота генерируемая лутом 381.55 МГц.

image

Теперь к проекту добавим мультиплексор, и по аналогии как с одним лутом посчитаем частоту для него, ну и для лута (должно ведь что-то поменяться).

image

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

  • Частота на мультиплексоре: 5953.89 МГц
  • Частота на луте(изменилась): 379.98 МГц

Ну и на конец добавим к проекту замкнутую петлю из пары GPIO. На плате KC705 есть SMA разъемы J13 и J14. Вот их то я и замкнул проводником длиной примерно 10 см. В результате:

  • Частота на GPIO: 90.59 МГц
  • Частота на мультиплексоре: 12994.13 МГц
  • Частота на луте: 380.18 МГц

Заменим, эксперимента ради, проводник на более длинный, у меня имеется провод в два раза длиннее. В итоге частота упала до 85.29 МГц.

На данном этапе эксперимента можно отметить, что частота работы примитивов в ПЛИС не одинаковая. В случае, когда был только один лут то синтезатор выбрал самый быстрый лут и строил вокруг него схему, затем когда добавился мультиплексор синтезатор попытался найти ту супер позицию где и лут и мультиплексор работают максимально быстро, а это уже другие элемента и частоты уже медленнее. Когда добавились внешние пины то весь проект на кристалле в принципе передислоцировался к этим ножкам и проект стал синтезироваться на близ лежащих элементах, по какой-то причине в том месте частоты лута и мультиплексора заметно выросли, но не стоит забывать что на фоне всего этого к проекту подключён ChipScope глубиной 1024 и шиной данных от 64 до 128(от проекта к проекту меняется). Теперь перейдем к следующей плате.

Virtex-5:

Я не стал проходить весь путь что прошел с предыдущей платой, сразу добавил все 3 варианта генерации клока и посмотрел в ChipScope что получилось.

image

На рисунке видны две метки Х и О. А так же их значения в столбцах, формат чисел беззнаковый десятичный. Стоит отметить, что базовый счетчик теперь считает на частоте 100 МГц. И так результат:

  • Частота на GPIO: 96.34 МГц
  • Частота на мультиплексоре: 614.41 МГц
  • Частота на луте: 5761.1 МГц

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

А теперь последний вариант с китайской платой.

Spartan-6:

В ChipScope появилось два базовых счетчика, на самом деле это один и тот же счетчик просто не хотел перенастраивать ChipScope. В данном проекте базовый счетчик тактируется на частоте 50 МГц.

image

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

  • Частота на GPIO: 51.77 МГц
  • Частота на мультиплексоре: 3 490 504 МГц
  • Частота на луте: не получилось собрать

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

Заключение

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

Встретившиеся проблемы:
В ходе эксперимента синтезаторы Vivado и ISE ругались на комбинаторные петли, и трудности развода сигналов. Эти трудности решаются добавлением парой строк в констрейн:

  • set_property ALLOW_COMBINATORIAL_LOOPS TRUE [get_nets -of_objects [get_cells gen_clk_inst/LUT1_inst]]
  • NET s1 CLOCK_DEDICATED_ROUTE = FALSE;

Подробнее..

Как прошла наша вторая конференция 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 комьюнити за поддержку. Без вас бы это мероприятие не состоялось.

Подробнее..

Разработка простейшего логического анализатора на базе комплекса Redd

16.06.2020 14:11:48 | Автор: admin
В прошлой статье цикла мы потренировались сохранять данные из потокового интерфейса в память средствами DMA. Пришла пора сделать какую-то полезную поделку, используя полученные навыки. Очень полезная при удалённой отладке вещь анализатор. Вообще, при работе с комплексом скорее нужны специализированные шинные анализаторы, но начинать лучше с чего-то попроще. Поэтому сейчас мы сделаем простейший логический анализатор на 32 канала. Понятно, что он будет совсем-совсем примитивным, но зато мы сделаем его своими руками. У кого ещё нет комплекса Redd, могут повторить опыт, используя любую макетную плату с ПЛИС фирмы Altera (Intel) и микросхемой ОЗУ. Итак, приступаем.



Предыдущие статьи цикла
  1. Разработка простейшей прошивки для ПЛИС, установленной в Redd, и отладка на примере теста памяти.
  2. Разработка простейшей прошивки для ПЛИС, установленной в Redd. Часть 2. Программный код.
  3. Разработка собственного ядра для встраивания в процессорную систему на базе ПЛИС.
  4. Разработка программ для центрального процессора Redd на примере доступа к ПЛИС.
  5. Первые опыты использования потокового протокола на примере связи ЦП и процессора в ПЛИС комплекса Redd.
  6. Веселая Квартусель, или как процессор докатился до такой жизни.
  7. Методы оптимизации кода для Redd. Часть 1: влияние кэша.
  8. Методы оптимизации кода для Redd. Часть 2: некэшируемая память и параллельная работа шин.
  9. Экстенсивная оптимизация кода: замена генератора тактовой частоты для повышения быстродействия системы.
  10. Доступ к шинам комплекса Redd, реализованным на контроллерах FTDI
  11. Работа с нестандартными шинами комплекса Redd
  12. Практика в работе с нестандартными шинами комплекса Redd
  13. Проброс USB-портов из Windows 10 для удалённой работы
  14. Использование процессорной системы Nios II без процессорного ядра Nios II
  15. Практическая работа с ПЛИС в комплекте Redd. Осваиваем DMA для шины Avalon-ST и коммутацию между шинами Avalon-MM



Определяем функциональность анализатора


На самом деле, самый-самый простейший анализатор мы сделали ещё в прошлый раз. Напомню, как выглядит шина AVALON_ST, скопировав рисунок из старой статьи:



То есть пробросили внешние линии на шину data, взвели сигнал valid, и началось запоминание по принципу отсюда и до обеда. Ну, то есть, пока память не закончится. Так работал мой осциллограф смешанных сигналов RIGOL, так работал логический анализатор HANTEK. Если для осциллографа смешанных сигналов по-другому нельзя, ведь аналоговый сигнал всё время изменяется, а он сохраняется вместе с цифрой, то для логического анализатора такой подход более, чем странен. Зачем сохранять данные без сжатия? В далёком 2007-м году добыл я китайский анализатор LA5034. Он был настолько китайским, что даже программа к нему сначала не имела английского интерфейса! Так вот, даже он уже не расходовал память на сохранение одних и тех же данных. Имея всего несколько килобайт ОЗУ (встроенного в ПЛИС), он позволял делать намного больше, чем дурацкий HANTEK с многомегабайтными микросхемами памяти.

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

Методика сжатия потока


Я выбрал самую простейшую методику сжатия. Линия задержки и компаратор прямого и задержанного на один такт сигнала.



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

И вот. Надо нам, скажем, перейти из состояния 0000 в зависимости от условий, или в 0001 или в 0110. И вот условие перехода изменилось очень близко к тактовому импульсу. Давайте я обозначу красными те биты, до которых данные успеют добежать, поэтому они примут новые значения для перехода в 0001, а синими те, до которых не успеют, и они примут значение для перехода в 0110. Итак: 0000.

В итоге, получаем состояние 0011. А на графе такого перехода не было! Кодирование методом OneHot не решит проблему, просто она станет очевидной (а так пока я отловил врага, пока понял, кто виноват 4 дня убил, ведь проявлялась беда очень редко, да и сначала я грешил на неверную реализацию логики).

Чтобы избежать этого, дискретизируем всё и вся! Что бы там ни защёлкнулось на входе, на выходе оно будет иметь стабильное состояние на протяжении такта. Поэтому на вход компаратора попадут уже стабильные данные!

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

У такой системы сжатия только один недостаток, но он делает её в таком виде совершенно неприемлемой. Мы не знаем, как долго держалось каждое стабильное состояние. Чтобы устранить данную проблему, добавим таймер. Если сейчас данные 32 бита, то имеет смысл добавить ещё 32-битный таймер, так как суммарная шина должна удваиваться, а после 32 идёт разрядность 64. Просто будем защёлкивать натикавшие показания. Зная значение таймера для прошлой и текущей записи, мы всегда поймём, как долго держалось прошлое значение. Правда, таймер имеет свойство переполняться. На частоте 100 МГц он переполнится через 42.9 секунды. Но ничто не мешает нам при нулевом значении таймера также произвести защёлкивание данных. Накладные расходы памяти будут не так велики, а программа догадается, что произошло переполнение и надо начать отмерять значения с начала. В итоге, получаем такую блок-схему:



Производительность анализатора


64-битная шина данных при 16-битной микросхеме SDRAM это не совсем хорошо. Допустим, мы тактируем ОЗУшку частотой 100 Мгц. Тогда, чисто теоретически, мы не можем использовать частоту дискретизации выше 25 МГц, ведь фактически каждое 64-битное слово будет уходить в ОЗУ в виде четырёх 16-битных слов. А практически, с поправкой на подачу команд микросхеме ОЗУ и циклы регенерации, предельная рабочая частота будет и того меньше. Может, даже 20 МГц.

Что на это можно сказать? Да, при разработке комплекса Redd не стояло задачи сделать супер производительный анализатор. Давайте взглянем на фирменные анализаторы, имеющиеся у меня под рукой. Вот простенький 16-битный. В нём стоит целых две ОЗУшины. Все ножки ПЛИС обслуживают каналы и ОЗУ. Ну, ещё на стык с USB уходят. А в Redd они ещё и для других целей используются.



Вот тот самый многострадальный абсолютно бесполезный HANTEK. Сжатия нет, но тоже две ОЗУшины. Причём, насколько я помню DDR. В своё время, неплохо его изучил: несколько лет назад хотел сделать прошивку со сжатием, даже выпросил у производителя UCF-файл, но так и не освоил работу с ОЗУ у Xilinx. Но с тех пор я мог подзабыть детали схемы.



А вот так изнутри выглядит туловище анализатора LeCroy через отверстие под установку головы:



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

В целом, если кому-то позарез нужна производительность, он может или приобрести макетную плату с 32-битной ОЗУ, или разработать свою, установив туда две 32-битные ОЗУшины. Или даже модули DIMM. Но это уже будет BGA ПЛИС, у неё будет уже другая цена, и всё (включая класс печатной платы) другое. А теория будет та же, что и сейчас, просто надо будет выкинуть преобразователь разрядности шины. Так что продолжаем рассуждения.

Вообще, на самом деле, и у нас всё не так плохо. Если данные идут небольшими пачками, то необходимо и достаточно установить блок FIFO. Пришла пачка она попала в очередь. Дальше на входе тишина, а данные из очереди постепенно уходят в ОЗУ. Таким образом, мгновенная производительность анализатора будет 100 МГц Но в целом всё будет хорошо при условии, что не переполняется FIFO. Именно поэтому я сделал целую статью, которая помогает оставить как можно больше памяти для нужд этого самого блока FIFO. Самое главное блок должен быть установлен там, где шина данных ещё 64-битная. Итого, получаем блок-схему анализатора:



Разработка головы анализатора


Ну что ж, приступаем к разработке головы. Я как-то привык к терминологии мощных шинных анализаторов, у которых имеется универсальное туловище, а уже к нему подключаются проблемно ориентированные головы. Поэтому и у нас будет туловище и голова. Для реализации выбранной схемы не нужно даже делать никаких автоматов.
Интерфейс модуля будет таким:
module AnalyzerHead (    input                   clk,    input                   reset,    input  logic            source_ready,    output logic            source_valid,    output logic[63:0]      source_data,    input logic [31:0]      channels);

Вот так мы реализуем процесс, который защёлкивает данные в регистрах и увеличивает счётчик:
    logic [31:0] counter = 0;    logic [31:0] channels_D1 = 0;    logic [31:0] channels_D2 = 0;    always @ (posedge clk, posedge reset)    if (reset == 1)    begin        counter <= 0;        channels_D1 <= 0;        channels_D2 <= 0;    end else    begin        channels_D1 <= channels;        channels_D2 <= channels_D1;        counter <= counter + 1;    end

Первое условие записи:
    logic valid1;  // Вариант срабатывания 1 - разные данные    assign valid1 = (channels_D1==channels_D2)?0:1;

Второе условие записи:
    logic valid2;    // Вариант срабатывания 2 - переполнение счётчика    assign valid2 = (counter == 0)?1:0;

Результирующее условие записи:
    // Сводим оба варианта воедино    // Если FIFO не готово - увы, данные пропадут    // В полноценном анализаторе надо зажигать аварию при этом    // тут - ну пропадут и пропадут...    assign source_valid = (valid1 | valid2) & source_ready;

Ну, и из опытов ясно, что байты на шине надо немного перекрутить:
 // Данные вот так вот вывернуты    assign source_data [63:56] = counter [7:0];    assign source_data [55:48] = counter [15:7];    assign source_data [47:40] = counter [23:16];    assign source_data [39:32] = counter [31:24];     assign source_data [31:24] = channels_D1 [7:0];    assign source_data [23:16] = channels_D1 [15:7];    assign source_data [15:8] = channels_D1 [23:16];    assign source_data [7:0] = channels_D1 [31:24];

Собственно, всё. Давайте для полноты картины я вставлю полный текст модуля в слитном варианте.
Полный текст модуля
module AnalyzerHead (    input                   clk,    input                   reset,    input  logic            source_ready,    output logic            source_valid,    output logic[63:0]      source_data,    input logic [31:0]      channels);    logic [31:0] counter = 0;    logic [31:0] channels_D1 = 0;    logic [31:0] channels_D2 = 0;    logic valid1;    logic valid2;    always @ (posedge clk, posedge reset)    if (reset == 1)    begin        counter <= 0;        channels_D1 <= 0;        channels_D2 <= 0;    end else    begin        channels_D1 <= channels;        channels_D2 <= channels_D1;        counter <= counter + 1;    end // Вариант срабатывания 1 - разные данные    assign valid1 = (channels_D1==channels_D2)?0:1; // Вариант срабатывания 2 - переполнение счётчика assign valid2 = (counter == 0)?1:0; // Сводим оба варианта воедино // Если FIFO не готово - увы, данные пропадут // В полноценном анализаторе надо зажигать аварию при этом // тут - ну пропадут и пропадут...    assign source_valid = (valid1 | valid2) & source_ready;  // Данные вот так вот вывернуты    assign source_data [63:56] = counter [7:0];    assign source_data [55:48] = counter [15:7];    assign source_data [47:40] = counter [23:16];    assign source_data [39:32] = counter [31:24];     assign source_data [31:24] = channels_D1 [7:0];    assign source_data [23:16] = channels_D1 [15:7];    assign source_data [15:8] = channels_D1 [23:16];    assign source_data [7:0] = channels_D1 [31:24]; endmodule


Упаковка головы в компонент для процессорной системы


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

У меня получилась шина AVALON_ST, штатные линии тактирования и сброса, и Но сначала рисунок с типовыми вещами:



Из нетиповых: для будущей задумки линии conduit пришлось дать осознанное имя типу сигнала. Оно нам ещё пригодится.



В остальном вроде, всё понятно.

Проектируем процессорную систему


Как мы уже рассматривали в этой статье, мы не станем добавлять в систему процессорное ядро Nios II, а воспользуемся блоком Altera JTAG-to-Avalon-MM.

Работать с контроллером SDRAM мы учились в этой статье, а в этой разбирались, как при помощи блока PLL разогнать систему до 100 Мгц. Экспериментировали с FIFO и изменением ширины шины AVALON_ST при помощи блока AVALON_ST_ADAPTER мы в этой статье. Наконец, с DMA мы экспериментировали буквально в прошлой статье.

Пришла пора собрать все эти знания в едином проекте! Вот такая у меня получилась навёрнутая структурная схема.



Страшно? Ничуть. Давайте пройдёмся по ней сверху вниз. Сначала идёт блок тактирования и сброса. Как всегда, для комплекса Redd, чтобы не мучиться, физическую ножку Reset я не использую (я её всегда виртуальной делаю). Так удобнее для данной конкретной аппаратуры, хоть и не совсем правильно. Тактирование же идёт на блок PLL. Как его настраивать, мы уже подробно рассматривали раньше. Если я вставлю сюда массу скриншотов, то сильно перегружу статью. С выхода c0 мы берём тактовый сигнал для всей нашей системы, а выход c1 экспортируем и подключаем к тактовому входу микросхемы SDRAM.

Master0 это тот самый компонент Altera JTAG-to-Avalon-MM, через который мы будем достукиваться до шины AVALON_MM. Он в настройках не нуждается. Доступ к шине нам нужен, чтобы управлять блоком DMA и чтобы считывать содержимое SDRAM с накопленными результатами.

Дальше идёт наш компонент Голова. А уже из неё растекается поток через цепочку шин AVALON_ST. Сначала он затекает в блок FIFO. Это первый блок, настройки которого стоит показать особо:



8 символов на слово, каждый символ 8 бит. Итого 8*8=64 бита. Ёмкость 4 килослова. Все остальные вещи протокола AVALON_ST отключены. Двойное тактирование сделано для того, чтобы в будущем голова могла работать на частоте, отличной от частоты работы туловища. Это нам пригодится, когда мы будем делать шинный анализатор USB.

Дальше данные перетекают в преобразователь разрядности. Вот его настройки:



Собственно, 8 символов на слово на входе, 4 символа на слово на выходе. 8 бит на символ. Тоже всё просто. Наконец, поток входит в блок DMA. Ему я только типы шин и максимальную длину передачи поправил, да выставил режим доступа только в режиме полного слова, чтобы поднять Fmax. По уму, такой огромный объём памяти дескрипторов не нужен (хотя, нутром чую, что через них мы можем реализовать кольцевой буфер для анализатора). Размер входного FIFO тоже можно уменьшить до минимума, ведь у нас есть FIFO до этого блока. Но, честно говоря, работа над статьёй и так уже затянулась, так что оставим эту оптимизацию для читателей в качестве самостоятельной работы.



Всё. Потоковая часть завершена. Дальше данные попадают в контроллер SDRAM. Напомню его настройки





Из функциональной части всё. Но кто следит за рассказом не по диагонали, а внимательно, наверное, заметил ещё один странный блок DataGen_0. Что это такое? Мы раньше такого не применяли!

Дело в том, что мне же как-то надо проверить работу головы. А это надо все 32 линии назначить на какие-то ножки ПЛИС, подключить к ним какой-то источник А все должны будут поверить мне на слово, что я это сделал. И потом думать, как это повторить у себя. Зачем? Давайте добавим тестовый генератор данных и подключим его не проводами, а через трассировочные ресурсы ПЛИС. Я сделал самый простой счётчик, который увеличивает своё значение в случайные моменты времени. В качестве генератора случайных чисел я взял 32-разрядную M-последовательность, а увеличиваю счётчик, когда в младших восьми битах появляется константа 0x12. Вот такой получился SystemVerilog код, реализующий эту функциональность (обратите внимание, что я по-прежнему не использую сигнал reset, хотя, здесь бы он пригодился):
module DataGen(   input clk,   output logic [31:0] data = 0);// Генератор случайных чиселlogic [31:0]shift_reg = 0;logic next_bit;assign next_bit = shift_reg[31] ^ shift_reg[30]   ^ shift_reg[29] ^ shift_reg[27] ^ shift_reg[25]   ^ shift_reg[ 0];always @(posedge clk)  if(shift_reg == 0)    shift_reg <= 32'h12345678;  else    shift_reg <= { next_bit, shift_reg[31:1] };// Целевой счётчикalways @(posedge clk)begin    if (shift_reg [7:0] == 8'h12)        data <= data + 1;endendmodule

В настройках компонента самая важная деталь это имя параметра Signal Type у conduit шины. Он должен быть таким же, какой я заполнил у соответствующего параметра головы. В остальном всё просто, здесь же нет никаких специальных шин, только conduit



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

Финал работ


Делаем линию reset виртуальной. Всем остальным ножкам я предпочёл сделать назначение не в GUI, а скопировал фрагмент файла *.qsf из проекта, сделанного в самой первой статье.
Вот этот фрагмент:
set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to clk_clkset_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_addr[12]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_addr[11]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_addr[10]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_addr[9]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_addr[8]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_addr[7]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_addr[6]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_addr[5]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_addr[4]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_addr[3]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_addr[2]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_addr[1]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_addr[0]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_ba[1]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_ba[0]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_cas_nset_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_clk_clkset_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_cs_nset_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_dq[15]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_dq[14]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_dq[13]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_dq[12]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_dq[11]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_dq[10]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_dq[9]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_dq[8]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_dq[7]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_dq[6]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_dq[5]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_dq[4]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_dq[3]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_dq[2]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_dq[1]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_dq[0]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_dqm[1]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_dqm[0]set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_ras_nset_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to sdram_we_nset_location_assignment PIN_64 -to sdram_addr[12]set_location_assignment PIN_60 -to sdram_addr[11]set_location_assignment PIN_44 -to sdram_addr[10]set_location_assignment PIN_59 -to sdram_addr[9]set_location_assignment PIN_58 -to sdram_addr[8]set_location_assignment PIN_55 -to sdram_addr[7]set_location_assignment PIN_54 -to sdram_addr[6]set_location_assignment PIN_53 -to sdram_addr[5]set_location_assignment PIN_52 -to sdram_addr[4]set_location_assignment PIN_51 -to sdram_addr[3]set_location_assignment PIN_50 -to sdram_addr[2]set_location_assignment PIN_49 -to sdram_addr[1]set_location_assignment PIN_46 -to sdram_addr[0]set_location_assignment PIN_73 -to sdram_dq[15]set_location_assignment PIN_72 -to sdram_dq[14]set_location_assignment PIN_71 -to sdram_dq[13]set_location_assignment PIN_70 -to sdram_dq[12]set_location_assignment PIN_69 -to sdram_dq[11]set_location_assignment PIN_68 -to sdram_dq[10]set_location_assignment PIN_67 -to sdram_dq[9]set_location_assignment PIN_66 -to sdram_dq[8]set_location_assignment PIN_30 -to sdram_dq[7]set_location_assignment PIN_28 -to sdram_dq[6]set_location_assignment PIN_11 -to sdram_dq[5]set_location_assignment PIN_10 -to sdram_dq[4]set_location_assignment PIN_7 -to sdram_dq[3]set_location_assignment PIN_3 -to sdram_dq[2]set_location_assignment PIN_2 -to sdram_dq[1]set_location_assignment PIN_1 -to sdram_dq[0]set_location_assignment PIN_65 -to sdram_dqm[1]set_location_assignment PIN_31 -to sdram_dqm[0]set_location_assignment PIN_34 -to sdram_ras_nset_location_assignment PIN_32 -to sdram_we_nset_location_assignment PIN_42 -to sdram_cs_nset_location_assignment PIN_33 -to sdram_cas_nset_location_assignment PIN_38 -to sdram_ba[0]set_location_assignment PIN_39 -to sdram_ba[1]set_location_assignment PIN_25 -to clk_clkset_location_assignment PIN_43 -to sdram_clk_clkset_instance_assignment -name VIRTUAL_PIN ON -to sdram_cke


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

Разработка логического анализатора на базе Redd проверяем его работу на практике

30.06.2020 18:10:31 | Автор: admin
В прошлой статье мы сделали аппаратуру, реализующую логический анализатор на базе комплекса Redd. Статья разрослась так, что рассмотрение программной поддержки мы отложили на потом. Пришла пора разобраться с тем, как мы будем получать и отображать данные, которые анализатор копит в ОЗУ.



Предыдущие статьи цикла
  1. Разработка простейшей прошивки для ПЛИС, установленной в Redd, и отладка на примере теста памяти.
  2. Разработка простейшей прошивки для ПЛИС, установленной в Redd. Часть 2. Программный код.
  3. Разработка собственного ядра для встраивания в процессорную систему на базе ПЛИС.
  4. Разработка программ для центрального процессора Redd на примере доступа к ПЛИС.
  5. Первые опыты использования потокового протокола на примере связи ЦП и процессора в ПЛИС комплекса Redd.
  6. Веселая Квартусель, или как процессор докатился до такой жизни.
  7. Методы оптимизации кода для Redd. Часть 1: влияние кэша.
  8. Методы оптимизации кода для Redd. Часть 2: некэшируемая память и параллельная работа шин.
  9. Экстенсивная оптимизация кода: замена генератора тактовой частоты для повышения быстродействия системы.
  10. Доступ к шинам комплекса Redd, реализованным на контроллерах FTDI
  11. Работа с нестандартными шинами комплекса Redd
  12. Практика в работе с нестандартными шинами комплекса Redd
  13. Проброс USB-портов из Windows 10 для удалённой работы
  14. Использование процессорной системы Nios II без процессорного ядра Nios II
  15. Практическая работа с ПЛИС в комплекте Redd. Осваиваем DMA для шины Avalon-ST и коммутацию между шинами Avalon-MM
  16. Разработка простейшего логического анализатора на базе комплекса Redd


Сегодня нам очень понадобится опыт, который мы получили в в одной из недавних статей. Там я говорил, что в идеале было бы полезно выучить язык Tcl, но в целом, можно программировать на дикой смеси высокоуровневой логики, описанной на C++ и низкоуровневых запросах на Tcl. Если бы я вёл разработку для себя, то так бы и сделал. Но когда пишешь статью, приходится стараться умять всё в как можно меньшее количество файлов. Одна из задуманных статей так никогда и не была написана именно по этой причине. Мы договорились с коллегой, что он сделает код в рамках проекта, а я потом опишу его. Но он построил код так, как это принято в жизни раскидал его по огромному количеству файлов. Потом код оброс бешеным количеством технологических проверок, которые нужны в жизни, но за которыми не видно сути. И как мне было описывать всё это? Посмотрите налево, посмотрите направо Здесь играй, здесь не играй, а здесь коллега рыбу заворачивал? В итоге, полезный материал не пошёл в публикацию (правда, статьи по той тематике всё равно не набирали рейтинга, так что полезный-то он полезный, но мало кому интересный).

Вывод из всего этого прост. Чтобы в статье не бегать от модуля к модулю, пришлось разобраться, как сделать на чистом Tcl. И знаете, не такой это оказался страшный язык. Вообще, всегда можно перейти в каталог C:\intelFPGA_lite и задать поиск интересующих слов по файлам *.tcl. Решения почти всегда найдутся. Так что он мне нравится всё больше и больше. Ещё и ещё раз советую приглядеться к этому языку.

Я уже говорил, что при запуске Квартусового Tcl скрипта под Windows и под Linux, тексты должны немного различаться в районе инициализации. Запуск под Linux непосредственно на центральном процессоре Reddа должен давать большее быстродействие. Но зато запуск под Windows во время опытов более удобен для меня, так как я могу редактировать файлы в удобной для себя среде. Поэтому все дальнейшие файлы я писал под запуск из System Console в Windows. Как их переделать в Линуксовый вариант, мы разбирались в той самой статье по ссылке выше.

Обычно я даю фрагменты кода с пояснениями, а затем уже справочно полный код. Но это хорошо, когда во фрагментах каждый видит что-то знакомое. Так как для многих, кто читает эти строки, язык Tcl считается экзотикой, сначала я приведу для справки полный текст своего первого пробного скрипта.
Полный текст находится здесь.
variable DMA_BASE 0x2000000variable DMA_DESCR_BASE 0x2000020# Чтение регистра блока DMA.#proc dma_reg_read { address } {  variable DMA_BASE  variable m_path  set address [expr {$address * 4 + $DMA_BASE}]  return [master_read_32 $m_path $address 1]}# Запись регистра блока DMAproc dma_reg_write { address data } {  variable DMA_BASE  variable m_path  set address [expr {$address * 4 + $DMA_BASE}]  master_write_32 $m_path $address $data}# Запись регистра дескрипторов блока DMAproc dma_descr_reg_write { address data } {  variable DMA_DESCR_BASE  variable m_path  set address [expr {$address * 4 + $DMA_DESCR_BASE}]  master_write_32 $m_path $address $data}proc prepare_dma {sdram_addr sdram_size} {# Остановили процесс, чтобы всё понастраивать# Да, мне лень описывать все константы, # я делаю всё на скорую рукуdma_reg_write 1 0x20# На самом деле, тут должно быть ожидание фактической остановки,# но в рамках теста, оно не нужно. Точно остановимся.# Добавляем дескриптор в FIFO# Адрес источника (вообще, это AVALON_ST, но я всё# с примеров списывал, а там он зануляется)dma_descr_reg_write 0 0# Адрес приёмника. dma_descr_reg_write 1 $sdram_addr# Длинаdma_descr_reg_write 2 $sdram_size# Управляющий регистр (взводим бит GO)dma_descr_reg_write 3 0x80000000# Запустили процесс, не забыв отключить прерыванияdma_reg_write 1 4}puts "running"set m_path [lindex [get_service_paths master] 0]open_service master $m_pathprepare_dma 0 0x100puts [master_read_32 $m_path 0 16]



Разбираем фрагменты скрипта


При работе со скриптом нам надо знать базовые адреса блоков. Обычно я их беру из заголовочных файлов, сделанных для BSP, но сегодня мы не создали никаких проектов. Однако адреса всегда можно посмотреть в Platform Designer хоть на привычной нам структурной схеме (я покажу только пример одного адреса):



Хоть там же на специальной вкладке, где всё собрано в виде таблиц:



Нужные мне адреса я вписал в начало скрипта (а нулевой начальный адрес буферного ОЗУ я буду подразумевать всегда, чтобы облегчить код):
variable DMA_BASE 0x2000000variable DMA_DESCR_BASE 0x2000020


Функции доступа к DMA в позапрошлой статье я специально написал в простейшем виде. Сегодня же я просто взял и аккуратно перенёс этот код из C++ в Tcl. Эти функции требуют доступа к аппаратуре, а именно чтения и записи порта самого блока DMA и функция записи порта блока дескрипторов DMA. Читать дескрипторы пока не нужно. Но понадобится допишем по аналогии. Мы уже тренировались работать с аппаратурой здесь, поэтому делаем такие функции:
# Чтение регистра блока DMA.#proc dma_reg_read { address } {  variable DMA_BASE  variable m_path  set address [expr {$address * 4 + $DMA_BASE}]  return [master_read_32 $m_path $address 1]}# Запись регистра блока DMAproc dma_reg_write { address data } {  variable DMA_BASE  variable m_path  set address [expr {$address * 4 + $DMA_BASE}]  master_write_32 $m_path $address $data}# Запись регистра дескрипторов блока DMAproc dma_descr_reg_write { address data } {  variable DMA_DESCR_BASE  variable m_path  set address [expr {$address * 4 + $DMA_DESCR_BASE}]  master_write_32 $m_path $address $data}

Теперь уже можно реализовывать логику работы с DMA. Ещё и ещё раз говорю, что я специально в позапрошлой статье не обращался к готовому API, а сделал как можно более простые собственные функции. Я знал, что мне придётся эти функции портировать. И вот так я портировал инициализацию DMA (аргументы начальный адрес и длина в байтах для буфера, в который будет идти приём):
proc prepare_dma {sdram_addr sdram_size} {# Остановили процесс, чтобы всё понастраивать# Да, мне лень описывать все константы, # я делаю всё на скорую рукуdma_reg_write 1 0x20# На самом деле, тут должно быть ожидание фактической остановки,# но в рамках теста, оно не нужно. Точно остановимся.# Добавляем дескриптор в FIFO# Адрес источника (вообще, это AVALON_ST, но я всё# с примеров списывал, а там он зануляется)dma_descr_reg_write 0 0# Адрес приёмника. dma_descr_reg_write 1 $sdram_addr# Длинаdma_descr_reg_write 2 $sdram_size# Управляющий регистр (взводим бит GO)dma_descr_reg_write 3 0x80000000# Запустили процесс, не забыв отключить прерыванияdma_reg_write 1 4}

Ну, собственно, всё. Дальше идёт основное тело скрипта. Какое я там сделал допущение? Я не жду окончания работы DMA. Я же знаю, что таймер тикает весьма быстро. Поэтому запрошенные мною 0x100 байт заполнятся весьма шустро. И все мы знаем, что JTAG работает очень неспешно. В реальной жизни, разумеется, надо будет добавить ожидание готовности. А может и отображение текущего адреса. И ещё чего-нибудь И тогда код станет точно непригодным для статьи, из-за того, что все эти мелочи станут закрывать суть. А пока простейший код выглядит так:
puts "running"set m_path [lindex [get_service_paths master] 0]open_service master $m_pathprepare_dma 0 0x100puts [master_read_32 $m_path 0 16]

То есть, настроились на работу с аппаратурой, приняли 0x100 байт в буфер с адресом 0, отобразили первые 16 байт.

Заливаем прошивку, запускаем System Console, исполняем скрипт, наблюдаем такую красоту:



Таймер 0, счётчик 0. Таймер 0x18, счётчик 1. Таймер 0x5EB, счётчик 2. Ну, и так далее. В целом, мы видим то, что хотели. На этом можно было бы и закончить, но текстовое отображение не всегда удобно. Поэтому продолжаем.

Как отобразить результаты в графическом виде


То, что мы получили это вполне себе замечательно, но временные диаграммы обычного логического анализатора часто удобнее смотреть в графическом виде! Надо бы написать программу, чтобы можно было это делать Вообще, написать свою программу всегда полезно, но время ресурс ограниченный. А в рамках работы с комплексом Redd, мы придерживаемся подхода, что все эти работы вспомогательные. Мы должны тратить время на основной проект, а на вспомогательный нам никто его не выделит. Можно этим заняться в свободное от работы время, но лично мне это не даёт делать балалайка. Она тоже много времени отнимает. Так что свободное время лично у меня тоже занято. К счастью, уже существуют решения, которые могут облегчить нам жизнь. Я узнал о нём, когда читал статьи про Icarus Verilog. Там строят графическую визуализацию времянок через некие файлы VCD. Что это такое?

В стандарте Verilog (я нашёл его здесь ) смотрим там раздел 18. Value change dump (VCD) files. Вот так! Оказывается, можно заставить тестовую систему на языке Verilog генерировать подобные файлы. И они будут стандартизированы, то есть, едины, независимо от среды, в которой производится моделирование. А где стандарт, там и единство логики систем отображения. А что, если мы тоже будем формировать такой же файл на основании того, что пришло из анализатора? Причём заставим Tcl скрипт делать это Осталась самая малость: выяснить, что в файл следует записать, а также как это сделать.

Радостный, я попросил Гугля показать мне пример vcd-файла. Вот, что он мне дал. Я выделил всё, что там было в таблице и сохранил в файл tryme.vcd. Если к моменту чтения статьи страничка перестанет существовать, вот содержимое этого файла ниже.
Содержимое файла tryme.vcd
$commentFile created using the following command:vcd files output.vcd $dateFri Jan 12 09:07:17 2000$end$versionModelSim EE/PLUS 5.4$end$timescale1ns$end$scope module shifter_mod $end$var wire 1 ! clk $end$var wire 1 " reset $end$var wire 1 # data_in $end$var wire 1 $ q [8] $end$var wire 1 % q [7] $end$var wire 1 & q [6] $end$var wire 1 ' q [5] $end$var wire 1 ( q [4] $end$var wire 1 ) q [3] $end$var wire 1 * q [2] $end$var wire 1 + q [1] $end$var wire 1 , q [0] $end$upscope $end$enddefinitions $end#0$dumpvars0!1"0#0$0%0& 0'0(0)0*0+0,$end#1001!#1500!#2001!$dumpoffx!x"x#x$x%x&x'x(x)x*x+x,$end#300$dumpon1!0"1#0$0% 0&0'0(0)0*0+1,$end#3500!#4001!1+#4500!#5001!1*#5500!#6001!1)#6500!#7001!1(#7500!#8001!1'#8500!#9001!1&#9500! #10001!1%#10500!#11001!1$#11500!1"0$0%0&0'0(0)0*0+0,#12001!$dumpall1!1"1#0$0%0&0'0(0)0*0+0,$end


Какое-то Юстас Алексу. Можно, конечно, разобраться, глядя в стандарт, но больно там много всего. Попробуем всё сделать на практике, загрузив файл в какую-нибудь среду, визуализирующую его содержимое, и сопоставив текст с полученной картинкой. Несмотря на то, что заголовок web-страницы гласит, что всё это пример от среды ModelSim, мне не удалось открыть данный файл в этой среде. Возможно, в комментариях кто-то подскажет, как это сделать. Однако не МоделСимом единым жив человек. Существует такая кроссплатформенная система с открытым исходным кодом GtkWave. Сборку под Windows я взял тут . Запускаем её, выбираем в меню File->Open New Tab:



Указываем наш файл и получаем такую картинку:



Выделяем все сигналы и через меню правой кнопки Мыши выбираем Recurse Import->Append:



И вот результат:



Видя такое дело, вполне можно разобраться, что в файле зачем добавлено.
$timescale1ns$end

Ну, тут всё понятно. Это в каких единицах время будет задаваться.
$scope module shifter_mod $end$var wire 1 ! clk $end$var wire 1 " reset $end$var wire 1 # data_in $end$var wire 1 $ q [8] $end$var wire 1 % q [7] $end$var wire 1 & q [6] $end

Это объявляются переменные с весьма экзотическими именами Восклицательный знак, Кавычки, решётка и т.п. Шина в найденном примере объявляется побитово. Идём дальше:
#0$dumpvars0!1"0#0+0,$end

Время равно нулю. Дальше значения переменных. Зачем ключевое слово $dumpvars? Придётся заглянуть в стандарт. Как я и думал, какое-то непонятное занудство. Но создаётся впечатление, что нам сообщают, что эти данные получены при помощи директивы языка $dumpvars. Давайте попробуем убрать это слово и соответствующую ему строку $end. Загружаем обновлённый файл и видим результат:



Как говорится, найдите десять отличий Никакой разницы. Значит, нам это добавлять на выход не нужно. Идём дальше.
#1001!#1500!

Мы видим, что в моменты 100 и 150 нс тактовый сигнал переключился, а остальные нет. Поэтому мы можем добавлять только изменившиеся значения сигналов. Идём дальше.
#2001!$dumpoffx!x"x,$end#300$dumpon1!0"

Теперь мы умеем задавать состояние X. Проверяем, нужны ли ключевые слова $dumpoff и $dumpon, выкинув их из файла (не забываем про парные им $end)

Добавлять ещё один рисунок, полностью идентичный предыдущим, я не буду. Но можете проверить это же у себя.

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


То же самое текстом:
vcd_declaration_vars ::=$var var_type size identifier_code reference $endvar_type ::=event | integer | parameter | real | realtime | reg | supply0 | supply1 | time| tri | triand | trior | trireg | tri0 | tri1 | wand | wire | worsize ::=decimal_numberreference ::=identifier| identifier [ bit_select_index ]| identifier [ msb_index : lsb_index ]index ::=decimal_number


Прекрасно! Мы можем задавать и вектора! Пробуем добавить одну переменную, дав ей имя звёздная собака, так как односимвольные варианты уже израсходованы авторами оригинального примера:
$var reg 32 *@ test [31:0] $end

И добавим ей таких присвоений Обратите внимание на пробелы перед именем переменной! Без них не работает, но и стандарт требует их наличия в отличие от однобитных вариантов:
#0b00000001001000110100010101100111 *@#100b00000001XXXX0011010001010110zzzz *@

Смотрим результат (я в нём ради интереса раскрыл вектор)



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

Делаем Tcl-скрипт, создающий файл VCD


Если весь предыдущий опыт я брал из файлов *.tcl, идущих в комплекте с Квартусом, то здесь всё плохо. Слово file есть в любом из них. Пришлось спросить у Гугля. Он выдал ссылку на замечательный справочник.

Функции не трогаем, а основное тело скрипта переписываем так.
Основное тело скрипта.
puts "running"set m_path [lindex [get_service_paths master] 0]open_service master $m_pathprepare_dma 0 0x100set fileid [open "ShowMe.vcd" w]puts $fileid "\$timescale"puts $fileid "1ns"puts $fileid "\$end"puts $fileid "\$scope module megaAnalyzer \$end"puts $fileid "\$var reg 32 ! data \[31:0] \$end"puts $fileid "\$upscope \$end"puts $fileid "\$enddefinitions \$end"# А зачем рисовать где-то справа?# Будем рисовать от первой точки# Для этого запомним базуset startTime [lindex [master_read_32 $m_path 0 1] 0]for { set i 0}  {$i < 20} {incr i} {   set cnt [lindex [master_read_32 $m_path [expr {$i * 8}]  1] 0]   set data [lindex [master_read_32 $m_path [expr {$i * 8 + 4}]  1] 0]   # Таймер тикает с частотой 100 МГц   # Один тик таймера - это 10 нс   # поэтому на 10 и умножаем   puts $fileid "#[expr {($cnt - $startTime)* 10}]"   puts $fileid "b[format %b $data] !"}close $fileid


Здесь я не стал учитывать перескоки счётчика через ноль. Перед нами простейший демонстрационный скрипт, так что не будем его перегружать. Запускаем Дааааа. Это именно то, о чём я предупреждал. Оно работает весьма и весьма долго. Сколько оно будет работать в случае заполненных мегабайтных буферов страшно даже предположить. Но зато мы не потратили много времени на разработку! Опять же, кто работал с анализатором BusDoctor, тот не даст соврать: этот фирменный анализатор отдаёт данные больших объёмов тоже очень и очень неспешно.
Итак, получаем файл:
$timescale1ns$end$scope module megaAnalyzer $end$var reg 32 ! data [31:0] $end$upscope $end$enddefinitions $end#0b0 !#240b1 !#15150b10 !#25170b11 !#26510b100 !#26690b101 !#31180b110 !#31830b111 !#35540b1000 !#35630b1001 !#36940b1010 !#39890b1011 !#46130b1100 !#55540b1101 !#60820b1110 !#71270b1111 !#71480b10000 !#76270b10001 !#77990b10010 !#91000b10011 !


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



Счётчик щёлкает! В случайные моменты времени. Что хотели, то и получили

Заключение


Мы сделали простейший логический анализатор, потренировались принимать данные с него и производить их визуализацию. Чтобы довести этот простейший пример до совершенства, разумеется, придётся ещё потрудиться. Но путь, по которому идти, понятен. Мы получили опыт изготовления реальных измерительных устройств на базе ПЛИС, установленной в комплексе Redd. Теперь каждый сможет доработать эту основу так, как сочтёт нужным (и как ему позволит время), а в следующей серии статей я начну рассказ о том, как заменить этому анализатору голову, чтобы сделать шинный анализатор для USB 2.0.

Те, у кого нет настоящего Redd, смогут воспользоваться вот такой свободно доставаемой платой, которая имеется у массы продавцов на Ali Express.


Искать следует по запросу WaveShare ULPI. Подробнее о ней на странице производителя.
Подробнее..

Моделируем поведение Quartus-проекта на Verilog в среде ModelSim

29.07.2020 16:12:53 | Автор: admin
В прошлой статье мы сделали достаточно сложный модуль. Разумеется, я вставил в тело статьи уже отлаженный результат. Но мне показалось, что достаточно странно, когда автор говорит делай, как я, но при этом не показывает очень важного процесса. Давайте я покажу, как вообще проводится отладка системы путём моделирования. Причём в следующей статье будут содержаться сведения, которые ещё неделю назад не знал даже я. Но, чтобы перейти к ним, надо разобраться с базовыми принципами. Итак. Давайте рассмотрим, как быстро подготовить и не менее быстро запустить процесс моделирования в среде ModelSim.



Предыдущие статьи цикла
  1. Разработка простейшей прошивки для ПЛИС, установленной в Redd, и отладка на примере теста памяти
  2. Разработка простейшей прошивки для ПЛИС, установленной в Redd. Часть 2. Программный код
  3. Разработка собственного ядра для встраивания в процессорную систему на базе ПЛИС
  4. Разработка программ для центрального процессора Redd на примере доступа к ПЛИС
  5. Первые опыты использования потокового протокола на примере связи ЦП и процессора в ПЛИС комплекса Redd
  6. Веселая Квартусель, или как процессор докатился до такой жизни
  7. Методы оптимизации кода для Redd. Часть 1: влияние кэша
  8. Методы оптимизации кода для Redd. Часть 2: некэшируемая память и параллельная работа шин
  9. Экстенсивная оптимизация кода: замена генератора тактовой частоты для повышения быстродействия системы
  10. Доступ к шинам комплекса Redd, реализованным на контроллерах FTDI
  11. Работа с нестандартными шинами комплекса Redd
  12. Практика в работе с нестандартными шинами комплекса Redd
  13. Проброс USB-портов из Windows 10 для удалённой работы
  14. Использование процессорной системы Nios II без процессорного ядра Nios II
  15. Практическая работа с ПЛИС в комплекте Redd. Осваиваем DMA для шины Avalon-ST и коммутацию между шинами Avalon-MM
  16. Разработка простейшего логического анализатора на базе комплекса Redd
  17. Разработка логического анализатора на базе Redd проверяем его работу на практике
  18. Делаем голову шинного USB-анализатора на базе комплекса Redd


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

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

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

И здесь возникает вопрос о внешней среде. Как сымитировать её? Нам на помощь приходят модели. На языке Verilog (как и VHDL, и других похожих) вполне можно описать поведение чего угодно. Делаем мы систему, которая работает с микросхемой ULPI Значит, чтобы проверить её работу, на том конце должно быть что-то, что ведёт себя именно, как ULPI. То есть, модель ULPI. Но этого мало. Наш блок реагирует на команды от шины ALAVON_MM. Именно эта шина заставляет блок жить. Поэтому надо ещё добавить модель шины AVALON_MM, причём эта модель должна быть активной. Именно она будет подавать тестовые воздействия.



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

Цель сегодняшней статьи не рассказать о том, что такое моделирование вообще (это долгая история), а показать, как это моделирование провести быстрее всего. И рассмотрим мы это не на боевой задаче, а на простом примере. Сделаем совсем простенькую тестовую систему, чтобы в следующей статье уже понимать, откуда растут ноги у более сложного её варианта, ведь при чтении удобнее не сидеть и недоумевать: Зачем он это делает?, а знать все базовые принципы, из которых уже вытекают усложнения. Кстати, недавно выяснилось, что один мой знакомый хоть и владеет мастерством моделирования, но не знал, что в среду Quartus встроены механизмы, которые позволяют делать это легко и непринуждённо. Он тратил на это намного больше усилий, чем требуется. Так что может, кто-то тоже сейчас узнает для себя что-то новое о возможностях, заложенных в Quartus. Итак, приступаем.

Создание простейшей модели на языке Verilog


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

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

Давайте ради интереса прицепим к нашему ULPI-проекту вот такой забавный модуль на SystemVerilog, написанный мной специально для иллюстрации и не имеющий никакого отношения к разрабатываемому анализатору. Просто некоторое время назад довелось много возиться с вычислением контрольных сумм, вот он в голову и пришёл.
module sum(input         clk,input [7:0]   data,input         we,input         sof,output [15:0] sum);logic [15:0] temp;always @ (posedge clk)begin     if (we)      begin         if (sof)             temp <= data;         else             temp <= temp + data;     endend// В идеале - так//assign sum = (~temp)+1;// Но контролировать проще так:assign sum = temp;endmodule

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

Положим получившийся файл в каталог с нашим проектом, но не станем включать его в проект в Quartus. Вместо этого создадим тестовый набор специально под него. Для этого выбираем пункт меню Assignments>Settings:



и в появившемся дереве ищем пункт EDA Tools Settings>Simulation:



Кстати, о типе моделирования, выделенном зелёной рамкой. Возможно, кто-то помнит, в первых статьях я говорил, что при создании проекта чисто по привычке выбираю ModelSim Altera? Это было то самое ружьё на сцене, которое рано или поздно должно было выстрелить. Но если при создании проекта тип моделирования не был выбран, его можно выбрать или изменить здесь.

Продолжаем создавать тестовый набор. Переключаем радиокнопку на Compile test bench (кстати, а как этот термин красиво переводится на русский? Я не могу заставить себя писать тестовый стенд, так как не вижу никакого стенда) и нажимаем кнопку Test Benches:



В открывшемся диалоге нажимаем New:



Если делать тестовый набор вручную, то можно заполнить поля за один проход. Но так как мы делаем всё при помощи мышки, то сейчас заполняем только часть полей, а остальные дозаполним позже. В поле Test bench name я вбил слово Parazit (а как ещё назвать тест, который просто паразитирует на проекте?). Слово Parazit под ним заполнилось автоматически. Сейчас мы не будем его менять, но в будущем нам ещё предстоит это сделать. Также при помощи кнопки ... я выбрал файл sum.sv с кодом отлаживаемого сумматора, после чего, при помощи кнопки Add, затолкнул его в список файлов теста. Пока всё. Закрываем диалог



Дальше мы продолжим формирование теста в среде ModelSim. Для этого выбираем пункт меню Tools>Run Simulation Tools>RTL Simulation:



Открывается окно ModelSim. Возможно, будут найдены ошибки в коде Verilog, тогда надо закрывать ModelSim, править ошибки, открывать вновь. Но рано или поздно, перечень ошибок станет чисто организационным. У меня он выглядит так:



Не найдено модуля верхнего уровня. Это нормально. Мы его ещё не создали просто. Поэтому идём в перечне библиотек к work и раскрываем её. Вот он, наш сумматор.



Наводимся на него, нажимаем правую кнопку Мыши и выбираем пункт меню Create Wave. Это в тексте всё так занудно, если бы я снимал видео, весь процесс занимал бы десятки секунд, так что не пугайтесь, а следите за руками дальше. Итак, Create Wave



Интерфейсные сигналы модуля автоматически переехали на график:



Надо назначить значение какого-нибудь из них. Не важно какого, важно назначить. Очень старая среда моделирования Квартуса умела красиво генерить тактовые сигналы. Увы, её давно изъяли из поставки, так как стали прилагать ModelSim, а тут с подобным всё не так красиво. Проку в формировании генератора здесь, я не увидел, поэтому даже показывать не буду. Так что Ну, давайте линию we нулю присвоим. Наводимся на сигнал, нажимаем правую кнопку, выбираем пункт меню Edit>Wave Editor>Create/Modify WaveForm.



В появившемся диалоге выбираем Constant. И время заодно поменяем, скажем, на 100 микросекунд:



Далее указываем значение 0:



Всё, минимально необходимый набор данных мы создали, а остальное проще будет ручками сделать. Экспортируем файл. Для этого выбираем пункт меню File>Export>Waveform:



Выбираем тип файла Verilog Testbench (кстати, очень жаль, что не SystemVerilog, но в будущем можно будет поправить и ручками). Также задаём имя файла. Я назвал его parazit_tb, по принципу а почему бы и нет?.



Всё, ModelSim можно закрывать, времянку при этом сохранять не нужно.

Что делать с моделью дальше


Вот такой кривоватый, но всё-таки готовый Верилоговский файл нам создала система:
`timescale 1ns / 1nsmodule parazit_tb  ;    reg    sof   ;   reg    we   ;   wire  [15:0]  sum   ;   reg  [7:0]  data   ;   reg    clk   ;   sum     DUT  (        .sof (sof ) ,      .we (we ) ,      .sum (sum ) ,      .data (data ) ,      .clk (clk ) ); // "Constant Pattern"// Start Time = 0 ns, End Time = 100 us, Period = 0 ns  initial  begin  end  initial#0 $stop;endmodule

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

Как видим, толку от задания констант, сделанного автогенератором никакого. Но всё-таки, созданы все цепи, подключён модуль, подлежащий тестированию, даже секция initial создана. Давайте облагородим код. Первое выкинем точку останова, удалив строки:
  initial#0 $stop;

Дальше добавим модель тактового генератора (как же мне не хватает замечательного генератора, который делали старинные Квартусы! Там можно было задать частоту в мегагерцах и не думать о пересчёте её в период, а тем более полупериод).
  always   begin      clk = 0;      #5;      clk = 1;      #5;  end

Теперь нам надо послать несколько байт данных. Проще всего это сделать прямо в секции initial, но если я буду прописывать там каждую фазу доступа к шине, код в этой секции станет запутанным. Поэтому я сделаю такую задачку (именно она выступает в роли модели шины):
task SendByte (input reg[7:0] D);    begin        data = D;        we = 1;        @(posedge clk);        #1        we = 0;   endendtask

Ну, и впишу назначение констант и вызов циклов работы с шиной в блок initial. Напоминаю, что запись типа #123 означает ждать 123 единицы времени. У нас это наносекунды. Также напоминаю, что так как присвоения идут последовательно, используем операцию равно, а не стрелка. Итого, имеем следующий основной код тестирования:
Смотреть здесь
  initial  begin     sof = 0;     we = 0;     data = 0;     #13;     // Первый байт кадра     sof = 1;     SendByte (1);     // Остальные байты     sof = 0;     SendByte (5);     SendByte (1);     // А тут мы промоделируем небольшую задержечку     #20;     SendByte (1);  end


Итого, у нас полный код модуля приобрёл такой вид:
Смотреть полный код модуля.
`timescale 1ns / 1nsmodule parazit_tb  ;    reg    sof   ;   reg    we   ;   wire  [15:0]  sum   ;   reg  [7:0]  data   ;   reg    clk   ;   sum     DUT  (        .sof (sof ) ,      .we (we ) ,      .sum (sum ) ,      .data (data ) ,      .clk (clk ) );   always   begin      clk = 0;      #5;      clk = 1;      #5;  endtask SendByte (input reg[7:0] D);    begin        data = D;        we = 1;        @(posedge clk);        #1        we = 0;   endendtask// "Constant Pattern"// Start Time = 0 ns, End Time = 100 us, Period = 0 ns  initial  begin     sof = 0;     we = 0;     data = 0;     #13;     // Первый байт кадра     sof = 1;     SendByte (1);     // Остальные байты     sof = 0;     SendByte (5);     SendByte (1);     // А тут мы промоделируем небольшую задержечку     #20;     SendByte (1);  endendmodule



Завершение подготовки тестового набора


Пришла пора добавить этот текст к тестовому набору. Для этого идём в уже известный нам диалог



Но теперь наш набор не создаём, а выбираем в списке. В будущем список будет расти по мере добавления наборов Выбрав, нажимаем кнопку Edit. Я внёс в настройки три правки:
  1. Добавил файл parazit_tb.v в список.
  2. Так как файле parazit_tb.v модуль верхнего уровня имеет имя parazit_tb (можете убедиться, глянув исходник из предыдущего раздела), я вписал это имя в строку Top level module in test bench.
  3. Я сказал вести моделирование в течение 10 микросекунд, после чего приостановиться. Если что я домоделирую через нажатие кнопок ручного управления.




Итого


Закрываем всё. Снова запускаем ModelSim. Видим, что всё работает верно. Данные приходят и учитываются в сумме. Если же на такте нет данных (we в нуле) сумма не увеличивается.



Как пользоваться самой средой моделирования это тема на несколько статей. Причём скорее в видеоформате. Но в целом мы познакомились с методикой быстрой подготовки и запуска тестов на языке Verilog из среды Quartus.

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

Увы и ах. Один из элементов внешней среды модуль ULPI. Чтобы разработать его модель самостоятельно, надо, во-первых, тщательно разобраться в логике работы той микросхемы. А в предыдущей статье я говорил, что она очень заковыристая. Ну и, во-вторых, надо затратить уйму времени на разработку кода модели. И устранение ошибок в нём Понятно, что проще найти что-то готовое. Но готовую модельку удалось найти только на языке SystemC. Поэтому в следующей статье мы будем учиться моделировать систему с использованием этого языка.
Подробнее..

Моделирование прошивки в среде ModelSim с использованием моделей на языке SystemC

29.09.2020 14:15:47 | Автор: admin
В прошлой статье мы познакомились с процессом моделирования прошивки в среде ModelSim, где и целевой код, и генератор тестовых воздействий написаны на языке Verilog. Жаль, но для решаемой в цикле цели этого недостаточно. Я уже многократно продвигал идею, что разработка для комплекса Redd должна идти с наименьшими трудозатратами. Если модель устройства пишется быстро, её можно написать с нуля. В прошлый раз мы сделали модель шины, по которой писали байты в сумматор. Но ULPI очень сложная вещь. Написать её модель с нуля ой, как не просто. Если можно найти готовую, лучше это сделать. И я нашёл Увы и ах, она оказалась на языке SystemC. Как начать работать с этим языком, мы сейчас и рассмотрим.




Предыдущие статьи цикла
  1. Разработка простейшей прошивки для ПЛИС, установленной в Redd, и отладка на примере теста памяти
  2. Разработка простейшей прошивки для ПЛИС, установленной в Redd. Часть 2. Программный код
  3. Разработка собственного ядра для встраивания в процессорную систему на базе ПЛИС
  4. Разработка программ для центрального процессора Redd на примере доступа к ПЛИС
  5. Первые опыты использования потокового протокола на примере связи ЦП и процессора в ПЛИС комплекса Redd
  6. Веселая Квартусель, или как процессор докатился до такой жизни
  7. Методы оптимизации кода для Redd. Часть 1: влияние кэша
  8. Методы оптимизации кода для Redd. Часть 2: некэшируемая память и параллельная работа шин
  9. Экстенсивная оптимизация кода: замена генератора тактовой частоты для повышения быстродействия системы
  10. Доступ к шинам комплекса Redd, реализованным на контроллерах FTDI
  11. Работа с нестандартными шинами комплекса Redd
  12. Практика в работе с нестандартными шинами комплекса Redd
  13. Проброс USB-портов из Windows 10 для удалённой работы
  14. Использование процессорной системы Nios II без процессорного ядра Nios II
  15. Практическая работа с ПЛИС в комплекте Redd. Осваиваем DMA для шины Avalon-ST и коммутацию между шинами Avalon-MM
  16. Разработка простейшего логического анализатора на базе комплекса Redd
  17. Разработка логического анализатора на базе Redd проверяем его работу на практике
  18. Делаем голову шинного USB-анализатора на базе комплекса Redd
  19. Моделируем поведение Quartus-проекта на Verilog в среде ModelSim


Вообще, эта статья в виде DOC файла появилась ещё в июне. Тогда был написан блок из пяти статей одновременно. Но выгрузить DOC файл на Хабр та ещё задача. Поэтому так вышло, что время именно на неё появилось только сейчас (а ещё две томятся в ожидании). При выгрузке, я заметил, что если не пропитаться духом предыдущих статей, эта выглядит каким-то занудством. Поэтому, если есть такое желание освежите в памяти хотя бы прошлую статью, а лучше эти две (Делаем голову шинного USB-анализатора... и Моделируем поведение Quartus-проекта...).

Введение


Итак, готовая модель, где её взять? Есть проект, решающий точно такую же задачу, что и анализатор, который мы разрабатываем, но имеющий пару особенностей. Первая особенность он для ПЛИС Xilinx. Вторая он совершенно не документирован. Как-то работает. Можно даже купить готовую макетную плату, залить в неё готовый двоичный код И получить какую-то функциональность. Кому нужен прибор любой ценой, может просто пойти по этому пути. Но как его развивать не знает никто. Тот проект лежит тут . В каталоге \ulpi_wrapper\testbench лежит комплект файлов для тестирования подсистемы обёртки вокруг ULPI. Там рекомендуют вести моделирование в среде Icarus Verilog, но я порылся и не нашёл на поверхности путных описаний, как это делать на языке SystemC. Поэтому решил продолжить работу в среде ModelSim. Если бы я знал, чем это кончится Но я не знал. Поэтому начал исследования. По ходу изложения будут показаны как успехи, так и неудачи. Начнём с неудач, чтобы все видели, как не стоит делать.

Неудачная попытка сделать всё в лоб


Сначала я решил взять и прогнать через моделирование готовый пример. Привычным движением руки (а руку мы набивали в прошлой статье), я создал тестовый набор, содержащий файлы на Verilog и SystemC. У меня вышло как-то так:



Запускаю ModelSim и не вижу в группе work ничего, что было бы связано с SystemC. Верилоговский код вижу, а Сишный нет.



Если посмотреть на логи, то видно, что его и не пытались собирать. В чём дело?



Полезная информация про настройку файла *.do


Известно, что для запуска ModelSim используется файл *.do. Но как любитель всё делать мышкой, я никогда не заглядывал ему внутрь. Давайте его поищем и откроем! В каталоге проекта находится только один такой файл. Наверное, это то, что нам нужно.



Открываем его. В начале сборка всяких служебных вещей и файлов, входящих в проект.
Смотреть текст
transcript onif ![file isdirectory verilog_libs] {file mkdir verilog_libs}if ![file isdirectory vhdl_libs] {file mkdir vhdl_libs}vlib verilog_libs/altera_vervmap altera_ver ./verilog_libs/altera_vervlog -vlog01compat -work altera_ver {c:/intelfpga_lite/17.1/quartus/eda/sim_lib/altera_primitives.v}vlib verilog_libs/lpm_vervmap lpm_ver ./verilog_libs/lpm_vervlog -vlog01compat -work lpm_ver {c:/intelfpga_lite/17.1/quartus/eda/sim_lib/220model.v}vlib verilog_libs/sgate_vervmap sgate_ver ./verilog_libs/sgate_vervlog -vlog01compat -work sgate_ver {c:/intelfpga_lite/17.1/quartus/eda/sim_lib/sgate.v}


А вот в конце явно сборка нужных нам вещей, это я сужу по имени файла ulpi_wrapper.v:
vlog -vlog01compat -work work +incdir+C:/Work/UsbHead1/SystemCPlay {C:/Work/UsbHead1/SystemCPlay/ulpi_wrapper.v}vsim -t 1ps -L altera_ver -L lpm_ver -L sgate_ver -L altera_mf_ver -L altera_lnsim_ver -L cycloneive_ver -L rtl_work -L work -L UsbHead1 -voptargs="+acc"  lalalaadd wave *view structureview signalsrun 10 us

Действительно. Есть сборка Verilog-овского модуля, и нет никаких намёков на сборку модулей на SystemC. Жаль только, что этот DO-файл автоматически создаётся при каждом запуске моделирования, так что просто взять и отредактировать его не получится. Его создаёт очень сложный TCL-скрипт. Править его нет никакого желания. Но после статьи про весёлую квартусель, наверное, понятно, что такая мелочь не повод опускать руки. Наверняка, всё уже есть. Жаль только, что в документации сказано, что вы можете сделать скрипт так, а можете так, и нет никаких намёков на примеры. Ну что ж, давайте выводить всё экспериментальным путём. Создаём файл C:\Work\UsbHead1\SystemCPlay\myrun.do и пытаемся передать ему управление. Сначала пробуем это сделать так:



Основной DO-файл всё равно продолжает вырабатываться, но его концовка становится такой:
vlog -sv -work UsbHead1 +incdir+C:/Work/UsbHead1/UsbHead1/synthesis/submodules {C:/Work/UsbHead1/UsbHead1/synthesis/submodules/UsbHead1_master_0_b2p_adapter.sv}vlog -sv -work UsbHead1 +incdir+C:/Work/UsbHead1/UsbHead1/synthesis/submodules {C:/Work/UsbHead1/UsbHead1/synthesis/submodules/UsbHead1_master_0_timing_adt.sv}vlog -vlog01compat -work work +incdir+C:/Work/UsbHead1/SystemCPlay {C:/Work/UsbHead1/SystemCPlay/ulpi_wrapper.v}vsim -t 1ps -L altera_ver -L lpm_ver -L sgate_ver -L altera_mf_ver -L altera_lnsim_ver -L cycloneive_ver -L rtl_work -L work -L UsbHead1 -voptargs="+acc"  lalalado C:/Work/UsbHead1/SystemCPlay/myrun.do

Мы видим, что Verilog файл по-прежнему компилится, далее по-прежнему запускается процесс моделирования (правда, я-то это видел при пробных запусках, но теперь могу точно сказать, что команда vsim запускает этот процесс), после чего управление передаётся нашему скрипту. Этот скрипт должен процессом отображения управлять. Но сборкой мы управлять по-прежнему не можем. Если собранных файлов не хватает, система отвалится по ошибке раньше, чем нам дадут что-либо сделать. Ну и отлично, пробуем последний вариант настройки.



И тут начинается самое интересное. Оно настолько важно, что я возьму это в рамочку.

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


Скрипт всё равно формируется, но его концовка стала более удобной для нас.
vlog -sv -work UsbHead1 +incdir+C:/Work/UsbHead1/UsbHead1/synthesis/submodules {C:/Work/UsbHead1/UsbHead1/synthesis/submodules/UsbHead1_master_0_timing_adt.sv}do "C:/Work/UsbHead1/SystemCPlay/myrun.do"

Наконец-то процесс сборки исходников для проекта полностью отдан нам на откуп! Замечательно! На тот момент я смог найти только документ SystemC Verification with ModelSim, написанный для Xilinx. Но ModelSim, он и в Африке ModelSim. Пользуясь примерами из этого документа и образцами DO-файла, созданного на прошлых опытах, я сделал следующий текст скрипта (не пугайтесь обилию ключей, ниже мы почти все выкинем, абсолютные пути тоже потом заменим на относительные, на этом этапе я просто всё дергал из примеров и автоматически сгенерённых образцов).
vlog -vlog01compat -work work +incdir+C:/Work/UsbHead1/SystemCPlay {C:/Work/UsbHead1/SystemCPlay/ulpi_wrapper.v}vlib sc_worksccom g I C:/intelFPGA_lite/17.1/quartus/cusp/systemc/include work sc_work C:/Work/UsbHead1/SystemCPlay/ulpi_driver.cpp

Барабанная дробь И ModelSim нам заявляет:



Если опустить все неприличные слова, то мне и сказать-то нечего Но такой путь пройден! И где взять другую модель ULPI? Разумеется, я договорился с иностранными знакомыми, профессионально занимающимися серьёзными проектами для ПЛИС. Специально для меня они открыли на выходные удалённый доступ к машине с лицензионным ModelSim. Второй блин также оказался комом: 64-битная версия даже в лицензионном виде не работает с SystemC. Но в конце концов, мне удалось поиграть с 32-битной версией лицензионного ModelSim. Поэтому продолжаем рассказ

Пара слов о документации


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

C:\modeltech_10.2c\docs\pdfdocs документация, включая файлы в формате PDF. Мне понравились файлы modelsim_se_ref.pdf (ModelSim SE Command Reference Manual), modelsim_se_user.pdf (ModelSim SE Users Manual) и modelsim_se_tut.pdf (ModelSim SE Tutorial). По самому языку там мало что есть, но по тому, как подключать файлы и как решать проблемы диалектов вполне.

Дальше, полезный каталог C:\modeltech_10.2c\examples. Там есть примеры готовых файлов *.do и готовых файлов cpp и h. Самый полезный для нас пример C:\modeltech_10.2c\examples\systemc\vlog_sc. В нём показано, как обращаться из Verilog кода к коду на SystemC. Мы, в итоге, пойдём именно этим путём.

В каталоге C:\modeltech_10.2c\include\systemc содержатся исходные коды библиотеки типов языка. Неплохой справочник. Как говорится, на безрыбье и рак рыба.

Из каталогов всё. Теперь название замечательной книги, из которой можно узнать многое как о языке, так и о методике программирования на нём. SystemC From the Ground Up, Second Edition. Авторы David C. Black, Jack Donovan, Bill Bunton, Anna Keist.

Диалекты языка SystemC


Итак. Получив доступ к работающей системе, я радостный собрал проект, согласно ранее созданному скрипту. Он собрался без ошибок! Первая моделька с ГитХаба согласилась работать с нами! Желая прогнать эталонный тест, я добавил в проект файл ulpi_wrapper_tb.cpp из того же каталога и получил массу ошибок. Допустим, ошибку в строке:
m_vpi_handle = vpi_handle_by_name((const char*)name, NULL);
поправить сложно, но ещё можно. Но строка
        // Update systemC TB        if(sc_pending_activity())            sc_start((int)(time_value-m_last_time),SC_NS);

навевала плохие мысли. Функция sc_pending_activity() в библиотеках отсутствует. Имеется функция sc_pending_activity_at_current_time(), но с нею я даже разбираться не стал. Вместо тысячи слов объяснения, приведу дамп:



И файлов с таким текстом (*.exe, *.dll и т. п.) нашлось 44 штуки.

Можно было попытаться всё переписать Но надо ли оно? Напомню, я вообще-то начал всё это, так как хотел воспользоваться всем готовым. Разработать я всё и в бесплатной среде на чистом SystemVerilog могу, если уж тратить кучу времени Я шёл сюда, чтобы время не потратить, а сэкономить! Но на самом деле Главное не забыть, что мы делаем. Мы хотим воспользоваться моделькой шины ULPI. Она собралась. Проблемы возникли при попытке собрать полную тестовую систему из примера А зачем это? Ну не работает полная система, и ладно. Будем осваивать одну модельку, не глядя на работу системы, методом проб и ошибок.

Устраняем непонимание, основанное на диалектах


Итак. Мы будем делать смешанную систему. Модуль с моделью будет написан на языке SystemC, а тестовые воздействия ему и разрабатываемому модулю я буду подавать на языке Verilog. То есть, надо добиться появления модуля ulpi_driver в группе work.

Осматривая примеры файлов *.do из поставки ModelSim, я сильно упростил скрипт, и в итоге, сделал такой:
vlog +../../SystemCPlay {../../MyCores/ULPIhead.sv}sccom -g ../../SystemCPlay/ulpi_driver.cppsccom -link

Ошибок нет, но и модуль в группе не появился. Осматривая файлы примера (напомню, лучший пример, реализующий именно такое смешивание языков есть в каталоге C:\modeltech_10.2c\examples\systemc\vlog_sc), я понял, что в конец файла ulpi_driver.cpp надо добавить строчку:
SC_MODULE_EXPORT(ulpi_driver);

Документация на ModelSim говорит, что это особенности диалекта. И вуаля! Вот он, наш модуль:



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

Делаем тактовый генератор


Оказалось, что у модели есть пара отличий от настоящего ULPI. Первое отличие состоит в том, что тактовый сигнал 66 МГц должен вырабатывать чип. А что мы видим в модели?
    sc_in<bool>             clk_i;

Непорядок! Начинаем переделку! Все работы, если не указано иное, ведём в файле ulpi_driver.h.
Заменяем тип порта. Было:
    sc_in<bool>             clk_i;

стало (я ещё и имя порта поменял):
    sc_inout<bool>             clk;

Из книжки я узнал, что настоящий генератор вставляется путём добавления переменной:
    sc_clock oscillator;

Параметры задаём в конструкторе. В итоге, конструктор приобретает вид:
    //-------------------------------------------------------------    // Constructor    //-------------------------------------------------------------    SC_HAS_PROCESS(ulpi_driver);    ulpi_driver(sc_module_name name): sc_module(name),                                      m_tx_fifo(1024),                                       m_rx_fifo(1024),                                      oscillator ("clk66",sc_time(15,SC_NS))    {

Последняя строка как раз для этого. При желании, можно даже уже запустить моделирование, дважды щёлкнуть по модулю usb_driver, дальше вытянуть clk66 на времянку и немного прогнать процесс моделирования. Мы уже видим, как работает генератор:



Не забудем поменять имя тактового сигнала и в месте, где стартует основной поток. Было:
        SC_CTHREAD(drive, clk_i.pos());

Стало:
        SC_CTHREAD(drive, clk.pos());


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

Добавляем под конструктором функцию потока:
    void clkThread(void)     {       while (true)       {           wait(oscillator.posedge_event());           clk.write (true);           wait(oscillator.negedge_event());           clk.write (false);       }    }

И добавляем ссылку на неё в конструктор класса:
        SC_THREAD(clkThread);

Давайте я покажу текущий район конструктора, чтобы было целостное видение текущего результата:
    SC_HAS_PROCESS(ulpi_driver);    ulpi_driver(sc_module_name name): sc_module(name),                                      m_tx_fifo(1024),                                       m_rx_fifo(1024),                                      oscillator ("clk66",sc_time(15,SC_NS))    {        SC_CTHREAD(drive,clk.pos());        SC_THREAD(clkThread);        m_reg[ULPI_REG_VIDL]    = 0x24;        m_reg[ULPI_REG_VIDH]    = 0x04;        m_reg[ULPI_REG_PIDL]    = 0x04;        m_reg[ULPI_REG_PIDH]    = 0x00;        m_reg[ULPI_REG_FUNC]    = 0x41;        m_reg[ULPI_REG_OTG]     = 0x06;        m_reg[ULPI_REG_SCRATCH] = 0x00;    }    void clkThread(void)     {       while (true)       {           wait(oscillator.posedge_event());           clk.write (true);           wait(oscillator.negedge_event());           clk.write (false);       }    }

Всё. Первая правка завершена.

Делаем двунаправленную шину данных


У ULPI двунаправленная шина данных. А в модели мы видим следующее её описание:
    sc_out <sc_uint<8> >    ulpi_data_o;    sc_in  <sc_uint<8> >    ulpi_data_i;

Непорядок! Сначала мы сделаем заготовку на базе выходной шины, а затем переключим всё на неё. С чего начать? С того, что шина должна уметь переходить в третье состояние, а тип sc_uint<8> работает только с двоичными данными. Нам поможет тип sc_lv<8>. Поэтому меняем объявление шины на:
    sc_inout <sc_lv<8> >    ulpi_data_o;

Теперь переходим в файл ulpi_driver.cpp и там ищем все обращения к шине ulpi_data_o. Интуитивно я понял, что поправить надо только одно место:


То же самое текстом.
void ulpi_driver::drive_input(void){    // Turnaround    ulpi_dir_o.write(false);    ulpi_nxt_o.write(false);    ulpi_data_o.write(0x00);    wait(oscillator.posedge_event());}



Меняем выделенную строку на
    ulpi_data_o.write("ZZZZZZZZ");

Всё. Теперь можно вместо двух строк:
    sc_inout <sc_lv<8> >    ulpi_data_o;    sc_in  <sc_uint<8> >    ulpi_data_i;

написать одну:
    sc_inout <sc_lv<8> >    ulpi_data;

и заменить все ссылки на старые переменные как в h-нике, так и в cpp-шнике на ссылки на переменную ulpi_data.

Добавляем псевдонимы для портов


Итак. После долгих поисков я пришёл к выводу (возможно, ошибочному), что в среде ModelSim просто взять и увидеть порты для отдельно лежащего модуля на SystemC средствами GUI, не судьба. Однако, если этот модуль вставить в тестовую систему, они появятся. Но пока рылся с теорией, нашёл, как красиво задать псевдонимы для имён портов. Итоговый конструктор класса стал выглядеть так:
    SC_HAS_PROCESS(ulpi_driver);    ulpi_driver(sc_module_name name): sc_module(name),                                      m_tx_fifo(1024),                                       m_rx_fifo(1024),                                      oscillator ("clk66",sc_time(15,SC_NS)),                                      rst_i ("rst"),                                           ulpi_data ("data"),                                      ulpi_dir_o ("dir"),                                      ulpi_nxt_o ("nxt"),                                      ulpi_stp_i ("stp")    {        SC_CTHREAD(drive,clk.pos());        SC_THREAD(clkThread);        m_reg[ULPI_REG_VIDL]    = 0x24;        m_reg[ULPI_REG_VIDH]    = 0x04;        m_reg[ULPI_REG_PIDL]    = 0x04;        m_reg[ULPI_REG_PIDH]    = 0x00;        m_reg[ULPI_REG_FUNC]    = 0x41;        m_reg[ULPI_REG_OTG]     = 0x06;        m_reg[ULPI_REG_SCRATCH] = 0x00;    }

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


Ну что же. Сделать всё на автомате, чтобы сразу два отлаживаемых модуля (голова анализатора и модель шины ULPI) сами запрыгнули в тестовый файл, у меня не получилось. Но сделаем хотя бы тест для головы, а потом добавим к нему ULPI. Пользуясь методикой из прошлой статьи, я сделал тестовую систему для файла ULPIhead.sv. Файл я назвал sim1.v и тут же переименовал его в sim1.sv.

После чего ручками добавил туда модуль ulpi_driver. Итоговый скрипт myrun.do выглядит так:
vlog +../../SystemCPlay {../../MyCores/ULPIhead.sv}sccom -g ../../SystemCPlay/ulpi_driver.cppsccom -linkvlog +../../SystemCPlay {../../SystemCPlay/sim1.sv}vsim -voptargs="+acc" sim1

Последняя строка вымученная. Без неё не было портов у Verilog кода. Изменяя параметры оптимизации, мы устраняем эту беду. Её я подсмотрел в том файле *.do, который был создан для моделирования нашей системы в самом начале, когда всё ещё делалось на автомате. Правда, там строка длиннющая. Я просто нашёл тот ключ, который решает проблему, и скопировал его. А так не люблю длинных строк, всё лишнее я выкинул.

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

У меня получился такой тест.
Смотреть текст.
`timescale 1ns / 1nsmodule sim1  ;    reg    ulpi_dir   ;   wire   source_valid   ;   wire    ulpi_stp   ;   reg    ulpi_clk   ;   reg    ulpi_nxt   ;   reg    reset_n   ;   reg    read   ;   reg  [31:0]  writedata   ;   wire    ulpi_rst   ;   reg    clk   ;   wire  [7:0]  source_data   ;   reg    write   ;   wire  [7:0]  ulpi_data   ;   reg    source_ready   ;   reg  [1:0]  address   ;   wire  [31:0]  readdata   ;   always   begin     clk = 1;     #5;     clk = 0;     #5;  end  ULPIhead  DUT    (       .ulpi_dir (ulpi_dir ) ,      .source_valid (source_valid ) ,      .ulpi_stp (ulpi_stp ) ,      .ulpi_clk (ulpi_clk ) ,      .ulpi_nxt (ulpi_nxt ) ,      .reset_n (reset_n ) ,      .read (read ) ,      .writedata (writedata ) ,      .ulpi_rst (ulpi_rst ) ,      .clk (clk ) ,      .source_data (source_data ) ,      .write (write ) ,      .ulpi_data (ulpi_data ) ,      .source_ready (source_ready ) ,      .address (address ) ,      .readdata (readdata ) );   ulpi_driver ULPI  (      .clk (ulpi_clk),      .rst (ulpi_rst),      .data (ulpi_data),      .dir (ulpi_dir),      .nxt (ulpi_nxt),      .stp (ulpi_stp)  );  initial  begin     reset_n  = 1'b0;     source_ready = 1;     writedata = 0;     address = 0;     read = 0;     write = 0;     #20     reset_n  = 1'b1;  endendmodule





Заключение


Худо-бедно, но мы освоили моделирование на языке SystemC с использованием системы ModelSim. Правда, оказалось, что для этого необходимо иметь доступ к лицензионной 32-битной версии. Свободная версия и лицензионная 64-битная версия такой возможности не дают. Как я понял, совершенно бесплатно всё можно сделать в системе Icarus Verilog, но как именно этого достичь, не разобрался. Мне оказалось проще получить доступ к требуемому ModelSim. В следующей статье мы воспользуемся полученными знаниями, чтобы провести моделирование нашей головы.

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

Практические опыты с USB-анализатором на базе Redd

02.11.2020 16:19:26 | Автор: admin
Три статьи назад мы сделали голову для шинного анализатора USB, после чего обсуждали вопросы моделирования и гоняли эту голову на моделях. Правда, всё это было не зря. В прошлой статье я показательно нашёл пару сбоев логики, а в реальности их было чуть больше. Поэтому, начни я всё проверять сразу в железе сидел бы и сокрушался: А чего оно так себя ведёт? Теперь же я уверен, что в поведенческой модели вся логика делает именно то, что было задумано. Что дальше? Те, кто делает серьёзные большие проекты, дальше переходят к моделированию синтезированной модели, а в конце модели, привязанной к реальной топологии упаковки в ПЛИС (так называемое Gate-Level моделирование). Там уже учитываются задержки на всех трассировочных ресурсах при именно этом результате компиляции. Но проект нашей сложности этого не требует. Нам было достаточно убедиться, что вся логика реализована верно. И теперь мы можем перейти к сборке реального анализатора и проверке его работы на практике.




Предыдущие статьи цикла
  1. Разработка простейшей прошивки для ПЛИС, установленной в Redd, и отладка на примере теста памяти
  2. Разработка простейшей прошивки для ПЛИС, установленной в Redd. Часть 2. Программный код
  3. Разработка собственного ядра для встраивания в процессорную систему на базе ПЛИС
  4. Разработка программ для центрального процессора Redd на примере доступа к ПЛИС
  5. Первые опыты использования потокового протокола на примере связи ЦП и процессора в ПЛИС комплекса Redd
  6. Веселая Квартусель, или как процессор докатился до такой жизни
  7. Методы оптимизации кода для Redd. Часть 1: влияние кэша
  8. Методы оптимизации кода для Redd. Часть 2: некэшируемая память и параллельная работа шин
  9. Экстенсивная оптимизация кода: замена генератора тактовой частоты для повышения быстродействия системы
  10. Доступ к шинам комплекса Redd, реализованным на контроллерах FTDI
  11. Работа с нестандартными шинами комплекса Redd
  12. Практика в работе с нестандартными шинами комплекса Redd
  13. Проброс USB-портов из Windows 10 для удалённой работы
  14. Использование процессорной системы Nios II без процессорного ядра Nios II
  15. Практическая работа с ПЛИС в комплекте Redd. Осваиваем DMA для шины Avalon-ST и коммутацию между шинами Avalon-MM
  16. Разработка простейшего логического анализатора на базе комплекса Redd
  17. Разработка логического анализатора на базе Redd проверяем его работу на практике
  18. Делаем голову шинного USB-анализатора на базе комплекса Redd
  19. Моделируем поведение Quartus-проекта на Verilog в среде ModelSim
  20. Моделирование прошивки в среде ModelSim с использованием моделей на языке SystemC
  21. Проводим моделирование системы для проверки работоспособности головы USB-анализатора


Доработка кода головы


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

Первое тактирование. Сначала я планировал тактировать систему от основного генератора, удвоив его частоту (с 50 до 100 МГц), как мы это делали для логического анализатора, а голову от выходной частоты ULPI (60 МГц). Для этого я предусмотрел два тактовых домена. Жизнь внесла свои коррективы. Эти тактовые домены потребовали таких пространных объяснений, что статья стала не про USB-анализатор, а про них. Поэтому я избавился от двух тактовых частот. Но как всё тактировать? Решено было, что источником тактовых сигналов станет сама голова! Поэтому ей был добавлен новый порт:
   output              clk60,

И добавлена буквально одна строка текста:
assign clk60 = ulpi_clk;

В верилоговской части всё. Хотя, при упаковке в компонент, мы к тактам ещё вернёмся.

Вторая доработка сигнал GO. В логическом анализаторе в том виде, в каком он пошёл в статью, имеется одна весёлая вещь заполнение FIFO. Давайте я покажу фрагмент готового рисунка из статьи, где мы делали логический анализатор (Разработка простейшего логического анализатора на базе комплекса Redd). Детали реализации (ширина шины) там чуть отличаются, но суть проблемы ясна.



Мы включаем DMA, в итоге сначала пробегают не данные из головы, а данные, накопившиеся в FIFO. А когда они там копились, кто же знает? Надо, как минимум, добавить операцию Flush. Здесь же я сделал сигнал, являющийся ключом, запирающим выход данных из самой головы. Пока он не взведён, голова не выдаёт данные в AVALON_ST, а значит, они не попадут и в FIFO. Соответственно, теперь вместо прямого управления сигналом шины source_valid есть внутренний флаг ядра:
logic source_valid_priv;

Именно его взводит или сбрасывает автомат. А внешний сигнал формируется с учётом бита GO:
logic go;assign source_valid = source_valid_priv & go;

Ну, и сам бит Go формируется регистром управления:
// Обслуживание AVALON_MM на записьalways_ff @(posedge ulpi_clk)begin...   if (write == 1)    begin      case (address)...          2 : begin...                // Бит 1 - запуск анализа,                // Без него данные наружу не выйдут                go <= writedata [1];...

Ну, и третья доработка чисто для красоты. Но эта красота была одной из причин, почему пришлось отказаться от двух тактовых доменов. Я хотел отображать текущий адрес DMA на экране PC при работе TCL-скрипта. Возможно, это как-то даже и можно сделать, но я не нашёл правильного решения. Не вижу я порта, который можно читать через шину AVALON_MM на лету, чтобы видеть текущий адрес записи, и всё тут. Запутавшись в документе, описывающем типовые AVALON-устройства (включая и все виды DMA-контроллеров), я решил подойти к решению вопроса кардинально. Если я не могу узнать, какой адрес сейчас обрабатывается, то кто мне мешает сообщать, сколько данных отправлено в него из источника? Пусть будет так (я опять беру схему от логического анализатора, но кроме ширины шин она ничем не отличается):



На рисунке показано, что счётчик (cnt) блока DMA не имеет выхода на шину AVALON_MM. А новый счётчик (cnt) блока Голова подсчитывает число транзакций записи в шину AVALON_ST и позволяет считать своё значение через шину AVALON_MM. Ну, а мы можем получить ему доступ через порт JTAG и блок Altera HTAG-to-Avalon_MM, а дальше отобразить считанное значение на экране пусть в абсолютном виде, пусть в процентах от размера буфера. Так делают все приличные анализаторы!

Приступаем к внедрению. Добавим в ядре головы ещё один порт. Но адресное пространство шины уже всё занято четырьмя портами. Поэтому я расширил шину адреса. Теперь она выглядит так:
input        [2:0]  address,

Читаем счётчик так:
// Обслуживание AVALON_MM на чтениеalways_comb begin   case (address)...      // Счётчик переданных данных для красивого отображения      4: readdata <= transfer_cnt;

а формируем по перепаду GO. Типовое решение для тех, кто собрался ловить перепад добавить в код задержку на шаг и сравнение прямого и задержанного значений. Итого:
// Это - для красоты. Счётчик переданных данныхlogic [31:0] transfer_cnt = 0;// Его красивое формирование:logic go;logic go_prev;always_ff @(posedge ulpi_clk)begin    // Для ловли перепада нам надо знать    // предыдущее значение бита "go"    go_prev <= go;    // Если анализ только что запустился - сбросили счётчик    if ((!go_prev) && (go))         transfer_cnt <= 0;    // Иначе - считаем каждый такт, когда данные уехали в AVALON_ST    else if (go & source_valid_priv)         transfer_cnt <= transfer_cnt + 1;end

У нас ещё будут доработки головы, поэтому справочный полный текст модуля я приведу в конце статьи.

Упаковка головы в компонент


Компонент с USB-головой довольно сложен. В нём имеется целых две шины, плюс шина conduit, плюс ещё источник тактовых импульсов. Давайте потихоньку рассмотрим все особенности. Начнём с источника тактовых сигналов:



Обратите внимание, что я прописал параметр Clock rate и взвёл флажок Clock rate known. Без этого не будет собираться ядро контроллера SDRAM (оно ведь тоже будет тактироваться от этого источника). А вот так выглядит не шина, а единственный её сигнал (выделение в левом списке отличается на один уровень):



С этим понятно. Теперь шина AVALON_MM. Обратите внимание на времянку чтения. Исходно она была другой. Если я ничего не путаю, исходно параметр Read Wait был равен единице. В целом, вы всегда можете сравнивать свои настройки с моими и добиваться идентичности. Ну, и мы видим, что сопоставленным тактовым источником является тот самый clock_source, который мы только что создали.



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



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



А особенность состоит в том, что для каждого её сигнала надо вручную прописать уникальное имя signal_type. Вот пример для линии dir:



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

Внешний вид процессорной системы


Оказывается, прямо в редакторе Platform Designer можно менять цвета связей. Жаль только, что число контрастных цветов не так велико, поэтому основные линии я подкрасил вновь обнаруженными средствами редактора, а на какие контрастных цветов не хватило по старинке, вручную.

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

Источником сброса (красная линия) является блок JTAG_TO_AVALON_MM. А источником тактового сигнала (синяя линия) наша USB-голова.



Зелёная линия это AVALON_MM.

Блок PLL хоть и настроен на два выхода c0 и c1, но реально c0 не используется. c1 по-прежнему тактирует микросхему SDRAM. Настройки PLL просты: входная частота 60 МГц, выход c0 60 МГц, сдвиг 0, выход c1 60 МГц, сдвиг минус 60 градусов. Приведу скриншот для настройки именно этого выхода. Вообще, про настройку PLL рассказывается тут Ускорение программы для синтезированного процессора комплекса Redd без оптимизации: замена тактового генератора.



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

FIFO, в отличие от логического анализатора, настроено на 16-битную шину (2 символа на слово). Именно такой выход у нашей головы.



Соответственно, преобразователь формата данных работает по схеме 16->32 (2 символа на слово на входе и 4 на выходе):



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

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


Вспомогательные функции


Черновая проверка позволит нам набросать необходимую базу TCL скрипта и убедиться, что он в принципе работает. Функции для доступа к DMA я взял из кода для проверки логического анализатора. Единственно, что заменил базовые адреса на те, которые автоматически назначались для новой схемы:
variable ULPI_BASE 0x1000020variable DMA_BASE 0x1000000variable DMA_DESCR_BASE 0x1000050# Чтение регистра блока DMA.#proc dma_reg_read { address } {  variable DMA_BASE  variable m_path  set address [expr {$address * 4 + $DMA_BASE}]  return [master_read_32 $m_path $address 1]}# Запись регистра блока DMAproc dma_reg_write { address data } {  variable DMA_BASE  variable m_path  set address [expr {$address * 4 + $DMA_BASE}]  master_write_32 $m_path $address $data}# Запись регистра дескрипторов блока DMAproc dma_descr_reg_write { address data } {  variable DMA_DESCR_BASE  variable m_path  set address [expr {$address * 4 + $DMA_DESCR_BASE}]  master_write_32 $m_path $address $data}proc prepare_dma {sdram_addr sdram_size} {# Остановили процесс, чтобы всё понастраивать# Да, мне лень описывать все константы, # я делаю всё на скорую рукуdma_reg_write 1 0x20# На самом деле, тут должно быть ожидание фактической остановки,# но в рамках теста, оно не нужно. Точно остановимся.# Добавляем дескриптор в FIFO# Адрес источника (вообще, это AVALON_ST, но я всё# с примеров списывал, а там он зануляется)dma_descr_reg_write 0 0# Адрес приёмника. dma_descr_reg_write 1 $sdram_addr# Длинаdma_descr_reg_write 2 $sdram_size# Управляющий регистр (взводим бит GO)dma_descr_reg_write 3 0x80000000# Запустили процесс, не забыв отключить прерыванияdma_reg_write 1 4}

Новые функции это функции доступа к регистрам ULPI. Вообще, по уму там нужно ждать снятия сигнала BSY. Но я утверждаю, что шина JTAG настолько медленная, что BSY на ULPI снимется гарантированно медленнее, чем скрипт успеет сделать обращение к регистру. Поэтому я не трачу на эту заведомо бесполезную работу силы и время. Так что при записи просто положили адрес, положили данные, вышли:
proc ulpi_reg_write {reg_addr reg_data} {  variable ULPI_BASE  variable m_path  set port_addr [expr {$ULPI_BASE + 0}]  set port_data [expr {$ULPI_BASE + 4}]  # Задали адрес регистра  master_write_32 $m_path $port_addr $reg_addr   master_write_32 $m_path $port_data $reg_data   # Надо бы дождаться готовности, но JTAG точно медленнее,  # поэтому практического смысла в этом нет.}

При чтении положили адрес, инициировали процесс чтения, считали результат без ожидания:
proc ulpi_reg_read {reg_addr} {  variable ULPI_BASE  variable m_path  set port_addr [expr {$ULPI_BASE + 0}]  set port_data [expr {$ULPI_BASE + 4}]  set port_ctrl [expr {$ULPI_BASE + 8}]  # Задали адрес регистра  master_write_32 $m_path $port_addr $reg_addr   # Запустили процесс чтения регистра  master_write_32 $m_path $port_ctrl 1  # Ждать я не буду, JTAG стопудово медленней работает  return [master_read_32 $m_path $port_data 1]}

Все желающие могут добавить процесс ожидания готовности самостоятельно.

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

Проверка доступности ОЗУ


Первый тест просто позволяет убедиться, что ОЗУ доступно. Основной код предельно прост:
puts "running"set m_path [lindex [get_service_paths master] 0]open_service master $m_pathputs "ID:"master_write_32 $m_path 0x00 0x11111111master_write_32 $m_path 0x04 0x22222222master_write_32 $m_path 0x08 0x33333333master_write_32 $m_path 0x0c 0x44444444puts [master_read_32 $m_path 0 4]

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



Проверка чтения регистров


Убедиться, что регистры читаются, удобно на регистрах идентификации. Вот что говорит на эту тему документация:



Делаем простейший скрипт:
puts "running"set m_path [lindex [get_service_paths master] 0]open_service master $m_pathputs "ID:"puts [ulpi_reg_read 0]puts [ulpi_reg_read 1]puts [ulpi_reg_read 2]puts [ulpi_reg_read 3]

Прогоняем, проверяем результат:



Работает! Регистры читаются верно!

Проверка записи регистров


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



Поэтому я буду читать регистр 0x0A, писать в регистр 0x0C, а в итоге при повторном чтении в регистре 0x0A сбросятся некоторые биты!
Вот текст скрипта:
puts "running"set m_path [lindex [get_service_paths master] 0]open_service master $m_pathputs "Otg Control Before: [ulpi_reg_read 0x0a]"# На пробу отключим подтяжку напрочьulpi_reg_write 0x0c 7puts "Otg Control After: [ulpi_reg_read 0x0a]"

Вот результат:



Все кубики системы работают! Можно начинать боевые опыты

Боевая проверка


Масса команд


Ну что ж. Пришла пора провести боевую проверку. Делаем такое основное тело скрипту (там я отключаю все резисторы шины, перевожу работу в режим FS, настраиваю DMA, взвожу GO и начинаю ждать, когда счётчик заполнения превысит запрошенный мною объём, после чего вывожу на экран начало принятого буфера):
puts "running"set m_path [lindex [get_service_paths master] 0]open_service master $m_path# Готовим регистры к анализу# Отключим подтяжки напрочьulpi_reg_write 0x0c 7# Включаем режим Full Speed (биты 1:0 = 01)    # Зануляем TermSelect (бит 2)# Включаем режим Non Driving (биты 4:3 = 01)ulpi_reg_write 0x06 0x1fulpi_reg_write 0x05 0x09# Собственно, всё. Теперь запускаем DMAprepare_dma 0x00 0x1000# Взводим бит GOmaster_write_32 $m_path [expr {$ULPI_BASE + 8}] 2# И начинаем отображать адресаset cur_dma_addr [master_read_32 $m_path [expr {$ULPI_BASE + 0x10}]  1]while {$cur_dma_addr < 0x100} {puts -nonewline "$cur_dma_addr \r"after 1000set cur_dma_addr [master_read_32 $m_path [expr {$ULPI_BASE + 0x10}]  1]}puts -nonewline "$cur_dma_addr \r"# Сняли бит GOmaster_write_32 $m_path [expr {$ULPI_BASE + 8}] 0puts [master_read_16 $m_path 0 128]puts "Finished!"

Запускаем скрипт и начинаем играть в любимую игру ослика Иа Входит-выходит. Аккуратно подключаем какое-нибудь FS-устройство. Лично мне под руку попался китайский клон USB-бластера. Результат меня сильно озадачил. Он был примерно таким:
0x4801 0x4c01 0x4801 0x4c01 0x4d01 0x4c01 0x4d01 0x4c01 0x4d01 0x4c01 0x4d01 0x4c01 0x4d01 0x4c01

Команды, команды, команды А где данные? Но при одном из прогонов, данные промелькнули. Ровно одна посылка! Тогда я решил, что надо смотреть на данные большого объёма. Поэтому переписал скрипт так, чтобы он принимал целый мегабайт и скидывал его в файл. Тело стало выглядеть следующим образом:
puts "running"set m_path [lindex [get_service_paths master] 0]open_service master $m_path# Готовим регистры к анализу# Отключим подтяжки напрочьulpi_reg_write 0x0c 7# Включаем режим Full Speed (биты 1:0 = 01)    # Зануляем TermSelect (бит 2)# Включаем режим Non Driving (биты 4:3 = 01)ulpi_reg_write 0x06 0x1fulpi_reg_write 0x05 0x09# Собственно, всё. Теперь запускаем DMAprepare_dma 0x00 0x100000# Взводим бит GOmaster_write_32 $m_path [expr {$ULPI_BASE + 8}] 2# И начинаем отображать адресаset cur_dma_addr [master_read_32 $m_path [expr {$ULPI_BASE + 0x10}]  1]while {$cur_dma_addr < 0x100000} {puts -nonewline "$cur_dma_addr \r"after 1000set cur_dma_addr [master_read_32 $m_path [expr {$ULPI_BASE + 0x10}]  1]}puts -nonewline "$cur_dma_addr \r"# Сняли бит GOmaster_write_32 $m_path [expr {$ULPI_BASE + 8}] 0set fileid [open "ShowMe.txt" w]puts $fileid [master_read_16 $m_path 0 0x40000]close $fileidputs "Finished!"

И вот так выглядят участки с данными:


То же самое текстом.
... 0x5e01 0x5e01 0x5e01 0xa500 0x5e01 0x5e01 ...... 0x5d01 0x5d01 0x5d01 0x8c00 0x5d01 0x5d01 ...... 0x5c01 0x5c01 0x5c01 0xba00 0x5d01 0x5d01 ...... 0x5d01 0x5d01 0x5d01 0xa500 0x5d01 0x5d01 ...... 0x5e01 0x5e01 0x5e01 0x8d00 0x5e01 0x5d01 ...... 0x5c01 0x5c01 0x5c01 0x4200 0x5c01 0x5d01 ...


Так получилось, что я знаю суть магического числа 0xA5. Это признак PID. То есть, сначала идёт пакет номер 0x8C, затем пакет номер 0x8D Но все они накрыты просто бешеным количеством команд. Зачем эти команды?

И тут меня осенило. Я же работаю с FS-устройством. Оно гонит данные на смешной частоте. А ULPI выдаёт их с той же частотой, с какой бы выдавал и для HS. И чем ему заполнять пустоты? Вот он командами их и заполняет! Хорошо, что я начал проверку с FS-устройства!

Значит, надо производить фильтрацию.

Фильтр номер раз


Сначала я решил, что надо фильтровать поток по факту начала пакета. То есть выводить команду только в этот момент, а остальные команды пакета игнорировать:



Так как решение ошибочное, я не буду приводить здесь код, который его реализует, но напомню, что это место прекрасно отлавливается, так как в это и только в это время автомат находится в состоянии wait1. Радостный, я добавил ещё один флаг и стал сохранять в памяти только команды, где он взведён. Дамп стал лучше, но всё равно весьма насыщенный:


То же самое текстом.
... 0x4e03 0x4d03 0x5d03 0xa500 0xec00 0x6a00...... 0x4d03 0x4e03 0x5d03 0xa500 0xed00 0x9200...


Здесь всё понятно. Много пакетов со сброшенным RxActive, и только перед самыми данными RxActive взлетает в единицу. Когда команда равна 5X, биты 5:4 равны 01 (RxActive равны единице). 4X же соответствует значению 00 в этих битах. Вот фрагмент из документации:



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


То же самое текстом.
0x5d03 0xa500 0x1800 0x7600 0x5d03 0xa500 0x1900 0x8e00 0x5d03 0xa500 0x1a00 0xce00 0xa500 0x1b00 0x3600 0x5d03 0xa500 0x1c00 0x4e00 0x5d03 0xa500 0x1d00 0xb600 0x5d03 0xa500 0x1e00 0xf600 0xa500 0x1f00 0x0e00 0x5d03 0xa500 0x2000 0x6e00 0x5d03 0xa500 0x2100 0x9600 0xa500 0x2200 0xd600 0x5d03 0xa500 0x2300 0x2e00 0x5d03 0xa500 0x2400 0x5600 0x5d03 0xa500 0x2500 0xae00 0xa500 0x2600 0xee00 0x5d03 0x2d00 0x0000 0x1000 0x5e03 0xc300 0x8000 0x0600 0x0000 0x0100 0x0000 ...0x5e03 0xd200 0x5d03 0x6900 0x0000 0x1000 0x5d03 0x5a00 0x6900 0x0000 0x1000 0x5e03 0x4b00 0x1200 0x0100 0x1000 0x0100 0x0000 ...0x5e03 0xd200 0x5d03 0xe100 0x0000 0x1000 0x5d03 0x4b00 0x0000 0x0000 0x5e03 0x5a00


Мы видим, что в некоторых местах PIDы склеились, не будучи разделёнными командой (такие приклеившиеся вещи я выделил жёлтым). Значит, решение близкое к верному, но не совсем

Фильтр номер два


Правильный фильтр я подсмотрел в проекте usbsniffer. Я был почти прав. Оказывается, в пределах одного пакета шины ULPI (то есть без падения линии DIR) может пройти несколько USB-запросов. Поэтому состояние wait1 я зря правил. Надо просто сохранять те команды, где RxActive перешёл из нуля в единицу, в каком бы месте пакета это ни случилось. Замечательно. Если мы ловим переход, то нам нужно защёлкивать прошлое значение. То есть добавить в код процесс, который его защёлкивает. Важно только защёлкивать, когда передаётся команда и не защёлкивать в остальных случаях. Признак команды живёт в source_data[8], защёлкиваемое значение в source_data[4].
logic active_prev = 0;always_ff @(posedge ulpi_clk)begin     if (source_data[8])         active_prev <= source_data[4];end

Ну, и теперь, имея текущее и предыдущее значение, мы можем написать:
logic activated;assign activated = (!active_prev) & source_data[4];

Кто заметил, что я здесь не анализирую факт команды? Всё в порядке, это не ошибка. Просто теперь готовность шины AVALON_ST я задаю так:
assign source_valid = source_data[8]?(source_valid_priv & go & activated):(source_valid_priv & go);

Если команда, то анализировать этот флаг. Иначе не анализировать. В итоге, получился такой симпатичный дамп, в котором все пакеты A5 начинаются после персональной команды (то есть, с начала строки):
0x5d01 0xa500 0x4e00 0x1c00 0x5d01 0xa500 0x4f00 0xe400 0x5d01 0xa500 0x5000 0x0c00 0x5d01 0xa500 0x5100 0xf400 0x5d01 0xa500 0x5200 0xb400 0x5d01 0x2d00 0x0000 0x1000 0x5e01 0xc300 0x8000 0x0600 0x0000 0x0100 0x0000 ...0x5e01 0xd200 0x5d01 0x6900 0x0000 0x1000 0x5d01 0x5a00 0x5d01 0x6900 0x0000 0x1000 0x5e01 0x4b00 0x1200 0x0100 0x1000 0x0100 0x0000 ...0x5e01 0xd200

Если что:
  • A5 PID_SOF
  • 2D PID_SETUP
  • C3 PID_DATA0
  • D2 PID_ACK
  • 69 PID_IN
  • 4B PID_DATA1

Вроде, последовательность пакетов вполне себе логичная. То есть, анализатор начерно работает.

Заключение


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

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

Что же касается самого анализатора, то этот блок статей, как я уже упоминал раньше, был написан в июне. Дальше были сделаны заготовки ещё для пары статей (разумеется, продвинулся и сам анализатор), после чего руководство бросило меня в бой по совершенно другим задачам. И завертелось Так что пока в теме будет сделана небольшая пауза. Но, во-первых, в планах стоит развитие анализатора, а во-вторых, рейтинг у статей такой, что видно, что они кому-то нужны (в отличие от статей про Линукс, одна из которых собрала один балл рейтинга и жалкое количество просмотров, но я пингвинов тоже не люблю). Поэтому наверняка будет продолжение. Но пока что перерыв. Однако, все желающие могут начать свои практические исследования, опираясь на полученные знания.

Для справки.
Исходный код головы USB на момент завершения данной статьи выглядит так.
Смотреть исходный код головы USB.
module ULPIhead(   input               reset,   output              clk60,   // AVALON_MM   input        [2:0]  address,   input               write,   input        [31:0] writedata,   input               read,   output logic [31:0] readdata = 0,   // AVALON_ST   input  logic        source_ready,   output logic        source_valid,   output logic [15:0] source_data = 0,   // ULPI   inout        [7:0]  ulpi_data,   output logic        ulpi_stp = 0,   input               ulpi_nxt,   input               ulpi_dir,   input               ulpi_clk,   output              ulpi_rst);logic      have_reg = 0;logic      reg_served = 0;logic      reg_request = 0;logic      read_finished = 0;logic [5:0] addr_to_ulpi;logic [7:0] data_to_ulpi;logic [7:0] data_from_ulpi;logic      write_busy = 0;logic      read_busy = 0;logic [7:0] ulpi_d = 0;logic force_reset = 0;logic active_prev = 0;logic activated;assign activated = (!active_prev) & source_data[4];always_ff @(posedge ulpi_clk)begin     if (source_data[8])         active_prev <= source_data[4];end// Это - для красоты. Счётчик переданных данныхlogic [31:0] transfer_cnt = 0;// Его красивое формирование:logic go;logic go_prev;logic source_valid_priv;assign source_valid = source_data[8]?(source_valid_priv & go & activated):(source_valid_priv & go);always_ff @(posedge ulpi_clk)begin    // Для ловли перепада, нам надо знать    // предыдущее значение бита "go"    go_prev <= go;    // Если анализ только что запустился - сбросили счётчик    if ((!go_prev) && (go))         transfer_cnt <= 0;    // Иначе - считаем каждый такт, когда данные уехали в AVALON_ST    else if (go & source_valid_priv)         transfer_cnt <= transfer_cnt + 1;end// Формирование регистра статусаalways_ff @(posedge ulpi_clk)begin      // Приоритет у сброса выше      if  (reg_served)           write_busy <= 0;      else if (have_reg)           write_busy <= 1;      // Приоритет у сброса выше      if  (read_finished)           read_busy <= 0;      else if (reg_request)           read_busy <= 1;end// Обслуживание AVALON_MM на чтениеalways_comb begin   case (address)      // Регистр адреса (чисто для самоконтроля)      0 : readdata <= {26'b0, addr_to_ulpi};      // Регистр данных      1 : readdata <= {23'b0, data_from_ulpi};      // 2 - регистр управления, а он - только на запись      // Регистр статуса      3 : readdata <= {30'b0, (reg_request | read_busy), (have_reg | write_busy)};      // Счётчик переданных данных для красивого отображения      4: readdata <= transfer_cnt;      default: readdata <= 0;   endcaseend   // Обслуживание AVALON_MM на записьalways_ff @(posedge ulpi_clk)begin   // Назначение вещей по умолчанию, они могут быть перекрыты   // внутри условия сроком на один такт   have_reg    <= 0;   reg_request <= 0;   if (write == 1)    begin      case (address)          0 : addr_to_ulpi <= writedata [5:0];          // Запись в регистр данных требует сложной работы          1 : begin                data_to_ulpi <= writedata [7:0];                have_reg <= 1;              end          2 : begin                // Младший бит регистра инициирует процесс чтения                reg_request <= writedata[0];                // Бит 1 - запуск анализа,                // Без него данные наружу не выйдут                go <= writedata [1];force_reset = writedata [31];              end         default: begin end      endcase   endend   // Самый главный автоматenum {idle,wait1,wr_st,wait_nxt_w,hold_w,wait_nxt_r,wait_dir1,latch,wait_dir0} state = idle;always_ff @ (posedge ulpi_clk)begin   if (reset)   begin       state <= idle;   end else   begin      // Присвоение сигналов по умолчанию      source_valid_priv <= 0;      reg_served  <= 0;      ulpi_stp <= 0;      read_finished <= 0;      case (state)      idle: begin           if (ulpi_dir)               state <= wait1;           else if (have_reg)                 begin                  // Как я и рассуждал в документе, команду                  // мы выставим прямо тут, не будем плодить                  // состояния                  ulpi_d [7:6] <= 2'b10;                  ulpi_d [5:0] <= addr_to_ulpi;                  state <= wait_nxt_w;                end           else if (reg_request)                begin                  // Логика - как для записи                  ulpi_d [7:6] <= 2'b11;                  ulpi_d [5:0] <= addr_to_ulpi;                  state <= wait_nxt_r;                end         end      // Здесь мы просто пропускаем такт TURN_AROUND      wait1 : begin            state <= wr_st;            // Начиная со следующего такта, можно ловить данные            source_valid_priv <= 1;             // Бит 9 в единице отмечает начало пакета            source_data <= {7'h0,!ulpi_nxt,ulpi_data};         end      // Пока не изменится сигнал DIR - гоним данные в AVALON_ST      wr_st : begin            if (ulpi_dir)            begin              // На следующем тактеа, всё ещё ловим данные               source_valid_priv <= 1;               source_data <= {7'h0,!ulpi_nxt,ulpi_data};            end else               // В документе было ещё состояние wait2,               // но я решил, что оно - лишнее.                state <= idle;         end      wait_nxt_w : begin           if (ulpi_nxt)           begin              ulpi_d <= data_to_ulpi;              state <= hold_w;           end         end      hold_w: begin           // при моделировании выяснилось, что ULPI может           // быть не готова принимать данные. и снять NXT           // Добавил условие...           if (ulpi_nxt) begin              // Всё, по AVALON_MM можно принимать следующий байт              reg_served  <= 1;              ulpi_d <= 0;    // Шину в idle              ulpi_stp <= 1;  // На один такт взвели STP              state <= idle;  // А потом - уйдём в состояние idle           end         end       // От состояния STPw я решил отказаться...       // ...      // Это уже начало чтения. Ждём, когда скажут NXT      // И тем самым подтвердят, что наша команда распознана      wait_nxt_r : begin           if (ulpi_nxt)           begin              ulpi_d <= 0;    // Номер регистра можно убирать              state <= wait_dir1;           end         end      // Ждём, когда нам выдадут данные      wait_dir1: begin          if (ulpi_dir)             state <= latch;        end      // Тут мы защёлкиваем данные      // и без каких-либо условий идём дальше      latch: begin          data_from_ulpi <= ulpi_data;          state <= wait_dir0;        end      // Ждём, когда шина вернётся к чтению      wait_dir0: begin          if (!ulpi_dir)          begin             state <= idle;             read_finished <= 1;          end        end         default:begin         state <= idle;         end      endcase    endend// Так традиционно назначается выходное значение inout-линииassign ulpi_data = (ulpi_dir == 0) ? ulpi_d : 8'hzz;// reset мог прийти извне, а могли его и мы сформироватьassign ulpi_rst = reset | force_reset;assign clk60 = ulpi_clk;endmodule

Подробнее..

Учимся работать с 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-командами или скриптами.

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

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

07.10.2020 08:20:40 | Автор: admin

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

Возвращаемся к публикации последних событий из мира FPGA/ПЛИС. Ниже приведены несколько ссылок на новости, анонсы, вебинары, воркшопы, туториалы, видео и тд. Подобные новостные дайджесты есть, например, на хабе про php, почему бы и не сделать что-то подобное и для ПЛИС?



Подробности в конце статьи :)

Мы решили вернуться к традиционному формату представления: картинка заголовок/ссылка небольшое описание

Новостная лента FPGA/ПЛИС сентябрь 2020


Начинающим: Выложены материалы Сколковской школы цифрового дизайна


С 15-17 сентября 2020 в рамках выставки Чип-Экспо проводилась СКОЛКОВСКАЯ ШКОЛА СИНТЕЗА ЦИФРОВХ СХЕМ НА VERILOG. Рады сообщить, что материалы этой школы запись видеолекций и исходники заданий выложены в открытый доступ на Youtube.
Стоит напомнить, что предложение по получению бесплатной отладки для школьников и студентов для прохождения школы, все еще действует.

Релиз Quartus Prime Pro Edition: Version 20.3

Intel FPGAвыпустила информацию об обновлении программного обеспечения Quartus Prime версии Pro. В частности, в новой версии:

Высокоуровневое программирование FPGA для терабитных линий связи

Что такое частота семплирования 5кГц? Для цифровой обработки сигналов даже на микроконтроллере такое число звучит вполне посильно. Однако, в задаче оптической связи между спуником и наземной станцией, где на последней расположен массив линз, каждая из которых фокусирует изображение на часть сенсора камеры ...

Это интересно: Шифровальная машина Enigma M3 в FPGA Intel MAX10

Шифровальная машина имела систему роторов, которые по некоторому закону делали многократную замену букв:

Онлайн конференция: Verification Day 2020

Компания Synopsys проводит онлайн конференциюVerification Day 2020.Это виртуальное мероприятие дает возможность оставаться в курсе последних инноваций, методов и методологий в области статической, формальной и маломощной верификации.

Вебинар: Презентация инструментов для верификации процессорных ядер на базе RISC-V

Static Verification for RISC-V Cores and SoCs

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

Реализация: Мондельбротим на PYNQ

Построение фракталов на ПЛИС, в целом не ново. На выставках частенько можно встретить их реализацию. Такое демо всегда привлекает, не то что эти ваши отчеты в консоли и распознавание котиков.

Анонс: Самая большая FPGA Xilinx перешла в статус Production

ПЛИС Virtex UltraScale+ VU19P позволяет создавать прототипы и эмулировать самые передовые ASIC и SoC, а также разрабатывать сложные алгоритмы. ПЛИС VU19P обладает самой высокой плотностью логики и количеством портов ввода-вывода это самая большая FPGA когда-либо выпускаемая компанией Xilinx....

Практика: Подключаем Xilinx FPGA софт-процессор к сенсорам ST Micro

на портале element14.com появился анонс трех предстоящих воркшопов по работе с MicroBlaze софт-процессор от компании Xilinx и его интеграции с несколькими сенсорами от компании ST Micro. Работа будет проходить на бюджетном отладочном комплекте Arty-S7.

Туториал: Демонстрация и моделирование RoE

В блоге компании Xilinx появилось новое руководство по моделированию радио-через-ethernet Roe
Enhanced CPRI (eCPRI) становится важной технологией для создания беспроводных приложений следующего поколения 5G. IP ядро Xilinx Radio over Ethernet Framer (RoE Framer) является частью полного системного р...


Это Интересно: Как устроена первая ПЛИС в мире


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


Заметка: 5 Вызовов для высокоуровнего синтеза на ПЛИС

Любопытная заметка появилась на сайте компании Silexica. John Inkeles рассматривает 5 пять проблем, с которыми приходится сталкиваться при использовании HLS для проектирования ПЛИС

Вебинар: Максимизация производительности системы при использовании FPGA с интерфейсом PCIe Gen5

На вебинаре от компании Achronix вы узнаете, как максимизировать производительность системы с помощью ПЛИС с интерфейсами PCIe Gen5. Вы поймете, почему вам нужно не только высокоскоростное подключение, но и возможность обрабатывать входящие данные на высоких скоростях для ускорения производительности приложений.

Вебинар: Проверка пользовательских инструкций RISC-V с помощью UVM

Благодаря интеграции в Aldec Rivera-PRO утилитыCodasips Studio становится возможным верификация пользовательских инструкций процессора RISC-V.



Заметка: Vivado подскажет, как свести тайминги в проекте (перевод)

Что делать, если тайминги вашего проекта не сходятся? Как найти эти места и как исправить положение?
Ответы вопросы вам поможет найти сама среда разработки Xilinx Vivado. В Vivado есть просто невероятное количество отчётов, формируемых по ходу маршрута проектирования вашего проекта.



Онлайн-семинар: Разработка ускорителей в Xilinx Vitis




Единая программная платформа Vitis позволяет разработчикам легко использовать преимущества гетерогенных SoC Xilinx и ускорять свои приложения, не нуждаясь в передовых знаниях по разработке аппаратного обеспечения. Этот семинар предоставит вам базовые знания о том, как начать работу с Vitis и Vitis AI.


Очень маленькая FPGA плата M02mini от marsohod.org

Команда проекта marsohod.orgсообщила о выходе новой очень маленькой FPGA платы M02mini на базе чипа Intel MAX10 10M02DCV36C8.

Аналитика: Сможет ли HLS код побить HDL по производительности? (перевод)

По общему убеждению, HLS проигрывает в производительности написанному вручную HDL. С правильным выбором инструментов может оказаться верным обратное утверждение.



Cadence открыла бесплатный доступ ко всем видео-тренингам и обучающим материалам


Компания Cadence открыла свои учебные курсы. Теперь для того чтобы обучаться по программам Cadence достаточно только иметь активированный Cadence Support Account.


Руководство: Делаем алгоритм шифрования AES на Verilog


Интересное руководство попалось на портале medium.com
Читателю предлагается пошаговое руководство реализации алгоритма AES 128 для FPGA/ASIC с использованием языка Verilog.

Туториал: Генерация синусоиды на C и Xilinx Zynq

На портале hackster.io появилась новая статья/гайд по использованию созданию генератора синусоиды с использованием Xilinx DDS Compiler + C+ baremetal + Zynq. Это полноценное руководство с боль...

Презентация: Компиляция для Xilinx AI Engine с использованием MLIR

Если вы интересуетесь развертыванием нейронных сетей и ИИ на новых адаптируемых ACAP Versal AI, то эта презентация вероятно вам пригодится при изучении. Несмотря на ее относительную давность создания (02-02-2020), презентация пригодится как дополнительный материал для изучения. Возможно, вам удастся узнать что-то нов...

Проведение тестирования проекта с помощью VUnit (перевод)

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



Конференция FPGA разработчиков


Конференции по FPGA за рубежом проходят достатоно часто, по крайней мере 3-4 раза в год, что нельзя сказать про отечетсвенный сегмент. Ребята вот из этого проекта организуют конференцию по FPGA тематике, которая пройдет в Мск 28 ноября и Спб 5 декабря, также планируется онлайн трансляция мероприятия. Участие в конференции бесплатное.


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


PS: по словам организаторов, колчество докладов для Мск уже хватает, чего нельзя сказать про СПб. Поэтому если Вы планируете учавствовать в Санкт-Петербурге, то может у Вас есть что рассказать за FPGA ?



Предыдущие выпуски новостного дайджеста


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


Новостной дайджест событий из мира FPGA/ПЛИС 002-003 (2020_02/2020_03)


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


Приглашаю желающих помочь в наполнении новостной ленты. Пишите в личку.

Подробнее..
Категории: Intel , Fpga , Плис , Lattice , Xilinx , Achronix

Новостной дайджест событий из мира 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

Временные ограничения для внешних интерфейсов ПЛИС

24.07.2020 00:21:22 | Автор: admin
Здравствуйте. В данной статье я хочу по возможности максимально просто и понятно рассказать о том, как рассчитываются временные ограничения (timing constraints) на синхронные интерфейсы ПЛИС. Просто не значит коротко, но зато простыми словами, которые вы сможете легко понять. Если вы новичок и перед вами стоит задача описать свой первый SPI, то данная статья должна вам помочь понять для чего нужны ограничения и как их рассчитать.

Введение


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

В предыдущей статье (ссылка) я рассказал о том, что из себя представляет передача данных между двумя регистрами и как рассчитывается выполнение временных соотношений в случае передачи данных внутри ПЛИС, когда оба рассматриваемых регистра, пути прохождения тактовых сигналов и сигналов данных лежат внутри ПЛИС. Из той статьи нужно вынести понимание концепции синхронной логики как сети, сплетенной из множества межрегистровых передач (рис.1), и термина слэк (рис.2).
В этой статье мы рассмотрим случаи передачи данных, в которых один из двух регистров передачи находится вне ПЛИС это регистр внешнего устройства микроконтроллера, АЦП и т.п.


Рис. 1. Схема межрегистровой передачи данных от регистра-источника (source) к регистру-получателю (destination).


Рис. 2. Смысл слэков относительно фронта захвата на входе регистра-получателя.

Общие формулы расчета слэков, выведенные в предыдущей статье:

$\min SetupSlack = \min DataRequired - \max DataArrival = $

$=T-CSU+\min t_{CLKtoDST}-\max t_{CLKtoSRC}-\max t_{SU}-\max t_{CO}-\max t_D$

$\min Hold Slack = \min DataArrival - \max DataRequired =$

$=-CHU+\min t_{CLKtoSRC}-\max t_{CLKtoDST}+\min t_{CO}-\max t_H+\min t_D$


Пробежимся по терминам.
Слэк (slack) это запас по времени. Он должен быть положительным.
Время предустановки (setup time, $t_{SU}$) минимальное время, которое данные должны уже находиться на входе регистра-получателя на момент прихода фронта клока на тактовый вход получателя.
Время удержания (hold time, $t_H$) минимальное время, которое данные должны всё ещё держаться на входе получателя после момента прихода фронта клока на тактовый вход получателя.
Время срабатывания регистра (clock-to-output time, $t_{CO}$) время от прихода фронта клока на тактовый вход регистра-источника до появления на его выходе новых стабильных данных.
Нестабильность тактовой частоты по предустановке/удержанию (CSU/CHU) мера нестабильности тактового сигнала. В расчетах несет смысл запаса на всякий случай для учтения явлений, вызванных неидеальной периодичностью клока.
$t_D$ это время прохождения сигналом данных пути между регистрами.
$t_{CLKtoSRC}$ это время, за которое фронт тактового сигнала доходит от источника тактового сигнала до тактового входа регистра-источника. А от источника тактового сигнала до регистра-получателя соответственно $t_{CLKtoDST}$.
Пути прохождения сигналов проиллюстрированы на рисунке 3.


Рис. 3. Обозначение времени прохождения путей тактовых сигналов и данных.

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

Виды анализируемых передач


Давайте проведем классификацию случаев для временного анализа. Анализ проводится отдельно для каждой ножки ПЛИС относительно некоторого клока. В результате анализа оценивается возможность ножки корректно отдать или принять данные. Внешний интерфейс синхронного обмена данными ПЛИС с внешним устройством представляет собой несколько линий, подведенных к ножкам. Из них одна линия это линия клока. Он должен быть общим для регистров на обеих сторонах. Остальные линии, одна или множество, это однонаправленные линии ввода или вывода. То, что для ПЛИС является линией ввода, для внешнего устройства является линией вывода, и наоборот. В данной статье мы рассматриваем только простейшие случаи временного анализа, поэтому не касаемся асинхронных и самосинхронных интерфейсов, а также использования двунаправленных линий ввода-вывода.
Случаи анализа можно классифицировать по направлению данных относительно ПЛИС (Input/Output) и по направлению клока относительно ПЛИС (System/Source Synchronous). Для каждого случая есть два типа анализа (Setup/Hold). Итого, четыре случая для анализа и восемь уравнений, которые нам надо вывести.

Sytem Synchronous Output / Вывод данных, тактированных внутренним клоком ПЛИС


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

Рис. 4. Передача данных наружу по собственному клоку ПЛИС.

На рисунке 4 мы видим схему межрегистровой передачи. На ней показаны времена процессов, относящиеся к каждому из регистров, и времена, за которые сигналы проходят пути.
Я задал следующую индексацию: время, относящееся к данным, помечено буквой D. Время, относящееся к клоку, помечено буквой C. Если событие происходит внутри ПЛИС, то его время помечено индексом int, а если во внешнем устройстве, то ext. Время на путь клока от источника клока до выходной клоковой ножки имеет самый длинный индекс Cintout. DataTrace и ClkTrace это времена на проход соответствующих дорожек на печатной плате. Блок CLK символизирует собой некий внутренний источник клока.
Главная черта упомянутых на рисунке величин все они положительные. Все они равны абсолютной длительности каких-либо физических процессов, а значит физически могут быть только положительными величинами. Это важно для дальнейших рассуждений.

Теперь мы максимально подробно рассмотрим анализ слэка предустановки. Глядя на рисунок 4 мы легко соотносим увиденные величины со слагаемыми формул слэков:

$t_{CLKtoSRC} = t_{Cint}$

$t_D = t_{Dint}+DataTrace+t_{Dext}$

$t_{CLKtoDST}=t_{Cintout}+ClkTrace+t_{Cext}$

Теперь подставим эти равенства в формулу слэка предустановки:

$\min SetupSlack = \min DataRequired - \max DataArrival = $

$=T-CSU+\min t_{CLKtoDST}-\max t_{CLKtoSRC}-\max t_{SU}-\max t_{CO}-\max t_D=$

$=T-CSU+\min (t_{Cintout}+ClkTrace+t_{Cext})-\max (t_{Cint})-t_{SU}-t_{CO}-$

$-\max (t_{Dint}+DataTrace+t_{Dext})$


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

$\min SetupSlack = T-CSU+\min t_{Cintout}+\min ClkTrace+\min t_{Cext} -\max t_{Cint}-$

$ -t_{SU}-t_{CO}-\max t_{Dint}-\max DataTrace-\max t_{Dext} $


Теперь переставим слагаемые местами так, чтобы сгруппировать в скобках слагаемые процессов внутри ПЛИС и процессов внутри внешнего устройства:

$\min SetupSlack = T-CSU-(\max t_{Cint}+t_{CO}+\max t_{Dint}-\min t_{Cintout})+$

$ +\min ClkTrace-\max DataTrace-(\max t_{Dext}+t_{SU}-\min t_{Cext}) $


Что же мы видим в скобках? Если присмотреться и еще раз вспомнить про то, что все указанные величины больше нуля, то можно сказать, что мы видим выражение некого эквивалентного (со звездочкой) времени срабатывания регистра ПЛИС и эквивалентного времени предустановки регистра внешнего устройства:

$\max t_{Cint}+t_{CO}+\max t_{Dint}-\min t_{Cintout}=\max t_{CO}^*$

$\max t_{Dext}+t_{SU}-\min t_{Cext}=\max t_{SU}^*$

$\min SetupSlack = T-CSU-\max t_{CO}^*+\min ClkTrace-\max DataTrace-\max t_{SU}^* $


Почему бы нам не пользоваться эквивалентными величинами, если производители микросхем уже посчитали их за нас с учетом своих внутренних задержек? Эквивалентные значения на стороне ПЛИС рассчитает компилятор без участия пользователя, а эквивалентные значения внешнего устройства указываются в явном виде в даташите на устройство.
Теперь вглядимся внимательно еще раз в последнее выражение. В нем первые три слагаемых уже известны анализатору, ведь мы уже указали ему рабочую частоту и величину нестабильности. Компилятор произвел трассировку и анализатор знает эквивалентное время срабатывания своего регистра. Значит анализатору известна длительность всех процессов внутри ПЛИС. А вот последние три слагаемых уникальны для каждой ножки и пользователю необходимо самому их посчитать и указать анализатору в виде числа. Что же это за число? Рассмотрев внимательнее три последних слагаемых мы увидим, что их можно интерпретировать как отрицательный максимум некой величины:

$\min ClkTrace-\max DataTrace-\max t_{SU}^*=-\max OutputDelay $


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

$\min SetupSlack = T-CSU-\max t_{CO}^*-\max OutputDelay $

$ \max OutputDelay = \max DataTrace+\max t_{SU}^*-\min ClkTrace = $

$=\max (DataTrace+t_{SU}^*-ClkTrace )$


Теперь выведем аналогичное уравнение для слэка удержания. Вспомним общее уравнение и подставим в него новые слагаемые:

$\min Hold Slack = \min DataArrival - \max DataRequired =$

$=-CHU+\min t_{CLKtoSRC}-\max t_{CLKtoDST}+\min t_{CO}-\max t_H+\min t_D = $

$=-CHU+\min (t_{Cint})-\max (t_{Cintout}+ClkTrace+t_{Cext}) +t_{CO}- t_H+$

$+\min (t_{Dint}+DataTrace+t_{Dext}) $

Раскроем скобки и сразу же сгруппируем слагаемые вокруг каждого из регистров:

$\min Hold Slack =-CHU+(t_{CO}+\min t_{Cint}+\min t_{Dint}-\max t_{Cintout})-$

$ -\max ClkTrace +\min DataTrace-(t_H-\min t_{Dext}+\max t_{Cext}) $

И снова мы видим как слагаемые группируются в эквивалентные величины, на сей раз $t_{CO}$ и $t_H$:

$\min Hold Slack =-CHU+\min t_{CO}^* +\min DataTrace -\max ClkTrace -\max t_H^* $

И последние три слагаемых можно понимать как минимальную внешнюю задержку:

$\min DataTrace -\max ClkTrace -\max t_H^* = \min OutputDelay$


Подытожим:

$\min Hold Slack =-CHU+\min t_{CO}^* +\min OutputDelay $

$\min OutputDelay = \min (DataTrace -ClkTrace - t_H^*) $


Надо подчеркнуть, что $\min OutputDelay $ и $\max OutputDelay $ это минимум и максимум не абсолютно одной и той же величины. В каждом анализе по предустановке и анализе по удержанию рассматриваются разные ситуации и разные внешние задержки. А значит и слагаемые, как видно из формул, берутся разные.

Source Synchronous Output / Вывод данных, тактированных внешним клоком


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

Рис. 5. Передача данных наружу по внешнему клоку.

На рисунке 5 мы видим источник клока снаружи. Соответственно исчезла величина $t_{Cintout}$ и появилось время прохода клока до внешнего устройства Clk Trace Ext.
Глядя на рисунок 5 соотнесём увиденные величины со слагаемыми общих формул слэков:

$t_{CLKtoSRC} = ClkTrace+ t_{Cint}$

$t_D = t_{Dint}+DataTrace+t_{Dext}$

$t_{CLKtoDST}=ClkTraceExt+t_{Cext}$


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

$\min SetupSlack = \min DataRequired - \max DataArrival = $

$=T-CSU+\min t_{CLKtoDST}-\max t_{CLKtoSRC}-\max t_{SU}-\max t_{CO}-\max t_D=$

$=T-CSU-(\max t_{Cint} +t_{CO} +\max t_{Dint})-\max DataTrace - $

$ -(\max t_{Dext}+t_{SU}-\min t_{Cext}) -\max ClkTrace+\min ClkTraceExt =$

$ = T-CSU-\max t_{CO}^*-\max (t_{SU}^*+DataTrace+ClkTrace-ClkTraceExt)$

$\min SetupSlack = T-CSU-\max t_{CO}^*-\max OutputDelay $

$ \max OutputDelay = \max (t_{SU}^*+DataTrace+ClkTrace-ClkTraceExt)$


Аналогично разбираем слэк удержания:

$\min Hold Slack = \min DataArrival - \max DataRequired =$

$=-CHU+\min t_{CLKtoSRC}-\max t_{CLKtoDST}+\min t_{CO}-\max t_H+\min t_D = $

$=-CHU+(\min t_{Cint}+t_{CO}+\min t_{Dint})+\min DataTrace+\min ClkTrace -$

$-(t_H-\min t_{Dext}+\max t_{Cext}) - \max ClkTraceExt$

$\min Hold Slack = -CHU+\min t_{CO}^* + \min OutputDelay$

$ \min OutputDelay= \min (DataTrace+ClkTrace-ClkTraceExt-t_H^*) $


Если в этой схеме рассмотреть частный (но частый) случай, когда источник клока находится внутри внешнего устройства, то выведенные формулы изменятся только лишь тем, что ClkTraceExt станет в них равным нулю. Перемещение источника клока вовнутрь внешнего устройства вызовет в наших расчетах микроскопическое растекание клока внутри внешнего устройства между тактовым выходом и регистром, но оно будет учтено производителем внешнего устройства и войдет в величины эквивалентных $t_{SU}^*$ и $t_H^*$.

System Synchronous Input / Ввод данных, тактированных внутренним клоком ПЛИС


Теперь переходим к рассмотрению входных ног синхронного интерфейса. ПЛИС и внешнее устройство на рисунке 6 поменялись местами.

Рис. 6. Прием данных по собственному клоку ПЛИС.

Анализ такой схемы ничуть не сложнее предыдущих.

$t_{CLKtoSRC} = t_{Cintout} +ClkTrace +t_{Cext}$

$t_D = t_{Dext}+DataTrace+t_{Dint}$

$t_{CLKtoDST}=t_{Cint}$

Раскрываем, группируем

$\min SetupSlack = \min DataRequired - \max DataArrival = $

$=T-CSU+\min t_{CLKtoDST}-\max t_{CLKtoSRC}-\max t_{SU}-\max t_{CO}-\max t_D=$

$=T-CSU-(t_{SU}+\max t_{Dint}-\min t_{Cint} +\max t_{Cintout}) -\max DataTrace - $

$ -(t_{CO}+\max t_{Dext} +\max t_{Cext} )- \max ClkTrace $

И получаем величину внешней задержки, которую на этот раз уже мы назовем входной:

$\min SetupSlack = T-CSU-\max t_{SU}^* -\max InputDelay$

$\max InputDelay = \max (DataTrace + t_{CO}^* +ClkTrace)$


Теперь слэк удержания:

$\min Hold Slack = \min DataArrival - \max DataRequired =$

$=-CHU+\min t_{CLKtoSRC}-\max t_{CLKtoDST}+\min t_{CO}-\max t_H+\min t_D = $

$=-CHU-(t_H-\min t_{Dint} + \max t_{Cint} - \min t_{Cintout})+\min DataTrace + $

$ + (t_{CO}+ \min t_{Dext} + \min t_{Cext}) +\min ClkTrace $

$\min Hold Slack = -CHU-\max t_H^*+\min InputDelay$

$ \min InputDelay= \min (t_{CO}^*+DataTrace+ClkTrace) $



Source Synchronous Input / Ввод данных, тактированных внешним клоком


Также, по накатанной, смотрим на схему передачи данных и раскрываем слагаемые общей формулы.

Рис. 7. Прием данных по внешнему клоку.

$t_{CLKtoSRC} = ClkTraceExt +t_{Cext}$

$t_D = t_{Dext}+DataTrace+t_{Dint}$

$t_{CLKtoDST}=ClkTrace+t_{Cint}$


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

$\min SetupSlack = \min DataRequired - \max DataArrival = $

$=T-CSU+\min t_{CLKtoDST}-\max t_{CLKtoSRC}-\max t_{SU}-\max t_{CO}-\max t_D=$

$=T-CSU-(t_{SU}+\max t_{Dint}-\min t_{Cint})-\max DataTrace - $

$ -(t_{CO}+\max t_{Dext}+\max t_{Cext}) -\max ClkTraceExt+\min ClkTrace=$

$ = T-CSU-\max t_{SU}^*-\max (t_{CO}^*+DataTrace+ClkTraceExt-ClkTrace)$

$\min SetupSlack = T-CSU-\max t_{SU}^*-\max InputDelay $

$ \max InputDelay = \max (t_{CO}^*+DataTrace+ClkTraceExt-ClkTrace)$


Формула слэка удержания:

$\min Hold Slack = \min DataArrival - \max DataRequired =$

$=-CHU+\min t_{CLKtoSRC}-\max t_{CLKtoDST}+\min t_{CO}-\max t_H+\min t_D = $

$=-CHU-(t_H-\min t_{Dint}+\max t_{Cint})+\min DataTrace+$

$+(t_{CO}+\min t_{Dext}+\min t_{Cext})+\min ClkTraceExt - \max ClkTrace$

$\min Hold Slack = -CHU-\max t_H^* + \min InputDelay$

$ \min InputDelay= \min (t_{CO}^*+DataTrace+ClkTraceExt-ClkTrace) $


Опять же, если источник клока находится внутри внешнего устройства то просто приравниваем ClkTraceExt нулю.

Как пользоваться полученными формулами


Мы получили формулы внешних задержек и можем рассчитать конкретные числа задержек, чтобы вписать их в файл временных ограничений. Эти формулы глобально состоят из двух видов слагаемых это временные характеристики портов внешнего устройства и задержка на дорожках платы.
Характеристики портов обычно находятся в даташитах в разделах под названиями вроде Electrical Characteristics / AC Specifications / Timing Requirements. Порой эти характеристики названы другими именами и придется проявить смекалку, чтобы найти их. Но таблицы с числами как правило сопровождаются временными диаграммами, которые позволят вам идентифицировать нужный параметр.

С дорожками несколько сложнее. Точный расчет задержки на плате вопрос нетривиальный. Задержка зависит от длины, ширины, толщины и углов поворота дорожки, от толщины и материала платы, от расстояния до разных земляных слоев, от близости дорожек друг к другу и от множества иных факторов. Впрочем влияние каждого из этих факторов довольно невелико и на низких частотах интерфейсов, до десятков мегагерц, этими сложностями можно пренебречь. Упрощенный расчет задержки на дорожке выглядит так: скорость распространения волны в фольге считается равной половине скорости света в вакууме. В пересчете это дает примерно 0.007 нс/мм. Погрешность такой оценки нивелируется широким диапазоном оценки задержки. Для максимальной задержки считаем удельную задержку равной 0.010 нс/мм, а для минимальной 0.005 нс/мм.
Есть еще один нюанс. Схемотехники, проектируя печатные платы, стараются протягивать дорожки синхронных интерфейсов примерно одинаковым путем и соблюсти их равные длины. Проверьте длины дорожек данных и клока на своей целевой плате. Скорее всего они почти равны и компонент выражений (DataTrace ClkTrace) на практике можно считать нулевым.
Если на пути данных или клока стоит элемент вносящий задержку ее тоже нужно учесть. Это может быть буфер, инвертор, преобразователь уровня или гальваноразвязка. Такие элементы способны вносить очень большую задержку, десятки наносекунд, поэтому к ним нужно отнестись очень внимательно.

Заключение


В данной статье вы увидели формулы, по которым определяется выполнение временных ограничений в синхронных интерфейсах. Зная эти формулы можно понимать от чего зависит успешная передача данных и как рассчитать верные ограничения для анализатора. В следующей статье я приведу пример оформления файла временных ограничений и использования анализатора.
Подробнее..
Категории: Fpga , Плис , Constraints , Sdc

Категории

Последние комментарии

  • Имя: Макс
    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