-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathtx_sample_buffer.vhd
More file actions
418 lines (361 loc) · 18.5 KB
/
Copy pathtx_sample_buffer.vhd
File metadata and controls
418 lines (361 loc) · 18.5 KB
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
library ieee;
use ieee.std_logic_1164.all;
use IEEE.NUMERIC_STD.ALL;
use work.audioclks_pkg.all;
entity tx_sample_buffer is
generic
(
samples_per_channel_depth : integer := 64; -- number of samples per channel to buffer (power of two: media-clock-derived pointer)
global_channel_count : integer := 16; -- number of channels to buffer
bytes_per_sample : integer := 3; -- wire bytes per sample (RTP L24 = 3). Internal RAM slot is 4.
ENABLE_METERING: boolean := true;
-- TDM serial input frontend (symmetric to rx_ringbuffer's TDM output).
-- TDM_INPUT=false: legacy parallel audio_in bus.
-- TDM_INPUT=true : serial TDM demux integrated here; audio_in unused.
TDM_INPUT : boolean := false;
TDM_INPUTS : integer := 2; -- number of serial TDM data pins
TDM_CONFIG : t_audio_clock_io_cfg;
-- Simulation-only backdoor into sample_ram (see rx_ringbuffer). Disabled
-- / optimized away in synthesis.
SIM_SAMPLE_RAM_BACKDOOR : boolean := false
);
port
(
sys_clk : in std_logic;
reset_n : in std_logic;
audio_in : in STD_LOGIC_VECTOR((bytes_per_sample * 8) * global_channel_count - 1 downto 0) := (others => '0');
fs_clk_i : in std_logic;
bclk_sync_i : in std_logic := '0'; -- TDM bit clock (only used when TDM_INPUT=true)
tdm_in : in std_logic_vector(TDM_INPUTS - 1 downto 0) := (others => '0');
-- PTP media clock (same net as the transmitter/rx use). The write sample
-- base is derived from this so the write position is media-clock-anchored
-- exactly like the transmitter read pointer -- they cannot drift even
-- though the fs (NCO) and media-clock edges are not simultaneous.
media_clock_i : in std_logic_vector(31 downto 0) := (others => '0');
-- 50%-duty LR clock. The media clock is ONLY stable/correct around this
-- clock's falling edge (fs/NCO is not synchronous with the media clock),
-- so media_clock_i is latched there -- the same edge tx_router uses for
-- packet timestamps. Do NOT sample media_clock_i on the fs_clk_i edge.
fs_halfduty_clk_i : in std_logic := '0';
wr_ready_o : out std_logic := '0';
read0Addr : in unsigned(15 downto 0);
data0_out : out std_logic_vector(7 downto 0); -- 8 bit
metering_signal_o : out std_logic_vector(global_channel_count - 1 downto 0);
metering_clip_o : out std_logic_vector(global_channel_count - 1 downto 0);
metering_clear_i : in std_logic;
-- ===== Simulation backdoor into sample_ram (see SIM_SAMPLE_RAM_BACKDOOR) =====
dbg_wr_en_i : in std_logic := '0';
dbg_wr_addr_i : in unsigned(15 downto 0) := (others => '0');
dbg_wr_data_i : in std_logic_vector(7 downto 0) := (others => '0');
dbg_rd_addr_i : in unsigned(15 downto 0) := (others => '0');
dbg_rd_data_o : out std_logic_vector(7 downto 0)
);
end entity;
architecture Behavioral of tx_sample_buffer is
-- ceil(log2(n)) (same helper as rx_ringbuffer)
function clog2(n : positive) return natural is
variable result : natural := 0;
variable val : natural := n - 1;
begin
while val > 0 loop
result := result + 1;
val := val / 2;
end loop;
return result;
end function;
-- Internal RAM slot = 4 bytes per sample (pad 24-bit -> 32-bit).
constant SLOT_BYTES : integer := 4;
constant CHANNEL_STRIDE : integer := SLOT_BYTES; -- 4
constant SAMPLE_STRIDE : integer := global_channel_count * SLOT_BYTES; -- ch * 4
constant SAMPLE_SHIFT : integer := clog2(SAMPLE_STRIDE); -- log2(ch*4)
-- Single (non-doubled) buffer, exactly 2**ADDR_BITS like rx_ringbuffer. The
-- write pointer is no longer exported; both the write sample base (here) and
-- the transmitter read pointer (tx_router) are derived from the media clock,
-- so the read window sits a fixed samples_per_packet behind the write head
-- with no overrun headroom needed. samples_per_channel_depth MUST be a power
-- of two (asserted below) so the low SAMPLE_IDX_BITS of the media clock index
-- the sample row directly.
constant AUDIO_BUFFER_LENGTH : integer := samples_per_channel_depth * global_channel_count * SLOT_BYTES;
constant ADDR_BITS : integer := clog2(AUDIO_BUFFER_LENGTH);
constant SAMPLE_IDX_BITS : integer := ADDR_BITS - SAMPLE_SHIFT; -- = clog2(samples_per_channel_depth)
type t_sample_ram is array (0 to AUDIO_BUFFER_LENGTH - 1) of std_logic_vector(7 downto 0);
signal sample_ram : t_sample_ram := (others => (others => '0'));
signal sample_wr_ptr : integer range 0 to AUDIO_BUFFER_LENGTH - 1 := 0;
signal media_clock_latch: unsigned(SAMPLE_IDX_BITS - 1 downto 0) := (others => '0');
signal media_clock_latch2: unsigned(SAMPLE_IDX_BITS - 1 downto 0) := (others => '0');
signal zaudio_sync : std_logic := '0';
signal zbclk : std_logic := '0';
-- Sample base byte address derived from the media clock latched on the falling
-- edge of the 50%-duty LR clock, where the media clock is stable (see
-- fs_halfduty_clk_i). Both capture frontends use THIS, not live media_clock_i.
-- = low SAMPLE_IDX_BITS of (media+1), shifted left by SAMPLE_SHIFT (no mod/mult).
signal fs_halfduty_sync : std_logic := '0';
signal wr_sample_base : integer range 0 to AUDIO_BUFFER_LENGTH - 1 := 0;
signal current_channel_id : integer range 0 to global_channel_count - 1 := 0;
signal write_active : std_logic := '0';
signal byte_count : integer range 0 to bytes_per_sample - 1 := 0;
-- Snapshot of audio_in taken atomically on the fs edge (parallel path only).
signal audio_in_latched : std_logic_vector(audio_in'range) := (others => '0');
-- Common RAM write port (driven by whichever input frontend is active)
signal ram_wr_en : std_logic := '0';
signal ram_wr_data : std_logic_vector(7 downto 0) := (others => '0');
signal ram_wr_addr : integer range 0 to AUDIO_BUFFER_LENGTH - 1 := 0;
signal wr_ready_pending : std_logic := '0';
-- Metering CDC
signal metering_clear_i_sync1 : std_logic := '0';
signal metering_clear_i_sync2 : std_logic := '0';
signal metering_clear_last : std_logic := '0';
constant SAMPLE_BITS : integer := bytes_per_sample * 8;
constant CMP_BITS : integer := 9;
constant CLIP_THRESHOLD : unsigned(CMP_BITS - 1 downto 0) := to_unsigned(16#064#, CMP_BITS);
constant SIGNAL_THRESHOLD : unsigned(CMP_BITS - 1 downto 0) := to_unsigned(16#001#, CMP_BITS);
signal metering_ch_id : integer range 0 to global_channel_count - 1;
signal metering_sample_reg : std_logic_vector(SAMPLE_BITS - 1 downto 0) := (others => '0');
signal metering_ch_id_reg : integer range 0 to global_channel_count - 1 := 0;
signal metering_valid_reg : std_logic := '0';
-- ===== TDM demux frontend signals (TDM_INPUT=true) =====
-- bit/slot counters, clocked on bclk edges, reset on fs edge.
signal tdm_bit_counter : unsigned(clog2(TDM_CONFIG.tdm_channels * SLOT_BYTES * 8) - 1 downto 0) := (others => '0'); -- 0..31 within a slot
type t_tdm_shift is array (0 to TDM_INPUTS - 1) of std_logic_vector(7 downto 0);
signal tdm_shift : t_tdm_shift := (others => (others => '0'));
-- demux write FSM
type t_demux_state is (ds_idle, ds_write);
signal demux_state : t_demux_state := ds_idle;
signal demux_pin : integer range 0 to TDM_INPUTS := 0;
signal tdm_in_sync : STD_LOGIC_VECTOR(TDM_INPUTS - 1 downto 0);
signal tdm_in_sync2 : STD_LOGIC_VECTOR(TDM_INPUTS - 1 downto 0);
begin
assert 2 ** clog2(samples_per_channel_depth) = samples_per_channel_depth
report "tx_sample_buffer: samples_per_channel_depth must be a power of two"
severity failure;
process (sys_clk, reset_n)
begin
if (reset_n = '0') then
metering_clear_i_sync1 <= '0';
metering_clear_i_sync2 <= '0';
zaudio_sync <= '0';
zbclk <= '0';
fs_halfduty_sync <= '0';
--wr_sample_base <= 0;
elsif (rising_edge(sys_clk)) then
metering_clear_i_sync1 <= metering_clear_i;
metering_clear_i_sync2 <= metering_clear_i_sync1;
zaudio_sync <= fs_clk_i;
zbclk <= bclk_sync_i;
fs_halfduty_sync <= fs_halfduty_clk_i;
if (fs_halfduty_clk_i = '0' and fs_halfduty_sync = '1') then
media_clock_latch <= unsigned(media_clock_i(SAMPLE_IDX_BITS - 1 downto 0));
end if;
if (fs_halfduty_clk_i = '1' and fs_halfduty_sync = '0') then
media_clock_latch2 <= media_clock_latch + 1;
end if;
tdm_in_sync <= tdm_in;
tdm_in_sync2 <= tdm_in_sync;
end if;
end process;
-- =========================================================================
-- PARALLEL INPUT FRONTEND (TDM_INPUT = false)
-- =========================================================================
parallel_in_gen: if (TDM_INPUT = false) generate
wr_sample_base <= to_integer(
media_clock_latch2
& to_unsigned(0, SAMPLE_SHIFT));
-- metering process (operates on audio_in)
metering_proc_gen: if (ENABLE_METERING = true) generate
process (sys_clk, reset_n)
variable metering_active : std_logic := '0';
variable v_sample_top : unsigned(CMP_BITS - 1 downto 0);
begin
if (reset_n = '0') then
metering_active := '0';
metering_ch_id <= 0;
metering_sample_reg <= (others => '0');
metering_ch_id_reg <= 0;
metering_valid_reg <= '0';
metering_clear_last <= '0';
elsif (rising_edge(sys_clk)) then
if (zaudio_sync = '0' and fs_clk_i = '1') then
metering_active := '1';
end if;
if (metering_clear_i_sync2 /= metering_clear_last) then
metering_clear_last <= metering_clear_i_sync2;
metering_clip_o <= (others => '0');
metering_signal_o <= (others => '0');
end if;
metering_valid_reg <= '0';
if (metering_active = '1') then
if (metering_ch_id = global_channel_count - 1) then
metering_active := '0';
end if;
metering_sample_reg <=
audio_in((SAMPLE_BITS * (metering_ch_id + 1) - 1) downto (SAMPLE_BITS * metering_ch_id));
metering_ch_id_reg <= metering_ch_id;
if byte_count = 0 then
metering_valid_reg <= '1';
end if;
metering_ch_id <= metering_ch_id + 1;
end if;
if (metering_valid_reg = '1') then
v_sample_top := unsigned(metering_sample_reg(SAMPLE_BITS - 2 downto SAMPLE_BITS - 1 - CMP_BITS));
if metering_sample_reg(SAMPLE_BITS - 1) = '1' then
v_sample_top := not v_sample_top;
end if;
if v_sample_top >= CLIP_THRESHOLD then
metering_clip_o(metering_ch_id_reg) <= '1';
end if;
if v_sample_top >= SIGNAL_THRESHOLD then
metering_signal_o(metering_ch_id_reg) <= '1';
end if;
end if;
end if;
end process;
end generate;
metering_proc_disable_gen: if (ENABLE_METERING = false) generate
metering_signal_o <= (others => '0');
metering_clip_o <= (others=> '0');
end generate;
-- Capture FSM: parallel audio_in -> RAM with 4-byte slots.
process(sys_clk, reset_n)
begin
if reset_n = '0' then
sample_wr_ptr <= 0;
current_channel_id <= 0;
write_active <= '0';
byte_count <= 0;
wr_ready_o <= '0';
wr_ready_pending <= '0';
ram_wr_en <= '0';
ram_wr_data <= (others => '0');
ram_wr_addr <= 0;
audio_in_latched <= (others => '0');
elsif rising_edge(sys_clk) then
wr_ready_o <= wr_ready_pending;
wr_ready_pending <= '0';
ram_wr_en <= '0';
if (zaudio_sync = '0' and fs_clk_i = '1') then
audio_in_latched <= audio_in;
write_active <= '1';
sample_wr_ptr <= wr_sample_base;
end if;
if write_active = '1' then
ram_wr_data <= audio_in_latched((SAMPLE_BITS * current_channel_id) + (SAMPLE_BITS - 1 - byte_count*8)
downto (SAMPLE_BITS * current_channel_id) + (SAMPLE_BITS - 8 - byte_count*8));
ram_wr_addr <= sample_wr_ptr + current_channel_id * CHANNEL_STRIDE + byte_count;
ram_wr_en <= '1';
if byte_count = bytes_per_sample - 1 then
byte_count <= 0;
if current_channel_id = global_channel_count - 1 then
current_channel_id <= 0;
-- No pointer increment: the next sample's base is
-- re-latched from the media clock on the next fs edge.
write_active <= '0';
wr_ready_pending <= '1';
else
current_channel_id <= current_channel_id + 1;
end if;
else
byte_count <= byte_count + 1;
end if;
end if;
end if;
end process;
end generate;
-- =========================================================================
-- TDM SERIAL INPUT FRONTEND (TDM_INPUT = true)
-- =========================================================================
tdm_in_gen: if (TDM_INPUT = true) generate
-- ----- bclk-domain (sampled into sys_clk via edge detect) bit/slot counters -----
tdm_ctrl_proc: process(bclk_sync_i, reset_n)
begin
if reset_n = '0' then
tdm_bit_counter <= (others => '0');
tdm_shift <= (others => (others => '0'));
elsif (rising_edge(bclk_sync_i) and TDM_CONFIG.data_is_valid_on_rising_bclk_edge = true) or (falling_edge(bclk_sync_i) and TDM_CONFIG.data_is_valid_on_rising_bclk_edge = false) then
-- bclk falling edge: sample one bit per pin
--if (bclk_sync_i = '0' and zbclk = '1') then
tdm_bit_counter <= tdm_bit_counter + 1;
for p in 0 to TDM_INPUTS - 1 loop
tdm_shift(p) <= tdm_shift(p)(6 downto 0) & tdm_in_sync2(p);
end loop;
--end if;
-- frame sync: restart at channel 0 / bit 0
--if (fs_clk_i = '1' and zaudio_sync = '0') then
if (fs_clk_i = '1') then
tdm_bit_counter <= (others => '0');
end if;
end if;
end process;
ram_wr_addr <= to_integer(
media_clock_latch2 -- time index
& (tdm_bit_counter(tdm_bit_counter'length -1 downto 5) -- channel index per tdm
+ (demux_pin * TDM_CONFIG.tdm_channels)) -- add the corresponding demux offset
& tdm_bit_counter(4 downto 3)); -- byte index
tdm_write_proc: process(sys_clk, reset_n)
begin
if reset_n = '0' then
demux_pin <= 0;
ram_wr_en <= '0';
ram_wr_data <= (others => '0');
demux_state <= ds_idle;
write_active <= '0';
elsif rising_edge(sys_clk) then
ram_wr_en <= '0';
case demux_state is
when ds_idle =>
if (tdm_bit_counter(2 downto 0) = "111") then
if (write_active = '0') then
demux_state <= ds_write;
demux_pin <= 0;
end if;
write_active <= '1';
else
write_active <= '0';
end if;
when ds_write =>
ram_wr_data <= tdm_shift(demux_pin);
ram_wr_en <= '1';
if demux_pin = TDM_INPUTS - 1 then
demux_state <= ds_idle;
else
demux_pin <= demux_pin + 1;
end if;
end case;
end if;
end process;
metering_signal_o <= (others => '0');
metering_clip_o <= (others => '0');
end generate;
-- =========================================================================
-- Sample RAM: registered read (block RAM), registered write + sim backdoor
-- =========================================================================
process(sys_clk)
begin
if rising_edge(sys_clk) then
data0_out <= sample_ram(to_integer(read0Addr));
end if;
end process;
process (sys_clk)
begin
if (rising_edge(sys_clk)) then
if (ram_wr_en = '1') then
sample_ram(ram_wr_addr) <= ram_wr_data;
end if;
-- Simulation backdoor write (testbench only); takes priority.
if SIM_SAMPLE_RAM_BACKDOOR and dbg_wr_en_i = '1' then
sample_ram(to_integer(dbg_wr_addr_i)) <= dbg_wr_data_i;
end if;
end if;
end process;
-- Simulation backdoor read port (testbench only).
sim_backdoor_rd_gen : if SIM_SAMPLE_RAM_BACKDOOR generate
process(sys_clk)
begin
if rising_edge(sys_clk) then
dbg_rd_data_o <= sample_ram(to_integer(dbg_rd_addr_i));
end if;
end process;
end generate;
sim_backdoor_rd_off_gen : if not SIM_SAMPLE_RAM_BACKDOOR generate
dbg_rd_data_o <= (others => '0');
end generate;
end Behavioral;