Pull to refresh

SoC: поднимаем простой DMA на FPGA

Reading time 14 min
Views 53K


День добрый! В прошлой статье я описывал, как «поднять» с нуля SoC от Altera.
Мы остановились на том, что измерили пропускную способность между CPU и FPGA, когда копирование выполняется процессором.

В этом раз мы пойдем немного дальше и реализуем примитивный DMA в FPGA.
Кому интересно — добро пожаловать под кат.

Используемое железо


В прошлый раз мы использовали плату SoCrates от EBV.
В этот раз я буду использовать плату нашей собственной разработки — именно она представлена на фото.

Основное отличие — в нашей плате 2 интерфейса Gigabit Ethernet и они заведены не на CPU, а на FPGA.
Это позволяет выполнять очень гибкую обработку трафика. Плюс на разъемы выведено большое количество пинов.

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

А для выполнения действий, описанных ниже, подойдет любая плата с SoC Сyclone V

Исходный код


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

Детализация


Подробности сборки ядра, получения bootloader и других действий, описанных в прошлой статье, я приводить не буду.

Замечание насчёт ядра — лучше использовать более свежее ядро версии 3.18 отсюда:
git clone git://git.rocketboards.org/linux-socfpga.git 
git checkout remotes/origin/socfpga-3.18

Думаем над реализацией


Выбор DMA-контроллера


Итак, наша цель — передать данные от FPGA до процессора и/или обратно с максимальной пропускной способностью и минимальной загрузкой CPU.
Вариант копирования процессором сразу отпадает, нужно использовать DMA. Но кто может выполнять роль DMA-контроллера?
Для нашего SoC есть два варианта — либо FPGA либо встроенный в HPS контроллер DMA-330.

Судя по обсуждениям в сети, DMA-330 не очень производителен, а соответствующий драйвер, возможно, даже не полностью работоспособен.
Возможно, когда-нибудь, мы попробуем «оживить» DMA-330, но сейчас наш выбор — FPGA

Выбор интерфейса


Чтобы выполнять функции DMA-контроллера FPGA должнен быть мастером. Это возможно реализовать на одном из двух интерфейсов:
  • FPGA-to-HPS (fpga2hps)
  • FPGA-to-HPS SDRAM (fpga2sdram)


Структурная схема компонентов HPS и интерфейсов между ними:
Архитектура HPS


Давайте посмотрим, какие преимущества и недостатки у каждого варианта.

fpga2hps позволяет мастерам в FPGA получать доступ почти ко всем слейвам в системе. То есть не только как к памяти, но и к разнообразной периферии.

fpga2sdram даёт возможность FPGA работать с DDR-памятью, «принадлежащей» HPS. В этом случае доступ ограничен только RAM.

fpga2sdram позволяет получить большую пропускную способность.

При использовании fpga2hps обмен происходит через один интерфейс. Если в FPGA требуется наличие нескольких мастеров, то необходим арбитраж. Значит нужно либо писать свои модули, либо использовать генерированные при помощи Qsys, а они достаточно ресурсоемкие.
С другой стороны в fpga2sdram можно создать до 6 независимых портов, а все вопросы с арбитражем решит DDR-контроллер.
Внимание: число 6 не совсем «честное» — доступно 6 командных портов, 4 порта на запись и 4 порта на чтение.
При этом один 128-битный интерфейс требует использования 1-ого командного порта, 2-х портов на запись и 2-х портов на чтение.

И fpga2hps и fpga2sdram перед использованием должны быть проинициализированы записью в соответствующие регистры. К сожалению, для fpga2sdram это необходимо сделать после прошивки FPGA, но в момент, когда никаких транзакций на интерфейсе не происходит. Фактически, при использовании Linux, это означает, что прошивать FPGA нужно в U-boot'е. Подробности можно прочитать тут.

При работе с fpga2hps мастер в FPGA должен использовать байтовый адрес, при работе с fpga2sdram — адрес слова.

Более подробную информацию можно найти в Cyclone V Device Handbook, Volume 3: Hard Processor System Technical Reference Manual.
Главы 8 HPS-FPGA Bridges и 11 SDRAM Controller Subsystem.

