1.11k likes | 1.57k Views
可 综合 Verilog 语言及 FPGA 验证. 2013-12-11. 目录. Verilog HDL 简介. Verilog HDL 是一种应用广泛的硬件描述性语言,是硬件设计人员和电子设计自动化( EDA )工具之间的界面。
E N D
可综合Verilog语言及FPGA验证 2013-12-11
Verilog HDL简介 • Verilog HDL是一种应用广泛的硬件描述性语言,是硬件设计人员和电子设计自动化(EDA)工具之间的界面。 • 其主要目的是用来编写设计文件,建立电子系统行为级的仿真模型。即利用计算机的巨大能力对用Verilog HDL建模的复杂数字逻辑进行仿真,然后再自动综合以生成符合要求且在电路结构上可以实现的数字逻辑网表(Netlist), 根据网表和某种工艺的器件自动生成具体电路,然后生成该工艺条件下这种具体电路的延时模型。仿真验证无误后用于制造ASIC芯片或写入CPLD和FPGA器件中。 • 常用的Verilog HDL语言开发软件有Altera公司的Quartus II和Xilinx公司的ISE。
为什么要用Verilog HDL? • 电子设计规模越来越大(普通设计已达几百万门的数量级) ,复杂度越来越高。有必要用高级语言来表达其功能,隐藏其具体的细节实现。 • 提高逻辑设计的效率,降低设计成本,更重要的是缩短设计周期。 • 可读性强,易修改。 将HDL描述在目标器件上实现是由EDA工具软件的综合器完成,并不是所有Verilog HDL语句均可被综合。 注意:
可综合设计 Verilog HDL行为级或数据流电路建模 可综合指的是所设计的指令和代码能转化为具体的电路网表结构。在用CPLD/FPGA器件实现的数字设计中,综合就是将Verilog HDL语言描述的行为级或数据流电路模型转化为RTL级功能块或门级电路网表的过程。而不可综合设计则主要用于设计的验证、仿真。综合过程的示意图如图所示。 RTL级综合 RTL级功能块 逻辑优化 门级综合 优化后的门级网表 RTL级综合后得到由功能模块构成的电路结构,逻辑优化器以用户设定的面积和定时约束为目标优化电路网表,针对目标工艺产生优化后的电路门级网表结构。
可综合/不可综合 结构 1、综合工具支持的结构: always,assign,begin,end,case,wire,reg,integer,default,for,function,and,nand,or,nor,xor,xnor,if,inout,input,module,negedge,posedge,output,parameter…… 2、综合工具不支持的结构: time,defparam,$finish,fork,join,initial,delays,wait……
其他不可综合的结构 1、敏感列表里同时带有时钟的posedge和negedge 如:always @(posedgeclk or negedgeclk) 不可综合 2、同一个reg变量被多个always块驱动 3、以#开头的延时不可综合成硬件电路延时,综合工具会忽略所有延时代码,但不会报错。 如:a=#10 b; 这里的#10是用于仿真时的延时,在综合的 时候综合工具会忽略它,等同于a=b; 4、与不定态X、高阻态Z的比较 如 if ((b == 1’bz) || (b == 1’bx)) begin 不可综合
设计准则 要保证Verilog HDL语句的可综合性,在建模时应注意以下要点: (1)不使用initial。 (2)不使用#延时。 (3)不使用循环次数不确定的循环语句,如forever、while等。 (4)不使用用户自定义原语(UDP元件)。 (5)尽量使用同步方式设计电路。 (6)除非是关键路径的设计,一般不采用调用门级元件来描述设计的方法,建议采用行为语句来完成设计。
(7)用always过程块描述组合逻辑,应在敏感信号列表中列出所有的输入信号。 (8)所有的内部寄存器都应该能够被复位,在使用FPGA实现设计时,应尽量使用器件的全局复位端作为系统总的复位。 (9)对时序逻辑描述和建模,应尽量使用非阻塞赋值(<=)方式。对组合逻辑描述和建模,既可以用阻塞赋值(=),也可以用非阻塞赋值。但在同一个过程块中,最好不要同时用阻塞赋值和非阻塞赋值。 (10)不能在一个以上的always过程块中对同一个变量赋值。而对同一个赋值对象不能既使用阻塞式赋值,又使用非阻塞式赋值。 设计准则(续)
设计准则(续) (11)如果不打算把变量推导成锁存器,那么必须在if语句或case语句的所有条件分支中都对变量明确地赋值。 (12)避免混合使用上升沿和下降沿触发的触发器。 (13)同一个变量的赋值不能受多个时钟控制,也不能受两种不同的时钟条件(或者不同的时钟沿)控制。 (14)避免在case语句的分支项中使用不定态x值或高阻态z值。
敏感列表 • 在下面的例子,a, b, sl是块的输入 • sl用作条件 • a、b用在过程赋值语句的右边 敏感表不完全: module sens (a, q, b, sl); input a, b, sl; output q; reg q; always @( sl) begin if (! sl) q = a; else q = b; end endmodule 完全的敏感列表 module sensc (q, a, b, sl); input a, b, sl; output q; reg q; always @( sl or a or b) begin if (! sl) q = a; else q = b; end endmodule 将块的所有输入都列入敏感表是很好的描述习惯。不同的综合工具对不完全敏感表的处理有所不同。有的将不完全敏感表当作非法。其他的则产生一个警告并假设敏感表是完全的。在这种情况下,综合输出和RTL描述的仿真结果可能不一致。
条件语句 自然完全的条件语句 module compif (a, b, c, d, e); input a, b, c, d; output e; reg e; always @( a or b or c or d) if (a & b) e = d; else if (a & ~b) e = ~c; else if (~ a & b) e = 1'b0; else if (~ a & ~b) e = 1'b1; endmodule module comcase (a, b, c, d, e); input a, b, c, d; output e; reg e; always @( a or b or c or d) case ({ a, b}) 2'b11: e = d; 2'b10: e = ~c; 2'b01: e = 1'b0; 2'b00: e = 1'b1; endcase endmodule 例中定义了所有可能的选项,综合结果是纯组合逻辑,没有不期望的锁存器产生。
不完全条件语句 若 a 变为 0, e为何值 module inccase (a, b, c, d, e); input a, b, c, d; output e; reg e; always @( a or b or c or d) case ({ a, b}) 2'b11: e = d; 2'b10: e = ~c; endcase endmodule module incpif (a, b, c, d, e); input a, b, c, d; output e; reg e; always @( a or b or c or d) if (a & b) e = d; else if (a & ~b) e = ~c; endmodule 在上面的例子中,当a变为零时,不对e赋新值。因此e保存其值直到a变为1。这是锁存器的特性。
default完全条件语句 module comcase (a, b, c, d, e); input a, b, c, d; output e; reg e; always @( a or b or c or d) case ({ a, b}) 2'b11: e = d; 2'b10: e = ~c; default: e = 'bx; endcase endmodule module compif (a, b, c, d, e); input a, b, c, d; output e; reg e; always @( a or b or c or d) if (a & b) e = d; else if (a & ~b) e = ~c; else e = 'bx; endmodule 综合工具将 ’bx作为无关值,因此if语句类似于“ full case”,可以进行更好的优化。 例中没有定义所有选项,但对没有定义的项给出了缺省行为。同样,其综合结果为纯组合逻辑——没有不期望的锁存器产生。
指示完全条件语句 module dircase (a, b, c, d); input b, c; input [1: 0] a; output d; reg d; always @( a or b or c) case (a) //ambit synthesis case = full 2'b00: d = b; 2'b01: d = c; endcase endmodule 和前例一样,没有定义所有case项,但综合指示通知优化器缺少的case项不会发生。结果也为纯组合逻辑,没有不期望锁存器产生。注意如果缺少的case项发生,而其结果未定义,综合结果和RTL的描述的行为可能不同。
阻塞或非阻塞 • 使用的赋值类型依赖于所描述的逻辑类型: • 在时序块RTL代码中使用非阻塞赋值 • 非阻塞赋值保存值直到时间片段的结束,从而避免仿真时的竞争情况或结果的不确定性 • 在组合的RTL代码中使用阻塞赋值 • 阻塞赋值立即执行
使用阻塞赋值,此描述综合出一个D flip-flop: 使用非阻塞赋值,此描述将综合出两个D Flip-flop。 阻塞、非阻塞对比 非阻塞赋值语句并行执行,因此临时变量不可避免地在一个周期中被赋值,在下一个周期中被采样。 module bloc (clk, a, b); input clk, a; output b; reg y; reg b; always @( posedgeclk) begin y =a; b =y; end endmodule module nonbloc (clk, a, b); input clk, a; output b; reg y; reg b; always @( posedgeclk) begin y <= a; b <= y; end endmodule
资源共享 资源共享是指多节代码共享一组逻辑。例如: always @( a or b or c or d) if (a) out = b + c; else out = b + d; 资源共享与所用综合工具有关。但通常,要共享资源,表达式必须在同一个always块中的同一个条件语句中。 资源共享 没有资源共享
资源共享 只有在同一个条件语句(if和case)不同的分支中的算术操作才会共享。 条件操作符 ?: 中的算术操作不共享。 if (cond) z = a + b; else z = c + d; Z = (cond) ? (a + b) : (c + d);
括号的作用 利用括号分割逻辑。 a + + b z = a + b + c + d; + c z d a + b Z = (a + b) + (c + d); + z c + d
HDL代码编写 系统定义 功能验证 流片 布局布线 综合 数字IC设计流程 前端设计 后端设计 综合
为什么需要验证? • 1.语法检查 module clk_devider(clk,en,data_in,clk_d7,clk_d8,clk_out,); input clk,en;data_in; output clk_d7,clk_d8,clk_out; wire [2:0] count; wire clk_d8,clk_out; reg clk_d7; assign clk_out= data_in ? clk_d8 : clk_d7; assign clk_d7<=~count[2]; assign clk_d8=~count[2]; always @(posedgeclk) if(!en) count<=0; else if (en) if (!data_in) count=count+1; else begin if (count==6) count<=0; else count<=count+1; endmodule 端口申明结束 变量类型错误 reg类型变量不能使用连续赋值 同一过程块赋值类型要一致,reg类型最好使用“<=“非阻塞赋值 begin—end 要匹配
为什么需要验证? 1 2.逻辑检查 assign test=~a|(b&c); 如果a=0,b=1,c=0,test=
验证的必要性 • 经验表明,验证已经占到整个产品开发周期的70%以上,它已经成为复杂SOC开发中的重要壁垒。 • Verification is hard没有一个简单的工具可以解决你所有的验证问题。
如何验证? 1.软件验证---仿真 Modelsim,Questasim(后者对system verilog, system C支持较好) 2.硬件验证---FPGA或者CPLD 编译下载软件ISE,QuartusII
常用工具工作站启动方法 • Modelsim6.5 • source /opt/demo/modelsim.env • vsim • ISE12.4 • source /opt/demo/ise.env • ise &
前端设计流程 1.代码编写 2.编写测试矢量(testbench) 3.运行仿真软件仿真 或者 1.代码编写 2.运行FPGA软件编译下载进行验证
测试矢量--Testbench test=~a|(b&c); Testbench更像一个激励的产生器,对所设计的输入管脚产生相应的输入值(0,或者1),通过观察输出值来评估设计的正确性。 Testbench的工作就是把这些不同的输入值加载到所设计的电路中。
Testbench与设计模块的关系 • 仿真软件--铁锅 • 设计模块--准备好的生菜 • Testbench--调料 • 观察输出--尝味道 • 仿真---修改---再仿真
编写testbench 不需要端口列表和端口类型定义 `timescale 1ns/1ns //定义仿真时间步长 module top_test(); rega_test, b_test, c_test; test test (.a(a_test), .b(b_test), .c(c_test), .test(test_out)); //调用设计模块 initial begin begin a_test=0; b_test=0;c_test=0;end #(100) begin a_test=0; b_test=0;c_test=1;end #(100) begin a_test=0; b_test=1;c_test=0;end #(100) begin a_test=0; b_test=1;c_test=1;end #(100) begin a_test=1; b_test=0;c_test=0;end #(100) begin a_test=1; b_test=0;c_test=1;end #(100) begin a_test=1; b_test=1;c_test=0;end #(100) begin a_test=1; b_test=1;c_test=1;end end endmodule
Modelsim 仿真步骤 • 启动Modelsim • source /opt/demo/modelsim.env(注意空格) • vsim& • 建立新的编译库 FileNewlibrary • 建立新的工程 FileNewproject • 添加.v文件到新的project,包括测试激励testbench • 编译 • 仿真
建立新的工程2/2 存放verilog代码的目录
编译 编译前状态 编译后状态
编译状态刷新 • 状态刷新很慢的情况下 在主窗口下鼠标右键 弹出来的选项中选择 Update
编译错误查看 绿色对号表示编译无误。红色叉号表示有错误。 黄色感叹号表示有警告。错误查看:双击红色叉号。 数字表示错误代码行数,比如这里的16,表示代码第16行有错误,双击这行可以直接定位到错误处。
错误修改 • 错误修改后需要保存,保存后代码状态又变成问号,需要重新编译。
仿真1/2 • 启动仿真方法一 选择的这个模块一定要是testbench的模块
仿真2/2 • 启动 仿真 方法二 选中testbench模块后鼠标右键
波形查看 变量 过程块
波形查看 • 选中要查看 的模块, 鼠标右键
波形查看 跑固定时间 跑完全部
FPGA • FPGA (FieldProgrammable Gate Array),即现场可编程门阵列,它是作为专用集成电路(ASIC)领域中的一种半定制电路而出现的,既解决了定制电路的不足,又克服了原有可编程器件门电路数有限的缺点。
FPGA的结构 • FPGA由可编程逻辑块(CLB)、输入/输出模块(IOB)及可编程互连资源(PIR)等三种可编程电路和一个SRAM结构的配置存储单元组成。 • CLB是实现逻辑功能的基本单元,它们通常规则地排列成一个阵列,散布于整个芯片中; • IOB主要完成芯片上的逻辑与外部引脚的接口,它通常排列在芯片的四周; • IR包括各种长度的连线线段和一些可编程连接开关,它们将各个CLB之间或CLB与IOB之间以及IOB之间连接起来,构成特定功能的电路。