为什么要握手
- 跨时钟域处理:
握手与反压
如图所示,信号从输入端到A,经过模块A处理后,再送入到B模块进行处理。为了防止B错误读取A中的数据,A与B之间添加了信号Valid,只有当Valid信号为真时,A输出的数据才是有效数据,同时,为了防止B出现问题,A与B之间还加入了一个引脚Ready,B拉高该电平时,表明当前B模块可以接收、处理信号。只有当A送给B的信号有效(Valid为真),B此时做好的准备可以接收A的数据了(Ready为真),此时,数据才被顺利的送入B中,这个过程就叫做‘握手’。
如果B没能及时的处理完A送达的数据,就会将Ready拉低,提醒A模块不要再传输数据了。此时A模块接收到B的指令,为了不让数据丢失,在B模块读取当前数据前,A模块不会更新自己输出的Data内容。由于A目前不能更新输出值,因此需要告诉A的上级模块停止数据传输,因此A模块也会拉低自己的Ready输出信号,提醒上一级暂停数据的传输。这种当后面的模块未能及时处理上级模块的输入数据时,通过一个Ready信号告诉自己前面的模块暂停数据传输的方法被称之为‘反压’。
握手信号无非3种可能
- valid先变高:上游master提供的valid信号随数据一起拉高,但下游slave过一段时间才能准备好ready信号
- ready先变高:
- valid、ready一起变高:
怎样实现握手
案例一:数据反压
- 代码
关键点在于握手机制的实现、数据的处理,即利用上游的valid_i信号、下游的ready_i信号,判断本模块:
什么时候该接收上游数据(即什么时候向上游发送ready_o信号)
什么时候该向下级发送数据(即什么时候向下游发送valid_o、data_out信号) - 本代码设计中,ready_o信号是否输出?看的是下级什么时候需要数据,即ready_i信号是否为1
valid_o信号什么时候有效?该模块功能是数据被打一拍后输出,所以握手成功后(ready_i有效),valid_o就应该输出
`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)begin
data_o_r <= 8'b0;
end else if(valid_i && ready_i)begin
data_o_r <= data_i * 2;
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
valid_o_r <= 1'b0;
end else if(ready_i)begin
valid_o_r <= valid_i;
end
end
assign data_o = data_o_r;
assign valid_o = valid_o_r;
endmodule
- testbench
`timescale 1ns/10ps
module handshake_tb();
reg clk;
reg rst_n;
reg valid_i;
reg [7:0] data_i;
wire ready_o;
wire data_o;
reg ready_i;
wire valid_o;
always #10 clk = ~clk;
initial begin
clk = 0;
rst_n = 0;
data_i = 8'd0;
valid_i = 1'd0;
ready_i = 1'd1;
#15
rst_n = 1'b1;
#15
rst_n <= 1'b1;
data_i <= 8'b0000_1000;
valid_i <= 1'd0; //upstream data invalid
ready_i <= 1'd1; //downstream ready
#20
data_i <= 8'b0111_1000;
valid_i <= 1'd1; //upstream data valid
#20
data_i <= 8'b0100_0100; //upstream data change
#20
valid_i <= 1'd0; //upstream data invalid
#20
data_i <= 8'b0010_0100;
valid_i <= 1'd1; //upstream data valid
ready_i <= 1'd0; //downstream not ready
#20
ready_i <= 1'b1; //downstream ready
#20
valid_i <= 1'b0; //upstream data invalid
#500
$stop();
end
handshake u_handshake(
.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)
);
endmodule
结果
案例二:数据串转并输出
- 设计描述
案例二 :数据串转并电路
实现串并转换电路,输入端输入单bit数据,每当本模块接收到6个输入数据后,输出端输出拼接后的6bit数据。本模块输入端与上游的采用valid-ready双向握手机制,输出端与下游采用valid-only握手机制。数据拼接时先接收到的数据放到data_b的高位。
- 电路的接口如下图所示:
valid_a用来指示数据输入data_in的有效性;
valid_b用来指示数据输出data_out的有效性;
ready_a用来指示本模块是否准备好接收上游数据;
ready_b表示下游是否准备好接收本模块的输出数据;
clk是时钟信号;rst_n是异步复位信号。 - 代码
`timescale 1ns/10ps
module handshake_s_to_p(
input clk,
input rst_n,
input data_in,
input valid_a,
input ready_b,
output reg [5:0] data_out,
output reg valid_b,
output ready_a
);
reg [2:0] counter;
reg [5:0] data_in_r;
assign ready_a = ~valid_b || ready_b;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
counter <= 3'd0;
// end else if(counter == 2'd3)begin
// counter <= 2'd0;
end else if(valid_a && ready_a )begin
counter <= (counter == 3'd5) ? 3'd0 : counter + 1'b1;
// counter <= counter + 1'b1;
end else begin
counter <= counter;
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_in_r <= 5'd0;
end else if(valid_a && ready_a)begin
data_in_r <= {data_in_r[4:0],data_in};
end else begin
data_in_r <= data_in_r;
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_out <= 5'd0;
valid_b <= 1'b0;
end else if(counter == 3'd5)begin
data_out<= data_in_r;
valid_b <= 1'b1;
end else begin
data_out<= data_out;
valid_b <= 1'b0;
end
end
endmodule
- testbench
`timescale 1ns/10ps
module handshake_tb();
reg clk;
reg rst_n;
reg [7:0] data_in;
reg valid_a;
reg ready_b;
wire [9:0] data_out;
wire valid_b;
wire ready_a;
always #10 clk <= ~clk;
initial begin
clk = 1'b0;
rst_n = 1'b0;
ready_b = 1'b1;
#15
rst_n = 1'b1;
#15
data_in <= 1'b1;
valid_a <= 1'd1; //upstream data invalid
#20
data_in <= 1'b0; //upstream data change
#20
data_in <= 1'b1;
#20
data_in <= 1'b0; //upstream data change
#20
data_in <= 1'b0; //upstream data change
#20
data_in <= 1'b1; //upstream data change
#20
ready_b <= 1'b1; //downstream ready
#500
$stop();
end
handshake_s_to_p u_handshake_s_to_p(
.clk (clk ),
.rst_n (rst_n ),
.data_in (data_in ),
.valid_a (valid_a ),
.ready_b (ready_b ),
.data_out (data_out),
.valid_b (valid_b ),
.ready_a (ready_a )
);
endmodule
结果
案例三:数据累加输出
-
设计描述
实现串行输入数据累加输出,输入端输入8bit数据,每当模块接收到4个输入数据后,输出端输出4个接收到数据的累加结果。输入端和输出端与上下游的交互采用valid-ready双向握手机制。要求上下游均能满速传输时,数据传输无气泡,不能由于本模块的设计原因产生额外的性能损失。
valid_a和ready_a均为高电平,完成数据in握手,开始接收上游下行数据(应判断ready_a何时拉高)
本模块接收数据时,valid_b始终保持为低
接收完4个数据并完成累加后,valid_b拉高(表示本模块已经准备好输出数据),等待ready_b信号到来握手
ready_b信号到来同时,ready_a立刻拉高表示本模块继续准备接受数据(显然ready_a与ready_b存在assign逻辑),valid_b与ready_b握手成功后,valid_b在下一周期主动拉低,data_out输出数据 -
电路的接口如下图所示:
valid_a用来指示数据输入data_in的有效性;
valid_b用来指示数据输出data_out的有效性;
ready_a用来指示本模块是否准备好接收上游数据;
ready_b表示下游是否准备好接收本模块的输出数据;
clk是时钟信号;rst_n是异步复位信号。 -
代码
`timescale 1ns/10ps
module handshake_mux(
input clk,
input rst_n,
input [7:0] data_in,
input valid_a,
input ready_b,
output reg [9:0] data_out,
output reg valid_b,
output ready_a
);
reg [1:0] counter;
assign ready_a = ~valid_b || ready_b;
//若本模块数据已经准备好输出,ready_b拉高,此时ready_a拉高立刻允许新的data输入,保证无气泡传输
//ready_a与valid_b互斥,本模块还在接受数据时即数据没有准备好输出
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_out <= 8'd0;
end else if(valid_a && ready_a)begin
if(counter == 2'd0 && ready_b)begin
data_out <= data_in;
end else begin
data_out <= data_in + data_out;
end
end else begin
data_out <= data_out;
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
counter <= 2'd0;
// end else if(counter == 2'd3)begin //可以直接注释掉该句,因为计数器溢出后自动清零
// counter <= 2'd0;
end else if(valid_a && ready_a )begin
counter <= counter + 1'b1;
end else begin
counter <= counter;
end
end
//counter <= (counter==2'd3) ? 2'd0 : (counter+1'b1);
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
valid_b <= 1'b0;
end else if(counter == 2'd3 && valid_a && ready_a)begin
valid_b <= 1'b1;
end else if(valid_b && ready_b)begin
valid_b <= 1'b0;
end
end
endmodule
- testbench
`timescale 1ns/10ps
module handshake_tb();
reg clk;
reg rst_n;
reg [7:0] data_in;
reg valid_a;
reg ready_b;
wire [9:0] data_out;
wire valid_b;
wire ready_a;
always #10 clk <= ~clk;
initial begin
clk = 1'b0;
rst_n = 1'b0;
ready_b = 1'b0;
#15
rst_n = 1'b1;
#15
data_in <= 8'b0000_0001;
valid_a <= 1'd1; //upstream data invalid
#20
data_in <= 8'b0000_0010; //upstream data change
#20
data_in <= 8'b0000_0011;
#20
data_in <= 8'b0000_0100; //upstream data change
#20
data_in <= 8'b0000_0101; //upstream data change
#20
ready_b <= 1'b1; //downstream ready
#500
$stop();
end
handshake_mux u_handshake_mux(
.clk (clk ),
.rst_n (rst_n ),
.data_in (data_in ),
.valid_a (valid_a ),
.ready_b (ready_b ),
.data_out (data_out),
.valid_b (valid_b ),
.ready_a (ready_a )
);
endmodule
结果