本学习笔记主要参考小梅哥B站教学视频,网址如下:
https://www.bilibili.com/video/BV1va411c7Dz?p=1
使用的编译器为Vivado,HDL语言为verilog
一、从计数器到可控线性序列机
1.1 让LED按照亮0.25s,灭0.75s的状态循环亮灭。
思路:设置计数器计数到1s才清零。不用取反操作,而是在相应位置处拉高或者拉低电平。
设计文件:
module counter_led_0(
Clk,
Reset_n,
Led
);
input Clk;
input Reset_n;
output reg Led;
reg [25:0] counter;
parameter MCNT = 50000000;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
counter <= 0;
else if(counter == MCNT-1)
counter <=0;
else
counter <= counter + 1'b1;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Led <= 0;
else if(counter == (MCNT/2+MCNT/4)-1) //0.75s处
Led <= 1;
else if(counter == MCNT-1)
Led <= 0;
endmodule
测试文件:
`timescale 1ns/1ns
module counter_led_0_tb;
reg Clk;
reg Reset_n;
wire Led;
counter_led_1
#(
.MCNT(125000) //将参数调小一些
)
counter_led_1(
.Clk(Clk),
.Reset_n(Reset_n),
.Led(Led)
);
initial Clk = 1;
always #10 Clk = !Clk; //~按位取反,单位信号时与!等价
initial begin
Reset_n = 0;
# 201; //避开时钟上升沿,方便观察
Reset_n = 1;
#2000000000; //两秒钟
$stop;
end
endmodule
仿真结果:
1.2 让LED灯按照指定的亮灭模式亮灭,亮灭模式未知,由用户随机指定。以0.25s为一个变化周期,8个变化状态为一个循环
思路:2秒为一个循环周期,拿出一个ctrl(输入端口)来控制LED的亮灭模式,
设计文件:
module counter_led_2(
Clk,
Reset_n,
Ctrl,
Led
);
input Clk;
input Reset_n;
input [7:0]Ctrl;
output reg Led;
reg [26:0] counter;
parameter MCNT = 100000000;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
counter <= 0;
else if(counter == MCNT-1)
counter <=0;
else
counter <= counter + 1'b1;
// always@(posedge Clk or negedge Reset_n)
// if(!Reset_n)
// Led <= 0;
// else if(counter == MCNT/8-1)
// Led <= Ctrl[0];
// else if(counter == MCNT*2/8-1)
// Led <= Ctrl[1];
// else if(counter == MCNT*3/8-1)
// Led <= Ctrl[2];
// else if(counter == MCNT*4/8-1)
// Led <= Ctrl[3];
// else if(counter == MCNT*5/8-1)
// Led <= Ctrl[4];
// else if(counter == MCNT*6/8-1)
// Led <= Ctrl[5];
// else if(counter == MCNT*7/8-1)
// Led <= Ctrl[6];
// else if(counter == MCNT-1)
// Led <= Ctrl[7];
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Led <= 0;
else case(counter)
MCNT*1/8-1:Led <= Ctrl[0];
MCNT*2/8-1:Led <= Ctrl[1];
MCNT*3/8-1:Led <= Ctrl[2];
MCNT*4/8-1:Led <= Ctrl[3];
MCNT*5/8-1:Led <= Ctrl[4];
MCNT*6/8-1:Led <= Ctrl[5];
MCNT*7/8-1:Led <= Ctrl[6];
MCNT*8/8-1:Led <= Ctrl[7];
default Led <= Led;
endcase
endmodule
测试文件:
`timescale 1ns/1ns
module counter_led_2_tb;
reg Clk;
reg Reset_n;
reg [7:0]Ctrl;
wire Led;
counter_led_2
#(
.MCNT(100000)
)
counter_led_2(
.Clk(Clk),
.Reset_n(Reset_n),
.Ctrl(Ctrl),
.Led(Led)
);
initial Clk = 1;
always #10 Clk = !Clk; //~按位取反,单位信号时与!等价
initial begin
Reset_n = 0;
Ctrl = 0;
# 201;
Reset_n = 1;
#2000;
Ctrl = 8'b1000_0110;
#2000000000;
$stop;
end
endmodule
仿真结果:
从低位开始控制
1.3 让LED灯按照指定的亮灭模式亮灭,亮灭模式未知,由用户随机指定。8个变化状态为一个循环,每个变化状态的时间值可以根据不同的应用场景选择
思路:此时的MCNT不再是一个定值,而是可以指定的Time,所以之前的写法已经不再使用了。前面是化整为零(先计数整体的时间,然后切分为八段),后面可以是凑零为整(先计数每一小段的时间,然后一起凑成一个整周期)。
第一个计数器在第一小段循环计数基本时间,计满一次让第二个计数器加1,计满八次后,清零重新开始。
设计文件:
module counter_led_3(
Clk,
Reset_n,
Ctrl,
Time,
Led
);
input Clk;
input Reset_n;
input [7:0]Ctrl;
input [31:0]Time;
output reg Led;
reg [31:0] counter;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
counter <= 0;
else if(counter == Time-1)
counter <=0;
else
counter <= counter + 1'b1;
reg [2:0] counter2;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
counter2 <= 0;
else if(counter == Time - 1)
counter2 <= counter2 + 1'b1;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Led <= 0;
else case(counter2)
0:Led <= Ctrl[0];
1:Led <= Ctrl[1];
2:Led <= Ctrl[2];
3:Led <= Ctrl[3];
4:Led <= Ctrl[4];
5:Led <= Ctrl[5];
6:Led <= Ctrl[6];
7:Led <= Ctrl[7];
default Led <= Led;
endcase
endmodule
测试文件:
`timescale 1ns/1ns
module counter_led_3_tb;
reg Clk;
reg Reset_n;
reg [7:0]Ctrl;
reg [31:0]Time;
wire Led;
counter_led_3 counter_led_3(
.Clk(Clk),
.Reset_n(Reset_n),
.Ctrl(Ctrl),
.Time(Time),
.Led(Led)
);
initial Clk = 1;
always #10 Clk = !Clk; //~按位取反,单位信号时与!等价
initial begin
Reset_n = 0;
Ctrl = 0;
Time = 0;
# 201;
Reset_n = 1;
#2000;
Time = 2500;
Ctrl = 8'b1000_0110;
#2000000000;
$stop;
end
endmodule
1.4 每隔10ms,让LED灯的一个8状态循环执行一次(每个状态的变化时间值小一点,方便测试,比如设置为10us)
思路:之前的代码counter2只要跳出复位状态,就一直地在计数。应该有一个控制作用。counter也是一直在计数。
控制counter,让它每10ms计数一次。引入一个计数开关信号,En,高电平期间,counter计数,低电平时,counter不计数为0.
设计文件:
module counter_led_4(
Clk,
Reset_n,
Ctrl,
Time,
Led
);
input Clk;
input Reset_n;
input [7:0]Ctrl;
input [31:0]Time;
output reg Led;
reg [31:0] counter; //计数一个状态的基本时间
reg EN;
reg [2:0] counter2; //计数状态数
reg [18:0] counter0; //计数外部周期,10ms周期定时器
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
counter0 <= 0;
else if(counter0 == 500000 - 1)
counter0 <= 0;
else
counter0 <= counter0 + 1'b1;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
EN <= 0;
else if(counter0 == 0) //把EN拉高
EN <= 1;
else if(counter2 == 7) //&&(counter == Time - 1)
EN <= 0;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
counter <= 0;
else if(EN)begin
if(counter == Time-1)
counter <=0;
else
counter <= counter + 1'b1;
end
else
counter <= 0;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
counter2 <= 0;
else if(EN)begin
if(counter == Time - 1)
counter2 <= counter2 + 1'b1;
end
else
counter2 <= 0;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Led <= 0;
else
case(counter2)
0:Led <= Ctrl[0];
1:Led <= Ctrl[1];
2:Led <= Ctrl[2];
3:Led <= Ctrl[3];
4:Led <= Ctrl[4];
5:Led <= Ctrl[5];
6:Led <= Ctrl[6];
7:Led <= Ctrl[7];
default Led <= Led;
endcase
endmodule
测试文件:
`timescale 1ns/1ns
module counter_led_4_tb;
reg Clk;
reg Reset_n;
reg [7:0]Ctrl;
reg [31:0]Time;
wire Led;
counter_led_4 counter_led_4(
.Clk(Clk),
.Reset_n(Reset_n),
.Ctrl(Ctrl),
.Time(Time),
.Led(Led)
);
initial Clk = 1;
always #10 Clk = !Clk; //~按位取反,单位信号时与!等价
initial begin
Reset_n = 0;
Ctrl = 0;
Time = 0;
# 201;
Reset_n = 1;
#2000;
Time = 2500;
Ctrl = 8'b1000_0110;
#2000000000;
Time = 25000;
Ctrl = 8'b1010_0110;
#200000000;
$stop;
end
endmodule
注意:设计文件中该非阻塞赋值的就要非阻塞赋值,不然会出现不必要的错误。
观察仿真结果:
发现counter2为7这一段时间很短,仅保持了两个时钟周期,导致Led的最后一位也只保留了两个时钟周期。
观察代码:
counter2=7时,直接把EN关掉了,变为0。EN关掉后,counter2马上清除为0.
修改代码为
等于7还不够,还要等到基本计时周期结束后,才将EN置为0.
二、阻塞与非阻塞赋值
设计文件1
module block_nonblock(
Clk,
Reset_n,
a,
b,
c,
out
);
input Clk;
input Reset_n;
input a,b,c;
output reg[1:0]out;
reg [1:0] d;
// always@(posedge Clk or negedge Reset_n)
// if(!Reset_n)begin
// out <= 2'b0;
// d <= 0;
// end
// else begin
// d <= a+b;
// out <= d+c;
// out <= a+b+c;
// end
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)begin
d <= 0;
end
else begin
d <= a+b;
end
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)begin
out <= 2'b0;
end
else begin
out <= d+c;
end
endmodule
如果是直接out <= a+b+c;
设计文件2
module block_nonblock1(
Clk,
Reset_n,
a,
b,
c,
out
);
input Clk;
input Reset_n;
input a,b,c;
output reg[1:0]out;
reg [1:0] d;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)begin
out = 2'b0;
d = 0;
end
else begin
out = d+c;
d = a+b;
end
endmodule
设计文件2中,改为非阻塞赋值,得到的结构图与2毫无区别。
修改:
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)begin
out <= 2'b0;
d <= 0;
end
else begin
out <= d+c;
d <= a+b;
end
调换顺序为
d = a+b;
out = d+c;
图4与图2是对应的。
测试文件
`timescale 1ns / 1ns
`define Clk_period 20
module block_nonblock_tb();
reg Clk;
reg Reset_n;
reg a,b,c;
wire [1:0]out;
block_nonblock2 block_nonblock2(
Clk,
Reset_n,
a,
b,
c,
out
);
initial Clk = 1;
always#(`Clk_period/2) Clk = ~Clk;
initial begin
Reset_n = 1'b0;
a = 0;
b = 0;
c = 0;
#(`Clk_period*200+1);
Reset_n = 1'b1;
#(`Clk_period*200);
a = 0; b = 0; c = 0;
#(`Clk_period*200);
a = 0; b = 0; c = 1;
#(`Clk_period*200);
a = 0; b = 1; c = 0;
#(`Clk_period*200);
a = 0; b = 1; c = 1;
#(`Clk_period*200);
a = 1; b = 0; c = 0;
#(`Clk_period*200);
a = 1; b = 0; c = 1;
#(`Clk_period*200);
a = 1; b = 1; c = 0;
#(`Clk_period*200);
a = 1; b = 1; c = 1;
#(`Clk_period*200);
#(`Clk_period*200);
$stop;
end
endmodule
仿真结果
(阻塞赋值)
c变为1时,结果值在下一个时钟上升沿更新。
这里出现了一个奇怪的现象,出现了一个短暂的0.
通过加入测试文件的信号
out在下一个时钟沿来时,d变为1时,才为1
可以修改为
d <=#2 a+b;
out <=#2 d+c;
避开时钟上下沿,模拟寄存器的传输延迟。