Рабочий процесс генерации IP-ядра с процессором MicroBlaze: Xilinx Kintex-7 KC705

В этом примере показано, как использовать рабочий процесс HDL Coder™ IP Core Generation для разработки исходных проектов для деталей Xilinx ® без встроенного процессора ARM ®, но которые все еще используют HDL Coder™ сгенерированный интерфейс AXI для управления DUT. В частности, этот пример использует плату Kintex-7 KC705 Xilinx и MicroBlaze™ мягкий процессор, работающий на сервере микропрограммного обеспечения TCP/IP (lwIP) на основе LightWeightIP (lwIP) в исходный проект, для доступа к HDL Coder™ сгенерированным регистрам DUT из любой точки подключенной сети. Кроме того, этот пример также высвечивает различие между доступом к данным из набора регистров, реализованных как несколько скалярных портов, и набором регистров, реализованным как один векторный порт.

Требования

  • Xilinx Vivado Design Suite с поддерживаемой версией, перечисленной в документации HDL Coder

  • Плата разработки Kintex-7 KC705 Xilinx

  • Пакет поддержки HDL Coder™ для плат Xilinx FPGA

  • Подключение Ethernet

Плата разработки

Kintex-7 KC705 Xilinx

Примеры исходных проектов

MicroBlaze - простой, универсальный мягкий процессор, который может использоваться только в платформах Xilinx FPGA, таких как Kintex-7, для выполнения функциональности полноценного процессора, или как гибкий, программируемый IP. Когда программы малы, ELF может сидеть в BRAM, и проект становится полностью автономным в FPGA. Существует много приложений, хорошо подходящих для реализации на MicroBlaze. Здесь мы перечисляем всего несколько:

  1. Удаленное сетевое управление развернутым алгоритмом IP Core

  2. Встраиваемый веб-сервер для управления и отображения данных

  3. Интегрирование существующих алгоритмов программного обеспечения с аппаратными платформами

Ниже приведен уровень системы схема для этой системы MicroBlaze:

Исходный проект, «Xilinx MicroBlaze TCP/IP to AXI4-Lite Master», использует Vivado™ MicroBlaze IP, чтобы преобразовать пакеты TCP/IP в AXI4-Lite чтения и записи. Ниже приведен блок схема всей системы, включая все периферийные устройства, необходимые для работы сервера TCP/IP и отладки через последовательную консоль UART.

Setup MicroBlaze

Для порядок работы TCP/IP-сервера IP MicroBlaze требуется несколько базовых периферийных устройств:

  • локальная память (BRAM) для данных/инструкций

  • Ядро Ethernet для передачи и приема систем координат

  • Ядро UART для отправки отладочных сообщений

  • Ядро таймера для генерации прерываний тайм-аута

  • Контроллер прерывания для обработки прерываний от всех этих периферийных устройств.

Как видно из редактора адресов ниже, все эти периферийные устройства подключены к MicroBlaze через AXI4 интерфейсы.

Объем локальной памяти, выделенной с помощью BRAM, 1MB. Эта сумма необходима для запуска стека lwIP. Преимущество определения BRAM для локальной памяти заключается в том, что исполняемый ELF может быть включен в битовый поток, что упрощает программирование и позволяет нацеливать существующие платы FPGA, которые могут не иметь внешней памяти DRAM. Однако это удобство достигается за счет увеличения использования:

Если использование BRAM является проблемой и ресурсы DRAM доступны, можно выбрать замену локальной памяти BRAM на внешнюю память DRAM. Для получения дополнительной информации об альтернативных строениях и информации о приложении см. примечание к приложению Xilinx «xapp1026».

Пример Исходного проекта plugin_rd.m

Ниже показана plugin_rd.m для этого исходного проекта:

function hRD = plugin_rd()
% Reference design definition
%   Copyright 2014-2018 The MathWorks, Inc.
% Construct reference design object
hRD = hdlcoder.ReferenceDesign('SynthesisTool', 'Xilinx Vivado');
hRD.ReferenceDesignName = 'Xilinx MicroBlaze TCP/IP to AXI4-Lite Master';
hRD.BoardName = 'Xilinx Kintex-7 KC705 development board';
% Tool information
hRD.SupportedToolVersion = {'2017.2','2017.4'};
%% Add custom design files
% add custom Vivado design
hRD.addCustomVivadoDesign( ...
  'CustomBlockDesignTcl', 'system_top.tcl',...
  'VivadoBoardPart',      'xilinx.com:kc705:part0:1.1');
