- PVSM.RU - https://www.pvsm.ru -
На днях ко мне в руки попала EBV SoCrates Evaluation Board. В двух словах — это плата с SoC от фирмы Altera, на борту которой есть двухъядерный ARM и FPGA Cyclone V.
ARM и FPGA на одном чипе — это должно быть очень интересно! Но для начала всё это добро нужно «поднять».
Об этом процессе я и поведаю в данной статье.
Если вам в руки попала такая или подобная плата и вы не до конца уверены, что же с ней нужно делать. Если вы всегда думали, что FPGA — это что-то сложное и непонятно, как к этому подступиться. Или вы просто любопытный инженер. Тогда заходите. Мы всем рады.
А в качестве маленького бонуса измерим пропускную способность между CPU и FPGA.
Наш план состоит из следующих пунктов:
Поехали!
Первым делом нам нужно получить прошивку FPGA.
Из инструментов для этого понадобится САПР Quartus, скачать его можно на официальном сайте [1]
Описывать установку не буду — там всё достаточно очевидно.
Запускаем Quartus, идём в File -> New Project Wizard, жмём Next, заполняем директорию и название проекта:
Следующую страницу пропускаем, потом идёт выбор семейства и типа ПЛИС.
Остальные настройки для нас не важны, жмём Finish.
Qsys — отличный инструмент для начинающих. Позволяет получить прошивку, не написав ни строчки кода. Вместо этого разработчик собирает конструктор из заранее заданных кубиков (IP-корок). Требуется только правильно настроить каждую корку и соединить их должным образом.
Итак, Tools -> Qsys, в левом окне (IP Catalog) нам потребуются две IP-корки:
Hard Processor System (HPS) — это наш ARM. С его настроек и начнем.
На первой вкладке нас интересует HPS-to-FPGA interface width, чтобы мы имели доступ из CPU ко внутренней памяти FPGA:
Дальше идёт куча настроек для различных интерфейсов — в каких режимах работают, какие пины используются:
Следующая вкладка — настройка клоков. В Inputs Clocks оставляем всё без изменений:
В Output Clocks ставим галку на Enable HPS-to-FPGA user 0 clock:
Потом идёт большой подраздел с различными настройками для DDR3 памяти.
С HPS мы разобрались, переходим к настройке On-Chip памяти. Это память, которая расположена непосредственно внутри ПЛИС.
Настроек тут значительно меньше:
Теперь нужно соединить блоки между собой. Всё достаточно интуитивно (обратите внимание на значение базового адреса напротив s1):
Готово. Сохраняем (File -> Save) под именем soc.
Осталось сгенерировать файлы. Кнопка Generate HDL, в появившемся окне опять жмём Generate, ждём, Finish.
Теперь нужно добавить сгенерённые файлы в проект:
Assignments -> Settings вкладка Files, добавляем файл soc/synthesis/soc.qip
Нужно применить настройки для DDR пинов. Но перед этим нужно выполнить первую стадию компиляции:
Processing -> Start -> Start Analysis & Synthesis
Запускаем скрипт для настройки пинов:
Tools -> Tcl Scripts. В появившемся окне выбираем Project -> soc -> synthesis -> submodules -> hps_sdram_p0_pin_assignments.tcl, Run.
Финальная компиляция проекта:
Processing -> Start Compilation
Мы получили файл soc.sof c прошивкой FPGA. Но мы хотим прошивать ПЛИС прямо из CPU, поэтому нам понадобится другой формат. Выполним конвертацию. Это можно делать и из GUI, но в консоле проще. Да и вообще, пора уже отвыкать от GUI :).
Для конвертации надо запустить терминал и перейти в директорию с нашим проектом. Далее перейти в output_files и выполнить команду (не забываем, что директория с утилитами Quartus дожна быть в переменной PATH):
quartus_cpf -c soc.sof soc.rbf
Ура! Мы получили прошивку FPGA.
Теперь соберём ядро для нашего ARM.
Из инструментов потребуется Altera SoC EDS [2]. Отсюда мы будет брать компилятор arm-linux-gnueabihf- для кросс-компиляции.
Выкачиваем ядро:
git clone https://github.com/coliby/terasic_MTL.git
Запускаем скрипт, который добавит в PATH директории с компилятором и запустит bash:
/opt/altera/quartus14.0/embedded/embedded_command_shell.sh
Устанавливаем переменные окружения:
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
export LOADADDR=0x8000
Переходим в директорию с ядром и выполняем конфигурацию:
cd terasic_MTL/
make socfpga_defconfig
Cобираем образ ядра для U-Boot:
make -j 4 uImage
Теперь нам нужно получить так называемый .dtb (Device Tree Blob) файл. Это бинарный файл, содержащий информацию о платформе — интерфейсы, пины, тактовые сигналы, адресное пространство и т.д. Ядро читает этот файл во время инициализации и вносит в неё изменения. Это позволяет использовать одно собранное ядро на нескольких аппаратных платформах.
Итак, получаем .dtb файл:
make socfpga_cyclone5.dtb
Но этот файл не для нашей платформы, поэтому нам придётся внести в него небольшие изменения. Для этого конвертируем файл в текстовый формат .dts (Device Tree Source):
./scripts/dtc/dtc -I dtb -O dts -o soc.dts arch/arm/boot/dts/socfpga_cyclone5.dtb
Теперь в soc.dts нужно удалить блок bridge@0xff200000. Это можно сделать либо руками, либо наложив патч:
patch soc.dts dts.patch
942,966d941
< bridge@0xff200000 {
< compatible = "altr,h2f_lw_bridge-1.0", "simple-bus";
< reg = <0xff200000 0x200000>;
< #address-cells = <0x1>;
< #size-cells = <0x1>;
< ranges = <0x200 0xff200200 0x80 0x100 0xff200100 0x80>;
<
< tsc@0x200 {
< compatible = "terasic,mlt_touch_screen";
< reg = <0x200 0x80>;
< width_pixel = <0x320>;
< height_pixel = <0x1e0>;
< interrupts = <0x0 0x28 0x4>;
< };
<
< vip2@0x100 {
< compatible = "ALTR,vip-frame-reader-13.0", "ALTR,vip-frame-reader-9.1";
< reg = <0x100 0x80>;
< max-width = <0x320>;
< max-height = <0x1e0>;
< mem-word-width = <0x100>;
< bits-per-color = <0x8>;
< };
< };
<
Теперь конвертируем файл обратно в .dtb:
./scripts/dtc/dtc -I dts -O dtb -o soc.dtb soc.dts
Итого, нас интересует два файла:
Процесс запуска SoC выглядит следующим образом:
Boot ROM — это первая стадия загрузки, которая выполняется сразу после поднятия питания. Её основная функция — определить и выполнить вторую стадию, Preloader.
Функциями Preloader чаще всего являются инициализация SDRAM интерфейса и конфигурация пинов HPS. Инициализация SDRAM позволяет выполнить загрузку следующей стадии из внешней памяти, так как её код может не поместиться в 60 КБ доступной встроенной памяти.
Bootloader может участвовать в дальнейшей инициализации HPS. Также эта стадия выполняет загрузку операционной системы либо пользовательского приложения. Обычно (и в нашем случае) в качестве Bootloader выступает U-Boot.
OS — тут всё просто. Это наш любимый Linux. Ядро для него у нас уже есть, корневую файловую систему получим чуть позже.
А в сейчас мы займемся Preloader и U-Boot
Открываем терминал, запускаем уже знакомый нам скрипт:
/opt/altera/quartus14.0/embedded/embedded_command_shell.sh
Заходим в директорию с нашим проектом:
cd ~/src/soc_test/
После компиляции там должна появиться директория hps_isw_handoff, переходим в неё:
cd hps_isw_handoff
Запускаем генерацию необходимых файлов:
bsp-create-settings --type spl --bsp-dir build --preloader-settings-dir soc_hps_0 --settings build/settings.bsp --set spl.boot.WATCHDOG_ENABLE false
После этого дожна появиться директория build.
Собираем Preloader:
make -C build
Собираем U-boot:
make -C build uboot
Теперь нам нужно настроить переменные для U-Boot. Вначале создаем текстовый файл u-boot-env.txt.
console=ttyS0
baudrate=115200
bootfile=uImage
bootdir=boot
bootcmd=run mmcboot
bootdelay=3
fdt_file=soc.dtb
fdt_addr_r=0xf00000
ethaddr=00:01:02:03:04:05
kernel_addr_r=0x10000000
mmcroot=/dev/mmcblk0p2
mmcpart=2
con_args=setenv bootargs ${bootargs} console=${console},${baudrate}
misc_args=setenv bootargs ${bootargs} uio_pdrv_genirq.of_id=generic-uio
mmc_args=setenv bootargs ${bootargs} root=${mmcroot} rw rootwait
mmcboot=mmc rescan; ext2load mmc 0:${mmcpart} ${kernel_addr_r} ${bootdir}/${bootfile}; ext2load mmc 0:${mmcpart} ${fdt_addr_r} ${bootdir}/${fdt_file}; run mmc_args con_args misc_args; bootm ${kernel_addr_r} - ${fdt_addr_r}
verify=n
Затем конвертируем его в бинарный формат, не забыв указать размер области, содержащей переменные — 4096 байт нам вполне хватит. Даже если реальный размер превысит заданный, mkenvimage сообщит об этом.
./build/uboot-socfpga/tools/mkenvimage -s 4096 -o u-boot-env.img u-boot-env.txt
Нас интересуют три файла:
Это раздел написан для тех, кто использует Debian (или в если Вашем дистрибутиве тоже есть debootstrap). Если Вы не среди них — можете воспользоваться Yocto [3] или любым другим удобным для Вас методом.
Устанавливаем необходимые пакеты:
sudo apt-get install debootstrap qemu-user-static binfmt-support
Создаем директорию и выкачивает туда необходимые файлы:
mkdir rootfs
sudo debootstrap --arch armel --foreign wheezy rootfs http://ftp.debian.org/debian
Чтобы запускать приложения, собранные под ARM-архитектуру, будем использовать qemu static. Для этого скопируем файл в нашу rootfs:
sudo cp /usr/bin/qemu-arm-static rootfs/usr/bin/
Переходим в нашу новую файловую систему:
sudo chroot rootfs /bin/bash
Если приглашение интерпретатора изменилось на «I have no name!@hostname:/#», значит всё прошло успешно.
Заканчиваем процесс установки:
/debootstrap/debootstrap --second-stage
В /etc/inittab оставляем следующие строки:
id:5:initdefault:
si::sysinit:/etc/init.d/rcS
~~:S:wait:/sbin/sulogin
l0:0:wait:/etc/init.d/rc 0
l1:1:wait:/etc/init.d/rc 1
l2:2:wait:/etc/init.d/rc 2
l3:3:wait:/etc/init.d/rc 3
l4:4:wait:/etc/init.d/rc 4
l5:5:wait:/etc/init.d/rc 5
l6:6:wait:/etc/init.d/rc 6
z6:6:respawn:/sbin/sulogin
S:2345:respawn:/sbin/getty 115200 console
Устанавливаем пароль:
passwd
Создаём архив:
tar -cpzf rootfs.tar.gz --exclude=rootfs.tar.gz /
Если говорить в двух словах, то почти всё взаимодействие между компонентами SoC происходит при помощи отображения адресного пространства одного компонента в адресное пространство другого.
Рассмотрим на примере. В нашем проекте при помощи Qsys мы указали, что на интерфейсе HPS-to-FPGA начиная с адреса 0 расположен блок On-Chip памяти размером 262144 байт. Сам интерфейс HPS-to-FPGA отображается в адресное пространство CPU по адресу 0xC0000000 (см. документацию на Cyclone V). В итоге обращение CPU по адресам от (0xC0000000 + 0) до (0xC0000000 + 262143) будет приводить к обращению ко внутренней памяти FPGA.
Поэтому для работы нам потребуется утилита, с помощью которой можно читать/писать про произвольным адресам памяти. Вот её исходный код:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <unistd.h>
#include <errno.h>
#define MAP_SIZE (4096)
#define MAP_MASK (MAP_SIZE-1)
int main( int argc, char *argv[] )
{
int fd;
if( argc < 2 ) {
printf( "Usage:n" );
printf( "%s byte_addr [write_data]n", argv[ 0 ] );
exit( -1 );
}
// /dev/mem это файл символьного устройства, являющийся образом физической памяти.
fd = open( "/dev/mem", O_RDWR | O_SYNC );
if( fd < 0 ) {
perror( "open" );
exit( -1 );
}
void *map_page_addr, *map_byte_addr;
off_t byte_addr;
byte_addr = strtoul( argv[ 1 ], NULL, 0 );
// Выполняем отображение файла /dev/mem в адресное пространство нашего процесса. Получаем адрес страницы.
map_page_addr = mmap( 0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, byte_addr & ~MAP_MASK );
if( map_page_addr == MAP_FAILED ) {
perror( "mmap" );
exit( -1 );
}
// Вычисляем адрес требуемого слова (адрес при этом байтовый)
map_byte_addr = map_page_addr + (byte_addr & MAP_MASK);
uint32_t data;
// Если аргументов три, значит записываем данные, иначе -- читаем и выводим на экран.
if( argc > 2 ) {
data = strtoul( argv[ 2 ], NULL, 0 );
*( ( uint32_t *) map_byte_addr ) = data;
} else {
data = *( ( uint32_t *) map_byte_addr );
printf( "data = 0x%08xn", data );
}
// Убираем отображение.
if( munmap( map_page_addr, MAP_SIZE ) ) {
perror( "munmap" );
exit( -1 );
}
close( fd );
return 0;
}
Теперь нужно собрать её с использованием кросс-компилятора. Для этого запускаем скрипт:
/opt/altera/quartus14.0/embedded/embedded_command_shell.sh
И выполняем компиляцию:
arm-linux-gnueabihf-gcc -o mem.o mem.c
Также нам нужна утилита для измерения пропускной способности:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <unistd.h>
#include <errno.h>
// Валидные коды операций
#define COP_WRITE (0)
#define COP_READ (1)
#define COP_CHECK (2)
int main( int argc, char *argv[ 0 ] )
{
int fd;
void *map_addr;
if( argc < 5 ) {
printf( "Usage:n" );
printf( "%s <cop> <address> <word_count> <cycles>n", argv[ 0 ] );
exit( -1 );
}
// /dev/mem это файл символьного устройства, являющийся образом физической памяти.
fd = open( "/dev/mem", O_RDWR | O_SYNC );
if( fd < 0 ) {
perror( "open" );
exit( -1 );
}
uint8_t cop;
off_t addr;
uint32_t word_cnt;
uint32_t cycle_cnt;
// Код операции
cop = strtoul( argv[ 1 ], NULL, 0 );
// Начальный адрес
addr = strtoul( argv[ 2 ], NULL, 0 );
// Количество слова для записи/чтения
word_cnt = strtoul( argv[ 3 ], NULL, 0 );
// Количество циклов повторения
cycle_cnt = strtoul( argv[ 4 ], NULL, 0 );
// Выполняем отображение файла /dev/mem в адресное пространство нашего процесса.
map_addr = mmap( 0, word_cnt * 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr );
if( map_addr == MAP_FAILED ) {
perror( "map" );
exit( -1 );
}
uint32_t cycle;
uint32_t word;
uint32_t data;
// В зависимости от кода операции
switch( cop ) {
// Записываем в память "счётчик".
case( COP_WRITE ):
for( cycle = 0; cycle < cycle_cnt; cycle++ ) {
for( word = 0; word < word_cnt; word++ ) {
*( ( uint32_t *) map_addr + word ) = word;
}
}
break;
// Читаем данные и выводим на экран.
case( COP_READ ):
for( cycle = 0; cycle < cycle_cnt; cycle++ ) {
for( word = 0; word < word_cnt; word++ ) {
data = *( ( uint32_t *) map_addr + word );
printf( "idx = 0x%x, data = 0x%08xn", word, data );
}
}
break;
// Читаем данные и сравниваем с "гипотетически записанными".
case( COP_CHECK ):
for( cycle = 0; cycle < cycle_cnt; cycle++ ) {
for( word = 0; word < word_cnt; word++ ) {
data = *( ( uint32_t *) map_addr + word );
if( data != word ) {
printf( "Error! write = 0x%x, read = 0x%xn", word, data );
exit( -1 );
}
}
}
break;
default:
printf( "Error! Unknown COPn" );
exit( -1 );
}
if( munmap( map_addr, word_cnt * 4 ) ) {
perror( "munmap" );
exit( -1 );
}
close( fd );
return 0;
}
Компилируем:
arm-linux-gnueabihf-gcc -o memblock.o memclock.c
Соответственно, интересующие нас файлы:
Настало время собрать всё воедино. На текущий момент у нас должны быть следующие файлы:
Если какого-то из них нет — значит Вы что-то пропустили :)
Создадим директорию и скопируем все указанные файлы в неё. Далее нам нужно найти и подключить MicroSD карту.
В последующих командах предполагается, что карта определилась как устройство /dev/sdb. Мы создадим на ней два раздела:
Если карта определилась под другим именем, внесите соответствующие изменения.
На всякий случай затираем всё нулями.
Внимание! Eще раз проверьте, что /dev/sdb — это карта, а не Ваш второй жёсткий диск.
sudo dd if=/dev/zero of=/dev/sdb bs=10M
Для того, чтобы создать разделы, воспользуемся утилитой fdisk:
sudo fdisk /dev/sdb
Далее нужно ввести следующие команды (пустая строка — ввод Enter):
o
n
p
1
2048
+1M
n
p
2
t
1
a2
t
2
83
w
Можно проверить, что у нас получилось:
sudo fdisk -l /dev/sdb
Должно быть что-то похожее на:
Disk /dev/sdb: 1966 MB, 1966080000 bytes
61 heads, 62 sectors/track, 1015 cylinders, total 3840000 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x02be07e5
Device Boot Start End Blocks Id System
/dev/sdb1 2048 4095 1024 a2 Unknown
/dev/sdb2 4096 3839999 1917952 83 Linux
Теперь скопируем на карту образ с переменными U-Boot:
sudo dd if=u-boot-env.img of=/dev/sdb bs=1 seek=512
После этого копируем Preloader:
sudo dd if=preloader-mkpimage.bin of=/dev/sdb1
И сам U-Boot:
sudo dd if=u-boot.img of=/dev/sdb1 bs=64k seek=4
Создаём файловую систему ext3:
sudo mkfs.ext3 /dev/sdb2
Монтируем её:
sudo mount /dev/sdb2 /mnt/
И разворачиваем в неё нашу rootfs:
sudo tar xvf rootfs.tar.gz -C /mnt/
Далее копируем образ ядра, dtb, прошивку FPGA и тестовые программы:
sudo cp uImage /mnt/boot/
sudo cp soc.dtb /mnt/boot/
sudo cp soc.rbf /mnt/boot/
sudo cp mem.o /mnt/root/
sudo cp memblock.o /mnt/root/
Отмонтируем файловую систему:
sudo umount /dev/sdb2
Всё, карта готова!
Наконец-то всё готово для работы. Вставляем карту, подключаем USB и питание.
Заходим по консоли:
minicom -D /dev/ttyUSB0 -b 115200 -s
Первым делом прошьём FPGA.
Для это необходимо установить переключатель P18 на плате в положение «On On On On On» (выключатели с 1 по 5).
Смотрим текущее состояние FPGA:
cat /sys/class/fpga/fpga0/status
Мы должны увидеть configuration phase
Заливаем прошивку:
dd if=/boot/soc.rbf of=/dev/fpga0 bs=4096
И смотри состояние еще раз:
cat /sys/class/fpga/fpga0/status
Состояние должно смениться на user mode. Это означает, что ПЛИС сконфигурирована и готова к работе.
Теперь проверяем наши утилиты. Но перед этим ещё немного «работы напильником».
У нашего кросс-компилятора и у Debian разные названия динамического линкера. Поэтому для того, чтобы утилиты работали, нам необходимо создать ссылку на правильный линкер:
ln -s /lib/ld-linux.so.3 /lib/ld-linux-armhf.so.3
Итак, запускаем утилиту (пояснение, что это за адрес, будет чуть ниже):
./mem.o 0xFFD0501C
Если в результате Вы видите строку data = 0x00000007, значит всё в порядке.
Как я уже писал выше, внутренняя память ПЛИС у нас будет отображена в адресное пространство начиная с адреса 0xC0000000. Но перед тем, как мы сможем работать с этой памятью, нам нужно сделать еще два действия.
Первое — так как по умолчанию все интерфейсы между CPU и FPGA находятся в ресете, то мы должны его снять. За это отвечает блок Reset Manager (rstmgr), с базовым адресом 0xFFD05000, и конкретно его регистр brgmodrst со смещением 0x1C. Итоговый адрес регистра — 0xFFD0501C. В нём задействованы только три младших бита:
Логика работы всех битов одинакова — если там записана единица, значит соответствующий интерфейс находится в ресете. В итоге, значение по умолчанию для этого регистра — это 0x7, что мы и видели, когда читали из него при помощи нашей утилиты. Нам требуется снять ресет с интерфейса HPS-to-FPGA, значит мы должны записать в регистр число 0x6:
./mem.o 0xFFD0501C 0x6
После этого вновь прочитаем регистр, чтобы убедиться, что данные записались корректно:
./mem.o 0xFFD0501C
Второе — мы должны включить отображение интерфейса HPS-to-FPGA в адресное пространство CPU. За это отвечает блок L3 (NIC-301) GPV (l3regs) с базовым адресом 0xFF800000, и конкретно его регистр remap со смещением 0. За HPS-to-FPGA отвечает бит под номером 3. В итоге, нам нужно записать в регистр число 0x8:
./mem.o 0xFF800000 0x8
К сожалению, этот регистр доступен только для записи, поэтому прочитать для проверки данные у нас не получится.
Теперь мы можем читать и писать в память FPGA. Проверим это. Читаем:
./mem.o 0xC0000000
Естественно, там должны быть нули. Теперь запишем туда что-нибудь:
./mem.o 0xC0000000 0x12345678
И снова прочитаем:
./mem.o 0xC0000000
Должно совпасть с записанным.
Ура! Мы наконец-то сделали это! Мы получили работающую SoC с FPGA и организовали доступ к её памяти из CPU.
Но просто читать/писать — это как-то совсем скучно. Давайте хотя бы измерим пропускную способность нашего интерфейса. Тем более это займет совсем немного времени.
Для этого нам потребуется наша вторая утилита memblock:
root@desktop:~# ./memblock.o
Usage:
./memblock.o <cop> <address> <word_count> <cycles>
Она работает следующим образом: если первый аргумент cop равен 0, то в word_count 32-битных слов, начиная с адреса address, будет записана последовательность чисел от 0 до word_count-1. Вся процедура будет произведена cycles раз (это сделано для более точного измерения пропускной способности).
Если cop равен 1, то эти же слова будут считаны и выведены на экран.
Если cop равен 2, то слова будут считаны, а их значения будут сравниваться с теми, что гипотетически были записаны.
Проверим. Запишем немного данных:
./memblock.o 0 0xC0000000 10 1
Теперь считаем их:
./memblock.o 1 0xC0000000 10 1
Результат должен быть следующим:
data = 0x00000000
data = 0x00000001
data = 0x00000002
data = 0x00000003
data = 0x00000004
data = 0x00000005
data = 0x00000006
data = 0x00000007
data = 0x00000008
data = 0x00000009
Теперь попробуем сравнить данные, специально задав чуть большее количество слов:
./memblock.o 2 0xC0000000 11 1
Должны получить такую строку:
Error! write = 0xa, read = 0x0
Теперь запускаем запись по всему объему памяти в количестве 1000-ти повторений и замеряем время записи:
time ./memblock.o 0 0xC0000000 0x10000 1000
Среднее значение по 5 запускам равно 11.17 секунд. Считаем пропускную способность:
1000 раз * 65536 записей * 4 байта * 8 бит/в_байте / ( 11.17 * 10^6 ) = 187.75 Мбит/c
Не очень густо. А что у нас с чтением:
time ./memblock.o 2 0xC0000000 0x10000 1000
Среднее время 10.5 секунд. Что выливается в:
1000 * 65536 * 4 * 8 / ( 10.5 * 10^6 ) = 199.73 Мбит/c
Примерно то же самое. Естественно, на время выполнения любой из этих операций одно из двух ядер загружается на 100%.
Если при компиляции добавить флаг -O3, то пропускная способность на запись и на чтение станет 212 Мбит/c и 228 Мбит/c соответственно. Чуть лучше, но тоже не метеор.
Но это и не удивительно — мы же ничего не делали, чтобы эту самую пропускную способность увеличить. Неплохо было бы поиграться с более хитрой оптимизацией, посмотреть в сторону ядра, или, на худой конец, хотя бы прикрутить DMA, чтобы разгрузить процессор.
Но это уже в следующей статье, если, конечно, кому-то это будет интересно.
Спасибо тем, кто добрался до конца! Удачи!
Официальная документация [4] на Cyclone V
Rocketboards.org [5] — много разных статей про платы с SoC
Информация [6] конкретно по EBV SoCrates Evaluation Board
Автор: Des333
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/linux/68990
Ссылки в тексте:
[1] на официальном сайте: https://www.altera.com/download
[2] Altera SoC EDS: http://www.altera.com/devices/processor/arm/cortex-a9/software/proc-soc-embedded-design-suite.html
[3] Yocto: http://www.rocketboards.org/foswiki/Documentation/GSRD131GettingStartedYocto#Building_U_45Boot_47Kernel_47Rootfs
[4] Официальная документация: http://www.altera.com/literature/lit-cyclone-v.jsp
[5] Rocketboards.org: http://www.rocketboards.org/foswiki/Documentation/WebHome
[6] Информация: https://www.rocketboards.org/foswiki/Documentation/EBVSoCratesEvaluationBoard
[7] Источник: http://habrahabr.ru/post/235707/
Нажмите здесь для печати.