Menu Close

HDMI FPGA 工程设计4(vga_show.v 模块)

vga_show.v 模块是集合了所有的信息, 将VESA 视频数据推出FPGA,送入adv7511 芯片 ,最终实现hdmi 图像在显示器上的显示。这个模块集合了按键信息,h,v,de,vga_clk 信息, 通过发送rom 地址, 得到rom IP 里的lotus(莲花图像)信息。 最终,根据VESA 标准,输出图像数据。

vga_show.v 代码:

`timescale 1ns/1ps
module vga_show #(
    parameter   h_pix = 1920,
    parameter   v_pix = 1080
)
(
    input                   vga_clk,

    input                   h_sync,
    input                   v_sync,
    input                   hv_de,

    input                   center_flag,
    input                   up_flag,
    input                   down_flag,
    input                   left_flag,
    input                   right_flag,   

    input           [23:0]  rd_rom_data,
    output  reg     [16:0]  rd_rom_addr,

    output                  hdmi_hs,
    output                  hdmi_vs,
    output                  hdmi_en,
    output          [23:0]  hdmi_d,

    input                   rst_n
);

//==========================
reg     up = 0;
reg     down = 0;
reg     left = 0;
reg     right = 0;
reg     center = 0;

always @ (posedge vga_clk)
begin
    up <= up_flag;    
    down <= down_flag;  
    left <= left_flag;  
    right <= right_flag; 
    center <= center_flag;
end

//==========================
reg         [10:0]      h_pix_cnt;      //行像素
reg         [10:0]      v_line_cnt;     //要显示的行计数

always @ (posedge vga_clk)
if(!rst_n)
begin
    h_pix_cnt <= 0;
    v_line_cnt <= 1;
end
else
begin
    if(hv_de)
        h_pix_cnt <= h_pix_cnt + 1;      //0-1920
    else
        h_pix_cnt <= 0;
        
    if(v_line_cnt > v_pix)
        v_line_cnt <= 1;
    else if(h_pix_cnt == h_pix)         //0-1920
        v_line_cnt <= v_line_cnt + 1;
end

reg         [10:0]      h_pix_cnt_r, h_pix_cnt_rr;            //行像素 1-1920
reg         [10:0]      v_line_cnt_r, v_line_cnt_rr;        //要显示的行计数 1-1080

always @ (posedge vga_clk)
begin
    h_pix_cnt_r <= h_pix_cnt;
    v_line_cnt_r <= v_line_cnt;

    h_pix_cnt_rr <= h_pix_cnt_r;
    v_line_cnt_rr <= v_line_cnt_r;
end

//=======================================
reg     [10:0]  up_bor    = 11'd406,
                down_bor  = 11'd675,
                left_bor  = 11'd721,
                right_bor = 11'd1200;
always @ (posedge vga_clk)                  //控制上下左边界值
begin
    if(up)
    begin
        if(up_bor == 1)
        begin
            up_bor <= 11'd811;
            down_bor <= 11'd1080;
        end
        else
        begin
            up_bor <= up_bor -11'd135;
            down_bor <= down_bor - 11'd135;
        end
    end

    if(down)
    begin
        if(down_bor == 11'd1080)
        begin
            up_bor <= 11'd1;
            down_bor <= 11'd270;
        end
        else
        begin
            up_bor <= up_bor + 11'd135;
            down_bor <= down_bor + 11'd135;
        end
    end

    if(left)
    begin
        if(left_bor == 1)
        begin
            left_bor <= 11'd1441;
            right_bor <= 11'd1920;
        end
        else
        begin
            left_bor <= left_bor - 11'd240;
            right_bor <= right_bor - 11'd240;
        end
    end

    if(right)
    begin
        if(right_bor == 1920)
        begin
            left_bor <= 11'd1;
            right_bor <= 11'd480;
        end
        else
        begin
            left_bor <= left_bor + 11'd240;
            right_bor <= right_bor + 11'd240;
        end
    end

    if(center)
    begin
        up_bor <= 11'd406;    
        down_bor <= 11'd675;  
        left_bor <= 11'd721;  
        right_bor <= 11'd1200;
    end
end
//======================================
wire    rd_en;

assign rd_en = ((v_line_cnt_rr >= up_bor && v_line_cnt_rr <= down_bor) &&
                (h_pix_cnt_rr >= left_bor && h_pix_cnt_rr <= right_bor));

always @ (posedge vga_clk)
if(!rst_n || hdmi_vs)
    rd_rom_addr <= 0;
else if(rd_en)
begin
    if(rd_rom_addr < 129599 ) // 480 * 270 - 1
        rd_rom_addr <= rd_rom_addr + 1;
    else
        rd_rom_addr <= 0;
end
else
    rd_rom_addr <= rd_rom_addr;

reg     rd_en_r = 0;
always @ (posedge vga_clk)
    rd_en_r <= rd_en;
    

reg [23:0] hdmi_d_r = 0;  
always @ (posedge vga_clk)
    hdmi_d_r <= rd_en_r ? rd_rom_data : 24'h0;
    
//assign hdmi_d = rd_en_r ? rd_rom_data : 24'd0;
assign hdmi_d = hdmi_d_r;

//======================================
reg     [3:0]   hdmi_hs_r;
reg     [3:0]   hdmi_vs_r;
reg     [3:0]   hdmi_en_r;

always @ (posedge vga_clk)
begin
    hdmi_hs_r <= {hdmi_hs_r[2:0], h_sync };
    hdmi_vs_r <= {hdmi_vs_r[2:0], v_sync };
    hdmi_en_r <= {hdmi_en_r[2:0], hv_de };
end

assign hdmi_hs = hdmi_hs_r[3];
assign hdmi_vs = hdmi_vs_r[3];
assign hdmi_en = hdmi_en_r[3];

endmodule

代码解析:

reg up = 0;
reg down = 0;
reg left = 0;
reg right = 0;
reg center = 0;

always @ (posedge vga_clk)
begin
    up <= up_flag; 
    down <= down_flag; 
    left <= left_flag; 
    right <= right_flag; 
    center <= center_flag;
end

将按键输入的信息(按键滤波后形成的单沿时钟)所存(vga_clk 时钟域),供本模块其他部分使用。

 

reg [10:0] h_pix_cnt; //行像素
reg [10:0] v_line_cnt; //要显示的行计数

always @ (posedge vga_clk)
if(!rst_n)
begin
    h_pix_cnt <= 0;
    v_line_cnt <= 1;
end
else
begin
    if(hv_de)
        h_pix_cnt <= h_pix_cnt + 1; //0-1920
    else
        h_pix_cnt <= 0;

    if(v_line_cnt > v_pix)
        v_line_cnt <= 1;
    else if(h_pix_cnt == h_pix) //0-1920
        v_line_cnt <= v_line_cnt + 1;
end

实现两个标尺, 一个是水平方向的输出像素 标尺; 一个垂直方向输出像素行标尺。

reg [10:0] h_pix_cnt_r, h_pix_cnt_rr; //行像素 1-1920
reg [10:0] v_line_cnt_r, v_line_cnt_rr; //要显示的行计数 1-1080

always @ (posedge vga_clk)
begin
    h_pix_cnt_r <= h_pix_cnt;
    v_line_cnt_r <= v_line_cnt;

    h_pix_cnt_rr <= h_pix_cnt_r;
    v_line_cnt_rr <= v_line_cnt_r;
end

将当前得到的水平方向标尺, 垂直方向标尺所存。

reg [10:0] up_bor = 11'd406,
                   down_bor = 11'd675,
                   left_bor = 11'd721,
                   right_bor = 11'd1200;


always @ (posedge vga_clk) //控制上下左边界值
begin
    if(up)
    begin
        if(up_bor == 1)
        begin
            up_bor <= 11'd811;
            down_bor <= 11'd1080;
        end
        else
        begin
            up_bor <= up_bor -11'd135;
            down_bor <= down_bor - 11'd135;
        end
    end

    if(down)
    begin
        if(down_bor == 11'd1080)
        begin
            up_bor <= 11'd1;
            down_bor <= 11'd270;
        end
        else
        begin
            up_bor <= up_bor + 11'd135;
            down_bor <= down_bor + 11'd135;
        end
    end

    if(left)
    begin
        if(left_bor == 1)
        begin
            left_bor <= 11'd1441;
            right_bor <= 11'd1920;
        end
        else
        begin
            left_bor <= left_bor - 11'd240;
            right_bor <= right_bor - 11'd240;
        end
    end

    if(right)
    begin
        if(right_bor == 1920)
        begin
            left_bor <= 11'd1;
            right_bor <= 11'd480;
        end
        else
        begin
            left_bor <= left_bor + 11'd240;
            right_bor <= right_bor + 11'd240;
        end
    end

    if(center)
    begin
        up_bor <= 11'd406; 
        down_bor <= 11'd675; 
        left_bor <= 11'd721; 
        right_bor <= 11'd1200;
    end
end

初始化莲花图像的位置(屏幕的正中心 1920 x 1080),然后根据按键信息, 将莲花图像上下,左右移动, 当移动到边界的时候, 回到边界对应的相反方向(例如:横向移动到1920时, 如果再继续按下右移键, 图像将出现的1坐标的位置)。

wire rd_en;

assign rd_en = ((v_line_cnt_rr >= up_bor && v_line_cnt_rr <= down_bor) &&
(h_pix_cnt_rr >= left_bor && h_pix_cnt_rr <= right_bor));

圈出莲花图像在1920 x 1080 图像中出现的位置。

always @ (posedge vga_clk)
if(!rst_n || hdmi_vs)
    rd_rom_addr <= 0;
else if(rd_en)
begin
    if(rd_rom_addr < 129599 ) // 480 * 270 - 1
        rd_rom_addr <= rd_rom_addr + 1;
    else
        rd_rom_addr <= 0;
end
else
    rd_rom_addr <= rd_rom_addr;

根据rd_en ,读取rom IP 中的莲花数据,当一次没有读完所有的莲花数据, 等待下一行到来时,继续读, 知道所有的莲花数据被读出为止。当使用V 信号到来,作为rom IP 的重新开始。 这样, 就可以保证,即使一帧数据有错误, 也不会影响其他帧的数据显示输出了。

reg rd_en_r = 0;
always @ (posedge vga_clk)
    rd_en_r <= rd_en;


reg [23:0] hdmi_d_r = 0; 
always @ (posedge vga_clk)
    hdmi_d_r <= rd_en_r ? rd_rom_data : 24'h0;

//assign hdmi_d = rd_en_r ? rd_rom_data : 24'd0;
assign hdmi_d = hdmi_d_r;

reg [3:0] hdmi_hs_r;
reg [3:0] hdmi_vs_r;
reg [3:0] hdmi_en_r;

always @ (posedge vga_clk)
begin
    hdmi_hs_r <= {hdmi_hs_r[2:0], h_sync };
    hdmi_vs_r <= {hdmi_vs_r[2:0], v_sync };
    hdmi_en_r <= {hdmi_en_r[2:0], hv_de };
end

assign hdmi_hs = hdmi_hs_r[3];
assign hdmi_vs = hdmi_vs_r[3];
assign hdmi_en = hdmi_en_r[3];

由于rom IP 读出数据,需要延迟一个时钟周期, 所以,所有的VESA 输出,都是等待rom IP (h,v,de 等信号同步)。

Posted in FPGA, FPGA, Verilog, Verilog, Verilog语言编程与FPGA, 教材与教案, 文章

发表评论

相关链接