% add custom files, use relative path
hRD.CustomFiles         = {'mw_lwip_tcpip_axi4.elf'};
%% Add interfaces
% add clock interface
hRD.addClockInterface( ...
  'ClockConnection',      'clk_wiz_0/clk_out1', ...
  'ResetConnection',      'proc_sys_reset_0/peripheral_aresetn',...
  'DefaultFrequencyMHz',  100,...
  'MinFrequencyMHz',      100,...
  'MaxFrequencyMHz',      100,...
  'ClockNumber',          1,...
  'ClockModuleInstance',  'clk_wiz_0');
% add AXI4 and AXI4-Lite slave interfaces
hRD.addAXI4SlaveInterface( ...
  'InterfaceConnection', 'microblaze_0_axi_periph/M04_AXI', ...
  'BaseAddress',         '0x44A00000',...
  'MasterAddressSpace',  'microblaze_0/Data',...
  'InterfaceType',       'AXI4-Lite',...
  'InterfaceID',         'MicroBlaze AXI4-Lite Interface');
hRD.HasProcessingSystem = false; % No hard processing system

Обратите внимание, что исходный проект включает исполняемый файл MicroBlaze mw_lwip_tcpip_axi4.elf в свойстве исходный проект CustomFiles. При этом исполняемый файл будет скопирован из исходного проекта в проект Vivado, чтобы он мог быть связан с IP-адресом MicroBlaze.

Дополнительный код в 'system _ top.tcl' для присоединения ELF к uBlaze

Файл 'system _ top.tcl', включенный в большинство исходных проектов, используется для создания блока Vivado IP Integrator верхнего уровня, содержащей большую часть исходного проекта IP. Здесь мы добавляем некоторый дополнительный код Tcl к этому файлу после того, как блок была создаваем, чтобы связать автономный исполняемый файл MicroBlaze ELF с IP MicroBlaze.

   import_files -norecurse mw_lwip_tcpip_axi4.elf
   generate_target all [get_files system_top.bd]
   set_property SCOPED_TO_REF system_top [get_files -all -of_objects [get_fileset sources_1] {mw_lwip_tcpip_axi4.elf}]
   set_property SCOPED_TO_CELLS { microblaze_0 } [get_files -all -of_objects [get_fileset sources_1] {mw_lwip_tcpip_axi4.elf}]

Это позволяет упаковать ELF с битовым потоком и запрограммировать его в память MicroBlaze BRAM одновременно с FPGA.

Выполните рабочий процесс IP-ядра

Используя вышеописанные исходные проекты, вы сгенерируете IP-ядро HDL, которое мигает светодиодами на KC705 плате. Затем вы будете использовать tcpclient отправлять/получать форматированные пакеты в MicroBlaze для выдачи чтения/записи через интерфейс AXI4-Lite в сгенерированное IP-ядро HDL. Файлы, используемые в следующей демонстрации, расположены по адресу:

  • matlab/toolbox/hdlcoder/hdlcoderdemos/customboards/KC705

1. Добавить файлы исходного проекта MicroBlaze в путь MATLAB можно используя команду:

>> addpath(fullfile(matlabroot,'toolbox','hdlcoder','hdlcoderdemos','customboards','KC705'));

2. Настройте путь инструмента Vivado™ Xilinx с помощью следующей команды:

>> hdlsetuptoolpath('ToolName', 'Xilinx Vivado', 'ToolPath', 'C:\Xilinx\Vivado\2017.4\bin\vivado.bat');

Используйте собственный путь установки Vivado™ Xilinx при выполнении команды.

3. Откройте модель Simulink, которая реализует LED-мигание, а также векторные выходные порты для сравнения со скалярными портами, используя команду:

open_system('hdlcoder_led_vector')

4. Запустите HDL Workflow Advisor из hdlcoder_led_vector/DUT подсистема щелчком правой кнопкой мыши по DUT и выберите HDL-код > HDL Workflow Advisor.

5. Выберите исходный проект из выпадающего списка на шаге 1.2

6. Назначьте порты регистров «MicroBlaze AXI4-Lite Interface». Затем они будут доступны при шестнадцатеричном смещении, показанном в таблице.

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

