【FPGA】我的FPGA学习小结:用FPGA控制SDRAM,并通过VGA控制显示屏
FPGA介绍FPGA是一种能够运行硬件描述语言的设备。硬件描述语言是用来描述硬件行为的语言,集成电路芯片的设计,就是通过编写硬件描述语言来实现。但FPGA设备可以把硬件描述语言当成软件来运行,并具备一定的硬件性能。因此我们可以认为FPGA是实际投产生产真正的硬件芯片之前,用来帮助硬件工程师学习硬件原理,并测试硬件设计合理性的工具。
FPGA的功能实现,主要是靠它内部的大量(其实也才十几万个,连1MB的东西都存不下)的逻辑单元来实现。每个逻辑单元有多个输入(比如我的“大风四”,是四个输入),一个查表器,一个D触发器和一个1bit输出构成。查表器负责实现多个输入的任意逻辑运算——比如四路输入的16个逻辑结果,它都是用查表的方式得出来的。D触发器相当于一个1bit寄存器,能存储一个状态。D触发器寄存的状态通向这个逻辑单元的输出。
然后,FPGA内部使用可编程内部连线来连接每一个逻辑单元,以及FPGA的可编程IO(引脚)等。给FPGA编程,相当于设计它的每个逻辑单元的查找表,以及内部连线的连接方式。整个FPGA内部的电路连接,靠它的RAM来控制其开关的开闭。
FPGA芯片的内部不仅仅是由以上的东西构成,它还有一些辅助性的外设,比如PLL锁相环、块内存、高速乘除法器等。如果没有这些外设,光靠FPGA自己的门电路阵列去实现这些功能,一方面,并不是所有的功能都可以靠逻辑门来实现,比如PLL锁相环。另一方面,像乘除法器、块内存等,如果用逻辑门阵列来实现,我们会需要消耗大量的逻辑门,但实际上FPGA内部的逻辑门数量是有限的。所以针对FPGA的硬件描述语言的设计,与实际针对ASIC芯片的设计,还是会有相当大的差异。
上文提到的PLL锁相环是用于定制时钟频率的。举个例子,比如我有个 50 MHz 的时钟,我们可以把这个时钟输入给PLL,并设置PLL将这个频率提高 8 倍,再降低 3 倍,我们就可以得到约为 133.333333 MHz 频率的时钟,而这个时钟频率正好是我所使用的SDRAM芯片的工作频率。
那我们是不是可以使用PLL来生成频率很高的时钟呢?能是能,但要考虑到信号从一个逻辑门到达另一个逻辑门是有延时的(FPGA的PDF里会描述这个延时的具体数值),而且逻辑门本身的逻辑运算也是需要消耗时间的。当你把PLL输出的时钟频率调得过高,并在硬件描述语言里,使用这个时钟作为你的芯片的各种行为运行的工作时钟,那么这些语句是否都能在下一个周期到来前完成运行就无法得到保证,从而出现执行错误,导致芯片无法工作。但,我们可以优化我们的芯片逻辑设计,包括使用流水线方式提前执行一些耗时的语句,给硬件的运行留出时间;以及减少逻辑判断的层数,用寄存器来暂存复杂逻辑判断的结果,然后判断寄存器的数值等。
但FPGA的逻辑门本身的物理工作频率也有上限。我所使用的FPGA芯片是“大风四”的EP4CE6E22C8N,各路大佬都说“这玩意儿顶多跑到200 MHz,再高它就不行了”。虽然经过我的实测,我写了个UART模块,能让它在450 MHz的工作频率下,以45MHz的波特率进行正常工作(更高的我还没测过)。450MHz的工作频率,45MHz的波特率,相当于每10个周期发送1个bit,每个bit的Duty Cycle为50%。但我要是让它每9个周期或者每11个周期发送一个bit,它发出去的数据就有概率被对方读错,比如发送0xAA的时候有概率收到0xB5等。
SDRAM介绍
SDRAM就是我们所说的“内存”了。它是动态内存,数据要经过不断的刷新才能保留。一旦停止了刷新,它就会丢失数据。SDRAM比静态内存更廉价,但是控制起来麻烦得多。现在大家使用的DDR内存,它的前身就是SDRAM内存。
我的SDRAM模块是HY57V561620FTP-H,它长这个样子:
安装到我的FPGA板子上后,是这个样子的:
它的PDF在这里:
要控制SDRAM,也就是要能把数据写入SDRAM,再正确读出,是需要仔细看了SDRAM的PDF后,还需要看别的大佬的代码,才能稍微有些领悟的。在经过实践后,我表示:SDRAM的控制并不难。但我最初走了弯路:我想把SDRAM的控制单独封装为一个模块,以便今后的控制的方便。结果发现似乎有点儿画蛇添足。因为我封装模块是为了简化操作,但我自己封装出来的接口似乎更麻烦了。
要控制SDRAM首先要知道SDRAM的工作原理和大致结构,我所使用的SDRAM的结构如下图所示:
其实可以不用十分在意它的细节,反正现在你看它也看不出个啥。但可以看出,我的这个SDRAM有4个Bank。
SDRAM是一个状态机。根据PDF,你可以给它发送各种各样的命令,这些命令可以控制它的行为,包括写入数据、读出数据、自动刷新等。每个命令都有需要消耗时间,这个时间在PDF上有时候是以纳秒为单位,有时候是以周期为单位,你需要注意换算。在一个命令被发送出去后,如果它不能立即完成,在等待的它的时候你可以使用NOP(无操作)命令来过渡。
SDRAM上电后需要通过发送8个以上的Auto Refresh(自动刷新)命令,然后设置MRS寄存器,来完成初始化。完成初始化后就可以往里面写入数据、读出数据了。要进行数据的写入,你需要先激活Bank并Row,然后才能读、写这个Row里的内容。完成读写后,你需要使用Precharge命令关闭Row和Bank,进行读写数据的充能。如果不充能,读写操作所在的Row的数据会丢失的。
一次完整的SDRAM读或写操作流程应该是像如下图这样的:
读数据的时候,要发送读命令,然后等待tCAS个周期,再从SDRAM的数据总线上采样,才能获得数据。这个“CAS”的全称是“Column Access Strobe”,翻译过来叫“列寻址选通”。你可以连续发送多个读命令,并且每个读命令的地址都不一样(但必须是你打开的这个Row里的地址),这样你就可以连续读取多个不同地址的数据了(在发送第一个读取命令后,等待tCAS个周期,就能连续读取多个数据了)。而写命令则是同时把要写的地址和数据都发送过去,再等待同样的tCAS个周期后它才能保证数据得到了写入。同样你也可以连续发送多个写命令,但在发送最后一个写命令的时候,要等待N个周期再进行别的命令,因为你要等待它真正把数据写入进去。你可以同时打开多个Bank进行读写,但每个Bank只允许你打开一个Row。
根据PDF的描述,它的各项操作的耗时、延迟、坚持时间(比如打开一个Row后,它最大能坚持多久才必须要你来Precharge这个Row)等是这样的:
根据我们的时钟频率是133.333333 MHz,每个周期的时间相当于:
1s / 133,333,333 Hz
= 1,000ms / 133,333,333 Hz
= 1,000,000us / 133,333,333 Hz
= 1,000,000,000ns / 133,333,333 Hz
≈ 7.5ns
那么我们的激活Bank+Row的命令,按照PDF所说的
其中tRCD为最小18ns,最大20ns,也就是最小2.4个周期到最大2.666666个周期,向上取整,我们需要等待三个周期才能算它完成了Bank的激活和Row的开启。
事实上,激活Row的过程相当于把这个Row的数据从SDRAM的记忆阵列(一个三极管和一个电容器)中取出(三极管开启,电容器放电),读到Sense AMP进行信号放大,然后再被写入到它所在Bank的Row缓存用于供你读写。此时的内存阵列中的Row里的电容器因为放电了,所以实际上它的状态已经被清零了。
可以通过观察下图来了解SDRAM是如何定位数据的。需要注意的是:下图中存储阵列的每一个小格子存储的是一个Word长度的数据,而不是一个bit。根据PDF,我所使用的SDRAM的Word长度为2个字节,也就是16个bit。
SDRAM读写详细
在打开了Row后,我们就可以写入数据了。但我们必须要知道tCAS的值是啥。根据我的PDF,我的SDRAM的tCAS的数值是:
是3个周期。
并且读写数据的时序图是这个样子的:
而写入数据的时候,你需要同时给出数据和要写入的地址,然后等待tCAS后数据才能被写入。
连续的读取或写入命令可以是如下图的样子的操作:
SDRAM读取过程模拟:
[*]先指定Bank地址和Row地址来打开Bank和Row
[*]发送NOP,等待打开完成
[*]发送NOP,等待打开完成
[*]发送NOP,等待打开完成
[*]发送读命令和地址
[*]发送NOP,等待CL
[*]发送NOP,等待CL
[*]发送NOP,同时采样数据总线获得数据
[*]发送Precharge
[*]发送NOP,等待Precharge完成
[*]发送NOP,等待Precharge完成
[*]发送NOP,等待Precharge完成
SDRAM写入过程模拟:
[*]先指定Bank地址和Row地址来打开Bank和Row
[*]发送NOP,等待打开完成
[*]发送NOP,等待打开完成
[*]发送NOP,等待打开完成
[*]发送写命令、地址和数据
[*]发送NOP,等待CL
[*]发送NOP,等待CL
[*]发送Precharge
[*]发送NOP,等待Precharge完成
[*]发送NOP,等待Precharge完成
[*]发送NOP,等待Precharge完成
SDRAM读取多个数据的过程模拟:
[*]先指定Bank地址和Row地址来打开Bank和Row
[*]发送NOP,等待打开完成
[*]发送NOP,等待打开完成
[*]发送NOP,等待打开完成
[*]发送读命令和地址1
[*]发送读命令和地址2
[*]发送读命令和地址3
[*]发送读命令和地址4
与此同时采样数据总线获得数据1
[*]采样数据总线获得数据2
与此同时发送NOP
[*]采样数据总线获得数据3
与此同时发送NOP
[*]采样数据总线获得数据4
与此同时发送Precharge
[*]发送NOP,等待Precharge完成
[*]发送NOP,等待Precharge完成
[*]发送NOP,等待Precharge完成
SDRAM写入多个数据过程模拟:
[*]先指定Bank地址和Row地址来打开Bank和Row
[*]发送NOP,等待打开完成
[*]发送NOP,等待打开完成
[*]发送NOP,等待打开完成
[*]发送写命令、地址1和数据1
[*]发送写命令、地址2和数据2
[*]发送写命令、地址3和数据3
[*]发送写命令、地址4和数据4
[*]发送NOP,等待CL
[*]发送NOP,等待CL
[*]发送Precharge
[*]发送NOP,等待Precharge完成
[*]发送NOP,等待Precharge完成
[*]发送NOP,等待Precharge完成
SDRAM随机读写同一个Bank和Row里的数据的过程模拟:
[*]先指定Bank地址和Row地址来打开Bank和Row
[*]发送NOP,等待打开完成
[*]发送NOP,等待打开完成
[*]发送NOP,等待打开完成
[*]发送读命令和地址1
[*]发送读命令和地址2
[*]发送读命令和地址3
[*]发送读命令和地址4
与此同时采样数据总线获得数据1
[*]采样数据总线获得数据2
与此同时发送NOP
[*]采样数据总线获得数据3
与此同时发送NOP
[*]采样数据总线获得数据4
与此同时发送NOP
[*]发送写命令、地址1和数据1
[*]发送写命令、地址2和数据2
[*]发送写命令、地址3和数据3
[*]发送写命令、地址4和数据4
[*]发送NOP,等待CL
[*]发送NOP,等待CL
[*]发送读命令和地址1
[*]发送读命令和地址2
[*]发送读命令和地址3
[*]发送读命令和地址4
与此同时采样数据总线获得数据1
[*]采样数据总线获得数据2
与此同时发送NOP
[*]采样数据总线获得数据3
与此同时发送NOP
[*]采样数据总线获得数据4
与此同时发送Precharge
[*]发送NOP,等待Precharge完成
[*]发送NOP,等待Precharge完成
[*]发送NOP,等待Precharge完成
根据以上例子,目测大家应该已经能看懂它的规律了。你必须在激活了Bank和Row的条件下,才能进行读写,并且你需要等待数据完成读或者写。
也就是说:因为考虑到各种操作的延时的存在,你不能随便指定一个地址就可以读或者写,你需要先花费若干周期打开一个Row后才能读写,并且在读写完了后还要花费若干周期关闭Row才能保证数据不丢失。
更何况,读或者写的操作本身也有各自的延时存在,包括读命令需要等待数据到达总线,以及写命令需要等待数据到达存储器。
VGA接口介绍
VGA是非常常见而且古老的视频传输接口。VGA传输图像最主要的信号是这几个:
[*]红色成分含量(差分传输模拟信号)
[*]绿色成分含量(差分传输模拟信号)
[*]蓝色成分含量(差分传输模拟信号)
[*]水平同步HSYNC
[*]垂直同步VSYNC
其中HSYNC和VSYNC告诉对方显示器,画面的分辨率如何,何时传输的是哪个像素的颜色。
而红绿蓝的线,都是通过差分传输模拟信号的方式来传输颜色,并通过改变信号功率强度来表达数值大小。而差分传输的意思是,它使用两根信号线来传输同一个信号,但两根信号线的电流方向相反。接收端通过比较两根信号线的电平差异来读出信号。这样做的好处是可以抵抗外界的电磁干扰。
我使用FPGA传输VGA的视频信号。需要注意的是,我的FPGA并没有内置的数字信号转模拟信号的pin,所以我需要自己去实现它。难度倒是不大,使用一个电阻阵列就可以实现。通过使用多个数字信号的pin来输出信号到多个不同阻值的电阻,然后把电阻的输出的部分并联到一起,这样我们就可以通过控制数字信号输出的量来控制并联得到的模拟信号的电平。
但,显示器具体是如何判断我什么时候输出了哪个像素呢?其实我们输出的每一帧的像素数量并不等于对应分辨率的每帧像素数量,而是要把每一帧的面积扩大,制造一个黑色消隐区域,让显示器知道如何同步,如下图所示。
当然也有的图会把消隐部分放到左边或者上边来画,但其实是一样的。
对于分辨率640x480像素的画面(每一帧307200个像素),在加上消隐部分后的分辨率,其实是896x508,共455168像素。其中横向分辨率增加了256个消隐区域的像素,纵向则增加了28个。
而对于分辨率1024x768像素的画面(每一帧786432个像素),在加上消隐部分后的分辨率是1280x796,共1018880像素。
在传输VGA信号的时候,我们要按照严格的像素时钟顺序来依次从上到下传输每一行、从左到右传输每一个像素(设置红绿蓝各通道的输出),并在进入消隐区域后,输出“黑色”(红绿蓝都是低电平)。
此外,我们需要正确设置HSYNC和VSYNC的电平。不过方法很简单,横向在进入消隐区域后,并且在传输了40个消隐像素后,HSYNC进入低电平。而在HSYNC保持低电平128个像素后,再重新设置回高电平。回到高电平后再传输88个消隐像素就可以进入下一行的有效像素的传输了。
而在纵向进入消隐区域,并传输了1整行消隐像素后,VSYNC进入低电平,并传输4整行消隐像素,然后再恢复高电平传输23行消隐像素。完成传输后就可以进入下一帧的传输了。
需要注意的是:HSYNC和VSYNC信号之间相互没有影响。
由于传输VGA信号需要严格的像素时钟,我们需要计算传输每个像素所需要的时间。假设分辨率为1024x768,则实际分辨率(也就是包括了消隐区域的分辨率)是1280x796,像素数共1018880。假设屏幕刷新率为60 Hz,则每秒传输的像素数是61132800个。
每个像素的传输用时是:
1,000,000,000ns / 61132800px = 16.357830820770519262981574539363 ns 约等于 16.358 纳秒。
如果我的FPGA使用的时钟是133,333,333 Hz,那么我相当于每2.1810441039834589614740368509213 个周期,传输一个像素。
那假设我只取整数,每2个周期传输一个像素呢?
我们的帧数会变成 65.431323283082077051926298157454 Hz 约等于 65 Hz。很多显示器会直接提示“不支持当前显示模式”。事实上经过我的测试,刷新率误差在58 Hz到62 Hz之间,显示器能正确显示图像。
所以我们的FPGA要有能力可以更为精确地传输每个像素。我的做法是通过计时器的计算,每隔一定周期后延时1周期,用这种方式可以慢慢减少刷新率的误差。但有的像素可能会被稍微横向拉长。
如果我们把SDRAM和VGA结合到一起呢?
我如果想要使用FPGA以SDRAM为显存,并将其中的图像作为数据源来输出到VGA的时候,问题来了:
SDRAM需要先激活Bank、打开Row后,还要在发送了Read命令后等待数个周期才能读出一个Word,也就是说,SDRAM此时没有实时性,如下图所示。
而如果我试图在横向消隐期间事先完成激活Bank和打开Row,以及发送Read命令来做准备的话,虽然可行,但我的SDRAM每一个Row只有512个Word(根据PDF描述的地址线分布可以看出)。
我想要输出的分辨率是1024x768,这需要两个Row的数据才能填满一行有效像素,而每个Bank只允许你同时打开一个Row。完成一个Row的读取后,你需要关闭它,再打开下一个Row,这个过程中的延时,会导致一个空白时间,你无法在这个空白时间里继续给VGA输出数据,但VGA要求你必须按时输出每一个像素的颜色。
应对这个问题,我最初考虑过这样一个馊主意:
我同时打开两个Bank,这样我就能在输出每一行像素到VGA的时候,有两个可以即时读取的Row,通过先读取第一个打开的Bank的Row,再读取第二个打开的Bank的Row,来完成无缝读取两个Bank并输出像素到VGA的要求。完成一行像素的有效区域的输出后,我再关闭这两个Row,并准备下一行像素对应的两个Bank的两个Row就可以了。
这看起来是一个不错的主意,但它导致了这么几个问题:
[*]如果Bank地址被我当作高地址的话,那显存就被划分为两块区域,其中一块只存储画面左半部分,另一块只存储画面右半部分,而且这两块的存储地址还比较远,会造成内存分配上的麻烦。
[*]如果我把Bank地址当作低地址,那就会因为地址连续性的问题,导致我把显存划分为四块(因为我有四个Bank),每一块的每一Row都只能存储256个像素,导致浪费。
[*]对于其它分辨率,比如640x480,如果我的Bank地址是高地址,那么第偶数个Row只能存储128个像素,浪费了剩下的384个像素。
[*]往远一点讲,我只有4个Bank,这样的做法会导致我有分辨率的上限:横向2048个像素。虽说当我想要输出2K屏的时候我也不会考虑用VGA接口了,也不会用这种4Bank内存了,也不会用这种低性能的“大风四”了。
突然,我想到一件事:
PSP游戏机的显示器是480x272像素的,但它的显存是每行像素512字节对齐的……
(咳咳,512字节不等于512像素,以及PSP游戏机是支持16K色的)
于是,我决定再换一种思路来解决问题。
使用FPGA的块内存当作“缓存”
FPGA的“块内存”是一个好东西,它不像SDRAM那样需要激活Bank和Row,它可以随机读取任何地址上的任何数据。它不按照Bank划分,并且能立即完成你的操作。比如你设置了读的命令,在下一个周期,你用于接收数据的那个寄存器里存储的一定是你要的数据。
这样一来,块内存不仅具备实时性,而且延迟低。最主要的是:我可以使用一个双口的块内存,一个口在读的同时,另一个口可以写入数据到块内存,两个口互不冲突(而且在读写同一个地址的时候,读出来的是写入的新数据)。
于是,一个新的计划油然而生:
[*]依然使用SDRAM进行图像资源的存储,但不依赖它进行VGA画面的输出。
[*]需要进行画面输出的时候,直接把SDRAM里的显存数据读到块内存里,并且SDRAM里的像素行可以不对齐。
[*]块内存同时支持读和写,因此可以在给它写入一行像素数据的同时,用它来按照特性时钟输出VGA。
[*]在SDRAM完成输出一整行到块内存后,在需要输出下一行像素前,实际仍有大量空闲时间用于进行其它的处理。这个时间可以被利用起来,进行包括内存自动刷新、从输入口更新图像数据等操作。
块内存的操作非常简单,但因为我对VHDL知识的缺乏,我上网找了很久的资料才知道如何去控制它。
块内存的使用,需要你设置一堆generic,包括操作模式、地址宽度、数据宽度、字节大小、字Mask(说起来,SDRAM也有QDM用于只写入半个Word也就是一个字节,但我不需要它的Mask)、设备家族(大风)等。然后你需要用一系列的signal来接收它的数据或者控制它。
我按照自己的计划进行编写,并试图输出测试图像(像素颜色值 = RGB(x mod 256, y mod 256, (x xor y) mod 256)),在经历了各种各样的失败(包括之前不懂HSYNC和VSYNC的规则以及前后porch等问题)后,终于能成功稳定输出图像了。
只是因为我的FPGA的时钟不是特别精准,前几行像素会左右抖动。而且代码里,我也只是使用一种大致的方式来降低刷新率的误差。
library ieee;
use ieee.std_logic_1164.ALL;
use ieee.std_logic_unsigned.all;
use ieee.numeric_std.all;
library altera_mf;
use altera_mf.altera_mf_components.all;
entity vga_test is
generic
(
CLK_Freq : integer := 50000000;
USE_PLL : std_logic := '1';
USE_BRAM : std_logic := '1';
DO_VRAM_INIT : std_logic := '1'
);
port
(
-- 时钟和重置按钮
i_Clk : instd_logic;
i_NReset : instd_logic;
-- SDRAM接口
S_DQ : inout std_logic_vector(15 downto 0);
S_A : out std_logic_vector(12 downto 0);
S_B : out std_logic_vector(1 downto 0);
S_LDQM : inout std_logic;
S_UDQM : inout std_logic;
S_CKE : out std_logic;
S_CLK : out std_logic;
S_CS : out std_logic;
S_RAS : out std_logic;
S_CAS : out std_logic;
S_WE : out std_logic;
-- VGA接口
o_VGAD : out std_logic_vector(15 downto 0);
o_VGA_VSYNC : out std_logic;
o_VGA_HSYNC : out std_logic;
-- LED灯
o_LED1 : out std_logic;
o_LED2 : out std_logic
);
end vga_test;
architecture rtl of vga_test is
-- 锁相环,用于提供特定频率的时钟
component pll
port
(
inclk0: in std_logic;
c0 : out std_logic;
locked: out std_logic
);
end component;
signal r_Clk_133 : std_logic;
signal r_Clk_133_pll_locked : std_logic;
constant c_PLL_CLK_Freq : integer := 133333333;
-- SDRAM参数
constant c_SDRAM_RowLength : integer := 512;
constant c_SDRAM_tPRE : integer := 4;
constant c_SDRAM_tRCD : integer := 4;
constant c_SDRAM_tCAS : integer := 3;
constant c_SDRAM_tMRS : integer := 2;
constant c_SDRAM_tREF : integer := c_PLL_CLK_Freq * 8 / 125;
constant c_SDRAM_Cap : integer := 16777216;
signal r_SDRAM_Delay : integer range 0 to 12 := 0;
signal r_SDRAM_VRAM_InUse : std_logic := '0';
signal r_SDRAM_ROWACT : std_logic := '0';
signal r_SDRAM_CAS_CNT : integer range 0 to c_SDRAM_tCAS := 0;
signal r_SDRAM_INC_CNT : integer range 0 to c_SDRAM_tCAS := 0;
-- 显示方式
constant c_X_res : integer := 1024; -- 分辨率
constant c_Y_res : integer := 768;
-- 水平同步信号位置
constant c_X_front_porch : integer := 40;
constant c_X_sync_cnt : integer := 128;
constant c_X_sync_start : integer := c_X_res + c_X_front_porch;
constant c_X_sync_end : integer := c_X_res + c_X_front_porch + c_X_sync_cnt;
constant c_X_back_porch : integer := 88;
-- 垂直同步信号位置
constant c_Y_front_porch : integer := 1;
constant c_Y_sync_cnt : integer := 4;
constant c_Y_sync_start : integer := c_Y_res + c_Y_front_porch;
constant c_Y_sync_end : integer := c_Y_res + c_Y_front_porch + c_Y_sync_cnt;
constant c_Y_back_porch : integer := 23;
constant c_X_blank : integer := c_X_front_porch + c_X_sync_cnt + c_X_back_porch; -- 水平同步空白区域大小
constant c_Y_blank : integer := c_Y_front_porch + c_Y_sync_cnt + c_Y_back_porch; -- 垂直同步空白区域大小
constant c_X_total : integer := c_X_res + c_X_blank; -- 水平总大小
constant c_Y_total : integer := c_Y_res + c_Y_blank; -- 垂直总大小
signal r_VAVAIL : std_logic := '0';
signal r_X_pos : integer range 0 to c_X_total := 0;
signal r_Y_pos : integer range 0 to c_Y_total := 0;
-- 帧数等
constant c_FrameRate : integer := 60;
constant c_FrameCounterPeriod : integer := c_FrameRate * 1024;
constant c_FrameCounterMax : integer := c_FrameCounterPeriod - 1;
signal r_FrameCounter : integer range 0 to c_FrameCounterMax := 0; -- 帧计数
-- 像素时间
constant c_PixelCount : integer := c_X_total * c_Y_total; -- 每屏输出像素总量
constant c_PixelRate : integer := c_PixelCount * c_FrameRate; -- 每秒输出像素总量
constant c_PixelPeriod : integer := c_PLL_CLK_Freq / c_PixelRate; -- 每像素时钟数
signal r_PixelDelay : integer range 0 to c_PixelPeriod - 1 := 0; -- 像素时钟
constant c_BasicFrameClocks : integer := c_PixelPeriod * c_PixelCount; -- 帧基本时钟数
-- 每一帧多出来的时钟
constant c_ExtraClksPerFrame_1 : integer := c_PLL_CLK_Freq / c_FrameRate - c_BasicFrameClocks;
constant c_ExtraClksInsCnt_1 : integer := c_BasicFrameClocks / c_ExtraClksPerFrame_1;
constant c_ExtraClksAfterInserted_1 : integer := c_BasicFrameClocks + c_ExtraClksInsCnt_1;
signal r_ExtraClksCNTR_1 : integer range 0 to c_ExtraClksInsCnt_1 - 1 := 0;
signal r_NoExtraClkIns_1 : std_logic := '0';
-- 补上了那些时钟后,每一帧依然还多出来的时钟
constant c_ExtraClksPerFrame_2 : integer := c_ExtraClksPerFrame_1 - c_ExtraClksInsCnt_1;
constant c_ExtraClksInsCnt_2 : integer := c_ExtraClksAfterInserted_1 / c_ExtraClksPerFrame_2;
constant c_ExtraClksAfterInserted_2 : integer := c_ExtraClksAfterInserted_1 + c_ExtraClksInsCnt_2;
signal r_ExtraClksCNTR_2 : integer range 0 to c_ExtraClksInsCnt_2 - 1 := 0;
signal r_NoExtraClkIns_2 : std_logic := '0';
-- 补上了那些时钟后,每一帧依然还多出来的时钟
constant c_ExtraClksPerFrame_3 : integer := c_ExtraClksPerFrame_2 - c_ExtraClksInsCnt_2;
constant c_ExtraClksInsCnt_3 : integer := c_ExtraClksAfterInserted_2 / c_ExtraClksPerFrame_3;
constant c_ExtraClksAfterInserted_3 : integer := c_ExtraClksAfterInserted_2 + c_ExtraClksInsCnt_3;
signal r_ExtraClksCNTR_3 : integer range 0 to c_ExtraClksInsCnt_3 - 1 := 0;
signal r_NoExtraClkIns_3 : std_logic := '0';
-- 补上了那些时钟后,每一帧依然还多出来的时钟
constant c_ExtraClksPerFrame_4 : integer := c_ExtraClksPerFrame_3 - c_ExtraClksInsCnt_3;
constant c_ExtraClksInsCnt_4 : integer := c_ExtraClksAfterInserted_3 / c_ExtraClksPerFrame_4;
constant c_ExtraClksAfterInserted_4 : integer := c_ExtraClksAfterInserted_3 + c_ExtraClksInsCnt_4;
signal r_ExtraClksCNTR_4 : integer range 0 to c_ExtraClksInsCnt_4 - 1 := 0;
signal r_NoExtraClkIns_4 : std_logic := '0';
-- 补上了那些时钟后,每一帧依然还多出来的时钟
constant c_ExtraClksPerFrame_5 : integer := c_ExtraClksPerFrame_4 - c_ExtraClksInsCnt_4;
constant c_ExtraClksInsCnt_5 : integer := c_ExtraClksAfterInserted_4 / c_ExtraClksPerFrame_5;
constant c_ExtraClksAfterInserted_5 : integer := c_ExtraClksAfterInserted_4 + c_ExtraClksInsCnt_5;
signal r_ExtraClksCNTR_5 : integer range 0 to c_ExtraClksInsCnt_5 - 1 := 0;
signal r_NoExtraClkIns_5 : std_logic := '0';
-- 补上了那些时钟后,每一帧依然还多出来的时钟
constant c_ExtraClksPerFrame_6 : integer := c_ExtraClksPerFrame_5 - c_ExtraClksInsCnt_5;
constant c_ExtraClksInsCnt_6 : integer := c_ExtraClksAfterInserted_5 / c_ExtraClksPerFrame_6;
constant c_ExtraClksAfterInserted_6 : integer := c_ExtraClksAfterInserted_5 + c_ExtraClksInsCnt_6;
signal r_ExtraClksCNTR_6 : integer range 0 to c_ExtraClksInsCnt_6 - 1 := 0;
signal r_NoExtraClkIns_6 : std_logic := '0';
-- 补上了那些时钟后,每一帧依然还多出来的时钟
constant c_ExtraClksPerFrame_7 : integer := c_ExtraClksPerFrame_6 - c_ExtraClksInsCnt_6;
constant c_ExtraClksInsCnt_7 : integer := c_ExtraClksAfterInserted_6 / c_ExtraClksPerFrame_7;
constant c_ExtraClksAfterInserted_7 : integer := c_ExtraClksAfterInserted_6 + c_ExtraClksInsCnt_7;
signal r_ExtraClksCNTR_7 : integer range 0 to c_ExtraClksInsCnt_7 - 1 := 0;
signal r_NoExtraClkIns_7 : std_logic := '0';
constant c_ResetExtraClocksAtLine : std_logic := '1';
-- 显存分配和块内存
-- 因为SDRAM需要激活ROW后才能连续读取这个ROW的内容,并且读完了还需要进行Precharge来关闭ROW,此时数据是不能读取的
-- 而VGA的接口要求以恒定的速度一个个输出像素,所以需要借助块内存作为缓冲区,将SDRAM的内容先读到块内存,再从块内存里读到VGA接口上
-- 块内存不需要刷新和Precharge,因此不会影响像素的输出,而且还可以在使用两个不同的时钟的时候能同时进行输入输出。
constant c_VRAM_Base : integer := 0; -- 显存基址
constant c_VRAM_Pitch : integer := ((c_X_res - 1) / c_SDRAM_RowLength + 1) * c_SDRAM_RowLength; -- 每一行像素的长度,这里按照SDRAM的ROW长度进行对齐
constant c_VRAM_Size : integer := c_VRAM_Pitch * c_Y_res; -- 显存总大小
constant c_BRAM_Base : integer := 0;
signal r_VRAM_PixelPtr : integer range 0 to c_SDRAM_Cap - 1 := c_VRAM_Base; -- 显存像素指针
signal r_VRAM_LinePtr : integer range 0 to c_SDRAM_Cap - 1 := c_VRAM_Base; -- 行指针
signal r_VRAM_RowRead_CNT : integer range 0 to c_SDRAM_RowLength; -- SDRAM的Row的读取计数
signal r_VRAM_RowWrite_CNT : integer range 0 to c_SDRAM_RowLength; -- SDRAM的Row的写入计数
signal r_VRAM_LineRead_CNT : integer range 0 to c_X_res; -- 像素行读取计数
signal r_BRAM_Write_CNT : integer range 0 to c_X_res; -- 块内存写入计数
signal r_BRAM_Read_CNT : integer range 0 to c_X_res; -- 块内存读取计数
signal r_VRAM_VBLANK_REFA : std_logic := '0'; -- SDRAM在VGA输出空白区域时是否要进行自动刷新
type t_SM_Main is (s_Init, s_VRAM_Init, s_VGA_Display); -- 主要的状态机
signal r_SM_Main : t_SM_Main := s_Init;
signal r_LED1 : std_logic := '0';
signal r_LED2 : std_logic := '0';
-- VGA端口输出
signal r_VGA_VSYNC : std_logic := '0';
signal r_VGA_HSYNC : std_logic := '0';
signal r_VGAD : std_logic_vector (15 downto 0) := x"0000";
-- SDRAM自刷新计数
constant c_SDRAM_REFA_CNT_MAX : integer := c_SDRAM_tREF;
constant c_SDRAM_REFA_FRAME_CNT : integer := 64 / (c_SDRAM_tREF / c_PixelCount);
signal r_SDRAM_REFA_CNT : integer range 0 to c_SDRAM_REFA_CNT_MAX := 0;
-- 红绿蓝
function RGB
(
R : integer;
G : integer;
B : integer
) return std_logic_vector is
variable R_bits : std_logic_vector(7 downto 0);
variable G_bits : std_logic_vector(7 downto 0);
variable B_bits : std_logic_vector(7 downto 0);
variable RGB_bits : std_logic_vector(15 downto 0);
begin
R_bits := std_logic_vector(to_unsigned(R, R_bits'length));
G_bits := std_logic_vector(to_unsigned(G, G_bits'length));
B_bits := std_logic_vector(to_unsigned(B, B_bits'length));
RGB_bits(15 downto 11) := R_bits(7 downto 3);
RGB_bits(10 downto 5) := G_bits(7 downto 2);
RGB_bits(4 downto 0) := B_bits(7 downto 3);
return RGB_bits;
end function;
type t_SM_SDRAM is
(
s_PWDN,
s_IDLE,
s_REFA,
s_MRS,
s_ROWACT,
s_PRE,
s_READ,
s_WRITE
);
signal r_SM_SDRAM : t_SM_SDRAM := s_PWDN;
signal r_SDRAM_ROWADDR : std_logic_vector(14 downto 0);
function SDRAM_GetRowAddr (FullAddr : std_logic_vector) return std_logic_vector is
begin
return FullAddr(23 downto 9);
end function;
signal r_Clk_Phased : std_logic := '0';
signal r_BRAM_wren_a : std_logic := '0';
signal r_BRAM_wren_b : std_logic := '0';
signal r_BRAM_rden_a : std_logic := '0';
signal r_BRAM_rden_b : std_logic := '0';
signal r_BRAM_clocken0 : std_logic := '1';
signal r_BRAM_clocken1 : std_logic := '1';
signal r_BRAM_data_a : std_logic_vector(15 downto 0):= (others => '0');
signal r_BRAM_data_b : std_logic_vector(15 downto 0):= (others => '0');
signal r_BRAM_address_a : std_logic_vector(11 downto 0) := (others => '0');
signal r_BRAM_address_b : std_logic_vector(11 downto 0) := (others => '0');
signal r_BRAM_byteena_a : std_logic_vector(1 downto 0) := (others => '1');
signal r_BRAM_byteena_b : std_logic_vector(1 downto 0) := (others => '1');
signal r_BRAM_q_a : std_logic_vector(15 downto 0);
signal r_BRAM_q_b : std_logic_vector(15 downto 0);
begin
USING_PLL: if USE_PLL = '1' generate begin
pll_clk_133: pll
port map
(
i_Clk,
r_Clk_133,
r_Clk_133_pll_locked
);
end generate;
NOT_USE_PLL: if USE_PLL = '0' generate begin
r_Clk_133 <= i_Clk;
r_Clk_133_pll_locked <= '1';
end generate;
USING_BRAM: if USE_BRAM = '1' generate begin
BRAM: altsyncram
generic map
(
operation_mode => "bidir_dual_port",
width_a => 16,
widthad_a => 12,
outdata_reg_a => "unregistered",
width_byteena_a => 2,
width_b => 16,
widthad_b => 12,
outdata_reg_b => "unregistered",
width_byteena_b => 2,
byte_size => 8,
intended_device_family => "Cyclone",
lpm_type => "altsyncram"
)
port map
(
wren_a => r_BRAM_wren_a,
wren_b => r_BRAM_wren_b,
rden_a => r_BRAM_rden_a,
rden_b => r_BRAM_rden_b,
clock0 => r_Clk_Phased,
clock1 => r_Clk_Phased,
clocken0 => r_BRAM_clocken0,
clocken1 => r_BRAM_clocken1,
data_a => r_BRAM_data_a,
data_b => r_BRAM_data_b,
address_a => r_BRAM_address_a,
address_b => r_BRAM_address_b,
byteena_a => r_BRAM_byteena_a,
byteena_b => r_BRAM_byteena_b,
q_a => r_BRAM_q_a,
q_b => r_BRAM_q_b
);
end generate;
o_LED1 <= r_LED1;
o_LED2 <= r_LED2;
o_VGAD <= r_VGAD;
o_VGA_HSYNC <= r_VGA_HSYNC;
o_VGA_VSYNC <= r_VGA_VSYNC;
S_CLK <= not r_Clk_133;
r_Clk_Phased <= not r_Clk_133;
S_LDQM <= '0';
S_UDQM <= '0';
VGA_Main: process (r_Clk_133, i_NReset, r_Clk_133_pll_locked)
variable i : integer;
variable j : integer;
-- SDRAM命令过程
procedure SDRAM_POWERDOWN is
begin
S_CKE <= '0';
S_CS <= '0';
S_RAS <= '1';
S_CAS <= '1';
S_WE <= '1';
r_SM_SDRAM <= s_PWDN;
r_SDRAM_Delay <= 1;
end procedure;
procedure SDRAM_AUTOREFRESH is
begin
S_CKE <= '1';
S_CS <= '0';
S_RAS <= '0';
S_CAS <= '0';
S_WE <= '1';
r_SM_SDRAM <= s_REFA;
r_SDRAM_Delay <= 1;
end procedure;
procedure SDRAM_NOP is
begin
S_CKE <= '1';
S_CS <= '0';
S_RAS <= '1';
S_CAS <= '1';
S_WE <= '1';
end procedure;
procedure SDRAM_MRS is
begin
S_CKE <= '1';
S_CS <= '0';
S_RAS <= '0';
S_CAS <= '0';
S_WE <= '0';
S_A <= "0000000110000"; -- 突发写,CAS延时3,顺序突发,突发长度1
S_B <= "00";
r_SM_SDRAM <= s_MRS;
r_SDRAM_Delay <= c_SDRAM_tMRS - 1;
end procedure;
procedure SDRAM_ROWACT (Address : std_logic_vector(23 downto 0)) is
begin
S_CKE <= '1';
S_CS <= '0';
S_RAS <= '0';
S_CAS <= '1';
S_WE <= '1';
S_B <= Address(23 downto 22);
S_A <= Address(21 downto 9);
r_SDRAM_ROWADDR <= SDRAM_GetRowAddr(Address);
r_SM_SDRAM <= s_ROWACT;
r_SDRAM_Delay <= c_SDRAM_tRCD - 1;
end procedure;
procedure SDRAM_PRE (Address : std_logic_vector(23 downto 0)) is
begin
S_CKE <= '1';
S_CS <= '0';
S_RAS <= '0';
S_CAS <= '1';
S_WE <= '0';
S_B <= Address(23 downto 22);
S_A(10) <= '0';
r_SM_SDRAM <= s_PRE;
r_SDRAM_Delay <= c_SDRAM_tPRE - 1;
end procedure;
procedure SDRAM_PREALL is
begin
S_CKE <= '1';
S_CS <= '0';
S_RAS <= '0';
S_CAS <= '1';
S_WE <= '0';
S_A(10) <= '1';
r_SM_SDRAM <= s_PRE;
r_SDRAM_Delay <= c_SDRAM_tPRE - 1;
end procedure;
procedure SDRAM_READ (Address : std_logic_vector(23 downto 0)) is
begin
S_CKE <= '1';
S_CS <= '0';
S_RAS <= '1';
S_CAS <= '0';
S_WE <= '1';
S_B <= Address(23 downto 22);
S_A(8 downto 0) <= Address(8 downto 0);
S_A(10) <= '0';
S_DQ <= "ZZZZZZZZZZZZZZZZ";
r_SM_SDRAM <= s_READ;
r_SDRAM_Delay <= c_SDRAM_tCAS - 1;
end procedure;
procedure SDRAM_WRITE
(
Address : std_logic_vector(23 downto 0);
Data : std_logic_vector(15 downto 0)
) is
begin
S_CKE <= '1';
S_CS <= '0';
S_RAS <= '1';
S_CAS <= '0';
S_WE <= '0';
S_B <= Address(23 downto 22);
S_A(8 downto 0) <= Address(8 downto 0);
S_A(10) <= '0';
S_DQ <= Data;
r_SM_SDRAM <= s_WRITE;
r_SDRAM_Delay <= c_SDRAM_tCAS - 1;
end procedure;
begin
if (i_NReset = '0') or (r_Clk_133_pll_locked = '0') then
SDRAM_POWERDOWN;
r_SM_Main <= s_Init;
elsif rising_edge(r_Clk_133) then
case r_SM_Main is
when s_Init =>
r_X_pos <= 0;
r_Y_pos <= 0;
r_LED1 <= '1';
r_LED2 <= '1';
r_BRAM_wren_a <= '0';
r_BRAM_wren_b <= '0';
r_BRAM_rden_a <= '0';
r_BRAM_rden_b <= '0';
r_BRAM_clocken0 <= '1';
r_BRAM_clocken1 <= '1';
r_SDRAM_ROWACT <= '0';
r_SDRAM_VRAM_InUse <= '1';
case r_SM_SDRAM is
when s_PWDN =>
if r_SDRAM_Delay = 0 then
SDRAM_AUTOREFRESH;
r_SDRAM_REFA_CNT <= 8;
else
r_SDRAM_Delay <= r_SDRAM_Delay - 1;
SDRAM_NOP;
end if;
when s_REFA =>
if r_SDRAM_Delay = 0 then
if r_SDRAM_REFA_CNT = 0 then
SDRAM_MRS;
r_SDRAM_REFA_CNT <= c_SDRAM_REFA_CNT_MAX;
else
SDRAM_AUTOREFRESH;
r_SDRAM_REFA_CNT <= r_SDRAM_REFA_CNT - 1;
end if;
else
r_SDRAM_Delay <= r_SDRAM_Delay - 1;
SDRAM_NOP;
end if;
when s_MRS =>
if r_SDRAM_Delay = 0 then
r_SM_SDRAM <= s_IDLE;
SDRAM_NOP;
else
r_SDRAM_Delay <= r_SDRAM_Delay - 1;
SDRAM_NOP;
end if;
when others =>
r_SM_Main <= s_VRAM_Init;
r_X_pos <= 0;
r_Y_pos <= 0;
r_VRAM_LinePtr <= c_VRAM_Base;
r_VRAM_PixelPtr <= c_VRAM_Base;
r_SDRAM_VRAM_InUse <= '1'; -- 要开始占用显存
end case;
when s_VRAM_Init =>
if DO_VRAM_INIT = '1' then
if r_Y_pos /= c_Y_res then
if r_X_pos /= c_X_res then
if r_SDRAM_ROWACT = '0' then
if r_SM_SDRAM /= s_ROWACT then
SDRAM_ROWACT(std_logic_vector(to_unsigned(r_VRAM_PixelPtr, 24)));
else
SDRAM_NOP;
if r_SDRAM_Delay = 0 then -- 等到了
r_SDRAM_ROWACT <= '1';
SDRAM_WRITE(std_logic_vector(to_unsigned(r_VRAM_PixelPtr, 24)), RGB(r_X_pos, r_Y_pos, to_integer(unsigned(std_logic_vector(to_unsigned(r_X_pos, 8)) xor std_logic_vector(to_unsigned(r_Y_pos, 8))))));
r_X_pos <= r_X_pos + 1;
r_VRAM_PixelPtr <= r_VRAM_PixelPtr + 1; -- 像素指针
r_VRAM_RowWrite_CNT <= 1;
else
r_SDRAM_Delay <= r_SDRAM_Delay - 1;
end if;
end if;
else
if r_VRAM_RowWrite_CNT /= c_SDRAM_RowLength then
SDRAM_WRITE(std_logic_vector(to_unsigned(r_VRAM_PixelPtr, 24)), RGB(r_X_pos, r_Y_pos, to_integer(unsigned(std_logic_vector(to_unsigned(r_X_pos, 8)) xor std_logic_vector(to_unsigned(r_Y_pos, 8))))));
r_X_pos <= r_X_pos + 1;
r_VRAM_PixelPtr <= r_VRAM_PixelPtr + 1;
r_VRAM_RowWrite_CNT <= r_VRAM_RowWrite_CNT + 1;
else
if r_SM_SDRAM /= s_PRE then
if r_SDRAM_Delay = 0 then
-- 数据都写入了,准备关闭Row
SDRAM_PRE(r_SDRAM_ROWADDR & "000000000");
else
-- 等待数据写入
SDRAM_NOP;
r_SDRAM_Delay <= r_SDRAM_Delay - 1;
end if;
else
if r_SDRAM_Delay = 0 then
r_SDRAM_ROWACT <= '0';
r_VRAM_RowWrite_CNT <= 0;
r_SM_SDRAM <= s_IDLE;
else
SDRAM_NOP;
r_SDRAM_Delay <= r_SDRAM_Delay - 1;
end if;
end if;
end if;
end if;
else
if r_SDRAM_ROWACT = '1' then
if r_SM_SDRAM /= s_PRE then
if r_SDRAM_Delay = 0 then
-- 数据都写入了,准备关闭Row
SDRAM_PRE(r_SDRAM_ROWADDR & "000000000");
else
-- 等待数据写入
SDRAM_NOP;
r_SDRAM_Delay <= r_SDRAM_Delay - 1;
end if;
else
if r_SDRAM_Delay = 0 then
r_SDRAM_ROWACT <= '0';
else
r_SDRAM_Delay <= r_SDRAM_Delay - 1;
end if;
end if;
else
r_X_pos <= 0;
r_Y_pos <= r_Y_pos + 1;
r_VRAM_LinePtr <= r_VRAM_LinePtr + c_VRAM_Pitch;
r_VRAM_PixelPtr <= r_VRAM_LinePtr + c_VRAM_Pitch;
end if;
end if;
else
r_X_pos <= 0;
r_Y_pos <= 0;
r_VAVAIL <= '1';
r_SDRAM_ROWACT <= '0';
r_SM_Main <= s_VGA_Display;
r_VRAM_LinePtr <= c_VRAM_Base;
r_VRAM_PixelPtr <= c_VRAM_Base;
end if;
else
r_X_pos <= 0;
r_Y_pos <= 0;
r_VAVAIL <= '1';
r_SDRAM_ROWACT <= '0';
r_SM_Main <= s_VGA_Display;
r_VRAM_LinePtr <= c_VRAM_Base;
r_VRAM_PixelPtr <= c_VRAM_Base;
end if;
when s_VGA_Display =>
r_LED1 <= '0';
if r_Y_pos = c_Y_sync_start then
r_VGA_VSYNC <= '0';
end if;
if r_Y_pos = c_Y_sync_end then
r_VGA_VSYNC <= '1';
end if;
if r_Y_pos /= c_Y_total then
if r_X_pos = c_X_sync_start - 1 then
r_VGA_HSYNC <= '0';
end if;
if r_X_pos = c_X_sync_end - 1 then
r_VGA_HSYNC <= '1';
end if;
if r_ExtraClksCNTR_1 /= c_ExtraClksInsCnt_1 - 1 then
r_ExtraClksCNTR_1 <= r_ExtraClksCNTR_1 + 1;
r_NoExtraClkIns_1 <= '1';
else
r_ExtraClksCNTR_1 <= 0;
r_NoExtraClkIns_1 <= '0';
end if;
if r_ExtraClksCNTR_2 /= c_ExtraClksInsCnt_2 - 1 then
if r_NoExtraClkIns_1 = '1' then
r_ExtraClksCNTR_2 <= r_ExtraClksCNTR_2 + 1;
r_NoExtraClkIns_2 <= '1';
end if;
else
r_ExtraClksCNTR_2 <= 0;
r_NoExtraClkIns_2 <= '0';
end if;
if r_ExtraClksCNTR_3 /= c_ExtraClksInsCnt_3 - 1 then
if r_NoExtraClkIns_2 = '1' then
r_ExtraClksCNTR_3 <= r_ExtraClksCNTR_3 + 1;
r_NoExtraClkIns_3 <= '1';
end if;
else
r_ExtraClksCNTR_3 <= 0;
r_NoExtraClkIns_3 <= '0';
end if;
if r_ExtraClksCNTR_4 /= c_ExtraClksInsCnt_4 - 1 then
if r_NoExtraClkIns_3 = '1' then
r_ExtraClksCNTR_4 <= r_ExtraClksCNTR_4 + 1;
r_NoExtraClkIns_4 <= '1';
end if;
else
r_ExtraClksCNTR_4 <= 0;
r_NoExtraClkIns_4 <= '0';
end if;
if r_ExtraClksCNTR_5 /= c_ExtraClksInsCnt_5 - 1 then
if r_NoExtraClkIns_4 = '1' then
r_ExtraClksCNTR_5 <= r_ExtraClksCNTR_5 + 1;
r_NoExtraClkIns_5 <= '1';
end if;
else
r_ExtraClksCNTR_5 <= 0;
r_NoExtraClkIns_5 <= '0';
end if;
if r_ExtraClksCNTR_6 /= c_ExtraClksInsCnt_6 - 1 then
if r_NoExtraClkIns_5 = '1' then
r_ExtraClksCNTR_6 <= r_ExtraClksCNTR_6 + 1;
r_NoExtraClkIns_6 <= '1';
end if;
else
r_ExtraClksCNTR_6 <= 0;
r_NoExtraClkIns_6 <= '0';
end if;
if r_ExtraClksCNTR_7 /= c_ExtraClksInsCnt_7 - 1 then
if r_NoExtraClkIns_6 = '1' then
r_ExtraClksCNTR_7 <= r_ExtraClksCNTR_7 + 1;
r_NoExtraClkIns_7 <= '1';
end if;
else
r_ExtraClksCNTR_7 <= 0;
r_NoExtraClkIns_7 <= '0';
end if;
if r_VAVAIL = '1' then -- 垂直有效区域
-- 如果ROW没有被激活,则要激活ROW
if r_VRAM_LineRead_CNT /= c_X_res then
if r_SDRAM_ROWACT = '0' then
if r_SM_SDRAM /= s_ROWACT then
-- 进入Row激活
SDRAM_ROWACT(std_logic_vector(to_unsigned(r_VRAM_PixelPtr, 24)));
else
-- 等待ROW激活
SDRAM_NOP;
if r_SDRAM_Delay = 0 then -- 等到了
r_SDRAM_ROWACT <= '1';
r_BRAM_rden_b <= '0';
SDRAM_READ(std_logic_vector(to_unsigned(r_VRAM_PixelPtr, 24))); -- 开始读
r_SDRAM_INC_CNT <= 0; -- 重叠读取的数据量
r_SDRAM_CAS_CNT <= c_SDRAM_tCAS - 1; -- 数据到达地址线的时间
r_VRAM_RowRead_CNT <= 1; -- 行读取的个数
r_VRAM_PixelPtr <= r_VRAM_PixelPtr + 1; -- 像素指针
r_VRAM_LineRead_CNT <= r_VRAM_LineRead_CNT + 1; -- 这一行的总个数
else
r_SDRAM_Delay <= r_SDRAM_Delay - 1;
end if;
end if;
else -- ROW被激活,可以进行读取
if r_VRAM_RowRead_CNT /= c_SDRAM_RowLength then
-- 读取直到读完
SDRAM_READ(std_logic_vector(to_unsigned(r_VRAM_PixelPtr, 24)));
r_VRAM_RowRead_CNT <= r_VRAM_RowRead_CNT + 1;
r_VRAM_PixelPtr <= r_VRAM_PixelPtr + 1;
r_VRAM_LineRead_CNT <= r_VRAM_LineRead_CNT + 1;
if r_SDRAM_CAS_CNT /= 0 then
r_SDRAM_CAS_CNT <= r_SDRAM_CAS_CNT - 1;
end if;
if r_SDRAM_INC_CNT /= c_SDRAM_tCAS then
r_SDRAM_INC_CNT <= r_SDRAM_INC_CNT + 1;
end if;
else -- 读了一整个ROW,必须关闭这个ROW然后重新打开下一个ROW
if r_SDRAM_INC_CNT /= 0 then
-- 还有几个READ周期的数据没到齐
SDRAM_NOP;
r_SDRAM_INC_CNT <= r_SDRAM_INC_CNT - 1;
else -- 全部数据读完,关闭Row
if r_SM_SDRAM /= s_PRE then
-- 进入Precharge状态
SDRAM_PRE(r_SDRAM_ROWADDR & "000000000");
else
-- 进入后等待Precharge完成
SDRAM_NOP;
if r_SDRAM_Delay = 0 then
r_SM_SDRAM <= s_IDLE;
r_SDRAM_ROWACT <= '0';
r_VRAM_RowRead_CNT <= 0;
else
r_SDRAM_Delay <= r_SDRAM_Delay - 1;
end if;
end if;
end if;
end if;
end if;
else
-- 不用激活了
if r_SDRAM_ROWACT = '1' then
if r_SDRAM_INC_CNT /= 0 then -- 如果还有数据没到达数据总线则等待
r_SDRAM_INC_CNT <= r_SDRAM_INC_CNT - 1;
SDRAM_NOP;
else -- 准备关闭ROW
if r_SM_SDRAM /= s_PRE then
-- 进入Precharge状态
SDRAM_PRE(r_SDRAM_ROWADDR & "000000000");
else
-- 进入后等待Precharge完成
SDRAM_NOP;
if r_SDRAM_Delay = 0 then
r_SM_SDRAM <= s_IDLE;
r_SDRAM_ROWACT <= '0';
r_VRAM_RowRead_CNT <= 0;
r_SDRAM_VRAM_InUse <= '0';
else
r_SDRAM_Delay <= r_SDRAM_Delay - 1;
end if;
end if;
end if;
end if;
end if;
if (r_BRAM_Write_CNT /= c_X_res) and (r_SM_SDRAM = s_READ) and (r_SDRAM_CAS_CNT = 0) and (r_SDRAM_INC_CNT /= 0) then --and ((r_SDRAM_CAS_CNT = 0) or (r_SDRAM_INC_CNT /= 0))
r_BRAM_wren_b <= '1';
r_BRAM_address_b <= std_logic_vector(to_unsigned(c_BRAM_Base + r_BRAM_Write_CNT, r_BRAM_address_b'length));
r_BRAM_byteena_b <= "11";
r_BRAM_data_b <= S_DQ;
r_BRAM_Write_CNT <= r_BRAM_Write_CNT + 1;
else
r_BRAM_wren_b <= '0';
end if;
if r_BRAM_Read_CNT /= c_X_res then
r_BRAM_address_a <= std_logic_vector(to_unsigned(c_BRAM_Base + r_BRAM_Read_CNT, r_BRAM_address_a'length));
r_BRAM_byteena_a <= "11";
r_BRAM_rden_a <= '1';
if (r_BRAM_Write_CNT /= 0) and (r_BRAM_rden_a = '1') then
r_VGAD <= r_BRAM_q_a;
if r_PixelDelay /= c_PixelPeriod - 1 then
if r_NoExtraClkIns_7 = '1' then
r_PixelDelay <= r_PixelDelay + 1;
end if;
else
r_PixelDelay <= 0;
r_BRAM_Read_CNT <= r_BRAM_Read_CNT + 1;
r_X_pos <= r_X_pos + 1;
end if;
end if;
elsif r_X_pos /= c_X_total then
if r_PixelDelay /= c_PixelPeriod - 1 then
if r_NoExtraClkIns_7 = '1' then
r_PixelDelay <= r_PixelDelay + 1;
end if;
else
r_PixelDelay <= 0;
r_X_pos <= r_X_pos + 1;
r_BRAM_rden_a <= '0';
r_VGAD <= x"0000";
end if;
else
r_X_pos <= 0;
r_Y_pos <= r_Y_pos + 1;
if c_ResetExtraClocksAtLine = '1' then
r_ExtraClksCNTR_1 <= 0; r_NoExtraClkIns_1 <= '0';
r_ExtraClksCNTR_2 <= 0; r_NoExtraClkIns_2 <= '0';
r_ExtraClksCNTR_3 <= 0; r_NoExtraClkIns_3 <= '0';
r_ExtraClksCNTR_4 <= 0; r_NoExtraClkIns_4 <= '0';
r_ExtraClksCNTR_5 <= 0; r_NoExtraClkIns_5 <= '0';
r_ExtraClksCNTR_6 <= 0; r_NoExtraClkIns_6 <= '0';
r_ExtraClksCNTR_7 <= 0; r_NoExtraClkIns_7 <= '0';
end if;
r_VRAM_RowRead_CNT <= 0;
r_BRAM_Read_CNT <= 0;
r_BRAM_Write_CNT <= 0;
r_VRAM_LineRead_CNT <= 0;
r_VRAM_LinePtr <= r_VRAM_LinePtr + c_VRAM_Pitch;
r_VRAM_PixelPtr <= r_VRAM_LinePtr + c_VRAM_Pitch;
end if;
else -- 垂直空白区域
if r_X_pos /= c_X_total then
if r_PixelDelay /= c_PixelPeriod - 1 then
if r_NoExtraClkIns_7 = '1' then
r_PixelDelay <= r_PixelDelay + 1;
end if;
else
r_X_pos <= r_X_pos + 1;
r_PixelDelay <= 0;
end if;
else
r_X_pos <= 0;
r_Y_pos <= r_Y_pos + 1;
if c_ResetExtraClocksAtLine = '1' then
r_ExtraClksCNTR_1 <= 0; r_NoExtraClkIns_1 <= '0';
r_ExtraClksCNTR_2 <= 0; r_NoExtraClkIns_2 <= '0';
r_ExtraClksCNTR_3 <= 0; r_NoExtraClkIns_3 <= '0';
r_ExtraClksCNTR_4 <= 0; r_NoExtraClkIns_4 <= '0';
r_ExtraClksCNTR_5 <= 0; r_NoExtraClkIns_5 <= '0';
r_ExtraClksCNTR_6 <= 0; r_NoExtraClkIns_6 <= '0';
r_ExtraClksCNTR_7 <= 0; r_NoExtraClkIns_7 <= '0';
end if;
end if;
-- 垂直空白区域进行自刷新操作
if r_SDRAM_REFA_CNT < 8 then
if r_SDRAM_VRAM_InUse = '1' then
if r_SDRAM_Delay = 0 then
if r_SDRAM_REFA_CNT = 0 then
r_SM_SDRAM <= s_IDLE;
SDRAM_NOP;
r_SDRAM_VRAM_InUse <= '0';
r_SDRAM_REFA_CNT <= c_SDRAM_REFA_CNT_MAX;
r_VRAM_VBLANK_REFA <= '0';
r_LED2 <= '1';
else
r_SDRAM_REFA_CNT <= r_SDRAM_REFA_CNT - 1;
SDRAM_AUTOREFRESH;
end if;
else
r_SDRAM_Delay <= r_SDRAM_Delay - 1;
SDRAM_NOP;
end if;
end if;
else
if r_SDRAM_VRAM_InUse = '1' then
r_SM_SDRAM <= s_IDLE;
SDRAM_NOP;
r_SDRAM_VRAM_InUse <= '0';
end if;
r_SDRAM_REFA_CNT <= r_SDRAM_REFA_CNT - 1;
end if;
end if;
if r_Y_pos = c_Y_res - 1 then
r_VAVAIL <= '0'; -- 进入垂直空白区域
r_VGAD <= x"0000";
-- 刷新SDRAM
if (r_VRAM_VBLANK_REFA = '0') and (r_SDRAM_VRAM_InUse = '0') then
r_SDRAM_REFA_CNT <= 2;
SDRAM_AUTOREFRESH;
r_LED2 <= '0';
r_SDRAM_VRAM_InUse <= '1';
r_VRAM_VBLANK_REFA <= '1';
end if;
end if;
else -- 处理完当前帧最后一行
r_Y_pos <= 0;
if c_ResetExtraClocksAtLine = '1' then
r_ExtraClksCNTR_1 <= 0; r_NoExtraClkIns_1 <= '0';
r_ExtraClksCNTR_2 <= 0; r_NoExtraClkIns_2 <= '0';
r_ExtraClksCNTR_3 <= 0; r_NoExtraClkIns_3 <= '0';
r_ExtraClksCNTR_4 <= 0; r_NoExtraClkIns_4 <= '0';
r_ExtraClksCNTR_5 <= 0; r_NoExtraClkIns_5 <= '0';
r_ExtraClksCNTR_6 <= 0; r_NoExtraClkIns_6 <= '0';
r_ExtraClksCNTR_7 <= 0; r_NoExtraClkIns_7 <= '0';
end if;
r_SDRAM_REFA_CNT <= c_SDRAM_REFA_CNT_MAX;
r_VAVAIL <= '1';
r_VRAM_LinePtr <= c_VRAM_Base;
r_VRAM_PixelPtr <= c_VRAM_Base;
r_SDRAM_VRAM_InUse <= '1'; -- 占用显存
if r_FrameCounter = c_FrameCounterMax then -- 统计帧数
r_FrameCounter <= 0;
else
r_FrameCounter <= r_FrameCounter + 1;
end if;
end if;
end case;
end if;
end process;
end rtl; 帖子更新:使用了代码高亮。我差点忘了论坛还有pre代码高亮功能 好厉害~ 天羽 发表于 2020-5-23 23:55
好厉害~
谢谢你 本帖最后由 Ayala 于 2020-5-24 12:45 编辑
单片机时钟这个问题 以前在群里和一个自以为是的学生争吵过,这个东西利用定点数或者分数校正下 计算出秒晶振数就好了一般2位小数点就够用了 或者使用最小公倍数
或者单独用一个准确的晶振 Ayala 发表于 2020-5-24 12:18
单片机时钟这个问题 以前在群里和一个自以为是的学生争吵过,这个东西利用定点数或者分数校正下 计算出秒晶 ...
这边用了插入一些时钟的办法解决的,而且精确到了小数后5位
顺带一提我也记得那家伙 0xAA55 发表于 2020-5-24 13:33
这边用了插入一些时钟的办法解决的,而且精确到了小数后5位
顺带一提我也记得那家伙 ...
秒级的小数点5位 还是毫秒级的小数5位 一般来说毫秒级的小数2位就够了 如果为了方便计算使用4舍6入 5取偶的方式就足够精确了 运行开始的时候校准下10秒的晶振数然后取精度就好了 Ayala 发表于 2020-5-24 14:18
秒级的小数点5位 还是毫秒级的小数5位 一般来说毫秒级的小数2位就够了 如果为了方便计算使用4舍6入 5取偶 ...
“误差时钟数”后面的五位 0xAA55 发表于 2020-5-24 14:23
“误差时钟数”后面的五位
这样做代码处理起来也太难看了,显示之前计算下 10秒或者30秒的 vga的吞吐量, 计算下 10秒或者30秒的晶振数 ,然后使用吞吐量和 晶振数做接下来的运算 会准确很多 ,io吞吐量和晶振数的准确性是硬件编程的基本功 吞吐量决定最高的频率和最优的频率 (最小公倍数和最大公约数,以及瓶颈值), 晶振数/秒 晶振数/毫秒 比赫兹数更方便计算,让结果符合规范化。
晶振数/毫秒 换算成一毫秒 的晶振数 n , 1us = n次 延迟基本单位
吞吐量 iops 操作基本单位
这些参数计算好 可以让代码不受单片机限制 代码使用前校准一次 Ayala 发表于 2020-5-24 14:47
这样做代码处理起来也太难看了,显示之前计算下 10秒或者30秒的 vga的吞吐量, 计算下 10秒或者30秒的晶 ...
我使用了PLL所以晶振数并不是合适的吞吐量计算来源,而我使用的倍频时钟是为了满足SDRAM的工作要求。
你所说的“最小公倍数和最大公约数”从概念上就是想让我使用第二个PLL。
至于为什么不使用第二个PLL,或者不使用现有PLL的第二个输出,用于VGA针对的PCLK,其实是因为最初我并不考虑使用内部的块内存作为VGA的数据源,而是只使用SDRAM打开多个Bank。不想使用更高的时钟来增加调试的难度。 本帖最后由 Ayala 于 2020-5-24 21:48 编辑
0xAA55 发表于 2020-5-24 18:14
我使用了PLL所以晶振数并不是合适的吞吐量计算来源,而我使用的倍频时钟是为了满足SDRAM的工作要求。
你 ...
并不是使用第2个 PLL
上文提到的PLL锁相环是用于定制时钟频率的。举个例子,比如我有个 50 MHz 的时钟,我们可以把这个时钟输入给PLL,并设置PLL将这个频率提高 8 倍,再降低 3 倍,我们就可以得到约为 133.333333 MHz 频率的时钟,而这个时钟频率正好是我所使用的SDRAM芯片的工作频率。
50 MHz
1us 50次
提高8倍后降低3倍
3us 400次
15ns 2次
根据我们的时钟频率是133.333333 MHz,每个周期的时间相当于:
1s / 133,333,333 Hz
= 1,000ms / 133,333,333 Hz
= 1,000,000us / 133,333,333 Hz
= 1,000,000,000ns / 133,333,333 Hz
≈ 7.5ns
其中tRCD为最小18ns,最大20ns,也就是最小2.4个周期到最大2.666666个周期,向上取整,我们需要等待三个周期才能算它完成了Bank的激活和Row的开启。
由于传输VGA信号需要严格的像素时钟,我们需要计算传输每个像素所需要的时间。假设分辨率为1024x768,则实际分辨率(也就是包括了消隐区域的分辨率)是1280x796,像素数共1018880。假设屏幕刷新率为60 Hz,则每秒传输的像素数是61132800个。
这里应该是存在问题的,不同分辨率不同刷新率的消隐像素是不同的
https://www.ibm.com/support/knowledgecenter/TI0003M/p8egb/p8egb_supportedresolution.htm
http://web.mit.edu/6.111/www/s2004/NEWKIT/vga.shtml
1344*806*60= 64995840
每个像素的传输用时是:
1,000,000,000ns / 61132800px = 16.357830820770519262981574539363 ns 约等于 16.358 纳秒。
1,000,000,000ns / 64995840px = 15.3856 ns
所以我们的FPGA要有能力可以更为精确地传输每个像素。我的做法是通过计时器的计算,每隔一定周期后延时1周期,用这种方式可以慢慢减少刷新率的误差。但有的像素可能会被稍微横向拉长。
每隔一定周期,延迟一个周期
16666666.666666666666666666666667 ns传递一个画面 1344*806 = 1083264
最慢的像素时速 15.3856 ns 只能比15ns快不能比它慢 按照 15ns每像素的速度 要考虑指令本身耗时 一个画面传递完用时16248960 ns 空闲时间 417706.6 ns
空闲延迟直接硬性计数就好了 417706.6 / 15 * 2= 55694 次(55694* (15 / 2) + 16248960 ) * 60 = 999999900 ns还有100ns的误差 但是这个100 ns不可修正 因为没办法平分到每帧里,133,333,320 次 但是考虑实际情况 频率不可能那么精确 如果不显示时钟也不是很必要去校准 但是精益求精肯定需要去校准 只要下误差在 55694就是允许的 如果比55694还高 那就需要在15.3856ns内传递更多像素,或者降低频率 ,如果显示时钟每天还需要去修正一次误差,市面上的廉价电子钟都不会这么做
Ayala 发表于 2020-5-24 19:17
并不是使用第2个 PLL
编辑中....
真是十分复杂的编辑呀 本帖最后由 Ayala 于 2020-5-24 21:21 编辑
0xAA55 发表于 2020-5-24 20:35
真是十分复杂的编辑呀
编辑了一部分脑子不好使了 先这样啦 挑选了一些关键性的问题 一些技巧的东西想起来再继续编辑 很久不写硬件的东西了
代码里用800*600的消隐数据来处理1024*768 像素不出问题才怪呢 Ayala 发表于 2020-5-24 21:05
编辑了一部分脑子不好使了 先这样啦 挑选了一些关键性的问题 一些技巧的东西想起来再继续编辑 很久不写硬 ...
原来如此。我一直不知道消隐部分的大小和长度是怎么来的。但经过我的测试,好像我这个显示器都可以正常显示。只是我说的像素拉长,是因为我有的像素用3个时钟来显示,有的用两个。然而似乎看不太出来,就算我显示黑白交替的网格之类的 0xAA55 发表于 2020-5-24 23:13
原来如此。我一直不知道消隐部分的大小和长度是怎么来的。但经过我的测试,好像我这个显示器都可以正常显 ...
不同显示器的消隐位置不一样 但是参数还是规范化的 所以有时候需要调整画面 每显示一帧 然后延迟 55694 就好 实际需要校准 不同分辨率不同 但是完全没必要写一大坨代码 Ayala 发表于 2020-5-24 23:29
每显示一帧 然后延迟 55694 就好 实际需要校准 不同分辨率不同 但是完全没必要写一大坨代码 ...
经过我的测试是不能延迟的,会黑屏 0xAA55 发表于 2020-5-25 01:45
经过我的测试是不能延迟的,会黑屏
显存数据丢了么! Ayala 发表于 2020-5-25 02:03
显存数据丢了么!
显存数据很难丢的,这个SDRAM说是64ms要刷新一下,实际上等好几分钟数据都还在
以及,如果显存数据丢了,它的内容会变得以白色为主,偶尔会有几个其它颜色的点
页:
[1]