片上系统设计思想与源代码分析
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

7.3 RGB接口LCD控制器

7.3.1 RGB接口LCD控制器框图

这里针对DemoSoC的需求,设计了连接到WISHBONE总线的RGB接口LCD控制器驱动L2F50090,其方框图如图7-5所示。

图7-5 RGB接口LCD控制器框图

7.3.2 寄存器定义

RGB接口LCD控制器的寄存器定义及说明如表7-1和表7-2所示。

表7-1 LCDC控制寄存器

表7-2 LCDC PIXEL START ADDRESS REGISTER

7.3.3 RGB接口LCD控制器设计文件列表

RGB接口LCD控制器设计文件列表如表7-3所示。

表7-3 RGB接口LCD控制器设计文件列表

下文将分别讲述各个设计文件的细节内容。从命名可以看出,本书的LCD控制器是由http://www.opencores.org的ssvga改进而来。

7.3.4 RGB接口LCD控制器WISHBONE从设备接口

RGB接口LCD控制器WISHBONE从设备接口实现主处理器对LCD控制器的控制。源代码如下:

`define SEL_PAL 10  //调色板选择信号,在本设计中未实现
`define SEL_ADDRESS 2 //调色板地址,在本设计中未实现
module ssvga_wbs_if(// WISHBONE从设备接口
    wb_clk_i, wb_rst_i,wbs_cyc_i, wbs_stb_i, wbs_sel_i, wbs_we_i,wbs_adr_i, wbs_dat_i,
wbs_cab_i,wbs_dat_o, wbs_ack_o, wbs_err_o, wbs_rty_o,
    //其他信号
    ssvga_en, pal_wr_en, pal_rd_en, pal_dat,pix_start_addr,refresh_factor);
……//省略了部分输入输出信号
output ssvga_en; //LCD控制器使能信号,该信号由CPU控制,为1则LCD控制器开始工作调色板的作用是
//将输入的色彩调整到最适合屏幕显示的少量颜色,通常是256色。早期的CSTN显示器多需要调色板。最新的
//显示器多不需要,因此在本设计中未实现,只是预留了调色板操作接口,即调色板的读写
output  pal_wr_en; //调色板写使能
output  pal_rd_en;   //调色板读使能
input   [15:0]  pal_dat;//调色板数据
output  [31:2] pix_start_addr ;//主存储器中显存的起始地址
output [5:0] refresh_factor;//刷新分数,即列扫描多少个时钟周期更新一列
//LCDC控制寄存器实现
always @(posedge wb_clk_i or posedge wb_rst_i)
    if (wb_rst_i)   ctrl_r <= #1 7'h0F;//默认情况下LCD控制器使能
    else if (valid_access & wbs_we_i & !wbs_adr_i[`SEL_PAL] & !wbs_adr_i[`SEL_ADDRESS])
        ctrl_r <= #1 wbs_dat_i[6:0]; //写操作
reg [31:2] pix_start_addr ;// 主存储器中显存的起始地址
always @(posedge wb_clk_i or posedge wb_rst_i)
    if (wb_rst_i)
        pix_start_addr <= #1 30'h0000_0000 ;
    else if (valid_access & wbs_we_i & !wbs_adr_i[`SEL_PAL] & wbs_adr_i[`SEL_ADDRESS] )
        pix_start_addr <= #1 wbs_dat_i[31:2] ;
//WISHBONE应答和错误信号
always @(posedge wb_clk_i or posedge wb_rst_i)
    if (wb_rst_i) begin wbs_ack_o <= #1 1'b0;wbs_err_o <= #1 1'b0;end
    else if (valid_access) begin wbs_ack_o <= #1 1'b1;wbs_err_o <= #1 1'b0;  end
    else if (wbs_cyc_i & wbs_stb_i) begin   wbs_ack_o <= #1 1'b0;   wbs_err_o  <=  #1
1'b1;end
    else begin wbs_ack_o <= #1 1'b0;wbs_err_o <= #1 1'b0;end
