目录
1.两种参数化不改变源文件,只改仿真文件的值
2.参数化设计实现模块的重用
2.1不用参数化方法
2.1.1源文件
2.1.2仿真文件
2.1.3仿真波形及实验
2.2 用参数方法
2.2.1调用之前写的led灯闪烁模块,在本源函数中,例化4次调用之前的模块。
2.2.2 源文件
2.2.3仿真文件
2.2.4 电路图的理解
3阻塞与非阻塞语句
1.两种参数化不改变源文件,只改仿真文件的值
parameter的作用:
(1)在模块被例化时,在例化它的代码中,使用一定的语句修改其值。
(2)在仿真和实际运行时,分别要取不同的常量,使用parameter进行定义。
(3)仿真时,在testbench中通过defparam语句修改其值,以新的值进行仿真。
在源文件定义好参数后,仿真文件中有两种方式:
方式1:
该语句需要放在例化后,且修改仿真文件参数时,需要使用被例化后的模块名。先定义,后使用。
defparam run_led3_inst0.MCNT=25_000-1;
方式2:
直接在例化中进行修改。
run_led3
#(
.MCNT(25_00-1)
)
run_led3_inst0(
.clk(clk),
.reset(reset),
.led(led)
);
2.参数化设计实现模块的重用
实现下图所示的功能
2.1不用参数化方法
2.1.1源文件
即建立4个计数器,分别计数到各自需要的次数,然后进行累加。这里将后面的会合在一起写了,犯了一个错误:不能出现与rst并行的if语句。再用begin...end相隔开。vivado综合出现ambiguous clock in event control - 知乎 (zhihu.com)
module led_four(
clk,
reset,
led
);
input clk;
input reset;
output reg [3:0]led;
reg [24:0]counter1;
reg [24:0]counter2;
reg [24:0]counter3;
reg [21:0]counter4;
always@(posedge clk or negedge reset)
if(!reset)
counter1<=0;
else if(counter1 == 25_000_000-1)
counter1<=0;
else
counter1<=counter1+1'd1;
always@(posedge clk or negedge reset)
if(!reset)
counter2<=0;
else if(counter2 == 125_000_00-1)
counter2<=0;
else
counter2<=counter2+1'd1;
always@(posedge clk or negedge reset)
if(!reset)
counter3<=0;
else if(counter3 == 625_000_0-1)
counter3<=0;
else
counter3<=counter3+1'd1;
always@(posedge clk or negedge reset)
if(!reset)
counter4<=0;
else if(counter4 == 25_000_00-1)
counter4<=0;
else
counter4<=counter4+1'd1;
always@(posedge clk or negedge reset)
begin
if(!reset)
led[3:0]<=1'b0;
else begin
if(counter1 == 25_000_000-1)
led[0]<=!led[0];
if(counter2 == 125_000_00-1)
led[1]<=!led[1];
if(counter3 == 625_000_0-1)
led[2]<=!led[2];
if(counter4 == 25_000_00-1)
led[3]<=!led[3];
end
end
endmodule
2.1.2仿真文件
`timescale 1ns / 1ns
module led_four_tb();
reg clk;
reg reset;
wire [3:0]led;
led_four led_four(
.clk(clk),
.reset(reset),
.led(led)
);
initial clk=1;
always #10 clk=~clk;
initial begin
reset=0;
#201;
reset=1;
#2000_000_000;
#2000_000_000;
$stop;
end
endmodule
2.1.3仿真波形及实验
仿真波形如图所示,实验现象与仿真波形一致。
2.2 用参数方法
2.2.1调用之前写的led灯闪烁模块,在本源函数中,例化4次调用之前的模块。
另一种方法是把要调用模块的.v文件先复制到本源文件工程中。
这种方式不得勾选copy栏选项。
修改调用的源文件,设一个参数。
module led_light(
reset,
clk,
led
);
input reset;
input clk;
output reg led;
reg [24:0]counter;
parameter MCNT = 25_000_000-1;
always@(posedge clk or negedge reset)
if(!reset)
counter<=1'b0;
else if (counter == MCNT)
counter<=1'b0;
else
counter<=counter+1'd1;
always@(posedge clk or negedge reset)
if(!reset)
led<=1'b0;
else if(counter == MCNT )
led<=!led;
endmodule
2.2.2 源文件
分别例化这4个计数值,MCNT就好比结构体一样,MCNT1是参数。
module led_four2(
clk,
reset,
led
);
input clk;
input reset;
output [3:0]led;
parameter MCNT1 = 25_000_000-1;
parameter MCNT2 = 125_000_00-1;
parameter MCNT3 = 625_000_0-1;
parameter MCNT4 = 25_000_00-1;
led_light
#( .MCNT(MCNT1)
)
led_light_inst0(
.reset(reset),
.clk(clk),
.led(led[3])
);
led_light
#( .MCNT(MCNT2)
)
led_light_inst1(
.reset(reset),
.clk(clk),
.led(led[2])
);
led_light led_light_inst2(
.reset(reset),
.clk(clk),
.led(led[1])
);
defparam led_light_inst2.MCNT=MCNT3;
led_light led_light_inst3(
.reset(reset),
.clk(clk),
.led(led[0])
);
defparam led_light_inst2.MCNT=MCNT4;
endmodule
2.2.3仿真文件
即将源文件中的参数嵌套一次,可以跟1节一样修改这个值,改变计数的时间。
`timescale 1ns / 1ns
module led_four_tb();
reg clk;
reg reset;
wire [3:0]led;
led_four2 led_four2(
.clk(clk),
.reset(reset),
.led(led)
);
defparam led_four2.MCNT1=250_000_000-1;
defparam led_four2.MCNT2=125_000_00-1;
defparam led_four2.MCNT3=625_000_0-1;
defparam led_four2.MCNT4=25_000_00-1;
initial clk=1;
always #10 clk=~clk;
initial begin
reset=0;
#201;
reset=1;
#2000_000_000;
#2000_000_000;
$stop;
end
endmodule
仿真结果,这里最开始左边端口出现空白,以为是仿真文件的模块名写错,其实这个都没什么关系,模块名最好与文件名一致。就是忘加端口了,一直改模块名可能会让软件找不到路径,导致仿真一直卡住,从而无法进行仿真。
修改每个计数时间,进行仿真验证 ,计数时间已经被修改。
2.2.4 电路图的理解
调用最开始的底层模块,然后用例化4个,分别计数至各自的时间。
3阻塞与非阻塞语句
时序逻辑电路一般使用<=非阻塞赋值方式,确保所有相关信号在同一时刻被一致地更新,防止出现由于顺序不同导致的竞争冒险问题。组合逻辑电路一般使用=阻塞赋值方式,输出立即响应输入变化。
阻塞语句画电路图时依次往后,在out之前的一般没有触发器,在out之后的一般就需要触发器。非阻塞语句我认为只要有=号就会有触发器。
仿真图区别在于out2的第一级加法器没有经过一拍D触发器的存储,直接和当前的c进行了运算。
把led闪烁的例程,分别把某句变为阻塞型,查看仿真例子。左边一直为低电平,右边也是,视频说的是高电平是一个仅20ns的脉冲,这里有点理解不了电路图。
改前:
改后:
右边改后: