前两篇教程讲了通过USB控制鼠标和键盘的方法,顺便挖了个VGA的大坑,找了很久都没找到VGA转HDMI的转接器,好在在其中一个显示器后面发现了VGA接口。用VGA画一个大·害怕

FPGA基础入门【14】开发板VGA视频输出控制

  • VGA基础
    • CRT显示器
    • VGA控制与时序
  • 逻辑设计
    • IP配置
    • 代码设计
  • 模拟仿真
    • Testbench
    • 仿真脚本
    • 仿真结果
  • 编译烧写
  • 总结

VGA基础

在NEXYS 4开发板文档中写着,该板用14个引脚控制VGA,RGB红绿蓝三色各用4位数,加上HS - Horizontal Sync横向同步和VS - Vertical Sync纵向同步两个引脚。接口形式和连接如下,接口不对称用来防呆


CRT显示器

VGA是为老式的CRT阴极射线显示器使用,核心是加热的阴极发射电子(电视机开过后面会发热,过去熊孩子偷看电视必须知道的常识),经过格栅聚焦成电子束,经过偏转电场偏转后被高压加速,打在铺好三色荧光粉的显示屏上发光,其结构如下

尽管现在的LCD液晶显示屏和CRT本质上是不一样的,但VGA的逻辑时序是一样的

屏幕上的扫描分行和列,一般有240到1200行,320到1600列,行列相乘是像素数,每个像素对应RGB各4位即12位,因此逻辑设计时最好使用一个比较深的RAM。

VGA控制与时序

VGA控制电路需要生成有特定周期的HS和VS信号,用来校准偏转电场。VS决定了刷新率一般在50Hz到120Hz,而HS的周期则是扫完一整行用的时间。如下图所示,HS每个周期输出一次低电平脉冲,除了线性扫过一行的有效时间,还要留出偏转电场回到初始位置的时间retrace time,在低电平脉冲的前后都有一段

这个回复时间是根据实际实验观察测得的,因此基于不同的像素配置和刷新率,有相对固定的像素时钟和HS/VS配置。比如文档中推荐的640x480像素60Hz刷新率,用的像素时钟是25MHz,可以比较方便的用100MHz系统时钟生成。如果需要其他的配置,可以到这个网站找找:VGA_timing

NEXYS 4文档中还推荐了HS和VS的生成逻辑,这个逻辑可以用来作为RAM的读取地址

实际上NEXYS 4文档里对VGA时序的介绍还是不够细致,这个网页里对HS和VS的关系介绍更清晰:VGA timing

其中一张时序图可以更清楚的看出HS和VS之间的时序关系,对此我们要对文档中给出的逻辑结构做出一点改进,HS拉高的时机从0改到800-48=752,拉低的时机从3.84us也就是96改到800-48-96=656

逻辑设计

这篇教程计划使用一张480x640@60Hz的图片显示,使用开发板推荐的时序配置。

理论上来说,这样的图片显示需要一个宽度为12位,深度为480640=307200的RAM存储器,但从横向计数器和纵向计数器的结构中看,用来给纵向计数器计数的时间是HS为高电平的时间,这里面包括了偏转电场的回复时间。我们可以设计一些额外的逻辑来专门排除回复时间,但这样会增加逻辑复杂度,并且还要给RGB输出也加上输出选择,不能直接用RAM的输出,因此决定这里浪费一些RAM空间,在相应空间填入0。这样设计使用的深度为521(800-96)=366784

IP配置

新建vivado工程,名为vga,配置选择开发板NEXYS 4,到IP catalog中搜索RAM,双击下面这个

在Xilinx FPGA中,有几种存储方式,一种是外接存储设备,比如SPI flash和DDR,这些在前面的教程中介绍过使用方式;一种是BRAM块存储器,这个是芯片内部集成的存储器,每块可以储存36Kb或者18Kb,Xilinx最近有新的叫做URAM的内置存储器,每块有288Kb,比较类似;另一种是LUTRAM,LUT是FPGA逻辑配置的基础——查找表,有些查找表的存储可以被改成一个小的存储器,大小是128bit。

存储器的选择要看存储数据的深度和宽度,一般来说宽而浅的,不常变化且需要经常读取的,使用LUTRAM;稍微深且窄一点,需要持续读取的,使用BRAM;非常深,并且窄则使用外部存储器。VGA需要的是图片数据,一般都在MB量级,使用外部存储器是最好的选择,但这里为了展示RAM IP的配置方法,同时也是图方便,使用了BRAM(自然会用好多个)

