Итак. Имеем проект, максимально напичканный всяческими SytemVerilog-овскими штучками. Даже если кажется, что применение той или иной вещи не даёт особого выигрыша — это ошибочное впечатление, ведь главная задача «проекта» — именно изучить возможности SystemVerilog. И вот, у нас есть набор из нескольких модулей (конкретно у меня — это UART-приёмники), данные из которых следует «сливать» в единую шину, перебирая их по алгоритму RoundRobin (конкретно в случае с UART — сливаем накопленные данные в единую очередь, которая с другой стороны будет уходить в шину USB).
Вот так выглядит объявление модуля UART:
module UARTreceiver(
RxBus.Slave Bus,
input logic [15:0] divider,
input logic RxD
);
Вот так выглядит его интерфейс, с которым я планирую работать в по алгоритму RoundRobin:
// Интерфейс порта FIFO для связи с группой приёмников
interface RxFifoBus #(parameter width=8)(input clk);
logic [width-1:0] data;
logic rdReq;
logic empty;
modport slave (input clk, rdReq, output data,empty);
modport master (input clk, data, empty, output rdReq);
endinterface
В объединяющем модуле интерфейсы описаны в виде массива, чтобы их можно было бы индексировать:
RxBus rcvBuses [0:UARTS](.clk (Bus.clk),.reset_n);
С самими модулями они связаны через Generate:
genvar i;
generate
for (i=0;i<UARTS;i++)
begin : RxGen
UARTreceiver rec (
.Bus(rcvBuses[i]),
.divider (16'd3125),
.RxD (RxDs[i])
);
end
endgenerate
Казалось бы — красота! Знай себе занимайся коммутацией, вроде этой (я приведу пример только для линии data):
,
logic [$clog2(UARTS)-1:0] cnt;
always @ (posedge Bus.clk, negedge reset_n)
begin
if (!reset_n) begin
cnt <= 0;
end else begin
cnt <= cnt + 4'h1;
dataToFifo [7:0] <= rcvBuses[cnt].data;
dataToFifo [11:8] <= cnt;
end
end
Но то — ожидание. А в реальности — получаем ошибку о невозможности доступа к объекту rcvBuses. Если индексом служит константа или genvar-переменная (собственно, тоже эквивалент константы) — без проблем, индексируйся сколько хочешь. Например, никто не запрещает сделать «в лоб»:
always_comb begin
case (cnt)
4'h0: dataToFifo [7:0] = rcvBuses[0].data;
4'h1: dataToFifo [7:0] = rcvBuses[1].data;
4'h2: dataToFifo [7:0] = rcvBuses[2].data;
4'h3: dataToFifo [7:0] = rcvBuses[3].data;
4'h4: dataToFifo [7:0] = rcvBuses[4].data;
4'h5: dataToFifo [7:0] = rcvBuses[5].data;
4'h6: dataToFifo [7:0] = rcvBuses[6].data;
4'h7: dataToFifo [7:0] = rcvBuses[7].data;
4'h8: dataToFifo [7:0] = rcvBuses[8].data;
4'h9: dataToFifo [7:0] = rcvBuses[9].data;
4'ha: dataToFifo [7:0] = rcvBuses[10].data;
4'hb: dataToFifo [7:0] = rcvBuses[11].data;
4'hc: dataToFifo [7:0] = rcvBuses[12].data;
4'hd: dataToFifo [7:0] = rcvBuses[13].data;
4'he: dataToFifo [7:0] = rcvBuses[14].data;
4'hf: dataToFifo [7:0] = rcvBuses[15].data;
default:dataToFifo [7:0] = rcvBuses[0].data;
endcase
end
Так работать будет, но теряется возможность изменять количество обрабатываемых приёмников через параметризацию модуля. А сейчас мы именно проверяем, насколько красивыми могут быть решения на исследуемом языке.
Зарывшись в литературу, я прояснил для себя, что интерфейс — вещь неупакованная. И, в отличие от структуры, он не может быть объявлен, как упакованная сущность. В знаменитой книге SystemVerilog for Design 2nd Edition в одном из примеров вскользь упомянуто (но не описано детально) решение. Необходимо выйти из красивого объектно-ориентированного мира в жестокий обычный мир, для чего добавить массив цепей:
logic [7:0] dataBuses [0:UARTS-1];
Для связи двух миров (объектного и старого) добавим такую строчку:
genvar i;
generate
for (i=0;i<UARTS;i++)
begin : RxGen
assign dataBuses [i] = rcvBuses[i].data;
UARTreceiver rec (
.Bus(rcvBuses[i]),
.divider (16'd3125),
.RxD (RxDs[i])
);
end
endgenerate
И в цикле делаем так:
logic [$clog2(UARTS)-1:0] cnt;
always @ (posedge Bus.clk, negedge reset_n)
begin
if (!reset_n) begin
cnt <= 0;
end else begin
cnt <= cnt + 4'h1;
dataToFifo [7:0] <= dataBuses[cnt];
dataToFifo [11:8] <= cnt;
end
end
Новый массив — упакованный, поэтому система перестаёт ругаться на нас, хотя, на самом деле, после оптимизации это будут всего лишь два псевдонима одной и той же сущности.
Хорошо. Чего можно, а чего нельзя — выяснили. Теперь было бы хорошо на простых примерах убедиться, что всё это безобразие будет синтезировано верно. Так получилось, что у меня под рукой сейчас есть только макетная плата с парой кнопок и двухканальный осциллограф. Не густо, но что есть. Попробуем придумать задачу, которая красиво докажет работоспособность (или неработоспособность) описанной выше индексации в таких спартанских условиях.
Кнопок всего две. То есть, много источников не сымитировать. Но никто же не мешает проверять всё на обратной системе. Не много шин в одну, а одну во много! Две кнопки — двухбитная шина. Будем раздавать её на ножки ПЛИС:
Одна кнопка будет воздействовать на одну группу ножек и не воздействовать на другую. При изменении состояния кнопки, сигнал будет распространяться по ножкам с задержкой. Задержка составит один такт между каждой парой. Таким образом, можно будет проконтролировать все интересующие вещи — как индексацию элементов массива, так и тот факт, что шины коммутируются верно.
Делаем такой проект:
module ObjTest1 #(parameter cnt=4)
(
input logic clk50,
input logic [1:0] button,
output logic [cnt-1:0] group1,
output logic [cnt-1:0] group2
);
// Это чтобы осциллограф не насиловать,
// я там частоту до 1 МГц понижаю.
logic clk;
MainPll pll (
.inclk0 (clk50),
.c0 (clk)
);
// Массив двухбитных шин, которые мы будем поочерёдно
// подключать к выходной шине (с защёлкиванием)
logic [1:0] wires [0:cnt-1];
// Связываем массив шин с обычными выходами микросхемы
// В реальной жизни, здесь мы свяжем интерфейсы блоков с
// массивами
genvar i;
generate
for (i=0;i<cnt;i++)
begin : generilka
assign group1 [i] = wires [i][0];
assign group2 [i] = wires [i][1];
end
endgenerate
// Тут мы будем перебирать элементы
logic [$clog2(cnt)-1:0] iter;
// Имитация подключения блока к шине
// Здесь мы просто подключаем кнопку
always_ff @(posedge clk)
begin
iter <= iter + 1'b1;
wires [iter][0] <= button[0];
wires [iter][1] <= button[1];
end
endmodule
Из того, что я пока не описывал — PLL. В одной из прошлых статей я пришёл к ошибочным выводам, работая на высоких пределах осциллографа. Чтобы исключить подобное, PLL снижает частоту до одного мегагерца. Остальное — уже описывалось. Поэтому пробежимся по самым вершкам:
Фактические ножки микросхемы описываются в виде двух векторов. Не очень красиво, но потом украсим:
output logic [cnt-1:0] group1,
output logic [cnt-1:0] group2
А пока — связываем их с исследуемым массивом:
// Массив двухбитных шин, которые мы будем поочерёдно
// подключать к выходной шине (с защёлкиванием)
logic [1:0] wires [0:cnt-1];
вот таким образом:
// Связываем массив шин с обычными выходами микросхемы
// В реальной жизни, здесь мы свяжем интерфейсы блоков с
// массивами
genvar i;
generate
for (i=0;i<cnt;i++)
begin : generilka
assign group1 [i] = wires [i][0];
assign group2 [i] = wires [i][1];
end
endgenerate
Кнопки — описываются в виде шины:
input logic [1:0] button,
И алгоритм Round Robin реализуем следующим образом:
// Тут мы будем перебирать элементы
logic [$clog2(cnt)-1:0] iter;
// Имитация подключения блока к шине
// Здесь мы просто подключаем кнопку
always_ff @(posedge clk)
begin
iter <= iter + 1'b1;
wires [iter] <= button;
end
Компилируем, наслаждаемся тем, сколько ресурсов всё это заняло (у нас защёлкивается 8 ножек, плюс 2 бита на счётчик — итого меньше десяти триггеров получиться физически не могло)
RTL Viewer также не показывает ничего лишнего. Есть PLL, есть счётчик, есть дешифратор, есть триггеры, объединённые в двухбитные шины. Всё, как мы просили:
Заливаем в кристалл, подключаемся к двум соседним ножкам, начинаем играть кнопкой. Получаем задержку на 1 микросекунду, что соответствует частоте 1 МГц.
Переносим второй щуп на следующую ножку:
И на следующую:
Всё соответствует теории. На другую кнопку эта половина не реагирует.
Ну и, наконец, проверим, что нам скажет среда разработки, если мы опишем ножки не как две группы контактов, а как единый массив, что позволит избежать занудного блока generate, связывающего массив с группами. Такой код не содержит совсем ничего лишнего, только суть исследования (ну, и PLL, переносящий результаты в хорошо различимую на осциллографе область):
module ObjTest2 #(parameter cnt=4)
(
input logic clk50,
input logic [1:0] button,
output logic [1:0] group [0:cnt-1]
);
// Это чтобы осциллограф не насиловать,
// я там частоту до 1 МГц понижаю.
logic clk;
MainPll pll (
.inclk0 (clk50),
.c0 (clk)
);
// Тут мы будем перебирать элементы
logic [$clog2(cnt)-1:0] iter;
// Имитация подключения блока к шине
// Здесь мы просто подключаем кнопку
always_ff @(posedge clk)
begin
iter <= iter + 1'b1;
group [iter] <= button;
end
endmodule
Идём в Pin Planner и вспоминаем анекдот про смешанное чувство:
С одной стороны, есть какая-то странная группа (я обвёл её красным), которая ни к селу, ни к городу. Но с другой стороны — на неё ничего не назначено. А наша многомерная группа — тоже имеется. И на неё можно назначать ножки. И осциллограф показывает, что всё работает верно.
Кстати, картинка RTL View стала просто прекрасной! Хоть в учебник по схемотехнике вставляй!
Заключение
Замечательные возможности, предоставляемые языком SystemVerilog, прекрасно синтезируются в среде разработки Quartus II (я специально скачивал самую свежую версию, так как язык молодой, и в старых версиях Квартуса всё может быть не так радужно). К сожалению, язык обладает некоторыми неудобствами, из-за которых программирование исключительно в объектно-ориентированном мире невозможно. Но это — особенности языка. Они решаются созданием обычных сущностей, которые добавляют нагромождения в текст, но никак не влияют на сложность результирующего кода, так как являются всего лишь псевдонимами сущностей объектных.
Вопросы, о которых спорили по данной тематике у нас в компании — закрыты. Возможно, что-то из сказанного будет интересно и остальным.
Комментарии (10)
nerudo
27.12.2017 13:06Идея упакованных/неупакованных объектов — это большой шаг по сравнению с классическим верилогом, и она красива, в некотором смысле, но только пока мы находимся в рамках моделирования на последовательных машинах. Но если переходить к синтезу в аппаратуре, то она начинает существенно мешать, т.к. никаких упакованных/неупакованных объектов нет, есть лишь конкретная реализация, в которой можно делать что угодно (насколько хорошо оно ляжет на ту или иную технологическую базу — вопрос отдельный).
ilynxy
27.12.2017 22:42Ой. А в quartus завезли modport?! Урра, это ж приятно! А он чекает направления?
EasyLy Автор
28.12.2017 05:31Чуть переделал наброски неопубликованного примера. Получилось примерно так:
interface demoInterface (); logic in; logic out; modport InDevice (input in,output out); endinterface module ObjTest3 #(parameter cnt=4) ( input clk, input button ); demoInterface interfaces [cnt](); fakedevice f (interfaces[0]); endmodule module fakedevice (demoInterface.InDevice Bus); assign Bus.in = Bus.out; endmodule
На что мне было сказано:
Error (10231): Verilog HDL error at ObjTest3.sv(28): value cannot be assigned to input «in»
Вывод: Чекает! Ну, и второй вывод — не зря я всё-таки вёл осмотр возможностей на самой свежей версии.
Khort
29.12.2017 15:07Одно замечание. Тулы синтеза умеют только импортировать SV; на выходе у них в 100% случаев получается обычный верилог-нетлист. Соответственно, это означает флатованные до 1-го разряда интерфейсы и дикие, трехэтажные названия портов и сигналов, полученные при конверсии имен. С такими названиями очень трудно писать констрейнты и дебажить нетлист, ведь постоянно придется сопоставлять, какой цепи и регистру в RTL соответствует цепь и регистр из нетлиста. Поэтому SV (и конструкции generate в обычном верилоге) не рекомендуют использовать в сложных проектах, а особенно — на верхнем уровне. SV — язык красивый и удобный, но только до этапа синтеза.
zillant
Интересная статья. А моделирование в Active — HDL вы не проводили?
EasyLy Автор
Я моделирую в ModelSim Altera, так как она совершенно бесплатная. Ну, или в ISIM, если работа идёт с Xilinx (хоть они и не рассматривались в данной статье). ActiveHDL — на заре тысячелетия изучал, но «париться» с ломанными версиями при наличии совершенно бесплатных и встроенных в систему разработки — а смысл?
Но в рамках рассмотренной проблематики — проверять надо, как оно синтезируется, а Gate Level моделирование — замутное. Проще было осциллографом убедиться. Тем более, что хотелось именно реальные результаты увидеть.
zillant
Ну да, действительно. У ActiveHDL есть студенческая версия, которой я и пользуюсь, так как студент, то есть, к сожалению, с практическими задачами я еще не сталкивался. Для таких новичков как я, мне кажется, что было бы полезно в решении вашей задачи увидеть самое обычное временное моделирование)
EasyLy Автор
Кажется, я понял, где мы друг друга не понимаем. Давным-давно я прослушал курсы по программированию ПЛИС на VHDL, где нас как раз на ActiveHDL обучали. Изучили мы язык, набрались опыта, наделали лабораторных работ… И вот добыл я свою первую ПЛИС (тогда их было добыть достаточно сложно, из Швеции привёз), начинаю делать под неё первую прошивку… И на меня сыплется гора ошибок. Гружу в систему моделирования — всё прекрасно работает.
Оказалось, что нас забыли предупредить, что есть язык, а есть — его синтезируемое подмножество. И только жалкая часть языковых конструкций будет синтезирована. Да и там — тоже свои заморочки (например, управлять сигналом можно только из одного процесса). Пришлось потратить месяц, чтобы изменить стиль разработки.
Большинство обзорных статей про SystemVerilog едины по стилю. Сначала идёт рассказ о новых вещах, как они прекрасны. Затем — комментарий: «А это вообще синтезируемо?». И дальше — обычно разговор сходит на общие темы. Вот я и решил выбрать красивых вещей и проверить:
1) Насколько они синтезируемы
2) Насколько оптимален результат синтеза конкретно массивов с индексацией «на лету»
Результаты — в статье. И именно поэтому моделирование самого языка для данной статьи бесполезно. Оно сработает, куда ж оно денется? Моделировать пришлось бы на уровне вентилей после упаковки в ПЛИС. Осциллограмма — это практически то же самое. Но глядя на результаты моделирования, скептики у нас сказали бы, что этому верить можно, но с натягом, а вот то, что видно на осциллографе — оно видно на осциллографе. Это уже физическая вещь.
Посему в данной конкретной статье осциллограммы — более приемлемы.
EasyLy Автор
Беру свои слова обратно. Не отмоделируется… Чтобы отмоделировалось (я играл с последним примером) надо добавить начальную инициализацию переменной iter. В ПЛИС же она чему-то, да равна, а длина перебора — 4 такта. В модели же к неизвестно чему прибавляем единицу — будет опять неизвестно что. Так что
Тогда вот такая моделька
даст такую картинку
Ну, а там видно, что изменение состояния кнопки (я выделил их жёлтым) даст «разбегающееся» изменение состояния ножек. С какой ножки начнётся разбег — зависит от того, в какой момент кнопку нажали (на то он и Round Robin)
iBuilder
Я пару лет назад пробовал. Active-HDL сам по себе понравился, он жутко удобен именно как среда, в которой всё есть — полное УДОБНОЕ IDE. Mоdelsim — по сути только симулятор, тот-же редактор нужно отдельно прикручивать. Но Active-HDL отставал от Mоdelsim по числу поддерживаемых фич. языка. Мне это было критично, поэтому от него отказался.
За это время думаю много чего нагнали, но и Mоdelsim думаю не стоял на месте. Вопрос будет в том, насколько Вам будет критичны те фичи, что могут быть ещё не реализованы в Active-HDL.