PotatoPie 4.0 实验教程(17) —— FPGA实现SDRAM作为显存进行HDMI输出显示-Anlogic-安路论坛-FPGA CPLD-ChipDebug

PotatoPie 4.0 实验教程(17) —— FPGA实现SDRAM作为显存进行HDMI输出显示

手机扫码

20240416075513933-1713225291635

链接直达

https://item.taobao.com/item.htm?ft=t&id=776516984361

这个实验与上一个实验相比是加入了SDRAM,加入SDRAM的原因是为了给下一步的摄像头图像处理作铺垫,因为摄像头出的数据并不是像上一个实验一样刚好符合VESA的时序,需要用SDRAM做缓存。

工程层次分析

20240402112838583-image

工程模块简介

  • system_ctrl_pll 系统时钟和复位管理模块
  • video_tgp_24b 生成视频测试pattern数据模块
  • SDRAM_512Kx4x32bit  EG4S内部的SDRAM
  • Sdram_Control_2Port SDRAM控制器
  • lcd_driver 产生视频数据的时序
  • hdmi_tx HDMI发送模块

源码分析

system_ctrl_pll 系统时钟和复位管理模块

该模块主要是例化了一个PLL,同时进行了复位的处理。这个模块里的system_init_delay是对输入的参考时钟进行计数,用于等待时钟稳定。sys_pll就是例化的PLL。

这段代码是对板上的复位按键进行异步复位,同步释放。

//----------------------------------------------
//rst_n sync, only controlled by the main clk
reg     rst_nr1, rst_nr2;
always @(posedge clk_ref)
begin
    if(!rst_n)
        begin
        rst_nr1 <= 1'b0;
        rst_nr2 <= 1'b0;
        end
    else
        begin
        rst_nr1 <= 1'b1;
        rst_nr2 <= rst_nr1;
        end
end

最后这行代码是将内部产生的同步释放的复位与PLL的锁定信号locked进行与操作生成系统复位。

assign sys_rst_n = rst_nr2 & locked; //active low

video_tgp_24b 生成视频测试pattern数据模块

这一块的代码主要完成视频测试数据的生成,由于这个工程中有SDRAM缓存,因此我们并不需要按照VESA的标准来生成测试数据,我们可以按我们的需求来将数据写入到SDRAM。

代码首先对行列进行计数,

if(lcd_xpos < H_TOTAL - 1'b1)
                    lcd_xpos <= lcd_xpos + 1'b1;
                else
                    lcd_xpos <= 11'd0;
                    
                if(lcd_xpos == H_TOTAL - 1'b1)
                    begin
                    if(lcd_ypos < V_TOTAL - 1'b1)
                        lcd_ypos <= lcd_ypos + 1'b1;
                    else
                        lcd_ypos <= 11'd0;
                    end
                else
                    lcd_ypos <= lcd_ypos;

在行列计数都有效的的情况下进行测试视频的生成

if(lcd_xpos <= H_DISP - 1'b1 && lcd_ypos <= V_DISP - 1'b1)
                    case(img_cnt)
                    'd0:    data <= lcd_xpos * lcd_ypos;
                    'd1:    data <= ((lcd_ypos[4]==1'b1) ^ (lcd_xpos[4]==1'b1)) ? {24{1'b0}} : {24{1'b1}};
                    'd2:    data <= {lcd_ypos[7:0], 8'h00, lcd_xpos[7:0]};
                    'd3:    data <= (lcd_ypos[9]) ? {lcd_ypos[7:0], lcd_ypos[7:0], lcd_ypos[7:0]}:
                                                                 {lcd_xpos[7:0], lcd_xpos[7:0], lcd_xpos[7:0]};                 
				    'd4: begin
                        case (lcd_xpos[9:7])
                            'b111: data <= ROYAL ;
                            'b110: data <= CYAN  ;
                            'b101: data <= YELLOW;
                            'b100: data <= BLACK ;
                            'b011: data <= WHITE ; 
                            'b010: data <= BLUE  ;
                            'b001: data <= GREEN ;
                            'b000: data <= RED;   
                        endcase
                    end                                  
                    'd5: begin
                        case (lcd_ypos[9:7])
                            'b000: data <= RED;
                            'b001: data <= GREEN ;
                            'b010: data <= BLUE  ;
                            'b011: data <= WHITE ;
                            'b100: data <= BLACK ;
                            'b101: data <= YELLOW;
                            'b110: data <= CYAN  ;
                            'b111: data <= ROYAL;
                        endcase
                    end                                                           
                    'd6:   data <= {16'h0, lcd_xpos[7:0]};
                    'd7:   data <= {lcd_ypos[7:0], 16'h0};
                    endcase
                else
                      ...

