Altera SOPC FrameBuffer系统设计教程

 

Altera SOPC FrameBuffer系统设计教程

小梅哥编写,未经授权,严禁转载或用于任何商业用途

在嵌入式系统中,LCD屏作为最友好的人机交互方式,被大量的应用到了各个系统中。在基于ARM处理器的系统中,应用更是非常广泛。FPGA作为广义嵌入式系统的一员,自然也有很多时候需要来驱动显示屏显示一些内容,例如经常有需求要用FPGA来做液晶测试架,做显示器驱动测试卡。很多学习了FPGA的朋友都知道,FPGA驱动VGA显示器是比较轻松的,几乎每个板卡商提供的资料中都提供了有诸如显示彩条,显示图案,甚至显示简单的文字。但是,当希望显示更加复杂的内容的时候,往往就很难办到了。因为FPGA设计的是电路,是硬件,特点是头脑简单,四肢发达。而显示复杂的内容,恰恰需要一个头脑发达的控制器来执行这项任务。

在FPGA这些年的发展中,先后经历了简单逻辑扩展、复杂逻辑设计、SOPC系统和SOC系统,逻辑设计部分就不多说了,对于SOPC系统设计,Xilinx家有MicroBlaze软核、Altera家有大名鼎鼎的NIOS II软核处理器。而到了SOC时代,Xilinx家和Altera(现已被因特尔收购,成为Intel的可编程事业部)都推出了内嵌双核ARM Cortex-A9的SOC芯片,Xilinx家著名的zynq7000系列,以及Altera花了大力气在高校推广的Cyclone V SOC。

虽然随着技术的发展以及时间的推移,SOC的应用会越来越广泛,但是作为我们一般学习和使用来说,SOPC技术也还是有其使用的价值的,毕竟内嵌硬核的SOC芯片,动辄几百块,而使用软核的方案,只要40元不到的BOM成本就能完成一个系统设计,优势还是很明显的。本例,我们就带领大家使用Altera Cyclone IV系列最低端的FPGA芯片EP4CE6/EP4CE10设计的能够驱动640*480分辨率显示屏动态显示复杂内容的系统。例如显示文字,显示图片

在之前我们讲解过RGB显示屏的驱动,RGB显示屏驱动时序和VGA驱动时序几乎完全一样,只是不同的分辨率之间,其时序参数不同。说到这个驱动480*272分辨率显示屏动态显示复杂内容,相信很多朋友并不陌生,是的,在我们之前的SOPC公开课时候,就已经给大家讲解过实现这种系统的一种方式,即使用了一片独立的SRAM来作为显示屏的显存,显示驱动模块直接读取SRAM中的数据并实时刷新显示,而NIOS II CPU则只在需要刷新显示内容的时候,才执行对SRAM的写入操作。此种方式,需要一片独立的SRAM作为显存,以及一片SDRAM作为NIOS II的运行内存。除了SRAM的使用会增加系统的BOM成本外,SRAM也会占用掉FPGA芯片较多的管脚,导致必须得使用256脚BGA方案的芯片才能够实现。因此,此种方案仅适合在对成本不太敏感高的场合使用。本节要介绍的方案,是对该系统改进优化得到的。

本节所描述的系统,仅需用到以下硬件资源

1. EP4CE6/EP4CE10型FPGA芯片一片(30元)

2. 16Mbit 或以上SDRAM芯片一片(3元,如华邦w9816g6)

3. 50M有源晶振一片(3元)

4. 4Mbit SPI FLASH芯片(1.5元,如W25Q80)

5. 3.3V、2.5V、1.2V LDO稳压电源(AMS117系列,合计不到1元)

本节希望达到的目的:

在显示屏上显示一帧图像,并在指定位置显示字符内容

 

图片1

 

上述框架是我们经过分析,要实现本系统方案所需的最小硬件系统,本节我们将基于芯航线的AC620学习套件来完成FPGA的系统搭建和NIOS II软件设计。芯航线的AC620 FPGA开发板整体硬件配置高于上述分析,有助于我们进行原型验证。

FPGA系统搭建

