Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате

в 13:00, , рубрики: ARM+FPGA, AXI_DMA, C, c++, fpga, HLS, IP-ядра, linux, opencl, PCI, promwad, SoC, xilinx, интерфейсная плата, ПЛИС, Программирование, Производство и разработка электроники, Разработка под Linux, разработка электроники, системное программирование

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 1

В этой статье мы поделимся опытом разработки интерфейсных плат блока сопряжения на базе SoC ARM+FPGA Xilinx Zynq 7000. Платы предназначались для записи речевых сигналов в аналоговом и цифровом формате PRI/BRI (ISDN, E1/T1). Само конечное устройство будет использоваться для фиксации переговоров в гражданской авиации.

Железо: выбор аппаратной платформы устройства

Выбор аппаратной платформы был обусловлен поддержкой протоколов PRI/BRI, которые можно реализовать только на стороне FPGA. Микроконтроллеры (MCU) и микропроцессоры (MPU) не подходили.

Можно было выбрать два решения этой задачи:

  1. синтез IP-ядра Microblaze,
  2. SoC Zynq-7000.

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

Итого, в рамках проекта собрался следующий список железа:

1. Xilinx Zynq 7020 (Mars-ZX3 и Mars EB1)
Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 2
SOM-модуль Mars ZX3 компании Enclustra
Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 3
Базовая плата Mars EB1 компании Enclustra

2. TI TLV320AIC34 (tlv320aic34evm-k и USB motherboard).

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 4
Отладочная плата для tlv320aic34(tlv320aic34evm-k)

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 5
Плата расширения USB-MODEVM для tlv320aic34evm-k

3. Микросхемы IDT82P2288 — PRI, XHFC-4SU — BRI, отладочных комплектов не было, поэтому мы заложили только фундамент в качестве ip-ядра для тестирования, а боевое крещение происходило прямо в процессе работы, после изготовления опытных образцов плат.

Работа с системой на кристалле Xilinx Zynq 7000

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 6
Внутренняя структура SoC Xilinx Zynq 7000

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 7
Этапы формирования загрузочных файлов для Xilinx Zynq

Прошивка/загрузка исполняемых файлов для Zynq отличается от привычной загрузки для MPU. Привычная работа с процессорами Cortex-A — это загрузка u-boot, kernel linux, rootfs. А на Zynq появляется bitstream, файл прошивки для ПЛИС (FPGA). В bitstream содержится описание аппаратных блоков на ПЛИС и внутренняя связь с процессором. Этот файл загружается при старте системы. Также на стороне linux есть механизм, который позволяет прошивать PL-часть сразу во время работы, такое устройство называется xdevcfg (ZYNQ FPGA manager с 2018.1).

Интерфейсы PRI/BRI

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 8
Особенности цифровых сетей PRI/BRI

Интерфейс первичного уровня (Primary Rate Interface, PRI) — стандартный интерфейс сети ISDN, определяющий дисциплину подключения станций ISDN к широкополосным магистралям, которые связывают местные и центральные АТС или сетевые коммутаторы.

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 9
Вид передаваемого фрейма для PRI

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 10
Вид передаваемого фрейма для BRI

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 11
Внутренняя структура PRI-физики — IDT82P2288

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 12
Внутренняя структура BRI-физики — XHFC-4SU

Аудиокодек TLV320AIC34

Четырехканальный маломощный аудиокодек TLV320AIC34 для портативного аудио и телефонии — хорошее решение для применения в аналоговой телефонии.

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 13
A-часть tlv320aic34, аудиокодек содержит два таких функциональных блока

Данные можно передавать через I2S-интерфейс, а также через DSP, PCM, TDM.

I2S — стандарт интерфейса последовательной шины, он используется для соединения цифровых аудиоустройств и электрически представляет собой 3 проводника, которые идут от активного устройства к пассивному, а также 4 сигнала, которые соответствуют им следующим образом:

  1. Тактовый сигнал битовой синхронизации (BCLK).
  2. Тактовый сигнал фреймовой (по словам) синхронизации (WCLK).
  3. Сигнал данных, который может передавать или принимать 2 разделенных по времени канала (DIN/DOUT).

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

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 14
I2S-фрейм, особенности работы I2S-интерфейса

После выбора всех аппаратных составляющих мы решали задачу по соединению аудиокодека и Xilinx Zynq 7020.

Поиск I2S-ядер

Наверное, самым сложным моментом при работе с аудиопотоком в Xilinx Zynq 7020 было то, что на процессорной части этой системы на кристалле в принципе нет I2S-шины, поэтому нужно было найти I2S-ядра. Эта задача усложнялась тем условием, что ip-ядро должно было быть бесплатным.

Мы остановились на нескольких ip-ядрах. Нашли для bare metal ядро I2S Digilent. Нашли несколько ip-ядер на opencores и, наверное, самый лучший для нас вариант — это ip-ядро Analog Devices. Они выпускают ip-ядра для своего оборудования, для взаимодействия ПЛИС/FPGA.

Нас заинтересовало ip-ядро под названием AXI-I2S-ADI. Сама компания Analog Devices продвигает эти ip-ядра для своих аппаратных платформ.

Итого, список прецедентов для работы:

  1. Bare metal — IP core для I2S (Digilent ZYBO audio)
  2. opencores.org
  3. AXI-I2S-ADI controller (Analog Devices)

IP-ядро AXI-I2S-ADI

Само ip-ядро выглядит следующим образом: у него есть линии bclk, wclk, din, dout. Оно подключается к DMA Xilinx Zynq 7000, в нашем примере используется DMA PS-части. Весь обмен данными происходит через DMA. DMA может быть отдельным блоком или составной частью PS SoC.

При конфигурации данного ip-ядра важно не забыть подать мастер-частоту mclk на сам tlv320aic34, как вариант при использовании отладочного комплекта для tlv320aic34 — подавать внешнюю мастер-частоту.

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 15
Функциональный блок с подключенной axi-i2s-adi

После процедуры конфигурирования стояла задача запуска функционала в ОС Linux.

Запуск и настройка device tree для tlv320aic34

Настройка i2c (по данному интерфейсу конфигурируется tlv320aic34):

i2c0: i2c@e0004000 {
            ...
                    tlv320aic3x: tlv320aic3x@18 {
                        #sound-dai-cells = <0>;
                        compatible = "ti,tlv320aic3x";
                               reg = <0x18>;
                              gpio-reset = <&axi_gpio_0 0 0>; 
                           ai3x-gpio-func = 
                           <&axi_gpio_0 1 0>, 
                              /* AIC3X_GPIO1_FUNC_DISABLED */
                            <&axi_gpio_0 2 0>;
                              /* AIC3X_GPIO2_FUNC_DIGITAL_MIC_INPUT */
                        AVDD-supply = <&vmmc2>;
                        DRVDD-supply = <&vmmc2>;
                       IOVDD-supply = <&vmmc2>;
                       DVDD-supply = <&vmmc2>;
                        ai3x-micbias-vg = <1>;
                    };            
...
        };

Настройка i2s (по данному интерфейс передаются аудиоданные):

i2s_clk: i2s_clk {
            #clock-cells = <0>;
            compatible = "fixed-clock";
            clock-frequency = <11289600>;
            clock-output-names = "i2s_clk";
        };
        
        axi_i2s_adi_0: axi_i2s_adi@43C00000 {
            compatible = "adi,axi-i2s-1.00.a";
            reg = <0x43C00000 0x1000>;                                                                                                      
            xlnx,bclk-pol = <0x0>;
            xlnx,dma-type = <0x1>;
            xlnx,has-rx = <0x1>;
            xlnx,has-tx = <0x1>;
            xlnx,lrclk-pol = <0x0>;
            xlnx,num-ch = <0x1>;
            xlnx,s-axi-min-size = <0x000001FF>;
            xlnx,slot-width = <0x18>;    
        };

