基于FPGA的VGA简易显存设计&NIOS ii软核接入
来源:互联网 发布:网络吃土什么意思啊 编辑:程序博客网 时间:2024/05/21 14:59
项目简介
本项目基于Altera
公司的Cyclone IV
型芯片,利用NIOS II
软核,2-port RAM
与时序控制模块,实现64*48
分辨率的显存(再大的显存板载资源m9k
不够用)
实现效果如下:
VGA时序控制模块
VGA时序简介
网络上针对VGA
时序的讲解已经非常多了,简单的理解,VGA
主要有H_sync
和V_sync
这两个坐标同步信号,与RGB
这三个色彩信号。当H_sync
与V_sync
达到特定的值的时候,对应一个特别的坐标(x,y)
上的颜色为RGB
。VGA
上的RGB
信号是模拟信号,FPGA
通常通过接入一个DA
模块来实现数字信号到模拟信号的转换。我所用的板子上便是16位
的RGB数模转换的解决方案。
HSYNC的时序如下
VSYNC的时序如下
针对图中的不同段的长度,可以参考以下常用的参数配置表:
在我的IP核中,通过参数定义以增加核心的通用性:
//1024*758 60HZ parameter H_SyncPulse=136; //HSYNC_A parameter H_BackPorch=160; //HSYNC_B parameter H_ActivePix=1024; //HSYNC_C parameter H_FrontPorch=24; //HSYNC_D parameter LinePeriod =1344; //HSYNC_E parameter Hde_start=H_SyncPulse+H_BackPorch; //HSYNC_A+HSYNC_B parameter Hde_end=Hde_start+H_ActivePix; //HSYNC_A+HSYNC_B+HSYNC_C //1024*758 60HZ parameter V_SyncPulse=6; //VSYNC_O parameter V_BackPorch=29; //VSYNC_P parameter V_ActivePix=768; //VSYNC_Q parameter V_FrontPorch=3; //VSYNC_R parameter FramePeriod =806; //VSYNC_S parameter Vde_start=V_BackPorch+V_SyncPulse; parameter Vde_end=Vde_start+V_ActivePix+V_FrontPorch;
由于板载时钟信号是一个50MHZ
,利用PLL
超频至65MHZ
,接入板子时钟:
pllvga mypll(.inclk0(clk),.c0(vga_clk),.areset(~rstn),.locked());
根据上面所述VGA
的基本时序,可以书写VGA
的时序模块:
always @ (posedge vga_clk) if(~rstn) x_cnt <= 1; else if(x_cnt == LinePeriod) x_cnt <= 1; else x_cnt <= x_cnt+ 1; always @ (posedge vga_clk) begin if(~rstn) hsync_r <= 1'b1; else if(x_cnt == 1) hsync_r <= 1'b0; else if(x_cnt == H_SyncPulse) hsync_r <= 1'b1; if(~rstn) hsync_de <= 1'b0; else if(x_cnt == Hde_start) hsync_de <= 1'b1; else if(x_cnt == Hde_end) hsync_de <= 1'b0; end always @ (posedge vga_clk) if(~rstn) y_cnt <= 1; else if(y_cnt == FramePeriod) y_cnt <= 1; else if(x_cnt == LinePeriod) y_cnt <= y_cnt+1; always @ (posedge vga_clk) begin if(~rstn) vsync_r <= 1'b1; else if(y_cnt == 1) vsync_r <= 1'b0; else if(y_cnt == V_SyncPulse) vsync_r <= 1'b1; if(~rstn) vsync_de <= 1'b0; else if(y_cnt == Vde_start) vsync_de <= 1'b1; else if(y_cnt == Vde_end) vsync_de <= 1'b0; end
并且可以从x_cnt
与y_cnt
中得知当前对应的x
与y
的坐标的值:
assign display_x = x_cnt>Hde_start?x_cnt-Hde_start:12'd0; assign display_y = y_cnt>Vde_start?y_cnt-Vde_start:12'd0;
利用iVerilog
对VGA
基本时序进行仿真,并利用GTKwave
查看仿真结果:
可以通过仿真结果对VGA
时序做进一步的理解。
VGA显存模块
上面已经分析了最基本的VGA
时序,对应一个VGA
显示模块来说,需要做的,就是在对应的display_x
与display_y
,通过RGB
线输出该像素的颜色信息。
若想通过NIOS II
核控制VGA
显示内容,需要一个显存来保存整个屏幕的显示信息。但是对于1024*768
的屏幕,需要的显存太大,无法在板子上实现,这里退而求其次,选择64*48
的一个显存。并选择Altera
的2-port Memory
实现这一个显存。选择存储深度为4096
,字为16bit
。
注意在Altera
的存储器中,选择对于正在赋值的块若同时读取,直接输出老的数据。
至于为何选择64*48
的显存,是因为这样更好做内存的映射:
reg[11:0] addrx; reg[11:0] addry; always @ (posedge vga_clk) begin if(~rstn) readaddr = 0; else if(hsync_de & vsync_de) begin addrx = display_x >> 4; addry = display_y >> 4; addry = addry << 6; readaddr = addrx + addry; end else begin readaddr = 0; addrx = 12'd0; addry = 12'd0; end end
对于(x,y)
,每个坐标均向右移4位,纵轴坐标再*64 => <<6
,即可得到该像素点对应显存的地址。
读出的内容,通过color_buff
缓冲后输出即可。
assign vga_r = (hsync_de & vsync_de)?vga_r_reg:5'b00000; assign vga_g = (hsync_de & vsync_de)?vga_g_reg:6'b000000; assign vga_b = (hsync_de & vsync_de)?vga_b_reg:5'b00000; always @(negedge vga_clk) begin color_buff<=readdata; end always @(negedge vga_clk) if(~rstn) begin vga_r_reg<=0; vga_g_reg<=0; vga_b_reg<=0; end else begin vga_r_reg<=color_buff[15:11]; vga_g_reg<=color_buff[10:5]; vga_b_reg<=color_buff[4:0]; end
测试模块
单独测试模块选择自己初始化一个mif
进行读取测试:
利用python
书写生成测试填充显存的mif
f = open("test.mif",'w')address = 0f.write("WIDTH=16;\nDEPTH=4096;\nADDRESS_RADIX=HEX;\nDATA_RADIX=HEX;\nCONTENT BEGIN\n")for i in range(64): for j in range(64): if(i < 48): if( i % 2 == 1 and j %2 == 1): f.write("%x:ffff;\n"%(address)) else: f.write("%x:0000;\n"%(address)) else: f.write("%x:0000;\n"%(address)) address = address + 1f.write("END;\n")f.close()
NIOSii软核接入
在通过了基本的测试模块之后,就可以接入NIOS ii
软核进行写入显存测试了。这里配置软核非常非常简单,只需要留出PIO
口,接入VGA的IP核即可,完成后的顶层图如下:
图中还有留有进行查错的口子没有删除,对最后工程没有影响。
NIOSii软核显示驱动
驱动大体思路
针对我设计的IP核,驱动非常好写,以图片驱动的代码作为示例:
void print_img() { int i, j; int addr, data; for (i = 0; i < 48; i++) { for (j = 0; j < 64; j++) { IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 0); addr = i * 64 + j; data = gImage_test[addr * 2] << 8 & 0xff00; data |= gImage_test[addr * 2 + 1]; IOWR_ALTERA_AVALON_PIO_DATA(WRITEADDR_BASE, addr); IOWR_ALTERA_AVALON_PIO_DATA(WRITEDATA_BASE, data); IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 1); } }}
先通过WRITEADDR
将显存的地址传入,再通过WRITEDATA
将16
位的颜色数据传入,最后给WREN
置1
即可完成一个像素的写入。通过循环对每个点进行书写便可完成一帧的画面的写入。
显示字符驱动
下面通过对一个C语言的ascii显示库调用完成字符显示驱动
void display_ascii(unsigned int x, unsigned int y,unsigned int w_color, unsigned int b_color) { unsigned int i, j,k; unsigned char str; unsigned int OffSet; unsigned int addr; for(k = 0;lcd_buff[k]!='\0';k++){ OffSet = lcd_buff[k] * 11; printf("%c",lcd_buff[k]); for (i = 0; i < 11; i++) { str = word_lib[OffSet + i]; for (j = 0; j < 8; j++) { addr = (x + j)+k*8 + (y + i) * 64; IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 0); IOWR_ALTERA_AVALON_PIO_DATA(WRITEADDR_BASE, addr); if (str & 0x80) { IOWR_ALTERA_AVALON_PIO_DATA(WRITEDATA_BASE, w_color); } else { IOWR_ALTERA_AVALON_PIO_DATA(WRITEDATA_BASE, b_color); } IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 1); str <<= 1; } } }}
总结
- 这次的显存非常小,只能显示很少的内容,但是基本设计思路是相同的。
Altera
提供了Framebuffer
,可以缓存多帧数据,还没有研究是利用什么资源进行存储的。
附:VGA模块完整代码
`timescale 1ns / 1psmodule vga_test(clk,rstn,vga_hs,vga_vs,vga_r,vga_g,vga_b,readaddr,writeaddr,writedata,wren); input clk; input rstn; input [11:0]writeaddr; input [15:0]writedata; input wren; output vga_hs; output vga_vs; output [4:0] vga_r; output [5:0] vga_g; output [4:0] vga_b; output reg [11:0] readaddr; //1024*758 60HZ parameter H_SyncPulse=136; //HSYNC_A parameter H_BackPorch=160; //HSYNC_B parameter H_ActivePix=1024; //HSYNC_C parameter H_FrontPorch=24; //HSYNC_D parameter LinePeriod =1344; //HSYNC_E parameter Hde_start=H_SyncPulse+H_BackPorch; //HSYNC_A+HSYNC_B parameter Hde_end=Hde_start+H_ActivePix; //HSYNC_A+HSYNC_B+HSYNC_C //1024*758 60HZ parameter V_SyncPulse=6; //VSYNC_O parameter V_BackPorch=29; //VSYNC_P parameter V_ActivePix=768; //VSYNC_Q parameter V_FrontPorch=3; //VSYNC_R parameter FramePeriod =806; //VSYNC_S parameter Vde_start=V_BackPorch+V_SyncPulse; parameter Vde_end=Vde_start+V_ActivePix+V_FrontPorch; reg[10 : 0] x_cnt; reg[9 : 0] y_cnt; reg[15 : 0] color_buff; reg[4 : 0] vga_r_reg; reg[5 : 0] vga_g_reg; reg[4 : 0] vga_b_reg; reg hsync_r; reg vsync_r; reg hsync_de; reg vsync_de; wire[15:0] readdata; wire[31:0] display_x; wire[31:0] display_y; wire vga_clk; assign vga_hs = hsync_r; assign vga_vs = vsync_r; assign vga_r = (hsync_de & vsync_de)?vga_r_reg:5'b00000; assign vga_g = (hsync_de & vsync_de)?vga_g_reg:6'b000000; assign vga_b = (hsync_de & vsync_de)?vga_b_reg:5'b00000; pllvga mypll(.inclk0(clk),.c0(vga_clk),.areset(~rstn),.locked()); assign display_x = x_cnt>Hde_start?x_cnt-Hde_start:12'd0; assign display_y = y_cnt>Vde_start?y_cnt-Vde_start:12'd0; vga_ram vga_buff(.data(writedata),.inclock(vga_clk),.outclock(vga_clk),.rdaddress(readaddr),.wraddress(writeaddr),.wren(wren),.q(readdata)); //vga_rom vga_rom(.address(readaddr),.clock(vga_clk),.q(readdata)); always @ (posedge vga_clk) if(~rstn) x_cnt <= 1; else if(x_cnt == LinePeriod) x_cnt <= 1; else x_cnt <= x_cnt+ 1; always @ (posedge vga_clk) begin if(~rstn) hsync_r <= 1'b1; else if(x_cnt == 1) hsync_r <= 1'b0; else if(x_cnt == H_SyncPulse) hsync_r <= 1'b1; if(~rstn) hsync_de <= 1'b0; else if(x_cnt == Hde_start) hsync_de <= 1'b1; else if(x_cnt == Hde_end) hsync_de <= 1'b0; end always @ (posedge vga_clk) if(~rstn) y_cnt <= 1; else if(y_cnt == FramePeriod) y_cnt <= 1; else if(x_cnt == LinePeriod) y_cnt <= y_cnt+1; always @ (posedge vga_clk) begin if(~rstn) vsync_r <= 1'b1; else if(y_cnt == 1) vsync_r <= 1'b0; else if(y_cnt == V_SyncPulse) vsync_r <= 1'b1; if(~rstn) vsync_de <= 1'b0; else if(y_cnt == Vde_start) vsync_de <= 1'b1; else if(y_cnt == Vde_end) vsync_de <= 1'b0; end always @(negedge vga_clk) begin color_buff<=readdata; end always @(negedge vga_clk) if(~rstn) begin vga_r_reg<=0; vga_g_reg<=0; vga_b_reg<=0; end else begin vga_r_reg<=color_buff[15:11]; vga_g_reg<=color_buff[10:5]; vga_b_reg<=color_buff[4:0]; end reg[11:0] addrx; reg[11:0] addry; always @ (posedge vga_clk) begin if(~rstn) readaddr = 0; else if(hsync_de & vsync_de) begin addrx = display_x >> 4; addry = display_y >> 4; addry = addry << 6; readaddr = addrx + addry; end else begin readaddr = 0; addrx = 12'd0; addry = 12'd0; end endendmodule
附:Nios ii完整代码
#include <stdio.h>#include "system.h"#include "altera_avalon_pio_regs.h"#include "img.h"#include "ascii_lib.h"unsigned char lcd_buff[256];void display_ascii(unsigned int x, unsigned int y,unsigned int w_color, unsigned int b_color);void clean_screen();void print_img();void shine();void delay_ms(unsigned int i);int main() { clean_screen(); shine(); delay_ms(2000); clean_screen(); print_img(); delay_ms(2000); clean_screen(); sprintf((char*)lcd_buff,"Hello"); display_ascii(0,0,0xffff,0); sprintf((char*)lcd_buff,"World"); display_ascii(0,11,0xffff,0); sprintf((char*)lcd_buff,"VGATest"); display_ascii(0,22,0xffff,0); delay_ms(2000); return 0;}void shine() { int data, addr; int i = 0; int j = 0; int k = 0; int color = 0x238f; for (k = 0; k < 100; k++) { for (i = 0; i < 48; i++) { for (j = 0; j < 64; j++) { IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 0); addr = i * 64 + j; if (i % 2 && j % 2) data = color; else data = 0x0; IOWR_ALTERA_AVALON_PIO_DATA(WRITEADDR_BASE, addr); IOWR_ALTERA_AVALON_PIO_DATA(WRITEDATA_BASE, data); IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 1); } } color += 0x10; }}void print_img() { int i, j; int addr, data; for (i = 0; i < 48; i++) { for (j = 0; j < 64; j++) { IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 0); addr = i * 64 + j; data = gImage_test[addr * 2] << 8 & 0xff00; data |= gImage_test[addr * 2 + 1]; IOWR_ALTERA_AVALON_PIO_DATA(WRITEADDR_BASE, addr); IOWR_ALTERA_AVALON_PIO_DATA(WRITEDATA_BASE, data); IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 1); } }}void display_ascii(unsigned int x, unsigned int y,unsigned int w_color, unsigned int b_color) { unsigned int i, j,k; unsigned char str; unsigned int OffSet; unsigned int addr; for(k = 0;lcd_buff[k]!='\0';k++){ OffSet = lcd_buff[k] * 11; printf("%c",lcd_buff[k]); for (i = 0; i < 11; i++) { str = word_lib[OffSet + i]; for (j = 0; j < 8; j++) { addr = (x + j)+k*8 + (y + i) * 64; IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 0); IOWR_ALTERA_AVALON_PIO_DATA(WRITEADDR_BASE, addr); if (str & 0x80) { IOWR_ALTERA_AVALON_PIO_DATA(WRITEDATA_BASE, w_color); } else { IOWR_ALTERA_AVALON_PIO_DATA(WRITEDATA_BASE, b_color); } IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 1); str <<= 1; } } }}void clean_screen(){ int data, addr; int i = 0; int j = 0; int color = 0x0000; for (i = 0; i < 48; i++) { for (j = 0; j < 64; j++) { IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 0); addr = i * 64 + j; data = 0x0; IOWR_ALTERA_AVALON_PIO_DATA(WRITEADDR_BASE, addr); IOWR_ALTERA_AVALON_PIO_DATA(WRITEDATA_BASE, data); IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 1); } } color += 0x10;}void delay_ms(unsigned int i) { unsigned int j, k; for (j = 0; j < i; j++) for (k = 0; k < 1000; k++);}
- 基于FPGA的VGA简易显存设计&NIOS ii软核接入
- [FPGA]基于Qsys的Nios II流水灯系统设计
- [FPGA]基于Qsys的第一个Nios II系统设计
- 基于nios II的verilog VGA字符显示控制
- Fpga的vga显示设计
- 关于FPGA的VGA简易操作
- 关于FPGA软核的一些总结(microblaze && NIOS II)
- helloworld:基于Qsys的第一个Nios II系统设计
- 基于fpga的vga图片显示
- 基于FPGA的VGA视频弹球游戏
- 基于FPGA的VGA彩条发生器
- Fpga的vga显示设计(一)
- 基于NIOS II的PWM实现
- 基于NIOS II的流水灯
- 基于Nios II的DMA传输
- NIOS II软核处理器
- 基于NIOS II处理器的面阵CCD采集系统设计
- FPGA 硬件设计---VGA设计
- 使用RMS API 自定义Office(Word、Excel、PPT)加密策略
- json字符串转换成json数组并遍历属性值
- CSS文本属性
- 2736 FunctionTemplate(eden)
- |poj 1226|后缀数组|二分|Substrings
- 基于FPGA的VGA简易显存设计&NIOS ii软核接入
- 构造器
- w3svc服务启动 不了,错误 1068:依赖服务或组件无法启动
- windows下 mongo副本集配置 一主一从一仲裁
- 2738 Queue with Template(eden)
- poj 3461
- 简单记载一下,windows和linux 的清屏命令。
- 数据库连接池
- mysql之存储过程全面解析