Проверка синтезируемости красивых возможностей SystemVerilog на практике

    В силу проектной обстановки нашей команде пришлось изучить возможности языка SystemVerilog, после чего нет-нет, а возникают жаркие споры о том, какая его часть синтезируема, а какая — нет. Чтобы положить конец домыслам, я провёл небольшую проверку на практике. Во время разработки тестового проекта ряд вопросов удалось снять копаясь в литературе, но всплыл один интересный момент, явного описания которого не нашлось. Чтобы исправить положение, я решил его задокументировать.

    Итак. Имеем проект, максимально напичканный всяческими 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 (я специально скачивал самую свежую версию, так как язык молодой, и в старых версиях Квартуса всё может быть не так радужно). К сожалению, язык обладает некоторыми неудобствами, из-за которых программирование исключительно в объектно-ориентированном мире невозможно. Но это — особенности языка. Они решаются созданием обычных сущностей, которые добавляют нагромождения в текст, но никак не влияют на сложность результирующего кода, так как являются всего лишь псевдонимами сущностей объектных.

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

    Подробнее
    Реклама

    Комментарии 10

      0
      Интересная статья. А моделирование в Active — HDL вы не проводили?
        +1
        Я моделирую в ModelSim Altera, так как она совершенно бесплатная. Ну, или в ISIM, если работа идёт с Xilinx (хоть они и не рассматривались в данной статье). ActiveHDL — на заре тысячелетия изучал, но «париться» с ломанными версиями при наличии совершенно бесплатных и встроенных в систему разработки — а смысл?

        Но в рамках рассмотренной проблематики — проверять надо, как оно синтезируется, а Gate Level моделирование — замутное. Проще было осциллографом убедиться. Тем более, что хотелось именно реальные результаты увидеть.
          0
          Ну да, действительно. У ActiveHDL есть студенческая версия, которой я и пользуюсь, так как студент, то есть, к сожалению, с практическими задачами я еще не сталкивался. Для таких новичков как я, мне кажется, что было бы полезно в решении вашей задачи увидеть самое обычное временное моделирование)
            0
            Кажется, я понял, где мы друг друга не понимаем. Давным-давно я прослушал курсы по программированию ПЛИС на VHDL, где нас как раз на ActiveHDL обучали. Изучили мы язык, набрались опыта, наделали лабораторных работ… И вот добыл я свою первую ПЛИС (тогда их было добыть достаточно сложно, из Швеции привёз), начинаю делать под неё первую прошивку… И на меня сыплется гора ошибок. Гружу в систему моделирования — всё прекрасно работает.

            Оказалось, что нас забыли предупредить, что есть язык, а есть — его синтезируемое подмножество. И только жалкая часть языковых конструкций будет синтезирована. Да и там — тоже свои заморочки (например, управлять сигналом можно только из одного процесса). Пришлось потратить месяц, чтобы изменить стиль разработки.

            Большинство обзорных статей про SystemVerilog едины по стилю. Сначала идёт рассказ о новых вещах, как они прекрасны. Затем — комментарий: «А это вообще синтезируемо?». И дальше — обычно разговор сходит на общие темы. Вот я и решил выбрать красивых вещей и проверить:

            1) Насколько они синтезируемы
            2) Насколько оптимален результат синтеза конкретно массивов с индексацией «на лету»

            Результаты — в статье. И именно поэтому моделирование самого языка для данной статьи бесполезно. Оно сработает, куда ж оно денется? Моделировать пришлось бы на уровне вентилей после упаковки в ПЛИС. Осциллограмма — это практически то же самое. Но глядя на результаты моделирования, скептики у нас сказали бы, что этому верить можно, но с натягом, а вот то, что видно на осциллографе — оно видно на осциллографе. Это уже физическая вещь.

            Посему в данной конкретной статье осциллограммы — более приемлемы.
              0
              Беру свои слова обратно. Не отмоделируется… Чтобы отмоделировалось (я играл с последним примером) надо добавить начальную инициализацию переменной iter. В ПЛИС же она чему-то, да равна, а длина перебора — 4 такта. В модели же к неизвестно чему прибавляем единицу — будет опять неизвестно что. Так что
              logic	[$clog2(cnt)-1:0] iter = 0;

              Тогда вот такая моделька
              `timescale 1 ps / 1 ps
              module checkObj2();
              logic			clk50;
              logic	[1:0] button;
              logic [1:0] group [4];
              
              ObjTest2 dut(
              .clk50,
              .button,
              .group
              );
              always 
              begin
              	clk50 = 0;
              	#10000;
              	clk50 = 1;
              	#10000;
              end
              initial
              begin
              		button = 2'b00;
              		#5500000;
              		button = 1'b01;
              		#5000000;
              		button = 2'b11;
              		#5000000;
              		button = 2'b00;
              end
              endmodule
              

              даст такую картинку

              image
              Ну, а там видно, что изменение состояния кнопки (я выделил их жёлтым) даст «разбегающееся» изменение состояния ножек. С какой ножки начнётся разбег — зависит от того, в какой момент кнопку нажали (на то он и Round Robin)
            0
            Я пару лет назад пробовал. Active-HDL сам по себе понравился, он жутко удобен именно как среда, в которой всё есть — полное УДОБНОЕ IDE. Mоdelsim — по сути только симулятор, тот-же редактор нужно отдельно прикручивать. Но Active-HDL отставал от Mоdelsim по числу поддерживаемых фич. языка. Мне это было критично, поэтому от него отказался.
            За это время думаю много чего нагнали, но и Mоdelsim думаю не стоял на месте. Вопрос будет в том, насколько Вам будет критичны те фичи, что могут быть ещё не реализованы в Active-HDL.
            0
            Идея упакованных/неупакованных объектов — это большой шаг по сравнению с классическим верилогом, и она красива, в некотором смысле, но только пока мы находимся в рамках моделирования на последовательных машинах. Но если переходить к синтезу в аппаратуре, то она начинает существенно мешать, т.к. никаких упакованных/неупакованных объектов нет, есть лишь конкретная реализация, в которой можно делать что угодно (насколько хорошо оно ляжет на ту или иную технологическую базу — вопрос отдельный).
              0
              Ой. А в quartus завезли modport?! Урра, это ж приятно! А он чекает направления?
                0
                Чуть переделал наброски неопубликованного примера. Получилось примерно так:
                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»

                Вывод: Чекает! Ну, и второй вывод — не зря я всё-таки вёл осмотр возможностей на самой свежей версии.
                +1
                Одно замечание. Тулы синтеза умеют только импортировать SV; на выходе у них в 100% случаев получается обычный верилог-нетлист. Соответственно, это означает флатованные до 1-го разряда интерфейсы и дикие, трехэтажные названия портов и сигналов, полученные при конверсии имен. С такими названиями очень трудно писать констрейнты и дебажить нетлист, ведь постоянно придется сопоставлять, какой цепи и регистру в RTL соответствует цепь и регистр из нетлиста. Поэтому SV (и конструкции generate в обычном верилоге) не рекомендуют использовать в сложных проектах, а особенно — на верхнем уровне. SV — язык красивый и удобный, но только до этапа синтеза.

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

                Самое читаемое