10. 基于SD卡的HDMI图像显示

10.1. 章节导读

在上一章节我们学习了SD卡的相关理论知识,设计并实现了SPI模式下的SD卡数据读写控制器。本章节中,我们利用上一章节设计的SD卡读写控制器,将存储在SD卡的图像数据读取并通过HDMI显示出来,目的是加深读者对前面相关知识的理解掌握。

10.2. 理论学习

对于本章节涉及的SD卡相关理论知识,读者可查阅“SD卡数据读写控制”的理论学习小节;对于本章节涉及的HDMI显示的相关理论知识,读者可查阅“HDMI显示器驱动设计与验证”的理论学习小节,此处不再赘述。

10.3. 实战演练

10.3.1. 实验目标

使用SD卡数据读写控制器读取事先存储在SD卡的图片数据,将读取的图片数据通过SDRAM数据读写控制器暂存在SDRAM芯片中,通过HDMI显示屏将暂存在SDRAM 的图片显示出来。

SD卡内存储图片有两张,两张图片交替显示在HDMI显示屏上,分辨率为640*480。

10.3.2. 硬件资源

参见“SD卡数据读写控制”、“HDMI显示器驱动设计与验证”章节当然“硬件资源”小节。

10.3.3. 程序设计

10.3.3.1. 整体说明

在程序设计开始之前,我们先来对实验工程进行一个整体说明,让读者了解整个实验工程的框架结构。工程整体框图,具体见图 51‑1;各子功能模块简介,具体见表格 51‑1。

SD_HDMI002

图 51‑1 工程整体框图

表格 51‑1 子功能模块功能描述

模块名称

功能描述

clk_gen

时钟生成模块

data_rd_ctrl

图片数据读控制模块

sd_ctrl

SD卡读写控制器

sdram_top

SDRAM读写控制器

vga_ctrl

VGA显示驱动模块

hdmi_ctrl

HDMI显示驱动模块

sd_hdmi_pic

顶层模块

本实验工程共调用7个模块(SD卡读写控制器和SDRAM读写控制器均视为单个模块),由图标可知,模块sd_hdmi_pic作为实验工程的顶层模块,内部实例化5个子功能模块。

时钟生成模块clk_gen,为各子功能模块、外部SD卡和SDRAM提供工作时钟;图片数据读控制模块data_rd_ctrl,控制SD卡读写控制器读取SD卡内待显示图片的数据读取;SD卡读写控制器sd_ctrl,读取SD卡内待显示图片数据;SDRAM读写控制器sdram_top,控制SDRAM芯片的数 据读写操作;VGA显示驱动模块vga_ctrl,读出SDRAM的图像数据按照VGA时序进行处理;hdmi_ctrl模块将VGA传入的图像数据进行HDMI时序处理,并显示再HDMI显示屏上。

10.3.3.2. 图片数据预处理

讲到这里,读者应该对实验工程的整体框架有了了解,在开始子功能模块的讲解之前,我们先来对图片进行一下预处理,将图片转换为指定的数据格式,即bin文件。

  • 选取要显示的图片两张,使用Window系统自带的画图工具对图片进行处理,将图片处理为分辨率640*480,如图 51‑2所示。

SD_HDMI003

图 51‑2 图片处理

  • 如图 51‑3所示,使用软件“Image2Lcd”打开图片,设置相关参数,点击保存;

SD_HDMI004

图 51‑3 图片预处理(一)

  • 选取位置保存bin文件,对另外一张图片做相同处理;

SD_HDMI005

图 51‑4 图片预处理(二)

  • 使用计算机将SD卡格式化,将两图片生成的bin文件存储在格式化后的SD卡中;

SD_HDMI006

图 51‑5 图片预处理(三)

  • 以管理员身份运行软件WinHex软件,工具 —> 打开磁盘;

SD_HDMI007

图 51‑6 图片预处理(四)

  • 双击打开SD卡,记录两个bin文件的第一扇区地址;

SD_HDMI008

图 51‑7 图片预处理(五)

SD_HDMI009

图 51‑8 图片预处理(六)

  • 双击查看bin文件,记录两个bin文件的文件大小;

SD_HDMI010

图 51‑9 图片预处理(七)

图片预处理完成,记录两组数据,在后面的子功能模块会用到。

10.3.3.3. 时钟生成模块

