Verilog高级知识点

一、阻塞和非阻塞

       阻塞和非阻塞也是FPGA经常会遇到的概念,不仅仅在信号的赋值时候会出现,也经常在Xilinx IP核配置中出现,所以笔者想在这里把这个概念阐述清楚,方便大家对后续程序编写和IP核配置上的理解。

阻塞赋值(Blocking)

        阻塞顾名思义,就是说后面的语句会受到前面的语句的影响,通俗的说就是如果在一条阻塞赋值语句还没有被执行,那么该语句后面的语句也不能被执行即被阻塞了,换而言之就是说在同一个always块内的语句有着串行顺序执行的特点,这点和C语言有点相似,比如C语言里同一个函数,里面的每句话都是顺序被执行调用的,这里阻塞赋值符号为“=”,如b=a,我们来看下面一个always块内的代码,在复位信号rst_n置0的时候,a = 8'h1、b = 8'h2、c = 8'h3,而复位信号rst_n置1,即没有被复位的时候,a的值应该被赋值为8'h0,但是由于always模块用的是时序逻辑,所以下一个时钟上升沿即第五个时钟上升沿a才被赋值,同时因为使用了阻塞赋值,a的8'h0值也被顺序执行赋值给b,b再顺序执行赋值给c,代码和对应的波形图如下所示。    

Verilog高级知识点_第1张图片

Verilog高级知识点_第2张图片

图1 always模块阻塞赋值的波形图

 

非阻塞赋值(Non Blocking)

       和阻塞赋值相对应,如果把阻塞赋值看成是串行赋值,那么则可以把非阻塞赋值看成是并行赋值,符号“<=”用于非阻塞赋值,如b<=a,非阻塞赋值是由时钟节拍决定的,在时钟上升沿到来的时刻,将begin … end之间所有的赋值语句同时从右边赋值到左边,每个时钟上升沿去执行一次,属于并行赋值操作,这点和C语言串行顺序执行有着很大的区别。

       我们也来看下面一个always块内的代码,在复位信号rst_n置0的时候,a <= 8'h1、b <= 8'h2、c <= 8'h3,如图3-3所示,这里因为使用了非阻塞赋值,在第四个时钟周期内复位信号rst_n置1,即没有被复位的时候,a的值应该被赋值为8'h0,但是由于always模块用的是时序逻辑,所以在第五个时钟上升沿到来的时候,a的值才被赋值为8'h0,而又因为在第四个时钟周期内a的值是8'h1,b的值是8'h2,所以在第五个时钟上升沿到来的时候,b的值被赋值为8'h1,c的值被赋值为8'h2 ,同样的道理大家可以看到在第六和第七两个时钟上升沿来到的时候,b和c又被重新赋值。

Verilog高级知识点_第3张图片

 

Verilog高级知识点_第4张图片

图2 always模块非阻塞赋值的波形图 

        那么刚接触到Verilog的朋友们可能会疑惑,到底什么时候用阻塞赋值,什么时候用非阻塞赋值呢?其实一般说来,在组合逻辑的时候,我们通常使用阻塞赋值,比如用assign赋值语句和用不带时钟的always赋值语句,这种组合逻辑只和当前输入电平有关系,非常适合阻塞赋值;而在时序逻辑的时候,我们通常使用非阻塞赋值,比如用带时钟的always赋值语句,这种时序逻辑和时钟上升沿有关系,只有在触发沿时才会发生赋值的变化,非常适合非阻塞赋值。

        类似于一个always块内的赋值代码,Xilinx官方也提供了一些常用到IP核方便用户进行二次开发,大家可以把IP核简单理解成官方封装好的一个模块,对外提供input输入接口和output输出接口,好像一个黑盒子一样,也有一些IP核配置中会有阻塞模式和非阻塞模式的选择,这里举个cordic计算三角函数和平方根的IP核的例子,以下是Xilinx官方手册对于阻塞模式和非阻塞模式的说明截图,对比两张截图大家可以清楚地看到如果配置成阻塞模式,在s_axis_cartesian_tready和s_axis_cartesian_tvalid为高时,即s_axis_cartesian_tdata有效,同样的在s_axis_phase_tready和s_axis_phase_tvalid为高时,即s_axis_phase_tdata有效,m_axis_dout_tdata会使用前面没使用到的A1:B1作为输入,代入IP核计算,而如果把IP核配置成非阻塞模式,那么就不会考虑这么多了,直接看当前周期内的值把A2:B1作为输入代入IP核内计算,其实这里和一个always模块的阻塞赋值和非阻塞赋值是一样的意思,阻塞模式和非阻塞模式则有时候会出现在一些IP核配置上。