//WISHBONE输出信号
reg [31:0] wbs_dat_o ;
always@(wbs_adr_i or pal_dat or ctrl_r or pix_start_addr)
begin
    if ( wbs_adr_i[`SEL_PAL] ) wbs_dat_o = {16'h0000, pal_dat} ; //读取调色板
else  if ( wbs_adr_i[`SEL_ADDRESS] )  wbs_dat_o = {pix_start_addr, 2'b00};
//读取主存储器中显存的起始地址
    else  wbs_dat_o = {{31{1'b0}}, ctrl_r};//读取控制寄存器
end
assign wbs_rty_o = 1'b0;
//其他控制信号
assign valid_access = wbs_cyc_i & wbs_stb_i & (wbs_sel_i == 4'b1111);
assign ssvga_en = ctrl_r[0];
assign refresh_factor=ctrl_r[6:1];
assign pal_wr_en = valid_access & wbs_we_i & wbs_adr_i[`SEL_PAL];
assign pal_rd_en = valid_access & ~wbs_we_i & wbs_adr_i[`SEL_PAL];
endmodule

7.3.5 RGB接口LCD控制器WISHBONE主设备接口

RGB接口LCD控制器WISHBONE主设备接口从主存储器的显存读取数据并放入LCD控制器内部的FIFO(也称作像素缓存,英文为pixel buffer)等待显示,其源代码如下:

module ssvga_wbm_if(wb_clk_i, wb_rst_i,wbm_cyc_o, wbm_stb_o, wbm_sel_o, wbm_we_o,
    wbm_adr_o, wbm_dat_o, wbm_cab_o,wbm_dat_i, wbm_ack_i, wbm_err_i, wbm_rty_i,
    ssvga_en, fifo_full,fifo_wr_en, fifo_dat, pix_start_addr, resync);
……//忽略部分输入输出信号
input           fifo_full;  //FIFO满
output          fifo_wr_en; // FIFO写使能
output  [31:0]      fifo_dat;   // FIFO数据
input         resync; // 像素缓存下溢出时,一个屏幕的扫描必须重新开始
//内部寄存器
reg [`SSVGA_VMCW-1:0] vmaddr_r; //视频像素地址计数器,Video memory address counter
//一帧是指一个平面的数据,frame_read是一帧数据读取完毕指示
reg  frame_read ;
wire frame_read_in = ( vmaddr_r == `SSVGA_VMCW'h0_00_00 ) & wbm_ack_i & wbm_stb_o || ~
ssvga_en || resync;//LCD
//控制器未使能或发生错误重新开始读取数据,或者正常开始新的一帧数据
always@(posedge wb_clk_i or posedge wb_rst_i)
begin
    if (wb_rst_i)  frame_read <= #1 1'b0 ;
    else       frame_read <= #1 frame_read_in ;
end
//视频像素地址计数器,严格说,是剩余像素计数
always @(posedge wb_clk_i or posedge wb_rst_i)
    if (wb_rst_i)   vmaddr_r <= #1 ((`PIXEL_NUM/4)-1) ;//起始值为最大值,每次读取4个像
    //素,因此vmaddr_r为 ((`PIXEL_NUM/4)-1
    else if (frame_read)vmaddr_r <= #1 ((`PIXEL_NUM/4)-1);
    else if (wbm_ack_i & wbm_stb_o) vmaddr_r <= #1 vmaddr_r -1;//递减
//像素读取地址,递增
reg [31:2] wbm_adr ;
always@(posedge wb_clk_i or posedge wb_rst_i)
begin
    if (wb_rst_i)    wbm_adr <= #1 30'h0000_0000 ;
    else if (frame_read)  wbm_adr <= #1 pix_start_addr ;
    else if (wbm_ack_i & wbm_stb_o)  wbm_adr <= #1 wbm_adr + 1 ;
end
// WISHBONE输出信号
assign wbm_cyc_o = ssvga_en & !frame_read;
assign wbm_stb_o = wbm_cyc_o & !fifo_full;//FIFO未满就可以读取新的数据
assign wbm_sel_o = 4'b1111;
assign wbm_we_o = 1'b0;
assign wbm_adr_o = {wbm_adr, 2'b00};
assign wbm_dat_o = 32'h0000_0000;
assign wbm_cab_o = 1'b1;
// Generate other signals
assign fifo_wr_en = wbm_ack_i & wbm_stb_o ;
assign fifo_dat = wbm_dat_i ;
endmodule

7.3.6 LCD控制接口

LCD控制接口从像素缓存ssvga_fifo中读取像素数据,产生RGB接口LCD,源代码如下:

module ssvga_crtc(clk,rst,hsync,vsync,lcdck,lcdd,refresh_factor,wbm_restart,
fifo_rd_en,
fifo_data_in,fifo_empty);
……//此处忽略了输入输出信号定义
reg [`SSVGA_HCW-1:0]hcntr;  //水平扫描计数
reg [`SSVGA_VCW-1:0]vcntr;  //垂直扫描计数
reg [5:0] refresh_cnt;//刷新计数
always @(posedge clk or posedge rst) begin
    if(rst) refresh_cnt<=0;
    else if(refresh_cnt>=refresh_factor) refresh_cnt<=0;
    else refresh_cnt<=refresh_cnt+1;
end
wire dck_update=refresh_cnt>=refresh_factor;//像素时钟,refresh_factor+1个时钟周期将lcdck
//取反一次
reg negedge_dck;
always @(posedge clk or posedge rst) begin
    if(rst) begin lcdck<=1'b0;negedge_dck<=1'b0;end
    else if(dck_update) begin lcdck<=~lcdck; if(lcdck) negedge_dck<=1'b1; end
    else negedge_dck<=1'b0;
end
//生成水平和垂直扫描同步信号hsync和vsync,L2F50090显示器的像素阵列为240×176,
//数据有效行列数要小于240×176,与显示器有关,这些参数实际上可以作为LCD控制器的
//寄存器动态改变。本设计中约束为L2F50090的技术参数
always @(posedge clk or posedge rst) begin
    if(rst) begin hcntr<=`SSVGA_HCW'h0; vcntr<=0; hsync<=1'b1; end
    else if(negedge_dck) begin
      if(hcntr<=`SSVGA_HCW'd207) hcntr<=hcntr+1;
      else hcntr<=0;
      hsync<=(hcntr>=`SSVGA_HCW'd10);
      if(hcntr==`SSVGA_HCW'd0) begin
        if(vcntr>=`SSVGA_VCW'd249) vcntr<=0; else vcntr<=vcntr+1;
      end
    end
end
assign vsync=(vcntr!=`SSVGA_VCW'd1);
//一个帧正常结束或在一个帧的输出过程中FIFO为空,都要重新开始扫描整个帧
always @(posedge clk or posedge rst) begin
    if(rst) begin wbm_restart<=1'b0; fifo_rd_en<=1'b0; lcdd<=0; end
    else if(negedge_dck) begin
       lcdd<=fifo_data_in;//
      if(hcntr>=`SSVGA_HCW'd24 && hcntr<=`SSVGA_HCW'd199) begin
          if(vcntr>`SSVGA_VCW'd2 && vcntr<=`SSVGA_VCW'd242) begin
            if(!fifo_empty && !wbm_restart) fifo_rd_en<=1'b1;
            else begin fifo_rd_en<=1'b0;wbm_restart<=1'b1;end
          end
      end
      else begin //invalid period of a frame
        fifo_rd_en<=1'b0;
        if(vcntr<=`SSVGA_VCW'd2 && vcntr>`SSVGA_VCW'd242) begin
            wbm_restart<=1'b0;
        end
      end
    end
    else fifo_rd_en<=1'b0;
end
endmodule

7.3.7 LCD初始化接口

L2F50090的接口寄存器需要进行初始化,如第7.2.3节所述。本节内容只与L2F50090有关,写给对该LCD感兴趣的读者阅读。serial_interface使用一个状态机,按照如图7-3所示顺序初始化L2F50090的接口寄存器,初始化时序如图7-4所示。其源代码如下:

module serial_interface(clk,rstn,lcd_bk,lcdso,lcdncs,lcdsck,lcdnrst,lcdsi,lcda0);
…..//此处忽略输入输出信号的定义
//寄存器初始值的定义
parameter DISCTRL1_CMD={8'hc9,8'hf4,8'h00,8'hf0,8'h00,8'h02};
parameter DISCTRL2_CMD={8'hca,8'hcf,8'h02,8'h06,8'h18,8'h04,8'h00};
parameter GCPSET_CMD={8'hcb,8'h41,8'h00,8'h81,8'h02,8'h01,8'h01,8'h02,8'h04,
8'h08,8'h08,8'h20,8'h81,8'h08,8'h42,8'h08,8'h89,8'h12,8'h25,8'h29,8'h55,8'h2a,
8'ha9,8'h2a,8'hab,8'hff,8'hfe,48'h000000000000};
parameter SLPOUT_CMD=8'h94;
parameter DISPON_CMD=8'haf;
parameter DISPOFF_CMD=8'hAE;
parameter SLPIN_CMD=8'h95;
parameter VOLCTL_CMD={8'hc6,8'h9f};
parameter MAX_WAIT=26'h0FFFFF;
parameter RST_HOLD_CNT=16'd2000;
reg lcd_bk;
reg [12:0] bk_cnt;
always @ (posedge clk or negedge rstn) begin
    if(~rstn) begin lcd_bk<=1'b1; bk_cnt<=0; end
else begin  bk_cnt<=bk_cnt+1;end
end
reg lcdso,lcdsck,lcdnrst,lcda0;
//初始化状态机的状态定义
parameter  RST_HOLD=5'd1,DISPOFF=5'd2,WAIT1=5'd3,SLPIN=5'd4,
WAIT2=5'd5,DISPCTRL1=5'd6,WAIT3=5'd7,DISPCTRL2=5'd8,
WAIT4=5'd9,GCPSET=5'd10,WAIT5=5'd11,SLPOUT=5'd12,
WAIT6=5'd13,DISPON=5'd14,WAIT7=5'd15,VOLCTL=5'd16,
DISPEND=5'd17;
……//忽略了部分寄存器的定义
always @ (posedge clk or negedge rstn) begin
    if(~rstn) begin dispon_cnt<=0;lcdsck<=1'b0;  end
else begin dispon_cnt<=dispon_cnt+1;
      if(dispon_cnt==6'h3f) lcdsck<=~lcdsck;
    end
end
wire negedge_sck= (dispon_cnt==6'h3f) & (lcdsck==1'b1);
always @ (posedge clk or negedge rstn) begin
    if(~rstn) begin //初始化所有寄存器的初始值
      state<=RST_HOLD; lcdnrst<=0; lcd_cnt<=0; lcda0<=1'b1; lcdso<=1'b1;lcdncs<=1'b1;
      GCPSET_reg<= GCPSET_CMD; DISCTRL2_reg<= DISCTRL2_CMD;
      DISCTRL1_reg<= DISCTRL1_CMD; DISPOFF_reg<=DISPOFF_CMD;
      SLPIN_reg<=SLPIN_CMD; SLPOUT_reg<=SLPOUT_CMD;
      VOLCTL_reg<=VOLCTL_CMD; DISPON_reg<=DISPON_CMD;
      wait_cnt<=0;
    end
    else begin
      case(state)
      RST_HOLD: begin //等待一段时间,让LCD复位稳定
        lcdnrst<=1'b1;
        if(lcd_cnt>=RST_HOLD_CNT) begin
            param_cnt<=0; if(negedge_sck) state<=DISPOFF; lcd_cnt<=0;
        end
        else begin lcd_cnt<=lcd_cnt+1; end
        end
      DISPOFF:begin
        if(negedge_sck) begin  //输出DISPOFF命令,按照图7-4的时序
            DISPOFF_reg<={DISPOFF_reg[6:0],1'b0};
            lcdso<=DISPOFF_reg[7]; param_cnt<=param_cnt+1;
            lcda0<=param_cnt>=8;
            if(param_cnt>=10'd8) begin
              lcda0<=1'b1; state<= WAIT1; wait_cnt<=0; lcdncs<=1'b1; lcd_cnt<=0;
            end
            else begin lcdncs<=1'b0; lcda0<=1'b0; end
          end
      end
      WAIT1:begin //等待一段时间
        wait_cnt<=wait_cnt+1;
        if(wait_cnt>=MAX_WAIT) begin param_cnt<=0; state<=SLPIN;
        end
     end
     SLPIN:begin //输出SLPIN命令,按照图7-4的时序。SLPIN的下一个状态是WAIT2
          if(negedge_sck) begin
            SLPIN_reg<={SLPIN_reg[6:0],1'b0};
            …//此处省略代码类似DISPOFF状态的代码
          end
      end
      WAIT2:……//等待一段时间,下一状态是DISPCTRL1
      end
      DISPCTRL1:begin    //输出DISPCTRL1命令,按照图7-4的时序
          if(negedge_sck) begin
            DISCTRL1_reg<={DISCTRL1_reg[46:0],1'b0};
            ……//下一状态是WAIT3
          end
       end
     WAIT3:begin //等待一段时间进入DISPCTRL2
      DISPCTRL2:begin //输出DISPCTRL2命令,按照图7-4的时序
        if(negedge_sck) begin  DISCTRL2_reg<={DISCTRL2_reg[54:0],1'b0};
          ……//下一状态是WAIT4
     end
     WAIT4:  begin  //按照用户手册要求等待一段时间,进入GCPSET
GCPSET:begin  //输出GCPSET命令后进入WAIT5
        if(negedge_sck) begin GCPSET_reg<={GCPSET_reg[262:0],1'b0};
          ……//
     WAIT5:begin ……//等待一段时间后进入SLPOUT状态
SLPOUT:begin //输出SLPOUT命令,按照图7-4的时序
    if(negedge_sck) begin  SLPOUT_reg<={SLPOUT_reg[6:0],1'b0};
       ……//完成输出SLPOUT命令后进入WAIT6
     end
     WAIT6://等待一段时间后进入DISPON状态
     end
    DISPON:begin //输出DISPON命令,按照图7-4的时序
        if(negedge_sck) begin  DISPON_reg<={DISPON_reg[6:0],1'b0};
        ……//完成输出DISPON命令后进入WAIT7
     end
     WAIT7: //等待一段时间后进入VOLCTL状态
     VOLCTL:begin
        if(negedge_sck) begin VOLCTL_reg<={VOLCTL_reg[14:0],1'b0};
        ……//完成输出DISPON命令后进入DISPEND
     end
     DISPEND: begin lcdncs<=1'b1;end //初始化完毕后,永远停留在DISPEND状态
    endcase
    end
end
endmodule

7.3.8 LCD控制器像素缓存FIFO

由于多个主个设备占用总线,LCD控制器主设备接口从主存储器读取像素数据的瞬时速率具有一定的波动,而LCD控制器输出像素速率是均匀的,因此需要LCD控制器内置一个像素缓存FIFO进行速率平滑。像素缓存FIFO的宽度为32位,与总线宽度相同,深度与设计者有关,典型的深度为32~256位。在FPGA实现中,由于片上的块RAM较大,可以设计的深一些,本设计中为256位。LCD控制器像素缓存FIFO的源代码如下:

module  ssvga_fifo(clk,  rst,  dat_i,  wr_en,  rd_en,dat_o,  full,  empty,  ssvga_en,
wbm_restart);
……//忽略了输入输出信号
reg [2:0] state;
parameter FIFO_EMPTY=3'd1,DATA_WAIT=3'd2,DATA_PREPARED=3'd3,
DATA_NEXT=3'd4,NO_MORE_DATA=3'd5;
reg [15:0] data_out; //从FIFO中一次读出32比特,高16位送入data_out缓存,低16位赋值//到dat_o
always @(posedge clk or posedge rst) begin
    if(rst) begin empty<=1'b1; rdreq<=1'b0; state<=FIFO_EMPTY; end
    else if(ssvga_en) begin
      case (state)
      FIFO_EMPTY:begin //空闲状态下等待lcd_crtc的数据读取请求
        if(!iempty) begin rdreq<=1'b1;state<=DATA_WAIT;end
        else begin rdreq<=1'b0; end
      end
      DATA_WAIT: begin empty<=1'b0; rdreq<=1'b0; state<=DATA_PREPARED;end //读
      DATA_PREPARED:begin //将读取的数据复制到输出
        data_out<=fifo_q[31:16]; dat_o<= fifo_q[15:0];
        if(rd_en) begin state<=DATA_NEXT; end
        if(iempty && rd_en) begin rdreq<=1'b0;state<=NO_MORE_DATA;end
        else if(!iempty && rd_en) begin state<=DATA_NEXT; rdreq<=1'b1;end
      end
      NO_MORE_DATA:begin //FIFO中没有数据可读
        if(rd_en) begin //
          dat_o<= data_out; empty<=1'b1; state<=FIFO_EMPTY;
          if(!iempty) begin rdreq<=1'b1;state<=DATA_WAIT;end
          else begin  rdreq<=1'b0;state<=FIFO_EMPTY; end
        end
        else begin
          if(!iempty) begin rdreq<=1'b1;state<=DATA_NEXT;end
        end
      end
      DATA_NEXT:begin
        rdreq<=1'b0;
        if(rd_en) begin //连续读取FIFO的情况下送出数据
          data_out<=fifo_q[31:16]; dat_o<= data_out;
          state<=DATA_PREPARED;
        end
      end
    endcase
    end
    else if(wbm_restart) state<=FIFO_EMPTY;
  end
  wire sclr=(~ssvga_en) | wbm_restart; //清除FIFO内容
  wire ifull;
  assign full=ifull | rst;//复位或FIFO满
  //像素缓存,在FPGA实现时使用Altera的MegaWizard或Xilinx的CoreGenerator生成
  fifo256x32 fifo256x32(.aclr(rst),.clock(clk),.data(dat_i),.rdreq(rdreq),.sclr(sclr),
  .wrreq(wr_en),.empty(iempty),.full(ifull),.q(fifo_q));
  endmodule//像素缓存模块结束

7.3.9 LCD控制器顶层模块

LCD控制器顶层模块将ssvga_top模块和serial_interface模块组合到一起。源代码如下:

module lcdc_top(lcdhsn, lcd_bk, lcdso, lcdncs, lcdsck, lcdck, lcdvsn, lcdnrst, lcdsi,
lcda0, lcdd,
    wb_clk_i,wb_rst_i,wbm_cyc_o,wbm_stb_o,wbm_sel_o,wbm_we_o,wbm_adr_o,wbm_dat_o,
wbm_cab_o,wbm_dat_i,wbm_ack_i,wbm_err_i,wbm_rty_i,wbs_cyc_i,wbs_stb_i,wbs_sel_i,
wbs_we_i, wbs_adr_i, wbs_dat_i, wbs_cab_i, wbs_dat_o,wbs_ack_o, wbs_err_o,wbs_rty_o);
//根据LCD屏幕说明书连接输出数据信号
assign lcdd[0]=lcd_out[4];
assign lcdd[11:1]=lcd_out[10:0];
assign lcdd[12]=lcd_out[15];
assign lcdd[17:13]=lcd_out[15:11];
//例化ssvga_top和serial_interface
ssvga_top lcdc(……);
serial_interface si(……);
endmodule//lcd_top模块结束

7.3.10 RGB接口LCD控制器的FPGA验证

本章给出的RGB接口LCD控制器经过FPGA开发板的实际验证。

7.3.11 RGB接口LCD控制器的改进

本章的RGB接口LCD控制器是一个针对特定屏幕的设计,可改进的地方包括如下几点。

(1)通过增加寄存器支持多种不同的分辨率;

(2)支持黑白屏幕,支持帧率控制;

(3)支持调色板;

(4)支持Alpha-Blending,以产生多幅图片重叠的效果。

这些设计特征读者可以根据自己的需要自行添加。