fpga: lib: add handshake to replace FIFO for ctrlport CDC
The clock crossing of the ctrlport used FIFOs to transfer requests and responses between clock domains. This commit adds a handshake based on the pulse synchronizer to reduce the resource usage for ctrlport clock domain crossing. Data is stored in a single register while the pulse synchronizer handles the signaling of valid flags. Original-commit: 2c2bb3a0b7269dcbd8b5933ce1c0c29ca17f599d
This commit is contained in:
@@ -59,4 +59,5 @@ map/cam.v \
|
|||||||
map/kv_map.v \
|
map/kv_map.v \
|
||||||
map/axis_muxed_kv_map.v \
|
map/axis_muxed_kv_map.v \
|
||||||
axil_ctrlport_master.v\
|
axil_ctrlport_master.v\
|
||||||
|
handshake.v\
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Copyright 2020 Ettus Research, A National Instruments Brand
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// Module: handshake
|
||||||
|
// Description:
|
||||||
|
// Implements clock domain crossing for data from one clock domain
|
||||||
|
// to another independent of the relative clock frequencies or phases of
|
||||||
|
// clk_a and clk_b.
|
||||||
|
//
|
||||||
|
// Once a handshake is triggered it cannot be aborted. A proper design needs to
|
||||||
|
// apply reset to the target clock domain downstream module to make sure a
|
||||||
|
// valid_b pulse does not cause any issues.
|
||||||
|
//
|
||||||
|
// Input Behavior:
|
||||||
|
// ┌┐┌┐┌┐┌┐┋ ┋┌┐┌┐┌┐┌┐┌┐┋ ┋┌┐┌┐┌┐┌┐┌┐┌┐┌┐
|
||||||
|
// clk_a ┘└┘└┘└┘└┋ ┋┘└┘└┘└┘└┘└┋ ┋┘└┘└┘└┘└┘└┘└┘└
|
||||||
|
// ┌─┐ ┋ ┋ ┌─┐ ┋ ┋
|
||||||
|
// valid_a ──┘ └───┋ ┋────┘ └───┋ ┋──────────────
|
||||||
|
// ▄▄┬─┬▄▄▄┋ ┋▄▄▄▄┬─┬▄▄▄┋ ┋▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||||
|
// data_a ▀▀┴─┴▀▀▀┋ ┋▀▀▀▀┴─┴▀▀▀┋ ┋▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
// ┌───┋ ┋────┐ ┌───┋ ┋────────┐
|
||||||
|
// busy_a ────┘ ┋ ┋ └─┘ ┋ ┋ └─────
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Output Behavior:
|
||||||
|
// ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┋ ┋ ┌─┐ ┌─┐ ┌─┐ ┌─┐
|
||||||
|
// clk_b ─┘ └─┘ └─┘ └─┘ └─┋ ┋─┘ └─┘ └─┘ └─┘ └─
|
||||||
|
// ┌───┐ ┋ ┋ ┌───┐
|
||||||
|
// valid_b ─────────┘ └───┋ ┋─────┘ └───────
|
||||||
|
// ▄▄▄▄▄▄▄▄▄┬───┬▄▄▄┋ ┋▄▄▄▄▄┬───┬▄▄▄▄▄▄▄
|
||||||
|
// data_b ▀▀▀▀▀▀▀▀▀┴───┴▀▀▀┋ ┋▀▀▀▀▀┴───┴▀▀▀▀▀▀▀
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
module handshake #(
|
||||||
|
parameter WIDTH = 32 // data width
|
||||||
|
) (
|
||||||
|
// source clock domain
|
||||||
|
input wire clk_a,
|
||||||
|
input wire rst_a,
|
||||||
|
input wire valid_a, // trigger handshake on rising edge
|
||||||
|
input wire [WIDTH-1:0] data_a,
|
||||||
|
output wire busy_a,
|
||||||
|
|
||||||
|
// target clock domain
|
||||||
|
input wire clk_b,
|
||||||
|
output wire valid_b,
|
||||||
|
output wire [WIDTH-1:0] data_b
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handling of the handshaking between the two clock domains. The reset does
|
||||||
|
// not delete a pulse, which is already triggered!
|
||||||
|
pulse_synchronizer #(
|
||||||
|
.MODE("PULSE"), .STAGES(4)
|
||||||
|
) push_sync_inst (
|
||||||
|
.clk_a(clk_a), .rst_a(rst_a), .pulse_a(valid_a), .busy_a(busy_a),
|
||||||
|
.clk_b(clk_b), .pulse_b(valid_b)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Capture the data aligned with triggering the handshake.
|
||||||
|
reg [WIDTH-1:0] data_a_lcl;
|
||||||
|
always @(posedge clk_a) begin
|
||||||
|
if (valid_a & ~busy_a) begin
|
||||||
|
data_a_lcl <= data_a;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// Transfer data with timing exception. Data is captured upfront and kept
|
||||||
|
// stable in clk_a domain. As there are more synchronizer stages in the
|
||||||
|
// pulse_synchronizer the data in these 2 stage synchronizer is stable once
|
||||||
|
// the valid_b pulse arrives in clk_b domain. 2 stages are used to resolve
|
||||||
|
// meta-stability. Reset is not needed on the data path as it is controlled by
|
||||||
|
// the valid signals.
|
||||||
|
synchronizer #(
|
||||||
|
.WIDTH(WIDTH), .STAGES(2), .INITIAL_VAL({WIDTH {1'b0}}), .FALSE_PATH_TO_IN(1)
|
||||||
|
) data_sync_inst (
|
||||||
|
.clk(clk_b), .rst(1'b0), .in(data_a_lcl), .out(data_b)
|
||||||
|
);
|
||||||
|
|
||||||
|
endmodule
|
||||||
@@ -11,9 +11,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
module ctrlport_clk_cross #(
|
module ctrlport_clk_cross (
|
||||||
parameter DEVICE = "7SERIES" // FPGA technology identifier (for optimal FIFO inference)
|
|
||||||
)(
|
|
||||||
input wire rst, // Can be either clock domain, but must be glitch-free
|
input wire rst, // Can be either clock domain, but must be glitch-free
|
||||||
|
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
@@ -54,6 +52,16 @@ module ctrlport_clk_cross #(
|
|||||||
input wire [ 1:0] m_ctrlport_resp_status,
|
input wire [ 1:0] m_ctrlport_resp_status,
|
||||||
input wire [31:0] m_ctrlport_resp_data
|
input wire [31:0] m_ctrlport_resp_data
|
||||||
);
|
);
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Reset sync to both clock domains
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
wire m_rst, s_rst;
|
||||||
|
reset_sync master_reset_sync_inst (
|
||||||
|
.clk(m_ctrlport_clk), .reset_in(rst), .reset_out(m_rst)
|
||||||
|
);
|
||||||
|
reset_sync slave_reset_sync_inst (
|
||||||
|
.clk(s_ctrlport_clk), .reset_in(rst), .reset_out(s_rst)
|
||||||
|
);
|
||||||
|
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
// Slave to Master Clock Crossing (Request)
|
// Slave to Master Clock Crossing (Request)
|
||||||
@@ -77,52 +85,53 @@ module ctrlport_clk_cross #(
|
|||||||
wire m_ctrlport_req_wr_tmp;
|
wire m_ctrlport_req_wr_tmp;
|
||||||
wire m_ctrlport_req_rd_tmp;
|
wire m_ctrlport_req_rd_tmp;
|
||||||
|
|
||||||
// Sort by descreasing order of usage possibility.
|
|
||||||
// This way instance, which do not need port IDs, time etc. can save the MSBs.
|
|
||||||
assign s_req_flat = {
|
assign s_req_flat = {
|
||||||
|
s_ctrlport_req_wr,
|
||||||
|
s_ctrlport_req_rd,
|
||||||
|
s_ctrlport_req_addr,
|
||||||
s_ctrlport_req_portid,
|
s_ctrlport_req_portid,
|
||||||
s_ctrlport_req_rem_epid,
|
s_ctrlport_req_rem_epid,
|
||||||
s_ctrlport_req_rem_portid,
|
s_ctrlport_req_rem_portid,
|
||||||
s_ctrlport_req_has_time,
|
|
||||||
s_ctrlport_req_time,
|
|
||||||
s_ctrlport_req_byte_en,
|
|
||||||
s_ctrlport_req_data,
|
s_ctrlport_req_data,
|
||||||
s_ctrlport_req_addr,
|
s_ctrlport_req_byte_en,
|
||||||
s_ctrlport_req_wr,
|
s_ctrlport_req_has_time,
|
||||||
s_ctrlport_req_rd
|
s_ctrlport_req_time
|
||||||
};
|
};
|
||||||
|
|
||||||
axi_fifo_2clk #(
|
// Busy flag can be ignored as the response handshake takes at least the same
|
||||||
.WIDTH (REQ_W),
|
// amount of cycles to transfer the response as this handshake instance needs
|
||||||
.SIZE (3),
|
// to release the busy flag as they are configured with the same amount of
|
||||||
.DEVICE (DEVICE)
|
// synchronization stages. Furthermore the ctrlport protocol just allows for
|
||||||
) req_fifo (
|
// one transaction to be active at the same time. A request can only be issued
|
||||||
.reset (rst),
|
// once the response is provided.
|
||||||
.i_aclk (s_ctrlport_clk),
|
handshake #(
|
||||||
.i_tdata (s_req_flat),
|
.WIDTH(REQ_W)
|
||||||
.i_tvalid (s_ctrlport_req_wr | s_ctrlport_req_rd),
|
) req_handshake_inst (
|
||||||
.i_tready (),
|
.clk_a(s_ctrlport_clk),
|
||||||
.o_aclk (m_ctrlport_clk),
|
.rst_a(s_rst),
|
||||||
.o_tdata (m_req_flat),
|
.valid_a((s_ctrlport_req_wr | s_ctrlport_req_rd) & ~s_rst),
|
||||||
.o_tready (1'b1),
|
.data_a(s_req_flat),
|
||||||
.o_tvalid (m_req_flat_valid)
|
.busy_a(),
|
||||||
|
.clk_b(m_ctrlport_clk),
|
||||||
|
.valid_b(m_req_flat_valid),
|
||||||
|
.data_b(m_req_flat)
|
||||||
);
|
);
|
||||||
|
|
||||||
assign {
|
assign {
|
||||||
|
m_ctrlport_req_wr_tmp,
|
||||||
|
m_ctrlport_req_rd_tmp,
|
||||||
|
m_ctrlport_req_addr,
|
||||||
m_ctrlport_req_portid,
|
m_ctrlport_req_portid,
|
||||||
m_ctrlport_req_rem_epid,
|
m_ctrlport_req_rem_epid,
|
||||||
m_ctrlport_req_rem_portid,
|
m_ctrlport_req_rem_portid,
|
||||||
m_ctrlport_req_has_time,
|
|
||||||
m_ctrlport_req_time,
|
|
||||||
m_ctrlport_req_byte_en,
|
|
||||||
m_ctrlport_req_data,
|
m_ctrlport_req_data,
|
||||||
m_ctrlport_req_addr,
|
m_ctrlport_req_byte_en,
|
||||||
m_ctrlport_req_wr_tmp,
|
m_ctrlport_req_has_time,
|
||||||
m_ctrlport_req_rd_tmp
|
m_ctrlport_req_time
|
||||||
} = m_req_flat;
|
} = m_req_flat;
|
||||||
|
|
||||||
assign m_ctrlport_req_wr = m_ctrlport_req_wr_tmp & m_req_flat_valid;
|
assign m_ctrlport_req_wr = m_ctrlport_req_wr_tmp & m_req_flat_valid & ~m_rst;
|
||||||
assign m_ctrlport_req_rd = m_ctrlport_req_rd_tmp & m_req_flat_valid;
|
assign m_ctrlport_req_rd = m_ctrlport_req_rd_tmp & m_req_flat_valid & ~m_rst;
|
||||||
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
@@ -145,20 +154,23 @@ module ctrlport_clk_cross #(
|
|||||||
m_ctrlport_resp_data
|
m_ctrlport_resp_data
|
||||||
};
|
};
|
||||||
|
|
||||||
axi_fifo_2clk #(
|
// Busy flag can be ignored as the request handshake takes at least the same
|
||||||
.WIDTH (RESP_W),
|
// amount of cycles to transfer the request as this handshake instance needs
|
||||||
.SIZE (3),
|
// to release the busy flag as they are configured with the same amount of
|
||||||
.DEVICE (DEVICE)
|
// synchronization stages. Furthermore the ctrlport protocol just allows for
|
||||||
) resp_fifo (
|
// one transaction to be active at the same time. A response can only be
|
||||||
.reset (rst),
|
// issued once the request is available.
|
||||||
.i_aclk (m_ctrlport_clk),
|
handshake #(
|
||||||
.i_tdata (m_resp_flat),
|
.WIDTH(RESP_W)
|
||||||
.i_tvalid (m_ctrlport_resp_ack),
|
) resp_handshake_inst (
|
||||||
.i_tready (),
|
.clk_a(m_ctrlport_clk),
|
||||||
.o_aclk (s_ctrlport_clk),
|
.rst_a(m_rst),
|
||||||
.o_tdata (s_resp_flat),
|
.valid_a(m_ctrlport_resp_ack & ~m_rst),
|
||||||
.o_tready (1'b1),
|
.data_a(m_resp_flat),
|
||||||
.o_tvalid (s_resp_flat_valid)
|
.busy_a(),
|
||||||
|
.clk_b(s_ctrlport_clk),
|
||||||
|
.valid_b(s_resp_flat_valid),
|
||||||
|
.data_b(s_resp_flat)
|
||||||
);
|
);
|
||||||
|
|
||||||
assign {
|
assign {
|
||||||
@@ -167,6 +179,6 @@ module ctrlport_clk_cross #(
|
|||||||
s_ctrlport_resp_data
|
s_ctrlport_resp_data
|
||||||
} = s_resp_flat;
|
} = s_resp_flat;
|
||||||
|
|
||||||
assign s_ctrlport_resp_ack = s_ctrlport_resp_ack_tmp & s_resp_flat_valid;
|
assign s_ctrlport_resp_ack = s_ctrlport_resp_ack_tmp & s_resp_flat_valid & ~s_rst;
|
||||||
|
|
||||||
endmodule
|
endmodule
|
||||||
|
|||||||
Reference in New Issue
Block a user