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



    В этой статье мы поделимся опытом разработки интерфейсных плат блока сопряжения на базе 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)

    SOM-модуль Mars ZX3 компании Enclustra

    Базовая плата Mars EB1 компании Enclustra

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


    Отладочная плата для tlv320aic34(tlv320aic34evm-k)


    Плата расширения USB-MODEVM для tlv320aic34evm-k

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

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




    Внутренняя структура SoC Xilinx Zynq 7000


    Этапы формирования загрузочных файлов для 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



    Особенности цифровых сетей PRI/BRI

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


    Вид передаваемого фрейма для PRI


    Вид передаваемого фрейма для BRI


    Внутренняя структура PRI-физики — IDT82P2288


    Внутренняя структура BRI-физики — XHFC-4SU

    Аудиокодек TLV320AIC34


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


    A-часть tlv320aic34, аудиокодек содержит два таких функциональных блока

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

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

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

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


    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 — подавать внешнюю мастер-частоту.


    Функциональный блок с подключенной 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:



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



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



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



    AXI DMA


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


    Окно конфигурации 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 и сделать loopback, чтобы подстраховаться от возможных будущих ошибок. Закольцевали отправку и прием данных, так мы проверили результат своей работы и работоспособность нашего драйвера. Мы немного адаптировали функциональность, довели ее до наших пожеланий по интерфейсу взаимодействия и еще раз проверили работоспособность драйвера и выбранного принципа взаимодействия.


    Блок-дизайн с 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-ядра).


    Циклограмма работы 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/).


    Этапы синтеза 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
    

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


    Пример выполнения проекта по тактам

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

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

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


    Блок-дизайн будущего тестирования custom I2S-ядра и генератор I2S-трафика

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

    Выводы


    Нам удалось разработать необходимые виды интерфейсных плат, а также ip-ядра для tdm, pri, bri. Мы значительно улучшили текущий подход при разработке таких устройств и создали комплексное решение, которое сможет конкурировать с аналогичными интерфейсными платам компаний Asterick, patton и других. Преимущество нашего решения в том, что разработчику для передачи данных не понадобится промежуточное звено связи PC с PCI, он сможет напрямую передавать принятую информацию по Ethernet.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 16

      +3
      Спасибо за статью.
      С уважением отношусь к тем, кто делает что-то конкретное.
      Сколько человек работало над этим проектом, если не секрет?
      И в каком городе находится ядро команды?
      • UFO just landed and posted this here
          +2
          djinninia, спасибо за отзыв! Над проектом работали 3 программиста (linux embedded), 4 FPGA-программиста, команда схемотехников и трассировщиков.

          luntik2012 прав. Наш основной центр разработок расположен в Минске.
          +1
          При проверке передачи данных через Axi DMA замеряли производительность? Интересно было бы посмотреть на реальные цифры.
            +3
            не похоже чтоб в их задаче стояла проблема скорости.
            двух мегагерцовый поток PRI это очень медленно и очень маленький трафик.
            я такой на атмеге8515-ой разруливал в реалтайме с CPLD MAX (да да первый в 128 LUT/Reg) только в качестве DMA устройства и CRC4 акселератора.
            А когда появились STM32 то и плис не понадобилась — ножкодрыганием в реалтайме через DMA справился плюс bitbanding помог очень сильно в подсчёте крк4 и прочих поисках мультифрейма и тд.
            • UFO just landed and posted this here
                0
                Эта реализация хорошая, только у нас была отправка для каждого канала аудио по rtp (ethernet), также там в потоке рассчитывалось fft и много было дополнительных математических вычислений. С такой функциональностью stm32 или другие микроконтроллеры не справились бы, при том условии, что на pri и bri у нас было 8 каналов (link-ов), для аудио — 16 каналов.
                0
                Скорость не замеряли для axi_dma. Для аудио — 12 Мбайт/c. Для pri/bri — в разы меньше.
                  0
                  Непонятно, почему был сделан выбор такой ПЛИС. На мой взгляд микроскопом по гвоздям.
                  Вы взяли ПЛИС, отказались от микроконтроллеров, а сами начали искать IP-ядро для i2S, который есть в каждом втором контроллере. И Ethernet, и DMA, и параллельные порты тоже есть в контроллерах для связи с памятью/ПЛИС.
                  Вряд ли вы расскажете о заказчике и инвесторе, но на разработку деньги (imho) вы потратили сверхмеры.
                    0
                    возможно они эту платформу знали, был опыт и были наработки.

                    а так я бы лично сделал бы на STM32F7 — в реалтайме отлично тянет ффт до мегасемпла в сек (125 каналов по 8к семпла), дма и прочие акселераторы тяжёлых вычислений есть (даже SIMD и даже может 4 тапа FIR фильтра за такт), но опять же таки у меня есть такие наработки и мне нужно применить только готовые блоки и чуток доработать окружение и мейн функцию шаблонного ISDN проекта.
                      0
                      я думаю если компания делала данный проект на bare metal или rtos, то они бы делали бы еще) (а bare metal в данной реализации вообще пагубное дело), плюс мне кажется у них был жесткая необходимость в работе с файловыми системами, а если это микроконтроллеры выбор не большой, чтобы не передумывать велосипеды полностью поддерживаю реализацию promwad. Плюс судя по статье у них очень много логики заточено на ethernet, поэтому делать на lwip это можно, но за чем, плюс автор пишет система должна быть масштабируемой, на linux это делать гораздо проще, так как по описанию у них явно много поточное приложение. Спасибо за статью.
                        0
                        Mirn, спасибо за дополнение!

                        У Promwad действительно большой опыт работы с различнными видами платформ, в двух комментариях выше мы уже объяснили выбор именно такой реализации.

                        Для работы с сетью на stm32 всегда возможно воспользоваться lwip, но тут опять же быстрота реализации и поддержка бизнес-логики со всеми вытекающими.

                        Любой проект, связанный с сетью, на микроконтроллерах занимает в разы больше времени чем на Linux. Аппартных подходов для решения нашей задачи и правда много. Но мы подозреваем, что если бы наша система была на rtos, мы бы делали данный проект до сих пор х-), а если на bare metal — так еще в 2 раза больше чем на rtos. Может, и не так долго, но было бы больно. :-)
                        +1
                        Мы остановились на системе на кристалле (SoC) Zynq 7000, т.к. она проще в плане написании программных приложений и дает больше функциональных возможностей для текущих и будущих задач. У нас был Linux на проекта, и при выборе микроконтроллеров пришлось бы использовать RTOS либо bare metal, плюс много бизнес-логики. На микроконтроллерах такое решение нам показалось нецелесообразным.
                          0
                          Costic, спасибо за комментарий!

                          Добавим еще фактов в поддержку нашей реализации:

                          У нас был overhead по времени, мы рассматривали вариант вашего решения, но отказались, т.к. платформа должна легко масштабироваться. Также нужно было оставить запас на документирование для файловой системы.

                          Вообще среди микроконтроллеров stm32 выбор небольшой, нужно должны знать fat от чена, либо yaffs. Другое дело linux, тут большое количество файловых систем. Выбор fpga был обусловлен тем, что все распространенные проекты в данной теме имеют ip-ядро для синхронизации.

                          Ваше решение тоже имеет право на жизнь, только нужно подойти комплексно, не только с аппаратной точки зрения. :-)
                      +2
                      Итого, список прецедентов для работы:

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

                      И дальше рассказ только про одно ядро. Поделитесь сравнением-то) Чем именно другие ядра оказались хуже или менее удобны?
                        +3
                        Нам было важно максимально быстро решить задачу и ограничиться только настройкой dts для Linux, без написания всех уровней для работы с аудио в Linux. Под наши требования подошла только реализация AXI-I2S-ADI controller (Analog Devices).

                      Only users with full accounts can post comments. Log in, please.