【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二十六:VGA模块

来源:转载

实验二十六:VGA模块

VGA这家伙也算孽缘之一,从《建模篇》那时候开始便一路缠着笔者。《建模篇》之际,学习主要针对像素,帧,颜色等VGA的简单概念。《时序篇》之际,笔者便开始摸索VGA的时序。《整合篇》之际,笔者尝试控制VGA的时序。如今《驱动篇I》的内容返回VGA的本题,也就是图像方面的故事。

此刻,澎湃之情不容怠慢,请怒笔者不再回忆往事,失忆者请复习《Verilog HDL那些事儿》,笔者虽然也想直奔主题 ... 可是在此之前,笔者必须补足那些不易注意的细节

。俗语有云,细节是关键的藏身之地,那些不起眼的小细节,往往都是左右大局的关键,学习也是如此。不过,实验二十六究竟存在多少影响成败的小细节呢?请竖起耳朵,让笔者慢慢告诉读者 ...

图26.1 C1计数的理想时序。

图26.1是一段计数时序,C1扮演计数器,而且时序理想。对此,这段时序一定按照“时间点”在表现。假设笔者想要建立判断,那么判断基准会基于C1的过去值,例如:

if( C1 == 0 );

那么if( C1 == 0 ) 必须在T1才能成立。再假设笔者想要拉长判断的长度,例如T0~T8之间,那么笔者可以这样写:

if( C1 == 0 || C1 == 1 || C1 == 2 || C1 == 3 || C1 == 4 || C1 == 5 || C1 == 6 || C1 == 7 );

如果长度像妈妈一样长气,例如T2~T99。当然,上述方法一定行不通,因为后果不仅累坏自己,而且模块的内容也会沉长臃肿。为此,笔者可以或者这样写:

if(C1 >= 0 && C1 <= 7);

其中,if( C1 >= 0 && C1 <= 7 ) 表示9个时钟沿的长度。

图26.2 长度有AB之别的计数时序。

假设某某长度是固定的家伙,例如图26.2,T0~T3组成4个时钟沿的长度A,而

T3~T8组成5个时钟沿的长度B。为了方便,笔者可以建立常量:

parameter A = 3, B = 5;

如果笔者想把长度A作为有效的判断基准,那么笔者可以这样写:

if( C1 >= 0 && C1 <= A -1 );

代码的用意非常明显,C1 >= 0表示长度的起始,然而 C1 <= A -1 则是长度的结束。

如果长度B也作为判断的基准,同样的写法也适用:

if( C1 >= 2 && C1 <= A + B -1 );

可是,不管怎么看 ... 笔者觉得 C1 >= 2 非常别扭,对此笔者可以这样修改:

if( C1 >= A -1 && C1 < A + B -1 );

怎么样,读者是不是稍微顺眼一点?

图26.3 长度有CDE之别的计数时序。

假设顽皮的长度作出手势,然后分身成为3个,结果如图26.3所示。T0~T1组成3个时钟沿的长度C,T2~T6组成5个时钟沿的长度D,T6~T8组成3个时钟沿的长度E。为了方便,笔者先做常量声明:

parameter C = 2, D = 4, E = 2。

如果笔者想把长度C作为判断的基准,那么笔者可以这样写:

if( C1 >= 0 && C1 <= C -1 );

如果笔者想把长度D作为判断的基准,那么笔者可以这样写:

if( C1 >= C -1 && C1 <= C + D -1 );

如果笔者想把长度E作为判断的基准,那么笔者可以这样写:

if( C1 >= C + D -1 && C1 <= C + D + E -1 );

理解完毕以后,笔者可以这样总结 ... 由于C1从0开始计数,除非长度的起始地方是从0开始,否则长度的起始地方必须添加 -1 的处理。反之,不管结束地方怎么歇斯底里抵抗,长度的结束地方都必须加上 -1 处理。

图26.4 VGA时序。

图26.4是典型的VGA时序,VGA有5条信号,其中 HSYNC 与 VSYNC 控制显示分辨率还有显示帧。本实验的显示标准选为 1024 × 768 @ 60Hz - 65Mhz,而表26.1就是该显示标准的内容:

表26.1 显示标准 1024 × 768 @ 60Hz - 65Mhz。

信号

A

B

C

D

E

VGA_HSYNC

136

160

1024

24

1344

信号

O

P

Q

R

S

VGA_VSYNC

6

29

768

3

806