IP的配置如下



第三张图的配置中出现了初始化配置,由于我们只显示图片,不打算输出视频,就把这个RAM当做ROM使用,一开始图片的信息就用这个.coe文件配置。

coe文件包含两部分,第一行是数据格式,是十六进制、十进制或者其他;第二行以后是数据具体内容。示例如下:

memory_initialization_radix=16;
memory_initialization_vector=3f, 1f;

从时序图中看,头31行为空,尾10行为空,并且尾64列为空

这里打算用MATLAB(如果没有license可以搜索Octave,相当于一个免费版本的MATLAB,大概功能都有)来生成coe文件。首先随便下载一张图片保存为pic.jpg,配置好MATLAB路径到图片的根目录后,使用如下脚本:

pic = imread('fear.jpg');
subplot(1,2,1);
imshow(pic);
pkg load image;
pic = imresize(pic, [480 640]);
pic = uint8(floor(double(pic)/16));
subplot(1,2,2);
imshow(pic*16);pic = double(pic);
pic = pic(:,:,1)*256 + pic(:,:,2)*16 + pic(:,:,3);
data = zeros([521,704]);
data(30:509,1:640) = pic;
data = reshape(data', [521*704 1]);
fileID = fopen('picture.coe','w');
fprintf(fileID,'memory_initialization_radix=10;\n');
fprintf(fileID,'memory_initialization_vector=\n');
fprintf(fileID,'%d,\n',data(1:end-1));
fprintf(fileID,'%d;\n',data(end));
fclose(fileID);

可以把fear.jpg改成下载的图片名称,最终生成一个picture.coe文件

JPEG图片都是8位RGB,这里要降低到4位,所以看起来会比较失真,另外所有图片都会被转为480x640,原本不是这个比例的图片都会变形:

最后Vivado的IP调用coe文件时,因为文件比较长,需要等待一段时间,最终生成IP Pic_RAM

代码设计

代码vga.v如下:
引脚定义,除了时钟和复位,就是VGA的14根引脚

module vga(input             clk,input             rst,// VGA portoutput reg [3:0]  VGA_R,output reg [3:0]  VGA_G,output reg [3:0]  VGA_B,output reg        VGA_HS,output reg        VGA_VS
);

资源定义

reg        clk_pixel;
reg        clk_count;
(* dont_touch = "true" *)reg  [9:0]  hor_count;
(* dont_touch = "true" *)reg         hor_zero_detect;
(* dont_touch = "true" *)reg         hor_3_84_us_detect;
(* dont_touch = "true" *)reg         hor_effective;
reg  [18:0]  ver_count;
(* dont_touch = "true" *)reg         ver_zero_detect;
(* dont_touch = "true" *)reg         ver_64_us_detect;
(* dont_touch = "true" *)reg         ver_effective;

用100MHz放慢四倍形成25MHz

// generation of 25MHz pixel clock
always @(posedge clk or posedge rst) beginif(rst) beginclk_pixel <= 1'b0;clk_count <= 1'b0;endelse beginclk_count <= ~clk_count;if(clk_count) beginclk_pixel <= ~clk_pixel;endend
end

横向扫描计数,以及HS信号控制

// horizontal counter up to 800
// horizontal counter zero detect and 3.84us detect
always @(posedge clk_pixel or posedge rst) beginif(rst) beginhor_count          <= 10'd0;hor_zero_detect    <= 1'b0;hor_3_84_us_detect <= 1'b0;endelse beginhor_count          <= (hor_count < 10'd799) ? hor_count + 10'd1 : 10'd0;hor_zero_detect    <= (hor_count == 10'd655) ? 1'b1 : 1'b0;hor_3_84_us_detect <= (hor_count == 10'd751) ? 1'b1 : 1'b0;end
end// HS control
always @(posedge clk_pixel or posedge rst) beginif(rst) beginVGA_HS <= 1'b1;endelse if(hor_zero_detect) beginVGA_HS <= 1'b0;endelse if(hor_3_84_us_detect) beginVGA_HS <= 1'b1;end
end

纵向计数以及VS信号控制,计数最高到521*704,同时用来作为RAM的地址

