Создание простой игры на базе FPGA

    1

    Привет Хабр. Изучение FPGA я начал совсем недавно. Одним из моих проектов, который был направлен на изучения интерфейсов PS/2 и VGA, была игра в Пин-Понг на одного человека. Одна из реализаций которой работает на плате DE0-CV, которую мне любезно предоставил замечательный проект Silicon Russia в рамках конкурса (http://www.silicon-russia.com/2015/12/11/board-giveaway-for-mipsfpga/).

    Суть игры: есть ползунок управляемый с клавиатуры, который должен отбивать мячик, еремещающийся по экрану. В качестве средства отображения был выбран VGA дисплей, а клавиатура была выбрана с простым интерфейсом PS/2. Счет самой игры отображается на семисегментном индикаторе.

    Отладочная плата


    2

    DE0-CV — это официальная отладочная плата, распространяемая Alter’ой, ее цена составляет 150$, а по академической — 99$. На самой плате имеем:

    — шесть семисегментных индикаторов, 10 светодиодов, 10 переключателей, 4 кнопки;
    — VGA разъем, PS/2 разъем, слот под micro SD карту;

    — SDRAM память объемом 64Мбайта;

    — два GPIO разъема на 35 выводов каждый.

    Логика работы


    3

    В программе можно выделить 4 основных блока. Каждый из которых выполняет определённую функцию.
    • PLL — готовый ip блок для получения синхронизирующих импульсов обходимых для тактирования системы.
    • PS/2 – блок, на вход которого приходят сигналы с PS/2 порта и переводятся в коды нажатых клавиш.
    • vga – блок — драйвер для работы с VGA монитором
    • game – непосредственно реализация самой логики игры. На входы приходят сигналы с vga, ps2 и pll блоков .

    Сердцем всей программы является PLL. Именно благодаря его правильной настройке можно работать с VGA и тактировать другие блоки.

    4

    Контроллер PS/2 клавиатуры


    Для управления ракеткой в игре мы используем клавиатуру с PS/2 интерфейсом. Перед тем как перейти к рассмотрению реализации блока, давайте немного пробежимся по протоколу PS/2.

    5


    Выводами, служащими для обмена данными в протоколе PS/2, являются вывод Data и Clock. Посылка битов состоит из: одного стартового бита, 8 бит данных, бита четности и стоп бита. Вывод Clock служит, как можно догадаться, тактирующими.

    Установка битов со стороны устройства происходит по переднему фронту, восходящему фронту Clock, а считывание — со стороны устройства по нисходящему фронту сигнала. Когда устройство ничего не передает, Clock и Data подтянуты к питанию. Затем шина Data и Clock переходит в ноль, что является признаком того, что начата отправка сообщения. После чтения 8 бит, идет бит четности и стоп бит, который всегда равен единице.

    В первом обработчике мы считаем такты для того, что бы понять нажата кнопка или нет. Если PS2_CLK_in выставлена в течении 52500000 тактов — кнопка не нажата. Так же тут мы проверяем коды нажатых клавиш:
    — в случае если код нажатой клавиши совпадает с кодом клавиши «стрелки вверх» выход up переходит в 1;
    — если нажата клавиша «стрелка вниз»-выход down переходит в 1.
    always @(negedge clock) 
    begin
    	if(PS2_CLK_in == 1)
    		count_clk <= count_clk + 1;
    	else 
    		count_clk <= 0;
    	if(count_clk>=52500000)
    	begin
    		led_out <= 0;
    	end
    	else
    		led_out <= bit;
    		if(led_out == 8'b01110010)
    		begin
    			down <= 1;
    			up <= 0;
    		end
    		else 
    			if(led_out == 8'b01110101)
    			begin
    				up <= 1;
    				down <= 0;
    			end
    			else
    			begin
    				down <= 0;
    				up <= 0;
    			end		
    end
    

    В случае если на входе PS2_CLK_in фиксируется переход от высокого уровня к низкому, то происходит считывания состояния с входа PS2_DAT_in.
    always @(negedge PS2_CLK_in)
    begin
      	if(s == 0) begin
    		if(count<=7)
    		begin
    			bit <= bit|(PS2_DAT_in<<count);
    		end
    		if(count == 9)
    		begin
    				s <= 1;
    		end
    		else 
    		begin
    			count <= count + 1;
    		end		
    	end
    		if(s == 1)
    			if(PS2_DAT_in == 0)
    			begin
    				s <= 0;
    				count <= 0;
    				bit <= 0;
    			end
    end
    endmodule
    

    Код для тестирования в среде ModelSim приведен ниже:
    initial
    begin
      #0 clock_r=1;
      #275 clock_r = 1; //s
      repeat( 22 ) 
      begin
        #25 clock_r=~clock_r;
      end
      #100 clock_r = 1;  
      repeat( 22 ) 
      begin
        #25 clock_r=~clock_r;
      end
      #300 clock_r = 1; 
      repeat( 22 ) 
      begin
        #25 clock_r=~clock_r;
      end
      #50 clock_r = 1; 
      repeat( 22 ) 
      begin
        #25 clock_r=~clock_r;
      end
    end
    
    initial
    begin
        #250 PS2_CLK_r = 1; //s
        #50 PS2_CLK_r = 0; //start
        #50 PS2_CLK_r = 0; //0
        #50 PS2_CLK_r = 1; //1
        #50 PS2_CLK_r = 1; //2
        #50 PS2_CLK_r = 0; //3
        #50 PS2_CLK_r = 1; //4
        #50 PS2_CLK_r = 0; //5
        #50 PS2_CLK_r = 1; //6
        #50 PS2_CLK_r = 1; //7
        #50 PS2_CLK_r = 1; //parity bit
        #50 PS2_CLK_r = 0; //stop
        #50 PS2_CLK_r = 1; //s
        #50 PS2_CLK_r = 1; //s
        
        #50 PS2_CLK_r = 0; //start
        #50 PS2_CLK_r = 1; //0
        #50 PS2_CLK_r = 1; //1
        #50 PS2_CLK_r = 0; //2
        #50 PS2_CLK_r = 0; //3
        #50 PS2_CLK_r = 1; //4
        #50 PS2_CLK_r = 0; //5
        #50 PS2_CLK_r = 1; //6
        #50 PS2_CLK_r = 1; //7
        #50 PS2_CLK_r = 1; //parity bit
        #50 PS2_CLK_r = 0; //stop
        #50 PS2_CLK_r = 1; //s    
        #250 PS2_CLK_r = 1; //s
    
        #50 PS2_CLK_r = 0; //start
        #50 PS2_CLK_r = 0; //0
        #50 PS2_CLK_r = 1; //1
        #50 PS2_CLK_r = 1; //2
        #50 PS2_CLK_r = 1; //3
        #50 PS2_CLK_r = 1; //4
        #50 PS2_CLK_r = 1; //5
        #50 PS2_CLK_r = 1; //6
        #50 PS2_CLK_r = 1; //7
        #50 PS2_CLK_r = 1; //parity bit
        #50 PS2_CLK_r = 0; //stop
        #50 PS2_CLK_r = 1; //s
    
        #50 PS2_CLK_r = 0; //start
        #50 PS2_CLK_r = 0; //0
        #50 PS2_CLK_r = 1; //1
        #50 PS2_CLK_r = 1; //2
        #50 PS2_CLK_r = 0; //3
        #50 PS2_CLK_r = 1; //4
        #50 PS2_CLK_r = 0; //5
        #50 PS2_CLK_r = 1; //6
        #50 PS2_CLK_r = 1; //7
        #50 PS2_CLK_r = 1; //parity bit
        #50 PS2_CLK_r = 0; //stop
        #50 PS2_CLK_r = 1; //s
        #50 PS2_CLK_r = 1; //s
        #50 PS2_CLK_r = 1; //s
    end
    
    assign clock = clock_r;
    assign PS2_DAT_in = PS2_CLK_r;
    

    Диаграммы поведения блока:

    6

    Работа VGA-блока.


    Плата DE0 снабжена VGA выходом, в качестве ЦАП, для выходов RGB, используется простая схема на резисторах.

    Для начала работы с VGA нам нужно заглянуть в спецификацию VESA(http://tinyvga.com/vga-timing) и выбрать нужный режим работы. Посмотреть необходимую частоту и тайминги. Выберем видорежим 1440x900 60Hz. Необходимая тактовая частота — 106,5Мгц.

    На плате установлен кварц на 50МГц. С помощью специального блока PLL мы можем производить преобразование 50МГц в нужные нам 106,5. Для этого нам необходимо вытащить нужный блок на рабочую область и произвести его настройку

    7

    Из документации берем необходимые значения таймингов:

    8

    
    	parameter h_front_porch = 80;
    	parameter h_sync = 152;
    	parameter h_back_porch = 232;
    	parameter h_active_pixels = 1440;
    	
    	parameter v_front_porch = 3;
    	parameter v_sync = 6;
    	parameter v_back_porch = 25;
    	parameter v_active_scanilines = 900;
    
    

    При каждом положительном фронте поступившем на вход pixel_clock, увеличиваем на единицу счетчик pixel_count и в зависимости от его значения выставляется нужный логический уровень на выход горизонтальной синхронизации hsync.
    wire w_hsync = (pixel_count < h_sync);
    always @(posedge pixel_clock)
    begin
    	hsync <= (pixel_count < h_sync);
    	hvisible <= (pixel_count >= (h_sync+h_back_porch)) && (pixel_count < (h_sync+h_back_porch+h_active_pixels));
    	
    	if(pixel_count < (h_sync+h_back_porch+h_active_pixels+h_front_porch) ) begin
    		pixel_count <= pixel_count + 1'b1;
    		char_count <= pixel_count;
    	end	
    	else
    	begin
    		pixel_count <= 0;
    	end
    end
    

    Когда счетчик pixel_count доходит до конца строки, происходит увеличение счетчика строк line_count и, в зависимости от заданных ранее параметров, выставляются нужные значения на выход вертикальной синхронизации vsync.
    wire w_hsync_buf = w_hsync&~hsync;
    
    always @(posedge pixel_clock)
    begin
    	if(w_hsync_buf)begin
    		vsync <= (line_count < v_sync);
    		vvisible <= (line_count >= (v_sync+v_back_porch)) && (line_count < (v_sync+v_back_porch+v_active_scanilines));
    		
    		if(line_count < (v_sync+v_back_porch+v_active_scanilines+v_front_porch) )begin
    			line_count <= line_count + 1'b1;
    			line_count_out <= line_count;
    		end
    		else
    		begin
    			line_state <= 0;
    			line_count <= 0;
    		end
    	end
    end
    
    

    Когда pixel_count и line_count попадают в диапазон принадлежащий видимой части экрана то visible выставляется в высокий уровень, тем самым разрешая блоку game начинать отрисовку игрового поля:
    always @*
    begin
    	visible <= hvisible & vvisible;
    end
    

    Работа game блока.


    Переход сигнала pixel_state в логическую единицу означает лучение разрешения на отрисовку игрового поля от vga-блока. Входные сигналы char_count и line_count информируют нас о координатах точки, которая отрисовывается на экране в настоящий момент. Исходя из координат мячика и ракетки, закрашиваем нужными цветами зоны, которые соответствуют им.
    always @(pixel_state)
    begin
    		if((char_count>=start_horz) && (char_count<=start_horz+50))begin if((line_count>=i) && (line_count<=i+100)) begin
    					VGA_BLUE<=6'b111110;
    			end
    			else
    				VGA_BLUE<=6'b000000;
    			end
    		else
    			VGA_BLUE<=6'b000000;
    	if((ball_x-char_count)*(ball_x-char_count)+(ball_y-line_count)*(ball_y-line_count)<400)
    		VGA_RED<=5'b11110;
    	else
    		VGA_RED<=5'b00000;
    end
    

    Перерасчет координат мячика и ракетки происходит при восходящем фронте тактового сигнала clk. Так же, если мячик столкнулся со стенкой, происходит изменение направления его движения.
    always @(posedge clk)
    begin		
    	if(key_2==0)
    	begin
    		if(i<vert_sync+vert_back_porch+vert_addr_time) i=i+1; else i=0; end if(key_0==0) begin if(i>vert_sync+vert_back_porch)
    			i=i-1;
    		else
    			i=vert_sync+vert_back_porch+vert_addr_time;
    	end
    	if(flag == 2'b00)
    	begin
    		ball_x=ball_x-1;
    		ball_y=ball_y-1;
    	end
    	if(flag == 2'b01)
    	begin
    		ball_x=ball_x+1;
    		ball_y=ball_y+1;
    	end
    	if(flag == 2'b10)
    	begin
    		ball_x=ball_x-1;
    		ball_y=ball_y+1;
    	end
    	if(flag == 2'b11)
    	begin
    		ball_x=ball_x+1;
    		ball_y=ball_y-1;
    	end
    	if(ball_y<=vert_sync+vert_back_porch)
    	if(flag==2'b00)
    		flag=2'b10;
    	else
    		flag=2'b01;
    	if(ball_x<=horz_sync+horz_back_porch) if(flag==2'b10) flag = 2'b01; else flag = 2'b11; if(ball_y>=vert_sync+vert_back_porch+vert_addr_time)
    		if(flag==2'b01)
    			flag=2'b11;
    		else
    			flag=2'b00;
    	if(ball_x>=start_horz && ball_y>=i && ball_y<=i+100) if(flag==2'b11) flag=2'b00; else flag=2'b10; if(ball_x>=horz_sync+horz_back_porch+horz_addr_time)
    		begin
    			if(goal_2==9)
    			begin
    				goal_2<=0;
    				goal<=goal+1;
    			end
    			else 
    			goal_2<=goal_2+1;
    			if(flag==2'b11)
    				flag<=2'b00;
    			else
    				flag<=2'b10;
    		end
    end
    

    В случае:
    — если шарик не встретился с ракеткой при приближении к правому краю игрового поля, то счет, отображаемый на семисегментных индикаторах, увеличится на единицу, тк происходит срабатывание на изменение goal;
    — переполнения goal — происходит: изменение goal_2 и увеличение на единицу десятичного разряда.
    always @(clk)
    begin
     case(goal)
     0: HEX_1 <= 7'b1000000;
     1: HEX_1 <= 7'b1111001;
     2: HEX_1 <= 7'b0100100;
     3: HEX_1 <= 7'b0110000;
     4: HEX_1 <= 7'b0011001;
     5: HEX_1 <= 7'b0010010;
     6: HEX_1 <= 7'b0000010;
     7: HEX_1<= 7'b1111000;
     8: HEX_1 <= 7'b0000000;
     9: HEX_1 <= 7'b0010000;
     default: HEX_1 <= 7'b1111111;
     endcase
    end
    
    always @(clk)
    begin
     case(goal_2)
     0: HEX_2 <= 7'b1000000;
     1: HEX_2 <= 7'b1111001;
     2: HEX_2 <= 7'b0100100;
     3: HEX_2 <= 7'b0110000;
     4: HEX_2 <= 7'b0011001;
     5: HEX_2 <= 7'b0010010;
     6: HEX_2 <= 7'b0000010;
     7: HEX_2 <= 7'b1111000;
     8: HEX_2 <= 7'b0000000;
     9: HEX_2<= 7'b0010000;
     default: HEX_2 <= 7'b1111111;
     endcase 
    end
    

    Заключение


    Синтезируем полученный проект и получаем статистику по занятым в ПЛИС ресурсам:

    9

    Реализуя этот проект, мы увидели, что с помощью FPGA достаточно просто можно реализовывать сложные интерфейсы такие как VGA, с очень высокими требованиями к таймингам которые трудно выдержать используя МК.  https://github.com/MIPSfpga/pre-mipsfpga/tree/master/pinpong

    PS: в мир FPGA вошел недавно, очень извиняюсь перед более опытными людьми которым мой код выжег глаза. Прошу понять, простить, помочь советом.

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 39

    • UFO just landed and posted this here
      • UFO just landed and posted this here
          0
          А вас не удивляет, что в комбинационной логике присвоение происходит переменной, объявленной как регистр?
          Компилятор не по этим признакам определит что ему использовать: триггер или LUT. Для него важнее список чувствительности always.
          • UFO just landed and posted this here
              0
              Даже если поставить <= синтезатор будет использовать LUT, если список чувствительности включает все сигналы, например так: always @*. Если вы собираетесь делать именно комбинационное устройство, то все входные сигналы должны быть в списке чувствительности, а все мультиплексоры (операторы if, case, ?:) должны иметь явную ветвь default.
              Если сигнал в списке чувствительности указан явно и не используется в качестве информационного в потоке, то он будет воспринят как clk или асинхронный сброс/установка. В этом случаи будет использован триггер.
              Когда пишешь код для синтеза нужно знать не только особенность стандарта Verilog, но и архитектуру аппаратной платформы, как минимум, устройство логического элемента — что допустимо для Стратикса, пойдет на Циклоне со скрипом.
              • UFO just landed and posted this here
                  +2
                  Я тут мимо проходил, и на верилоге почти не пишу, поэтому не могу сказать, где код ужасен, а где нет, но хотел бы научиться отличаться ужасный код от нормального и толковые комментарии от профессионалов могли бы в этом помочь. Вместо них у вас тут «и так понятно».
                  Посмотрите для примера на комментарии тов. khim, он тоже не терпит говнокода, но при этом не ленится пояснять почему конкретный код — говно и что с этим можно сделать. В итоге в выигрыше все, и автор, и комментатор, и читатели.
                  TL;DR: Критикуешь — предлагай.
                    +7
                    Как говорится, я, конечно, не гинеколог, но посмотреть могу… Я не профессионал, но могу пару общих правил привести, по возможности, с примером ошибки из кода:

                    1. Необходимо чётко знать, какой тип схемы должен получиться из куска кода: синхронная, защёлка или комбинационная. Для каждого типа есть свои правила написания кода, их я ниже изложу
                    2. В синхронной схеме нужно использовать только неблокирующие присваивания, т.е. <=
                    В приведённом коде этот кусок с ошибкой

                    always @(negedge clock)
                    begin
                    if(PS2_CLK_in == 1)
                    count_clk = count_clk + 1;
                    else

                    3. В комбинационной только блокирующие =
                    В приведённом коде этот кусок с ошибкой

                    always @*
                    begin
                    visible <= hvisible & vvisible;
                    end

                    4. Все возможные ветви комбинационного кода должны быть описаны, иначе получится защёлка. Например, если у вас кейс по 4х-битной переменной, нужно либо описать все 16 ветвей либо добавить ветку default для всех неописанных.

                    Если получилась где-то защёлка, где вы не собирались её создавать (вы это увидите в варнингах), то скорее всего, код косячный.

                    Ещё по коду увидел такую вещь: синхронизация дисплея берётся с выхода комбинационной схемы

                    wire w_hsync = (pixel_count < h_sync);


                    Выход комбинационной схемы может давать иголки из-за неодновременного переключения составляющих логических элементов, поэтому их всегда надо пропускать через синхронизатор, прежде чем заводить куда-то дальше в синхронных схемах
                      0
                      Большое спасибо. Плюсанул бы вам в карму с удовольствием, но у вас статей нет. Напишите что-нибудь о верилоге и FPGA, если найдете время.
                        0
                        Не за что) Я думаю насчёт статьи, но у меня нет пока достойного проекта)
                        0
                        При всем уважении, почему в комбинационном только блокирующие?
                        В моей практике часто бывает, когда комбинационную схему приходится делать регистровой, например, если по времянке не проходит, то добавляю еще одно звено конвейера. В этом случае просто редактируется список чувствительности always, но если там везде блокирующие присваивания, то придется менять и их. А потом удалось оптимизировать где-то еще, и захотелось вернуться к комбинационной схеме… Вероятность допустить ошибку повышается.
                        Лично я стараюсь использую везде неблокирующие присваивания, и только там, где это действительно нужно использую блокирующие, причем это бывает и в синхронных схемах. Другим так делать не рекомендую, потому как не уверен в своей правоте.
                          0
                          Я сразу указал, что я не профессионал, поэтому не могу ответить на это) читал в книгах и слышал от разбирающихся людей, что в комб. логике не следует использовать неблокирующие присваивания.
                          Я пишу на SystemVerilog, там можно явно указать, какая логика требуется: флип-флопы, защёлки или комбинационная ( always_ff, always_latch, always_comb) соответственно. У 2 и 3 списка чувствительности нет в принципе.
                          • UFO just landed and posted this here
                              0
                              Я думал это написать, но не смог внятно сформулировать) Насколько я понял, при использовании блокирующих присваиваний важен порядок строк в коде, и тогда если написать
                              b = c;
                              a = b;
                              то для компилятора переменная b уже будет как бы «определена», а её значение равно c. И он подставит c вместо b во всех строках, идущих после.
                            0
                            Пару замечаний.

                            >1. Необходимо чётко знать, какой тип схемы должен получиться из куска кода: синхронная, защёлка или комбинационная.
                            Согласен, кроме того, что защелок следует избегать.

                            >2. В синхронной схеме нужно использовать только неблокирующие присваивания, т.е.
                              0
                              Продублируйте, пожалуйста, текст не до конца сохранился
                  0
                  Я не автор, но спрошу — какие в этом проблемы? Я в HDL языках нуб полнейший, не понял, чему вы так ужасаетесь.
                    0
                    Не за что) Я думаю насчёт статьи, но у меня нет пока достойного проекта)
                      0
                      сорри, не в ту ветку попал, с мобильным приложением какие-то проблемы
                  –7
                  отличное упражнение написать игру пинг понг на любом языке программирования. В один тред. С AI на генеративных функциях (иногда ошибается) и с Command pattern'ом.

                  Хорошая задача, нормальный студент должен суметь кое-как написать.

                  Вот тут ныли-ныли, что они не хотят быть «мясом» и писать на го. А вот идите ка на своей скале напишите хоть пинг-понг. И покажите и поговорим тогда.
                    +2
                    За тематику плюсую. Но к оформлению проекта и кода есть много вопросов. Какая религия не позволила сделать проект полностью на HDL, зачем Top Level в Schematic? Зачем в тексте кода так много Magic number? Но не оставляет ощущение, что вы до сих пор пишете на Си: много begin-end, сложные логические конструкции. Множество приоритетных мультиплексоров на if с неявными else. Вам компилятор про Latch предупреждение не выдавал?

                    P.S.: Недостаток запятых в тексте делает чтение не комфортным.
                      0
                      Я начинающий, по роду своей деятельности пишу на Си под МК. В FPGA попробовал себя недавно и изучаю эту область сам, ну и решил разродиться статьей.
                      0
                      Привет! Отличная статья, хорошая мотивация для новичков, в т.ч. и меня.
                      Попробуйте в тестбенчах для генерации клока такую конструкцию:

                      initial begin
                      #0 clock_r = 1;
                      //…
                      end

                      always begin
                      #x clock_r
                        0
                        Не уловил смысл, того что предлагаете сделать.
                          0
                          Почему-то текст комментария не до конца разместился

                          initial begin
                          #0 clock_r = 1;
                          //…
                          end

                          always begin
                          #x clock_r = ~clock_r;
                          end

                          х — это половина периода синхронизации

                          Это нужно чтобы клок сам бегал, без вашего постоянного контроля и портянки кода для этого)
                        0
                        Красивая девушка.
                          0
                          Фронтальной проекции не хватает.
                          +1
                          Не требовалось ли в рамках этого конкурса использовать процессор MIPSfpga для решения задачи?

                          PS: в мир FPGA вошел недавно, очень извиняюсь перед более опытными людьми которым мой код выжег глаза. Прошу понять, простить, помочь советом.

                          Коли спрашиваете, даю советы:
                          • Прочитать «Совершенный код» Макконнелла.
                          • Найти в интернете или взять какой-то готовый coding-style по Verilog'у. На правах рекламы показываю тот, которым руководствуюсь я. (Версия не самая полная, я еще кое-чего там буду дописывать).
                          • Не путать блокирующие и неблокирующие присваивания.
                          • Разобраться, какие файлы надо класть в систему контроля версий, а какие нет. Невооруженным взглядом видно, что много мусорных файлов (например, директория db) лежит в проекте.
                          • Для описания констрейнов (например, значений частот у клоков в системе) не использовался sdc-файл. По отчету project_1.sta.rpt видно, что клоки clk:inst4|clock_out[17] и PS2_CLK взяты с частотой 1 ГГц, что на самом деле, конечно не так.


                          YuriPanchul, вы как-то модерируете (ревьюите) проекты (и исходные коды), которые потом попадают к вам на гитхаб?
                            0
                            Спасибо за coding-style. А на какой-либо площадке предполагается обсуждение и вопросы? Может статью на Хабр заделаете?
                            Мне кое-что не ясно, есть спорные моменты. Например, чем не угодил вам TAB? Много где встречаю, что вместо него пробелами ровняют, но почему? Переносимость между редакторами?
                              0
                              Кодинг стайл состоит из двух вещей: субъективной (расстановка скобок, пробелов, выравнивание, конвенция о наименовании переменных) и объективной (к примеру, корректное использование присваиваний).

                              ИМХО, большая часть кодинг стайла — субъективная, и обсуждать её я не вижу особого смысла.
                              Два примера с if:
                              1. if( a > b )
                              2. if (a > b)
                              

                              Кому-то больше нравится первый вариант, вариант кому-то второй. Это абсолютно нормально: и я не вижу смысла тратить время на холивары.

                              Пробелы гарантируют одинаковое отображение исходного кода везде (при использовании monospace шрифта, разумеется), а табы — в зависимости от настроек, но это не значит, что табы должны быть запрещены: в linux kernel их используют, и я не имею права за это в них кинуть камень. У них есть свой CodingStyle, где это явно прописано.

                              Тот кодинг стайл, что я скинул, это компиляция опыта (~5-7 лет коммерческой разработки) и предпочтений небольшой команды FPGA-разработчиков. Этот стиль позволял быстро (за предсказуемое время), качественно разрабатывать новые модули (сам себя не похвалишь — никто не похвалит) и осуществлять дальнейшую поддержку и багфикс кода. Версия не самая полная, потому что оригинал находится в формате wiki/html, и я постепенно переношу это в markdown, причёсывая его структуру.

                              Я всегда рад услышать вопросы, замечания, предложения, но скатываться в холивар я не буду. Это является оффтопом к теме с созданием игры, поэтому можно писать мне в личку.
                              +2
                              ishevchuk в данном случае я не модерировал, рассматривая это как упражнение для Бориса, которого все равно раскритикуют. Когда он собрался публиковать, я ему сказал про проблемы с стилем (см. описание ниже) и предложил либо их исправить, либо опубликовать как есть, с идеей, что на Хабре его все равно раскритикуют.

                              Раз уж тут пошла критика, прибавляю своей:

                              Борис, я рекомендую перед публикацией привести стиль вашего кода к некоторым принятым в индустрии традициям, которые были сформулированы в районе 2000-го года (т.е. вы можете найти много старого верилога, который этому не следует, но лучше чтобы следовало). В принципе, можно публиковать и так, но вас тогда раскритикуют на Хабре (это может быть полезно, так как там много людей, и они сделают критику быстрее чем я, потому что я связан другими делами).

                              Но как минимум стоит избавиться от блокирующих присваиваний внутри последовательностных always-блоков. С ними можно нарваться на гонки, race-condition, когда результат симуляции может не совпасть с результатом работы платы после синтеза. Например см. мой коммент в обсуждении на https://habrahabr.ru/post/278005/

                              Простейший пример race condition:
                              module dut
                              (
                                  input              clk,
                                  input        [7:0] d,
                                  output logic [7:0] q
                              );
                                  logic [7:0] r;
                               
                                  always @(posedge clk)  // ПЛОХО! Результат симуляции зависит от того, какой блок симулируется раньше в event queue – первый или второй
                                      r = d;
                               
                                  always @(posedge clk)
                                      q = r;
                               
                              endmodule
                               


                              А вот если делать так, то все будет однозначно:
                              module dut
                              (
                                  input              clk,
                                  input        [7:0] d,
                                  output logic [7:0] q
                              );
                                  logic [7:0] r;
                               
                                  always @(posedge clk) 
                                      r <= d;
                               
                                  always @(posedge clk)
                                      q <= r;
                               
                              endmodule
                               


                              Также использование генерируемого сигнала в качестве clock-а – это не очень хорошо:
                              always @ (posedge pixel_clock)
                              begin
                                  hsync <= (pixel_count < h_sync);
                               
                              ….
                              always @ (posedge hsync)
                              …
                               

                              Если вам нужно такое сгенерить, то лучше делать чего-нибудь типа (хоть это и выглядит громоздко):
                              wire  hsync <= (pixel_count < h_sync);
                              reg prev_hsync;
                               
                              always @ (posedge clk)
                                  prev_hsync <= hsync;
                              …
                              wire pos_hsync = hsync & ~ prev_hsync;
                               
                              ….
                              always @ (posedge clk)
                                  if(reset)
                                    …
                                  else if (pos_hsync)
                               



                              Кроме этого, код типа
                                              if(goal == 0)
                                              begin
                                                              HEX_1 = 7'b1000000;
                                              end
                                             
                                              if(goal == 1)
                                              begin
                                                              HEX_1 = 7'b1111001;
                                              end
                                                             
                                              if(goal == 2)
                                              begin
                                                              HEX_1 = 7'b0100100;
                                              end       
                               

                              лучше заменить на
                              case (goal)
                              0: HEX_1 = 7'b1000000;
                              1: HEX_1 = 7'b1111001;
                              2: HEX_1 = 7'b0100100;
                              . . . .
                              default:  HEX_1 = 7’b1111111;
                              endcase
                               

                              Наконец, есть более компактные способы записи кода в testbench:

                              Вместо:
                                  #50 PS2_CLK_r = 0; //start
                                  #50 PS2_CLK_r = 0; //0
                                  #50 PS2_CLK_r = 1; //1
                                  #50 PS2_CLK_r = 1; //2
                                  #50 PS2_CLK_r = 0; //3
                                  #50 PS2_CLK_r = 1; //4
                                  #50 PS2_CLK_r = 0; //5
                                  #50 PS2_CLK_r = 1; //6
                                  #50 PS2_CLK_r = 1; //7
                                  #50 PS2_CLK_r = 1; //parity bit
                                  #50 PS2_CLK_r = 0; //stop
                                  #50 PS2_CLK_r = 1; //s
                                  #50 PS2_CLK_r = 1; //s
                                 

                              Можно написать что-нибудь типа:
                              reg [511:0] test_bits;
                              integer i;
                               
                              initial
                              begin
                                 …
                               
                                 test_bits = 512’b01010111000110010101;
                               
                                 for (i = 0; i < 512; i = i + 1)
                                     #50 PS2_CLK_r = test_bits [i];
                               


                              Но опять же: вы можете выложить as-is и посмотреть, как народ на Хабре будет реагировать (так как вы начинающий, то вашу репутацию это не испортит).

                              Кроме этого я рекомендую выложить ваш код на GitHub (например поместить проект в https://github.com/MIPSfpga/pre-mipsfpga ) чтобы ссылаться на ваш код из поста.

                              Это мои первые мысли – завтра я посмотрю ваш код более внимательно.

                              Спасибо,
                              Юрий Панчул
                                0
                                Спасибо за подробный ответ.

                                Жаль, что изначальная предпосылка такая:
                                рассматривая это как упражнение для Бориса, которого все равно раскритикуют. Когда он собрался публиковать, я ему сказал про проблемы с стилем (см. описание ниже) и предложил либо их исправить, либо опубликовать как есть, с идеей, что на Хабре его все равно раскритикуют.
                              +2
                              Коллеги, будьте объективны. Человек написал первый в жизни простенький проектик. Несмотря на объективные проблемы с кодом, довел его до работоспособного состояния (по крайней мере в данных конкретных условиях, хе-хе). Зачем же сразу зло клевать, ласковее надо… Другое дело, для чего его выкладывать сразу на общее обозрение… Я вот прошлую неделю впервые на жабаскрипте писал, но показывать код, пока, пожалуй никому не буду. Пусть лучше потом следственный комитет разбирается, отчего ракета упала.

                              PS Блокирующие и не блокирующие назначение при синтезе дают одит и тот же [правильный] результат в массе своей, так что проблемы только в моделировании можно заметить.
                                0
                                [quote]PS Блокирующие и не блокирующие назначение при синтезе дают один и тот же [правильный] результат в массе своей, так что проблемы только в моделировании можно заметить.[/quote]

                                Не совсем так.
                                Если под рукой есть синтезатор, тот же квартус, попробуйте написать такой код ( SystemVerilog ):

                                module top (
                                input logic rst_i,
                                input logic clk_i,
                                input logic data_i,
                                output logic data_o
                                );

                                logic data_between;

                                always_ff @( posedge clk_i or posedge rst_i ) begin
                                if ( rst_i ) begin
                                data_o <= 0;
                                data_between <= 0;
                                end else begin
                                data_between <= data_i;
                                data_o <= data_between;
                                end
                                end

                                endmodule

                                Просинтезируйте и посмотрите RTL модель, затем поменяйте неблокирующие присваивания на блокирующие, просинтезируйте и посмотрите, что будет. А будет следующее: цепочка из 2х триггеров будет «оптимизирована» и один из триггеров будет выкинут просто-напросто
                                  0
                                  Ну да, в этом-то случае естественно, когда есть прямая последовательность назначений в одном процессе.
                                    +1
                                    Тогда есть смысла _всегда_ использовать неблокирующие присваивания, чтобы не задумываться, в каком порядке писать строчки кода
                                      0
                                      Конечно. Я лишь говорил о том, что в синтезе используются заметные упрощения, не позволяющие порой обнаружить формальные ошибки ;)
                                  –1
                                  Причём если поменять местами 2 строчки и написать так:

                                  data_o <= data_between;
                                  data_between <= data_i;

                                  То никакой оптимизации не произойдёт)) У меня нещадно бомбило однажды от такого поведения кода
                                  0
                                  Бесплатный совет всем, у кого не хватило денег на Spyglass. Используйте Verilator в Lint-режиме.

                                  Only users with full accounts can post comments. Log in, please.