本系统主要在Qsys系统中完成,包含了以下IP核:

  • clk_0:输入时钟管理单元,系统默认添加
  • altpll_0:PLL锁相环,对输入时钟进行分频和倍频,以得到两路频率为100MHz,相位相差180度的时钟信号,分别提供给系统中逻辑电路工作(包含NIOS II软核处理器)和SDRAM芯片使用。以及1路24M的时钟信号,供640*480分辨率的VGA驱动电路使用。
  • NIOS II CPU:实现系统的控制以及显示内容的处理。
  • SDRAM:NIOS II CPU运行内存和TFT显示图像帧缓存。
  • onchip_memory:片上存储器,指定SGDMA要执行的数据传输,主要用作SGDMA的描述符存储器。
  • lcd_sgdma:SGDMA IP,主要实现大量数据的高效搬运,支持流模式,效率比Avalon MM接口的DMA核高。
  • timing_adapter:时序匹配IP核,主要用于流数据在两个不同模块间进行传递时,对部分信号加上一定延迟,使得数据和标志信号能够完全同步(说的简单点儿,就是用寄存器打拍的方式对一些信号进行延迟,使数据和控制信号对齐)。
  • fifo:双时钟fifo,主要完成数据流的传输速度转换。SGDMA输出的数据流,是按照和存储器相同的时钟速度(本例中为100M)进行的,而在数据使用端,即VGA显示部分,是按照9M的时钟取用数据的,因此使用双时钟fifo,解决跨时钟域的问题。
  • VGA_SINK:该IP为第三方开源IP,由友晶科技提供,主要完成Avalon ST流数据转换成VGA行场时序并驱动VGA屏进行显示。
  • EPCS:EPCS FLASH存储器控制IP,使用该IP,使得EPCS芯片能够存储FPGA固件和NIOS II的程序。
  • jtag_uart0:调试串口,主要用来打印一些调试信息,在设计的前期调试中很有用

 

图片2

 

接下来,将详细讲解本系统的搭建过程。

1、 altpll_0

为了保证整个系统能够具有较高的性能,因此让其运行在100MHz,所以需要使用PLL对外部晶振产生的50MHz输入的时钟信号进行倍频,得到两路频率为100M,相位相差90度的时钟信号。另外,还需要提供一路9MHz时钟信号供480*272分辨率的TFT屏驱动使用。关于PLL的详细添加和参数修改方式这里不再多说,仅提供设置结果:

inclk0:50M

不需要areset信号和locked信号

c0:9MHz

c1:100MHz,相位为0

c2:100MHz,相位为-180度

2、 nios ii cpu

所有设置使用默认值,等后续添加完sdram和EPCS和,将复位向量(Reset Vector)设置为epcs,将异常向量(Exception Vector)设置为sdram。

3、 sdram

数据位宽(Data Width)为16bit。

结构(Architecture)为一个片选,4个bank。

地址宽度(Address Width)Row地址为12、Column地址为9。

其他默认即可。

 

图片3

 

4、 onchip_memory

片上缓存onchip_memory作为SGDMA的描述符存储器,存储SGDMA的数据传输描述内容。

类型为RAM;

数据位宽选择32位;

存储器总量选择16384Bytes(用户可以根据自己芯片的RAM容量适当增加或减小该值);

 

图片4

 

5、 lcd_sgdma

选择传输模式为存储器到数据流模式(Memory To Streram);

数据宽度(Data width)为16bit,该位宽与存储器(SDRAM)的Avalon MM总线宽度一致。

其他按照默认即可,如下图所示。

 

图片5

 

6、 timing_adapter

时序匹配IP核,主要用于流数据在两个不同模块间进行传递时,对部分信号加上一定延迟,使得数据和标志信号能够完全同步(说的简单点儿,就是用寄存器打拍的方式对一些信号进行延迟,使数据和控制信号对齐)。

如下图所示设置,所有勾选项都勾选上,输入Ready Latency设置为0,输出Ready Latency设置为1,每个数据总共有两个symbols,每个symbol含8位数据。(根据RGB数据流的实际意义,这里应该是每个数据有3个symbols,但是本设计使用的是16位色RGB565模式,即RGB总共才16位,所以在搭建系统的时候,假设只有2个symbols,只是在最终数据输出到VGA的时候,将16bit数据拆分成RGB565,分别送给VGA的R、G、B色通道)

 

图片6

 

以上各个模块,都是工作在100MHz的时钟频率下。主要完成将需要显示的数据从SDRAM中读取出来。而读取出来的数据最终是需要送到TFT显示屏上去显示的,TFT显示部分,对于本系统,数据传输速率为9M,因此就需要实现数据从100M时钟域到9M时钟域的跨时钟域传输。而实现数据流跨时钟域传输,较好的方式是使用双时钟fifo。因此,接下来就需要添加一个双时钟fifo,先将100M时钟域的数据缓存起来,然后再等待9M时钟域的读取逻辑来读取。

