Interactive documentation

I2S AXI4-Lite IP, explained visually from clock source to serial data

Use the controls to change the clock family, divider mode, sample width, and runtime behaviour. Every derived value updates live so the register map, timing math, and I2S waveform stay connected.

single viewport walkthroughlive derivationscode-synchronised animationwaveform previewhardware ready

What the core does

FS_FAMILYBUFGMUXinternal_mclkdividerCDCserializerDATA

The software-visible CONTROL register chooses the family, mode, sample width, and run state. The audio side uses that selection to create MCLK, BCLK, WS, and the serial payload.

Real-time Visualization

Overall I2S Flow & FS Divide

This live animation shows the internal DDS logic. The accumulator adds the frequency step every system clock. When it overflows, BCLK toggles. DATA bits are shifted out MSB-first based on the bit counter.

Step Value

6,144,000

Architecture Evolution

Cycle Divider: Old vs. New Design

Compare how the clock was generated in the first version versus the latest DDS-optimized version.

Legacy Design

⏱️ Fixed Integer Divider

// Counts 0, 1, 2, 3... 0

bclk_en = (div_count == 4'd3);

How it worked:

  • Used a simple counter (0, 1, 2, 3).
  • Fired exactly every 4 system cycles.
  • Result: Fixed frequencies (44.1k or 48k).
  • Limited: Hard to get non-standard frequencies.
0
1
2
🔥
0
1
2
🔥
Latest Design

🌊 DDS Phase Accumulator

// Accumulates water (step)

bclk_en = (acc >= 100MHz);

How it works now:

  • Uses a 32-bit "Bucket" (Accumulator).
  • Adds any "Step" value every cycle.
  • Result: Arbitrary frequencies (1Hz to 1MHz).
  • Flexible: Change Fs in real-time via software.

The Big Difference: The old design was like a gear that only fits specific teeth. The new DDS design is like a liquid flow that can fill the bucket at any speed, giving usInfinite Flexibility.

Verilog Logic Deep Dive

Code Logic Explorer

Understand how the Verilog code translates into hardware behavior. Select a code block below to see its specific role in the I2S IP core.

"Logic in Verilog is like a factory blueprint. Every line defines a wire or a flip-flop that works in parallel."

i2s_dds_slave_lite.v
1// Section 13: Frequency words (increments per AXI clock cycle)
2wire [31:0] bclk_toggle_rate_hz = {sample_rate_sync, 7'b0}; // Fs × 128
3wire [31:0] mclk_toggle_rate_hz = {sample_rate_sync, 9'b0}; // Fs × 512

🔍 What this does in the hardware

This code calculates how much to 'fill' the clock bucket every cycle. By shifting the sample rate (e.g., 48,000 Hz), we get a large number that represents the target frequency multiplied by a power of 2. Shifting left by 7 is equivalent to multiplying by 128.

Fs (Hz)
48,000
➡️Shift Left 7
6,144,000

Multiplication by shifting wires

✨

Visual Effect

Shows the frequency slider value being shifted left (multiplied) to create the 'Step' value seen in the overall animation.

Section 0

One-Viewport Serializer Walkthrough

This panel follows the real datapath in order: AXI write, CDC, frame latch, WS delay bit, left-slot serialization, WS transition, and right-slot serialization. The motion now tracks the sample itself so you can see when it is still a parallel word, when it is frozen for a frame, and when it finally becomes a single serial DATA bit.

Step 1/8: AXI clock domain

RTL focus

AXI register write

01slv_reg0 <= S_AXI_WDATA; // CPU writes left sample
02cdc_left_stage1_q <= slv_reg0; // first sync flop
03cdc_left_stage2_q <= cdc_left_stage1_q; // second sync flop
04if (bclk_en_w && bit_count_q == 6'd0) sample_left_q <= cdc_left_stage2_q;
05i2s_ws_o <= bit_count_q[5]; // 0 for left slot, 1 for right slot
06if (bit_count_q == 6'd0 || bit_count_q == 6'd32) i2s_data_o <= 1'b0;
07else if (bit_count_q <= g_width) i2s_data_o <= sample_left_q[g_width - bit_count_q];
08else if (bit_count_q > 6'd32 && bit_count_q <= (6'd32 + g_width))
09 i2s_data_o <= sample_right_q[g_width - (bit_count_q - 6'd32)];
10else i2s_data_o <= 1'b0; // pad remaining slot bits with zero

Sample journey

CPU writes DATA_LEFT

bit_count_q0
WS0
DATA0
sample width24 bit
AXI writeCPU updates DATA_LEFT / DATA_RIGHT
CDC stage 1first audio-domain sample
CDC stage 2cleaned destination value
Frame latchfreeze both channels at bit_count = 0
Serializerdelay bit, then MSB-first output
Parallel word in motion0xD4A5C3F0110101001010010111000011...
What DATA pin sees now0
Current slotLeft / WS=0

Frame serializer

Where the parallel word turns into a serial I2S stream

cursorbit_count 0
L delay0
L payload1-24
L pad25-31
R delay32
R payload33-56
R pad57-63
0
Left slotWS = 0, bit_count 0..31
0-30delay
4-71D20
8-110D16
12-150D12
16-191D8
20-230D4
24-271D0
28-310pad
Right slotWS = 1, bit_count 32..63
32-350delay
36-390D20
40-430D16
44-471D12
48-511D8
52-551D4
56-590D0
60-630pad

Why this step matters

The processor updates slv_reg0 immediately, but the serializer does not touch it yet.

Current step

This decouples software timing from the audio bitstream. A write can happen at any AXI cycle without corrupting an in-flight I2S word.

Hardware consequence

WS = 0, DATA = 0, and the active serializer position is bit_count 0. That state is what a logic analyzer or ILA would observe on this exact cycle.

What to learn here

The serializer never streams directly from AXI registers. It waits for a clean CDC transfer and a frame boundary, then emits one delay bit plus MSB-first payload for each channel.

BCLK
0
1
0
1
0
1
0
1
0
1
WS
0
0
0
0
0
1
1
1
1
1
DATA
0
0
0
0
0
0
0
0
0
0

Section 1

Clock generation

FS_FAMILY chooses the clock source before anything else happens. That source then feeds a toggle flop so the final MCLK is always exactly half of internal_mclk.

48 kHz family44.1 kHz family
Step 1

FS_FAMILY = 0 selects audio_48_clk from the BUFGMUX.

Step 2

internal_mclk = 24.576 MHz.

Step 3

mclk_q toggles every cycle, so MCLK = internal_mclk ÷ 2 = 12.288 MHz.

Selected sourceaudio_48_clk

48 kHz family: 24.576 MHz master clock, then divide by 2 for MCLK.

internal_mclk24.576 MHz

That is the clock before the toggle flop divides it down.

mclk_q phase
0

The flip-flop changes state on each edge, so the output period spans two input cycles.

MCLK12.288 MHz

Exactly half of internal_mclk.

mclk_q = 0
mclk_q = 1
MCLK low half
MCLK high half

Section 2

Clock divider and BCLK enable

FS_MODE changes which low bits are inspected. That changes how often bclk_en_w fires, and the divider math then produces BCLK, WS, and Fs.

Step 1

FS_MODE = 0 uses clock_div_q[1:0] == 2'b00, so the enable fires every 4 cycles.

Step 2

bclk_en period = internal_mclk ÷ divider = 6.144 MHz.

Step 3

BCLK = bclk_en_rate ÷ 2 = 3.072 MHz because one full clock needs both a rising and falling edge.

Step 4

WS = BCLK ÷ 64 = 48.000 kHz and Fs = WS = 48.000 kHz.

q = 0000bclk_en_w fires
q = 0011no fire
q = 0102no fire
q = 0113no fire
q = 1004bclk_en_w fires
q = 1015no fire
q = 1106no fire
q = 1117no fire
FS_FAMILYFS_MODEinternal_mclkdividerbclk_en rateBCLKWSFs
0024.576 MHz46.144 MHz3.072 MHz48.0 kHz48.0 kHz
0124.576 MHz212.288 MHz6.144 MHz96.0 kHz96.0 kHz
1022.579 MHz45.645 MHz2.822 MHz44.1 kHz44.1 kHz
1122.579 MHz211.290 MHz5.645 MHz88.2 kHz88.2 kHz

Section 3

Power-on reset

audio_por_q starts at 1111 and shifts a 0 in from the right each cycle. That makes audio_rst stay high for four cycles and then drop low cleanly in the audio clock domain.

audio_rst = 1
audio_por_q[3]1
audio_por_q[2]1
audio_por_q[1]1
audio_por_q[0]1
Why this works

The reset must be generated inside the audio clock domain, not copied directly from S_AXI_ARESETN, because the AXI reset belongs to a different clock domain.

Cycle count

At cycle 0 the register is 1111. By cycle 4 it becomes 0000 and audio_rst drops to 0.

Section 4

CDC synchroniser

A two-stage synchroniser carries a value from the AXI domain into the destination domain. The first flop samples it, the second flop exports it one more cycle later, which improves MTBF and reduces the chance of metastability escaping into the core.

2-cycle propagation
AXI domain0

Value is generated in the AXI/register world.

stage1_q0

First flop captures the asynchronous transition.

stage2_q / dest_out0

Second flop exports a cleaner version into the destination domain.

Step 1

The source value is launched in the AXI domain.

Step 2

stage1_q captures the transition first, then stage2_q follows one cycle later.

Why ASYNC_REG matters

Marking both flip-flops with ASYNC_REG=TRUE tells the tool to keep them adjacent so the routing is short and the synchroniser is more reliable.

Section 5

CONTROL register bitfield

The register is the software contract. Each field is clickable, and the default value 0x00001801 is annotated so you can see exactly which settings are active at reset, including reserved bits that must stay 0.

Default value0x00001801
Decoded defaultENABLE = 1, SAMPLE_WIDTH = 24, RESERVED = 0
Current selectionENABLE

Selected field

ENABLE

Turns the audio engine on or off.

Effect on the audio path

When 0, BCLK, WS, and DATA are held low and the bit counter is reset.

Range and value

bit 0 · default 1

Section 6

Philips I2S frame structure

This is one full 64-bit stereo frame. The left channel occupies cells 0–31, the right channel occupies cells 32–63, and the sample width slider changes where payload ends and padding begins.

162432
Cell rules

pos 0 and pos 32 are the always-zero delay cells. pos 1 is sample[width-1]. pos 2..width count down through the remaining payload bits. Everything after width is padding.

Left slotRight slotDelay bitPayloadPadding
00
11
21
30
41
50
61
70
80
91
100
111
120
130
141
150
161
171
181
190
200
210
220
231
241
250
260
270
280
290
300
310
320
330
341
351
360
371
381
391
400
410
420
431
441
450
461
470
481
491
501
511
521
530
540
550
560
570
580
590
600
610
620
630
pos 0

Always 0, creating the 1 BCLK delay after the WS edge required by Philips I2S.

pos 1

sample[width-1], the MSB of the selected sample word.

pos 2..width

sample[width-pos], continuing MSB-first until the payload is exhausted.

pos > width

0, which is the padding region.

Placeholder

[PHOTO: ILA capture showing BCLK, WS, DATA for one full 64-bit stereo frame at 48 kHz]

Use this slot for a real capture when you want to prove the frame on hardware with the same timing shown above.

Section 7

WS timing

bit_count_q[5] is the clean left/right split. It is 0 for bit_count 0..31 and 1 for bit_count 32..63, so WS changes once per half-frame while DATA changes on the falling BCLK edge.

WS timing

Live waveform preview

This preview tracks the 64-bit stereo frame and highlights the current bit_count_q slot.

Current bit_count_q0
Left channel

bit_count 0..31 means bit[5] = 0, so WS = LOW.

Right channel

bit_count 32..63 means bit[5] = 1, so WS = HIGH.

Section 8

ENABLE and MUTE behaviour

These toggles show what the output pins do in real time. ENABLE stops the whole audio engine, while MUTE keeps the clocks alive and only forces DATA low.

ENABLE = 0

BCLK, WS, and DATA all stay low and bit_count resets to 0.

MUTE = 1

BCLK and WS continue normally, but DATA is forced to 0.

Both normal

ENABLE = 1 and MUTE = 0 produce the full I2S stream.

Output behaviour

Live waveform preview

Watch the same frame preview respond instantly to ENABLE and MUTE.

Current bit_count_q0

Section 9

Full formula summary

This card ties the whole chain together. Pick a family and mode anywhere on the page, then read the selected path all the way down to Fs.

FS_FAMILY→ internal_mclk→ MCLK = internal_mclk ÷ 2→ FS_MODE→ divider→ BCLK = internal_mclk ÷ divider ÷ 2→ WS = BCLK ÷ 64→ Fs = WS
FS_FAMILY = 048 kHz family: 24.576 MHz master clock, then divide by 2 for MCLK.
FS_MODE = 0

Fs = 48.0 kHz

divider 4 · BCLK 3.072 MHz
FS_MODE = 1

Fs = 96.0 kHz

divider 2 · BCLK 6.144 MHz
FS_FAMILY = 144.1 kHz family: 22.5792 MHz master clock, then divide by 2 for MCLK.
FS_MODE = 0

Fs = 44.1 kHz

divider 4 · BCLK 2.822 MHz
FS_MODE = 1

Fs = 88.2 kHz

divider 2 · BCLK 5.645 MHz

Placeholder

[PHOTO: Oscilloscope capture showing MCLK, BCLK, WS, and DATA aligned for one complete stereo frame]

Use a hardware image here when you want the visual summary to match a real board capture.