本文描述如何实现一个以60px / sec初始方向斜向下45°移动的方块(碰到边界后根据反射原理反向移动)。方块颜色由8个开关控制,方块大小为32px * 24px。
1 基本步骤:
编写主体代码:
1编写扫描整个屏幕的模块;
2编写根据vsync信号和坐标边界判断当前矩阵有效范围坐标,移动方向的模块;
3 编写根据当前所扫描坐标和矩阵坐标范围确定对应颜色的模块。
查看综合电路图,确定是否符合设计逻辑。
编写接口约束。将输入输出变量指定到开发板的端口。
编写测试代码,进行模拟测试.模拟测试可以观察波形图来判断设计是否正确。
将程序写入开发板运行,检查运行结果。
2 基本原理:
1)VGA引脚图
一般常用引脚就是行同步信号,列同步信号,和8个RGB引脚。实验中也只用到了VGA的这几个引脚。RGB引脚控制颜色信号是0 ~0.7v 。 0v就是全黑。0.7V就是全亮,代码中二进制1为高电平,0为低电平。
HS为水平有效信号,VS为垂直有效信号。在建立阶段垂直信号有2个时钟,水平信号有96个时钟,这个时段对应有效信号要设为0。之后要设为1。
2)VGA时序逻辑:
可以看出水平扫描和垂直扫描周期中各个阶段要花费的时钟周期。
可以得知水平信号是和25MHz时钟同步计数的,垂直信号是用水平信号的周期计数的。
之后发现这张表有不对的地方,就是Ts开始的地方应该是在下表所示的位置。
3) VGA counter 的电路逻辑:
3 源代码
.v
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: // // Create Date: 21:08:50 12/07/2014 // Design Name: // Module Name: VGA // Project Name: // Target Devices: // Tool versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// module VGA( input clk, rst, input [7:0] rect_color, output reg hsync,vsync, output reg [7:0] color ); reg [9:0] hgrid = 0; // 800 TS , x reg [9:0] vgrid = 0; // 521 Ts , y reg clk_4fp = 0; reg [1:0] count = 0; //分频 25M Hz always @ (posedge clk or posedge rst) begin if(rst) count <= 0; else count <= count + 1; end always @ (posedge clk or posedge rst) begin if(rst) clk_4fp <= 0; else begin if (count[1] == 1) clk_4fp <= 1; else clk_4fp <= 0; end end // 扫描整个屏幕,包括非显示区,确定什么时候 always @ (posedge clk_4fp or posedge rst) begin if(rst) begin hgrid <= 0; vgrid <= 0; end else begin //根据basic VGA controller的电路图,在水平方向扫描一次后,使得垂直方向开始扫描 // 因为水平方向是时钟计数的,垂直方向是根据水平方向的脉冲计数的 if(hgrid >= 800) begin hgrid <= 0; vgrid <= (vgrid >= 521? 0 : vgrid + 1'b1); end else hgrid <= hgrid + 1'b1; end end //设置行选,列选信号有效。 由于有建立的Tpw时间,所以要把Tpw(脉冲宽度)时间段内的坐标视为无效 always @(posedge clk_4fp or posedge rst) begin if(rst) begin hsync <= 0; vsync <= 0; end else begin if(hgrid < 752 &&hgrid >= 656) // 脉冲内为0 (800 - Tbp -Tpw) ~ (800 - Tbp) hsync <= 0; else hsync <= 1; if(vgrid < 492 && vgrid >= 490 ) // 脉冲内为0 (521 - Tbp -Tpw) ~ (521 - Tbp) vsync <= 0; else vsync <= 1; end end //////////////////////////////////////////// // 显示移动矩形 parameter WIDTH = 32, //矩形长 HEIGHT = 24, //矩形宽 // 显示区域的边界 DISV_TOP = 10'd480, // display top bound DISV_DOWN =10'd0, // display down bound DISH_LEFT = 10'd0, // display left bound DISH_RIGHT = 10'd640; // display right bound //初始矩形的位置,在显示区的左下角 reg [9:0] topbound = DISV_DOWN + HEIGHT; reg [9:0] downbound = DISV_DOWN ; reg [9:0] leftbound = DISH_LEFT ; reg [9:0] rightbound = DISH_LEFT + WIDTH ; //初始方向为东南方向 reg [1:0] movexy = 2'b11; /* 根据时间选择不同范围坐标的像素显示颜色,使其成为一个移动的矩形。 由于是60/s, vsync的Ts恰好是移动1px所花的时间,所以用vsync信号的上升沿判断 */ //确立每一个像素时钟里矩形的坐标范围 always @ (posedge vsync or posedge rst) begin if(rst) begin topbound = DISV_DOWN + HEIGHT ; downbound = DISV_DOWN; leftbound = DISH_LEFT; rightbound = DISH_LEFT + WIDTH ; movexy = 2'b11; end else begin //碰到边界,改变方向 case(movexy[1:0]) 2'b11: begin // 东南 if (topbound == DISV_TOP && rightbound < DISH_RIGHT ) movexy = 2'b10; else if (topbound < DISV_TOP && rightbound == DISH_RIGHT ) movexy = 2'b01; else if (topbound == DISV_TOP && rightbound == DISH_RIGHT ) movexy = 2'b00; end 2'b10: begin // 东北 if (downbound == DISV_DOWN&& rightbound < DISH_RIGHT ) movexy = 2'b11; else if (downbound > DISV_DOWN && rightbound == DISH_RIGHT ) movexy = 2'b00; else if (downbound == DISV_DOWN && rightbound == DISH_RIGHT ) movexy = 2'b01; end 2'b00: begin // 西北 if (downbound == DISV_DOWN && leftbound > DISH_LEFT ) movexy = 2'b01; else if (downbound > DISV_DOWN && leftbound == DISH_LEFT ) movexy = 2'b10; else if (downbound == DISV_DOWN && leftbound == DISH_LEFT ) movexy = 2'b11; end 2'b01: begin // 西南 if (topbound == DISV_TOP && leftbound > DISH_LEFT ) movexy = 2'b00; else if (topbound < DISV_TOP && leftbound == DISH_LEFT ) movexy = 2'b11; else if (topbound == DISV_TOP && leftbound == DISH_LEFT ) movexy = 2'b10; end default: movexy = 2'b11; endcase topbound <= topbound + ( movexy[0]? 1 : -1 ); downbound <= downbound + ( movexy[0]? 1 : -1 ); leftbound <= leftbound + ( movexy[1]? 1 : -1 ); rightbound <= rightbound + ( movexy[1]? 1 : -1 ); end end // 确定扫描到哪一个像素该显示什么颜色 always @(posedge clk_4fp or posedge rst) begin if(rst) color <= 8'b0000_0000; else begin if (hgrid >= DISH_LEFT && hgrid <= DISH_RIGHT && vgrid >= DISV_DOWN && vgrid <= DISV_TOP) begin if(hgrid >= leftbound && hgrid <= rightbound && vgrid >= downbound && vgrid <= topbound) color <= rect_color; else color <= 8'b0000_0011; //黑色 end else begin color <= 8'b0000_0000; end end end endmodule
.ucf
NET "clk" LOC = "V10"; NET "rst" CLOCK_DEDICATED_ROUTE = FALSE; NET "color[7]" LOC = "U7"; NET "color[6]" LOC = "V7"; NET "color[5]" LOC = "N7"; NET "color[4]" LOC = "P8"; NET "color[3]" LOC = "T6"; NET "color[2]" LOC = "V6"; NET "color[1]" LOC = "R7"; NET "color[0]" LOC = "T7"; NET "rect_color[7]" LOC = "T5"; NET "rect_color[6]" LOC = "V8"; NET "rect_color[5]" LOC = "U8"; NET "rect_color[4]" LOC = "N8"; NET "rect_color[3]" LOC = "M8"; NET "rect_color[2]" LOC = "V9"; NET "rect_color[1]" LOC = "T9"; NET "rect_color[0]" LOC = "T10"; NET "hsync" LOC = "N6"; NET "vsync" LOC = "P7";
4 后记
在调试过程中出现的显示区域边界与显示屏边界不能重合的问题,这些主要就是这四个参数怎么计算的问题:
DISV_TOP = 10'd480, // display top bound
DISV_DOWN =10'd0, // display down bound
DISH_LEFT = 10'd0, // display left bound
DISH_RIGHT = 10'd640; // display right bound
这四个参数最开始是依据下面的图和参数来算的,可是发现不对。按照这样算的话有效显示的计数坐标范围是(144-784, 31-511),之后发现这样计算显示就会有问题。方块可以正常移动反弹,就是边界显示有问题,于是就不断去调整显示区的边界坐标,发现是和pdf说明文档中另一幅图吻合的。真是个坑。
正确的TS应该是以下这幅图中“Total Horizon time”这一段,也就是说有效显示区的计数范围是(0-640 ,0-480)的。然后hsync和vsync为0的阶段也是Tpw阶段,之前根据上面那幅图Tpw阶段是(0-96,0-2),运行的时候会出现方块颜色为渐变色的bug,不过根据下面这幅图计算,计数坐标的范围就是(656-752, 490-492)为Tpw阶段,这样就正常了。