Modules have to talk to each other, and a design falls apart quickly if every pair invents its own way to do it. Modern FPGA designs settle on a small handshake called AXI-Stream, the streaming member of the Advanced eXtensible Interface family, and once it is second nature the blocks in a system snap together. The whole protocol for moving a stream of data is two signals plus the data, and understanding the one rule that governs them is most of what there is to know.
The valid and ready handshake
A stream flows from a source to a sink over three things, the data
itself, a valid signal the source raises when the data is
real, and a ready signal the sink raises when it can
accept. A transfer happens on exactly the cycles where both
valid and ready are high on the same edge.
When the source has nothing it drops valid. When the sink
is busy it drops ready, which is called backpressure and
makes the source wait. This single mechanism lets fast and slow blocks
connect without either overrunning the other, and it is why a whole
pipeline can be assembled from parts that each run at their own pace.
There is one rule that keeps the handshake lossless, and violating it
is the classic AXI-Stream bug. Once a source raises valid
it must hold both valid and the data steady until it sees
ready, so it may not offer a word, get stalled, and then
change its mind or swap the data. A transfer that is offered must
remain on the table until it is taken. Every correct AXI-Stream block
obeys this, and every stream utility exists to obey it while doing
something useful in between.
The skid buffer, pipelining a stream
The problem a register slice solves is small but everywhere. To hit a
high clock frequency you want to register the signals crossing between
two blocks, cutting the long path between them. Registering the data
and valid going forward is easy. The trouble is
ready, which flows backward, so registering it adds a
cycle of delay before the source hears that the sink has stalled, and
in that cycle the source may already have sent a word that now has
nowhere to go. A skid buffer solves this by holding one extra register,
the skid, that catches exactly that in-flight word so nothing is lost,
while still registering the outputs and keeping full throughput when
the stream runs freely.
axis_skid_buffer.svmodule axis_skid_buffer #(
parameter int W = 8
)(
input logic clk,
input logic rst,
input logic [W-1:0] s_data, // upstream (slave) port
input logic s_valid,
output logic s_ready,
output logic [W-1:0] m_data, // downstream (master) port
output logic m_valid,
input logic m_ready
);
logic [W-1:0] skid_data;
logic skid_valid;
logic [W-1:0] m_data_r;
logic m_valid_r;
assign s_ready = !skid_valid; // room while the skid is empty
assign m_valid = m_valid_r;
assign m_data = m_data_r;
always_ff @(posedge clk) begin
if (rst) begin
m_valid_r <= 1'b0; skid_valid <= 1'b0;
end else if (m_ready || !m_valid_r) begin
// output slot is free to advance, take the skid first if it holds a beat
if (skid_valid) begin
m_data_r <= skid_data; m_valid_r <= 1'b1; skid_valid <= 1'b0;
end else if (s_valid && s_ready) begin
m_data_r <= s_data; m_valid_r <= 1'b1;
end else begin
m_valid_r <= 1'b0;
end
end else if (s_valid && s_ready) begin
// output is stalled, park the incoming beat in the skid register
skid_data <= s_data; skid_valid <= 1'b1;
end
end
endmoduleThe design accepts an input word as long as the skid is empty, which
is what s_ready = !skid_valid says. When the downstream
sink stalls with a word already at the output, one more word can still
arrive, and it goes into the skid rather than being dropped. When the
output frees up, the skid feeds it before any new input, preserving
order. The testbench in the
repository
drives the input with random gaps and the output with random stalls,
two thousand beats in all, and checks every word comes out exactly
once and in order under every backpressure pattern. This little block
is the glue that lets a design register its interfaces freely without
ever thinking about losing data.
With a compute tile, memory, pipelining, and a way to connect blocks, the pieces are in place to build something whole. The next page is the capstone, a matrix-multiply engine assembled from what came before.