赞
踩
Verilog的基本设计单元是“模块”block
,一部分描述接口,另一部分描述逻辑功能,比如:
module block(a,b,c,d);
input a,b; //描述接口
output c,d;
assign c = a | b; //描述逻辑功能
assign d = a & b;
endmodule
以上是一个简单的模块,首先描述了四个接口a,b,c,d
之后利用assign
语句描述了接口之间的逻辑关系,即c等于a或b,d等于a且b
这样一个完整的模块就定义好了
功能定义部分有三种方法,每个逻辑功能定义之间是并行的关系,即每个功能没有先后顺序,都是同时进行,但每个功能内部中是按照顺序执行的
and #2 u1(q,a,b)
定义了一个输入为a和b,输出为q的与门在模块调用时,信号通过模块端口在模块之间传递
module seg_led_static_top ( input sys_clk , // 系统时钟 input sys_rst_n, // 系统复位信号(低有效) output [5:0] sel , // 数码管位选 output [7:0] seg_led // 数码管段选 ); //parameter define parameter TIME_SHOW = 25'd25000_000; // 数码管变化的时间间隔0.5s //wire define wire add_flag; // 数码管变化的通知信号 //调用模块 //每隔0.5s产生一个时钟周期的脉冲信号 time_count #( //列出调用的模块名 .MAX_NUM(TIME_SHOW) //列出模块中的参数并重命名 ) u_time_count( .clk (sys_clk ), //将底层信号传到上层格式: .被调用模块信号名 (顶层信号名) .rst_n (sys_rst_n), .flag (add_flag ) ); //每当脉冲信号到达时,使数码管显示的数值加1 seg_led_static u_seg_led_static ( .clk (sys_clk ), .rst_n (sys_rst_n), .add_flag (add_flag ), .sel (sel ), .seg_led (seg_led ) ); //被调用模块 module time_count( input clk , // 时钟信号 input rst_n , // 复位信号 output reg flag // 一个时钟周期的脉冲信号 ); //parameter define parameter MAX_NUM = 25000_000; // 计数器最大计数值 //reg define reg [24:0] cnt; // 时钟分频计数器
利用逻辑结构框图,上述模块之间的调用以及信号连接可以表示如下:
结构语句有initial
和always
initial语句在模块中只执行一次
它常用于test bench
(测试文件)的编写,用来产生仿真测试信号(激励信号),或者用于对储存器变量赋初值:
initial
begin
sys_clk <=1'b0;
sys_rst_n <=1'b0;
touch_key <=1'b0; //全部拉低
#20 sys_rst_n <=1'b1; //20个单位时间(20ns)后拉高电平
#10 touch_key <=1'b1;
#30 touch_key <=1'b0;
#110 touch_key <=1'b1;
#30 touch_key <=1'b0;
end
always #10 sys_clk <= ~sys_clk //每10ns对sys_clk取反一次,也就是说产生周期为20ns,频率为50Mhz的时钟
上面的例子画出波形图如下:
可以看到,实际仿真的波形与我们设定的完全相符,起始状态全部为低电平,sys_clk
以周期为20ns为单位不断进行电平反转,20ns后sys_rst_n
被拉高,再经过10ns后touch_key
被拉高…
sys_clk
信号,并不是取反一次就结束了,而是不停地进行每10ns取反一次。or
连接,连接而组成的列表称为“敏感列表”posedge
是指上升沿触发,negedge
是指下降沿触发时序逻辑电路中,任一时刻的输出不仅取决于当时的输入信号,而且还取决于电路原来的状态。或者说还与以前的输入有关,因此时序逻辑必须具备记忆功能
module flow_led( input sys_clk , //系统时钟 input sys_rst_n, //系统复位,低电平有效 output reg [3:0] led //4个LED灯 ); //reg define reg [23:0] counter; //计数器对系统时钟计数,计时0.2秒 always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) counter <= 24'd0; else if (counter < 24'd1000_0000) 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'd1000_0000) led[3:0] <= {led[2:0],led[3]}; else led <= led; end endmodule
上面的例子很好理解:
如果遇到sys_clk
上升沿或sys_rst_n
下降沿则进入always语句,然后进行always中的条件判断等等…综合为逻辑框图,可用下图来表示:
组合逻辑电路中,任意时刻的输出仅仅取决于该时刻的输入,与电路原来的状态无关
比如,我们可以用always表示一组逻辑门电路:
always @(a or b or c or d or e or f or g or h or p or m)
begin
out1 = a ? (b + c) : (d + e);
out2 = f ? (g + h) : (p + m);
end
这个逻辑是:
判断a是否为1?若a为1,则out1 = b + c;反之out1 = d + e;
判断f是否为1?若f为1,则out1 = g + h;反之out1 = p + m;
如果组合逻辑快语句的输入变量很多,那么编写敏感列表会很繁琐且容易出错,这时候可以用*
来表示对后面语块中所有输入变量的变化都是敏感的
always @( * )
begin
out1 = a ? (b + c) : (d + e);
out2 = f ? (g + h) : (p + m);
end
在描述组合逻辑的 always 块中用阻塞赋值 = ,综合成组合逻辑的电路结构;
这种电路结构只与输入电平的变化有关系。
在描述时序逻辑的 always 块中用非阻塞赋值 <=,综合成时序逻辑的电路结构;
这种电路结构往往与触发沿有关系,只有在触发沿时才可能发生赋值的变化。
阻塞赋值和其他常用语言中赋值的效果相同,即计算RHS(Right Hand Side)并更新LHS(Left Hand Side),也就是说,在同一个always中,后面的赋值语句是在前一句赋值结束之后才开始赋值的,比如:
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
a = 1;
b = 2;
c = 3;
end
else
begin
a = 0;
b = a;
c = b;
end
end
显然,执行完always后a,b,c都为0,这是因为阻塞时赋值先计算了a = 0
,然后再处理了b = a
,以此类推,b和c的值都和a相同了
那么有没有办法让b和c赋第一个begin中的值呢?
非阻塞赋值可以看做两个步骤:
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
a <= 1;
b <= 2;
c <= 3;
end
else
begin
a <= 0;
b <= a;
c <= b;
end
end
执行以后,b=1,c=2,a=0,可见非阻塞赋值确实是并行的
注意: 在同一个always块中不要既用非阻塞赋值又用阻塞赋值
不允许在多个always块中对同一个变量进行赋值!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。