Определение адресов из отчета IP-ядра

Базовый адрес для HDL Coder™ IP-ядра определяется в исходный проект plugin_rd.m следующей командой:

% add AXI4 and AXI4-Lite slave interfaces
hRD.addAXI4SlaveInterface( ...
  'InterfaceConnection', 'microblaze_0_axi_periph/M04_AXI', ...
  'BaseAddress',         '0x44A00000',...
  'MasterAddressSpace',  'microblaze_0/Data',...
  'InterfaceType',       'AXI4-Lite',...
  'InterfaceID',         'MicroBlaze AXI4-Lite Interface');

Для этого проекта базовый адрес 0x44A0_0000. Смещения можно найти в таблице IP Core Report Register Address Mapping:

Вектор данных с синхронизацией строба

Векторные данные поддерживаются на AXI4/AXI4-Lite интерфейсах на R2017a год. В отличие от набора скалярных портов, все элементы вектора данных рассматриваются как синхронные логике IP- Основного алгоритма. Дополнительные регистры строба, добавленные для каждого вектора входа и выхода порта, поддерживают эту синхронизацию через несколько последовательных AXI4 чтения/записи.

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

Подключение к серверу TCP/IP

Чтобы начать взаимодействие с TCP/IP сервером, работающим на MicroBlaze, сначала подключите последовательную консоль UART для просмотра отладки сообщений, что поможет убедиться, что дела работают должным образом. Сначала найдите последовательный порт, который соединяется с UART на плате:

Затем используйте этот порт для подключения с помощью программы, такой как PuTTY™:

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

   >>hdlworkflow_ProgramTargetDevice
   ### Workflow begin.
   ### Loading settings from model.
   ### ++++++++++++++ Task Program Target Device ++++++++++++++
   ### Generated logfile: hdl_prj\hdlsrc\hdlcoder_led_vector\workflow_task_ProgramTargetDevice.log
   ### Task "Program Target Device" successful.
   ### Workflow complete.

В окне консоли вы увидите следующий заголовок, отображающий IP-адрес и номер порта, к которому подключен сервер.

ПРИМЕЧАНИЕ. Если плата подключена к сети с включенным DHCP-сервером, IP-информация будет отличаться от показанной выше. В этом случае вам потребуется изменить линию 43 read_write_test.m скрипт для подключения к правильному IP-адресу платы:

t = tcpclient('192.168.1.10',7);

Отправка AX4-Lite транзакций в MicroBlaze из MATLAB с помощью tcpipclient

Для чтения и записи в ядро IP через TCP/IP и AXI4-Lite адрес, данные и команда, которые должны быть выполнены, должны быть закодированы в пакете, отправленном на сервер TCP/IP. В данном примере мы используем следующий формат пакета:

               [--Address--]  32-bits
               [----Cmd----]  lower byte of 32-bit word (READ = 0, WRITE = 1, DEBUG = 2)
               [---Length---]  lower byte of 32-bit work (N<255)
               [----Data---]  32-bits, used only for WRITE cmd

Для примера, пакет, выдающий чтение 3 последовательных значений, начиная с адреса 0x44a0010c:

               [44 a0 01 0c]
               [00 00 00 00]
               [00 00 00 03]

Данные будут возвращены при смещениях 0x10c, 0x110,0x114.

Для примера пакет, выдающий запись 0x0 для смещения 0x04, будет:

               [44 a0 00 04]
               [00 00 00 01]
               [00 00 00 01]
               [00 00 00 00]

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

               [xx xx xx xx]
               [00 00 00 03]
               [00 00 00 01] %0 = no msg, 1 = READ|WRITE, 2 = full pkt

Запустите скрипт read_write_test.m

Этот пример включает скрипт, который будет настройкой подключение к серверу TCP/IP, работающему на MicroBlaze, создавать команды для включения/отключения DUT, считывать 6 скалярных портов и те же данные, что и вектор порт, и сравнивать результаты.

1. Чтобы запустить этот скрипт, сначала скопируйте его в локальную директорию

>> copyfile(fullfile(matlabroot,'toolbox','hdlcoder','hdlcoderdemos','ublaze_lwip_read_write_vector_test.m'),'ublaze_test.m');

и откройте скрипт в редакторе:

>> edit('ublaze_test.m');

Скрипт имеет три раздела. Первый раздел, соединяется с платой и настраивает команды, которые будут использоваться. При необходимости обновите IP-адрес платы в линии 41.

2. Выполните раздел 1. Вы сгенерируете следующие команды как массивы типов uint32:

read6_cmd      = uint32([hex2dec('44a0010c') 0 6]);   %read 6 32-bit regs
read_vec_cmd   = uint32([hex2dec('44a00140') 0 6]);   %read 6 elements of vec
strobe_vec_cmd = uint32([hex2dec('44a00160') 1 1 1]); %write strobe for vec
enable_cmd     = uint32([hex2dec('44a00004') 1 1 1]); %enable ip core
disable_cmd    = uint32([hex2dec('44a00004') 1 1 0]); %disable ip core
debug0_cmd     = uint32([hex2dec('00000000') 3 0]);   %disable all debug printfs
debug1_cmd     = uint32([hex2dec('00000000') 3 1]);   %enable READ|WRITE printfs
debug2_cmd     = uint32([hex2dec('00000000') 3 2]);   %enable pkt printfs

ПРИМЕЧАНИЕ: эти массивы хранят данные в эндовом формате, используемом локальной машиной, которая для многих систем x86 является маленьким эндовым форматом. Однако сервер TCP/IP ожидает значения в большом эндовом формате (порядок сетевых байтов). В результате, если система, на которой вы работаете, является маленькой эндовой, байты в каждом элементе должны быть заменены с помощью swapbytes.

3. Выполните раздел 2, чтобы отключить логику DUT и считать одно значение счетчика, подключенное к 6 скалярным портам, а также ко всем 6 элементам порта вектора. Заметьте, что все значения счетчика совпадают. Это происходит из-за того, что одни и те же данные управляются во все порты, и DUT отключен, поэтому асинхронный доступ через AXI4 интерфейс не очевиден.

   Scalar port (top) vs vector port (bottom) access with DUT disabled:
      7e8aec14   7e8aec14   7e8aec14   7e8aec14   7e8aec14   7e8aec14
      7e8aec14   7e8aec14   7e8aec14   7e8aec14   7e8aec14   7e8aec14

4 . Выполните раздел 3, чтобы повторно включить логику DUT и считать те же значения счетчика назад. Заметьте, что все 6 скалярных портов показывают различные значения, в то время как 6 элементов векторного порта все одинаковые. Это связано с последовательным доступом, который должен происходить через интерфейс AXI4, и отсутствием регистра синхронизации в случае скалярного порта и наличием явного регистра синхронизации в случае порта вектора.

   Scalar port (top) vs vector port (bottom) access with DUT enabled:
      7f7796dc   7fc70e4b   8016860a   8065fdce   80b5758d   8104ed4d
      815964dd   815964dd   815964dd   815964dd   815964dd   815964dd

Соответствующий выход отладки на последовательной консоли:

Сводные данные

В этой демонстрации подчеркивалось использование soft-core процессора MicroBlaze только в проектах FPGA. MicroBlaze хорошо подходит для работы как полноценный процессор или как гибкий IP, работающий под управлением устаревших Кодов С как приложение для прошивки. Эта демонстрация также показала различие между набором скалярных портов и вектора портом в отношении синхронизации данных через интерфейс AXI4.

Приложение A: Создание и редактирование приложения Xilinx SDK

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

1. Откройте проект Xilinx Vivado, нажав на ссылку в HDL Workflow Advisor шаг 4.1 «Создать проект»:

2. Экспорт существующего проекта, включая сгенерированный битовый поток, в локальную папку. Как только это сделано, вперед и «Запуск SDK» также.

3. Из SDK создайте новый проект приложения

4. Затем у вас будет опция назвать проект и создать новый bsp

Далее можно выбрать из нескольких предварительно настроенных проектов example/template для начала работы. Этот пример построен из проекта «lwIP Echo Server», поэтому выберите его сейчас.

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

Приложение B: Копировать содержимое файла C в проект

/* Copyright 2016-2020 The MathWorks, Inc. */
#define IPCOREBASE 0x44a00000
#define WRITE  0x01
#define READ   0x00
#define DEBUG  0x03

int transfer_data() {
    return 0;
}