&axi_i2s_adi_0 {
    #sound-dai-cells = <0>;
    compatible = "adi,axi-i2s-1.00.a";
    clocks = <&clkc 15>, <&i2s_clk>;
    clock-names = "axi", "ref";
    dmas = <&dmac_s 0 &dmac_s 1>;
    dma-names = "tx", "rx";
};

Настройка sound card в device tree (аудиокарты):

        sound {
            compatible = "simple-audio-card";
            simple-audio-card,name = "TLV320AIC34";
            simple-audio-card,format = "i2s";
            simple-audio-card,bitclock-master = <&dailink0_master>;
            simple-audio-card,frame-master = <&dailink0_master>;
            simple-audio-card,widgets =
            ...
            simple-audio-card,routing =
            ...
            dailink0_master: simple-audio-card,cpu {
                clocks = <&i2s_clk>;
                sound-dai = <&axi_i2s_adi_0>;
            };  
            simple-audio-card,codec {
                clocks = <&i2s_clk>;
                sound-dai = <&tlv320aic3x>;
            };
        };
    };

После всех манипуляций по настройке и конфигурированию кодека в дереве устройств в ОС Linux появилась заветная аудиокарта и мы смогли услышать музыку (наш первый музыкальных трек — Highway to Hell, AC/DC).

Вот что нам пришлось ради этого сделать:

  • Сформировали необходимую частоту с помощью clk_wiz (clocking wizard)
  • Правильно сконфигурировали DTS для tlv320aic34
  • Добавили поддержку драйвера tlv320aic3x
  • Добавили audio-пакеты в buildroot для воспроизведения audio stream (aplay, madplay и др.)

В процессе разработки конечного устройства перед нами стояла задача подключить 4 микросхемы tlv320aic34. Описанная выше микросхема tlv320aic34 содержит 2 блока по работе с аудиопотоком, каждый блок имеет собственную линию i2c по конфигурированию и настройке аудиопараметров. Блок может иметь только четыре адреса, соответственно, подключить четыре микросхемы tlv320aic34 на один интерфейс i2c невозможно, нужно использовать два интерфейса i2c (8 независимых аудиоблоков). На каждый блок, если заводить индивидуально mclk, blck, wclk, din/dout, суммарно нужно завести 40 линий сигнала, что невозможно и нерационально со схемотехнической точки зрения для выбранного нами som-модуля, ведь кроме этих сигналов нужно было подключить множество других линий и интерфейсов.

В итоге мы решили перевести аудиоплату на TDM-режим, в котором все линии mclk, bclk, din, dout объединяются, что позволяет сократить общие колличество линий связи. Это решение повлияло на работу axi-i2s-adi, из за того что само ip-ядро работало в мастер-режиме. Также это изменение не позволяло использовать наше ip-ядро в TDM-режиме, и волевым решением нам пришлось отказаться от использования выбранного ip-ядра. Пришлось писать ip-ядро для слушания i2s-трафика и отправки его в dma, такое решение позволило нам создать общий интерфейс по приему данных, который не зависел бы от типа платы для записи разговоров (аналоговая и цифровая платы).

Первоначальная архитектура приема аудиопотока и его обработки по интерфейсу i2s:

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 16

Окончательная архитектура приема аудиопотока и его обработки по интерфейсу i2s:

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 17

Архитектура приема PRI-потока и его обработки:

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 18

Архитектура приема BRI-потока и его обработки:

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 19

AXI DMA

Это важный элемент системы синхронизации обмена данным для dma.

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 20
Окно конфигурации AXI DMA в Xilinx Vivado