如表26.1所示,A段与O段都是拉低的起始段,然后B段与P段是准备段,C段与Q段是数据段,D段与R段都是结束段,至于E段是ABCD段的总和,S段则是OPRQ段的总和。HSYNC有时候也称为列像素或者X,VSYNC则称为行像素或者Y。列像素最小的周期时间(或称像素时钟 Pixel Clock)是 1/65Mhz,行像素最小的周期时间则是 E段 × 1/65Mhz。话题继续之前,笔者再稍微补充一下细节内容。

图26.5 计数例子。

我们先假设 HSYNC长度有8,前3个为拉低的起始段,后8个为拉高的数据段。如图26.5所示,C1总共计数两次,分别是T0~T8还有T8~T16。期间,第一次是无效的计数,所以HSYNC并没有拉低起始段。换之,第二次开始才是有效的计数,因为HSYNC拉低起始段。为此,Verilog可以这样描述:

if( C1 == 7 ) HSYNC <= 1’b0;

else if( C1 == 2 ) HSYNC <= 1’b1;

假设起始段声明为A,而结束段声明为D,E则是A与D的总和,那么内容可以继续修改:

parameter A = 3, D = 5, E = 8;

if( C1 == E -1 ) HSYNC <= 1’b0;

else if( C1 == A -1 ) HSYNC <= 1’b1;

明白以后,我们便可以用Verilog 描述表26.1的内容,结果如代码26.1所示:

1. parameter SA = 11'd136, SE = 11'd1344;

2. parameter SO = 11'd6, SS = 11'd806;

3. 

4. reg [10:0]C1;

5. reg [9:0]C2;

6. reg rH,rV;

7. 

8. always @ ( posedge CLOCK or negedge RESET )

9. if( !RESET )

10. begin

11. C1 <= 11'd0;

12. C2 <= 10'd0;

13. rH <= 1'b1;

14. rV <= 1'b1;

15. end

16. else 

17. begin

18. if( C1 == SE -1 ) rH <= 1'b0; 

19. else if( C1 == SA -1 ) rH <= 1'b1;

20. 

21. if( C2 == SS -1 ) rV <= 1'b0;

22. else if( C2 == SO -1 ) rV <= 1'b1;

23. 

24. if( C2 == SS -1 ) C2 <= 10'd0;

25. else if( C1 == SE -1 ) C2 <= C2 + 1'b1;

26. 

27. if( C1 == SE -1 ) C1 <= 11'd0;

28. else C1 <= C1 + 1'b1; 

29. end


代码26.1

第1~2行是 A段与E段,O段还有S段的常量声明。第4~6是相关的寄存器声明,第10~14行则是这些寄存器的复位操作。请注意一下第8行的主时钟是65Mhz。第27~28行是针对列像素的计数器C1,计数范围为0~1343。第24~25行是针对行像素的计数器C2,计数范围为0~805。第18~19行用来控制 HSYNC,第21~22行则是用来控制VSYNC。

理解完毕 HSYNC 与 VSYNC信号以后,接下来我们便要学习 RGB 信号。事实上,HSYNC与VSYNC的数据段,其实也是RGB有效的数据段。表26.1基于1024×768 @ 60Hz,所以HSYNC数据段的长度有1024,而VSYNC的数据段的长度则有768。如果VGA拥有显示标准,那么RGB信号也有所谓的颜色标准。根据Photoshop ,常见的颜色如表26.2所示:

表26.2 常见的颜色。

颜色

位宽

黑白

1bit

灰度级

4bit,8bit

RGB

8bit,16bit,24bit,32bit

黑白可谓是最典型的颜色,不禁让笔者想起怀念的小绿人。灰度级正如字面上的意思,意指失去颜色的图像,4bit有16灰度级,8bit则有256灰度级。RGB是电脑专用的颜色标准,从过去发展至今,RGB的颜色分辨率也从4bit发展到32bit。虽说8位RGB是历史悠久的前辈,不过至今它还未退休吔,例如街边的LED招牌。8位RGB也称为索引色。

16位RGB可是人眼比较接近的的颜色标准,也是本实验的主题,其中RGB[15:11]是5位红色,RGB[10:5]是6位绿色,RGB[4:0]则是5位蓝色,16位RGB称为高彩。24位RGB也是目前流行的颜色标准,其中字节0为蓝色,字节1为绿色,字节2为红色,24位称为真彩。32位RGB相较24位RGB则多了一个字节3的通道字节,通道也指透明效果,它称为全彩。

为消除读者对RGB的恐惧,笔者就解剖一下16位RGB:

图26.6 4×1与16位RGB图像。

图26.6是一幅宽为4高为1的16位RGB图像,如果从左至右开始数起,列0为饱和的红色,列1为饱和的绿色,列2为饱和的蓝色,列3为纯黑。如果将其卸甲并且一字排开高位在前的内容,结果如表26.2所示:

表示26.3 4×1与16位RGB图像内容(高位在前)。

列0

列1

列2

列3

饱和红

饱和绿

饱和蓝

16’hF800

16’h07E0

16’h001F

16’h0000

16’b1111_1000_0000_0000

16’b0000_0111_1110_0000

16’b0000_0000_0001_1111

16’b0000_0000_0000_0000

如表26.3所示,我们可以看见列0的红色占据RGB[15:11]的位置,亦即红色有25=32的分辨率。列1的绿色则是占据RGB[10:5]的位置,亦即26= 64的分辨率。至于列2的蓝色占据RGB[4:0]的位置,结果它有25=32的分辨率。

图26.7 4×1与16位RGB图像(红色渐变)。

图26.7也是一幅宽有4高有1的16位RGB图像,不过是红色渐变的图像。如果从左至右开始数起,列0为4/4饱和的红色,列1为3/4饱和的红色,列2为2/4饱和的红色,列3为1/4饱和的红色。笔者随之伸出魔爪将其卸甲,然后一字排开高位在前的内容,结果如表26.4所示:

表示26.4 4×1与16位RGB图像内容(红色渐变与高位在前)。

列0

列1

列2

列3

4/4饱和红

3/4饱和红

2/4饱和红

1/4饱和红

16’hF800

16’hC000

16’h8000

16’h4000

16’b1111_1000_0000_0000

16’b1100_0000_0000_0000

16’b1000_0000_0000_0000

16’b0100_0000_0000_0000

如表26.4所示,我们可以看见列0的红色充斥整座RGB[15:11],列1的红色则是占据其中的14份内容而已。列2更惨,它占据的份儿只有8份而已,而最惨的列3则是只有麻雀眼泪般的4份内容而已。

图26.8 实验用的小可爱。

图26.6是实验二十六所用的图像资源,内容是一群可爱的比卡丘在玩耍。图像大小为128 × 96 × 16 bit,结果容量为:

128 × 96 × 16 bit = 196608 bit

如果储存器的位宽有16位,那么储存器的地址位宽则是:

128 × 96 = 12208

因此,必须建立位宽为 214 的地址信号:

214 = 16384

简单而言,图26.8的X为128还有Y为96,而Y也充当偏移量的角色。为此,正确的地址公式如下所示:

Address = X + ( Y × 128 )

= X + (Y << 7)

理解完毕这些准备知识以后,我们便可以开始建模了。

图26.9 实验二十六(VGA基础模块)的建模图。

图26.9是实验二十六的建模图,其中PLL将50Mhz 的CLOCK增至 65Mhz。VGA储存模块有 128 × 96 × 16 bit 的容量,里边暂存皮卡丘的图像信息。VGA功能模块则是负责1024 × 768 @ 60Hz的显示标准,还有将内部的计数信息反馈给VGA控制模块。VGA控制模块位与中间,它借助 iAddr然后转换成为图像读取的地址,最后再将反馈回来的图像信息发至 VGAD顶层信号。

vga_savemod.v

图26.10 VGA储存模块的建模图。

图26.10虽是VGA储存模块的建模图,不过内容却是简单ROM模块,具体内容让我们来看代码吧。

1. module vga_savemod

2. (

3. input CLOCK, RESET,

4. input [13:0]iAddr,

5. output [15:0]oData

6. );

7. (* ramstyle = "no_rw_check , m9k" , ram_init_file = "pikachu_128x96_16_msb.mif" *) 

8. reg [15:0] RAM[12287:0];

9. reg [15:0]D1;

10. 

11. always @ ( posedge CLOCK or negedge RESET )

12. if( !RESET )

13. D1 <= 16'd0;

14. else 

15. D1 <= RAM[ iAddr ];

16. 

17. assign oData = D1;

18. 

19. endmodule


以上内容虽然简单,不过还是注意一下第7行的建立声明。其中 no_rw_check是用来告诉综合器无视 read during write 的检测,m9k则声明片上内存用 m9k,至于ram

init file 则是RAM的初始化内容,亦即图26.8。

vga_funcmod.v

图26.11 VGA功能模块的建模图。

图26.11是VGA功能模块的建模图,左边是反馈给朋友的oAddr,其中[20:10]是X地址,[9:0]则是Y地址。右边是驱动顶层的VGA_HSYNC与 VGA_VSYNC。

1. module vga_funcmod 

2. (

3. input CLOCK, RESET,

4. output VGA_HSYNC, VGA_VSYNC,

5. output [20:0]oAddr

6. );

