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 вошел недавно, очень извиняюсь перед более опытными людьми которым мой код выжег глаза. Прошу понять, простить, помочь советом.

Комментарии (39)


  1. artemonster
    16.04.2016 19:08
    +1

    Выколите мне глаза! Blocking assign в секвенциальной логике. аааааааааааааа


    1. artemonster
      16.04.2016 20:10
      -4

      А, ну и '<=' присвоения там, где логика комбинаторная.
      я удивлен, что это работает :)


      1. Shamrel
        17.04.2016 06:30

        А вас не удивляет, что в комбинационной логике присвоение происходит переменной, объявленной как регистр?
        Компилятор не по этим признакам определит что ему использовать: триггер или LUT. Для него важнее список чувствительности always.


        1. artemonster
          17.04.2016 09:36

          в комбинаторной логике присвоение регистру это окей, до тех пор, пока присовение НЕ <=, синтезатор правильно из «регистра» сделает LUT.
          только вот глупости с тупостями в код писать не надо. а потом минусами раскидываться ещё ))
          и ваш аргумент с списком чувствительности — гляньте на любой «комбинаторный» блок — учитывая, что там не хватает кучи сигналов в списке чувствительности и используется <=, синтезатор налепит защелкок или флипфлопов, но никак не логику.


          1. Shamrel
            17.04.2016 10:13

            Даже если поставить <= синтезатор будет использовать LUT, если список чувствительности включает все сигналы, например так: always @*. Если вы собираетесь делать именно комбинационное устройство, то все входные сигналы должны быть в списке чувствительности, а все мультиплексоры (операторы if, case, ?:) должны иметь явную ветвь default.
            Если сигнал в списке чувствительности указан явно и не используется в качестве информационного в потоке, то он будет воспринят как clk или асинхронный сброс/установка. В этом случаи будет использован триггер.
            Когда пишешь код для синтеза нужно знать не только особенность стандарта Verilog, но и архитектуру аппаратной платформы, как минимум, устройство логического элемента — что допустимо для Стратикса, пойдет на Циклоне со скрипом.


            1. artemonster
              17.04.2016 10:48
              -3

              я не понимаю, чем вы занимаетесь.
              вы защищаете этот говнокод, который даже по вашим стандартам не проходит (в списке чувствительности не все указано, дефолтов нет)?
              вы пытаетесь мне основы хорошего RTL кода объяснить? спасибо, не надо.
              или вы просто мне перечите от скуки? и при чем тут ваще архитектура?!
              я лишь высказал свой тезис: этот код ужасен. решил не вадаваться в причины, ибо и так понятно. тут появляетесь вы и начинаете что-то затирать непонятное. ух.


              1. CodeRush
                17.04.2016 11:35
                +2

                Я тут мимо проходил, и на верилоге почти не пишу, поэтому не могу сказать, где код ужасен, а где нет, но хотел бы научиться отличаться ужасный код от нормального и толковые комментарии от профессионалов могли бы в этом помочь. Вместо них у вас тут «и так понятно».
                Посмотрите для примера на комментарии тов. khim, он тоже не терпит говнокода, но при этом не ленится пояснять почему конкретный код — говно и что с этим можно сделать. В итоге в выигрыше все, и автор, и комментатор, и читатели.
                TL;DR: Критикуешь — предлагай.


                1. GREGOR_812
                  17.04.2016 13:57
                  +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);


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


                  1. CodeRush
                    17.04.2016 14:06

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


                    1. GREGOR_812
                      17.04.2016 16:04

                      Не за что) Я думаю насчёт статьи, но у меня нет пока достойного проекта)


                  1. Shamrel
                    17.04.2016 15:40

                    При всем уважении, почему в комбинационном только блокирующие?
                    В моей практике часто бывает, когда комбинационную схему приходится делать регистровой, например, если по времянке не проходит, то добавляю еще одно звено конвейера. В этом случае просто редактируется список чувствительности always, но если там везде блокирующие присваивания, то придется менять и их. А потом удалось оптимизировать где-то еще, и захотелось вернуться к комбинационной схеме… Вероятность допустить ошибку повышается.
                    Лично я стараюсь использую везде неблокирующие присваивания, и только там, где это действительно нужно использую блокирующие, причем это бывает и в синхронных схемах. Другим так делать не рекомендую, потому как не уверен в своей правоте.


                    1. GREGOR_812
                      17.04.2016 16:08

                      Я сразу указал, что я не профессионал, поэтому не могу ответить на это) читал в книгах и слышал от разбирающихся людей, что в комб. логике не следует использовать неблокирующие присваивания.
                      Я пишу на SystemVerilog, там можно явно указать, какая логика требуется: флип-флопы, защёлки или комбинационная ( always_ff, always_latch, always_comb) соответственно. У 2 и 3 списка чувствительности нет в принципе.


                    1. artemonster
                      17.04.2016 16:17

                      Когда блокирующие присваивания интерпретируются синтезатором, то синтезатор знает, что левая часть должна быть присвоена моментально и делает эквивалент assign wire = expr;, т.е. комбинаторную логику.
                      Если стоит неблокирующее присваивание (т.е. апдейт LHS сдинут на следующий дельта-цикл), а логика имелась в виду комбинаторная, синтезатор может не понять (особенно, если перед присваиванием стоит условие) и вместо комбинаторики сделать защёлку (latch) с enable сигналом, что, чаще всего, катастрофически сказывается на результате. (не в симуляции RTL, а в симуляции нетлиста).


                      1. GREGOR_812
                        17.04.2016 17:03

                        Я думал это написать, но не смог внятно сформулировать) Насколько я понял, при использовании блокирующих присваиваний важен порядок строк в коде, и тогда если написать
                        b = c;
                        a = b;
                        то для компилятора переменная b уже будет как бы «определена», а её значение равно c. И он подставит c вместо b во всех строках, идущих после.


                  1. rooi-oog
                    18.04.2016 10:22

                    Пару замечаний.

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

                    >2. В синхронной схеме нужно использовать только неблокирующие присваивания, т.е.


                    1. GREGOR_812
                      18.04.2016 12:36

                      Продублируйте, пожалуйста, текст не до конца сохранился


      1. Alex_ME
        17.04.2016 09:10

        Я не автор, но спрошу — какие в этом проблемы? Я в HDL языках нуб полнейший, не понял, чему вы так ужасаетесь.


        1. GREGOR_812
          17.04.2016 15:21

          Не за что) Я думаю насчёт статьи, но у меня нет пока достойного проекта)


        1. GREGOR_812
          17.04.2016 16:04

          сорри, не в ту ветку попал, с мобильным приложением какие-то проблемы


  1. gena_glot
    16.04.2016 19:36
    -7

    отличное упражнение написать игру пинг понг на любом языке программирования. В один тред. С AI на генеративных функциях (иногда ошибается) и с Command pattern'ом.

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

    Вот тут ныли-ныли, что они не хотят быть «мясом» и писать на го. А вот идите ка на своей скале напишите хоть пинг-понг. И покажите и поговорим тогда.


  1. Shamrel
    17.04.2016 05:57
    +2

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

    P.S.: Недостаток запятых в тексте делает чтение не комфортным.


    1. Boris_92
      17.04.2016 06:36

      Я начинающий, по роду своей деятельности пишу на Си под МК. В FPGA попробовал себя недавно и изучаю эту область сам, ну и решил разродиться статьей.


  1. GREGOR_812
    17.04.2016 08:56

    Привет! Отличная статья, хорошая мотивация для новичков, в т.ч. и меня.
    Попробуйте в тестбенчах для генерации клока такую конструкцию:

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

    always begin
    #x clock_r


    1. Boris_92
      17.04.2016 08:59

      Не уловил смысл, того что предлагаете сделать.


      1. GREGOR_812
        17.04.2016 13:05

        Почему-то текст комментария не до конца разместился

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

        always begin
        #x clock_r = ~clock_r;
        end

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

        Это нужно чтобы клок сам бегал, без вашего постоянного контроля и портянки кода для этого)


  1. RichardDoe
    17.04.2016 08:56

    Красивая девушка.


    1. vagran
      17.04.2016 10:43

      Фронтальной проекции не хватает.


  1. ishevchuk
    17.04.2016 10:45
    +1

    Не требовалось ли в рамках этого конкурса использовать процессор MIPSfpga для решения задачи?

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

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


    YuriPanchul, вы как-то модерируете (ревьюите) проекты (и исходные коды), которые потом попадают к вам на гитхаб?


    1. Shamrel
      17.04.2016 15:54

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


      1. ishevchuk
        18.04.2016 07:00

        Кодинг стайл состоит из двух вещей: субъективной (расстановка скобок, пробелов, выравнивание, конвенция о наименовании переменных) и объективной (к примеру, корректное использование присваиваний).

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

        1. if( a > b )
        2. if (a > b)
        

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

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

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

        Я всегда рад услышать вопросы, замечания, предложения, но скатываться в холивар я не буду. Это является оффтопом к теме с созданием игры, поэтому можно писать мне в личку.


    1. YuriPanchul
      17.04.2016 19:17
      +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 ) чтобы ссылаться на ваш код из поста.

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

      Спасибо,
      Юрий Панчул


      1. ishevchuk
        18.04.2016 07:10

        Спасибо за подробный ответ.

        Жаль, что изначальная предпосылка такая:

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


  1. nerudo
    17.04.2016 15:27
    +2

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

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


    1. GREGOR_812
      17.04.2016 16:03

      [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х триггеров будет «оптимизирована» и один из триггеров будет выкинут просто-напросто


      1. nerudo
        17.04.2016 16:35

        Ну да, в этом-то случае естественно, когда есть прямая последовательность назначений в одном процессе.


        1. GREGOR_812
          17.04.2016 16:51
          +1

          Тогда есть смысла _всегда_ использовать неблокирующие присваивания, чтобы не задумываться, в каком порядке писать строчки кода


          1. nerudo
            17.04.2016 17:13

            Конечно. Я лишь говорил о том, что в синтезе используются заметные упрощения, не позволяющие порой обнаружить формальные ошибки ;)


    1. GREGOR_812
      17.04.2016 16:10
      -1

      Причём если поменять местами 2 строчки и написать так:

      data_o <= data_between;
      data_between <= data_i;

      То никакой оптимизации не произойдёт)) У меня нещадно бомбило однажды от такого поведения кода


  1. valeriyk
    18.04.2016 19:45

    Бесплатный совет всем, у кого не хватило денег на Spyglass. Используйте Verilator в Lint-режиме.