На принтскрине представлен сам AXI DMA-блок. У него множество параметров. Можно настроить шину, сколько данных передавать. Данные могут быть выровненными или быть в произвольном формате. Подробное описание работы и взаимодействия с axi dma описано в технической документации (от версии к версии идет дополнение и исправление неточностей в описании, а также доработка ip-ядер).

Проверка передачи данных через AXI DMA, варианты тестирования AXI DMA

При разработке драйвера мы решили найти open source и адаптировать его под нашу задачу. В итоге выбрали исходники github-проекта ezdma (игра слов, читается как easy dma).

Следующий шаг — разработка тестового драйвера, это был подготовительный этап в ожидании момента момента, когда к нам придет ip-ядро с уже готовой функциональностью из отдела FPGA-разработки (описанный процесс проработки формировали embedded-программисты). До наступления этого момента мы решили взять AXI DMA, AXI DATA FIFO и сделать look back, чтобы подстраховаться от возможных будущих ошибок. Закольцевали отправку и прием данных, так мы проверили результат своей работы и работоспособность нашего драйвера. Мы немного адаптировали функциональность, довели ее до наших пожеланий по интерфейсу взаимодействия и еще раз проверили работоспособность драйвера и выбранного принципа взаимодействия.

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 21
Блок-дизайн с look-back, первый способ проверки AXI DMA

Пример описание DMA и ezdma в device tree:

/ {
    amba_pl: amba_pl {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "simple-bus";
        ranges ;
        axi_dma_1: axi_dma {
            #dma-cells = <1>;
            compatible = "xlnx,axi-dma-1.00.a";
            reg = <0x40400000 0x10000>;
            clock-names = "s_axi_lite_aclk", "m_axi_sg_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk";
            clocks = <&clkc 15>, <&clkc 15>, <&clkc 15>, <&clkc 15>;
            interrupt-parent = <&intc>;
            interrupts = <0 29 4 0 30 4>;
            xlnx,addrwidth = <0x20>;
            xlnx,include-sg;
            dma-channel@40400000 {
                compatible = "xlnx,axi-dma-mm2s-channel";
                dma-channels = <0x1>;
                interrupts = <0 29 4>;
                xlnx,datawidth = <0x20>;
                xlnx,device-id = <0x0>;
                xlnx,include-dre ;
            };
            dma-channel@40400030 {
                compatible = "xlnx,axi-dma-s2mm-channel";
                dma-channels = <0x1>;
                interrupts = <0 30 4>;
                xlnx,datawidth = <0x20>;
                xlnx,device-id = <0x0>;
                xlnx,include-dre ;
            };
        };
        ezdma0 {
            compatible = "ezdma";
            dmas = <&axi_dma_1 0 &axi_dma_1 1>;
            dma-names = "loop_tx", "loop_rx";    // used when obtaining reference to above DMA core using dma_request_slave_channel()
            ezdma,dirs = <2 1>;                  // direction of DMA channel: 1 = RX (dev->cpu), 2 = TX (cpu->dev)
        };
    ...
    };
};

Генерацию dts/dtsi-файлов можно с легкость делать с помощью инструмента Device Tree Generator.

Второй шаг в нашем процессе разработки — создание тестового ip-ядра для проверки работоспособности драйвера, только на это раз данные будут осмысленными, с передачей по AXIS в AXI_DMA (как это будет в конечном варианте ip-ядра).

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 22
Циклограмма работы AXIS-интерфейса

Мы реализуем два варианта ip-ядер для генерации данных, первый проверенный вариант реализация через verilog, второй — на HLS (HLS в данном контексте появился под лозунгом «стильно- модно-молодежно»).

Генератор данных на verilog (и вообще на языках семейства hdl — verilog, vhdl и т.д.), является стандартным решением при разработке ip-ядер такого типа. Вот небольшие выдержки кода промежуточного ip-ядра:

