【精品博文】FPGA 时序描述语言

先明确一下这里所指的“时序”,代表一组信号的逻辑关系,而不是指延时 steptime holdtime这些时序。

想要了解为什么会有这篇文章,请参考《FPGA何去何从》系列,和《FPGA需要怎样的HLS》,这里只关注TDL的实现和带来效果。

上图为不同实现的简单对比,我现在的状态处于图中X位置,说明现在我大量采用interface的设计,开发效率和verilog还处于同一个量级(这和我一直在攒的interface ip数量有关系)。本质上TDL不是新的东西,完全是建立在interface上面的,这个和ARM SOC 建立在AXI4上是一样的。没有sv 的interface就不会有TDL,由interface到TDL又是自然而然的事情,TDL的语法几乎是建立在特定总线sv模块上面的。现在我已经攒了足够多的AXI 模块(数量应该是xilinx axi ip的4倍左右),有些模块只是几行代码,但是对TDL是很有用的。

感觉过得很快,从我开始大量使用interface做设计(写《FPGA何去何从的时候》)到现在不过半年的时间,我已经开始构建TDL的实现环境了,估计今年就可以完成。

TDL的核心就是自动例化sv 的interface和相关模块,它最终还是要生成一个sv文件的。当FPGA设计里面,对几个特定总线(AXI,AVALON或其他)大量使用,和大量复用使用总线的模块时,用脚本去自动编写代码效率提升是十分大的。而这里涉及到一个问题,我们所面临的设计是否都可以只使用特定interface(如axi)进行设计?,我的回答是可以的,不仅可以,而且设计就应该只走标准总线。想想FPGA SOC 也只是采用一种总线。

《How we get to now》讲得挺有道理,时间点就是刚刚好,modelsim直到最新版的(10.5,去年刚出的,10.4版都不行)才有效支持sv的 interface语法参数层级引用,vivado也是最近两个版本对sv的综合才勉强够用,虽然需要优化的地方很多。所以即使在一年前,想在FPGA里面大量使用interface互联设计,肯定不如意,你要面对一堆软件 BUG。《How we get to now》也说应该不止我一个人想到TDL这个点子。

-----------------------------------------------------------------------------------------------------------------------------------------

设计本来应该针对时序的,而且是面向对象的,可持续集成,容易拓展,测试,修改的。

上面这条是写到page的,下面这条才是

用最少的代码,完成最多的功能

好了,开始正文,我们来重新构建一个硬件设计生态,时序描述语言。所用的时序都是类,clock,video,axi,avalon等

从最基本的说起,最简单的时序,时钟clock,时钟对象的属性为周期,那么我们可以有这样的语法(我借鉴Ruby)。

clock_obj = Clock.new(freq:"100M")

这就是最简单的定义,那么下面就是对象的方法,Clock类都有一个分频的方法

div2_clock = clock_obj.div(2)

现在看虽然和传统的HDL差别很大,但是并不能看出怎么提升生产力。不急,来讲个复杂的,Video类。

v0 = Video.new(1080)    #定义一个1080P RGB的时序v1 = Video.new(1080)    #定义另一个1080P RGB的时序

我们把图像处理的模块都看成对象的方法,低通 高通滤波,边缘锐化,中值滤波。

v_lpf = v0.LPF    #图像0做低通滤波,参数用默认值v_hpf= v1.HPF   #图像1做高通滤波,参数用默认值

如果,类中没有我们想要的方法呢,比如Video类中并没有我们想要的中值滤波,我们可以自己定义一个

def Video.medium_filter    # 只要已经有了medium filter的hdl文件,和port定义满足Video要求end

然后就可以这样用

v0_mf = v_lpf.medium_filterv1_mf = v_hpf.medium_filter

最后做alpha 35%叠加输出

v_out = v0_mf.alpha(v1_mf,0.35)

上面的时序对象其实都是中间变量,完全可以把整个功能压缩成一句

v_out = Video.new(1080) .LPF.medium_filter.alpha(Video.new(1080) .LPF.medium_filter,0.35)

就这样两路1080视频滤波后叠加的代码就只是上面一行。可以看到特意"强化"了信号时序(像sv 的interface),"弱化"了模块,传统设计“关联组信号时序”只是verilig几个相关reg,sv的interface,Qsys粗细不一的连线,现在变成实实在在的类对象,而且有自己特定的方法,这些方法就是对应了module,却不像module 例化那么麻烦,没有port inout名称,只保留核心功能。

------------------------------------------------------------------------------------------------------------------------

为了跟进一步深入说明,我们假定做一个实实在在的简单工程,4路1080P拼接成4K,看懂可能需要一些 axi 知识。

and,再假定我们还有下面这些基础模块(这些模块一些对应原厂真实IP)

DDR3 IP AXI4接口

video_to_AXI_STREAM

AXI_STREAM_to_video

AXI_STREAM_to_AXI4_WR    #这个xilinx没有,把axi stream按自己的定义转成axi4,本质上就是吧addr + len 附加到 stream上

AXI4_RD_to_AXI_STREAM          #这个只是简单的剔除掉除了 stream的其他信号

AXI4_packet_fifo

AXI4_burst_partition  # 这个xilinx好像没有,burst切分成小块

AXI4_datawidth_convert #数据位宽转换

下面就是TDL CODE

wr_origin_1080P[3:0] = Video.new()[3:0]    #直接例化4个1080P原始时序wr_orgin_axis[3:0]  =  wr_origin_1080P[3:0].video_to_AXI_STREAM  # video 流转成 axi stream 流wr_orgin_axi4[0] = wr_orgin_axis[0].AXI_STREAM_to_AXI4_WR(addr:0)  # 写到 0地址wr_orgin_axi4[1] = wr_orgin_axis[1].AXI_STREAM_to_AXI4_WR(addr:100)  # 写到 100地址wr_orgin_axi4[2] = wr_orgin_axis[2].AXI_STREAM_to_AXI4_WR(addr:200)  # 写到 200地址wr_orgin_axi4[3] = wr_orgin_axis[3].AXI_STREAM_to_AXI4_WR(addr:300)  # 写到 300地址wr_par_axi4[3:0]    = wr_orgin_axi4[3:0].AXI4_datawidth_convert( 256  ) # 转成256位宽的总线wr_pkt_axi4[3:0]    = wr_par_axi4[3:0].AXI4_packet_fifo(4)    # fifo 深度4,表示可以存4 packet,ddr多路读写仲裁必须要有fifo存数据wr_pkt_axi4[3:0].DDR3_IP_AXI4() # 直接调用ddr3 ip 方法,完全把仲裁 interconnect隐藏掉了rd_pkt_axi4[3:0] = AXI4.new(4)[3:0]    #直接例化4个axi4 接口DDR3_IP_AXI4(rd_pkt_axi4[3:0] , [0,100,200,300])    # 从 DDR3 IP 读出axi4rd_axi_stream[3:0] = rd_pkt_axi4[3:0].AXI4_RD_to_AXI_STREAM   # axi4 转成 axi streamrd_video_1080p[3:0] = rd_axi_stream[3:0].AXI_STREAM_to_video  # axi stream 转成 videoK4_sync(rd_video_1080p[3:0])     #4K分辨率 行场时序生成控制

上面功能基本已经写完....

未完待续

先提一点,interface 对于verilog,会有3-5倍的效率提升,TDL对于sv interface 会有10倍左右的效率提升,那么TDL对于传统的verilog会有30-50倍的效率。

什么概念呢? A7 200T的期间,1000行代码,就能撑满资源。

我很少声明版权,但是这篇极其重要,必需加版权声明

(0)

相关推荐