7、 fifo

设置fifo的深度为512字节,每个数据含2个symbols,每个symbols由8位数据组成,即整个数据宽度为16位。时钟设置为双时钟模式(Dual Clock Mode),输入和输出都使用Avalon ST接口,使能数据包(Enable Packet Data)。这里顺带说一下,Cyclone IV E器件中的嵌入式块RAM为M9K存储器,每个器件里都有若干个M9K存储器。所谓M9K存储器,就是一个块存储器中有9Kb个存储位,每个M9K存储器可以被配置为以下模式:

  • 8192 × 1
  • 4096 × 2
  • 2048 × 4
  • 1024 × 8
  • 1024 × 9
  • 512 × 16
  • 512 × 18
  • 256 × 32
  • 256 × 36

由于本设计中,数据是16位的,因此,如果要想仅使用一个M9K存储器就实现本Fifo,存储深度可在1到512之间任选。假如我们选择存储深度为128,那么就只占用1/4个M9K的存储容量,但是,剩下的3/4的存储容量也依旧无法单独使用,在该设计中会永远的被浪费掉,因此,不如将存储深度直接设置为512,还能保证缓存足够大,避免意外丢失数据。

 

图片7

8、 VGA_SINK

VGA_SINK模块是从友晶提供的设计中修改得到的,该IP原本仅支持24位色模式,而本系统,由于SDRAM为16位宽度,如果强行使用24位色模式,则必须得从SDRAM中读取两个数据才能拼接成一个像素,导致SDRAM的负载过大,从而使得系统带宽遇到瓶颈,无法正常工作,因此对该IP进行了修改,即降低为16bit的RGB565模式。

(根据RGB数据流的实际意义,这里应该是每个数据有3个symbols,但是本设计使用的是16位色RGB565模式,即RGB总共才16位,所以在搭建系统的时候,假设只有2个symbols,只是在VGA_SINK模块内部,在最终数据输出到VGA的时候,进行了修改,将16bit数据拆分成RGB565,分别送给VGA的R、G、B色通道)。这里参数分别如下设置:

H_DISP(行显示有效像素):640

H_FPORCH:16时钟周期

H_SYNC:96时钟周期

H_BPROCH:48时钟周期

V_DISP(行显示有效像素):480

V_FPORCH:1时钟周期

V_SYNC:2时钟周期

V_BPROCH:33时钟周期

 

图片8

 

这些参数都是从VGA标准中查到的。

9、EPCS

EPCS的添加没有什么需要特别注意的,一切按照默认设置即可。

至此,我们已经完成了整个显示系统的组件添加工作,接下来就该进行组件间的总线连接了。

在总线连接之前,必须要先给大家讲解一下本系统的数据流向,因为本系统和我们之前在SOPC公开课时候讲过的系统架构还不太一样,本系统主要引入了Avalon ST总线。在之前公开课时候讲过的各种例子,都是基于Avalon MM总线的,所有外设IP都通过Avalon MM总线连接到NIOS II CPU上,因此总线连接是非常简单的。但是Avalon MM总线的数据搬运能力是比较弱的,无法支持高速大量的数据传输。而在基于Avalon ST总线的数据流中,NIOS II CPU实质相当于被旁路了,只起到了状态管理的作用,所有的数据流转都是模块间通过Avalon ST总线进行交互的,不需要NIOS II去执行搬运工作,因此数据的搬运效率大大提高。

本节暂不对Avalon ST总线进行过多的讲解,总之,我们可以把Avalon ST总线想象成一个水管,水从水管的一头可以很容易且快速的流到另一头,不需要第三方用水瓢一瓢一瓢的去舀过去。

Framebuffer系统数据流

在整个FrameBuffer系统中,SGDMA模块是最核心的部分,他实现了对SDRAM存储器的直接读取操作,并将读到的数据最终以数据流的形式输出。SGDMA的使用相当的广泛,不仅是在本系统中,在Altera很多官方设计中也都使用了SGDMA IP核,比如PCIE 示例、千兆以太网示例、VIP示例等,关于SGDMA的详细介绍,可以参看SGDMA的用户手册,我们也希望能尽快推出SGDMA的专题讲解。这里仅对SGDMA的端口和功能进行下简述。