module GenCnt (
    ….
    assign HandsHake = m_axis_din_tready & m_axis_dout_tvalid;
    always @(posedge Clk) begin
        if (Rst) begin
            smCnt <= sIDLE;
        end else begin
            case (smCnt)   
                sIDLE: begin            
                    smCnt <= sDATA;
                    end
                sDATA: begin
                    if (Cnt == cTopCnt - 1) begin
                        smCnt <= sLAST;
                    end
                end
...
endmodule

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

Более интересным «зверьком» здесь является HLS. Vivado HLS (High Level Synthesis) – новая САПР Xilinx для создания цифровых устройств с применением языков высокого уровня, таких как OpenCL, C или C++.

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

Приведем два небольших примера по работе с HLS. Первый пример — это генератор данных для AXI_DMA, второй пример — это обмен данными между процессорной частью и программируемой логикой по интерфейсу s_axilite.

Обмен данными по интерфейсу s_axilite (второй пример) был реализован, чтобы в любой момент времени в procfs можно было вычитать, какой bitstream загружен, и чтобы можно было отслеживать корректность работы по версионности для PL-части SoC. Тут появляется очень интересный момент c s_axilite: Vivado HLS генерирует драйвер для Linux (драйвер в свою очередь мы адаптировали для работы через procfs, чтобы сохранить наследственность по написанию). Пример сгенерировано кода для Linux ниже (путь к исходникам solution1/impl/ip/drivers/name_xxx/src/).

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 23
Этапы синтеза HLS и формирования rtl-кода

Генератор данных на HLS для проверки работы с AXI_DMA:

#include <ap_axi_sdata.h>
#include <hls_stream.h>
#define SIZE_STREAM 1024

struct axis {
    int  tdata;
    bool tlast;
};
void data_generation(axis outStream[SIZE_STREAM])
{
#pragma HLS INTERFACE axis port=outStream

    int i = 0;
    do{
        outStream[i].tdata = i;
        outStream[i].tlast = (i == (SIZE_STREAM - 1)) ? 1 : 0;
        i++;
    }while( i < SIZE_STREAM);
}

Пример получения версионности и типа интерфейсной платы:

#include <stdio.h>

void info(
        int &aVersion, int &bSubVersion, int &cTypeBoard,
        int version, int subVersion, int typeBoard
        ){
#pragma HLS INTERFACE s_axilite port=aVersion 
#pragma HLS INTERFACE s_axilite port=bSubVersion 
#pragma HLS INTERFACE s_axilite port=cTypeBoard 

#pragma HLS INTERFACE ap_ctrl_none port=return

    aVersion = version;
    bSubVersion = subVersion;
    cTypeBoard = typeBoard;

}

Как вы заметили, для разработки на hls очень важно понимать работу и применение различных прагм (HLS pragma), так как процесс синтеза напрямую завязан на прагмах.

Сгенерированный драйвер для s_axilite:

// ==============================================================
// File generated by Vivado(TM) HLS - High-Level Synthesis from C, C++ and SystemC
// Version: 2016.4
// Copyright (C) 1986-2016 Xilinx, Inc. All Rights Reserved.
// 
// ==============================================================

#ifdef __linux__

/***************************** Include Files *********************************/
#include "xinfo.h"

/***************** Macros (Inline Functions) Definitions *********************/
#define MAX_UIO_PATH_SIZE       256
#define MAX_UIO_NAME_SIZE       64
#define MAX_UIO_MAPS            5
#define UIO_INVALID_ADDR        0

/**************************** Type Definitions ******************************/
typedef struct {
    u32 addr;
    u32 size;
} XInfo_uio_map;

typedef struct {
    int  uio_fd;
    int  uio_num;
    char name[ MAX_UIO_NAME_SIZE ];
    char version[ MAX_UIO_NAME_SIZE ];
    XInfo_uio_map maps[ MAX_UIO_MAPS ];
} XInfo_uio_info;

/***************** Variable Definitions **************************************/
static XInfo_uio_info uio_info;

/************************** Function Implementation *************************/
static int line_from_file(char* filename, char* linebuf) {
    char* s;
    int i;
    FILE* fp = fopen(filename, "r");
    if (!fp) return -1;
    s = fgets(linebuf, MAX_UIO_NAME_SIZE, fp);
    fclose(fp);
    if (!s) return -2;
    for (i=0; (*s)&&(i<MAX_UIO_NAME_SIZE); i++) {
        if (*s == 'n') *s = 0;
        s++;
    }
    return 0;
}

static int uio_info_read_name(XInfo_uio_info* info) {
    char file[ MAX_UIO_PATH_SIZE ];
    sprintf(file, "/sys/class/uio/uio%d/name", info->uio_num);
    return line_from_file(file, info->name);
}

static int uio_info_read_version(XInfo_uio_info* info) {
    char file[ MAX_UIO_PATH_SIZE ];
    sprintf(file, "/sys/class/uio/uio%d/version", info->uio_num);
    return line_from_file(file, info->version);
}

static int uio_info_read_map_addr(XInfo_uio_info* info, int n) {
    int ret;
    char file[ MAX_UIO_PATH_SIZE ];
    info->maps[n].addr = UIO_INVALID_ADDR;
    sprintf(file, "/sys/class/uio/uio%d/maps/map%d/addr", info->uio_num, n);
    FILE* fp = fopen(file, "r");
    if (!fp) return -1;
    ret = fscanf(fp, "0x%x", &info->maps[n].addr);
    fclose(fp);
    if (ret < 0) return -2;
    return 0;
}

static int uio_info_read_map_size(XInfo_uio_info* info, int n) {
    int ret;
    char file[ MAX_UIO_PATH_SIZE ];
    sprintf(file, "/sys/class/uio/uio%d/maps/map%d/size", info->uio_num, n);
    FILE* fp = fopen(file, "r");
    if (!fp) return -1;
    ret = fscanf(fp, "0x%x", &info->maps[n].size);
    fclose(fp);
    if (ret < 0) return -2;
    return 0;
}

int XInfo_Initialize(XInfo *InstancePtr, const char* InstanceName) {
    XInfo_uio_info *InfoPtr = &uio_info;
    struct dirent **namelist;
    int i, n;
    char* s;
    char file[ MAX_UIO_PATH_SIZE ];
    char name[ MAX_UIO_NAME_SIZE ];
    int flag = 0;

    assert(InstancePtr != NULL);

    n = scandir("/sys/class/uio", &namelist, 0, alphasort);
    if (n < 0)  return XST_DEVICE_NOT_FOUND;
    for (i = 0;  i < n; i++) {
        strcpy(file, "/sys/class/uio/");
        strcat(file, namelist[i]->d_name);
        strcat(file, "/name");
        if ((line_from_file(file, name) == 0) && (strcmp(name, InstanceName) == 0)) {
            flag = 1;
            s = namelist[i]->d_name;
            s += 3; // "uio"
            InfoPtr->uio_num = atoi(s);
            break;
        }
    }
    if (flag == 0)  return XST_DEVICE_NOT_FOUND;

    uio_info_read_name(InfoPtr);
    uio_info_read_version(InfoPtr);
    for (n = 0; n < MAX_UIO_MAPS; ++n) {
        uio_info_read_map_addr(InfoPtr, n);
        uio_info_read_map_size(InfoPtr, n);
    }

    sprintf(file, "/dev/uio%d", InfoPtr->uio_num);
    if ((InfoPtr->uio_fd = open(file, O_RDWR)) < 0) {
        return XST_OPEN_DEVICE_FAILED;
    }

    // NOTE: slave interface 'Axilites' should be mapped to uioX/map0
    InstancePtr->Axilites_BaseAddress = (u32)mmap(NULL, InfoPtr->maps[0].size, PROT_READ|PROT_WRITE, MAP_SHARED, InfoPtr->uio_fd, 0 * getpagesize());
    assert(InstancePtr->Axilites_BaseAddress);

    InstancePtr->IsReady = XIL_COMPONENT_IS_READY;

    return XST_SUCCESS;
}

int XInfo_Release(XInfo *InstancePtr) {
    XInfo_uio_info *InfoPtr = &uio_info;

    assert(InstancePtr != NULL);
    assert(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);

    munmap((void*)InstancePtr->Axilites_BaseAddress, InfoPtr->maps[0].size);

    close(InfoPtr->uio_fd);

    return XST_SUCCESS;
}

#endif

Важный файл, который подскажет расположение переменных (регистров) в адресном пространстве, — файл x#ваше_имя#_hw.h. Всегда можно проверить правильность написанного ip-ядра, используя инструмент devmem.

Содержание данного файла:

// ==============================================================
// File generated by Vivado(TM) HLS - High-Level Synthesis from C, C++ and SystemC
// Version: 2016.4
// Copyright (C) 1986-2016 Xilinx, Inc. All Rights Reserved.
// 
// ==============================================================

// AXILiteS
// 0x00 : reserved
// 0x04 : reserved
// 0x08 : reserved
// 0x0c : reserved
// 0x10 : Data signal of aVersion
//        bit 31~0 - aVersion[31:0] (Read)
// 0x14 : Control signal of aVersion
//        bit 0  - aVersion_ap_vld (Read/COR)
//        others - reserved
// 0x18 : Data signal of bSubVersion
//        bit 31~0 - bSubVersion[31:0] (Read)
// 0x1c : Control signal of bSubVersion
//        bit 0  - bSubVersion_ap_vld (Read/COR)
//        others - reserved
// 0x20 : Data signal of cTypeBoard
//        bit 31~0 - cTypeBoard[31:0] (Read)
// 0x24 : Control signal of cTypeBoard
//        bit 0  - cTypeBoard_ap_vld (Read/COR)
//        others - reserved
// (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)

#define XINFO_AXILITES_ADDR_AVERSION_DATA    0x10
#define XINFO_AXILITES_BITS_AVERSION_DATA    32
#define XINFO_AXILITES_ADDR_AVERSION_CTRL    0x14
#define XINFO_AXILITES_ADDR_BSUBVERSION_DATA 0x18
#define XINFO_AXILITES_BITS_BSUBVERSION_DATA 32
#define XINFO_AXILITES_ADDR_BSUBVERSION_CTRL 0x1c
#define XINFO_AXILITES_ADDR_CTYPEBOARD_DATA  0x20
#define XINFO_AXILITES_BITS_CTYPEBOARD_DATA  32
#define XINFO_AXILITES_ADDR_CTYPEBOARD_CTRL  0x24

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

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 24
Пример выполнения проекта по тактам

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

При этом HLS не подходит для реализации конкретных аппаратных интерфейсов, например, в нашем случае это был I2S, а сгенерированный rtl-код занимает больше места на FPGA, чем написанный на стандартных hdl-языках.

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

Разработка интерфейсных плат на SoC Xilinx Zynq 7000 для записи речи в аналоговом и цифровом формате - 25
Блок-дизайн будущего тестирования custom I2S-ядра и генератор I2S-трафика

В итоге мы получили результаты работы hls, axi dma и s_axilite, проверили работоспособность нашего ПО и драйверов.

Выводы

Нам удалось разработать необходимые виды интерфейсных плат, а также ip-ядра для tdm, pri, bri. Мы значительно улучшили текущий подход при разработке таких устройств и создали комплексное решение, которое сможет конкурировать с аналогичными интерфейсными платам компаний Asterick, patton и других. Преимущество нашего решение в том, что разработчику для передачи данных не понадобится промежуточное звено связи PC с PCI, он сможет напрямую передавать принятую информацию по Ethernet.

Автор: Promwad

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js