SDRAM_512Kx4x32bit  EG4S内部的SDRAM

module EG_PHY_SDRAM_2M_32 (
    clk,
    ras_n,
    cas_n,
    we_n,
    addr,
    ba,
    dq,
    cs_n,
    dm0,
    dm1,
    dm2,
    dm3,
    cke
);

这个就是EG4S内部合封的SDRAM,跟独立的SDRAM信号引脚一样,不作赘述。

Sdram_Control_2Port SDRAM控制器

module Sdram_Control_2Port(
		//	HOST Side
        REF_CLK,		//sdram control clock
		OUT_CLK,		//sdram	output clock
        RESET_N,		//global clock reset
		
		//	FIFO Write Side 1
        WR_DATA,
		WR,
		WR_MIN_ADDR,
		WR_MAX_ADDR,
		WR_LENGTH,
		WR_LOAD,
		WR_CLK,

		//	FIFO Read Side 1
        RD_DATA,
		RD,
		RD_MIN_ADDR,
		RD_MAX_ADDR,
		RD_LENGTH,
		RD_LOAD,	
		RD_CLK,

		//	SDRAM Side
        SA,
        BA,
        CS_N,
        CKE,
        RAS_N,
        CAS_N,
        WE_N,
        DQ,
        DQM,
		SDR_CLK,
		
		//User interface
		Sdram_Init_Done,		//SDRAM Init done signal
		Sdram_Read_Valid,		//SDRAM Read valid : output
		Sdram_PingPong_EN		//SDRAM PING-PONG operation enable
        );

这个模块是上一个模块是绍的SDRAM的控制器,主要完成SDRAM的初始化以及读写操作,这个模块本身相对复杂,后面会专门出一篇教程就先不在这里介绍了,这个模块原本是Northwest Logic 公司提供的,经过网友们魔改之后把它两弄成了一个双FIFO接口。关于每个信号的作用在代码中有详细的注释。

可以看到主要分成以下几部分:

  • HOST端,主要提供SDRAM控制器的参考时钟输入,SDRAM的时钟输出,以及SDRAM控制器的复位输入。
  • SDRAM端,就直接连接到了EG4S内部的SDRAM脚。
  • FIFO 写端, FIFO读端,可以从模块内部看到实际就是连到了内部的两个异步FIFO,用于同步写入时钟和读出时钟,对于写端由用户进行写,由SDRAM进行读。对于读端由SDRAM进行写,由用户进行读。