Verilog高级知识点_第5张图片

图3 Cordic IP核对阻塞模式的说明截图

Verilog高级知识点_第6张图片

图4 Cordic IP核对非阻塞模式的说明截图

二、assign和always的区别

           assign和always是Verilog里面最基本的两个语句,经常被使用,区别在于assign不能带时钟,而always可以带时钟也可以不带时钟,在always不带时钟的时候,比如写成always  @(*)begin,和assign实现的逻辑功能完全一致,都是去产生组合逻辑;如果简单的组合逻辑,建议大家用assign语句去产生,规范好变量名代码显得清晰明了,如果复杂的组合逻辑,建议大家用always语句去产生,为了让代码的可读性更高,也举个上面提到的代码例子,大家可以去体会下,其实assign mcu_read_done = (~spi_cs_r1 & spi_cs_r0) ? 1'b1 : 1'b0; 也可以写成下面的always  @(*)begin组合逻辑,另外其中else begin mcu_read_done = 1'b0; end也可以省略不写,用always和assign语句去产生的组合逻辑,还有一点需要注意下,如果always语句中产生的信号需要定义成reg型,而assign语句产生的信号需要定义成wire型。

        这里也特意说明一下组合逻辑和时序逻辑的区别:1.组合逻辑在当前时钟做条件判断直接在当前周期变化,而时序逻辑在当前时钟做条件判断后在下个周期时钟上升沿才做变化;2.组合逻辑模块在所有的if条件均为假的时候,在当前周期会立刻被清零,所以用always  @(*) begin产生组合逻辑的时候可以不用写else begin … end中间的mcu_read_done = 1'b0,而时序逻辑模块在所有的if条件均为假的时候,则会去保留之前的值不被清零,所以用always  @(posedge clk or negedge rst_n)begin产生时序逻辑的时候,如果想清零某些标志信号一般需要去写else begin … end中间的mcu_read_done <= 1'b0。

Verilog高级知识点_第7张图片

 

三、模块例化

        通过前面的Verilog基础知识点介绍,大家也都知道了FPGA程序内部是由一个个模块组成的,这些模块可能是手写的不同功能的模块,也可能是调用芯片厂家提供的IP核,那么在整个工程中就需要把每个模块的信号相互连接起来,这里术语就叫作模块例化,举个实际的例子,不同按键控制不同LED灯亮灭,这也是开发板自带的一个简单入门例程,我们把按键检测写成一个接口模块命名为key_scan,具体信号列表如图5所示,顶层模块命名为key_led,然后来具体看下顶层模块key_led是如何例化子模块的,例化的方法如下图6所示,在这里简单地向大家说明下,子模块名是指被例化模块的模块名,而例化模块名相当于一种标识,当例化多个相同模块时,就可以通过例化模块名的不同去加以区分,通常人们都习惯写成“u” +“子模块名”,信号列表中“.”之后的信号都是key_scan模块定义的端口信号,括号内的信号则是顶层模块key_led声明的信号,这样就可以将顶层模块的信号和子模块的信号一一对应起来了,这时候另外需要注意信号的位宽要保持一致,此外还需要注意最后一个例化信号的括号后面不要加“,”,否则会报编译错误。

Verilog高级知识点_第8张图片

图5 key_scan子模块的信号列表 

Verilog高级知识点_第9张图片

图6 key_led模块例化key_scan子模块中的信号

