前期工作完成后,就要设计发送和显示了,本次设计采用 UART进行发送和 HDMI 进行显示。
一、总体框架
二、PC端UART
用串口助手即可,网上很多这种软件,发送时注意一下波特率、停止位和 hex 格式。图片数据的产生采用 Matlab 软件,将图片转换成 RGB565 的 16bit 像素数据,每个像素数据用两个 8bit 数据表示,串口发送两个数据表示一个像素点。
三、uart_rx
之前的博客已经详细介绍了串口的发送和接收,此处不再赘述。
四、8转128bit
1、方法1
本来的设想是串口过来的 8bit 数据直接转成 128bit 的数据,后来发现 FIFO 不给力,写 8bit,读只能最多选 64bit,因此先代码设计一下 8bit 转 16bit,再用 FIFO实现 16bit 转 128 bit。这里 FIFO 除了转bit位数,还一个功能是跨时钟域。此外设计读使能时需注意,因为 user_wr_ctrl 模块里设计的突发长度是64,因此设计 FIFO 时需要注意两点:一是FIFO内的数据个数必须大于64才能启动读,也就是用户写;二是读使能64次后关闭读使能,又回到第一步判断是否能启动读。如此反复。
//==========================================================================
//== 16bit转128bit的FIFO读使能
//==========================================================================
always @(posedge sclk) begin
if(rst) begin
fifo_rd_cnt <= 0;
end
else if(add_fifo_rd_cnt) begin
if(end_fifo_rd_cnt)
fifo_rd_cnt <= 0;
else
fifo_rd_cnt <= fifo_rd_cnt + 1;
end
end
assign add_fifo_rd_cnt = fifo_rd_en;
assign end_fifo_rd_cnt = add_fifo_rd_cnt && fifo_rd_cnt== BURST_LEN-1; //64
always @(posedge sclk) begin
if(rst) begin
fifo_rd_en <= 1'b0;
end
else if(rd_data_count>=BURST_LEN) begin //FIFO内数据个数大于64突发长度则启动读
fifo_rd_en <= 1'b1;
end
else if(end_fifo_rd_cnt) begin //读完64突发个数则先暂停
fifo_rd_en <= 1'b0;
end
end
//==========================================================================
//== 输出
//==========================================================================
assign user_wr_en = fifo_rd_en;
assign user_wr_data = fifo_rd_data;
2、方法2
如果左右时钟相同,那就可以不做时钟域处理,直接用移位寄存器做 8bit 转 128bit 即可,用移位寄存器写的代码,输出使能和 128bit 数据都是一个个的,每个都会让 user_wr_ctrl 模块启动读命令,这样的话,之前的 user_wr_ctrl 的突发长度就得改成 1。
//==========================================================================
//== 方式二:直接拼接128bit,后面user_wr_ctrl的突发长度 BURST_LEN 须改成1
//==========================================================================
reg [ 5:0] cnt ;
wire add_cnt ;
wire end_cnt ;
always @(posedge sclk) begin
if(rst)
cnt <= 0;
else if(add_cnt) begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
end
assign add_cnt = rx_data_vld;
assign end_cnt = add_cnt && cnt== 16-1;
always @(posedge sclk) begin
if(rst) begin
user_wr_en <= 'd0;
end
else if(end_cnt) begin
user_wr_en <= 'd1;
end
else begin
user_wr_en <= 'd0;
end
end
always @(posedge sclk) begin
if(rst) begin
user_wr_data <= 'd0;
end
else if(add_cnt) begin
user_wr_data <= {user_wr_data[119:0],rx_data};
end
end
五、缓存处理
1、缓存介绍
在做FPGA设计中,经常会遇到不同频率、相位的时钟需要控制同一组数据,此类不同频率、相位的时钟控制的数据,我们称为跨时钟域处理,如果跨时钟域的数据不经过处理,可能会出现数据部分丢失和增多的可能性。

由于缓存器的存储容量有限,因此我们不能无限制的将 DDR3 内的数据传输到缓存器内,但是也需要保证 HDMI 向缓存器要数据时,不能出现缓存器内数据量无法满足 HDMI 所需数据的问题,因此我们需要合理的使用该缓存器,使其能为 DDR3 和 HDMI 的数据解决跨时钟域的问题。可以举一个现实生活的例子解释该状况,我们可以将 DDR3 理解成水库,缓存器理解成蓄水池,HDMI 理解成用水的市民,由于蓄水池内部存储容量有限,因此如果将水库的水大量注入蓄水池,就会出现蓄水池溢出的情况,但是考虑到市民的用水,因此蓄水池内的水量又不能过少,最好的办法就是设定一个阈值,当蓄水池的水量低于阈值时,就开闸将水库的水注入蓄水池,当高于阈值时,就关闭水库的水闸,这样就能保证蓄水池的水量不会偏差阈值过多。根据市民的用水情况,设定一个合适的阈值,这样蓄水池就能很好的解决水库与市民用水之间的缓存。
2、缓存FIFO解读

由于本次设计采用 HDMI,在 FIFO 缓存1.5 行像素数据前,如果 HDMI 就工作,有可能出现一些意想不到的错误,为严谨期间,也需将 HDMI 延时一段时间再启动,可以利用 FIFO 的 rd_data_count 来控制 HDMI 的复位信号,即可达到延时的目的。
六、HDMI部分
HDMI 模块在另一篇博客《协议——HDMI》中已经详细叙述,此处不再赘述。HDMI 模块可以和 hdmi_buffer 模块封装成一个 hdmi_top 模块,方便后面的使用。如图所示:
七、上板查看现象
在顶层端口出也不要忘了添加 HDMI 的接口,我一开始忘了这,怎么检查都差不多错误,无语了。既然要上板,那就得加上引脚约束,因为 DDR3 的引脚已经在设置 IP 时就填进去校验了,所以这里引脚约束只需要设置时钟、复位、 HDMI 和 UART 即可。
最后生成 bit 文件,连接好各种接口线,bit 文件并下载到板卡中即可得到如下图像,HDMI 显示器亮了,并出现乱序的彩条。
由 Matlab 软件将一副 1280x768 分辨率的图片生成TXT格式的图像数据,打开串口助手将数据发送出去,得到如下图像:
成功!完结撒花!
参考资料:威三学院FPGA教程
来源:oschina
链接:https://my.oschina.net/u/4364580/blog/3337691