- Разработка простейшей «прошивки» для ПЛИС, установленной в Redd, и отладка на примере теста памяти
- Разработка простейшей «прошивки» для ПЛИС, установленной в Redd. Часть 2. Программный код
- Разработка собственного ядра для встраивания в процессорную систему на базе ПЛИС
- Разработка программ для центрального процессора Redd на примере доступа к ПЛИС
- Первые опыты использования потокового протокола на примере связи ЦП и процессора в ПЛИС комплекса Redd
- Веселая Квартусель, или как процессор докатился до такой жизни
- Методы оптимизации кода для Redd. Часть 1: влияние кэша
- Методы оптимизации кода для Redd. Часть 2: некэшируемая память и параллельная работа шин
- Экстенсивная оптимизация кода: замена генератора тактовой частоты для повышения быстродействия системы
- Доступ к шинам комплекса Redd, реализованным на контроллерах FTDI
- Работа с нестандартными шинами комплекса Redd
- Практика в работе с нестандартными шинами комплекса Redd
- Проброс USB-портов из Windows 10 для удалённой работы
- Использование процессорной системы Nios II без процессорного ядра Nios II
- Практическая работа с ПЛИС в комплекте Redd. Осваиваем DMA для шины Avalon-ST и коммутацию между шинами Avalon-MM
- Разработка простейшего логического анализатора на базе комплекса Redd
- Разработка логического анализатора на базе Redd – проверяем его работу на практике
- Делаем голову шинного USB-анализатора на базе комплекса Redd
- Моделируем поведение Quartus-проекта на Verilog в среде ModelSim
Вообще, эта статья в виде DOC файла появилась ещё в июне. Тогда был написан блок из пяти статей одновременно. Но выгрузить DOC файл на Хабр — та ещё задача. Поэтому так вышло, что время именно на неё появилось только сейчас (а ещё две томятся в ожидании). При выгрузке, я заметил, что если не пропитаться духом предыдущих статей, эта выглядит каким-то занудством. Поэтому, если есть такое желание — освежите в памяти хотя бы прошлую статью, а лучше эти две («Делаем голову шинного USB-анализатора…» и «Моделируем поведение Quartus-проекта…»).
Введение
Итак, готовая модель, где её взять? Есть проект, решающий точно такую же задачу, что и анализатор, который мы разрабатываем, но имеющий пару особенностей. Первая особенность — он для ПЛИС Xilinx. Вторая — он совершенно не документирован. Как-то работает. Можно даже купить готовую макетную плату, залить в неё готовый двоичный код… И получить какую-то функциональность. Кому нужен прибор любой ценой, может просто пойти по этому пути. Но как его развивать — не знает никто. Тот проект лежит тут . В каталоге ulpi_wrappertestbench лежит комплект файлов для тестирования подсистемы обёртки вокруг ULPI. Там рекомендуют вести моделирование в среде Icarus Verilog, но я порылся и не нашёл на поверхности путных описаний, как это делать на языке SystemC. Поэтому решил продолжить работу в среде ModelSim. Если бы я знал, чем это кончится… Но я не знал. Поэтому начал исследования. По ходу изложения будут показаны как успехи, так и неудачи. Начнём с неудач, чтобы все видели, как не стоит делать.
Неудачная попытка сделать всё «в лоб»
Сначала я решил взять и прогнать через моделирование готовый пример. Привычным движением руки (а руку мы набивали в прошлой статье), я создал тестовый набор, содержащий файлы на Verilog и SystemC. У меня вышло как-то так:
Запускаю ModelSim и не вижу в группе work ничего, что было бы связано с SystemC. Верилоговский код вижу, а Сишный — нет.
Если посмотреть на логи, то видно, что его и не пытались собирать. В чём дело?
Полезная информация про настройку файла *.do
Известно, что для запуска ModelSim используется файл *.do. Но как любитель всё делать «мышкой», я никогда не заглядывал ему внутрь. Давайте его поищем и откроем! В каталоге проекта находится только один такой файл. Наверное, это то, что нам нужно.
Открываем его. В начале — сборка всяких служебных вещей и файлов, входящих в проект.
transcript on
if ![file isdirectory verilog_libs] {
file mkdir verilog_libs
}
if ![file isdirectory vhdl_libs] {
file mkdir vhdl_libs
}
vlib verilog_libs/altera_ver
vmap altera_ver ./verilog_libs/altera_ver
vlog -vlog01compat -work altera_ver {c:/intelfpga_lite/17.1/quartus/eda/sim_lib/altera_primitives.v}
vlib verilog_libs/lpm_ver
vmap lpm_ver ./verilog_libs/lpm_ver
vlog -vlog01compat -work lpm_ver {c:/intelfpga_lite/17.1/quartus/eda/sim_lib/220model.v}
vlib verilog_libs/sgate_ver
vmap sgate_ver ./verilog_libs/sgate_ver
vlog -vlog01compat -work sgate_ver {c:/intelfpga_lite/17.1/quartus/eda/sim_lib/sgate.v}
А вот в конце — явно сборка нужных нам вещей, это я сужу по имени файла ulpi_wrapper.v:
vlog -vlog01compat -work work +incdir+C:/Work/UsbHead1/SystemCPlay {C:/Work/UsbHead1/SystemCPlay/ulpi_wrapper.v}
vsim -t 1ps -L altera_ver -L lpm_ver -L sgate_ver -L altera_mf_ver -L altera_lnsim_ver -L cycloneive_ver -L rtl_work -L work -L UsbHead1 -voptargs="+acc" lalala
add wave *
view structure
view signals
run 10 us
Действительно. Есть сборка Verilog-овского модуля, и нет никаких намёков на сборку модулей на SystemC. Жаль только, что этот DO-файл автоматически создаётся при каждом запуске моделирования, так что просто взять и отредактировать его не получится. Его создаёт очень сложный TCL-скрипт. Править его — нет никакого желания. Но после статьи про весёлую квартусель, наверное, понятно, что такая мелочь — не повод опускать руки. Наверняка, всё уже есть. Жаль только, что в документации сказано, что «вы можете сделать скрипт так, а можете – так», и нет никаких намёков на примеры. Ну что ж, давайте выводить всё экспериментальным путём. Создаём файл C:WorkUsbHead1SystemCPlaymyrun.do и пытаемся передать ему управление. Сначала пробуем это сделать так:
Основной DO-файл всё равно продолжает вырабатываться, но его концовка становится такой:
vlog -sv -work UsbHead1 +incdir+C:/Work/UsbHead1/UsbHead1/synthesis/submodules {C:/Work/UsbHead1/UsbHead1/synthesis/submodules/UsbHead1_master_0_b2p_adapter.sv}
vlog -sv -work UsbHead1 +incdir+C:/Work/UsbHead1/UsbHead1/synthesis/submodules {C:/Work/UsbHead1/UsbHead1/synthesis/submodules/UsbHead1_master_0_timing_adt.sv}
vlog -vlog01compat -work work +incdir+C:/Work/UsbHead1/SystemCPlay {C:/Work/UsbHead1/SystemCPlay/ulpi_wrapper.v}
vsim -t 1ps -L altera_ver -L lpm_ver -L sgate_ver -L altera_mf_ver -L altera_lnsim_ver -L cycloneive_ver -L rtl_work -L work -L UsbHead1 -voptargs="+acc" lalala
do C:/Work/UsbHead1/SystemCPlay/myrun.do
Мы видим, что Verilog файл по-прежнему компилится, далее — по-прежнему запускается процесс моделирования (правда, я-то это видел при пробных запусках, но теперь могу точно сказать, что команда vsim запускает этот процесс), после чего управление передаётся нашему скрипту. Этот скрипт должен процессом отображения управлять. Но сборкой мы управлять по-прежнему не можем. Если собранных файлов не хватает, система отвалится по ошибке раньше, чем нам дадут что-либо сделать. Ну и отлично, пробуем последний вариант настройки.
И тут начинается самое интересное. Оно настолько важно, что я возьму это в рамочку.
Выбираю скрипт, а он не выбирается. Вхожу в настройку (у меня предыдущий выбранный вариант). Выбираю, а не выбирается. И так — хоть до посинения. Пока я это заметил, пока нашёл, как победить — вечер убил! Оказалось, если просто выбрать файл, кнопка Apply останется серой. И изменения не будут запомнены. Надо обязательно через правку других параметров диалога добиться, чтобы кнопка Apply почернела! На рисунке выше, она именно чёрная. Если останется серой, изменения не сохранятся, и на использование скрипта всё не перенастроится.
Скрипт всё равно формируется, но его концовка стала более удобной для нас.
vlog -sv -work UsbHead1 +incdir+C:/Work/UsbHead1/UsbHead1/synthesis/submodules {C:/Work/UsbHead1/UsbHead1/synthesis/submodules/UsbHead1_master_0_timing_adt.sv}
do "C:/Work/UsbHead1/SystemCPlay/myrun.do"
Наконец-то процесс сборки исходников для проекта полностью отдан нам на откуп! Замечательно! На тот момент я смог найти только документ SystemC Verification with ModelSim, написанный для Xilinx. Но ModelSim, он и в Африке ModelSim. Пользуясь примерами из этого документа и образцами DO-файла, созданного на прошлых опытах, я сделал следующий текст скрипта (не пугайтесь обилию ключей, ниже мы почти все выкинем, абсолютные пути тоже потом заменим на относительные, на этом этапе я просто всё дергал из примеров и автоматически сгенерённых образцов).
vlog -vlog01compat -work work +incdir+C:/Work/UsbHead1/SystemCPlay {C:/Work/UsbHead1/SystemCPlay/ulpi_wrapper.v}
vlib sc_work
sccom –g –I C:/intelFPGA_lite/17.1/quartus/cusp/systemc/include –work sc_work C:/Work/UsbHead1/SystemCPlay/ulpi_driver.cpp
Барабанная дробь… И ModelSim нам заявляет:
Если опустить все неприличные слова, то мне и сказать-то нечего… Но такой путь пройден! И где взять другую модель ULPI? Разумеется, я договорился с иностранными знакомыми, профессионально занимающимися серьёзными проектами для ПЛИС. Специально для меня они открыли на выходные удалённый доступ к машине с лицензионным ModelSim. Второй блин также оказался комом: 64-битная версия даже в лицензионном виде не работает с SystemC. Но в конце концов, мне удалось поиграть с 32-битной версией лицензионного ModelSim. Поэтому продолжаем рассказ…
Пара слов о документации
Итак. Теперь, когда я получил доступ к лицензионному ПО, самое время поговорить о том, где искать информацию и где черпать вдохновение. В сети сведения о языке достаточно обрывочны. Но в поставке системы, имеются следующие полезные каталоги:
C:modeltech_10.2cdocspdfdocs — документация, включая файлы в формате PDF. Мне понравились файлы modelsim_se_ref.pdf (ModelSim SE Command Reference Manual), modelsim_se_user.pdf (ModelSim SE User’s Manual) и modelsim_se_tut.pdf (ModelSim SE Tutorial). По самому языку там мало что есть, но по тому, как подключать файлы и как решать проблемы диалектов — вполне.
Дальше, полезный каталог C:modeltech_10.2cexamples. Там есть примеры готовых файлов *.do и готовых файлов cpp и h. Самый полезный для нас пример — C:modeltech_10.2cexamplessystemcvlog_sc. В нём показано, как обращаться из Verilog кода к коду на SystemC. Мы, в итоге, пойдём именно этим путём.
В каталоге C:modeltech_10.2cincludesystemc содержатся исходные коды библиотеки типов языка. Неплохой справочник. Как говорится, на безрыбье и рак рыба.
Из каталогов всё. Теперь название замечательной книги, из которой можно узнать многое как о языке, так и о методике программирования на нём. SystemC — From the Ground Up, Second Edition. Авторы David C. Black, Jack Donovan, Bill Bunton, Anna Keist.
Диалекты языка SystemC
Итак. Получив доступ к работающей системе, я радостный собрал проект, согласно ранее созданному скрипту. Он собрался без ошибок! Первая моделька с ГитХаба согласилась работать с нами! Желая прогнать эталонный тест, я добавил в проект файл ulpi_wrapper_tb.cpp из того же каталога и получил массу ошибок. Допустим, ошибку в строке:
m_vpi_handle = vpi_handle_by_name((const char*)name, NULL);
поправить сложно, но ещё можно. Но строка
// Update systemC TB
if(sc_pending_activity())
sc_start((int)(time_value-m_last_time),SC_NS);
навевала плохие мысли. Функция sc_pending_activity() в библиотеках отсутствует. Имеется функция sc_pending_activity_at_current_time(), но с нею я даже разбираться не стал. Вместо тысячи слов объяснения, приведу дамп:
И файлов с таким текстом (*.exe, *.dll и т. п.) нашлось 44 штуки.
Можно было попытаться всё переписать… Но надо ли оно? Напомню, я вообще-то начал всё это, так как хотел воспользоваться всем готовым. Разработать я всё и в бесплатной среде на чистом SystemVerilog могу, если уж тратить кучу времени… Я шёл сюда, чтобы время не потратить, а сэкономить! Но на самом деле… Главное — не забыть, что мы делаем. Мы хотим воспользоваться моделькой шины ULPI. Она собралась. Проблемы возникли при попытке собрать полную тестовую систему из примера… А зачем это? Ну не работает полная система, и ладно. Будем осваивать одну модельку, не глядя на работу системы, методом проб и ошибок.
Устраняем непонимание, основанное на диалектах
Итак. Мы будем делать смешанную систему. Модуль с моделью будет написан на языке SystemC, а тестовые воздействия ему и разрабатываемому модулю я буду подавать на языке Verilog. То есть, надо добиться появления модуля ulpi_driver в группе work.
Осматривая примеры файлов *.do из поставки ModelSim, я сильно упростил скрипт, и в итоге, сделал такой:
vlog +../../SystemCPlay {../../MyCores/ULPIhead.sv}
sccom -g ../../SystemCPlay/ulpi_driver.cpp
sccom -link
Ошибок нет, но и модуль в группе не появился. Осматривая файлы примера (напомню, лучший пример, реализующий именно такое смешивание языков есть в каталоге C:modeltech_10.2cexamplessystemcvlog_sc), я понял, что в конец файла ulpi_driver.cpp надо добавить строчку:
SC_MODULE_EXPORT(ulpi_driver);
Документация на ModelSim говорит, что это особенности диалекта. И вуаля! Вот он, наш модуль:
Правда, для него меню Create Wave (это меню мы рассматривали в прошлой статье) недоступно. И портов у него нет. Исторически я сначала разбирался с портами, но методически — я отложу рассказ про них на потом. Иначе придётся дважды править код. Чтобы этого не делать, сначала проведём небольшую подготовку.
Делаем тактовый генератор
Оказалось, что у модели есть пара отличий от настоящего ULPI. Первое отличие состоит в том, что тактовый сигнал 66 МГц должен вырабатывать чип. А что мы видим в модели?
sc_in clk_i;
Непорядок! Начинаем переделку! Все работы, если не указано иное, ведём в файле ulpi_driver.h.
Заменяем тип порта. Было:
sc_in clk_i;
стало (я ещё и имя порта поменял):
sc_inout clk;
Из книжки я узнал, что настоящий генератор вставляется путём добавления переменной:
sc_clock oscillator;
Параметры задаём в конструкторе. В итоге, конструктор приобретает вид:
//-------------------------------------------------------------
// Constructor
//-------------------------------------------------------------
SC_HAS_PROCESS(ulpi_driver);
ulpi_driver(sc_module_name name): sc_module(name),
m_tx_fifo(1024),
m_rx_fifo(1024),
oscillator ("clk66",sc_time(15,SC_NS))
{
Последняя строка как раз для этого. При желании, можно даже уже запустить моделирование, дважды щёлкнуть по модулю usb_driver, дальше — вытянуть clk66 на времянку и немного прогнать процесс моделирования. Мы уже видим, как работает генератор:
Не забудем поменять имя тактового сигнала и в месте, где стартует основной поток. Было:
SC_CTHREAD(drive, clk_i.pos());
Стало:
SC_CTHREAD(drive, clk.pos());
Внутренние связи заменены. А вот как красиво вывести сигнал наружу, я не нашёл. Возможно, мне просто не хватает квалификации. Но так или иначе, а все попытки вытянуть порт наружу, не увенчались успехом. Всегда что-то мешало. Я даже нашёл на одном форуме обсуждение, где автору нужно было сделать то же самое. Коллектив решил, что можно пробросить только на входные порты. Но нам же надо на выход! Поэтому делаем так.
Добавляем под конструктором функцию потока:
void clkThread(void)
{
while (true)
{
wait(oscillator.posedge_event());
clk.write (true);
wait(oscillator.negedge_event());
clk.write (false);
}
}
И добавляем ссылку на неё в конструктор класса:
SC_THREAD(clkThread);
Давайте я покажу текущий район конструктора, чтобы было целостное видение текущего результата:
SC_HAS_PROCESS(ulpi_driver);
ulpi_driver(sc_module_name name): sc_module(name),
m_tx_fifo(1024),
m_rx_fifo(1024),
oscillator ("clk66",sc_time(15,SC_NS))
{
SC_CTHREAD(drive,clk.pos());
SC_THREAD(clkThread);
m_reg[ULPI_REG_VIDL] = 0x24;
m_reg[ULPI_REG_VIDH] = 0x04;
m_reg[ULPI_REG_PIDL] = 0x04;
m_reg[ULPI_REG_PIDH] = 0x00;
m_reg[ULPI_REG_FUNC] = 0x41;
m_reg[ULPI_REG_OTG] = 0x06;
m_reg[ULPI_REG_SCRATCH] = 0x00;
}
void clkThread(void)
{
while (true)
{
wait(oscillator.posedge_event());
clk.write (true);
wait(oscillator.negedge_event());
clk.write (false);
}
}
Всё. Первая правка завершена.
Делаем двунаправленную шину данных
У ULPI двунаправленная шина данных. А в модели мы видим следующее её описание:
sc_out > ulpi_data_o;
sc_in > ulpi_data_i;
Непорядок! Сначала мы сделаем заготовку на базе выходной шины, а затем — переключим всё на неё. С чего начать? С того, что шина должна уметь переходить в третье состояние, а тип sc_uint<8> работает только с двоичными данными. Нам поможет тип sc_lv<8>. Поэтому меняем объявление шины на:
sc_inout > ulpi_data_o;
Теперь переходим в файл ulpi_driver.cpp и там ищем все обращения к шине ulpi_data_o. Интуитивно я понял, что поправить надо только одно место:
void ulpi_driver::drive_input(void)
{
// Turnaround
ulpi_dir_o.write(false);
ulpi_nxt_o.write(false);
ulpi_data_o.write(0x00);
wait(oscillator.posedge_event());
}
Меняем выделенную строку на
ulpi_data_o.write("ZZZZZZZZ");
Всё. Теперь можно вместо двух строк:
sc_inout > ulpi_data_o;
sc_in > ulpi_data_i;
написать одну:
sc_inout > ulpi_data;
и заменить все ссылки на старые переменные как в h-нике, так и в cpp-шнике на ссылки на переменную ulpi_data.
Добавляем псевдонимы для портов
Итак. После долгих поисков я пришёл к выводу (возможно, ошибочному), что в среде ModelSim просто взять и увидеть порты для отдельно лежащего модуля на SystemC средствами GUI, не судьба. Однако, если этот модуль вставить в тестовую систему, они появятся. Но пока рылся с теорией, нашёл, как красиво задать псевдонимы для имён портов. Итоговый конструктор класса стал выглядеть так:
SC_HAS_PROCESS(ulpi_driver);
ulpi_driver(sc_module_name name): sc_module(name),
m_tx_fifo(1024),
m_rx_fifo(1024),
oscillator ("clk66",sc_time(15,SC_NS)),
rst_i ("rst"),
ulpi_data ("data"),
ulpi_dir_o ("dir"),
ulpi_nxt_o ("nxt"),
ulpi_stp_i ("stp")
{
SC_CTHREAD(drive,clk.pos());
SC_THREAD(clkThread);
m_reg[ULPI_REG_VIDL] = 0x24;
m_reg[ULPI_REG_VIDH] = 0x04;
m_reg[ULPI_REG_PIDL] = 0x04;
m_reg[ULPI_REG_PIDH] = 0x00;
m_reg[ULPI_REG_FUNC] = 0x41;
m_reg[ULPI_REG_OTG] = 0x06;
m_reg[ULPI_REG_SCRATCH] = 0x00;
}
Делаем тестовую систему
Ну что же. Сделать всё на автомате, чтобы сразу два отлаживаемых модуля (голова анализатора и модель шины ULPI) сами запрыгнули в тестовый файл, у меня не получилось. Но сделаем хотя бы тест для головы, а потом добавим к нему ULPI. Пользуясь методикой из прошлой статьи, я сделал тестовую систему для файла ULPIhead.sv. Файл я назвал sim1.v и тут же переименовал его в sim1.sv.
После чего ручками добавил туда модуль ulpi_driver. Итоговый скрипт myrun.do выглядит так:
vlog +../../SystemCPlay {../../MyCores/ULPIhead.sv}
sccom -g ../../SystemCPlay/ulpi_driver.cpp
sccom -link
vlog +../../SystemCPlay {../../SystemCPlay/sim1.sv}
vsim -voptargs="+acc" sim1
Последняя строка вымученная. Без неё не было портов у Verilog кода. Изменяя параметры оптимизации, мы устраняем эту беду. Её я подсмотрел в том файле *.do, который был создан для моделирования нашей системы в самом начале, когда всё ещё делалось на автомате. Правда, там строка длиннющая. Я просто нашёл тот ключ, который решает проблему, и скопировал его. А так — не люблю длинных строк, всё лишнее я выкинул.
Теперь добавляем в тестовую систему блок ULPI и делаем тест-пустышку. Просто чтобы убедиться, что все тактовые сигналы тикают, а шины устанавливаются в нужные значения.
У меня получился такой тест.
`timescale 1ns / 1ns
module sim1 ;
reg ulpi_dir ;
wire source_valid ;
wire ulpi_stp ;
reg ulpi_clk ;
reg ulpi_nxt ;
reg reset_n ;
reg read ;
reg [31:0] writedata ;
wire ulpi_rst ;
reg clk ;
wire [7:0] source_data ;
reg write ;
wire [7:0] ulpi_data ;
reg source_ready ;
reg [1:0] address ;
wire [31:0] readdata ;
always
begin
clk = 1;
#5;
clk = 0;
#5;
end
ULPIhead DUT
(
.ulpi_dir (ulpi_dir ) ,
.source_valid (source_valid ) ,
.ulpi_stp (ulpi_stp ) ,
.ulpi_clk (ulpi_clk ) ,
.ulpi_nxt (ulpi_nxt ) ,
.reset_n (reset_n ) ,
.read (read ) ,
.writedata (writedata ) ,
.ulpi_rst (ulpi_rst ) ,
.clk (clk ) ,
.source_data (source_data ) ,
.write (write ) ,
.ulpi_data (ulpi_data ) ,
.source_ready (source_ready ) ,
.address (address ) ,
.readdata (readdata ) );
ulpi_driver ULPI
(
.clk (ulpi_clk),
.rst (ulpi_rst),
.data (ulpi_data),
.dir (ulpi_dir),
.nxt (ulpi_nxt),
.stp (ulpi_stp)
);
initial
begin
reset_n = 1'b0;
source_ready = 1;
writedata = 0;
address = 0;
read = 0;
write = 0;
#20
reset_n = 1'b1;
end
endmodule
Заключение
Худо-бедно, но мы освоили моделирование на языке SystemC с использованием системы ModelSim. Правда, оказалось, что для этого необходимо иметь доступ к лицензионной 32-битной версии. Свободная версия и лицензионная 64-битная версия такой возможности не дают. Как я понял, совершенно бесплатно всё можно сделать в системе Icarus Verilog, но как именно этого достичь, не разобрался. Мне оказалось проще получить доступ к требуемому ModelSim. В следующей статье мы воспользуемся полученными знаниями, чтобы провести моделирование нашей головы.
В ходе работ были произведены достаточно сложные доработки моделей. Получившиеся файлы можно скачать тут.