What
发展历史
Verilog 的优点:
既能进行面向综合的电路设计,也可用于电路的模拟仿真。
能够在多个层次上对所设计的系统进行描述:开关级、门级、寄存器传输级、行为级。
不对设计的规模施加任何限制。
有混合建模能力,即在一个设计中,各个模块可在不同层次上建模和描述。
灵活多样的电路描述风格:行为描述、结构描述、数据流描述。
其行为描述语句(条件、赋值、循环)类似于高级语言,易学易用。
内置各种基本逻辑门,适合门级建模。内置各种开关级元件,适合开关级建模。可灵活创建用户定义原语(UDP)。
可通过编程语言接口(PLI)调用 C 语言编写的各种函数。
模块化设计理念
常量
**reg型、wire型、integer型、parameter型**
large型、medium型、scalared型、time型、small型、tri型、trio型、tri1型、triand型、trior型、trireg型、vectored型、wand型、wor型。这些数据类型除time型外都与基本逻辑单元建库有关,与系统设计没有很大的关系,我们**无需刻意去掌握**。**系统设计工程师不必过多地关心门级和开关级的Verilog HDL语法现象。**
数字
**整数**:
- 二进制整数(b或B)
- 十进制整数(d或D)
- 十六进制整数(h或H)
- 八进制整数(o或O)
表达方式
- `<位宽><进制><数字>`这是一种全面的描述方式。
- `<进制><数字>`在这种描述方式中,数字的位宽采用缺省位宽(这由具体的机器系统决定,但至少32位)。
- `<数字>`在这种描述方式中,采用缺省进制十进制。
**x和z值: x**代表不定值,**z(?)**代表高阻值, ? 在使用case表达式时建议使用, 以提高程序的可读性
4'b10x0 //位宽为4的二进制数从低位数起第二位为不定值
4'b101z //位宽为4的二进制数从低位数起第一位为高阻值
12'dz //位宽为12的十进制数其值为高阻值(第一种表达方式)
12 'd? //位宽为12的十进制数其值为高阻值(第二种表达方式)
8'h4x //位宽为8的十六进制数其低四位值为不定值
参数 (Parameter)
定义常量, 称符号常量,即标识符形式的常量
parameter型数据是一种常数型的数据,参数型常数经常用于定义延迟时间和变量宽度。在模块或实例引用时可通过参数传递改变在被引用模块或实例中已定义的参数。其说明格式如下:
parameter 参数名1=表达式,参数名2=表达式, …, 参数名n=表达式;
parameter msb = 7 ; //定义参数msb为常量7
parameter e = 25 , f = 29 ; //定义二个常数参数
parameter r = 5 . 7 ; //声明r为一个实型参数
parameter byte_size = 8 , byte_msb = byte_size - 1 ; //用常数表达式赋值
parameter average_delay = (r + f) / 2 ; //用常数表达式赋值
//该表达式只能包含数字或先前已定义过的参数。
下面将通过两个例子进一步说明在层次调用的电路中改变参数常用的一些用法。
[例]:在引用Decode实例时,D1,D2的Width将采用不同的值4和5,且D1的Polarity将为0。可用例子中所用的方法来改变参数,即用 \#(4,0)向D1中传递 Width=4,Polarity=0; 用\#(5)向D2中传递Width=5,Polarity仍为1。
module Decode (A,F);
parameter Width = 1 , Polarity = 1 ;
……………
endmodule
module Top ;
wire [ 3 : 0 ] A4;
wire [ 4 : 0 ] A5;
wire [ 15 : 0 ] F16;
wire [ 31 : 0 ] F32;
Decode #( 4 , 0 ) D1 (A4,F16);
Decode #( 5 ) D2 (A5,F32);
endmodule
[例]:下面是一个多层次模块构成的电路,在一个模块中改变另一个模块的参数时,需要使用defparam命令
module Test ;
wire W;
Top T ( );
endmodule
module Top ;
wire W
Block B1 ( );
Block B2 ( );
endmodule
module Block ;
parameter P = 0 ;
endmodule
module Annotate ;
defparam
Test.T.B1.P = 2 ,
Test.T.B2.P = 3 ;
endmodule
变量
- **网络数据类型表示结构实体(例如门)之间的物理连接**
- **不能储存值**,而且它必需受到驱动器(例如门或连续赋值语句,assign)的驱动。
- 如没有驱动器连接到网络类型的变量上,则该变量高阻,值为**z**。
- **常用的网络数据类型包括wire型和tri型**。用于**连接器件单元**
- 之所以提供这两种名字来表达相同的概念是为了与模型中所使用的变量的实际情况相一致。
- wire型: 单个门驱动或连续赋值语句驱动的网络型数据
- tri型: 多驱动器驱动的网络型数据
- 如果wire型或tri型变量没有定义逻辑强度(logic strength),在多驱动源的情况下,逻辑值会发生冲突从而产生不确定值。下表为wire型和tri型变量的真值表。
wire 型
- **常表示用于以assign关键字指定的组合逻辑信号**。
- 模块中信号类型缺省时自动定义为wire型。
- wire型信号可以用作任何方程式的输入,也可以用作“assign”语句或实例元件的输出。
- wire型信号的格式同reg型信号的很类似。其格式如下:
wire [n - 1 : 0 ] 数据名1,数据名2,…数据名i; //共有i条总线,每条总线内有n条线路
wire [n: 1 ] 数据名1,数据名2,…数据名i;
wire a; //定义了一个一位的wire型数据
wire [ 7 : 0 ] b; //定义了一个八位的wire型数据
wire [ 4 : 1 ] c, d; //定义了二个四位的wire型数据
***wire用法总结***
- wire可以在Verilog中表示任意宽度的单线/总线
- wire可以用于模块的输入和输出端口以及一些其他元素并在实际模块声明中
- wire不能存储值(无状态),并且不能在always @块内赋值(=或<=)左侧使用。
- wire是assign语句左侧唯一的合法类型
- wire只能用于组合逻辑
reg 型
- **寄存器是数据储存单元的抽象**。
- 其作用与改变触发器储存的值相当。
- reg类型数据的缺省初始值为不定值,x。reg型数据可以赋正值,也可以赋负值。但当一个reg型数据是一个表达式中的操作数时,它的值被当作是无符号值,即正值。例如:当一个四位的寄存器用作表达式中的操作数时,如果开始寄存器被赋以值-1,则在表达式中进行运算时,其值被认为是+15。
- reg型只表示被定义的信号将用在“always”块内,常**代表触发器**。并不是说reg型信号一定是寄存器或触发器的输出。虽然reg型信号常常是寄存器或触发器的输出,但并不一定总是这样。
- 通常,在设计中要由“always”块通过使用行为描述语句来表达逻辑关系。
- **在“always”块内被赋值的每一个信号都必须定义成reg型。** 赋值语句的作用就象改变一组触发器的存储单元的值
reg型数据的格式如下:
reg [n - 1 : 0 ] 数据名1,数据名2,… 数据名i;
reg [n: 1 ] 数据名1,数据名2,… 数据名i;
reg rega; //定义了一个一位的名为rega的reg型数据
reg [ 3 : 0 ] regb; //定义了一个四位的名为regb的reg型数据
reg [ 4 : 1 ] regc, regd; //定义了两个四位的名为regc和regd的reg型数据
在Verilog中有许多构造(construct)用来控制何时或是否执行这些赋值语句。这些控制构造可用来描述硬件触发器的各种具体情况,如触发条件用时钟的上升沿等,或用来描述具体判断逻辑的细节,如各种多路选择器。
***reg用法总结***
- 类似于电线,但可以存储信息(有内存,有状态)允许连接到模块的输入端口,但不能连接到实例化的输出
- 在模块声明中,reg可以用作输出,但不能用作输入
- 在always@(......)语句块内,= 或者 <= 赋值语句的左边必须是是reg变量
在initial语句块内,= 赋值语句的左边必须是是reg变量
- Reg不能用于assign赋值语句的左侧
- 当与@(posedge clock)块一起使用时,reg可用于创建寄存器
- reg可用于组合逻辑和时序逻辑
***构建一个模块module时**,*
input必须是wire
output可以是wire也可以是reg
inout必须是wire
***例化模块时**,*
外部连接input端口的可以是wire也可以是reg
外部连接output端口的必须是wire
外部连接inout端口的必须是wire
memory 型
Verilog HDL通过对reg型变量建立数组来对存储器建模,可以描述RAM型存储器,ROM存储器和reg文件。memory型数据是通过扩展reg型数据的地址范围来生成的。其格式如下:
reg [n - 1 : 0 ] 存储器名[m - 1 : 0 ];
//在这里,reg[n-1:0]定义了存储器中每一个存储单元的大小,即该存储单元是一个n位的寄存器。存储器名后的[m-1:0]或[m:1]则定义了该存储器中有多少个这样的寄存器。最后用分号结束定义语句。下面举例说明:
reg [ 7 : 0 ] mema[ 255 : 0 ]; // 该存储器有256个8位的存储器
**注意:对存储器进行地址索引的表达式必须是常数表达式。**
另外,在同一个数据类型声明语句里,可以同时定义**存储器**型数据和reg型数据。见下例:
parameter wordsize = 16 , //定义二个参数。
memsize = 256 ;
reg [wordsize - 1 : 0 ] mem[memsize - 1 : 0 ],writereg, readreg;
// 尽管memory型数据和reg型数据的定义格式很相似,但要注意其不同之处。如一个由n个1位寄存器构成的存储器组是不同于一个n位的寄存器的。见下例:
reg [n - 1 : 0 ] rega; //一个n位的寄存器
reg mema [n - 1 : 0 ]; //一个由n个1位寄存器构成的存储器组
**一个n位的寄存器可以在一条赋值语句里进行赋值,而一个完整的存储器则不行。**见下例:
rega = 0 ; //合法赋值语句
mema = 0 ; //非法赋值语句
//如果想对memory中的存储单元进行读写操作,必须指定该单元在存储器中的地址。下面的写法是正确的。
mema[ 3 ] = 0 ; //给memory中的第3个存储单元赋值为0。
// 进行寻址的地址索引可以是表达式,这样就可以对存储器中的不同单元进行操作。表达式的值可以取决于电路中其它的寄存器的值。例如可以用一个加法计数器来做RAM的地址索引。
运算符
Verilog HDL语言的运算符范围很广,其运算符**按其功能可分为以下几类:**
- 算术运算符(+,-,×,/,%)
- 赋值运算符(=,<=)
- 关系运算符(>,<,>=,<=)
- 逻辑运算符(&&,||,!)
- 条件运算符(?:)
- 位运算符(~,|,^,&,^~)
- 移位运算符(<<,>>)
- 拼接运算符({ })
- 其它
在Verilog HDL语言中运算符所带的操作数是不同的,**按其所带操作数的个数运算符可分为三种:**
- 单目运算符(unary operator):可以带一个操作数,操作数放在运算符的右边。
- 二目运算符(binary operator):可以带二个操作数,操作数放在运算符的两边。
- 三目运算符(ternary operator):可以带三个操作,这三个操作数用三目运算符分隔开。
见下例:
clock = ~ clock; // ~是一个单目取反运算符, clock是操作数。
c = a | b; // 是一个二目按位或运算符, a 和 b是操作数。
r = s ? t : u; // ?: 是一个三目条件运算符, s,t,u是操作数。
下面对常用的几种运算符进行介绍。
算术运算符
在Verilog HDL语言中,算术运算符又称为二进制运算符,共有下面几种:
1) + (加法运算符,或正值运算符,如 rega+regb,+3)
2) - (减法运算符,或负值运算符,如 rega-3,-3)
3) × (乘法运算符,如rega*3)
4) / (除法运算符,如5/3)
5) % (模运算符,或称为求余运算符,要求%两侧均为整型数据。如7%3的值为1)
在进行整数除法运算时,结果值要略去小数部分,只取整数部分。而进行取模运算时,结果值的符号位采用模运算式里第一个操作数的符号位。见下例。
模运算表达式 结果 说明
10%3 1 余数为1
11%3 2 余数为2
12%3 0 余数为0即无余数
-10%3 -1 结果取第一个操作数的符号位,所以余数为-1
11%3 2 结果取第一个操作数的符号位,所以余数为2.
**注意:** **在进行算术运算操作时,如果某一个操作数有不确定的值x,则整个结果也为不定值x。**
位运算符
Verilog HDL作为一种硬件描述语言,是针对硬件电路而言的。在硬件电路中信号有四种状态值1,0,x,z.在电路中信号进行与或非时,反映在Verilog HDL中则是相应的操作数的位运算。Verilog HDL提供了以下五种位运算符:
~ //取反
& //按位与
| //按位或
^ //按位异或
^~ //按位同或(异或非)
说明:
- 位运算符中除了~是单目运算符以外,均为二目运算符,即要求运算符两侧各有一个操作数.
- 位运算符中的二目运算符要求对两个操作数的相应位进行运算操作。
Details
module my (o,t,th,f,out);
input o,t,th,f;
output out;
reg out;
always @( o or t or th or f)
begin
case ({o,t,th,f})
4'b0000 :out <= 0 ;
4'b0001 :out <= 0 ;
4'b0010 :out <= 0 ;
4'b0011 :out <= 0 ;
4'b0100 :out <= 0 ;
default :out <= 1 ;
endcase
end
endmodule
module Dff_p (clk, d, q,n_q) ;
input clk, d;
output q,n_q;
reg q ,n_q;
always @ ( posedge clk)
begin
q <= d;
n_q <= d;
end
endmodule
module Dff_p (clk, Reset, Set,d, q,n_q);
input clk, d, Reset, Set;
output q,n_q;
reg q,n_q;
always @ ( posedge clk or negedge Reset or negedge Set)
if ( ! Reset)
begin q <= 0 ; n_q <= 1 ; end
else if ( ! Set)
begin q <= 1 ;n_q = 0 ; end
else
begin q <= d; n_q <=~ d; end
endmodule
回头补充......
EXPERIENCE
被 赋值 与 OUTPUT 的 变量 都应该定义为 REG
always 行为描述
⇒ 非阻塞式赋值
时序计数
Reference
FPGA超级入门教程
归约运算符
归约运算符是一元操作数,相当于 C 中的单目运算符。它是对一个操作数进行位操作,最后得到一个一位的数。
归约运算的过程是第一步先用操作数的第一位和第二位进行位操作,然后再用第一步的结果和操作的数的下一位
进行位操作,如此重复直到最后一位。
1、归约与
2、归约或
3、归约异或
一个例子
a = 4’b0000;
&a = 0; ~&a = 1 ;|a = 0 ; ~|a = 1; ^a = 0; ~^a = 1
More
时钟脉冲概念
常看到说,时钟信号是用来“同步”系统各器件(CPU、内存、总线等)的工作的。但是这里的“同步”实在是太笼统了。什么是“同步”?各器件为什么要同步?
以下内容为个个学习总结出来的观点,不保证其正确性
下面举存储器的例子来说明。
先要了解到“存储器”是用触发器 (flip-flop) 或电容器 (capacitor) 做的。用触发器的就是 SRAM,用电容器的就是 DRAM。因为电容是会不断放电的,所以要不断对其充电(刷新),所以才叫做 Dynamic RAM。
然后要了解到,触发器和电容器做的都分为两类:不同步的和同步的。不同步的触发器叫做简单 (simple) 或透明 (transparent) 触发器;同步的触发器叫做钟控 (clocked) 触发器。另一方面,不同步的电容器做的 RAM 就叫 DRAM,同步的电容器做的 RAM 就叫 SDRAM。
触发器和电容器都是放在电路里工作(例如返回它们保存的值,设置它们的值等)的;它们工作是要时间的;它们完成工作后,要“通知”其他器件它们工作完成了(这就是各器件要“同步”的原因)。“通知”方式就有两种:通过外部时钟信号和其他方式(例如不同步的 CPU 用的 “pipeline controls” or “FIFO sequencers.” 等)。通过外部时钟信号来告诉其他部件工作已完成就叫做“同步”。具体地说,就是触发器和电容器在一个时钟周期内必须完成工作,这样其他部件就可以认为是“被通知了”。
我们知道电信号以低电压和高电压来区分 1 和 0,假如我们要传输一段 01010101 的电信号,只要根据变化就能区分出来得到正确的解析,但是如果我们需要传输一段 0000 的电信号,你就会发现一个问题,那就是我到底传输了几个 0?因为电信号一直没有变化,无法区分,所以时钟信号的作用就在这里,根据时间间隔来分割每一段电信号。