文章目录
- D触发器
- 两级D触发器
- 带异步复位的D触发器
- 带异步复位和同步置数的D触发器
- 移位寄存器
- 单口RAM
- 伪双口RAM
- 真双口RAM
- 单口ROM
组合逻辑电路在逻辑功能上特点是任意时刻的输出仅仅取决于当前时刻的输入,与电路原来的状态无关。
时序逻辑在逻辑功能上的特点是任意时刻的输出不仅仅取决于当前的输入信号,而且还取决于电路原来的状态。
本文中的例子中模块名都是timing,仿真测试文件中的模块名都是sim_timing。
D触发器
D触发器在时钟的上升沿或下降沿存储数据,其输出与时钟跳变之前输入信号的状态相同。
D触发器的设计源代码如下。
module timing(clk,d,q);
input clk;
input d;
output reg q;
always@(posedge clk)
begin
q <= d;
end
endmodule
仿真测试源代码如下。
module sim_timing();
reg clk;
reg d;
wire q;
initial
begin
d = 0;
clk = 0;
forever
begin
#({$random}%100)
d = ~d;
end
end
always #10 clk = ~clk;
timing uut_timing(
.clk(clk),
.d(d),
.q(q)
);
endmodule
D触发器的仿真输出结果如下图所示。
由上面仿真结果可以看到,输出在时钟的上升沿发生变化,且其值与时钟跳变之前输入信号的状态相同。
D触发器的RTL图如下。
两级D触发器
下面例子是一个两级D触发器,可以分析在同一时刻两个D触发器输出数据的不同。
两级D触发器的设计源代码在D触发器设计源代码的基础上再引入一个输出变量,增加一个always块语句,在时钟上升沿到来时,再将本级输出赋值给D触发器的输出,仿真测试源代码中只在例化处加入新的变量即可。
两级D触发器的仿真输出结果如下图所示。
由上面输出结果可以看到,在q0的值变化后的下一个上升沿到来后,q1的值才等于变化后的q0值。
两级D触发器的RTL图如下。
带异步复位的D触发器
异步复位独立于时钟,当其有效的时候,就触发复位操作。
带异步复位的D触发器的设计源代码如下。
module timing(clk,rst,d,q);
input clk;
input rst;
input d;
output reg q;
always@(posedge clk or negedge rst)
begin
if(!rst)
q <= 0;
else
q <= d;
end
endmodule
仿真测试源代码如下。
module sim_timing();
reg clk;
reg d;
reg rst;
wire q;
initial
begin
rst = 1;
#300
rst = 0;
#200
rst = 1;
#200
rst = 0;
#100
rst = 1;
end
initial
begin
d = 0;
clk = 0;
forever
begin
#({$random}%100)
d = ~d;
end
end
always #10 clk = ~clk;
timing uut_timing(
.clk(clk),
.d(d),
.rst(rst),
.q(q)
);
endmodule
带异步复位的D触发器的仿真输出结果如下图所示。
由上图输出结果可以看到,当复位信号有效时,输出q即刻置为0,尽管d不为0。
带异步复位的D触发器的RTL图如下。
带异步复位和同步置数的D触发器
带异步复位D触发器中的复位独立与时钟,同步置数则有别于异步复位,它同步于时钟信号,这里的同步置数可以置1、置0或置高阻状态等,根据电路的需要设定。
需要注意的是,同步操作不能把信号放到敏感列表里,也就是always语句后的括号里。
带异步复位和同步置数的D触发器的设计源代码如下。
module timing(clk,rst,set_num,d,q);
input clk;
input rst;
input set_num;
input d;
output reg q;
always@(posedge clk or negedge rst)
begin
if(!rst)
q <= 0;
else if(set_num)
q <= 1'bz;
else
q <= d;
end
endmodule
仿真测试源代码如下。
module sim_timing();
reg clk;
reg d;
reg rst;
reg set_num;
wire q;
initial
begin
rst = 0;
set_num = 0;
#300
rst = 1;
set_num = 1;
#200
set_num = 0;
end
initial
begin
d = 0;
clk = 0;
forever
begin
#({$random}%100)
d = ~d;
end
end
always #10 clk = ~clk;
timing uut_timing(
.clk(clk),
.d(d),
.rst(rst),
.set_num(set_num),
.q(q)
);
endmodule
带异步复位和同步置数的D触发器的仿真输出结果如下图所示。
由上图可知,复位信号有效时,输出一直为0,在置数信号有效时,输出并不像异步复位那样即刻变化,而是在时钟上升沿到来后再变化。
带异步复位和同步置数的D触发器的RTL图如下。
移位寄存器
移位寄存器是指在每个时钟脉冲来临时,向左或向右移动一位,通过上述D触发器的例子可知其特性,数据输出同步于时钟边沿,故移位寄存器在每个时钟来临后,每个D触发器的输出q等于前一个D触发器的输出值,从而实现移位的功能。移位寄存器的代码中需要用到拼接运算符“{}”。
移位寄存器的设计源代码如下。
module timing(clk,rst,d,q);
input clk;
input rst;
input d;
output reg[7:0] q;
always@(posedge clk or negedge rst)
begin
if(!rst)
q <= 0;
else
q <= {q[6:0],d}; //后7位左移,最低位补输入值
// q <= {d,q[7:1]}; //前7位右移,最高位补输入值
end
endmodule
仿真测试源代码如下。
module sim_timing();
reg clk;
reg d;
reg rst;
wire[7:0] q;
initial
begin
rst = 0;
#200
rst = 1;
end
initial
begin
d = 0;
clk = 0;
forever
begin
#({$random}%100)
d = ~d;
end
end
always #10 clk = ~clk;
timing uut_timing(
.clk(clk),
.d(d),
.rst(rst),
.q(q)
);
endmodule
移位寄存器向左移位的输出结果如下图所示。
由上面的输出结果可以看到,复位信号无效时,最低位在时钟上升沿到来后置为d,同时其余各位向左移动一位。
移位寄存器向右移位的输出结果如下图所示。
由上面的输出结果可以看到,复位信号无效时,最高位在时钟上升沿到来后置为d,同时其余各位向右移动一位。
移位寄存器的RTL图如下。
单口RAM
单口RAM的写地址与读地址共用一个地址,代码中将地址保留,延迟一周期之后将数据读出。
单口RAM的设计源代码如下。
module timing(
input clk,
input write,
input [7:0] data,
input [4:0] addr,
output [7:0] q
);
reg[7:0] ram[31:0]; //定义32个8位宽度的数据
reg[4:0] addr_reg;
always@(posedge clk)
begin
if(write)
ram[addr] <= data; //write data
addr_reg <= addr; //memory address
end
assign q = ram[addr_reg]; //read data
endmodule
仿真测试源代码如下。
module sim_timing();
reg clk;
reg write;
reg [7:0] data;
reg [5:0] addr;
wire[7:0] q;
initial
begin
clk = 0;
write = 1;
data = 0;
addr = 0;
end
always #10 clk = ~clk;
always@(posedge clk)
begin
data <= data + 1'b1;
addr <= addr + 1'b1;
end
timing uut_timing(
.clk(clk),
.write(write),
.data(data),
.addr(addr),
.q(q)
);
endmodule
单口RAM的输出结果如下图所示。
通过上面的输出结果可以看到,输出延迟一个周期后将数据读出。
伪双口RAM
伪双口RAM的读写地址是独立的,可以随机选择写或者读地址,同时进行读写操作。
伪双口RAM的设计源代码如下。
module timing(
input clk,
input write,
input read,
input [7:0] data,
input [4:0] write_addr,
input [4:0] read_addr,
output reg[7:0] q
);
reg[7:0] ram[31:0]; //定义32个8位宽度的数据
always@(posedge clk)
begin
if(write)
ram[write_addr] <= data; //write data
if(read)
q <= ram[read_addr]; //read data
end
endmodule
仿真测试源代码如下。
module sim_timing();
reg clk;
reg write;
reg read;
reg [7:0] data;
reg [5:0] write_addr;
reg [5:0] read_addr;
wire[7:0] q;
initial
begin
clk = 0;
write = 0;
read = 0;
data = 0;
write_addr = 0;
read_addr = 0;
#40 write = 1;
#40 read = 1;
end
always #10 clk = ~clk;
always@(posedge clk)
begin
if(write)
begin
data <= data + 1'b1;
write_addr <= write_addr + 1'b1;
if(read)
read_addr <= read_addr + 1'b1;
end
end
timing uut_timing(
.clk(clk),
.write(write),
.read(read),
.data(data),
.write_addr(write_addr),
.read_addr(read_addr),
.q(q)
);
endmodule
伪双口RAM的输出结果如下图所示。
通过上面的输出结果可以看到,在写信号有效时往RAM中写数据,在读信号有效时从RAM中读出数据,读写操作可以同时进行。
真双口RAM
真双口RAM有两套控制线,数据线,允许两个系统同时对其进行读写操作。
真双口RAM的设计源代码如下。
module timing(
input clk,
input write_a,write_b,
input read_a,read_b,
input [7:0] data_a,data_b,
input [4:0] addr_a,addr_b,
output reg[7:0] q_a,q_b
);
reg[7:0] ram[31:0]; //定义32个8位宽度的数据
always@(posedge clk)
begin
if(write_a)
ram[addr_a] <= data_a; //write data
if(read_a)
q_a <= ram[addr_a]; //read data
end
always@(posedge clk)
begin
if(write_b)
ram[addr_b] <= data_b; //write data
if(read_b)
q_b <= ram[addr_b]; //read data
end
endmodule
仿真测试源代码如下。
module sim_timing();
reg clk;
reg write_a,write_b;
reg read_a,read_b;
reg [7:0] data_a,data_b;
reg [5:0] addr_a,addr_b;
wire[7:0] q_a,q_b;
initial
begin
clk = 0;
write_a = 0;
write_b = 0;
read_a = 0;
read_b = 0;
data_a = 0;
data_b = 0;
addr_a = 0;
addr_b = 0;
#40 write_a = 1;
#40 read_b = 1;
end
always #10 clk = ~clk;
always@(posedge clk)
begin
if(write_a)
begin
data_a <= data_a + 1'b1;
addr_a <= addr_a + 1'b1;
end
end
always@(posedge clk)
begin
if(read_b)
addr_b <= addr_b + 1'b1;
end
timing uut_timing(
.clk(clk),
.write_a(write_a),
.write_b(write_b),
.read_a(read_a),
.read_b(read_b),
.data_a(data_a),
.data_b(data_b),
.addr_a(addr_a),
.addr_b(addr_b),
.q_a(q_a),
.q_b(q_b)
);
endmodule
真双口RAM的输出结果如下图所示。
我们在代码里设置的是a往RAM里面写数据,而b从RAM中往出读数据,通过上面的输出结果可以看到,这与我们在代码中设置的相符。
单口ROM
ROM是用来存储数据的,下面是一个单口ROM的例子。
单口ROM的设计源代码如下。
module timing(
input clk,
input [2:0] addr,
output reg[7:0] q
);
always@(posedge clk)
begin
case(addr)
3'b000 : q <= 8'd1;
3'b001 : q <= 8'd12;
3'b010 : q <= 8'd23;
3'b011 : q <= 8'd34;
3'b100 : q <= 8'd45;
3'b101 : q <= 8'd56;
3'b110 : q <= 8'd67;
3'b111 : q <= 8'd78;
default : q <= 8'd0;
endcase
end
endmodule
仿真测试源代码如下。
module sim_timing();
reg clk;
reg [2:0] addr;
wire[7:0] q;
initial
begin
clk = 0;
addr = 0;
end
always #10 clk = ~clk;
always@(posedge clk)
begin
addr <= addr + 1'b1;
end
timing uut_timing(
.clk(clk),
.addr(addr),
.q(q)
);
endmodule
单口ROM的输出结果如下图所示。
通过上面的输出结果可以看到,输出结果q的值与我们在ROM中预设的值是一样的。
参考资料: ZYNQ 开发平台 FPGA 教程 AX7020