四、FPGA模块化设计理念

        FPGA设计理念虽然有很多种,但实际应用上基本都是采用自顶向下的设计理念,因为随着需求上对功能的丰富和代码的增加,传统的自低向上的设计理念已经慢慢不太实用了,不利于工程升级和维护,实际项目工程中,一个设计往往从系统级开始,然后再把系统分为几大功能模块,功能模块再逐渐细分到基本单元,类似下面示意图的树状结构。

Verilog高级知识点_第10张图片

 图7 FPGA自顶向下模块化的设计理念

       可能大家刚开始猛地一听觉得有些抽象不太好理解,笔者举个“SD卡存放音频WAV播放”的例子来帮助大家理解FPGA自顶向下模块化的设计理念,整体软件上的需求是这样的:SD卡里事先存放了各类文件,包括bmp格式的图片和wav格式的音乐等等,然后用户按下按键,FPGA去遍历SD卡的每个扇区搜索带wav包头的文件,其中wav包头文件有特定字段表示该音频的长度,FPGA找到wav音频后,把SD卡所存储的wav音频数据去除包头再写入一个FIFO缓存中,音频编解码芯片WM8731再去从FIFO缓存中读这些音频数据,读出后WM8731芯片通过内部的DA转换输出音频音乐,用户插上开发板上的耳机即可听到动听的音乐,播放完一首歌后,按下按键,FPGA再从后面的扇区号开始搜索下一首歌曲,在播放歌曲的时候开发板上的LED灯也会来回闪烁,整个工程自顶向下模块化的设计如下图8所示。

Verilog高级知识点_第11张图片

图8 SD卡存放音频WAV播放自顶向下模块化的设计 

        我们可以拿这个例程去分析下FPGA自顶向下的设计理念,也把它看成个一个小的项目吧,大家可以类比下,这里也有些经典的FPGA设计理念,对于FPGA来说,SD卡是数据产生的地方,而WM8731音频编解码芯片是使用数据的地方这是一个程序设计上的主干,然后我们来看一看这些数据怎么去产生:1.FPGA用到SPI总线去读写SD卡;2.SD卡首先需要初始化才能正常读写操作;3.SD卡需要知道扇区号才能读出来对应扇区的数据512个字节的数据。一样的道理我们再去分析下这些从SD卡读出的音频数据怎么处理:1.FPGA用到IIC总线去配置WM8731芯片,对其进行初始化;2.初始化的数据配置按照芯片手册去操作;3.FPGA驱动WM8731芯片把SD卡读到的音频数据,按照一定的顺序组包再根据芯片手册上的时序送到其dac数据引脚。

      可能大家觉得这仅仅只是一个例程,但实际项目工程上其实在代码和设计方法也大同小异,只不过可能变换了应用场景功能并且细节上更加丰富了而已,举几个实际工程例子:串行SPI的adc去实时采集一个仪器的电压电流,其余模块再去根据一个周期采样到的点去计算平均值、有效值、最大值,对一个周期内的波形进行FFT快速傅里叶变化分析还原采集到的波形;类交换机产品SFP光口通信,协议层的组包和拆包,数据处理以后再去发送出去;驱动CMOS摄像头去实时采集视频,对视频进行压缩处理,多帧缓存,实时通过HDMI接口传输视频等等。站在FPGA的角度来看,其实说到底也是一端产生数据,而另一端去使用数据,当然可能数据产生和使用端具体情况要求去具体分析设计,可能产生数据端是SD卡内存着的数据;可能是adc采集到的波形数据;可能是各种高速接口比如Rapid IO,PCIE*4,SFP,SATA收到的数据;也可能是不同接口的摄像头实时采集到的视频。然后使用数据端则根据项目需求来具体设计,对于“SD卡存放音频WAV播放”的例程数据使用端就是WM8731音频编解码芯片;对于仪器仪表adc采样,数据使用端就是平均值、有效值、最大值等的计算模块和FFT快速傅里叶分析模块;对于高速接口应用数据使用端就是把从协议层取出的数据送到数据处理端或者数据应用端;对于视频处理,数据使用端可能就是图像压缩、图像卷积等等后续的图像处理模块。

你可能感兴趣的:(FPGA基础知识,fpga开发)