void print_app_header()
{
    xil_printf("\n\r\n\r-----MathWorks HDL Coder AXI4-Lite IP Core Read/Write Server ------\n\r");
    xil_printf("  TCP packets sent to port 7 will be issued as AXI4-Lite Read/Writes\n\r");
    xil_printf("\n\r");
    xil_printf("  [ 32-bit address ] (Base Address = 0x44a0_0000)\n\r");
    xil_printf("  [ 32-bit   cmd   ] (read = 0x00, write =0x01, debug = 0x03)\n\r");
    xil_printf("  [ 32-bit   len   ] ( N<255)\n\r");
    xil_printf("  [ 32-bit   data  ] (N 32-bit data values for write cmd)\n\r");
    xil_printf("------------------------------------------------------------ ------\n\r");

}

void print_packet(struct pbuf *p) {
    u16 ii;
    u8 *pktPtr;

    pktPtr = p->payload;
    xil_printf("DEBUG | packet payload:\r\n");
    for (ii=0;ii<p->len;ii+=4) {
        xil_printf("%02x %02x %02x %02x\r\n",*(pktPtr+ii),*(pktPtr+ii+1),*(pktPtr+ii+2),*(pktPtr+ii+3));
    }


}
err_t recv_callback(void *arg, struct tcp_pcb *tpcb,
                               struct pbuf *p, err_t err)
{
    u8 *pktPtr,*pktEnd;
    volatile u32 *addr;
    u32 data[255],cmd;
    u16 len;
    int ii;
    static u8 debug = 3;

    /* do not read the packet if we are not in ESTABLISHED state */
    if (!p) {
        tcp_close(tpcb);
        tcp_recv(tpcb, NULL);
        return ERR_OK;
    }

    /* indicate that the packet has been received */
    tcp_recved(tpcb, p->len);
    if (debug > 1) print_packet(p);

    //[ 32 bits address ]
    //[ 32 bits read = 0x00, write =0x01]
    //[ 32 bits length ]
    //[ 32 bits write data]
    pktPtr = p->payload;
    pktEnd = pktPtr+p->len;


    /* could be multiple commands per packet */
    while ( pktPtr < pktEnd) {
        addr = (u32*) (pktPtr[0]<<24 | pktPtr[1]<<16 | pktPtr[2]<<8 | pktPtr[3]);
        cmd  = (u32) pktPtr[7];      // cmd is 32 bits, but only 1st byte used, ignore rest
        pktPtr += 8;

        switch(cmd) {
        case WRITE :
            len  = (u32) pktPtr[3];  // len is 32 bits, but only 1st byte used, ignore rest
            pktPtr += 4;
            for (ii=0;ii<len; ii++) {
                data[0] = (u32) (pktPtr[0]<<24 | pktPtr[1]<<16 | pktPtr[2]<<8 | pktPtr[3]);
                *addr = data[0];
                if (debug > 0) xil_printf("WRITE | address: 0x%08x, data[0]: 0x%08x\r\n",addr,data[0]);
                addr++;
                pktPtr += 4;
            }
            break;
        case READ :
            len  = (u32)   pktPtr[3]; // len is 32 bits, but only 1st byte used, ignore rest
            pktPtr += 4;
            for (ii=0;ii<len; ii++) {
                data[ii] = *addr;
                if (debug > 0) xil_printf("READ  | address: 0x%08x, data[%d]: 0x%08x\r\n",addr,ii,data[ii]);
                addr++;
            }
            /* send the packet back */
            if (tcp_sndbuf(tpcb) > p->len)
                err = tcp_write(tpcb, data, 4*len, 1);
            else
                xil_printf("no space in tcp_sndbuf\n\r");
            break;
        case DEBUG:
            debug = pktPtr[3]; // only need the low byte
            pktPtr += 4;
            xil_printf("Debug level set to : 0x%02x\r\n",debug);
            break;
        default :
            xil_printf("INVALID | cmd: 0x%08x\r\n",cmd);

        }
    }
        /* free the received pbuf */
        pbuf_free(p);

        return ERR_OK;
}

6. Сохраните измененный файл echo.c, и приложение будет перестроено.

Приложение C: Программа FPGA с ELF и битовым потоком

Теперь можно запрограммировать FPGA с помощью экспортированного битового потока и только что созданного файла ELF.