Для нашей задачи нет принципиальной разницы, что использовать. Давайте выберем fpga2sdram в надежде получить большую пропускную способность.

Выбор реализации DMA-контроллера


Мы определились с тем, что будем реализовывать DMA-контроллер в FPGA и с тем, через какой интерфейс он будет работать.
Но как мы будем делать сам контроллер? Можно использовать одну из открытых «корок», например вот эту, которая также доступна через Qsys.

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

Верхний уровень


Со стороны софта всё тоже достаточно просто — нам нужен драйвер, который будет выделять память, получать шинный адрес этой памяти, конфигурировать и запускать DMA-контроллер в FPGA, дожидаться завершения транзакции и получать данные.

И мы его напишем. Но начнём мы не с драйвера, а с немного странной программы в userspace, которая будет выполнять те же самые функции.
Это позволит нам работать с DMA-контроллером в FPGA без необходимости писать что-то на уровне ядра.
Для «продакшена» такие решения обычно не применяют, но для отладки иногда это бывает удобно.

Для простоты прошивки в FPGA будем передавать данные в направлении FPGA -> CPU.
Передача данных в обратном направлении почти полностью аналогична, за исключением одного нюанса, о котором будет сказано ниже.
С направлением CPU -> FPGA мы будем работать при реализации фреймбуфера для LCD.

Итак, план:
  • Прошивка для FPGA
  • Программа в userspace
  • Драйвер ядра

Реализация прошивки FPGA


Начнём с нашего любимого Qsys. Нам потребуются три IP-корки:
  • Processors and Peripherals -> Hard Processor Systems -> Arria V / Cyclone V Hard Processor System
  • Basic Functions -> Bridges and Adaptors -> Memory Mapped -> Avalon-MM Pipeline Bridge
  • Basic Functions -> Bridges and Adaptors -> Clock -> Clock Bridge

Для HPS оставляем всё почти так же, как в предыдущей статье.
На вкладке FPGA Interfaces нужно добавить FPGA-to-HPS SDRAM интерфейс.
Выбираем тип Avalon-MM Bidirectional, ширину — 128 бит.

Ещё нужно поставить галку напротив Enable FPGA-to-HPS Interrupts.
Это позволит нашему DMA-контроллеру «сообщить» CPU о завершении транзакции посредством прерывания.

Также ширину интерфейса HPS-to-FPGA нужно поставить в 64 бита. Это интерфейс, через который CPU будет конфигурировать DMA-контроллер.
Его ширина может быть любой, 64 бита ставим просто потому, что у меня была выбрана такая ширина, и исходный код, описываемый далее, настроен под это значение.

Вот что должно получиться:
FPGA Interfaces

Переходим к Avalon-MM Bridge.
Это корка будет выполнять роль конвертора. Нам нужно экспортировать HPS-to-FPGA из автогенерённого Qsys модуля наружу.
Но если мы просто это сделаем, то получим интерфейс AXI, который намного сложнее, чем Avalon-MM. И работать с которым нам совсем не хочется. После добавления этого модуля Qsys автоматически конвертирует AXI в Avalon. Это займет часть ресурсов, но работать будет намного удобнее.

Настроить модуль нужно так:
Avalon-MM Bridge

Переходим к последнему модулю. Он нужен, чтобы мы могли экспортировать клок от HPS наружу и синхронизировать DMA-контроллер по этому клоку. Его настройка примитивна — нужно просто указать количество клоков, равное 1.

После этого нужно соединить все наши модули (обратите внимание на имена в колонке Export):
Qsys Connections

Осталось сохранить и сгенерировать файлы.

Пришло время реализации нашего примитивного DMA-контроллера. Как мы будем его настраивать?
Для настройки мы будем использовать так называемые Контрольные и Статусные Регистры (Control and Status Register, CSR)
Это блоки фиксированного размера, которые доступны CPU на чтение/запись (контрольные) или только на чтение (статусные).

Доступ к этим регистрам будет осуществляться через HPS-to-FPGA.
Так как интерфейс имеет ширину 64 бита, то можно либо сделать регистры такой же ширины, либо добавить конвертер.
Делать регистры 64-битными сильно накладно. Ведь очень часто в целом регистре используется всего лишь несколько бит.
Лучше сделать регистры 16-битными, а если возникнет необходимость иметь слово большой разрядности использовать 2 или 4 смежных регистра.

Теоретически можно было использовать конвертер, сгенерированный Qsys, указав для IP-корки Avalon-MM Bridge ширину в 16 бит, но на практике этого сделать не удалось — Qsys сгенерировал нерабочий модуль. Ничего страшного, будем использовать свой :)

В качестве конвертера используется модуль avalon_width_adapter.sv, а сами регистры реализованы в модуле regfile_with_be.v

Логика работы модуля регистров чрезвычайно проста — в зависимости от адреса выставляем на шину прочитанных данных содержимое нужного регистра. Если также пришел сигнал записи, то сохраняем в регистр входные данные. Адрес задает номер регистра, а не номер байта. Способ деление на контрольные и статусные регистры задается параметром при сборке — либо старшим битом адреса (адресное пространство в этом случае делится поровну между контрольными и статусными регистрами), либо по указанному параметрами количеству регистров.

Переходим непосредственно к DMA-контроллеру. Для простоты он расположен в топовом модуле.

Всё, из чего будет состоять наш DMA-контроллер — это три счётчика и пара сигналов.

Напомню, что данные наш контроллер выдает на интерфейс Avalon-MM. Подробное описание можно посмотреть тут, но в общем это достаточно простой интерфейс.
Для того, чтобы записать данные, нужно выставить следующие сигналы:
  • sdram0_address — адрес (напомню, что для fpga2sdram это должен быть адрес слова).
  • sdram0_writedata — данные для записи.
  • sdram0_byteenable — сигнал, указывающий на то, какие байты из данных нужно записать. Для простоты ставим его равным 16'hFFFF.
  • sdram0_burstcount — сигнал для управления burst'ом. Опять же для простоты ставим его равным 1.
  • sdram0_write — этот сигнал нужно установить в 1 для выполнения транзакции записи


Единственный нюанс, про который нужно помнить — это наличие сигнала sdram0_waitrequest. Если он равен 1, это означает, что слейв не может в данный момент обработать транзакцию и мастер должен оставить все свои сигналы неизменными. Именно то, как часто сигнал sdram0_waitrequest будет выставляться в 1 и определит в итоге пропускную способность нашего DMA.

Итак, опишем используемые счётчики. Первый — это счётчик адреса, addr_cnt. При начале DMA-транзакции он устанавливается в адрес, заданный CPU. После каждой успешной транзакции (когда sdram0_waitrequest не равен 1) этот счётчик увеличивается на 1.

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

Третий счётчик — счётчик тактов, cycle_cnt, будет сбрасываться в 0 при начале DMA-транзакции и дальше увеличиваться на 1 в каждом такте.
Он нужен для того, чтобы мы могли узнать, сколько тактов заняла наша DMA-транзакция, и рассчитать пропускную способность.

Итого, для счётчиков мы получаем следующий код:
Описание счётчиков
// For emulate data
logic [63:0] data_cnt;

// Current address on SDRAM iface
logic [31:0] addr_cnt;

// Overall cycles count. 
logic [31:0] cycle_cnt;

// Form pseudo-data 
always_ff @( posedge clk_w )
  if( !test_is_running )
    data_cnt <= '0;
  else
    if( !sdram0_waitrequest )
      if( data_cnt != ( dma_data_size - 1 ) )
        data_cnt <= data_cnt + 1;

// Increase address if no waitrequest
always_ff @( posedge clk_w )
  if( run_test_stb )
    addr_cnt <= dma_addr;
  else
    if( !sdram0_waitrequest )
      addr_cnt <= addr_cnt + 1;

always_ff @( posedge clk_w )
  if( test_is_running_stb )
    cycle_cnt <= '0;
  else
    if( test_is_running )
      cycle_cnt <= cycle_cnt + 1;