7. parameter SA = 11'd136, SE = 11'd1344;

8. parameter SO = 11'd6, SS = 11'd806;

9. 


以上内容为相关的出入端声明,还有A段,E段,O段还有S段的常量声明。

10. reg [10:0]C1;

11. reg [9:0]C2;

12. reg rH,rV;

13. 

14. always @ ( posedge CLOCK or negedge RESET )

15. if( !RESET )

16. begin

17. C1 <= 11'd0;

18. C2 <= 10'd0;

19. rH <= 1'b1;

20. rV <= 1'b1;

21. end

22. else 


以上内容为相关的寄存器声明,其中C1为HSYNC计数,C2则为VSYNC计数。

23. begin

24. 

25. if( C1 == SE -1 ) rH <= 1'b0; 

26. else if( C1 == SA -1 ) rH <= 1'b1;

27. 

28. if( C2 == SS -1 ) rV <= 1'b0;

29. else if( C2 == SO -1 ) rV <= 1'b1;

30. 

31. if( C2 == SS -1 ) C2 <= 10'd0;

32. else if( C1 == SE -1 ) C2 <= C2 + 1'b1;

33. 

34. if( C1 == SE -1 ) C1 <= 11'd0;

35. else C1 <= C1 + 1'b1;

36. 

37. end

38. 


以上内容为 HSYNC 与 VSYNC的驱动过程。第35~36行是计数HSYNC,第32~33行则是计数VYSNC,一个VSYNC为1344个HSYNC。第26~27行是 rH的控制,第29~30行则是 rV的控制。

39. reg [1:0]B1,B2,B3;

40. 

41. always @ ( posedge CLOCK or negedge RESET )

42. if( !RESET )

43. { B3, B2, B1 } <= 6'b11_11_11;

44. else

45. begin

46. B1 <= { rH,rV };

47. B2 <= B1;

48. B3 <= B2;

49. end 

50. 


第40~51则是用来延迟 rH与rV的周边操作,期间总共延迟3个时钟。详细内容往后再说。

51. assign { VGA_HSYNC, VGA_VSYNC } = B3;

52. assign oAddr = {C1,C2}

53. 

54. endmodule


以上内容为相关的输出驱动。

vga_ctrlmod.v

图26.12 VGA控制模块的建模图。

图26.12是VGA控制模块的建模图,而设计思路来源《整合篇》。左边信号连接储存模块,右边信号连接功能模块,北边信号则驱动顶层。具体内容还是让我们来看代码吧:

1. module vga_ctrlmod

2. (

3. input CLOCK, RESET,

4. output [15:0]VGAD,

5. output [13:0]oAddr,

6. input [15:0]iData,

7. input [20:0]iAddr

8. );


以上内容为相关的出入端声明。

9. parameter SA = 11'd136, SB = 11'd160, SC = 11'd1024, SD = 11'd24, SE = 11'd1344;

10. parameter SO = 11'd6, SP = 11'd29, SQ = 11'd768, SR = 11'd3, SS = 11'd806;

11. 

12. // Height , width, x-offset and y-offset

13. parameter XSIZE = 8'd128, YSIZE = 8'd96, XOFF = 10'd0, YOFF = 10'd0; 

14. 

15. wire isX = ( (iAddr[20:10] >= SA + SB + XOFF -1 ) && ( iAddr[20:10] <= SA + SB + XOFF + XSIZE -1) );

16. wire isY = ( (iAddr[9:0] >= SO + SP + YOFF -1 ) && ( iAddr[9:0] <= SO + SP + YOFF + YSIZE -1) );

17. wire isReady = isX & isY;

18. 

19. wire [31:0] x = iAddr[20:10] - XOFF - SA - SB -1; 

20. wire [31:0] y = iAddr[9:0] - YOFF - SO - SP -1;

21. 


以上内容为相关的常量声明。第9~10行是针对 1024 × 768 @ 60Hz 显示标准的常量声明。第13行声明图像信息,如大小还有位置。第15行声明有效的列像素,第16行则声明有效的Y像素,至于第17行则是声明图像的有效区域,注意它们都是即时事件。第19~20行用来是计算有效的X与Y,主要用来取得图像的读取地址,注意它们也是即时事件。

22. reg [31:0]D1;

23. reg [15:0]rVGAD;

24. 

25. always @ ( posedge CLOCK or negedge RESET )

26. if( !RESET )

27. begin

28. D1 <= 18'd0;

29. rVGAD <= 16'd0;

30. end

31. else


以上内容为相关的寄存器声明还有复位操作。

32. begin

33. 

