На плате MCY112 установлено 2 одинаковых чипа флэш памяти W25Q16, 2 Мегабайта в каждом чипе.
Первый чип (1) предназначен для хранения пользовательских данных, например, для хранения программы процессора RISC-V.
Второй чип (2) может использоваться для автозагрузки FPGA Cyclone после подачи питания на плату.
В этой статье я расскажу, как программировать оба чипа.
Давайте сперва расскажу про второй чип. Сказать по правде, при обычном ежедневном использовании платы врядли вам может понадобиться автоматическая загрузка FPGA. Обычно работаешь над проектом, изменяешь его и пробуешь просто загружая ПЛИС через JTAG. После выключения-включения питания конечно загружать ПЛИС нужно заново. Другое дело, если плата будет использоваться в составе какого-то другого оборудования. Тогда программатор там в общем и не нужен. Можно один раз прошить флэшку автозагрузки и после этого каждый раз при включении питания на плату она сама будет сразу стартовать. Обычно для загрузки FPGA мы ставили на наши платы микросхемы Altera типа EPCS, но оказалось, что другие SPI флэш микросхемы, например W25Q16, замечательно подходят. На рисунке выше показано где какая флэш на плате MCY112 и где разъем Active Serial и как установить программатор для прошивки флэшки автозагрузки FPGA для режима Active Serial.
Микросхему W25Q16 для автозагрузки FPGA можно прошить обычным программатором MBFTDI из среды САПР Quartus II, только JTAG программатор нужно в среде квартус также нужно установить в режим Active Serial:
Для создания файла прошивки FPGA зайдите в меню Assignments->Device. В диалоговом окне нажмите кнопку Device and pin options. На вкладке Configuration выбирайте Configuration Scheme -> Active Serial и ниже поставьте галочку Use configuration device -> EPCS4. Мы используем W25Q16, но она совместима с EPCS4. После компиляции проекта появится файл с расширением POF, он и будет использоваться для программирования флэшки в режиме Active Serial.
Теперь расскажу про первую флэшку.
Как я уже выше написал она предназначена для хранения пользовательских данных. Я уже опубликовал на github проект MCY112 https://github.com/marsohod4you/MCY112 и там есть несколько примеров ПЛИС проектов для этой платы MCY112. Одна из папок picorv32 и проект системы на кристалле RISC-V находится в picorv32/picosoc-altera. Это минимальный процессор RISC-V. Он не самый быстрый, но за то он занимает очень мало места в ПЛИС и запускается и работает сразу из SPI Flash. Про этот проект процессора я напишу отдельную статью. Сейчас же я говорю о флэшке. Её нужно как-то научиться прошивать, прежде чем пытаться запустить процессор, работающий из SPI Flash.
Я сделал такой довольно простой проект, который позволяет из компьютера через последовательный порт посылать данные, которые будут идти, как команды в SPI Flash и таким образом я смогу читать и писать SPI флэшку. Проект для Quartus находится на гитхаб в папке SpiFlash.
Мы предлагаем, как самый простой способ использовать плату MCY112 совместно с нашим программатором MBFTDI. Габариты платы так подобраны, что программатор MBFTDI точно устанавливается на JTAG разъем. Правда, должен сказать оригинальный программатор MBFTDI должен быть немного переделан, добавлена перемычка, которая с USB подает +5В на плату MCY112. Будущие программаторы MBFTDI мы так и будет делать.
Программатор MBFTDI для платы MCY112 хорош тем, что он
1) дает питание на плату из USB +5V
2) имеет JTAG для программирования ПЛИС
3) дает канал передачи последовательного порта.
Таким образом, через JTAG загружаем в ПЛИС специальный образ полученный компиляцией FPGA проекта SpiFlash. Потом используя написанные мною питоновские утилиты read_flash.py и write_flash.py можем читать флэшку или писать из неё.
Расскажу немного об FPGA проекте SpiFlash.
Шина SPI в отличии от шины последовательного порта имеет гораздо больше сигналов.
Как минимум в SPI есть сигналы /CS, CLK, DI, DO. А у последовательного порта по минимуму только SERIAL_RX и SERIAL_TX. Я придумал как принимаемый из последовательного порта байт сразу конвертировать в передачу в SPI шину. Есть нюанс, что при последовательной передаче байты поступают младшими битами вперед, а в шине SPI старшими битами вперед. Программа на питоне, которая будет работать с платой учитывает это и разворачивает биты в байтах при передаче.
Следующий момент. Я принимаю байт из последовательного порта и сразу же на лету отправляю его в SPI шину через сигнал DO, но только удаляю старт бит и стоп бит, приходящие при последовательной передачи на сигнале SERIAL_RX.
Сигнал CLK шины SPI так же подается только во время, когда принимается байт последовательной передачи. При этом, конечно, частота CLK на шине SPI получается четко связана со скоростью передачи в последовательном порту. У меня в проекте используется скорость последовательной передачи 6 Мегабит в секунду.
Импульсы CLK в SPI идут только во время битов данных на SERIAL_RX.
Шина SPI одновременно передает и принимает байты. Сколько байт отправлено в устройство подключенное по SPI столько данных и может быть одновременно получено от него. Этот механизм так же реализован у меня. Одновременно с приемом битов данных из SERIAL_RX они отправляются в SPI через сигнал DO и одновременно биты приходящие из SPI через сигнал DI фиксируются в отдельном сдвиговом регистре и потом отправляются на передачу через сигнал SERIAL_TX в последовательный порт к компьютеру.
Отдельно следует рассказать о сигнале /CS. Я предполагаю, что программист на компьютере сформировал пакет байтов представляющих из себя законеченную SPI команду. Этот пакет байтов отправляется например питоновской программой в последовательный порт. Физически все эти данные будут идти плотным потоком один за другим.
Я реализовал сигнал SPI /CS таким образом, что как только фиксируется старт бит на сигнале SERIAL_RX, то сразу начинается передача в SPI и значит сразу ставится сигнал /CS в ноль. Если после приема какого-то байта на SERIAL_RX некоторое время после последнего стоп бита нет никаких новых данных, то я считаю, что пакет окончен и /CS шины SPI возвращается в единицу. И еще один нюанс. Когда /CS переводится в единицу одновременно я посылаю последний дополнительный байт в сторону компьютера. Таким образом, программа на питоне может точно знать когда вся команда физически уже выполнена и следовательно точно знает когда уже можно посылать следующую команду. Для этого она должна прочитать из последовательного порта на один байт больше чем передала.
Можно просимулировать Verilog модуль serial2spi с помощью Icarus Verilog. У меня есть для этого написанный тестбенч в папке SpiFlash/sim.
Запустите в консоли команды:
>iverilog -o qqq tb.v serial2spi.v P25Q32H.v serial.v
>vvp qqq
>gtkwave testbench.vcd
В окне программы GtkWave можно посмотреть, как меняются все сигналы:
Здесь во флэшку передается команда Release Power-down (ABh). После переданного байта команды 0xAB (биты развернуты старшими вперед) так же передаются 4 вспомогательных байта нулей. Флешка отвечает байтом DeviceID на четвертом вспомогательном байте. А вот в компьютер уже позвращается не 5, а 6 байт. Шестой принятый байт на стороне ПК будет збозначать, что вся команда выполнена и /CS переведен в единицу.
В принципе, всё то же самое можно увидеть не только в симуляционной модели, но и в реальной жизни, если подключить к проекту SignalTap - инструмент Quartus для захвата сигналов проекта:
Только DeviceID отличается от модели, потому, что в симуляции на на плате используются разные модели чипов.
Ну и на последок расскажу, как я запускаю питоновские утилиты:
> /d/Programs/Python39/python read_flash.py COM14 cat0_read.jpg 1000 23015
Питоновские программы принимают в командной строке параметрами имя последовательного порта, имя файла для записи или чтения, адрес во флэш памяти в шестнадцатеричном виде (только адреса выровненные по границе сегмента) и размер файла.
Я записываю JPEG файл по адресу 0x1000, затем считываю его из флэшки в другой файл и могу проверить его контрольную сумму или просто открыть программой просмотра изображений. Работает.
Теперь я могу компилировать программу для RISC-V и записывать firmware во флэш и пробовать запускать процессор на плате MCY112.
Подробнее...