时钟生成模块clk_gen,为各子功能模块、外部SD卡和SDRAM提供工作时钟。输入50MHz板卡晶振时钟信号,输出5路时钟信号c0-c4,时钟频率为125MHz、125MHz(相位偏移)、50MHz、50MHz(相位偏移)、25MHz,前两路时钟信号输入SDRAM读写控制器和SDRAM存储芯片;中 间两路输入SD卡读写控制器和SD存储卡;最后一路输入VGA显示驱动模块;25MHz、125MHz时钟作为HDMI显示驱动模块输入时钟。

时钟生成模块clk_gen,调动IP和生成,具体生成方法,读者可参阅“快速开发的法宝 — IP核”章节,此处不再赘述。

10.3.3.4. SD卡数据读写控制器

SD卡数据读写控制器的相关内容在“SD卡数据读写控制”章节已经做了详细说明,读者若有遗忘可回顾翻阅,此处不再赘述。

10.3.3.5. SDRAM数据读写控制器

SDRAM数据读写控制器的相关内容在“SDRAM读写控制器的设计与验证”章节已经做了详细说明,读者若有遗忘可回顾翻阅,此处不再赘述。

10.3.3.6. 图片数据读控制模块

模块框图

本实验工程中的子功能模块大多为复用模块,在前文中都详细介绍过,图片数据读控制模块data_rd_ctrl为新增的子功能模块,它的主要作用是为SD卡数据读写控制器提供读使能和读地址,控制其读取SD卡中的图片数据。模块框图,具体见图 51‑10;输入输出信号功能描述,具体见表格 51‑2。

SD_HDMI011

图 51‑10 图片数据读控制模块框图

表格 51‑2 模块输入输出信号功能描述

信号

位宽

类型

功能描述

sys_clk

1bit

input

模块工作时钟

sys_rst_n

1bit

input

复位信号,低电平有效

rd_busy

1bit

input

读忙信号

rd_en

1bit

output

SD卡读使能信号

rd_addr

32bit

output

SD卡读扇区地址

由图表可知,模块有3路输入、2路输出,共5路信号;输入信号中时钟和复位信号必不可少,时钟信号与输入SD卡读写控制器的时钟为同一时钟信号,频率50MHz,未做相位偏移处理,复位信号低电平有效,rd_busy为有SD卡读写控制器传入的读忙信号,作为约束条件控制读使能信号的产生;输出信号rd_en为读使能 信号,rd_addr为读扇区地址,两信号传入SD卡读写控制器,控制SD卡的数据读取。

波形图绘制

在模块框图部分,我们对模块的输入输出信号功能做了介绍,那么如何利用输入信号来产生争取的输入信号,实现模块功能呢。接下来,我们通过波形图的绘制,为读者讲解模块的设计与功能实现。模块整体波形图,具体见。

SD_HDMI012

图 51‑11 图片数据读控制模块整体波形图

由图可知,为了实现模块功能,我们声明了诸多寄存器来辅助输出信号的产生,接下来,我们会对各信号的设计目的与实现方法作出详细介绍。

输入的时钟和复位信号不需要多说,这是模块工作必不可少。输入的rd_busy读忙信号的目的是,使用读忙信号的下降沿,来产生读使能信号rd_en,因为读忙信号由高电平转为低电平时,表示SD卡一次读扇区操作结束,上次的读操作结束,就可以开始新的读操作。为了产生读忙信号下降沿,声明寄存器rd_busy_dl y对读忙信号打拍;声明读忙信号下降沿rd_busy_fall,当读忙信号rd_busy为低电平,rd_busy_dly为高电平时,rd_busy_fall为高电平。上述各信号波形图如下。

SD_HDMI013

图 51‑12 rd_busy_fall及相关信号波形图

使用读忙信号下降沿产生读使能信号不失为一个好方法,但是问题来了,SD卡读写控制器只有在输入有效读使能信号才会开始数据读操作,产生读忙信号,那么第一个读使能信号怎么产生?

如果第一个读使能信号由我们自己产生,剩余的读使能信号由读忙信号产生也是可以的,但是这又带来了新的问题。如果一幅图片要读取n次才能读取完成,就会产生n个有效的读忙信号,n个有效的读忙信号就会产生n个读使能信号,再加上产生的第一个读使能信号,一共有n+1个有效的读使能信号,这显然是不正确的。

如何解决这一问题呢,我们可以使用状态机。声明状态机变量state,声明三个状态:IDLE(初始状态)、READ(数据读状态)和WAIT(等待状态)。

