前言:
组合逻辑电路并不是常态,电路总会受到各种各样“时间因素”的影响,时序逻辑自然被提出。在这一章将会介绍简单时序逻辑电路。
知识点:
- 阻塞赋值与非阻塞赋值
- 非阻塞赋值只能用于寄存器类型变量进行赋值,因此只能用在 initial 块和 always 块中
- 非阻塞赋值允许其他 Verilog 语句同时操作,可以看做两个过程
- 在赋值开始时刻,计算非阻塞赋值 RHS 表达式
- 在赋值结束时刻,更新非阻塞赋值 LHS 表达式
- 阻塞赋值的执行可以认为是只有一个步骤的操作,即计算 RHS(计算等号右手方向部分的值) 并更新 LHS,此时不允许有任何其他 Verilog 语句的干扰
- 如果在一个过程块中,阻塞赋值的 RHS 变量正好是另一个过程块中 LHS 的变量,且两者又同时受一个时钟信号触发,则此时赋值会出现问题,产生竞争避免竞争冒险的赋值使用
- 时序电路建模,用非阻塞赋值
- 锁存器电路建模,用非阻塞赋值
- 用 always 块建立组合逻辑模型时,用阻塞赋值
- 在同一个 always 块中建立时序和组合逻辑电路时,用非阻塞赋值
- 在同一个 always 块中不要既用阻塞赋值又用非阻塞赋值
- 不要在一个以上的 always 块中为同一个变量赋值
- 用$stobe 系统任务来显示用非阻塞赋值的变量值
- 为什么不能在多个 always 块中为同一变量赋值?
多个 always 块中为同一个变量赋值可能会导致竞争冒险即使使用非阻塞赋值也可能产生竞争冒险。
- D 触发器(DFF)是逻辑电路中最基本的存储元件,而上升沿触发的 D 触发器是最简单的 D 触发器。
- 与组合逻辑电路的 Verilog 的 always 不同,组合逻辑中 always 的敏感列表中为所有变量,而时序逻辑 always 的敏感列表中为特殊的几个值,常见的有 posedge(positive edge)、negedge (negative edge)或者是 reset、areset、en,分别为上升沿触发信号、使能信号下降沿触发信号、同步复位信号、异步复位信号。
- 锁存器与触发器的不同在于,触发器是受到 clk(设定触发信号为 clk )的上升或下降沿的控制,看重的是变化的哪个动作,锁存器则是受到 clk(设定触发信号为 clk )的状态控制,即 clk 的电平状态,高电平或低电平。
- Mealy 型时序电路,输出是与组合逻辑部分与时序逻辑部分均有关的;Moor 型时序电路,输出是仅与时序逻辑有关的
- 状态图可以将状态表中的转换情况形象地描绘出来,在后续复杂的时序逻辑电路的状态机中提供了一种非常有效的思维描述方式
实例题目:
1.1 比较阻塞赋值与非阻塞赋值的区别。JK 触发器可以用 case 语句实现。
解:
module Circuit_A(
input A,B,C,CP,
output reg f,g
);
always@(posedge CP)
begin
f=A&B;
g=f|c;
end
endmodule
和
module Circuit_B(
input A,B,C,CP,
output reg f,g
);
always@(posedge CP)
begin
f<=A&B;
g<=f|c;
end
endmodule
因为阻塞赋值在 always 模块内是有顺序性的,如果采用第一种,那么 g=f|c
等价于 g=(A&B)|c
。非阻塞语句在 always 中会使用各个变量的原始值将两个表达式右边的值计算出来并存入了暂存器,并在 always 结束时将 f、g 同时更新到最新。
1.2 实现 D 触发器和 JK 触发器。
(温习与反思: 康华光上的锁存器雨触发器更多的是从两者内部设计分析,但对于编程语言的学习并不那么重要。在 Verilog 中,将逻辑电路中的敏感事件分为两种类型:电平敏感事件和边沿触发事件。)
解:
最简单的 D 触发器实现如下:
module DFF_1( clk,d,q );
input clk,d;
output q;
wire clk,d;
reg q;
always@( posedge clk )
begin
q<=d;
end
endmodule
Testbench 仿真如下:
`timescale 1ns/1ps
module DFF_tb;
reg clk,d;
wire q;
initial begin
clk=1'b0;
d=1'b0;
end
always@(*)
begin
forever begin
#50 clk=~clk;
#150 d=1'b1;
#170 d=1'b0;
#400 d=1'b1;
end
end
DFF_1 DFF_tb_1(
.clk(clk),
.d(d),
.q(q)
);
endmodule
但是因为 SparkRoad 上的有源晶振提供的 clk 周期为 24 ns,肉眼无法区分,所以暂时不在实物上演示。
JK 触发器实现如下:
module JK(
input j,k,cp,
output reg q,
output q_n
);
assign q_n=~q;
always@( posedge cp)
begin
case({j,k})
2'b00:q<=q;
2'b01:q<=1'b0;
2'b10:q<=1'b1;
2'b11:q<=~q;
endcase
end
endmodule
Testbench 代码如下:
`timescale 1ns/1ps
module JK_tb;
reg cp,j,k;
wire q,q_n;
initial begin
j=1'b0;
k=1'b0;
cp=1'b1;
end
always@(*)begin
forever begin
#100 cp<=~cp;
end
end
always@(posedge cp)begin
#150 j<=1'b1;
k<=1'b0;
#350 j<=1'b0;
k<=1'b1;
#550 j<=1'b1;
k<=1'b1;
#750 j<=1'b0;
k<=1'b0;
end
JK JK_tb_1(
.j(j),
.k(k),
.cp(cp),
.q(q),
.q_n(q_n)
);
endmodule
波形仿真如下:
![[1688744104340(1).png]]
2.1 寄存器的实现,以及移位寄存器的实现。
(温习与反思: 寄存器实现了对数据的存储。)
解:
Verilog 代码实现如下:
module jicunqi(
input clk,reset,
input in_data,
input load,
output reg out_data
);
always@(posedge clk,reset)begin
if(reset)begin
out_data<=1'b0;
end
else if(load)begin
out_data<=in_data;
end
end
endmodule
Testbench 代码实现如下:
`timescale 1ns/1ps
module jicunqi_tb;
reg clk,reset,in_data,load;
wire out_data;
initial begin
clk=1'b0;
reset=1'b0;
in_data=1'b1;
load=1'b0;
end
always@(*)begin
forever begin
#100 clk<=~clk;
end
end
always@(posedge clk)begin
#150 load<=1'b0;
in_data<=1'b0;
reset<=1'b0;
#350 load<=1'b1;
in_data<=1'b1;
reset<=1'b0;
#550 load<=1'b1;
in_data<=1'b1;
reset<=1'b1;
#750 load<=1'b1;
in_data<=1'b0;
reset<=1'b0;
end
jicunqi jicunqi_tb_1(
.reset(reset),
.clk(clk),
.in_data(in_data),
.out_data(out_data),
.load(load)
);
endmodule
波形仿真如下:
![[1688747741891(1).png]]
接下来,实现移位寄存器:
Verilog 代码实现如下:
module yiwei_jicunqi(
input [1:0]jud,
input clk,reset,
input [7:0]in,
output [7:0]out //用来单个输出
);
reg [7:0]in_n,in_reg; //作为下一状况变量in_n和将值暂存的in_reg,这个是准备用去输出的
always@(posedge clk,posedge reset)begin
if(reset)begin
in_reg<=0;
end
else begin
in_reg[7:0]<=in_n[7:0];
end
end
always@(*)begin
case(jud)
2'b00:in_n=in_reg; //
2'b01:in_n={in[6:0],1'b0};
2'b10:in_n={1'b0,in[7:1]};
default:in_n=in; //重新载入
endcase
end
assign out=in_reg;
endmodule
Testbench 代码实现如下:
`timescale 1ns/1ps
module yiwei_jicunqi_tb;
reg clk,reset;
reg [1:0]jud;
reg [7:0]in;
wire [7:0]out;
wire [7:0]in_n,in_reg;
initial begin
clk=1'b0;
reset=1'b0;
jud=2'b0;
in=8'b0;
end
always@(*)begin
forever begin
#100 clk<=~clk;
end
end
always@(posedge clk,posedge reset)
fork
#150 in<=8'b10101010;
#350 in<=8'b01010101;
#550 in<=8'b11110000;
#750 in<=8'b00001111;
join
always@(posedge clk,posedge reset)
fork
#130 jud<=2'b01;
#330 jud<=2'b10;
#530 jud<=2'b11;
#730 jud<=2'b00;
join
always@(posedge clk,posedge reset)begin
#950 reset<=1'b1;
end
yiwei_jicunqi yiwei_jicunqi_tb_1(
.clk(clk),
.reset(reset),
.jud(jud),
.in(in),
.out(out)
);
endmodule
仿真波形如下:
![[1688805974241(1) 1.png]]
计数器
实现一个八位计数器。
解:
module jishuqi(
input clk,reset,
output jw, //显示是否要进位
output [7:0]out
);
reg [7:0]out_n; //设置一个中间量方便替代
always@(posedge clk)begin
if(reset)begin
out_n<=0;
end
else begin
out_n<=out_n+1;
end
end
assign out=out_n;
assign jw=(out_n==15)?1'b1:1'b0;
endmodule
Testbench 代码如下:
`timescale 1ns/1ps
module jishuqi_tb;
reg clk,reset;
wire jw;
wire [7:0]out;
initial begin
clk=1'b0;
reset=1'b0;
end
always@(*)begin
forever begin
#100 clk<=~clk;
end
end
always@(posedge clk)begin
#420 reset<=1'b1;
#620 reset<=1'b0;
end
jishuqi jishuqi_tb_1(
.clk(clk),
.reset(reset),
.jw(jw),
.out(out)
);
endmodule
波形仿真如下:
![[1688811990021.png]]
当调整 testbench 代码后又可见如下:
![[1688812312605.png]]
`timescale 1ns/1ps
module jishuqi_tb;
reg clk,reset;
wire jw;
wire [7:0]out;
initial begin
clk=1'b0;
reset=1'b0;
end
always@(*)begin
forever begin
#100 clk<=~clk;
end
end
initial begin
#50 reset<=1'b1;
#350 reset<=1'b0;
end
jishuqi jishuqi_tb_1(
.clk(clk),
.reset(reset),
.jw(jw),
.out(out)
);
endmodule