握手信号
握手信号,就是为了模块之间的数据交互正确才衍生出来的信号。其无非就是三种可能
- 上游master提供的valid 信号随数据一起拉高,但下游slave过一段时间才准备好的valid先高为敬。
- 下游slave一直准备好,但上游数据过段时间才有效的ready先高为敬。
- 上游master的valid信号和下游slave的ready同时为高。
对于master来说,master发出一个数据,如果数据有效,master就会把传送给slave的valid信号拉高,即告诉slave数据有效可以接收!如果slave准备好了,slave就把传送给master的ready信号拉高,告诉master我准备好了,如果你数据有效,我就可以接收!所以当master传输的数据有效,且slave也准备好时,数据就会在下一个周期被slave取走,master可以接着传下一个数据。
案例一
代码:
`timescale 1ns/1ns
module handshake(
input clk,
input rst_n,
input valid_i,
input [7:0] data_i,
output ready_o,
output [7:0]data_o,
input ready_i,
output valid_o
);
reg [7:0] data_o_r;
reg valid_o_r;
assign ready_o = ready_i; // 下游准备好时,此模块也准备好接收上游数据
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
data_o_r <= 8'b0;
else if(valid_i && ready_i) //如果下游准备好了,并且上游数据有效,那就把输入的数据乘以二输出
data_o_r <= data_i * 2;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
valid_o_r <= 1'b0;
else if(ready_o)
valid_o_r <= valid_i; //如果准备好了,就把上游的valid传递给下游。
end
assign data_o = data_o_r;
assign valid_o = valid_o_r;
endmodule
Testbench:
`timescale 1ns/1ns
module handshake_tb();
reg clk;
reg rst_n;
reg valid_i;
reg[7:0] data_i;
wire ready_o;
wire [7:0]data_o;
reg ready_i;
wire valid_o;
handshake inst(
.clk(clk),
.rst_n(rst_n),
.valid_i(valid_i),
.data_i(data_i),
.ready_o(ready_o),
.data_o(data_o),
.ready_i(ready_i),
.valid_o(valid_o)
);
always #10 clk = ~clk;
initial begin
clk = 0;
rst_n = 0;
data_i = 8'd0;
valid_i = 1'b0;
ready_i = 1'b1;
#15
rst_n = 1;
#15
rst_n <= 1'b1;
ready_i <= 1'b1; //下游准备好了
valid_i <= 1'b0; //上游数据无效
data_i <= 8'b0000_1000;
#20
data_i <= 8'b0111_1000;
valid_i <= 1'b1; //上游数据有效
#20
data_i <= 8'b0100_0100;
#20
valid_i <= 1'b0;
#20
data_i <= 8'b0010_0100;
valid_i <= 1'b1; //虽然上游数据有效,但下游没准备好
ready_i <= 1'b0;
#20
ready_i <= 1'b1; //上游数据有效,下游准备好了
#20
valid_i <= 1'b0;
#500
$stop();
end
endmodule
用tb来模拟本模块的上游master和下游slave,用tb给本模块提供上游数据data_i和valid_i信号。分别模拟了上游断流,即valid_i中途拉低,以及下游反压,即ready_i中途为低的情况。
仿真结果:
案例二 :数据串转并电路
实现串并转换电路,输入端输入单bit数据,每当本模块接收到6个输入数据后,输出端输出拼接后的6bit数据。本模块输入端与上游的采用valid-ready双向握手机制,输出端与下游采用valid-only握手机制。数据拼接时先接收到的数据放到data_b的低位。
电路的接口如下图所示。valid_a用来指示数据输入data_a的有效性,valid_b用来指示数据输出data_b的有效性;ready_a用来指示本模块是否准备好接收上游数据,本模块中一直拉高;clk是时钟信号;rst_n是异步复位信号。
module s_to_p(
input clk,
input rst_n,
input valid_a,
input data_a,
output reg ready_a,
output reg valid_b,
output reg [5:0] data_b
);
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
ready_a = 1'b0;
else
ready_a = 1'b1;
end
reg[2:0]cnt;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 3'd0;
else if(valid_a && ready_a)
cnt <= (cnt == 3'd5)?3'd0:cnt + 1'b1;
else
cnt <= cnt;
end
reg [5:0] data_b_r;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
data_b_r <= 6'd0;
else if(valid_a && ready_a)
data_b_r <= {data_a,data_b_r[5:1]};
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_b <= 6'd0;
valid_b <= 1'b0;
end
else if(cnt == 3'd5)begin
data_b <= {data_a,data_b_r[5:1]};
valid_b <= 1'b1;
end
else
valid_b <= 1'b0;
end
endmodule
Testbench:
`timescale 1ns/1ns
module s_to_p_tb();
reg clk;
reg rst_n;
reg valid_a;
reg data_a;
wire ready_a;
wire valid_b;
wire[5:0] data_b;
s_to_p inst(
.clk(clk),
.rst_n(rst_n),
.valid_a(valid_a),
.data_a(data_a),
.ready_a(ready_a),
.valid_b(valid_b),
.data_b(data_b)
);
always #10 clk = ~clk;
initial begin
clk= 0;
rst_n = 0;
valid_a = 0;
data_a = 0;
#15
rst_n = 1;
#15
valid_a = 1;
data_a = 1;
#20
data_a = 0;
#20
data_a = 1;
#20
data_a = 0;
#20
data_a = 0;
#20
data_a = 1;
#20
data_a = 1;
#20
valid_a = 0;
#40
valid_a = 1;
#40
data_a = 0;
#40
data_a = 1;
#20
data_a = 0;
#200
$stop;
end
endmodule
仿真结果:
案例三:数据累加输出
实现串行输入数据累加输出,输入端输入8bit数据,每当模块接收到4个输入数据后,输出端输出4个接收到数据的累加结果。输入端和输出端与上下游的交互采用valid-ready双向握手机制。要求上下游均能满速传输时,数据传输无气泡,不能由于本模块的设计原因产生额外的性能损失。
电路的接口如下图所示。valid_a用来指示数据输入data_in的有效性,valid_b用来指示数据输出data_out的有效性;ready_a用来指示本模块是否准备好接收上游数据,ready_b表示下游是否准备好接收本模块的输出数据;clk是时钟信号;rst_n是异步复位信号。
代码:
module valid_ready(
input clk,
input rst_n,
input [7:0] data_in,
input valid_a,
input ready_b,
output ready_a,
output reg valid_b,
output reg [9:0] data_out
);
assign ready_a = !valid_b || ready_b;
reg [1:0]cnt;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 2'd0;
else if(valid_a && ready_a)
cnt <= (cnt == 2'd3)?2'd0:cnt + 1'd1;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
valid_b <= 1'b0;
else if(cnt == 2'd3 && valid_a && ready_a)
valid_b <= 1'b1;
else if(valid_b && ready_b)
valid_b <= 1'b0;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
data_out <= 10'd0;
else if(cnt == 2'd0 && valid_a && ready_a && ready_b)
data_out <= data_in;
else if(valid_a && ready_a)
data_out <= data_in + data_out;
end
endmodule
Testbench:
`timescale 1ns/1ns
module valid_ready_tb();
reg clk;
reg rst_n;
reg [7:0] data_in;
reg valid_a;
reg ready_b;
wire ready_a;
wire valid_b;
wire [9:0] data_out;
valid_ready inst(
.clk(clk),
.rst_n(rst_n),
.data_in(data_in),
.valid_a(valid_a),
.ready_b(ready_b),
.ready_a(ready_a),
.valid_b(valid_b),
.data_out(data_out)
);
always #10 clk= ~clk;
initial begin
clk = 0;
rst_n = 0;
ready_b = 0;
#15
rst_n = 1;
#15
valid_a = 1;
data_in = 1;
#20
data_in = 2;
#20
data_in = 3;
#20
data_in = 4;
#20
data_in = 5;
#40
ready_b = 1;
#200
$stop;
end
endmodule
仿真结果: