Давно я ничего не писал про LiteX. Во-первых, очень много работы. Во-вторых, пришлось почитать курс студентам, подготовка тоже дико отвлекала, но наконец семестр подходит к концу. Ну, и в-третьих, в своих опытах я на пару шагов дальше того, что описываю, и вот эти опыты меня затянули. Пока что там всё выглядит достаточно мрачно. Производительность там такая, что плакать хочется. В общем, было трудно прерваться, чтобы описать то, что находится ещё на гарантированно светлой стороне. Но если вы читаете эти строки, то я себя пересилил.
Я уже многократно писал, что рассматриваю LiteX как некий аналог подсистемы Qsys из среды разработки Quartus. То есть, как удобное средство составить шинно-ориентированную систему из множества готовых ядер. Но если Qsys – он только для Альтер, то LiteX – он подходит и для Altera (Intel), и для Xilinx, и для Lattice. А сейчас я по работе плотно вожусь именно с Латтисами. У Латтисов самое узкое место – это параметр FMax. И вот построение базовых систем на базе шины Wishbone у Litex получается очень красиво. Там FMax выходит достаточно высоким. Даже у Латтисов он превышает 100 МГц.
В предыдущих статьях мы уже научились добавлять в систему устройства, доступные по шине через регистры команд-состояний (CSR), а также пассивные (Slave) устройства с шиной Wishbone. Сегодня мы добавим на шину активное (Master) устройство. Поехали!
На самом деле мы уже знаем практически всё, чтобы добавить своё активное устройство. Мало того, если для пассивного приходилось добавлять регион, то здесь никаких регионов не требуется. Мастер – он же к любому участку шины может доступ получить! Поэтому класс, описывающий включение мастера, будет выглядеть так:
class SpiToWishBone (Module):
def __init__(self, clk, rst, spi, notEmpty, lastByte, wishbone):
self.bus = wishbone
self.data_width = 32
self.specials += Instance("SpiToWishBone",
i_clk = clk,
i_rst = rst,
i_sck = spi["sck"],
i_mosi = spi["mosi"],
o_miso = spi["miso"],
i_cs = spi["cs"],
o_notEmpty = notEmpty,
i_lastByte = lastByte,
o_wb_adr_o = wishbone.adr,
i_wb_dat_i = wishbone.dat_r,
o_wb_dat_o = wishbone.dat_w,
o_wb_we_o = wishbone.we,
o_wb_sel_o = wishbone.sel,
o_wb_stb_o = wishbone.stb,
i_wb_ack_i = wishbone.ack,
i_wb_err_i = wishbone.err,
o_wb_cyc_o = wishbone.cyc
)
Как видно, это интерфейс для внешнего (описанного на Верилоге) моста между SPI и шиной Wishbone. Линии MOSI, MISO, SCK и CS – стандартные SPI-ные линии. Так как у линии sck префикс «i» — это вход. То есть, spi является пассивным. Но об этом – чуть ниже. Линии notEmpty и lastData тоже относятся к нашей реализации шины SPI.
А всё, что ниже – это шина Wishbone.
Как эти шины подключаются к нашей системе – ну мне уже скучно описывать, это я всё неоднократно описывал в предыдущих статьях. Кто попробовал всё это на практике, тот уже видит, что мы вышли из области творчества в область рутины. Давайте, чтобы хоть что-то новое рассказать, я покажу, как раскрываются эти префиксы сигналов «i», «o», «io» и бывают ли какие-то ещё префиксы. Чтобы быстро найти разгадку, ставим точку останова на одну из строк, где эти префиксы фигурируют. Такие вещи лучше всего ловить при трассировке.
Так как мы создаём объект класса Instance, то ставим курсор на его имя, нажимаем правую кнопку «мыши» и выбираем «Перейти к определению».
Немного поездив по тексту, находим ответ на наш вопрос:
Оказывается, тут же мы можем добавлять не только входы, выходы и двунаправленные порты для модуля, но и параметры для него. В этом случае, префикс будет «p_». Ну хоть что-то новое. А то статьи какие-то шаблонные пошли.
Итак. Мы сделали класс устройства. Теперь в основном коде его надо подключить. Тоже уже отработанный процесс! Причём он намного проще, чем мы это делали раньше!
# Self Reset Generator
local_rst = Signal()
reset_gen = rst_gen (clk,rst,local_rst)
soc.platform.add_source("rst_gen.v")
soc.submodules.reset_gen = reset_gen
Это я добавил собственный генератор сброса, так как на плате он отсутствует, а шина требует. Сразу после загрузки он сформирует короткий импульс на линии local_rst:
#WishBone Master
soc.platform.add_source("axis_fifo.v")
soc.platform.add_source("axis_wb_master.v")
soc.platform.add_source("SPItoAXIS.sv")
soc.platform.add_source("SpiToWishBone.sv")
wbMaster = wishbone.Interface()
spi_pins = {
'sck' : soc.platform.request ('sck',0),
'mosi' : soc.platform.request ('mosi',0),
'miso' : soc.platform.request ('miso',0),
'cs' : soc.platform.request ('cs',0)
}
spi2wb = SpiToWishBone (clk, local_rst,spi_pins,soc.platform.request ('notEmpty',0),
soc.platform.request ('lastByte',0),wbMaster)
soc.submodules.spi2wb = spi2wb
soc.bus.add_master ("spi2wb_master",wbMaster)
Сначала мы надобавляли Верилоговскимх файлов (зачем так много – будет описано ниже), после чего привычным движением руки описали шину, описали ножки, которые пойдут на SPI, объявили объект мастера и подключили его к общей шине системы. ВСЁ! На этом можно расходиться… Тема статьи исчерпана!
Неожиданное заключение
Написанное выше – это примерно четверть от задуманной статьи. Дальше по планам должно идти описание тестового окружения. Вторая четверть – как был найден более-менее приемлемый Wishbone мастер (стык Wishbone с AXI Stream). Если что – я про этот проект alexforencich/verilog-wishbone: Verilog wishbone components. Попутно я хотел пройтись по тому, чем плохи другие найденные реализации. Третья четверть планировалась на то, чтобы рассказать о недостатках найденного проекта (нет таймера для ловли таймаутов, а также система адресации и инкремента адреса не совместима с тем, что принято в LiteX). Ну, и рассказ о том, как всё было состыковано. Ну, и последняя четверть – моя реализация преобразователя SPI в AXI Stream и программная поддержка FT232H через кросс платформенную библиотеку libftdi libFTDI » FTDI USB driver with bitbang mode.
Но и так-то не хватало сил из-за подготовки лекций и лабораторных для студентов, плюс случилось то, из-за чего пропало вдохновение вовсе. И очень долго не возвращалось. Мало того, из случившегося вытекло, что фирма Латтис начала угнетать нас по национальному, расовому или религиозному признаку. Документацию на чипы уже не скачать. Хорошо, хоть ГитХаб с открытой средой разработки пока доступен. Но всё равно, интерес к их и без того не самым лучшим в мире чипам, подорван. Короче, со вдохновением – труба. Мы посовещались и решили, что раз у статьи есть завершённая мысль, то опубликуем её так, а недостающие части я опишу, когда это самое вдохновение вернётся.
Что делать с циклом дальше – большой вопрос. Дело в том, что дальше хоть делай процессорную систему, хоть что-то без процессора, а всё равно надо задействовать SDRAM. И вот выяснилось, что при этом параметр FMax будет не очень высоким. Буквально 50 МГц. Судя по критическим цепям, задержки вносит не сам контроллер SDRAM, а подсистема кэширования. Если превысить FMax, тесты ОЗУ будут давать ошибки. А производительность ОЗУ будет совсем смешной. Я несколько дней поснимал времянки (и в режиме 1:1, и в режиме 1:2), и вижу, что особо там выскакивать некуда. Попутно была найдена табличка, которая очень похожа на мои выводы: LiteDRAM benchmarks summary. Там у неё много вкладок. И результаты – один другого хуже для SDRAM. А на целевой плате припаян SDRAM, и он нужен хоть для процессора, хоть как буферное ОЗУ для того же HDMI, хоть для хранения данных.
В общем, как шинная основа, LiteX – просто замечательная штука, а как полноценная система – кажется, уже не очень. Возможно, кто-то знает про подсистему памяти что-то такое, что опровергнет мои выводы. Рад буду списаться хоть в комментариях, хоть в диалогах. Но эта часть – уже совсем на будущее. Сначала пусть вернётся вдохновение для описания тестового набора, а там уж и времянки со своими мыслями я сделаю. А так – все принципы для того, чтобы построить шинно-ориентированную систему на базе LiteX, описав для неё рабочие блоки на чистом Verilog, мы изучили. И эта подсистема получается весьма и весьма эффективной. Буду рад, если кому-то это пригодится. И буду надеяться на то, что следующие части выйдут более-менее скоро.