[置顶] 基于FPGA的VGA简易显存设计&NIOS ii软核接入

项目简介

本项目基于Altera公司的Cyclone IV型芯片,利用NIOS II软核,2-port RAM与时序控制模块,实现64*48分辨率的显存(再大的显存板载资源m9k不够用)

实现效果如下:

这里写图片描述

VGA时序控制模块

VGA时序简介

网络上针对VGA时序的讲解已经非常多了,简单的理解,VGA主要有H_syncV_sync这两个坐标同步信号,与RGB这三个色彩信号。当H_syncV_sync达到特定的值的时候,对应一个特别的坐标(x,y)上的颜色为RGBVGA上的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_cnty_cnt中得知当前对应的xy的坐标的值:

    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;

利用iVerilogVGA基本时序进行仿真,并利用GTKwave查看仿真结果:

可以通过仿真结果对VGA时序做进一步的理解。

VGA显存模块

上面已经分析了最基本的VGA时序,对应一个VGA显示模块来说,需要做的,就是在对应的display_xdisplay_y,通过RGB线输出该像素的颜色信息。

若想通过NIOS II核控制VGA显示内容,需要一个显存来保存整个屏幕的显示信息。但是对于1024*768的屏幕,需要的显存太大,无法在板子上实现,这里退而求其次,选择64*48的一个显存。并选择Altera2-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 = 0
f.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 + 1
f.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将显存的地址传入,再通过WRITEDATA16位的颜色数据传入,最后给WREN1即可完成一个像素的写入。通过循环对每个点进行书写便可完成一帧的画面的写入。

显示字符驱动

下面通过对一个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 / 1ps
module 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     
    end
endmodule

附: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++);
}

转载于:https://www.cnblogs.com/he11o-liu/p/7503227.html

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值