当状态机处于初始状态时,我们产生第一个有效的读使能信号传入SD卡读写控制器,SD卡进行第一个扇区的数据读写操作,同时状态机跳转到数据读状态;SD卡读写控制器会产生有效的读忙信号,后面就利用回传的读忙信号产生读使能信号;当检测到第(n-1)个读忙信号下降沿,产生第n个有效读使能信号,同时,状态机跳转到 等待状态,第n个读忙信号下降沿不再产生读使能信号。上述各信号波形图如下。

SD_HDMI014

图 51‑13 读使能信号rd_en及相关信号波形图

使用状态机的方法确定了,状态机的跳转条件我们也要说一下,系统上电后,状态机处于初始状态,随即跳转到数据读状态;当产生的读使能信号与图片所占扇区个数相同时;状态机跳转到等待状态,等待一段时间后跳回初始状态,一幅图片读取完成。

为了实现实现状态机的正确跳转,声明计数器cnt_rd对读使能信号进行计数,初值为0,读使能信号每拉高一次,自加1,计数到最大值清0,状态机以此为条件进行状态跳转;声明计数器cnt_wait对等待状态的等待时间进行计数,初值为0,每个时钟周期自加1,计数到最大值清0,状态机以此为条件跳转。上述各信号波 形图如下。

SD_HDMI015

图 51‑14 状态机跳转相关信号波形图

前面实验目标中我们提到过,我们要实现两幅图片的交替显示,实现这个效果是比较简单的,就是交替读取SD卡的两幅图片即可。

我们声明信号pic_c实现图片读取首地址的切换,pic_c信号初值为低电平,状态机处于初始状态时对其进行取反操作;当pic_c信号为低电平、状态机处于初始化状态时,将图片0的扇区首地址赋值给SD卡读扇区地址rd_addr,状态机处于数据读状态、读使能信号rd_en有效时,SD卡读扇区地址自加1;当p ic_c信号为高电平、状态机处于初始化状态时,将图片1的扇区首地址赋值给SD卡读扇区地址rd_addr,状态机处于数据读状态、读使能信号rd_en有效时,SD卡读扇区地址自加1,这样就实现了图片的交替读取。上述各信号波形图如下。

SD_HDMI016

图 51‑15 读扇区地址rd_addr及相关信号波形图

到这里,模块设计的各信号均已计数完毕,将各信号整合后,接可以得到小节开始部分的模块整体波形图,本波形图的设计与实现仅供参考,读者也可根据自己的理解绘制波形图。

代码编写

模块波形图绘制完毕,参照绘制的波形图进行参考代码编写,模块各信号已经做了详细介绍,这里不再过多说明。模块参考代码,具体见代码清单 51‑1。

