我的verilog VGA驱动程序导致屏幕闪烁(Basys2)

时间:2015-04-25 04:49:01

标签: verilog fpga system-verilog xilinx vga

我试图在Verilog中重建冒险(1979),到目前为止,我已完成角色移动,碰撞和地图生成。在我将地图分成模块之前,它没有闪现那么多,现在它不断地闪烁。当我查看这个问题时,我发现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

这里是startCastle:

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

1 个答案:

答案 0 :(得分:1)

我发布此答案是因为我无法在评论中添加照片。

这是你的设计的样子吗? VGA output from the OP design

我唯一的猜测是,在vga_driver和/或map_generator实例化期间(如果使用旧样式实例化),您可能错放了一些端口。不过,我要检查VGA时间,因为我可以在屏幕左侧看到一条奇怪的垂直线,好像可以看到hblank间隔。

顺便说一下:我改变了生成显示的方式。您可以将regs用于HS,VS等,并在下一个时钟周期更新。我将显示生成视为FSM,因此输出来自由计数器的某些值(或值范围)触发的组合块。此外,我启动水平和垂直计数器,因此在屏幕的像素坐标中测量的位置(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

只是一个FSM,其输出是像素中的颜色。输入是当前像素的坐标。

因此,当显示地图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坐标来选择屏幕上显示的内容。因此,不需要倒置时钟。我的设计的最终版本只有一个25MHz的时钟。

顺便说一句,您希望保持设备相关的结构远离您的设计,将时钟发生器等设置在单独的模块中,这些模块将连接到顶层模块中的设计,这应该是唯一依赖设备的模块。

所以,我写了一个与设备无关的冒险模块,它将包含整个游戏:

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培训师编写。在这里,您需要使用设备的可用资源生成适当的时钟,例如Spartan 3 / 3E设备中的DCM。

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核心生成器生成)

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中,您将使用您真正拥有的每个颜色分量的实际位数,但如果您从具有8位颜色训练板的Basys2移动到具有12位颜色的Nexys4板,您将自动享受更丰富的输出显示。

现在,它看起来像这样(左边没有竖条,颜色看起来更有活力)

Another output from Basys using a different VGA display module