Sdram_Control_2Port    u_Sdram_Control_2Port
(
    //    HOST Side
    .REF_CLK            (clk_sdr_ref),       //sdram module clock
    .OUT_CLK            (clk_sdr_out),       //sdram clock input
    .RESET_N            (sys_rst_n),         //sdram module reset

    //    SDRAM Side
    .SDR_CLK            (sdram_clk),        //sdram clock
    .CKE                (sdram_cke),        //sdram clock enable
    .CS_N               (sdram_cs_n),       //sdram chip select
    .WE_N               (sdram_we_n),       //sdram write enable
    .CAS_N              (sdram_cas_n),      //sdram column address strobe
    .RAS_N              (sdram_ras_n),      //sdram row address strobe
    .DQM                (sdram_dqm),        //sdram data output enable
    .BA                 (sdram_ba),         //sdram bank address
    .SA                 (sdram_addr),       //sdram address
    .DQ                 (sdram_data),       //sdram data

    //    FIFO Write Side
    .WR_CLK             (sdram_clk_wr),      //write fifo clock
    .WR_LOAD            (1'b0),              //write register load & fifo clear
    .WR_DATA            (sdram_din),         //write data input
    .WR                 (sdram_wr),          //write data request
    .WR_MIN_ADDR        (21'd0),             //write start address
    .WR_MAX_ADDR        (21'd1280 * 21'd720),//write max address
    .WR_LENGTH          (9'd256),            //write burst length

    //    FIFO Read Side
    .RD_CLK             (clk_read),          //read fifo clock
    .RD_LOAD            (1'b0),              //read register load & fifo clear
    .RD_DATA            (sys_data_out),      //read data output
    .RD                 (sys_rd_out),        //read request
    .RD_MIN_ADDR        (21'd0),             //read start address
    .RD_MAX_ADDR        (21'd1280 * 21'd720),//read max address
    .RD_LENGTH          (9'd256),            //read length
    
    //User interface
    .Sdram_Init_Done    (sdram_init_done),  //SDRAM init done signal
    .Sdram_Read_Valid   (1'b1),             //Enable to readl
    .Sdram_PingPong_EN  (1'b1)              //SDRAM PING-PONG operation enable
);

lcd_driver 产生视频数据的时序

该模块跟我们前一个教中的vga_tc模块相似,通过行场计数器产生VESA所要求的720P的行场同步信号。将其中的DE信号作为SDRAM控制器的读请求从SDRAM中读出数据发送到HDMI_TX。同时该模块产生的行场同步还发送给HDMI TX,HDMI_TX将SDRMA控制器输出的数据串行发送出去。

hdmi_tx HDMI发送模块

前面一个教程描述过了,该模块负责将行场视频的并行数据转化为TMDS串行发送出去。

物理约束

set_pin_assignment	{ clk_10m }	{ LOCATION = P11; IOSTANDARD = LVCMOS33; PULLTYPE = NONE; }
set_pin_assignment	{ i_rst_n }	{ LOCATION = P83; IOSTANDARD = LVCMOS33; PULLTYPE = PULLUP; }

#HDMI TX
set_pin_assignment	{ HDMI_CLK_P }	{ LOCATION = P47; IOSTANDARD = LVDS33; }
set_pin_assignment	{ HDMI_D0_P }	{ LOCATION = P55; IOSTANDARD = LVDS33; }
set_pin_assignment	{ HDMI_D1_P }	{ LOCATION = P61; IOSTANDARD = LVDS33; }
set_pin_assignment	{ HDMI_D2_P }	{ LOCATION = P64; IOSTANDARD = LVDS33; }

时序约束

#Created Clock
create_clock -name clk_10m -period 100 [get_ports {clk_10m}]


# Automatically constrain PLL and other generated clocks
derive_pll_clocks
rename_clock -name {clk_sdr_ref} -source [get_ports {clk_10m}] -master_clock {clk_10m} [get_pins {u_system_ctrl_pll/u_sys_pll/pll_inst.clkc[0]}]
rename_clock -name {clk_sdr_out} -source [get_ports {clk_10m}] -master_clock {clk_10m} [get_pins {u_system_ctrl_pll/u_sys_pll/pll_inst.clkc[1]}]
rename_clock -name {clk_pixel} -source [get_ports {clk_10m}] -master_clock {clk_10m} [get_pins {u_system_ctrl_pll/u_sys_pll/pll_inst.clkc[2]}]
rename_clock -name {clk_pixel_5x} -source [get_ports {clk_10m}] -master_clock {clk_10m} [get_pins {u_system_ctrl_pll/u_sys_pll/pll_inst.clkc[3]}]
set_clock_groups -exclusive -group [get_clocks {clk_10m}] -group [get_clocks {clk_sdr_ref}] -group [get_clocks {clk_sdr_out}] -group [get_clocks {clk_pixel clk_pixel_5x}]

上板实验

实验现像跟上一个教程的现象差不多,只是图案有变化。

请登录后发表评论

    没有回复内容