1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
|
//
// Copyright 2016 Ettus Research
// Copyright 2018 Ettus Research, a National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// - Tracks and fills out header information for an axi stream that is
// asynchronous or does not have a 1:1 input / output ratio.
// - User must pass through **ALL** received words and use the tkeep
// signal to flag which words to keep.
// - This module is not intended to work with decimation / interpolation blocks.
//
// Open design questions:
// - If a tkeep burst occurs between packet boundaries, an internal tlast is
// generated splitting the burst up into two (or more) packets. This is
// an easy way to make sure the packet sizes are bounded and the VITA
// time is correct. Is this desirable, since the downstream block
// will likely want the full burst and is then forced to aggregate packets?
//
module axi_async_stream #(
parameter WIDTH = 32,
parameter HEADER_WIDTH = 128,
parameter HEADER_FIFO_SIZE = 5,
parameter MAX_TICK_RATE = 2**16-1)
(
input clk,
input reset,
input clear,
input [15:0] src_sid,
input [15:0] dst_sid,
input [$clog2(MAX_TICK_RATE)-1:0] tick_rate,
output header_fifo_full,
// From AXI Wrapper
input [WIDTH-1:0] s_axis_data_tdata,
input [HEADER_WIDTH-1:0] s_axis_data_tuser,
input s_axis_data_tlast,
input s_axis_data_tvalid,
output s_axis_data_tready,
// To AXI Wrapper
output [WIDTH-1:0] m_axis_data_tdata,
output [HEADER_WIDTH-1:0] m_axis_data_tuser,
output m_axis_data_tlast,
output m_axis_data_tvalid,
input m_axis_data_tready,
// To User
output [WIDTH-1:0] o_tdata,
output o_tlast,
output o_tvalid,
input o_tready,
// From User
input [WIDTH-1:0] i_tdata,
input i_tlast,
input i_tvalid,
input i_tkeep,
output i_tready
);
wire [WIDTH-1:0] i_reg_tdata;
wire i_reg_tvalid, i_reg_tlast, i_reg_tkeep, i_reg_tready;
reg [WIDTH-1:0] pipe_tdata;
reg pipe_tvalid, pipe_tlast, pipe_tkeep;
wire pipe_tready;
/********************************************************
** Register user input
** - The output logic in some cases needs to wait for
** i_tvalid to assert before asserting i_tready.
** However, users may implement logic that waits for
** i_tready to assert before asserting i_tvalid.
** Without this register, that would result in a
** deadlock.
** - Note: Technically, the user waiting for i_tready
** violates the AXI specification that a master cannot
** wait for ready from the slave. However, it is common
** for users to accidentally break this rule and this is
** a cheap workaround.
********************************************************/
axi_fifo_flop #(.WIDTH(WIDTH+2)) axi_fifo_flop (
.clk(clk), .reset(reset), .clear(clear),
.i_tdata({i_tkeep,i_tlast,i_tdata}), .i_tvalid(i_tvalid), .i_tready(i_tready),
.o_tdata({i_reg_tkeep,i_reg_tlast,i_reg_tdata}), .o_tvalid(i_reg_tvalid), .o_tready(i_reg_tready));
/********************************************************
** Keep track of headers for user
********************************************************/
wire header_in_tready, header_in_tvalid, header_out_tvalid, header_out_tready;
wire [HEADER_WIDTH-1:0] header_in_tdata, header_out_tdata;
reg first_word = 1'b1;
reg [15:0] word_cnt;
reg [16+$clog2(MAX_TICK_RATE)-1:0] time_cnt; // 16 bit payload length + max tick rate increment
wire [63:0] vita_time;
wire [15:0] payload_length;
// Track first word to make sure header is read only once per packet
always @(posedge clk) begin
if (reset | clear) begin
first_word <= 1'b1;
end else begin
if (s_axis_data_tvalid & s_axis_data_tready) begin
if (s_axis_data_tlast) begin
first_word <= 1'b1;
end else if (first_word) begin
first_word <= 1'b0;
end
end
end
end
// Header FIFO
axi_fifo #(.WIDTH(HEADER_WIDTH), .SIZE(HEADER_FIFO_SIZE)) axi_fifo (
.clk(clk), .reset(reset), .clear(clear),
.i_tdata(header_in_tdata), .i_tvalid(header_in_tvalid), .i_tready(header_in_tready),
.o_tdata(header_out_tdata), .o_tvalid(header_out_tvalid), .o_tready(header_out_tready),
.space(), .occupied());
assign header_in_tdata = s_axis_data_tuser;
assign header_in_tvalid = s_axis_data_tvalid & o_tready & first_word;
assign header_out_tready = i_reg_tvalid & i_reg_tready & (word_cnt >= payload_length);
assign header_fifo_full = ~header_in_tready;
// Track VITA time offset and word count for emptying header FIFO
always @(posedge clk) begin
if (reset | clear) begin
word_cnt <= WIDTH/8;
time_cnt <= 0;
end else begin
if (pipe_tvalid & pipe_tready) begin
if (word_cnt >= payload_length) begin
word_cnt <= WIDTH/8;
time_cnt <= 0;
end else begin
word_cnt <= word_cnt + WIDTH/8;
time_cnt <= time_cnt + tick_rate;
end
end
end
end
// Form output header
cvita_hdr_decoder cvita_hdr_decoder (
.header(header_out_tdata),
.pkt_type(), .eob(), .has_time(),
.seqnum(), .payload_length(payload_length),
.src_sid(), .dst_sid(),
.vita_time(vita_time));
cvita_hdr_modify cvita_hdr_modify (
.header_in(header_out_tdata),
.header_out(m_axis_data_tuser),
.use_pkt_type(1'b0), .pkt_type(),
.use_has_time(1'b0), .has_time(),
.use_eob(1'b0), .eob(),
.use_seqnum(1'b0), .seqnum(), // AXI Wrapper handles this
.use_length(1'b0), .length(), // AXI Wrapper handles this
.use_payload_length(1'b0), .payload_length(),
.use_src_sid(1'b1), .src_sid(src_sid),
.use_dst_sid(1'b1), .dst_sid(dst_sid),
.use_vita_time(1'b1), .vita_time(vita_time + time_cnt));
/********************************************************
** Data to user from AXI Wrapper
** - Throttles if header FIFO is full
********************************************************/
assign o_tdata = s_axis_data_tdata;
assign o_tvalid = s_axis_data_tvalid & header_in_tready;
assign o_tlast = s_axis_data_tlast;
assign s_axis_data_tready = o_tready & header_in_tready;
/********************************************************
** Data from user to AXI Wrapper
** - Handles asserting tlast
** - Asserts tlast in three cases:
** 1) User asserts tlast
** 2) End of a burst of samples (i.e. when tkeep deasserts).
** 3) End of a packet, in case VITA is different between packets
********************************************************/
wire ready;
always @(posedge clk) begin
if (reset | clear) begin
pipe_tdata <= 'd0;
pipe_tvalid <= 1'b0;
pipe_tlast <= 1'b0;
pipe_tkeep <= 1'b0;
end else begin
if (pipe_tready) begin
pipe_tdata <= i_reg_tdata;
pipe_tvalid <= i_reg_tvalid;
pipe_tlast <= i_reg_tlast;
pipe_tkeep <= i_reg_tkeep;
end
end
end
assign pipe_tready = ~pipe_tvalid | (m_axis_data_tready & header_out_tvalid & (i_reg_tvalid | (m_axis_data_tvalid & m_axis_data_tlast)));
assign i_reg_tready = pipe_tready;
assign m_axis_data_tdata = pipe_tdata;
assign m_axis_data_tvalid = pipe_tvalid & pipe_tkeep & (i_reg_tvalid | m_axis_data_tlast) & header_out_tvalid;
assign m_axis_data_tlast = pipe_tlast | (i_reg_tvalid & ~i_reg_tkeep) | (word_cnt >= payload_length);
endmodule
|