// vertical counter up to 521
// vertical counter zero detect and 64us detect
always @(posedge clk_pixel or posedge rst) beginif(rst) beginver_count <= 19'd0;endelse if(VGA_HS && (ver_count < 19'd366783)) beginver_count <= ver_count + 19'd1;endelse if(VGA_HS) beginver_count <= 19'd0;end
endalways @(posedge clk_pixel or posedge rst) beginif(rst) beginver_zero_detect <= 1'b0;endelse if(ver_count == 19'd0) beginver_zero_detect <= 1'b1;endelse beginver_zero_detect <= 1'b0;end
endalways @(posedge clk_pixel or posedge rst) beginif(rst) beginver_64_us_detect <= 1'b0;endelse if(ver_count == 19'd1408) beginver_64_us_detect <= 1'b1;endelse beginver_64_us_detect <= 1'b0;end
end// VS control
always @(posedge clk_pixel or posedge rst) beginif(rst) beginVGA_VS <= 1'b1;endelse if(ver_zero_detect) beginVGA_VS <= 1'b0;endelse if(ver_64_us_detect) beginVGA_VS <= 1'b1;end
end

存储部分,IP定义在前面有提到,随着地址而输出RGB信号

// RAM part and output
wire [11:0] dout;
Pic_RAM Pic_RAM (.clka(clk),         // input wire clka.ena(1'b1),         // input wire ena.wea(1'b0),         // input wire [0 : 0] wea.addra(ver_count),  // input wire [18 : 0] addra.dina(12'd0),       // input wire [11 : 0] dina.douta(dout)        // output wire [11 : 0] douta
);always @(posedge clk or posedge rst) beginif(rst) beginVGA_R <= 4'h0;VGA_G <= 4'h0;VGA_B <= 4'h0;endelse beginVGA_R <= dout[11: 8];VGA_G <= dout[ 7: 4];VGA_B <= dout[ 3: 0];end
endendmodule

模拟仿真

Testbench

Testbench不需要输入时钟和复位以后的信号,下面是源代码tb_vga.v:

`timescale 1ns/1nsmodule tb_vga;reg         clock;
reg         reset;
wire [3:0]  VGA_R;
wire [3:0]  VGA_G;
wire [3:0]  VGA_B;
wire        VGA_HS;
wire        VGA_VS;initial beginclock = 1'b0;reset = 1'b0;// Reset for 1us#100 reset = 1'b1;#1000reset = 1'b0;end// Generate 100MHz clock signal
always #5 clock <= ~clock;vga vga_top(.clk     (clock),.rst     (reset),// VGA port.VGA_R   (VGA_R),.VGA_G   (VGA_G),.VGA_B   (VGA_B),.VGA_HS  (VGA_HS),.VGA_VS  (VGA_VS)
);endmodule

仿真脚本

写一个仿真脚本sim.do,需要注意调用Xilinx的库文件和IP生成的netlist仿真文件,并且VS的周期大约是16.7ms,因此跑20ms需要一段时间,要耐心等待:

vlib work
vlog ../src/vga.v ../src/Pic_RAM_sim_netlist.v ./tb_vga.v ./glbl.v
vsim -L xpm -L secureip -L unisims_ver -L unimacro_ver -L unifast_ver -L simprims_ver work.tb_vga work.glbl -voptargs=+acc +notimingchecks
log -depth 7 /tb_vga/*
do wave.do
run 20ms

仿真结果

此教程有调用Xilinx的IP,因此记得先建立Xilinx的库文件:

vmap xpm C:/Users/[account]/xilinx_lib/xpm
vmap secureip C:/Users/[account]/xilinx_lib/secureip
vmap unisims_ver C:/Users/[account]/xilinx_lib/unisims_ver
vmap unimacro_ver C:/Users/[account]/xilinx_lib/unimacro_ver
vmap unifast_ver C:/Users/[account]/xilinx_lib/unifast_ver
vmap simprims_ver C:/Users/[account]/xilinx_lib/simprims_ver

具体操作可以参考这篇教程里的库文件调用部分:FPGA基础入门【8】开发板外部存储器SPI flash访问

在开始仿真之前记得把modelsim.ini文件拷贝到仿真根目录,开启ModelSim改变路径到根路径后,调用脚本do sim.do,加入相应信号后,结果如下:

可以看出VS的一个周期是16.672ms,在偏转电场回复好后就可以输出有效视频信息

编译烧写

Vivado的工程文件已经在前面新建好了,并且配置好了IP Pic_RAM,现在加入vga.v,由NEXYS4 官方约束文件修改出需要的引脚配置文件vga.xdc:

## This file is a general .xdc for the Nexys4 DDR Rev. C
## To use it in a project:
## - uncomment the lines corresponding to used pins
## - rename the used ports (in each line, after get_ports) according to the top level signal names in the project## Clock signal
set_property -dict {PACKAGE_PIN E3 IOSTANDARD LVCMOS33} [get_ports clk]
create_clock -period 10.000 -name sys_clk_pin -waveform {0.000 5.000} -add [get_ports clk]##Switchesset_property -dict {PACKAGE_PIN J15 IOSTANDARD LVCMOS33} [get_ports rst]##VGA Connectorset_property -dict {PACKAGE_PIN A3 IOSTANDARD LVCMOS33} [get_ports {VGA_R[0]}]
set_property -dict {PACKAGE_PIN B4 IOSTANDARD LVCMOS33} [get_ports {VGA_R[1]}]
set_property -dict {PACKAGE_PIN C5 IOSTANDARD LVCMOS33} [get_ports {VGA_R[2]}]
set_property -dict {PACKAGE_PIN A4 IOSTANDARD LVCMOS33} [get_ports {VGA_R[3]}]set_property -dict {PACKAGE_PIN C6 IOSTANDARD LVCMOS33} [get_ports {VGA_G[0]}]
set_property -dict {PACKAGE_PIN A5 IOSTANDARD LVCMOS33} [get_ports {VGA_G[1]}]
set_property -dict {PACKAGE_PIN B6 IOSTANDARD LVCMOS33} [get_ports {VGA_G[2]}]
set_property -dict {PACKAGE_PIN A6 IOSTANDARD LVCMOS33} [get_ports {VGA_G[3]}]set_property -dict {PACKAGE_PIN B7 IOSTANDARD LVCMOS33} [get_ports {VGA_B[0]}]
set_property -dict {PACKAGE_PIN C7 IOSTANDARD LVCMOS33} [get_ports {VGA_B[1]}]
set_property -dict {PACKAGE_PIN D7 IOSTANDARD LVCMOS33} [get_ports {VGA_B[2]}]
set_property -dict {PACKAGE_PIN D8 IOSTANDARD LVCMOS33} [get_ports {VGA_B[3]}]set_property -dict {PACKAGE_PIN B11 IOSTANDARD LVCMOS33} [get_ports VGA_HS]
set_property -dict {PACKAGE_PIN B12 IOSTANDARD LVCMOS33} [get_ports VGA_VS]

一切准备好以后开始编译综合生成bitstream,用USB线连上FPGA开发板,并烧写,再用VGA线连接上支持VGA的显示器,或者用一个VGA转HDMI的转换器接到任意一个显示器。如果你想的话,搬出一个老电视机也可以。

打开显示器,确保它读取的是VGA接口,你就可以看到我看到的真·害怕画面:

有点失真,但是。。。更害怕了呢!

总结

这是一个很害怕的教程,嘿嘿嘿。开发板上有Pmod通用接口,但是我没有可以和它相连的外接设备;有模数转换的设备,但我没有示波器和信号生成器,也没法做。只好挖个MicroSD卡存取的坑了

FPGA基础入门【14】开发板VGA视频输出控制相关推荐

  1. FPGA基础入门【12】开发板USB鼠标控制

    上一篇教程介绍了NEXYS4 开发板中UART串口通信的使用方式,这一篇介绍USB接口接收鼠标和键盘信号 FPGA基础入门[12]开发板USB鼠标控制 开发板USB芯片 信号时序图 鼠标初始化 逻辑设 ...

  2. FPGA基础入门【8】开发板外部存储器SPI flash访问

    前两篇教程利用数码管project介绍了chipscope和各种烧写开发板的方式,这篇开始继续探索开发板,这次关注外置存储器的控制,外置指的是芯片外部,不是开发板外部.板子上的外置存储器有DDR2和S ...

  3. FPGA基础入门【1】Vivado官方免费版安装

    本人自本科大二开始接触FPGA相关知识,现已将近五年,从这篇开始将从比较基础的角度讲述如何一步步了解FPGA.我相信动手一步步做下去是从零开始学习知识的最快方法,因此不会从最基础开始讲,而是在碰到相应 ...

  4. FPGA基础入门【3】Blink逻辑及仿真

    从这一篇开始正式介绍FPGA中的硬件逻辑,第一个目标就是从零开始在NEXYS 4开发板上实现闪烁LED. 软件编程中hello world是初学语言中实现的第一个功能,而硬件编程中blink是同等的地 ...

  5. FPGA基础入门【6】ChipScope的使用

    当FPGA设计中复杂度慢慢变高的时候,仿真的手段也要增加,目前我们仿真的手段都是在ModelSim中配置相应的testbench,给模块发送需要的信号.这种软件仿真的方式有几个缺点: 一个是软件仿真速 ...

  6. Tang Nano FPGA(35元开发板).初探

    ​Lichee Tang Nano 基于高云小蜜蜂系列GW1N-1 FPGA的简约型开发板.该芯片搭载了1K LUT4的逻辑资源,1 PLL和4 Block RAM,开发板引出了所有I/O接口,适用于 ...

  7. 一期完结《一篇文章让你从HTML零基础入门前端开发》12.28

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VRcXH9FM-1672214813897)(./assets/%E7%8E%84%E5%AD%90Shaer-%E4% ...

  8. 【视频回放与课件】零基础入门AI开发

    今天上午,受广州图书馆邀请,在第一讲<零代码上手人工智能>的基础上,以<零基础入门AI开发>为主题,分四步解锁人工智能学习的概念与开发工具,让您在一小时内轻松掌握人工智能开发要 ...

  9. FPGA基础入门篇(四) 边沿检测电路

    FPGA基础入门篇(四)--边沿检测电路 一.边沿检测 边沿检测,就是检测输入信号,或者FPGA内部逻辑信号的跳变,即上升沿或者下降沿的检测.在检测到所需要的边沿后产生一个高电平的脉冲.这在FPGA电 ...

  10. 零基础学习MSP430F552LP开发板,学习前期准备,Code Composer Studio(CCS)软件的安装

    零基础学习MSP430F552LP开发板 一.前言 零基础学习MSP430F552LP开发板,为电子设计竞赛做准备以及学好这一款芯片. 在选择比赛题目时,发现有的题目时规定使用ti的芯片作为控制MCU ...

最新文章

  1. QT各种版本第三方下载地址
  2. mongodb的id的唯一性_探讨MongoDB的_id字段含义,及对MongoDB数据库的重要性
  3. java反序列化漏洞 tomcat_CVE-2020-9484 Apache Tomcat反序列化漏洞浅析
  4. kotlin学习笔记——Kotlin Android Extensions
  5. 从Linux到Meego
  6. 【Java】接口(interface)VS抽象类
  7. 利用Hexo搭建个人博客-博客发布篇
  8. Redis Lock
  9. TIOBE 年度榜单揭晓:C 语言夺冠,Python 紧随其后
  10. C++编程语言中sizeof和strlen介绍
  11. checkbox是否被选中
  12. php 微信公众号跳转小程序,PHP微信公共号H5页面跳转小程序。
  13. 计算机系统文件夹图标不见了,为何我的电脑文件夹的图标都没有了
  14. ESP32C3 CORE+PIO+lvgl显示
  15. 服务器inetpub是什么文件夹,inetpub是什么文件夹?Win10怎么删除c盘下的inetpub文件夹?...
  16. 【修理】电脑维修 显卡错误(错误代码:43)
  17. 适用于编程开发自学的学习网站
  18. python 通信部分
  19. dowhile实现求水仙花数
  20. 花旗银行java面试_花旗金融—面经(已offer)

热门文章

  1. swing宾馆客房管理系统(文档)
  2. Python 爬取4K美女图片
  3. excel自动排班表_Excel通用值班表日历版,排班人员自动显示,万年历套用
  4. PHP实现讯飞语音转写demo
  5. Android ADB 环境变量配置
  6. linux自动微信发信息,Linux下发送微信消息
  7. python解决写入文件乱码问题
  8. echarts柱状图参数详解
  9. SSM项目实战:学生学籍管理系统
  10. Windows搭建kms服务器