赞
踩
写在前面:自学笔记,推荐小伙伴一起学习,欢迎提出宝贵意见。参考内容有Verilog菜鸟教程,徐文波版《Xilinx FPGA开发实用教程》;
目录
流控制(if、case、for、while、forever、repeat)
Verilog程序是由一个或者多个模块组成的,每个模块可以实现特定的功能,在设计时应该优先确定每个模块的功能,之后在进行代码编写。每个模块要进行端口定义,并说明输入、输出口,然后对模块的功能进行描述,即代码的编写。Verilog程序包括4个主要部分:端口定义、I/O说明、内部信号说明、功能定义。下面的代码即为一个模块:
- //二选一多路选择器为例
- module module_name(out, a, b, sl);
- input a, b, sl; //定义输入信号
- output out; //定义输出信号
-
- reg out; //定义存储器
-
- always @(sl or a or b) //语法体
- if(!sl)
- out = a;
- else
- out = b;
- endmodule
- /*module_name是模块名字,在定义时替换成合适的名字,字母和下划线,举例:uart_xx;*/
暂时这么多,之后会补充
写在前面,关于Verilog的代码特点
- 区分大小写;
- 书写自由,一行可以写多个语句,一个语句也可以分行;
- 空白符(换行、制表、空格)都没有实际的意义,在编译阶段可忽略。
在模块的端口声明了模块的输入输出口,其格式如下:
module 模块名(口1, 口2, 口3, ....);
这里只是声明了输入输出口,接下来需要具体指明每个端口是输入还是输出。
输入口—— 模块从外界读取数据的接口,在模块内不可写。
input [ 信号位宽 - 1 : 0 ] 端口 i 名;
input [7:0] a; //说明了一个8位的输入端口 "a"
输出口——模块往外界送出数据的接口,在模块内不可读。
output [ 信号位宽 - 1 : 0 ] 端口 j 名;
output [7:0] out; //说明了一个8位的输入端口 "out"
输入/输出口——可读取数据也可以送出数据,数据可双向流动。
inout [ 信号位宽 - 1 : 0 ] 端口1名;
inout [7:0] in_out; //说明了一个8位的输入/输出端口 "in_out"
注意:如果不定义位宽则默认位1
线网(wire):wire 类型表示硬件单元之间的物理连线,由其连接的器件输出端连续驱动。如果没有驱动元件连接到 wire 型变量,缺省值一般为 "Z"。(模块内的连接线)
wire [7:0] addr ; //声明8bit位宽的线型变量addr,
寄存器(reg):寄存器(reg)用来表示存储单元,它会保持数据原有的值,直到被改写。(模块内的寄存器)
reg [7:0] addr ; //声明8bit位宽的线型变量addr,
reg [15:0] ROMA [7:0] ; //声明了一个存储位宽为16位,存储深度为8的一个存储器。该存储器的地址范围是0到8。(memory型)
常量(parameter):定义常数(常量)。parameter 参数名1 = 数据;
parameter [3:0] S0 = 4'h0;
Verilog HDL 有下列四种基本的值来表示硬件电路中的电平逻辑:
x 意味着信号数值的不确定(这里不区分大小写),即在实际电路里,信号可能为 1,也可能为 0。z 意味着信号处于高阻状态,常见于信号(input, reg)没有驱动时的逻辑结果。例如一个 pad 的 input 呈现高阻状态时,其逻辑值和上下拉的状态有关系。上拉则逻辑值为 1,下拉则为 0 。
整技术表示格式:[长度] ' 基数 数值;
- 4'b1011 // 4bit 数值
- 5'o8 // 5位八进制数
- 9'd6 // 9位十进制数
- 32'h3022_c0de // 32bit 的数值
- /*不区分大小写;下划线是为了增加可读性; */
- //一般直接写数字时,默认为十进制表示,例如下面的 3 种写法是等效的:
- counter = 'd100 ; //一般会根据编译器自动分频位宽,常见的为32bit
- counter = 100 ;
- counter = 32'h64 ; //16进制64 == 十进制100
负数表示:通常在表示位宽的数字前面加一个减号来表示负数,
- -6'd15
- -15
- 4'd-2 //非法说明,错误
其中负数使用补码形式,-15 在 5 位二进制中的形式为 5'b10001(补码:取反加一为实际值,最高位1表示符号位,0001取反加一等于1111), 在 6 位二进制中的形式为 6'b11_0001(最高位1为符号扩展位)。
注意:减号放在基数和数字之间是非法的,
实数:实数可以用两种形式定义:十进制计数法和科学计数法。
- 1.十进制计数法
- 2.0
- 3.1415926
- 2.科学计数法
- 235.12e2 实际值23512
- 5e-4 实际值0.0005
- 其中e与E相同,根据Verilog语法定义,实数通过四舍五入隐式的转换为最相近的整数
字符串:字符串是双引号内的字符序列,字符串不能分成多行书写,用8位ASCII值表示的字符可看作是无符号整数,一个ASCII码占8位。
- reg [1:8*7] Char;
- Char = "counter";
数据流型描述一般都采用(assign)连续赋值语句来实现,主要用于实现组合功能。连续赋值语句右边所有的变量受持续监控,只要这些变量有一个发生变化,整个表达式将被重新赋值给左端。这种方法只能用于实现组合逻辑电路。
assign L_s = R_s;
- //一个利用数据流描述的移位器
- module mlshift2 (a, b)
- input a;
- output b;
-
- assign b == a << 2;
- endmodule
- //只要a值发生变化,b就会被重新赋值,所赋的值为a左移两位后的值
行为级描述主要包括过程结构、语句块、时序控制、流控制4个方面,主要用于时序逻辑功能的实现。(个人以为只需要了解关键字的意义即可)
initial模块从模拟0时刻开始执行,且在仿真过程中只执行一次,在执行完一次后,该initial就被挂起,不在执行。如果仿真中有多个initial模块,则同时从0时刻开始并行执行。(initial模块是面向仿真的,是不可增和的,通常被用来测试模块初始化、监视、波形生成等功能)
- initial begin
- //初始化输入
- clk = 0;
- ai = 0;
- bi = 0;
- //等待100ns,全局reset信号有效
- # 100;
- ai = 20;
- bi = 10;
- end
always模块是一只重复执行的,并且可被综合。敏感事件表的目的就是触发always模块的运行,而initial后面是不允许有敏感事件表的,always过程块的触发条件是:只要a、b、c信号的电平有任意一个发生变化。
- always @(a or b or c) begin
- ...
- end
always模块主要是对硬件功能的行为进行描述,可以实现锁存器和触发器,也可以用来实现组合逻辑。利用always实现组合逻辑时,要将所有的信号放进敏感列表,而实现时序逻辑时却不一定要将所有的结果放进敏感信息列表。(上例中只有a、b、c中存在变化时就会执行语句块,如果存在其他的信号,比如e发生变化则不会执行语句块)
语句块就是initial模块或者always模块中位于begin...end/fork...join块定义语句之间的一组行为语句。
- begin ... end 串行块,用来组合需要顺序执行的语句
- fork ... join 并行块,用来组合需要并行执行的语句
-
- //例:下面两段程序都是用来产生一系列延迟波形
- parameter d = 50;
- reg [7:0] r;
- begin
- # d r = 'h35; //语句1
- # d r = 'h35; //语句2
- # d r = 'h35; //语句3
- # d r = 'h35; //语句4
- # d -> end_wave; //语句5,触发事件end_wave
- end
-
- parameter d = 50;
- reg [7:0] r;
- fork
- # d r = 'h35; //语句1
- # 2d r = 'h35; //语句2
- # 3d r = 'h35; //语句3
- # 4d r = 'h35; //语句4
- # 5d -> end_wave; //语句5,触发事件end_wave
- join
- //由于并行块中语句是同步进行的,所以为了产生间隔d的波形需要递增延时时间
- //边沿触发事件计数器(计算时钟变化次数)
- reg [4:0] cnt;
- always @(posedge Clk or negedge Reset_n)begin //posedge上升沿关键字 negedge下降沿关键字
- if(!Reset_n)
- cnt <= 0;
- else
- cnt <= cnt + 1;
- end
-
- //电平触发事件计数器(记录a,b,c变化的次数)
- reg [4:0] cnt;
- always @(a or b or c)begin
- if(!Reset_n)
- cnt <= 0;
- else
- cnt <= cnt + 1;
- end
- ???如果a,b,c同时变化只记录一次???
流控制语句包含3类,即跳转、分支、循环语句。
- //if语句
- always @(a1 or b1)begin
- if(a1) q <= d;
- else q <= 0 //else分支也可以默认,但会产生一些不可预料的结果,生成成本不期望的锁存器。
- end
-
- //case语句
- always @(a1[1:0] or b1)begin
- case (a1)
- 2'b00 : q <= b1;
- 2'b01 : q <= b1 + 1;
- default : q <= b1 + 2; //default分支虽然可以默认,但一般不要默认,可能会生成锁存器。
- end
-
- //for循环
- for(i = 1; i <= size; i = i + 1)
- result = result + (a << (i - 1));
-
- //while循环
- while (temp)begin
- count = count + 1;
- end
-
- //forever循环
- /*forever必须写在initial模块中,用于产生周期性波形,系统函数 $finish 可退出 forever。*/
- initial forever begin
- if (d) a = b + c;
- else a = 0;
- end
-
- //repeat循环
- repeat (size)begin //指定循环次数
- c = b << 1;
- end
在一个模块中引用另一个模块,对其端口进行相关连接,叫做模块例化(一个模块其实就是一个器件,将器件连接起来的操作就是例化)。模块例化建立了描述的层次。信号端口可以通过位置或名称关联,端口连接也必须遵循一些规则。如果某些输出端口并不需要在外部连接,例化时 可以悬空不连接,甚至删除。一般来说,input 端口在例化时不能删除,否则编译报错,output 端口在例化时可以删除。建议 input 端口不要做悬空处理,无其他外部连接时赋值其常量,
当例化端口与连续信号位宽不匹配时,端口会通过无符号数的右对齐或截断方式进行匹配。子模块的端口信号位宽大于例化对应端口,则截断;反之补位右对齐。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。