Вернёмся к сигналам. Нам потребуется только:
  • test_is_running — сигнал, указывающий, в процессе ли сейчас DMA-транзакция.
  • run_test_stb — сигнал-строб, активный в течение 1 такта в момент, когда CPU запускает DMA-контроллер
  • test_finished — сигнал, показывающий, что необходимое количество данных записано. Также заводится на прерывание.

Формирование этих сигналов тривиально.

Что нам нужно для настройки DMA-контроллера (это будут наши контрольные регистры)?
  • Адрес буфера, куда нужно скопировать данные
  • Размер данных для записи
  • Сигнал для запуска транзакции, из которого потом мы выделим фронт

Статусными регистрами будут:
  • Сигнал занятости DMA-контроллера
  • Значение счётчика cycle_cnt

Итого, вот так выглядит наше объявление регистров:
Объявление регистров
// Control registers
`define DMA_CTRL_CR        0
        `define DMA_CTRL_CR_RUN_STB      0

`define DMA_ADDR_CR0       1
`define DMA_ADDR_CR1       2

`define DMA_SIZE_CR0       3
`define DMA_SIZE_CR1       4

// Status registers
`define DMA_STAT_SR        0
        `define DMA_STAT_SR_BUSY         0

`define DMA_CYCLE_CNT_SR0  1
`define DMA_CYCLE_CNT_SR1  2


А вот так выглядит назначение регистров:
Назначение регистров
// Control from CPU -- bit for start, DMA buffer address and transaction size.
assign run_test       = cregs_w[`DMA_CTRL_CR][`DMA_CTRL_CR_RUN_STB];
assign dma_addr       = { cregs_w[`DMA_ADDR_CR1], cregs_w[`DMA_ADDR_CR0] };
assign dma_data_size  = { cregs_w[`DMA_SIZE_CR1], cregs_w[`DMA_SIZE_CR0] };

// Status for CPU -- current state and overall cycles count.
assign sregs_w[`DMA_STAT_SR][`DMA_STAT_SR_BUSY] = test_is_running;
assign { sregs_w[`DMA_CYCLE_CNT_SR1], sregs_w[`DMA_CYCLE_CNT_SR0] } = cycle_cnt;


Всё, можно компилировать проект. Для начала выполним Analysis & Synthesis.

После этого создадим файл SignalTap — с его помощью мы сможем смотреть значения сигналов внутри FPGA
Для этого заходим File -> New -> SignalTap II Logic Analyzer File и жмём OK.
В появившемся окне нужно добавить необходимые сигналы. Должно получиться что-то вроде:
SignalTap File

Сохраняем файл, добавляем его в проект и выполняем полную сборку.

После окончания сборки нам нужно получить .rbf файл:
quartus_cpf -c etln.sof dma.rbf

Всё, прошивка готова. Переходим к софтовой части.

Внимание: помните, что после изменения настроек в Qsys (в частности после включения fpga2sdram) нужно перегенерировать и пересобрать Preloader.

Также обратите внимание, что в гитхабе для FPGA выложены только файлы с Verilog-кодом и файл с настройками Qsys.
Файлы проекта (.qpf, .qsf и т.д.) отсутствуют из-за того, что не несут реально полезной информации.

Реализация userspace программы


Что нам нужно для того, чтобы работать с DMA-контроллером со стороны софта?

Во-первых, нам нужно уметь настраивать и запускать DMA-контроллер. Для этого мы используем программу mem из предыдущей статьи.

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

Тут нужно маленькое отступление. Обычно все процессы в userspace и даже большинство в ядре работают с так называемыми виртуальными адресами. А вот DMA-контроллеру нужно передать физический адрес (более точно, шинный адрес, но для используемых нами платформ он равен физическому)

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

Что же делать в userspace? Нам поможет замечательный файл /proc/[PID]/pagemap, который содержит информацию о отображении всех виртуальных страниц в физические для любого процесса.

Информация для каждой страницы в этом файле занимает равно 8 байт. При этом младшие 55 бит содержат так называемый номер физической страницы — Page Frame Number (PFN), а старшие 9 бит — различные флаги (наличие страницы, нахождение в swap и т.д.) Подробное описание можно посмотреть тут или в man proc

Таким образом, зная виртуальный адрес и размер страницы, легко вычислить номер виртуальной страницы. После этого из файла /proc/[PID]/pagemap нужно просто считать 8 байт по нужному смещению и в младших 55 битах будет номер физической страницы. А его уже легко перевести в физический адрес, который мы и запишем в DMA-контроллер.

Если наша область памяти начинается на границе страницы, то всё становится ещё немного проще.
Поэтому вместо функции malloc() лучше использовать функцию posix_memalign(), которая позволяет задавать желаемое смещение.

Также, для того, чтобы предотвратить выгрузку данных из RAM в swap, желательно использовать функцию mlock()

Описанные выше вещи и выполняет программа phys_addr.c

Важное замечание — страницы, смежные в виртуальном адресном пространстве, не обязательно будут смежными в RAM.
Поэтому в данном способе мы не можем записывать DMA-контроллером данные, размером больше, чем размер страницы.
Обойти это ограничение мы сможем, когда напишем драйвер.

Промежуточная проверка


Итак, прошивка и тестовая программа готовы, время немного их потестировать.

Скопируем бинарники на SD-карту, подключим USB-Blaster и запустим нашу плату.

Выше я писал, что включать fpga2sdram интерфейс нужно до загрузки Linux. Это действительно так, но не всегда.
Если включить интерфейс уже в Linux и попытаться из FPGA прочитать данные в памяти, то система полностью зависнет.
А вот записать данные получится. Естественно, этот вариант явно не стоит использовать на боевой системе и ниже я напишу, как правильно инициализировать интерфейс fpga2sdram. Но для промежуточного тестирования нам это вполне подойдет.

Для начала прошьём FPGA:
cat dma.rbf > /dev/fpga0 

Теперь включим интерфейс HPS-to-FPGA:
echo 1 > /sys/class/fpga-bridge/hps2fpga/enable 

Если сейчас мы запустим SignalTap, то увидим, что сигнал sdram0_waitrequest постоянно висит в 1. Это связано с тем, что интерфейс fpga2sdram выключен.

Включим его:
./mem.o 0xFFC25080 0x3fff

Запись единиц в биты регистра 0xFFC25080 включает соответствующие порты интерфейса fpga2sdram. Описание, какие биты за какие порты отвечают, приведено в вышеуказанном Handbook'е. Нам для простоты достаточно включить все порты (всего в регистре используются 14 бит).

Теперь в SignalTap сигнал sdram0_waitrequest стал равен 0.

Запускаем утилиту phys_addr:
./phys_addr 

Она выделит буфер и выведет его физический адрес. У меня это 0x2d593000.
Мы помним, что при использовании интерфейса fpga2sdram нужно адресоваться по словам.
Так как слова у нас 128-битные, то адрес слова рассчитывается так:
0x2d593000 / 16 = 0x2d59300

Запишем этот адрес в регистры FPGA:
./mem.o 0xC0000002 0x2d59300 

Для адреса у нас используются контрольные регистры под номерами 1 и 2. Каждый адрес — это 16 бит, или 2 байта. Так как HPS-to-FPGA начинается с адреса 0xC0000000, то у первого контрольного регистра байтовый адрес будет равен 0xC0000002
Напомню, что утилита mem.c использует именно байтовые адреса.

После этого запишем длину DMA-транзакции в контрольный регистр номер 3. Длина не должны превышать размера страницы, а для нас это 4096 байт. Так как наш fpga2sdram интерфейс имеет ширину в 128 бит, а размер транзакции мы указываем в словах, то в третий регистр мы должны записать число 256:
./mem.o 0xC0000006 256 


Далее настроим SignalTap на захват по отрицательному фронту сигнала test_is_running и запустим DMA-контроллер.
Для этого нужно записать в нулевой бит нулевого регистра вначале 0 (если его там нет), а потом 1. При этому нужно помнить, что утилита mem.o выполняет транзакции по 4 байта, а это 2 наших регистра. Поэтому, если мы не будем осторожны, то затрём данные в соседнем регистре.

Итого, нам нужно вначале прочитать данные по адресу 0xC0000000, а потом записать их же, но с установленным нулевым битом.

Читаем:
./mem.o 0xC0000000 

У меня прочиталось 0x93000000

Записываем:
./mem.o 0xC0000000 0x93000001 

После этого мы должны получить в SignalTap примерно такую картинку:
SignalTap Result

Как видите, значение счётчика cycle_cnt на момент окончания транзакции равно 3167.
Давайте посчитаем пропускную способность. Частота тактового сигнала в моем проекте — 150 МГц (чтобы иметь возможность менять частоту в более широких пределах я не использую клок от HPS, а импортирую его туда, взял с PLL. Эти изменения тривиальны, но в гитхабе их нет).
Ширина — 128 бит. За 3167 тактов передано 256 слов. Итого:
128 * 150 / (3167/256) = 1551 Мбит/c 

UPDATE: Такая маленькая пропускная способность получена из-за опечатки, подробности в выводах.

Осталось убедиться, что данные записались правильно. «Снимаем» утилиту phys_addr с паузы, нажав Enter.
Мы должны увидеть такой текст:
Результат выполнения phys_addr

0: 0x0
1: 0xffffffffffffffff
2: 0x1
3: 0xfffffffffffffffe
...
507: 0xffffffffffffff02
508: 0xfe
509: 0xffffffffffffff01
510: 0xff
511: 0xffffffffffffff00


Если увидели, то всё прошло успешно.

Поэкспериментировав с разными параметрами, я увидел, что частота тактового сигнала почти не влияет на пропускную способность.
Она остается примерно одинаковой, что для 25 МГц, что для 150 МГц.
А вот ширина интерфейса fpga2sdram, напротив, даёт почти линейную зависимость — проверено при 64 и 128 битах. Для 256 не проверял.

Естественно, из-за того, что количество записываемых данных мало (всего 4096 байт), погрешность измерения довольно большая.
Увеличить размер DMA-транзакции мы сможем, написав свой примитивный драйвер.

Написание драйвера


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

Основная идея проста — при запуске драйвера мы задаем параметром, какой размер транзакции нам необходим.
Драйвер выделяет память и записывает шинный адрес и размер транзакции в FPGA.

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

После этого драйвер создает два char-девайса:
  • /dev/etn-ctrl — для запуска DMA-транзакции
  • /dev/etn-data — для получения данных

При чтении из файла /dev/etn-ctrl, происходит запуск DMA-транзакции.
После этого вызов блокируется до прихода прерывания от FPGA.

Когда прерывание приходит, вызов завершается. Это означает, что данные записаны и их можно считать из файла /dev/etn-data.

Чтобы драйвер заработал в .dts файл нужно добавить следующие строки:
Изменения в .dts

fpga {
    compatible = "mtk,etn";
    interrupts = <0x0 0x28 0x1>;
};


Первая строка задает совместимый драйвер, а вторая — номер и тип прерывания от FPGA.

При использовании транзакции размером 4MB пропускная способность выходит порядка 2000 Мбит/с 20 Гбит/c (см. UPFATE в Выводах).

Выводы


Был написан примитивный DMA-контроллер в FPGA и измерена его пропускная способность.

Она составила около 2 Гбит/c.
UPDATE:
Маленькая величина пропускной способности обусловлена опечаткой в настройках DDR3.
А именно тем, что клок PLL был задан равным 125 МГц, а не 25 МГц, как есть на самом деле.
Из-за этого коэффициенты умножителя и делителя для PLL были рассчитаны неверно.
В итоге DDR3 работал на 66 МГц вместо положенных 333 МГц.

При правильных коэффициентах и ширине интерфейса в 256 бит пропускная способность составляет около 16-17 Гбит/c, что соответствует теоретической для интерфейса DDR3 с шириной 32 бита и частотой 333 МГц.

Более подробно опишу в ближайшей статье.


Дальнейший план статей такой, если, конечно, они кому-нибудь интересны:
  • Реализация фреймбуфера для ILI9341 в FPGA
  • Работа с SGDMA-контроллером
  • Реализация гигабитного 2-х портового NIC в FPGA с использованием SGDMA-контроллера

Спасибо тем, кто добрался до конца! Удачи.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+31
Comments 4
Comments Comments 4

Articles