34. // step 1 : compute data address and index-n

35. if( isReady )

36. D1 <= (y << 7) + x; 

37. else

38. D1 <= 14'd0;

39. 

40. // step 2 : reading data from rom

41. // but do-nothing

42. 

43. // step 3 : assign RGB_Sig

44. rVGAD <= isReady ? iData : 16'd0;

45. 

46. end

47. 


以上内容为VGA控制模块的核心操作。该控制模块采用流水操作,一边不断发送读取地址,一边不断等待图像信息读出,一边不断输出图像信息。

图26.13 VGA功能模块的流水操作。

从某种程度来说,实验二十六的VGA基础模块可以看成如图26.13所示。VGA功能模块输出HSYNC与VSYNC的瞬间,它也发送 Addr 给 VGA控制模块。VGA控制模块计算读取地址以后再发送给 VGA储存模块,VGA储存模块随意也将图像信息返回发送给VGA控制模块,最后VGA控制模块再将图像信息驱动至 VGAD。

简单来说,图像信息一共慢HSYNC和VSYNC信号3拍,因此VGA功能模块多了旁路的周边操作,目的是为了同步 HSYNC,VSYNC还有 VGAD之间的延迟。返回话题,有效的读取地址只有 isReady 拉高的时候,如代码行第36所示。同样,有效的图像信息也是 isReady 拉高的时候,如代码行第45所示。

48. assign VGAD = rVGAD;

49. assign oAddr = D1[13:0];

50. 

51. endmodule


以上内容为相关的输出驱动。

vga_basemod.v

该模块是VGA基础模块,至于内部的连线部署请参考图26.9。

1. module vga_basemod

2. (

3. input CLOCK, RESET,

4. output VGA_HSYNC, VGA_VSYNC,

5. output [15:0]VGAD

6. );

7. wire CLOCK_65M;

8. 

9. pll_module U1 

10. (

11. .inclk0 ( CLOCK ),

12. .c0 ( CLOCK_65M ) // CLOCK 65Mhz

13. );

14. 

15. wire [20:0]AddrU2;

16. 

17. vga_funcmod U2 // 1024 * 758 @ 60Hz

18. (

19. .CLOCK( CLOCK_65M ), 

20. .RESET( RESET ),

21. .VGA_HSYNC( VGA_HSYNC ), 

22. .VGA_VSYNC( VGA_VSYNC ),

23. .oAddr( AddrU2 ),

24. ); 

25. 

26. wire [15:0]DataU3;

27. 

28. vga_savemod U3

29. (

30. .CLOCK( CLOCK_65M ),

31. .RESET( RESET ),

32. .iAddr( AddrU4 ),

33. .oData ( DataU3 )

34. );

35. 

36. wire [13:0]AddrU4;

37. 

38. vga_ctrlmod U4 // 128 * 96 * 16bit, X0,Y0

39. (

40. .CLOCK( CLOCK_65M ),

41. .RESET( RESET ),

42. .VGAD( VGAD ),

43. .iData( DataU3 ),

44. .oAddr( AddrU4 ),

45. .iAddr( AddrU2 ),

46. );

47. 

48. endmodule


详细的内容请读者自己看着办吧。

图26.14 实验二十六的结果。

综合完毕便下载程序,如果实验成功,分辨率为1024 * 768 的显示器左上角便会出现一幅128 × 96 × 16 bit 的图像显示在 X0与Y0的位置,结果如图26.14所示。

细节一:完整的个体模块

实验二十六的VGA基础模块虽为就绪状态,不过它是一只能力有限的家伙。原因很单纯,因为储存128 × 96 × 16 bit 的图像已经是EP4CE6F17C8 这块FPGA的极限,这家伙的度量很小,所以片上内存也不怎么大。

细节二: 片上内存

笔者曾经说过,片上内存是资粮很棒的储存资源,它不仅访问时间短,自定义强,而且操作也简单,但是肚量是它永远的痛。EP4CE6F17C8 虽然有 476280 bit 的片上内存,不过实际可用的范围只有其中的90%而已。

细节三: 缓冲图像的储存模块

如果片上内存不行,那么SDRAM当然是最好的替代。同样,笔者也曾经说过,SDRAM的优点除了肚量大以外,无论是访问速度还有操作程度也不及片上内存。一旦SDRAM谋朝散位成功,VGA控制模块,VGA储存模块还有VGA功能模块之间就会失去同步性。此外,SDRAM也不能经过综合器初始化内容。不管怎么样,实验二十六已经完成任务,以后的问题以后再说吧。


分享给朋友:
您可能感兴趣的文章:
随机阅读: