Verilog程序的基本单元是 “模块(block)” (类似C的函数)
一个模块由两部分组成
接口描述
端口定义
I/O说明
逻辑功能描述
内部信号声明
功能定义
可综合和不可综合
可综合的模块:可以用综合工具生成实际电路结构的模块
不可综合的模块:不能生成实际电路结构的模块,常用于testbench
module block(a,b,c,d); //端口定义
input a,c; //I/O说明
output c,d;
assign c = a | b; //功能定义
assign d = a & b;
endmodule
模块用 moudule
和 endmodule
包起来
moudle后接模块名
模块名后是参数列表,每个参数是一个端口,别忘了;
I/O说明
input
指定端口为输入端口,默认是wire
类型。写成input reg
则为reg
类型
ouput
指定端口为输出端口,默认是wire
类型。写成output reg
则为reg
类型
I/O说明语句也可以和端口定义写在一起
module block(
input a,
input b,
output c,
output d
);
这段程序对应的接口描述和逻辑功能
//端口定义 + I/O说明---------------------------------------------
module flow_led(
input sys_clk , //系统时钟
input sys_rst_n, //系统复位,低电平有效
output reg [3:0] led //4个LED灯,这是一个1位位宽reg的数组,数组长4
);
//内部信号声明--------------------------------------------------
reg [23:0] counter; //reg define,这是一个24位位宽reg类型变量
//功能定义-----------------------------------------------------
//计数器对系统时钟计数,计时0.2秒
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
counter <= 24'd0;
else if (counter < 24'd10)
counter <= counter + 1'b1;
else
counter <= 24'd0;
end
//通过移位寄存器控制IO口的高低电平,从而改变LED的显示状态
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
led <= 4'b0001;
else if(counter == 24'd10)
led[3:0] <= {led[2:0],led[3]};
else
led <= led;
end
endmodule
and #2 u1(q,a,b); //and例化一个与门模块
模块调用类似C中函数调用,信号通过模块端口在模块之间传递
示例
initial语句在模块中只在最开始执行一次
常用于测试文件的编写,用来产生仿真测试信号(激励信号),或者用来对存储器变量赋值
如果initial语句有多行,需要用begin
和 end
把它们括起来(类似C的大括号)
示例
//给输入信号的初始值
initial begin
//这三条同时执行
sys_clk <= 1'b0;
sys_rst_n <= 1'b0;
touch_key <= 1'b0;
//#是延时
#20 sys_rst_n <= 1'b1; //过20ns,拉高sys_rst_n
#10 touch_key <= 1'b1; //再过10ns,touch_key
#30 touch_key <= 1'b0; //再过30ns....
#110 touch_key <= 1'b1;
end
always语句一直在不断地重复活动
只有和一定的时间控制结合在一起才有作用(比如下面那个#10
)
如果always语句有多行,需要用begin
和 end
把它们括起来(类似C的大括号)
示例
//产生50Mhz时钟,周期20us
always #10 sys_clk <= ~sys_clk;
波形(最上面一行是clk信号)
always的时间控制(触发控制)
数字电路分为组合逻辑电路和时序逻辑电路两类
组合逻辑电路
时序逻辑电路
格式:b = [delay] a;
(这里的delay是语句内延时)
操作:计算右值,内部延时,更新左值。
所谓 “阻塞” :指同一个always块中,后面的赋值语句是在前一句赋值语句结束之后才开始赋值的
示例
a = 0;
这时a=0,b=2,c=3b = a;
这时a=0,b=0,c=3c = b;
这时a=0,b=0,c=0示例(2)
initial begin
t = #5 0; // 时间为(0,5]时,t=x
t = #4 1; // 时间为(5,9]时,t=0
t = #10 0; // 时间为(9,19]时,t=1
end
格式:b <= [delay] a;
(这里的delay是语句内延时)
操作:赋值开始的时候,计算右值;进行内部延时;赋值结束时,更新左值
所谓 “非阻塞”:指在计算右值和更新左值期间,允许其他的非阻塞赋值语句同时计算右值和左值
非阻塞赋值只能用于对reg
类型的变量进行赋值,因此只能用在 initial块
和 always块
中
示例
a = 0;
b = a;
c = b;
三行同时开始计算右值,然后同时更新左值,导致a的值变为0,b的值变为一开始a的值(1),c变为一开始b的值(2)initial begin
t = #5 1;
t = #4 0;
t = #10 0;
end
// 时间为(0,4]时,t=x,时间为(4,5]时,t=0,时间为(5,10]时,t=1,时间为(10,正无穷]时,t=0,
always @(negedge clk)
reg_a <= data; // 写
always @(negedge clk)
reg_b <= reg_a; // 读
对always语句块外用到的变量进行赋值时,使用<=
,计算中间结果时,使用=
注意
Verilog语言使用一个或多个模块对数字电路建模,通常可以用三种方式:
结构描述方式:即调用其它已定义好的低层模块或直接调用Verilog内部度基本门级元件描述电路结构和功能。
数据流描述方式:连续使用赋值语句(assign)对电路的逻辑功能进行描述。
行为风格描述方式:使用过程块语句结构(initial和always语句)和比较抽象的高级程序语句对电路的逻辑功能进行描述。
也可以把这些方式混用,称为混合设计风格描述方式
assign语句就属于第二种。
连续赋值语句用于对wire
型变量进答行赋值,它由关键字内assign
开始,后面跟着由操作数和运算符组成的逻辑表达式。
在assign
语句中,左边变量的数据类型必须是wire
型。模块的input和output如果容不特别声明类型,默认是wire类型。
格式assgin [delay]LHS_net = RHS_expression
assign右边的操作数无论何时发生变化,右边的表达式都会被重新计算,并且在指定的延时后(默认0),计算得到的值被赋予等号左边的线网变量。因此也称为 “连续赋值语句”
assign、always、initial等是并发执行的
例如:
wire A,B,SEL,L;//声明4个线型变量
assign L=(A&~SEL)|(B&SEL);//连续赋值
总之
assign
常习惯用在模块的output
赋值
out = ...
,这是把内部的reg信号直接作为输出,这里其实暗含了用根导线直接把reg的输出与out连接起来assign out = ...
,这是把上面的隐含的线显式表示了out
是一个wire
类型assign lholda= (条件)? (lholda_ra): lholda_rb;
可以嵌套使用。(这个真的很很很很常用!!)initial
和 always
引导的块语句)中使用//写法1--------------------------------------
if(a > b)
out = data_1;
//写法2--------------------------------------
if(a > b)
out = data_1;
else
out = data_2;
//写法3--------------------------------------
if(表达式1)
语句1;
else if(表达式2)
语句2;
...
else if(表达式n)
语句n;
else
语句n+1;
说明
允许一定程度的简写
if(a)
等同于 if(a==1)
if(!a)
等同于 if(a!=1)
if
语句对表达式的值进行判断
0
,x
,z
则按假处理1
则按真处理if
和 else
语句后面的操作语句可以用 begin
和 end
包含多行语句
允许 if
语句的嵌套