本系统中SGDMA被配置为存储映射模式到数据流模式,即数据的源头采用地址映射的方式组织,而数据的接收方则使用数据流的方式进行接收。在此种模式下,SGDMA总共有5个端口,分别为Avalon MM Slave接口的控制总线,Avalon MM Master接口源数据读取总线、Avalon MM Master接口的描述符写入总线和Avalon MM Master接口的描述符读取总线,以及最终的Avalon ST Source数据输出总线。

 

图片9

 

csr:Avalon MM Slave接口的控制总线,与NIOS II CPU进行连接,NIOS II CPU通过该接口读写SGDMA内部的控制和状态寄存器,以实现DMA的传输控制。这也是整个数据流传输唯一需要NIOS II参与的地方。NIOS II实际就扮演了个老板的角色,安排SGDMA去做什么,SGDMA接到任务后就下去干搬运的苦力活儿了,而NIOS II这个老板,则可以坐在办公室喝喝咖啡,看看报纸,等着SGDMA把苦力干完后回来报告(中断)或者隔一段时间打个电话问问工作状态和进度(查询)。

m_read:Avalon MM Master接口的源数据读取总线,该接口实现对Avalon MM总线上挂载的存储需要传输的数据源存储器的读取,例如SDRAM、DDR2 SDRAM。从这里,大家也可以看到,在Qsys搭建的系统中,不是只有NIOS II能够去读取SDRAM、DDR2等存储器,实际上,只要是Avalon MM Master接口的IP,都可以去读取这些存储器,我们甚至可以自己编写一个Avalon MM Master接口的逻辑,来取代NIOS II CPU完成各种IP核的读写操作。

descriptor_write:描述符写端口,SGDMA实现数据传输需要有一个描述符,SGDMA的所有传输都是受描述符控制的,通过描述符存储SGDMA的实时传输状态。这里专门使用一个片上存储器来存储SGDMA的描述符,能够节省FPGA资源。否则,如果使用FPGA资源来实现描述符,将会带来非常大的资源消耗。

descriptor_read:描述符读端口,SGDMA使用此端口读取描述符,以获得传输信息。

out:数据流输出端口,从SDRAM或DDR2中读取到的数据会经由此端口流出,以提供给数据的使用端使用。

对于我们分析数据流来说,可以暂时忽略csr、descriptor_write和descriptor_read端口,因为这些端口并不处于数据通路上,真正处于数据通路上的,是m_read(数据流入)和out(数据流出)端口。下图为本系统的数据流图。

 

图片10

 

NIOS II CPU使用SDRAM作为运行存储器,存储程序和数据,同时,SGDMA从SDRAM中实时读取需要显示到VGA屏上的数据。NIOS II 和 SGDMA之间对SDRAM的使用仲裁由Avalon MM总线的仲裁机制自动处理。

通过以上介绍,了解了系统的数据流向,就可以首先完成数据流总线的连接了。每个st 流接口的模块都有2个流端口,一个是流输入端口(in: Avalon Streaming Sink),另一个是流输出端口(out: Avalon Streaming Source)。连接时,只需将上一级模块的流out连接到下一级模块的流in端口,即可实现数据流的自动衔接。在本例中每个模块端口连接如下表所示:

 

图片11

 

整体连接完成后,结果如下图所示:

 

图片12

 

当然,连接完成总线后,不要忘了连接复位网络、导出需要引出到系统顶层的信号以及中断网络。

  • 修改NIOS II的复位向量为EPCS、异常向量为SDRAM。
  • 自动分配基地址。
  • 到HDL Eaxmple栏复制例化模版。
  • 保存并开始generate。

至此,整个系统就搭建完成了,下一步就是将系统加入到Quartus II工程中。首先是将mysystem.qsys文件添加到工程,然后完成工程顶层文件的编写。这些最基本的操作,这里就不在赘述,以下为工程顶层代码:

module Framebuffer_VGA(
        input  wire        clk_50m,          
        input  wire        reset_n,          
        output wire        vga_clk,          
        output wire        vga_de,           
        output wire [7:0]  vga_r,            
        output wire [7:0]  vga_g,            
        output wire [7:0]  vga_b,            
        output wire        vga_hs,           
        output wire        vga_vs,           
        output wire        vga_bl,           
        output wire        sdram_clk,        
        output wire [11:0] sdram_addr,       
        output wire [1:0]  sdram_ba,         
        output wire        sdram_cas_n,      
        output wire        sdram_cke,        
        output wire        sdram_cs_n,       
        inout  wire [15:0] sdram_dq,         
        output wire [1:0]  sdram_dqm,        
        output wire        sdram_ras_n,      
        output wire        sdram_we_n,       
        output wire        epcs_dclk,        
        output wire        epcs_sce,         
        output wire        epcs_sdo,         
        input  wire        epcs_data0        
);   

    assign vga_bl = 1;
    
    wire vga_clk_r;
    
    assign vga_clk = ~vga_clk_r;    //对VGA时钟反相,以保证数据中心对齐
    
    mysystem u0 (
        .clk_50m_clk                       (clk_50m),              
        .reset_50m_reset_n                 (reset_n),              
        .vga_clk                           (vga_clk_r),            
        .vga_de                            (vga_de),               
        .vga_r                             (vga_r),                
        .vga_g                             (vga_g),                
        .vga_b                             (vga_b),                
        .vga_hs                            (vga_hs),               
        .vga_vs                            (vga_vs),               
        .altpll_0_phasedone_conduit_export (),
        .altpll_0_locked_conduit_export    (),    
        .altpll_0_areset_conduit_export    (),    
        .epcs_dclk                         (epcs_dclk),            
        .epcs_sce                          (epcs_sce),             
        .epcs_sdo                          (epcs_sdo),             
        .epcs_data0                        (epcs_data0),           
        .sdram_clk_clk                     (sdram_clk),            
        .sdram_addr                        (sdram_addr),           
        .sdram_ba                          (sdram_ba),             
        .sdram_cas_n                       (sdram_cas_n),          
        .sdram_cke                         (sdram_cke),            
        .sdram_cs_n                        (sdram_cs_n),           
        .sdram_dq                          (sdram_dq),             
        .sdram_dqm                         (sdram_dqm),            
        .sdram_ras_n                       (sdram_ras_n),          
        .sdram_we_n                        (sdram_we_n) 
    );

endmodule

 

 


然后分配引脚,本系统在AC620开发板上的引脚分配请参照AC620开发板引脚分配表。
 

分配引脚后,记得在quartus ii软件中设置所有双功能引脚为用户模式。

 

图片14

 

编译工程得到sof编程文件。

至此,整个FrameBuffer系统硬件设计就完成了,下一步,我们就可以编写相应的驱动程序和应用程序,在目标板上运行了。

创建Eclipse工程

打开NIOS II EDS软件,导入我们提供的VGA和VGA_bsp两个工程。修改setting.bsp文件中第7行和第9行两个位置为自己电脑上对应的位置。关闭文件,然后build工程。如果编译失败,提示无权限,请先关闭所有文件,然后选择整个文件夹右键获取管理员权限。

 

图片15

 

以下为main函数内容

 

#include "stdio.h"
#include "stdlib.h"
#include "io.h"
#include "sys/alt_alarm.h"
#include "altera_avalon_sgdma.h"
#include "altera_avalon_sgdma_descriptor.h"
#include "altera_avalon_sgdma_regs.h"
#include "alt_types.h"
#include "alt_video_display.h"
#include "unistd.h"
#include "pic1.h"
#include "pic2.h"
#include  "system.h"


#define  WIDTH 640
#define  HEIGHT 480
#define  NUM_FRAME 1


int main() {
    unsigned int d = 0;

    ////Initial LCD Display
    alt_video_display* display_global;
//  printf("Initializing LCD display controller\n  ");
    display_global = alt_video_display_init(LCD_SGDMA_NAME, // Name of video controller
            WIDTH, // Width of display
            HEIGHT, // Height of display
            16, // Color depth (32 or 16)
            SDRAM_BASE + SDRAM_SPAN / 2, // Where we want our frame buffers
            ONCHIP_MEMORY_BASE, // Where we want our descriptors
            NUM_FRAME);
//  if (display_global)
//      printf(" - LCD Initialization OK\n");
//  else
//      printf(" - LCD FAILED\n");
    alt_video_display_clear_screen(display_global, 0xff);
    show_pic(display_global, pic1);
    usleep(1000000);
    while(1){
    }
}

 

编译完成后下载sof,并在nios ii eds中执行run。连接VGA显示器,就能在屏幕上显示一副美女图,如下图所示:

如有其它问题,请参见小梅哥博客中关于NIOS II的相关内容。如“【2017最新】解决NIOS II工程移动在磁盘上位置后project无法编译问题.pdf”和“NIOS II 开发注意点总结.pdf

本文档对应设计工程名为:AC620_Framebuffer_VGA,请到AC620开发板网盘/光盘资料中获取。

图片17图片18

posted @ 2017-04-18 11:16  小梅哥  阅读(2855)  评论(1编辑  收藏  举报