FPGA(Field Programmable Gate Array)现场 可编程 逻辑门 阵列;
是主要使用逻辑门(LE)和查找表(LUT)来生成逻辑电路的器件,还包含可编程逻辑,互连线,寄存器等资源;
veilog HDL(hardware description language)硬件描述语言是通过描述硬件来产生与之相对应的硬件电路的语言;是FPGA的主要语言之一;
硬件描述语言和软件编程语言有什么区别呢?
软件编程语言编译之后是工作在堆栈和内存上,是对堆栈和内存的数据处理,逻辑上数据的处理是单线程的;
硬件描述语言编译之后得到的是硬件电路,是具体的物理电路连接,器件间信号的传递可以实现并行执行;
1 verilog模块
verilog代码是以模块为最小仿真单位存在;可以将特定的逻辑功能封装成模块,在顶层模块中对子模块实例化来调用子模块,组成完整的项目;
verilog模块主要由三部分组成:端口声明A,内部信号量声明B,功能定义C;
每个.v文件就是一个verilog模块,.v文件的名字要与module中定义的名字相同;以下为两个module举例:
/***********顶层模块:trist1.v***********************/ /***********以下使用三态驱动器模块举例*****************/ /***********以下的功能定义部分C1,C2,C3是并行执行的******/ module trist1 ( output out, //A:端口声明; input in, //A: input enable //A:默认端口声明变量是wire型; ); wire [2:0] con1; //B:内部信号声明; wire [7:0] con2; //B wire[7:0] con; //B reg [7:0] and_con; //B mytri tri_inst(out,in,enable); //C1:调用子模块mytri, assign con={con1[2:0],con2[4:0]} //C2:组合逻辑,always块也属于组合逻辑; and and_inst(and_sum,con1,con2); //C3:使用实例元件and, endmodule /******** 子模块 mytri.v***************************/ module mytri //子模块被顶层trist1.v调用时,会生成实例元件tri_inst;实例元件的名字必须具有唯一性; ( output out, input in, input enable ); assign out= enable? in:1'bz; //assign声明组合逻辑;组合逻辑中被赋值的变量必须是net型;wire型属于net型; endmodule
2数据类型
verilog共有19种数据类型,如large、medium、scalared、time、small、tri、trio、tri1、triand、trior、trireg、vectored、wand、wor等
其中reg、wire、interger、parameter为基本的四种数据类型;以下简单说明一下;
2.1 parameter
parameter用来定义常量;先了解一下常量在verilog中的表达方式,然后再了解一下parameter如何使用;
2.1.1 常量的表示方式
/*常量由位宽,进制,具体数值组成,中间不能加空格; **位宽表示当前常量用二进制表示的具体位数, **进制表示后面的具体数值使用的进制, **具体数值表示实际值;在数字电路中,x代表不定值,z代表高阻值; **常量的位宽和进制缺省时,默认是32位位宽,10进制;*/ parameter NUM1 = 8'b11001000; // 8表示二进制位数为8;'b表示为当前常量使用2进制表示; 1100 1000为具体数值; parameter NUM2 = 8'hc8; // 8表示为二进制位数为8,'h表示当前常量使用16进制表示,c8为具体数值; parameter NUM3 = 4'b10x0; // 4表示二进制位数为4,'b表示当前常量用2进制表示;10x0为具体数值,其中bit1为不定值; parameter NUM4 = 8'h4z; // 8表示二进制位数为8,'h表示当前常量用16进制表示;4z为具体数值,其中bit[3:0]为高阻值; parameter NUM5 = 8'h4?; // 8表示二进制位数为8,'h表示当前常量用16进制表示;4?为具体数值,其中bit[3:0]为高阻值;
2.1.2 parameter 声明
可以在模块内声明,也可以在模块外声明;这里应该要补充一下模块外声明原型和调用方式的,等遇见了再补充把;
//在模块内部定义了一个常量OUT,作用域为当前模块;定义之后可以修改,也可以在被其他模块调用时修改值; module param1 ( input clk, output reg[3:0] sum ); parameter OUT= 4'b1100; //模块内声明常数参数,搭配状态机较为常用; always@(posedge clk) begin sum <= OUT; end endmodule //在模块外部定义模块使用的常量ADD2; module param2 #(parameter ADD2 = 2'd1) ( input clk, input [2:0]din, output reg [3:0]sum ); always@(posedge clk) begin sum <= din+ADD2; end endmodule
2.2 wire
wire属于网络数据类型,相当于电路的物理连线;在逻辑综合中,会被映射为真实的物理连线;
可以用作任何方程式的数据,组合逻辑或实例元件的输出;
在assign语句中被赋值的变量必须是wire类型;实例元件被调用后的输出必须赋值给wire型;默认缺省输入输出为wire型;
wire clk; //定义了一个wire类型的变量clk; assign clk = sys_clk; //作为组合逻辑的输出; wire [7:0] data; //定义了一个wire类型的变量data; ins instance1( .inst_output (data), //作为实例元件instance1的输出; ... );
2.3 reg
reg是数据存储单元的抽象,相当于芯片的寄存器;在逻辑综合中,会被映射为真实的物理寄存器;
在always块内被赋值的信号都必须被定义成reg类型;reg数据缺省值为x不定值;
reg[7:0] dataByte;always@(posedge clk) if(clk) dataByte <=revByte;//在always块内被赋值的信号都要为reg型的;
2.4 memory
通过组合reg型数据来表示存储器memory,相当于芯片的ram、rom存储器;存储器深度只能为一维数组,数组下标同c一样需要为常量;
reg[7:0] memFirst[255:0]; //定义了一个寄存器组memFirst,有256个寄存器,其中每个寄存器为8位的reg型; memFirst[3]= 0; //寄存器组的赋值需要给每个单元单独赋值,使用时在逻辑块外单独赋值报错了,放到逻辑块内赋值就可以了;
//并且寄存器组的寄存器不能单独使用某些位的数据,如memFirst[7:3];一般定义的寄存器就可以直接取某些位的数值,如data[7:2];
//大概在定义的时候,在逻辑块外一起赋值也是可以的;如果单独赋初值就是逻辑块了,可是又不符合逻辑块规则;寄存器也是把;
3 运算符
等式运算符:"=="和"!="为逻辑等式运算符,逻辑值可为不定值;而"==="和"!=="进行比较时对某些位的不定值x和高阻值z也进行比较,逻辑值非0即真;
阻塞赋值"=":阻塞赋值的意思是在当前块中,第一条语句的执行会阻塞第二条语句的执行;在当前块中语句顺序赋值,由于电路时序约束可能会出现数据交换意外;
非阻塞赋值"<=":非阻塞赋值的意思是begin-end块中,第一条语句的执行与否不会阻塞第二条语句的执行;块中语句一起执行完之后再一起赋值,较为常用;
3.1 组合逻辑与阻塞赋值 " = "
组合逻辑电路表示输出只与输入有关,与电路当前状态无关;根据电路特性,需要使用阻塞赋值;
对于阻塞赋值的信号,如果信号在跳变沿变化为状态B,则跳变沿的取值为新的状态B;其他信号在跳变沿对其取样的时候,取样信号为状态B;
3.2 时序逻辑与非阻塞赋值" <= "
时序逻辑电路表示输出不仅与输入有关,还与电路当前状态有关;根据电路特性,需要使用非阻塞赋值;
对于非阻塞赋值的信号,如果信号在跳变沿变化为状态B,跳变沿的取值并没有立即变化,而是为之前的状态A;其他信号在跳变沿对其取样的时候,取样信号为状态A;
4 testbench
quartus软件具有多种仿真功能;可以查看RTL(register transfer level)视图,可以编写VWF波形文件,可以硬件在线调试,也可以通过modelsim等软件仿真信号;
硬件在线调试:菜单栏Tools >> SignalTap.. >>然后配置一下时钟和查看参数 >> 重新编译工程加载进配置 >> 重新打开SignalTap,加载运行即可查看;
wire clk_200m/*synthesis keep*/; //表示仿真时保留该wire类型; wire clk_200m; //仿真过程中会自动优化wire类型的变量,以至于不能观察wire类型的信号;
软件仿真调试:编写好tb文件 >> 菜单栏Tools >> setting >> simulation >>配置仿真的testbench文件 >>重新编译后即可查看RTL仿真信号;
以下是testbencn文件的基础写法举例;
/**ipcore_tb.v 模块*****************************************/ `timescale 1ns/1ps //`时间单位/时间精度:时间单位表示#的延时单位,时间精度表示执行时间的误差范围; module ipcore_tb(); //当前模块为ipcore_tb,模块名等于文件名; reg sys_clk; reg sys_rst_n; initial begin //initial为testbench的语法功能,RTL模块中不可以使用; sys_rst_n = 1'b0; sys_clk = 1'b0; #40 sys_rst_n = 1'b1; //#40表示延时40ns; end
always #10 sys_clk <= ~sys_clk; //50MHz,20ns一个周期; inst instance_1 ( .sys_clk(sys_clk), .sys_rst_n(sys_rst_n) ); endmodule
5 简单模块举例
/**mux.v 二选一多路选择器 ************************************** *** always@()中的*为通配符,表示当always块内的输入信号发生变化,即执行当前块;*/ module mux ( input in1, input in2, input sel, output reg[0:0] out ); always@(*) begin case(sel) 1'b0: out <= in1; 1'b1: out <= in2; default: out <= out; endcase end endmodule /**adder.v 加法器 **********************************************/ module adder ( input add1, input add2, output sum, output carry ); assign {carry,sum} = add1 + add2; endmodule /**decode.v 译码器:将输入二进制数,转换成信号输出的逻辑器件;************ **3-8译码器:将输入的3'bxxx转换成二进制后输出8线;*********************** **8421BCD译码器:使用4个二进制来表示10进制数***************************/ module decode ( input reg[2:0] binary_in, output reg[7:0] out ); always@(*) begin case(binary_in) 3'b000: out <= 8'b0000_0001; 3'b001: out <= 8'b0000_0010; 3'b010: out <= 8'b0000_0100; 3'b011: out <= 8'b0000_1000; 3'b100: out <= 8'b0001_0000; 3'b101: out <= 8'b0010_0000; 3'b110: out <= 8'b0100_0000; 3'b111: out <= 8'b1000_0000; default: out <= out; endcase end endmodule //if语句的判断是顺序执行的,前面的判断优先级要稍微高于后面的语句; //case语句的判断先判断条件,执行语句的优先级都是相同的;