tag 标签: 芯航线

相关博文
  • 热度 24
    2016-2-1 17:58
    3785 次阅读|
    4 个评论
        我们在学习和调试NIOS II工程的时候,一般都是先使用Quartus II软件中提供的Quartus Programmer来烧写FPGA配置文件(SOF),然后NIOS II EDS中提供的Flash Programmer工具来进行烧写NIOS II的。这对于开发者来说,并没有什么不便,反而因为这种方式的灵活,为开发带了了很大的便利。然而,当我们的产品已经设计完成并量产的时候,就需要将固件烧写到产品中。生产线上进行烧录时,总希望能够用最简单的方式实现。试想,如果生产线上在进行烧写时,还需要几个工具换来换去,等待很久,效率自然就下去了。因此这种Quartus Programmer+Flash Programmer的方式并不适合生产。       小梅哥在最近的工作中也遇到了这样的问题。我们新设计的一批开发板,在工厂生产完毕后,都要进行出厂测试。然而SMT厂家却并不熟悉我们的这种Quartus Programmer+Flash Programmer烧写方式。再说了,要使用这种方式还得安装Quartus Programmer和NIOS II EDS软件。厂家表示使用这种方式对他们来说有一定难度,而且效率也不高。所以我就根据Altera 官方网站上的一个帖子,进行了转换,将SOF文件和NIOS II的elf固件合并并生成了一个jic文件,这样,厂家就只需要使用Quartus Programmer来烧写这个jic文件就能实现同时烧写FPGA配置文件和NIOS II固件的功能了,简化步骤,节省时间。       从SOF文件和ELF文件得到JIC文件的原帖地址如下: https://www.altera.com.cn/support/support-resources/knowledge-base/solutions/rd10132010_126.html       因为有经验不足的朋友反映在看了这个教程后还是不知道怎么操作,总是不成功,因此这里小梅哥使用我们芯航线FPGA的开发板,一步一步演示这个实现过程,将整个过程具体化。      先说明下我这个设计工程的结构(温馨提示:点击图片可查看高清大图): EPCS16 : 用来存储FPGA配置文件和NIOS的固件,本例中最终转换得到的JIC文件也是烧写到该器件中。 512K 字节 SRAM : 作为NIOS II运存或者4.3寸TFT显存,这里作为TFT显存。(PS:使用SRAM作为运存,相较于使用SDRAM作为运存,NIOS II的性能会有较大的提升。) 128Mbit SDRAM : 作为NIOS II运存或者4.3寸TFT显存,这里作为NIOS II运存。以运行较为复杂的程序或者GUI。 4.3 寸 TFT : 用来显示文字/图片等内容。 XPT2046 触摸控制器: 使用SPI接口,用来得到触摸屏信息,实现人机交互 CH340 USB2TTL : 将UART协议与USB协议进行互相转换。以实现调试的功能。 4bit LED : 指示程序运行状态。 2X 轻触按键: 输入控制信息      介绍完了这个系统,接下来就可以介绍整个转换过程了: 1   sof2flash:    从一个.sof 文件生成一个flash文件: sof2flash --input= .sof --output=hwimage.flash --epcs –verbose     首先我们打开我们的NIOS II软件工程和对应板级支持包,这里名为tft_touch和tft_touch_bsp (温馨提示:点击图片可查看高清大图)    然后选中tft_touch,单击右键选择Nios II  -  Nios II Command Shell (温馨提示:点击图片可查看高清大图)      我们的Quartus II生产的sof文件名为” TFT_SRAM.sof”,这个时候,如果我们直接输入 sof2flash --input= TFT_SRAM.sof --output=hwimage.flash --epcs –verbose    会提示找不到input file也就是找不到TFT_SRAM.sof文件。 (温馨提示:点击图片可查看高清大图)    这是因为该命令是在当前目录下寻找TFT_SRAM.sof文件,而我们的TFT_SRAM.sof文件在E:\easy_sopc\TFT_SRAM\prj\output_files目录下,因此当然无法找到该文件了。解决这个问题的方法有两种。 第一种,推荐方案。    因为很多不熟悉命令行的朋友在操作时速度慢而且容易出错,因此这里提供一种比较熟悉的方式。首先在windows中,将TFT_SRAM.sof文件从output_files文件夹中拷贝到tft_touch文件夹中 (温馨提示:点击图片可查看高清大图) :    然后回到命令行窗口再次执行 sof2flash --input= TFT_SRAM.sof --output=hwimage.flash --epcs –verbose命令    (提示:使用键盘上的向上方向键,可以快速切换到之前使用过的命令,这里在切换目录后,连按两次方向上键就应该能找到之前输入的sof2flash命令。)生成过程大约花费10秒钟。生成完成后的截图如下所示 (温馨提示:点击图片可查看高清大图) :    然后我们输入ls命令就能看到,确实生成了这样一个名为hwimage.flash的文件 (温馨提示:点击图片可查看高清大图) :      第二种方案:首先在shell中使用cd命令直接将目录切换到sof文件所在目录,也就是E:\easy_sopc\TFT_SRAM\prj\output_files。相应命令为(注意斜线方向): cd e:/easy_sopc/TFT_SRAM/prj/output_files (温馨提示:点击图片可查看高清大图)      然后再次执行sof2flash命令即可实现。生成完成后的截图如下所示 (温馨提示:点击图片可查看高清大图) :    然后我们输入ls命令就能看到,确实生成了这样一个名为hwimage.flash的文件 (温馨提示:点击图片可查看高清大图) :      一般推荐大家使用第一种方式,当然命令行高手除外。   2   elf2flash:    从一个,elf 生成一个flash文件: elf2flash --input= .elf --output=swimage.flash --epcs --after=hwimage.flash --verbose    因为推荐大家使用第一种方式操作,因此这里就按照第一种方式接着讲,相信有能力用命令行方式切换目录的朋友,也不会对其他操作存在问题。 这里我们就只需要直接输入elf2flash命令即可了,命令详细如下: elf2flash --input=tft_touch.elf --output=swimage.flash --epcs --after=hwimage.flash –verbose    从命令中可以看到,第一步生成的hwimage.flash文件是作为了参数的一部分的,所以这里必须保证hwimage.flash在当前目录下。(第一步中使用推荐的方式,则能够自动保证这一点)。命令执行结果如下 (温馨提示:点击图片可查看高清大图) :    然后我们输入ls命令,可以看到,在当前文件夹下确实生成了一个名为swimage.flash的文件 (温馨提示:点击图片可查看高清大图) : 3   flash2hex:    转换.flash文件到.hex文件:直接输入以下命令(注意:altera官网中原帖这个地方命令有误,前后对应不上,原帖为nios2-e…… mysw.flash mysw.hex,应该讲mysw改为swimage): nios2-elf-objcopy --input-target srec --output-target ihex swimage.flash swimage.hex    这个命令瞬间执行完成,我们ls下,就能看到当前文件夹下已经生成了一个swimage.hex的文件 (温馨提示:点击图片可查看高清大图) : 4   Convert Programming Files    在Quartus® II软件中,open File Convert Programming Files  Set the programming file as JTAG Indirect Configuration File (.jic). (温馨提示:点击图片可查看高清大图)     5   选择EPCS    在配置下拉菜单中选择合适大小的EPCS器件(见10步图) 6   命名jic    命名你的输出.jic 文件(见10步图) 7   Add Device    点击Flash Loader的下面,在右边选择Add Device (见10步图) 8   选择FPGA器件    从列表中选择你的FPGA器件(见10步图) 9   Add SOF    点击SOF Data,选择Add File,选择加.sof 文件(见10步图) 10 Add Hex data    点击Add Hex data,选择Relative addressing,选择上面生成的.hex 文件 (温馨提示:点击图片可查看高清大图)   11 Generate    然后点击Generate生成。生成完成后检查生成的 .map 文件(使用记事本打开)有Page_0在起始地址0x0,.hex文件在Page_0结束地址后的起始地址1 (温馨提示:点击图片可查看高清大图)   12 烧写    现在在Quartus II Programmer中,选择Add File,选择加.jic 文件。检查Program框,下一步.jic 文件,接着按Start即可。   13 女神镇楼    最后上一张测试图,女神镇楼(话说放这样一张图给客户,客户是不是会觉得特别开心呢) (温馨提示:点击图片可查看高清大图) :      如有更多问题,欢迎加入芯航线FPGA技术支持群:472607506   小梅哥 芯航线电子工作室 2016年1月30日星期六      
  • 热度 26
    2016-2-1 08:42
    2246 次阅读|
    2 个评论
            在Verilog语法中,可以实现参数化设计。所谓参数化设计,就是在一个功能模块中,对于一个常量,其值在不同的应用场合需要设置为不同的置,则将此值在设计时使用parameter 关键字声明,那么在上层模块例化使用该功能模块时,可以根据具体需求重新配置该常量的值,从而实现不同应用场合对对应常量的灵活调整。         以下为使用Verilog设计的一个控制LED闪烁灯的模块代码:   01  module counter ( Clk , Rst_n , led ); 02 03     input Clk ; //系统时钟,50M 04     input Rst_n ;   //全局复位,低电平复位 05     06     output reg led ;    //led输出 07     08     reg cnt ;     //定义计数器寄存器 09 10  //计数器计数进程 11     always @( posedge Clk or negedge Rst_n ) 12     if ( Rst_n == 1'b0 ) 13         cnt = 25'd0 ; 14     else if ( cnt == 25'd24_999_999 ) 15         cnt = 25'd0 ; 16     else 17         cnt = cnt + 1'b1 ; 18 19  //led输出控制进程 20     always @( posedge Clk or negedge Rst_n ) 21     if ( Rst_n == 1'b0 ) 22         led = 1'b1 ; 23     else if ( cnt == 25'd24_999_999 ) 24         led = ~ led ; 25     else 26         led = led ; 27 28  endmodule           在此设计中,定义了一个名为cnt的计数器,该cnt在输入时钟”Clk”的驱动下,每个时钟的上升沿执行一次自加操作,当计数到达24999999后,计数器清零,同时led驱动状态发生翻转。由于系统时钟为50M,即周期为20ns,cnt每计数25000000次,即500ms,驱动led状态翻转一次,从而可以实现控制led以1s为周期进行亮灭闪烁。在这个系统中,我们可以看到,如果我们希望将闪烁速度进行更改,例如改为每50ms让led翻转一次,则需要在代码中将”cnt == 25'd24_999_999”中25'd24_999_999这个常量修改为25'd24_999_99。           现在考虑另外一种情况,假如某个系统中有三个LED灯需要通过这种方式来进行驱动闪烁,每个LED灯的闪烁频率还不一样。现假设LED1闪烁频率为1s,LED2为0.1s,LED3为0.01秒。那么这三个LED驱动模块中,”25'd24_999_999”这个常量就需要分别修改为   LED1:”25'd24_999_999” LED2:”25'd24_999_99” LED3:”25'd24_999_9”           因此,必须独立的设计三个这样的模块,然后在上层模块中分别例化这三个模块,以实现需求的功能。           独立设计这样三个模块并分别例化,确实能够完全实现上述假设系统需求的功能,但是通过这种方式设计出来的模块,不具备通用性,如果要求发生变化,则还需要回到底层模块中去修改对应的常量,而底层模块中可能不止一个地方使用到了这个常量,例如上述代码中,第14行和第23行就分别用到了这个常量。如果模块中对此常量的使用次数较多,在修改的过程即增加了工作量,又极容易发生遗漏,从而导致错误。           而参数化设计的使用,则可以非常完美的解决这一问题。这里首先放上该设计使用参数化设计之后的该模块代码: 01  module counter ( Clk , Rst_n , led ); 02 03     input Clk ; //系统时钟 04     input Rst_n ;   //全局复位,低电平复位 05     06     output reg led ;    //led输出 07     08     reg cnt ;     //定义计数器寄存器 09     10     parameter CNT_MAX = 25'd24_999_999 ; 11 12  //计数器计数进程 13     always @( posedge Clk or negedge Rst_n ) 14     if ( Rst_n == 1'b0 ) 15         cnt = 25'd0 ; 16     else if ( cnt == CNT_MAX ) 17         cnt = 25'd0 ; 18     else 19         cnt = cnt + 1'b1 ; 20 21  //led输出控制进程 22     always @( posedge Clk or negedge Rst_n ) 23     if ( Rst_n == 1'b0 ) 24         led = 1'b1 ; 25     else if ( cnt == CNT_MAX ) 26         led = ~ led ; 27     else 28         led = led ; 29 30  endmodule        这里,我们使用parameter在第16行声明了一个参数化常量”CNT_MAX”,在第16行和第25行进行条件判断时,直接判断cnt的值是否与该参数值相等,而不再是直接写具体数字的方式。到这里大家就可以看到,通过这样一种方式,首先就能避免一个模块中多次使用该常量而对修改设计带来的隐患。因为当我们需要修改该常量的值以适应不同的应用要求时,直接修改这个parameter的值即可,从而可以避免因为该参数使用的地方过多而在修改时容易出现的遗漏的问题。           另外,在我们的系统中,假如也需要三个这样的模块来对应驱动三个LED,大家可能首先想到的就是将该代码复制得到三个文件,并将其中的parameter值修改为对应需要的值即可。此种方式虽然能够避免一个常量在模块中被多次使用,修改时容易发生的遗漏问题,但仍然还是需要存在三个独立的子模块,并未大大减轻设计工作量。其实,修改parameter有更加快捷的方式,这种方式不需要我们针对每一个需求分别复制设计代码并修改对应参数,只需要在上层模块例化该模块时,直接在例化的过程中修改该值即可。具体实现方法,这里以实例代码的形式进行讲解。           在上层模块中,例化子模块时直接修改其参数有两种语法结构,这里我们首先看第一种: 01  module LED_flicker ( 02     Clk , 03     Rst_n , 04     LED 05  ); 06 07     input Clk ; 08     input Rst_n ; 09     output LED ; 10 11     counter    counter0 ( 12         . Clk ( Clk ), 13         . Rst_n ( Rst_n ), 14         . led ( LED ) 15     ); 16     17     counter    counter1 ( 18         . Clk ( Clk ), 19         . Rst_n ( Rst_n ), 20         . led ( LED ) 21     ); 22     23     defparam counter0 . CNT_MAX = 24_999_99 ; 24     defparam counter1 . CNT_MAX = 24_999_9 ; 25     26  endmodule           这里,在顶层模块中例化了两个counter模块,并分别取名为counter0和counter1。counter0的LED输出驱动LED ,counter1的LED输出驱动LED ,为了实现LED 以0.1秒的频率闪烁,LED 以0.01秒的频率闪烁,在第23行和24行分别使用”defparam”来重新定义CNT_MAX的值。这里以第23行修改counter0中的CNT_MAX的值来具体讲解此种语法的结构。           首先,使用关键字”defparam”来声明这里需要对某个参数的值进行重新定义,空格之后紧跟着的counter0为counter模块被例化的名字,counter0后紧随一个”.”,”.”后紧随着的就是counter模块中需要修改的参数的名字,然后使用”=”将新的值赋给改参数即可。这里使用模块名+”.”+参数名的方式,与C语言结构体中使用结构体成员的形式很类似,大家可以对比学习。通过这种方式,大家可以在不修改原有设计文件的情况下,例化后,在上层模块直接修改参数。虽然模块中设定了有默认值,但是使用defparam修改的值比原始设计文件中的值拥有更高的编译优先级。当使用defparam修改了原始文件中的参数值后,原始文件中的默认参数值即被忽略。           当我们设计完成后,就需要对设计的模块进行仿真,由于仿真执行所需花费的时间远远大于板级系统执行功能时需要的时间,因此仿真时,我们希望对设计模块中某些耗时比较长的设计在不影响理论正确性的前提下进行简单的修改,例如,上述counter模块中,实际仿真如果也按照24999999的计数值进行计数,则需要花费太多的仿真时间,严重影响设计效率,因此也可以在仿真时直接在testbench中使用defparam来对设计文件中的参数进行修改,从而降低仿真复杂度。上述LED_flicker系统的testbench代码如下所示: 01  `timescale 1ns / 1ps 02  `define clk_period 20 03 04  module LED_flicker_tb ; 05 06  //source define 07     reg Clk ; 08     reg Rst_n ; 09 10  //probe define 11     wire LED ; 12     13  //instant user module 14     LED_flicker LED_flicker0 ( 15         . Clk ( Clk ), 16         . Rst_n ( Rst_n ), 17         . LED ( LED ) 18     ); 19     20     defparam LED_flicker0 . counter0 . CNT_MAX = 24 ; 21     defparam LED_flicker0 . counter1 . CNT_MAX = 24 ; 22 23  //generater clock 24     initial Clk = 1 ; 25     always #( `clk_period / 2 ) Clk = ~ Clk ; 26 27     initial begin 28         Rst_n = 1'b0 ; 29         #( `clk_period * 20 + 1 ); 30         Rst_n = 1'b1 ; 31         #( `clk_period * 2000 ); 32         $stop ; 33     end 34 35  endmodule           第20行和第21行,使用defparam对设计模块中的CNT_MAX进行了重新定义,我们希望通过这种方式,来在仿真的时候,让计数值非常小,这样仿真便能很快的得出结果,通过这两句话大家也可以看到,结构体的形式能够实现多级调用。然而,当使用此testbench文件直接仿真LED_flicker模块时,却并不能实现预期的效果。在modelsim中仿真时,报告如下信息 (提示:点击图片可查看高清大图) :         虽然并不影响仿真的执行,然而从仿真执行的结果来看,这种在testbench中再次修改设计模块中参数的方式无法与实体模块中例化子模块后进行参数定义同时存在,将实体模块中使用defparam修改参数的部分屏蔽,则仿真文件中的defparam生效,实现了预期效果。下图分别为不屏蔽LED_flicker中defparam内容和屏蔽LED_flicker中defparam内容后的仿真结果,从图中可知,不屏蔽,LED 和LED 的翻转频率分别为100ms和10ms,结果与LED_flicker中defparam定义的值一致,屏蔽掉后,LED 和LED 的翻转频率都是0.001ms,仿真结果与testench文件LED_flicker_tb中defparam定义的值一致。 (提示:点击图片可查看高清大图)   不屏蔽LED_flicker中defparam内容时仿真结果   屏蔽LED_flicker中defparam内容时仿真结果           在实际使用中,我们往往希望实体设计模块中能够设定某参数值为符合实际板级运行要求的值,而在仿真中,为了节约仿真时间,我们又希望修改该参数值以配合仿真通过上面的例子我们发现,使用defparam语句难以同时满足实体设计和仿真的需求。那么有没有更好的方法来实现实体设计与仿真验证中对参数的修改能够共存且互不干扰呢?         为了实现此功能,我们调整在实体模块中例化子模块时对参数的修改方式,上面我们介绍的是使用defparam语句重新定义子模块参数内容。接下来我们使用在模块例化时,直接对参数进行例化的方式来修改子模块中的参数值。使用参数例化方式修改参数值的LED闪烁灯模块代码如下所示: 01  module LED_flicker_inst ( 02     Clk , 03     Rst_n , 04     LED 05  ); 06 07     input Clk ; 08     input Rst_n ; 09     output LED ; 10 11     counter 12     #( 13         . CNT_MAX ( 24_999_99 ) 14     ) 15     counter0 ( 16         . Clk ( Clk ), 17         . Rst_n ( Rst_n ), 18         . led ( LED ) 19     ); 20     21     counter 22     #( 23         . CNT_MAX ( 24_999_9 ) 24     ) 25     counter1 ( 26         . Clk ( Clk ), 27         . Rst_n ( Rst_n ), 28         . led ( LED ) 29     ); 30     31  endmodule         这里,第11行到19行为例化counter0模块的代码。这里对于子模块中的参数,使用了和端口相类似的形式进行修改。通过这种方式,在例化每个功能模块的时候,直接将所需修改参数的值也同时通过例化的方式修改了。        下面对这种例化方式的代码结构进行简单介绍:         首先,第11行为原始设计模块的名字,第12行和14行为一对小括号,对参数例化的内容就在此括号中进行。注意11号,即括号前面需要使用”#”符号来声明这是对参数进行例化。括号内,参数例化形式与端口例化形式一致,使用”.”+原始参数名+新值的方式来修改新的值。通过这种方式,能够避开对于同一个参数多次使用defparam来重定义值而引发的冲突。使用上述testench文件仿真LED_flicker_inst模块的仿真结果如下图所示 (提示:点击图片可查看高清大图) : 使用defparam重新修改CNT_MAX值的仿真结果           可以看到,LED 和LED 的翻转频率都是0.001ms,为通过testbench修改后的结果。           为了验证使用这种例化的方式是否能够达到预期的效果,这里将testbench中defparam内容屏蔽,再来观察仿真结果即可,屏蔽掉testbench文件中defparam内容后仿真结果如下图所示 (提示:点击图片可查看高清大图) : 屏蔽testbench文件中defparam内容后仿真           可以看到,屏蔽掉testbench中队CNT_MAX值的修改内容后,LED 和LED 的翻转频率分别为100ms和10ms,仿真结果就与实际设计值一致。但是在我们实际板级使用的时候,testbench中的值并不会对实际设计内容产生影响,因此,即使不屏蔽testbench中对参数值的修改,也不会影响板级设计结果。           因此我们可以总结得出,为了同时保证实际逻辑设计和仿真验证时对参数的修改能够共存,互不影响,在实体模块设计中,使用参数例化的方式修改被例化模块中的参数。在仿真验证用的testbench中,使用defparam语句来修改对应的参数。           通过本教程内容,讲解了在Verilog中使用参数化设计的方法,展示了参数化设计在实际使用时候的巨大便利。介绍了两种修改参数值的方法,并通过实际的例子展示了两种修改方式的特点,并最终给出了能够同时兼容实际逻辑设计和仿真验证的参数修改方法。希望大家以后。在设计自己的代码的时候,多使用这种参数化的设计方式。   如有更多问题,欢迎加入芯航线FPGA技术支持群:472607506   小梅哥 2015年11月7日于芯航线电子工作室
  • 热度 23
    2016-1-29 23:40
    2320 次阅读|
    3 个评论
          今天,有网友在群里提问“我这个简单的闪灯程序(无其它任何多余的逻辑或*.c),运行久了,居然会死机!运行环境:EPCQ+片上RAM+NIOS II的方式。时钟:37.125MHz,通过JTAG电缆Debug NIOS,则不会。这是怎么回事呀 ?”     相信这样一个问题,很多人都遇到过,我之前在做NIOS II应用开发时,也遇到过这个问题,当时怀疑是NIOS II不稳定导致的,因为各大网络论坛和QQ群,吐槽NIOS II处理器鸡肋的实在是太多了。这里,小梅哥不去评判NIOS II处理器究运算竟性能几何,稳定性如何,因为各人有各人的观念和实际大环境,有的公司打死不愿用NIOS II,而有的公司做产品却离不开NIOS II。对于一个Altera推广了十多年的软核处理器,我不相信经过十多年的发展,还存在这么弱智的BUG。     该网友的问题,实际是可以合理解释并且解决的。       因为jtag uart实际是由jtag模拟的,本质上是个fifo,当你在NIOS II中使用printf(alt_putchar、alt_printf、alt_putchar)函数时,实际是将需要发送的数据写入了这个fifo中,这个fifo中的数据由PC机上的NIOS II EDS软件中附带的程序通过JTAG协议去读取并显示在NIOS II Console中。     那么,假如因为某些原因pc上的console断开(用户手动关闭Console通信,或者JTAG线缆被拔掉),则不会再有程序去持续读取这个fifo中的值,因此,一旦NIOS II中仍旧有发送请求,则会继续去写这个FIFO,因为没有PC机去及时读取这个FIFO中的值,所以当FIFO被写满之后,则NIOS II的JTAG UART驱动会判断得到当前发送FIFO已满,不能再写入数据,因此程序会一直停在这个地方等待,直到FIFO中的数据被读走一部分,以有足够的空间写入需要发送的数据。     所以,因为console已经断开了,所以永远不会再去读取FIFO中的值了,因此这个FIFO在被写满之后也将永远处于满状态,不能再写入新的数据,所以NIOS II CPU的程序也会一直卡在发送函数这里,不会继续执行,就发生了程序死机的现象。     另外,大家可能遇到过,带JTAG UART的并经常有数据通过JTAG UART传输的系统,在运行过程中,我们去进行一下其他的操作,然后Eclipse for NIOS II就停止响应了,程序会强制退出,其实也是受这个JTAG UART的影响。我就经常因为不小心就这样把eclipse搞死了,所以我现在比较保守了,一半不使用JTAG UART进行调试,而是直接用RS232 UART来进行调试。 小梅哥 2016年1月29日 芯航线电子工作室
  • 热度 11
    2016-1-29 17:59
    2168 次阅读|
    1 个评论
             小梅哥接触过不少的从0开始接触Verilog语法的朋友,他们对于语法的掌握基本趋近于0,对于使用Verilog描述一个逻辑模块的最基本结构都不清楚,因此这里小梅哥特地使用一个例子来介绍一下使用Verilog设计数字逻辑模块的一般结构。          本章主要讲解Verilog基础语法的内容,文章以一个最简单的例子“二选一多路器”来引入一个最简单的Verilog设计文件的基本结构。 以下为本章中例子中的代码: 01  /*======================================= 02  *   file neme : mux2.v 03  *   author :   小梅哥 04  *   Verison    :   V1.0 05  *   date       :   2015年07月01日 06  *   description: 07  *          当sel为0时,将输入端口in_a上的数据通过out端口输出, 08  *      否则将输入端口in_b上的数据通过out端口输出 09  =======================================*/ 10 11  module mux2 ( in_a , in_b , sel , out ); 12 13     input    in_a ;   //输入端口in_a 14     input in_b ; //输入端口in_b 15     16     input    sel ;    //通路选择线 17     18     output out ; //输出端口out 19 20  /*--------------------------------------------   21     当sel为0时,选择将in_a端口的数据通过out端口输出, 22     当sel为1时,选择将in_b端口的数据通过out端口输出. 23  --------------------------------------------*/    24     assign out = ( sel == 1'b0 )? in_a : in_b ; 25 26  endmodule            (注:最左侧一列行号是作者为了讲解方便加上的,不属于代码内容,请知悉) 以上代码描述的电路如下图所示: 图:二选一多路器电路模型   第1到9行:          该部分为文件头,记录了本设计文件中的一些基本信息如设计者、版本号、修改历史以及代码实现的功能描述。在编译时,该部分属于注释内容,将不被综合成任何内容。   第11行: module mux2 ( in_a , in_b , sel , out );          该行第一个字符串“module”为Verilog中的一个保留字(关键词),该保留字的出现表明了一个模块内容的开始。相对应的,第26行的“endmodule”也是一个保留字,该保留字的出现表明了一个模块内容的结束。在Verilog语法中,module和encmodule总是成对出现,module和encmodule之间的内容则是用户代码。          module之后是一个空格(或制表符“tab”),空格或制表符数量不一定限定为1个,也可以是多个,对结果没有任何影响。紧跟空格之后的mux2则是模块名称,该名称由用户自己定义,但一般要求与设计内容有一定关系,通过该名称能够体现出模块的功能或作用,例如这里使用mux2表明这是一个二选一多路器,让人一目了然。换个例子,如果要做PWM波生成模块,则可以直接以PWM作为模块名( module PWM (……); )这样简洁直观,一看就能理解模块的功能。          mux2之后使用圆括号括起来的部分则是端口列表,该列表中列出了该模块所有需要外部输入或者需要输出到外部的信号,信号间以英文中的逗号“,”隔开。端口名的命名也尽量能够代表该端口信号的实际功能或意义,例如“in_a”,很清晰的就能告诉读者这是数据输入通道的a端口。   第13行到18行: 13       input    in_a ;   //输入端口in_a 14       input in_b ; //输入端口in_b 15       16       input    sel ;    //通路选择线 17       18       output out ; //输出端口out          这里蓝色保留字“ input ”和“ output ”是端口类型,input表示该端口是本模块的输入型端口,output表示该端口是本模块的输出型端口。另外,在实际项目中,还有一种很常见的端口类型是双向端口,Verilog中用关键字“ inout ”来表示。在本例中,由于没有使用到双向端口,因此不做介绍,该部分内容将在后面以一个单独的章节来进行讲解。          第13行紧跟着“ input ”之后用方括号括起来的部分 表示端口的宽度,这里表示输入端口in_a的位宽为2。需要注意的是,定义位宽时虽然写成 和 表示的位宽是一致的,但是Verilog中习惯使用 的形式,请大家在自己写代码时也统一遵照这一要求。当位宽为1时,位宽定义部分可以省略,例如第16行的 是可以省略的,即写成 “ input sel ; ” , 与 “ input sel ; ” 效果是一样的。   第20行到23行:          该部分为注释内容,注释主要是为了方便阅读和理解代码,在综合电路时会被忽略。Verilog语法中,注释的格式与C语言中一致,也支持单行注释和块注释。单行注释以“//”开始,有效作用域只在以此符号开始的本行紧随其后的内容,换行后就不起作用了。例如第13行的“//输入端口in_a”就是注释内容,换行后第14行就不再是被注释掉的内容了。第20行到第23行为块注释,块注释以“/*    */”组织,两个“*”之间的内容即为注释内容,此种注释方式支持换行,注释内容以“/*”开始,换行后注释属性依然有效,直到出现“*/”,则注释部分结束。不过需要注意的是,块注释不支持嵌套,当强行使用这种方式时会导致编译错误。例如以下这种格式就是错误的: /*  一级块注释内容 /*  二级块注释内容 */ …… */ 第24行: 24     assign out = ( sel == 1'b0 )? in_a : in_b ;          这一行为赋值语句,这里是Verilog中最基础的一种赋值方式——连续赋值语句。这段代码的意义就是“判断括号中的条件是否成立,若成立,则将in_a的值赋给out,否则,则将in_b的值赋给out”,即通过这种方式实现了根据条件选择通道的功能。其中,“?”之后“:”之前的信号为括号中条件满足时需要赋给out的的源信号,“:”之后的信号则是括号中条件不满足时需要赋给out的源信号。此种赋值方式还支持多重选择,例如下面的代码: assign out = ( sel1 == 1'b0 )? in_a : ( sel2 == 1 )? in_b : in_c ;            这里有三个数据输入端口“in_a , in_b , in_c”,两个选择输入端口“sel1 , sel2”。该句话的意思就是,当第一个括号中的条件满足时,则将in_a的数据赋值给out,否则再判断第二个括号中的条件是否满足,满足则将in_b的值赋给out,不满足则将in_c的值赋给out。括号中的内容可以直接简写为(!sel1)或者(!sel2),这一点与C语言一样。            通过这一章,我们学习了采用Verilog HDL设计简单的模块的基本代码结构,包括声明模块的”module   endmodule”、端口的定义、端口类型以及位宽的声明、注释内容的格式以及连续赋值语句的写法,而且通过此例子完成了一个简单的二选一多路器的设计。希望对于Verilog HDL语法零基础的读者在看罢此文后能够关闭此文档,在Quartus II或Modelsim、ISE等工具中实际动手敲一遍。可结合前面介绍Altera 公司FPGA器件开发流程一章中的步骤,实际建立工程并输入代码,然后分析和综合,通过综合工具,查看自己编写的代码是否有错误,如有错误,则根据软件提示加以改正。          下一章,将以此模块的测试文件(testbench)为例,介绍Testbench的基本语法。     如有更多问题,欢迎留言,也可加入技术支持群一起交流学习。群号:472607506   小梅哥 芯航线电子工作室    
  • 热度 15
    2016-1-27 17:37
    3418 次阅读|
    1 个评论
        很多做过单片机的朋友都知道,我们在对MCU烧写完程序固件后,那么该程序固件就存储在了该MCU内部。即使MCU断电了再重新上电,程序也能继续运行。这是因为对MCU烧写固件的实质就是将程序固件写入到MCU的片上程序存储器ROM中,而现代的大部分MCU这个ROM都是FLASH存储器。FLASH存储器能够掉电保持数据,所以可以实现掉电程序不丢失。Altera或Xilinx的FPGA芯片,使用的是基于SRAM结构的查找表,而SRAM的一大特性就是掉电数据会丢失,当我们使用JTAG将SRAM配置文件(.sof)配置到FPGA芯片中后,这些数据是直接存储在SRAM结构的查找表中的,因此,一旦芯片掉电,则SRAM中的数据将丢失,再次上电后,SRAM中将不再有有效的数据。这也就是我们常见的,使用JTAG下载SOF固件到FPGA中后,板子重新上电,则之前下载的固件又不在了的原因。当我们的系统做稳定后并量产时,当然希望能够永久保持电路固件,即让FPGA上电后其查找表中就被写入有效的数据。但是我们又不能总是每次系统上电了就用JTAG去下载一次程序固件。因此,FPGA支持另外一种配置方式:主动串行配置。      所谓主动串行配置,就是在FPGA芯片外部放置一片能够掉电数据不丢失的存储器,例如最常见的EPCS、QFLASH、并口FLASH,来存储设计好的电路固件。而FPGA芯片内部,则设计了一个专用的硬件电路,在芯片刚上电的时刻就主动去读取存储在该存储器中的固件,并配置到FPGA芯片的每一个SRAM中去。通过这样一种方式,即可在不改变FPGA芯片SRAM工艺的查找表结构前提下,让每次芯片上电后,都能获得有效的配置数据。外部存储电路配置信息的芯片,我们称之为配置芯片。前些年,Altera的FPGA芯片指明只能使用其公司自己发售的EPCS芯片作为外部配置器件,该EPCS芯片实质就是一个SPI接口的串行FLASH芯片,不过是经过了Altera的严格测试,性能优异。而近些年,随着芯片生产工艺的不断发展,很多其他厂家生产的SPI接口的FLASH芯片也能够达到EPCS的技术标准,因此Altera就放开了该限制,并指出可以使用其他芯片厂家生产的SPI接口的FLASH芯片代替EPCS。我们芯航线starter board上就使用了一片ST公司生产的16Mbit的串行FLASH芯片M25P16来作为配置芯片。该芯片性能优异,完全能够达到EPCS的性能标准,而成本则只有相同容量的EPCS芯片的一半不到。      当我们需要将设计好的配置固件固化到该器件中时,有两种方式,第一种方式,也就是传统的方式是使用专用AS接口(与JTAG 10针接口独立)来直接烧写该配置芯片,该种方式需要在电路板上设置一个独立的AS接口,占用PCB板面积。第二种方式,也是现在流行的方式则是通过JTAG接口,经由FPGA芯片间接烧写该配置芯片。我们的开发板没有做独立的AS接口,因此只支持第二种烧写方式。以下对该种烧写方式以一个实际例子来进行讲解说明。   打开希望固化的FPGA设计工程,这里我直接打开按键消抖这节课的工程。   在quartus ii软件中点击File—Convert Programming Files,如下图所示:(温馨提示:点击图片可查看高清大图)   在弹出的窗口中,Programming file type 选择JTAG Indirect Configuration File(.jic),Mode选择Active Serial,Configuration device选择EPCS16,File name默认是output_file.jic,这里,我们养成良好的习惯,将其改成工程名字:key_filter.jic。 (温馨提示:点击图片可查看高清大图)   四、在input files to convert一栏中,点击Flash Loader一项,在右侧点击Add Device选项,如下图所示: (温馨提示:点击图片可查看高清大图)   五、点击Add Device选项后在弹出的选项卡中,选择EP4CE10,然后点击OK,如下图所示: (温馨提示:点击图片可查看高清大图)   六、点击OK后会回到先前的配置页面,此时再次鼠标点击SOF Data,再点击右侧的Add File,如下图所示: (温馨提示:点击图片可查看高清大图)   七、在弹出的窗口中,在output files文件夹下找到“key_filter.sof”文件,点击open,即可添加进来,如下图所示: (温馨提示:点击图片可查看高清大图)   八、点击open后,回到配置页面,点击Generate按钮,如下图所示: (温馨提示:点击图片可查看高清大图)   九、点击Generate按钮后,则软件开始转换文件,转换成功后弹出成功提示窗口,如下图所示: (温馨提示:点击图片可查看高清大图)   十、点击OK即可,然后close窗口。 十一、打开Quartus II中的下载工具programmer,将原有的sof文件移出,重新添加key_filter.jic文件进来,勾选programming/configuration,如下图所示: (温馨提示:点击图片可查看高清大图)   十二、设置完成后,点击Start(前提是下载器与开发板已经正确连接),则软件开始烧录固件,整个烧录时间大约花费20秒钟左右。烧录完成后,此时固件已经保存在了配置芯片中,但是此刻FPGA还不能运行该固件,因为当前的固件是存在配置芯片中的,并没有被配置到FPGA中,因此需要让FPGA执行一次从配置芯片中主动配置固件的过程,方法很简单,给开发板断电后重新上电即可。此时,我们按下按键0或者按键1,就可以看到LED的状态发生变化了。断电再上电,固件依旧保持,整个设计固化工作完成。   如有更多问题,欢迎加入芯航线 FPGA 技术支持群:472607506 各位朋友,接下来的一年到半年里,小梅哥将持续发布各种原创资料,各位如果对我们的内容感兴趣的,可以选择关注我的博客,这样每次我有新的动态,大家就能收到邮件了。 小梅哥 芯航线电子工作室