Мой драйвер Verilog VGA заставляет экран мерцать (Basys2)

Я пытаюсь воссоздать Adventure (1979) в Verilog, и пока у меня есть движения персонажей, коллизии и генерация карты. Он не так сильно мерцал, пока я не разделил карты на модули, теперь он постоянно мерцает. Когда я искал эту проблему, я обнаружил, что часы на плате Basys2 довольно шумные и могут быть причиной. Однако размещение карт в модулях не должно было ухудшить ситуацию, если только я что-то не напортачил. Есть идеи, что случилось?

Вот мой генератор карт:

module map_generator(clk_vga, reset, CurrentX, CurrentY, HBlank, VBlank, playerPosX, playerPosY, mapData
);

  input clk_vga;
  input reset;
  input [9:0]CurrentX;
  input [8:0]CurrentY;
  input HBlank;
  input VBlank;
  input [9:0]playerPosX;
  input [8:0]playerPosY;

  output [7:0]mapData;

  reg [7:0]mColor;
  reg [5:0]currentMap = 0;

  wire [7:0]startCastle;

  StartCastle StartCastle(
    .clk_vga(clk_vga),
    .CurrentX(CurrentX),
    .CurrentY(CurrentY),
    .mapData(startCastle)
  );

  always @(posedge clk_vga) begin
    if(reset)begin
      currentMap <= 0;
    end
  end

  always @(posedge clk_vga) begin
    if(HBlank || VBlank) begin
      mColor <= 0;
    end
    else begin
      if(currentMap == 4'b0000) begin
        mColor[7:0] <= startCastle[7:0];
      end
      //Add more maps later
    end
  end

  assign mapData[7:0] = mColor[7:0];

endmodule

Вот начало Замка:

module StartCastle(clk_vga, CurrentX, CurrentY, active, mapData);

  input clk_vga;
  input [9:0]CurrentX;
  input [8:0]CurrentY;
  input active;

  output [7:0]mapData;

  reg [7:0]mColor;

  always @(posedge clk_vga) begin

    if(CurrentY < 40) begin
      mColor[7:0] <= 8'b11100000;
    end
    else if(CurrentX < 40) begin
      mColor[7:0] <= 8'b11100000;
    end
    else if(~(CurrentX < 600)) begin
      mColor[7:0] <= 8'b11100000;
    end
    else if((~(CurrentY < 440) && (CurrentX < 260)) || (~(CurrentY < 440) && ~(CurrentX < 380))) begin
      mColor[7:0] <= 8'b11100000;
    end else
      mColor[7:0] <= 8'b00011100;       
  end

  assign mapData = mColor;
endmodule

Вот драйвер VGA, который подключен к моему верхнему модулю:

module vga_driver(clk_50MHz, vs_vga, hs_vga, RED, GREEN, BLUE, HBLANK, VBLANK, CURX, CURY, COLOR, CLK_DATA, RESET);

  input clk_50MHz;
  output vs_vga;
  output hs_vga;
  output [2:0] RED;
  output [2:0] GREEN;
  output [1:0] BLUE;
  output HBLANK;
  output VBLANK;

  reg VS = 0;
  reg HS = 0;

  input RESET;

  //current client data
  input [7:0] COLOR;
  output CLK_DATA;
  output [9:0] CURX;
  output [8:0] CURY;

  //##### Module constants (http://tinyvga.com/vga-timing/640x480@60Hz)
  parameter HDisplayArea = 640;  // horizontal display area
  parameter HLimit = 800;        // maximum horizontal amount (limit)
  parameter HFrontPorch = 16;    // h. front porch
  parameter HBackPorch = 48;         // h. back porch
  parameter HSyncWidth = 96;         // h. pulse width

  parameter VDisplayArea = 480;  // vertical display area
  parameter VLimit = 525;        // maximum vertical amount (limit)
  parameter VFrontPorch = 10;    // v. front porch
  parameter VBackPorch = 33;         // v. back porch
  parameter VSyncWidth = 2;      // v. pulse width 

  //##### Local variables
  wire clk_25MHz;

  reg [9:0] CurHPos = 0; //maximum of HLimit (2^10 - 1 = 1023)
  reg [9:0] CurVPos = 0; //maximum of VLimit
  reg HBlank_reg, VBlank_reg, Blank = 0;

  reg [9:0] CurrentX = 0;    //maximum of HDisplayArea
  reg [8:0] CurrentY = 0;    //maximum of VDisplayArea (2^9 - 1 = 511)

  //##### Submodule declaration
  clock_divider clk_div(.clk_in(clk_50MHz), .clk_out(clk_25MHz));

  //shifts the clock by half a period (negates it)
  //see timing diagrams for a better understanding of the reason for this
  clock_shift clk_shift(.clk_in(clk_25MHz), .clk_out(CLK_DATA));

  //simulate the vertical and horizontal positions
  always @(posedge clk_25MHz) begin
    if(CurHPos < HLimit-1) begin
      CurHPos <= CurHPos + 1;
    end
    else begin
      CurHPos <= 0;

      if(CurVPos < VLimit-1)
        CurVPos <= CurVPos + 1;
      else
        CurVPos <= 0;
    end
    if(RESET) begin
      CurHPos <= 0;
      CurVPos <= 0;
    end
  end

  //##### VGA Logic (http://tinyvga.com/vga-timing/640x480@60Hz)

  //HSync logic
  always @(posedge clk_25MHz)
    if((CurHPos < HSyncWidth) && ~RESET)
      HS <= 1;
    else
      HS <= 0;

  //VSync logic     
  always @(posedge clk_25MHz)
    if((CurVPos < VSyncWidth) && ~RESET)
      VS <= 1;
    else
      VS <= 0;

//Horizontal logic      
  always @(posedge clk_25MHz) 
    if((CurHPos >= HSyncWidth + HFrontPorch) && (CurHPos < HSyncWidth + HFrontPorch + HDisplayArea) || RESET)
      HBlank_reg <= 0;
    else
      HBlank_reg <= 1;

  //Vertical logic
  always @(posedge clk_25MHz)
    if((CurVPos >= VSyncWidth + VFrontPorch) && (CurVPos < VSyncWidth + VFrontPorch + VDisplayArea) || RESET)
      VBlank_reg <= 0;
    else
      VBlank_reg <= 1;

  //Do not output any color information when we are in the vertical
  //or horizontal blanking areas. Set a boolean to keep track of this.
  always @(posedge clk_25MHz)
    if((HBlank_reg || VBlank_reg) && ~RESET)
      Blank <= 1;
    else
      Blank <= 0;

  //Keep track of the current "real" X position. This is the actual current X
  //pixel location abstracted away from all the timing details
  always @(posedge clk_25MHz)
    if(HBlank_reg && ~RESET)
      CurrentX <= 0;
    else
      CurrentX <= CurHPos - HSyncWidth - HFrontPorch;

  //Keep track of the current "real" Y position. This is the actual current Y
  //pixel location abstracted away from all the timing details
  always @(posedge clk_25MHz) 
    if(VBlank_reg && ~RESET)
      CurrentY <= 0;
    else
      CurrentY <= CurVPos - VSyncWidth - VFrontPorch;

  assign CURX = CurrentX;
  assign CURY = CurrentY;
  assign VBLANK = VBlank_reg;
  assign HBLANK = HBlank_reg;
  assign hs_vga = HS;
  assign vs_vga = VS;

  //Respects VGA Blanking areas
  assign RED = (Blank) ? 3'b000 : COLOR[7:5];
  assign GREEN = (Blank) ? 3'b000 : COLOR[4:2];
  assign BLUE = (Blank) ? 2'b00 : COLOR[1:0];
endmodule

clk_div:

module clock_divider(clk_in, clk_out);
  input clk_in;
  output clk_out;

  reg clk_out = 0;

  always @(posedge clk_in)
    clk_out <= ~clk_out;

endmodule

clk_shift:

module clock_shift(clk_in, clk_out);
  input clk_in;
  output clk_out;

  assign clk_out = ~clk_in;
endmodule

person Dilancuan    schedule 25.04.2015    source источник
comment
Какой у вас модуль clk_div? Это DCM / PLL?   -  person Jonathan Drolet    schedule 25.04.2015
comment
Я использую Spartan 3E, который, к сожалению, не поддерживает ФАПЧ. Я добавил в OP модули clk_shift и clk_div.   -  person Dilancuan    schedule 25.04.2015
comment
Это может быть твоя проблема. Часы, генерируемые логикой, не анализируются механизмом синхронизации, поэтому может выйти из строя, не сообщая об этом. Нет причин использовать таким образом разделенные часы. Вместо этого сгенерируйте сигнал clock_en (половинную скорость clk) и используйте его в качестве включения синхронизации в своей логике 50 МГц, где бы вы ни использовали clk_25MHz. Попробуйте это и сообщите нам, решит ли это вашу проблему. Следите за результатами таймингов, они могут выйти из строя после этого изменения, так как путь будет фактически проанализирован.   -  person Jonathan Drolet    schedule 25.04.2015
comment
Spartan 3E поддерживает DCM, который можно использовать для генерации разделенной версии главных часов, а также их инвертированной версии.   -  person mcleod_ideafix    schedule 25.04.2015
comment
@Jonathan Drolet: по крайней мере, в Xilinx часы, генерируемые как выходные данные триггера, могут быть направлены в сети BUFG и использоваться как часы. Синтезатор обнаруживает это и обрабатывает эти сигналы как часы.   -  person mcleod_ideafix    schedule 25.04.2015
comment
Я только что протестировал эту конструкцию в модуле Basys (Spartan 3E-100) и не вижу мерцания. Как вы подключаете эти модули в верхний модуль?   -  person mcleod_ideafix    schedule 25.04.2015
comment
@mcleod_ideafix Их можно направить в bufg, но они не анализируются на предмет пересечения периодов или часов.   -  person Jonathan Drolet    schedule 25.04.2015


Ответы (1)


Я отправляю это как ответ, потому что не могу добавить фотографию в комментарий.

Так выглядит ваш дизайн? Выход VGA из конструкции OP

Я предполагаю, что ATM - это то, что вы могли потерять некоторые порты во время создания экземпляров vga_driver и / или map_generator (если вы использовали создание экземпляров в старом стиле). Тем не менее, я собираюсь проверить тайминги VGA, так как я вижу странную вертикальную линию в левой части экрана, как если бы был виден интервал hblank.

Кстати: я изменил способ создания отображения. Вы используете регистры для HS, VS и т. Д., Которые обновляются в следующем тактовом цикле. Я рассматриваю создание дисплея как автомат, поэтому выходные данные поступают из комбинационных блоков, запускаемых определенными значениями (или диапазоном значений) со счетчиков. Кроме того, я запускаю счетчики по горизонтали и вертикали, поэтому позиция (0,0), измеренная в пиксельных координатах на экране, фактически соответствует значениям (0,0) из счетчиков по горизонтали и вертикали, поэтому никаких арифметических действий не требуется.

Это моя версия поколения VGA-дисплеев:

module videosyncs (
   input wire clk,

   input wire [2:0] rin,
   input wire [2:0] gin,
   input wire [1:0] bin,

   output reg [2:0] rout,
   output reg [2:0] gout,
   output reg [1:0] bout,

   output reg hs,
   output reg vs,

   output wire [10:0] hc,
   output wire [10:0] vc
   );

   /* http://www.abramovbenjamin.net/calc.html */

   // VGA 640x480@60Hz,25MHz
   parameter htotal = 800;
   parameter vtotal = 524;
   parameter hactive = 640;
   parameter vactive = 480;
   parameter hfrontporch = 16;
   parameter hsyncpulse = 96;
   parameter vfrontporch = 11;
   parameter vsyncpulse = 2;
   parameter hsyncpolarity = 0;
   parameter vsyncpolarity = 0;

   reg [10:0] hcont = 0;
   reg [10:0] vcont = 0;
   reg active_area;

    assign hc = hcont;
    assign vc = vcont;

   always @(posedge clk) begin
      if (hcont == htotal-1) begin
         hcont <= 0;
         if (vcont == vtotal-1) begin
            vcont <= 0;
         end
         else begin
            vcont <= vcont + 1;
         end
      end
      else begin
         hcont <= hcont + 1;
      end
   end

   always @* begin
      if (hcont>=0 && hcont<hactive && vcont>=0 && vcont<vactive)
         active_area = 1'b1;
      else
         active_area = 1'b0;
      if (hcont>=(hactive+hfrontporch) && hcont<(hactive+hfrontporch+hsyncpulse))
         hs = hsyncpolarity;
      else
         hs = ~hsyncpolarity;
      if (vcont>=(vactive+vfrontporch) && vcont<(vactive+vfrontporch+vsyncpulse))
         vs = vsyncpolarity;
      else
         vs = ~vsyncpolarity;
    end

   always @* begin
      if (active_area) begin
         gout = gin;
         rout = rin;
         bout = bin;
      end
      else begin
         gout = 3'h00;
         rout = 3'h00;
         bout = 2'h00;
      end
   end
endmodule   

Который создается вашим модулем vga_driver, который становится не чем иным, как оболочкой для этого модуля:

module vga_driver (
  input wire clk_25MHz,
  output wire vs_vga,
  output wire hs_vga,
  output wire [2:0] RED,
  output wire [2:0] GREEN,
  output wire [1:0] BLUE,
  output wire HBLANK,
  output wire VBLANK,
  output [9:0] CURX,
  output [8:0] CURY,
  input [7:0] COLOR,
  input wire RESET
  );

  assign HBLANK = 0;
  assign VBLANK = 0;

  videosyncs syncgen (
     .clk(clk_25MHz),
     .rin(COLOR[7:5]),
     .gin(COLOR[4:2]),
     .bin(COLOR[1:0]),

     .rout(RED),
     .gout(GREEN),
     .bout(BLUE),

     .hs(hs_vga),
     .vs(vs_vga),

     .hc(CURX),
     .vc(CURY)
   );
endmodule

Обратите внимание, что в map_generator первый оператор if в этом always блоке никогда не будет истинным. Мы можем забыть об этом, так как модуль дисплея VGA будет гасить выходы RGB при необходимости.

  always @(posedge clk_vga) begin
    if(HBlank || VBlank) begin //
      mColor <= 0;             // Never reached
    end                        //
    else begin                 //
      if(currentMap == 4'b0000) begin
        mColor[7:0] <= startCastle[7:0];
      end
      //Add more maps later
    end
  end

Используя тот же подход, я преобразовал модуль генератора карт в комбинационный модуль. Например, для карты 0 (замок - я вижу без замка -) это выглядит так:

module StartCastle(
  input wire [9:0] CurrentX,
  input wire [8:0] CurrentY,
  output wire [7:0] mapData
  );

  reg [7:0] mColor;
  assign mapData = mColor;

  always @* begin
    if(CurrentY < 40) begin
      mColor[7:0] <= 8'b11100000;
    end
    else if(CurrentX < 40) begin
      mColor[7:0] <= 8'b11100000;
    end
    else if(~(CurrentX < 600)) begin
      mColor[7:0] <= 8'b11100000;
    end
    else if((~(CurrentY < 440) && (CurrentX < 260)) || (~(CurrentY < 440) && ~(CurrentX < 380))) begin
      mColor[7:0] <= 8'b11100000;
    end else
      mColor[7:0] <= 8'b00011100;       
  end
endmodule

Просто конечный автомат, цвет которого соответствует цвету пикселя. Входными данными являются координаты текущего пикселя.

Поэтому, когда пришло время отображать карту 0, map_generator просто переключается на нее на основе текущего значения currentMap.

module map_generator (
  input wire clk,
  input wire reset,
  input wire [9:0]CurrentX,
  input wire [8:0]CurrentY,
  input wire HBlank,
  input wire VBlank,
  input wire [9:0]playerPosX,
  input wire [8:0]playerPosY,
  output wire [7:0]mapData
  );

  reg [7:0] mColor;
  assign mapData = mColor;

  reg [5:0]currentMap = 0;

  wire [7:0] castle_map;
  StartCastle StartCastle(
    .CurrentX(CurrentX),
    .CurrentY(CurrentY),
    .mapData(castle_map)
  );

  always @(posedge clk) begin
    if(reset) begin
      currentMap <= 0;
    end
  end

  always @* begin
    if(currentMap == 6'b000000) begin
      mColor = castle_map;
    end
      //Add more maps later
  end
endmodule

Это может выглядеть так, как будто генерируется много логики гребенки, и поэтому могут возникать сбои. На самом деле это очень быстро, без заметных сбоев на экране, и вы можете использовать фактические текущие координаты x и y, чтобы выбрать, что отображать на экране. Таким образом, нет необходимости в перевернутых часах. Моя последняя версия вашего дизайна имеет только одну частоту 25 МГц.

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

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

module adventure (
  input clk_vga,
  input reset,
  output vs_vga,
  output hs_vga,
  output [2:0] RED,
  output [2:0] GREEN,
  output [1:0] BLUE
  );

  wire HBLANK, VBLANK;
  wire [7:0] COLOR;
  wire [9:0] CURX;
  wire [8:0] CURY;
  wire [9:0] playerPosX = 10'd320;  // no actually used in the design yet
  wire [8:0] playerPosY = 9'd240;   // no actually used in the design yet

  vga_driver the_screen (.clk_25MHz(clk_vga), 
                         .vs_vga(vs_vga), 
                         .hs_vga(hs_vga), 
                         .RED(RED), 
                         .GREEN(GREEN), 
                         .BLUE(BLUE), 
                         .HBLANK(HBLANK), 
                         .VBLANK(VBLANK), 
                         .CURX(CURX), 
                         .CURY(CURY), 
                         .COLOR(COLOR)
                         );
  map_generator the_mapper (.clk(clk_vga), 
                            .reset(reset), 
                            .CurrentX(CURX), 
                            .CurrentY(CURY), 
                            .HBlank(HBLANK), 
                            .VBlank(VBLANK), 
                            .playerPosX(playerPosX), 
                            .playerPosY(playerPosY), 
                            .mapData(COLOR)
                            );
endmodule

Этот модуль не завершен: в нем отсутствуют входы с джойстика или любого другого устройства ввода для обновления текущего положения игрока. На данный момент текущая позиция игрока фиксирована.

Дизайн верхнего уровня (TLD) написан исключительно для имеющегося у вас тренера FPGA. Именно здесь вам нужно сгенерировать правильные часы, используя доступные ресурсы вашего устройства, такие как DCM в устройствах Spartan 3 / 3E.

module tld_basys(
  input wire clk_50MHz,
  input wire RESET,
  output wire vs_vga,
  output wire hs_vga,
  output wire [2:0] RED,
  output wire [2:0] GREEN,
  output wire [1:0] BLUE
  );

  wire clk_25MHz;  

  dcm_clocks gen_vga_clock (
                            .CLKIN_IN(clk_50MHz), 
                            .CLKDV_OUT(clk_25MHz)
                           );

  adventure the_game (.clk_vga(clk_25MHz), 
                      .reset(RESET), 
                      .vs_vga(vs_vga), 
                      .hs_vga(hs_vga), 
                      .RED(RED), 
                      .GREEN(GREEN), 
                      .BLUE(BLUE)
                      );
endmodule

Часы, сгенерированные DCM, входят в этот модуль (генерируются Xilinx Core Generator).

module dcm_clocks (CLKIN_IN, 
             CLKDV_OUT
             );

   input CLKIN_IN;
   output CLKDV_OUT;

   wire CLKFB_IN;
   wire CLKFX_BUF;
   wire CLKDV_BUF;
   wire CLKIN_IBUFG;
   wire CLK0_BUF;
   wire GND_BIT;

   assign GND_BIT = 0;
   BUFG  CLKDV_BUFG_INST (.I(CLKDV_BUF), 
                         .O(CLKDV_OUT));                         
   IBUFG  CLKIN_IBUFG_INST (.I(CLKIN_IN), 
                           .O(CLKIN_IBUFG));
   BUFG  CLK0_BUFG_INST (.I(CLK0_BUF), 
                        .O(CLKFB_IN));
   DCM_SP #(.CLKDV_DIVIDE(2.0), .CLKIN_DIVIDE_BY_2("FALSE"), 
         .CLKIN_PERIOD(20.000), .CLKOUT_PHASE_SHIFT("NONE"), 
         .DESKEW_ADJUST("SYSTEM_SYNCHRONOUS"), .DFS_FREQUENCY_MODE("LOW"), 
         .DLL_FREQUENCY_MODE("LOW"), .DUTY_CYCLE_CORRECTION("TRUE"), 
         .FACTORY_JF(16'hC080), .PHASE_SHIFT(0), .STARTUP_WAIT("FALSE") ) 
         DCM_SP_INST (.CLKFB(CLKFB_IN), 
                       .CLKIN(CLKIN_IBUFG), 
                       .DSSEN(GND_BIT), 
                       .PSCLK(GND_BIT), 
                       .PSEN(GND_BIT), 
                       .PSINCDEC(GND_BIT), 
                       .RST(GND_BIT), 
                       .CLKDV(CLKDV_BUF), 
                       .CLKFX(), 
                       .CLKFX180(), 
                       .CLK0(CLK0_BUF), 
                       .CLK2X(), 
                       .CLK2X180(), 
                       .CLK90(), 
                       .CLK180(), 
                       .CLK270(), 
                       .LOCKED(), 
                       .PSDONE(), 
                       .STATUS());
endmodule

Хотя (для устройств Xilinx) безопасно использовать простой делитель времени, как и вы. Если вы опасаетесь, что синтезатор не будет рассматривать ваши разделенные часы как фактические часы, добавьте примитив BUFG для маршрутизации вывода от делителя в глобальный буфер, чтобы его можно было без проблем использовать в качестве часов (см. Модуль выше для пример того, как это сделать).

В заключение, вы можете захотеть добавить больше независимости от конечного устройства, используя 24-битные цвета для вашей графики. В TLD вы будете использовать фактическое количество бит на компонент цвета, которое у вас действительно есть, но если вы перейдете от Basys2 с 8-битной платой для обучения цветов к, скажем, плате Nexys4 с 12-битным цветом, вы автоматически наслаждайтесь более богатым отображением на выходе.

Теперь это выглядит так (слева нет вертикальных полос, а цвета кажутся более яркими)

Другой вывод Basys с использованием другого модуля дисплея VGA

person mcleod_ideafix    schedule 25.04.2015
comment
Я попробовал ваш модуль видеосинхронизации, и, похоже, он по большей части решил проблему. Я позаботился о перемещении игрока в верхнем модуле. Все динамические объекты рисуются в верхнем модуле, тогда как все статические объекты рисуются генератором карты. Я все еще относительно новичок в Verilog, и на самом деле я делаю это для своего последнего проекта в моем классе цифровой логики, поэтому это может показаться исправительным вопросом, но: В чем разница между @ * и @ (posedge clk)? - person Dilancuan; 27.04.2015
comment
@* используется для определения блока, в котором комбинационная логика описывается с помощью поведенческих операторов. @(posedge clk) используется для определения блока, в котором описывается последовательная логика, опять же с использованием поведенческих операторов. @* - это краткая форма @(*), которая, в свою очередь, является краткой формой @(.... sensitivity list...... ) - person mcleod_ideafix; 27.04.2015