代码清单 51‑1 图片数据读控制模块参考代码(data_rd_ctrl.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
module data_rd_ctrl
(
input wire sys_clk , //输入工作时钟,频率50MHz
input wire sys_rst_n , //输入复位信号,低电平有效
input wire rd_busy , //读操作忙信号

output reg rd_en , //数据读使能信号
output reg [31:0] rd_addr //读数据扇区地址
);

////
//\* Parameter and Internal Signal \//
////
//parameter define
parameter IDLE = 3'b001, //初始状态
READ = 3'b010, //读数据状态
WAIT = 3'b100; //等待状态
parameter IMG_SEC_ADDR0 = 32'd16640, //图片1扇区起始地址
IMG_SEC_ADDR1 = 32'd17856; //图片2扇区起始地址
parameter RD_NUM = 11'd1200 ; //单张图片读取次数
parameter WAIT_MAX= 26'd50_000_000 ; //图片切换时间间隔计数最大值

//wire define
wire rd_busy_fall; //读操作忙信号下降沿

//reg defien
reg rd_busy_dly ; //读操作忙信号打一拍
reg [2:0] state ; //状态机状态
reg [10:0] cnt_rd ; //单张图片读取次数计数
reg pic_c ; //图片切换
reg [25:0] cnt_wait ; //图片切换时间间隔计数

////
//\* Main Code \//
////
//rd_busy_dly:读操作忙信号打一拍
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_busy_dly <= 1'b0;
else
rd_busy_dly <= rd_busy;

//rd_busy_fall:读操作忙信号下降沿
assign rd_busy_fall = ((rd_busy == 1'b0) && (rd_busy_dly == 1'b1))
? 1'b1 : 1'b0;

//state:状态机状态
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= IDLE;
else
case(state)
IDLE: state <= READ;
READ:
if(cnt_rd == (RD_NUM - 1'b1))
state <= WAIT;
else
state <= state;
WAIT:
if(cnt_wait == (WAIT_MAX - 1'b1))
state <= IDLE;
else
state <= state;
default: state <= IDLE;
endcase

//pic_c:图片切换
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pic_c <= 1'b0;
else if(state == IDLE)
pic_c <= ~pic_c;
else
pic_c <= pic_c;

//cnt_rd:单张图片读取次数计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_rd <= 11'd0;
else if(state == READ)
if(cnt_rd == RD_NUM - 1'b1)
cnt_rd <= 11'd0;
else if(rd_busy_fall == 1'b1)
cnt_rd <= cnt_rd + 1'b1;
else
cnt_rd <= cnt_rd;

//rd_en:数据读使能信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_en <= 1'b0;
else if(state == IDLE)
rd_en <= 1'b1;
else if(state == READ)
if(rd_busy_fall == 1'b1)
rd_en <= 1'b1;
else
rd_en <= 1'b0;
else
rd_en <= 1'b0;

//rd_addr:读数据扇区地址
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_addr <= 32'd0;
else
case(state)
IDLE:
if(pic_c == 1'b1)
rd_addr <= IMG_SEC_ADDR1;
else
rd_addr <= IMG_SEC_ADDR0;
READ:
if(rd_busy_fall == 1'b1)
rd_addr <= rd_addr + 1'd1;
else
rd_addr <= rd_addr;
default:rd_addr <= rd_addr;
endcase

//cnt_wait:图片切换时间间隔计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_wait <= 26'd0;
else if(state == WAIT)
if(cnt_wait == (WAIT_MAX - 1'b1))
cnt_wait <= 26'd0;
else
cnt_wait <= cnt_wait + 1'b1;
else
cnt_wait <= 26'd0;

endmodule

注:代码中第18-20行,两图片初始扇区为前面图片预处理小节记录的初始扇区地址,扇区读取次数RD_NUM为图片大小除以512的结果,因为SD卡读操作一次读取512个字节。

仿真代码编写

模块参考代码编写完毕,我们开始对模块代码进行仿真验证,验证模块是否能够实现预期功能,正确输出相关信号。模块参考代码,具体见代码清单 51‑2。

代码清单 51‑2 图片数据读控制模块仿真参考代码(tb_data_rd_ctrl.v)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
\`timescale 1ns/1ns
module tb_data_rd_ctrl();

////
//\* Internal Signal and Defparam \//
////
//wire define
wire rd_en ; //数据读使能信号
wire [31:0] rd_addr ; //读数据扇区地址

//reg define
reg sys_clk ; //系统时钟
reg sys_rst_n ; //复位信号
reg cnt_en ; //计数器计数使能
reg [8:0] cnt ; //计数器
reg rd_busy ; //模拟读忙信号

//defparam
//重定义模块中的相关参数
defparam data_rd_ctrl_inst.RD_NUM = 20 ; //单张图片读取次数
defparam data_rd_ctrl_inst.WAIT_MAX = 3000 ; //图片切换时间间隔计数最大值

////
//\* Clk And Rst \//
////

//时钟、复位信号
initial
begin
sys_clk = 1'b1 ;
sys_rst_n <= 1'b0 ;
#200
sys_rst_n <= 1'b1 ;
end

always #10 sys_clk = ~sys_clk;

//cnt_en:计数器计数使能
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_en <= 1'b0;
else if(cnt == 9'd511)
cnt_en <= 1'b0;
else if(rd_en == 1'b1)
cnt_en <= 1'b1;
else
cnt_en <= cnt_en;


//cnt:计数器,约束读忙信号rd_busy
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt <= 9'd0;
else if(cnt_en == 1'b1)
cnt <= cnt + 1'b1;
else
cnt <= 9'd0;

//rd_busy:模拟读忙信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_busy <= 1'b0;
else if(cnt <= 9'd60)
rd_busy <= 1'b0;
else
rd_busy <= 1'b1;

////
//\* Instantiation \//
////
//------------- data_rd_ctrl_inst -------------
data_rd_ctrl data_rd_ctrl_inst
(
.sys_clk (sys_clk ), //输入工作时钟,频率50MHz
.sys_rst_n (sys_rst_n ), //输入复位信号,低电平有效
.rd_busy (rd_busy ), //读操作忙信号

.rd_en (rd_en ), //数据读使能信号
.rd_addr (rd_addr ) //读数据扇区地址
);

endmodule

注:为缩短仿真时间,模块中的相关参数均作了重定义。

仿真波形分析

模块仿真波形图如图 51‑16至图 51‑21,仿真波形图与绘制波形图个信号波形吻合,模块通过仿真验证。

SD_HDMI017

图 51‑16 图片数据读控制模块整体仿真波形图

SD_HDMI018

图 51‑17 图片数据读控制模块局部仿真波形图(一)

SD_HDMI019

图 51‑18 图片数据读控制模块局部仿真波形图(二)

SD_HDMI020

图 51‑19 图片数据读控制模块局部仿真波形图(三)

SD_HDMI021

图 51‑20 图片数据读控制模块局部仿真波形图(四)

SD_HDMI022

图 51‑21 图片数据读控制模块局部仿真波形图(五)

10.3.3.7. VGA驱动控制模块

该模块我们在“HDMI显示器驱动设计与验证”章节中说过,该模块是VGA显示的驱动模块,由于我们征途Mini开发板中没有VGA接口,所以该文档我们没有对VGA章节进行讲解。但是我们征途Pro开发板配套文档中是有对VGA相关内容进行讲解的,所以该模块我们可参阅征途Pro开发板配套文档中“VGA显示器驱动 设计与验证”章节进行了解。

10.3.3.8. HDMI驱动控制模块

HDMI驱动控制模块的相关内容在“HDMI显示器驱动设计与验证”章节已经做了详细说明,读者若有遗忘可回顾翻阅,此处不再赘述。

10.3.3.9. 顶层模块

讲到这里,实验工程涉及的子功能模块均已介绍完毕,接下来说明整个实验工程的顶层模块。

模块框图

顶层模块将各子功能模块实例化其中,连接各自对应信号,顶层模块模块框图如图 51‑22所示。

SD_HDMI023

图 51‑22 顶层模块模块框图

由图可知,顶层模块有输入输出信号共22路,3路输入信号有时钟信号、复位信号、和SD卡传入的主输入从输出信号sd_miso;16路输出信号中有3路输出到SD卡,包括SD卡时钟信号sd_clk、片选信号sd_cs_n和主输出从输入信号sd_mosi; 10路输出到SDRAM,包括时钟信号、使能信号、地址信号和数据信号;4路输出到HDMI驱动控制模块,驱动控制图像显示。

代码编写

顶层模块的代码较为较为简单,只是对各模块进行实例化以及连接各自对应信号,无需波形图的绘制。编写顶层模块参考代码,具体见代码清单 51‑3。

代码清单 51‑3 顶层模块参考代码(sd_hdmi_pic.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
module sd_hdmi_pic
(
input wire sys_clk , //输入工作时钟,频率50MHz
input wire sys_rst_n , //输入复位信号,低电平有效
//SD卡
input wire sd_miso , //主输入从输出信号
output wire sd_clk , //SD卡时钟信号
output wire sd_cs_n , //片选信号
output wire sd_mosi , //主输出从输入信号
//SDRAM
output wire sdram_clk , //SDRAM 芯片时钟
output wire sdram_cke , //SDRAM 时钟有效
output wire sdram_cs_n , //SDRAM 片选
output wire sdram_ras_n , //SDRAM 行有效
output wire sdram_cas_n , //SDRAM 列有效
output wire sdram_we_n , //SDRAM 写有效
output wire [1:0] sdram_ba , //SDRAM Bank地址
output wire [1:0] sdram_dqm , //SDRAM 数据掩码
output wire [12:0] sdram_addr , //SDRAM 行/列地址
inout wire [15:0] sdram_dq , //SDRAM 数据
//HDMI
output wire ddc_scl ,
output wire ddc_sda ,
output wire tmds_clk_p ,
output wire tmds_clk_n , //HDMI时钟差分信号
output wire [2:0] tmds_data_p ,
output wire [2:0] tmds_data_n //HDMI图像差分信号
);

////
//\* Parameter and Internal Signal \//
////
//parameter define
parameter H_VALID = 24'd640 ; //行有效数据
parameter V_VALID = 24'd480 ; //列有效数据

//wire define
wire rst_n ; //复位信号
wire clk_125m ; //生成100MHz时钟
wire clk_125m_shift ; //生成100MHz时钟,相位偏移180度
wire clk_50m ; //生成50MHz时钟
wire clk_50m_shift ; //生成50MHz时钟,相位偏移180度
wire clk_25m ; //生成25MHz时钟
wire locked ; //时钟锁定信号
wire sys_init_end ; //系统初始化完成

wire vga_hs ; //输出行同步信号
wire vga_vs ; //输出场同步信号
wire [15:0] vga_rgb ; //输出像素信息
wire rgb_valid ; //VGA有效显示区域

wire sd_rd_en ; //开始写SD卡数据信号
wire [31:0] sd_rd_addr ; //读数据扇区地址
wire sd_rd_busy ; //读忙信号
wire sd_rd_data_en ; //数据读取有效使能信号
wire [15:0] sd_rd_data ; //读数据
wire sd_init_end ; //SD卡初始化完成信号

wire wr_en ; //sdram_ctrl模块写使能
wire [15:0] wr_data ; //sdram_ctrl模块写数据
wire rd_en ; //sdram_ctrl模块读使能
wire [15:0] rd_data ; //sdram_ctrl模块读数据
wire sdram_init_end ; //SDRAM初始化完成

////
//\* Main Code \//
////
//rdt_n:复位信号,系统复位与时钟锁定取与
assign rst_n = sys_rst_n && locked;
assign ddc_scl = 1'b1;
assign ddc_sda = 1'b1;

//sys_init_end:系统初始化完成,SD卡和SDRAM均完成初始化
assign sys_init_end = sd_init_end && sdram_init_end;

////
//\* Instantiation \//
////
//------------- clk_gen_inst -------------
clk_gen clk_gen_inst
(
.areset (~sys_rst_n ), //复位信号,高有效
.inclk0 (sys_clk ), //输入系统时钟,50MHz

.c0 (clk_125m ), //生成100MHz时钟
.c1 (clk_125m_shift ), //生成100MHz时钟,相位偏移180度
.c2 (clk_50m ), //生成50MHz时钟
.c3 (clk_50m_shift ), //生成50MHz时钟,相位偏移180度
.c4 (clk_25m ), //生成25MHz时钟
.locked (locked ) //时钟锁定信号
);

//------------- data_rd_ctrl_inst -------------
data_rd_ctrl data_rd_ctrl_inst
(
.sys_clk (clk_50m ), //输入工作时钟,频率50MHz
.sys_rst_n (rst_n & sys_init_end ), //输入复位信号,低电平有效
.rd_busy (sd_rd_busy ), //读操作忙信号

.rd_en (sd_rd_en ), //数据读使能信号
.rd_addr (sd_rd_addr ) //读数据扇区地址
);

//------------- sd_ctrl_inst -------------
sd_ctrl sd_ctrl_inst
(
.sys_clk (clk_50m ), //工作时钟,频率50MHz
.sys_clk_shift (clk_50m_shift ), //工作时钟,频率50MHz,相位偏移180度
.sys_rst_n (rst_n ), //复位信号,低电平有效

.sd_miso (sd_miso ), //主输入从输出信号
.sd_clk (sd_clk ), //SD卡时钟信号
.sd_cs_n (sd_cs_n ), //片选信号
.sd_mosi (sd_mosi ), //主输出从输入信号

.wr_en (1'b0 ), //数据写使能信号
.wr_addr (32'b0 ), //写数据扇区地址
.wr_data (16'b0 ), //写数据
.wr_busy ( ), //写操作忙信号
.wr_req ( ), //写数据请求信号

.rd_en (sd_rd_en ), //数据读使能信号
.rd_addr (sd_rd_addr ), //读数据扇区地址
.rd_busy (sd_rd_busy ), //读操作忙信号
.rd_data_en (sd_rd_data_en ), //读数据标志信号
.rd_data (sd_rd_data ), //读数据

.init_end (sd_init_end ) //SD卡初始化完成信号
);

//------------- sdram_top_inst -------------
sdram_top sdram_top_inst
(
.sys_clk (clk_125m ), //sdram 控制器参考时钟
.clk_out (clk_125m_shift ), //用于输出的相位偏移时钟
.sys_rst_n (rst_n ), //系统复位
//用户写端口
.wr_fifo_wr_clk (clk_50m ), //写端口FIFO: 写时钟
.wr_fifo_wr_req (sd_rd_data_en ), //写端口FIFO: 写使能
.wr_fifo_wr_data (sd_rd_data ), //写端口FIFO: 写数据
.sdram_wr_b_addr (24'd0 ), //写SDRAM的起始地址
.sdram_wr_e_addr (H_VALID*V_VALID), //写SDRAM的结束地址
.wr_burst_len (10'd512 ), //写SDRAM时的数据突发长度
.wr_rst (~rst_n ), //写端口复位
//用户读端口
.rd_fifo_rd_clk (clk_25m ), //读端口FIFO: 读时钟
.rd_fifo_rd_req (rd_en ), //读端口FIFO: 读使能
.rd_fifo_rd_data (rd_data ), //读端口FIFO: 读数据
.sdram_rd_b_addr (24'd0 ), //读SDRAM的起始地址
.sdram_rd_e_addr (H_VALID*V_VALID), //读SDRAM的结束地址
.rd_burst_len (10'd512 ), //从SDRAM中读数据时的突发长度
.rd_fifo_num ( ), //读fifo中的数据量
.rd_rst (~rst_n ), //读端口复位
//用户控制端口
.read_valid (1'b1 ), //SDRAM 读使能
.pingpang_en (1'b0 ), //SDRAM 乒乓操作使能
.init_end (sdram_init_end ), //SDRAM 初始化完成标志
//SDRAM 芯片接口
.sdram_clk (sdram_clk ), //SDRAM 芯片时钟
.sdram_cke (sdram_cke ), //SDRAM 时钟有效
.sdram_cs_n (sdram_cs_n ), //SDRAM 片选
.sdram_ras_n (sdram_ras_n ), //SDRAM 行有效
.sdram_cas_n (sdram_cas_n ), //SDRAM 列有效
.sdram_we_n (sdram_we_n ), //SDRAM 写有效
.sdram_ba (sdram_ba ), //SDRAM Bank地址
.sdram_addr (sdram_addr ), //SDRAM 行/列地址
.sdram_dq (sdram_dq ), //SDRAM 数据
.sdram_dqm (sdram_dqm ) //SDRAM 数据掩码
);

//------------- vga_ctrl_inst -------------
vga_ctrl vga_ctrl_inst
(
.vga_clk (clk_25m ), //输入工作时钟,频率25MHz
.sys_rst_n (rst_n ), //输入复位信号,低电平有效
.data_in (rd_data ), //待显示数据输入

.rgb_valid (rgb_valid ), //VGA有效显示区域
.data_req (rd_en ), //数据请求信号
.hsync (vga_hs ), //输出行同步信号
.vsync (vga_vs ), //输出场同步信号
.rgb (vga_rgb ) //输出像素信息
);

//------------- hdmi_ctrl_inst -------------
hdmi_ctrl hdmi_ctrl_inst
(
.clk_1x (clk_25m ), //输入系统时钟
.clk_5x (clk_125m ), //输入5倍系统时钟
.sys_rst_n (rst_n ), //复位信号,低有效
.rgb_blue ({vga_rgb[4:0],3'b0} ), //蓝色分量
.rgb_green ({vga_rgb[10:5],2'b0} ), //绿色分量
.rgb_red ({vga_rgb[15:11],3'b0} ), //红色分量
.hsync (vga_hs ), //行同步信号
.vsync (vga_vs ), //场同步信号
.de (rgb_valid ), //使能信号
.hdmi_clk_p (tmds_clk_p ),
.hdmi_clk_n (tmds_clk_n ), //时钟差分信号
.hdmi_r_p (tmds_data_p[2] ),
.hdmi_r_n (tmds_data_n[2] ), //红色分量差分信号
.hdmi_g_p (tmds_data_p[1] ),
.hdmi_g_n (tmds_data_n[1] ), //绿色分量差分信号
.hdmi_b_p (tmds_data_p[0] ),
.hdmi_b_n (tmds_data_n[0] ) //蓝色分量差分信号
);

endmodule

RTL视图

顶层代码编写完成后,使用Quartus软件对实验工程进行编译,编译通过后查看RTL视图,如图 51‑23所示。RTL视图与实验工程框图一致,各信号连接正确。

SD_HDMI024

图 51‑23 RTL视图

10.3.4. 上板调试

10.3.4.1. 引脚约束

仿真验证通过后,准备上板验证,上板验证之前先要进行引脚约束。工程中各输入输出信号与开发板引脚对应关系如表格 51‑3所示。

表格 51‑3 引脚分配表

信号名

信号类型

对应引脚

备注

sys_clk

Input

E1

时钟

sys_rst_n

Input

M15

复位

sd_miso

input

J16

SD卡主输入从输出信号

sd_clk

output

J12

SD卡时钟信号

sd_cs_n

output

K12

SD卡片选信号

sd_mosi

output

J14

SD卡主输出从输入信号

sdram_clk

Output

R4

SDRAM芯片时钟

sdram_cke

Output

R9

SDRAM时钟有效信号

sdram_cs_n

Output

R12

SDRAM片选信号

sdram_cas_n

Output

R10

SDRAM列地址选通脉冲

sdram_ras_n

Output

R11

SDRAM行地址选通脉冲

sdram_we_n

Output

L9

SDRAM写允许位

sdram_ba[0]

Output

R13

SDRAM的L-Bank地址线

sdram_ba[1]

Output

R14

SDRAM的L-Bank地址线

sdram_addr[0]

Output

P11

SDRAM地址总线

sdram_addr[1]

Output

P14

SDRAM地址总线

sdram_addr[2]

Output

N9

SDRAM地址总线

sdram_addr[3]

Output

N11

SDRAM地址总线

sdram_addr[4]

Output

T14

SDRAM地址总线

sdram_addr[5]

Output

T13

SDRAM地址总线

sdram_addr[6]

Output

T12

SDRAM地址总线

sdram_addr[7]

Output

T11

SDRAM地址总线

sdram_addr[8]

Output

T10

SDRAM地址总线

sdram_addr[9]

Output

P9

SDRAM地址总线

sdram_addr[10]

Output

T15

SDRAM地址总线

sdram_addr[11]

Output

N12

SDRAM地址总线

sdram_addr[12]

Output

M11

SDRAM地址总线

sdram_dqm[0]

Output

M10

SDRAM数据掩码

sdram_dqm[1]

Output

M9

SDRAM数据掩码

sdram_dq[0]

Output

R3

SDRAM数据总线

sdram_dq[1]

Output

T9

SDRAM数据总线

sdram_dq[2]

Output

R5

SDRAM数据总线

sdram_dq[3]

Output

R6

SDRAM数据总线

sdram_dq[4]

Output

R7

SDRAM数据总线

sdram_dq[5]

Output

M8

SDRAM数据总线

sdram_dq[6]

Output

R8

SDRAM数据总线

sdram_dq[7]

Output

N8

SDRAM数据总线

sdram_dq[8]

Output

P8

SDRAM数据总线

sdram_dq[9]

Output

T8

SDRAM数据总线

sdram_dq[10]

Output

T7

SDRAM数据总线

sdram_dq[11]

Output

T6

SDRAM数据总线

sdram_dq[12]

Output

T5

SDRAM数据总线

sdram_dq[13]

Output

T4

SDRAM数据总线

sdram_dq[14]

Output

T3

SDRAM数据总线

sdram_dq[15]

Output

T2

SDRAM数据总线

tmds_clk_p

Output

R16

时钟差分信号

tmds_clk_n

Output

P16

时钟差分信号

tmds_data_p[2]

Output

K15

红色分量差分信号

tmds_data_n[2]

Output

K16

红色分量差分信号

tmds_data_p[1]

Output

L15

绿色分量差分信号

tmds_data_n[1]

Output

L15

绿色分量差分信号

tmds_data_p[0]

Output

N15

蓝色分量差分信号

tmds_data_n[0]

Output

N16

蓝色分量差分信号

下面进行管脚分配,管脚的分配方法在前面章节已有所讲解,在此就不再过多叙述,管脚的分配如下图 51‑24、图 51‑25所示。

SD_HDMI025

图 51‑24 管脚分配

SD_HDMI026

图 51‑25 管脚分配

10.3.4.2. 结果验证

如图 51‑26所示,开发板连接5V直流电源、USB-Blaster下载器JTAG端口、连接HDMI显示器、插入SD卡。线路正确连接后,打开开关为板卡上电。

SD_HDMI027

图 51‑26 程序下载连线图

如图 51‑27所示,使用“Programmer”为开发板下载程序。

SD_HDMI028

图 51‑27 程序下载图

程序下载完成后,两幅图片会交替显示在HDMI 显示屏上,如图 51‑28、图 51‑29所示。

SD_HDMI029

图 51‑28 SD卡存储图片显示

SD_HDMI030

图 51‑29 SD卡存储图片显示

10.4. 章末总结

本章节我们使用前面章节设计的SD卡数据读写控制器,与HDMI显示相结合,并通过实验实现了SD卡存储图片的HDMI显示。读者要认真理解相关理论知识,切实掌握SD卡数据读写控制器的设计与实现。