Extend I2S capabilities

This commit:
* adds the support for I2S standard mode,
* extends I2S left justified mode,
* allows to configure sample size for tx/rx in 1-32 bits range,
* implements I2S master mode,
* allows to concatenate channels or used the padded mode.

This required to rework the FSM.
This commit is contained in:
Pawel Sagan 2020-03-12 14:54:41 +01:00 committed by Mateusz Holenko
parent 6fedaa704b
commit ce49990084
2 changed files with 238 additions and 79 deletions

View File

@ -1,36 +1,56 @@
# This file is Copyright (c) 2020 bunnie <bunnie@kosagi.com>
# This file is Copyright (c) 2020 Antmicro <www.antmicro.com>
# License: BSD
from migen.genlib.cdc import MultiReg
from litex.soc.cores.clock import *
from litex.soc.interconnect import wishbone
from litex.soc.interconnect.csr_eventmanager import *
from litex.soc.integration.doc import AutoDoc, ModuleDoc
from enum import Enum
import math
class I2S_FORMAT(Enum):
I2S_STANDARD = 1
I2S_LEFT_JUSTIFIED = 2
class S7I2SSlave(Module, AutoCSR, AutoDoc):
def __init__(self, pads, fifo_depth=256):
class S7I2S(Module, AutoCSR, AutoDoc):
def __init__(self, pads, fifo_depth=256, master=False, concatenate_channels=True, sample_width=16, frame_format=I2S_FORMAT.I2S_LEFT_JUSTIFIED, lrck_ref_freq=100e6, lrck_freq=44100, bits_per_channel=28):
self.intro = ModuleDoc("""Intro
I2S slave creates a slave audio interface instance. Tx and Rx interfaces are inferred based
upon the presence or absence of the respective pins in the "pads" argument.
I2S master/slave creates a master/slave audio interface instance depending on a configured master variable.
Tx and Rx interfaces are inferred based upon the presence or absence of the respective pins in the "pads" argument.
When device is configured as master you can manipulate LRCK and SCLK signals using below variables.
- lrck_ref_freq - is a reference signal that is required to achive desired LRCK and SCLK frequencies.
Have be the same as your sys_clk.
- lrck_freq - this variable defines requested LRCK frequency. Mind you, that based on sys_clk frequency,
configured value will be more or less acurate.
- bits_per_channel - defines SCLK frequency. Mind you, that based on sys_clk frequency,
the requested amount of bits per channel may vary from configured.
When device is configured as slave I2S interface, sampling rate and framing is set by the
programming of the audio CODEC chip. A slave configuration defers the
generation of audio clocks to the CODEC, which has PLLs specialized to generate the correct
frequencies for audio sampling rates.
The interface is I2S-like, but note the deviation that the bits are justified left without a
1-bit pad after sync edges. This isn't a problem for talking to the LM49352 codec this was
designed for, as the bit offset is programmable, but this will not work well if are talking
to a CODEC without a programmable bit offset!
I2S core supports two formats: standard and left-justified.
- Standard format requires a device to receive and send data with one bit offset for both channels.
Left channel begins with low signal on LRCK.
- Left justified format requires from device to receive and send data without any bit offset for both channels.
Left channel begins with high signal on LRCK.
Sample width can be any of 1 to 32 bits.
When sample_width is less than or equal to 16-bit and concatenate_channels is enabled,
sending and reciving channels is performed atomically. eg. both samples are transfered from/to fifo in single operation.
System Interface
----------------
Audio interchange is done with the system using 16-bit stereo samples, with the right channel
mapped to the least significant word of a 32-bit word. Thus each 32-bit word is a single
stereo sample. As this is a slave I2S interface, sampling rate and framing is set by the
programming of the audio CODEC chip. A slave situation is preferred because this defers the
generation of audio clocks to the CODEC, which has PLLs specialized to generate the correct
frequencies for audio sampling rates.
`fifo_depth` is the depth at which either a read interrupt is fired (guaranteeing at least
`fifo_depth` stereo samples in the receive FIFO) or a write interrupt is fired (guaranteeing
at least `fifo_depth` free space in the transmit FIFO). The maximum depth is 512.
@ -84,9 +104,8 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
- Data is updated on the falling edge
- Data is sampled on the rising edge
- Words are MSB-to-LSB, left-justified (**NOTE: this is a deviation from strict I2S, which
offsets by 1 from the left**)
- Sync is an input (FPGA is slave, codec is master): low => left channel, high => right channel
- Words are MSB-to-LSB,
- Sync is an input or output based on configure mode,
- Sync can be longer than the wordlen, extra bits are just ignored
- Tx is data to the codec (SDI pin on LM49352)
- Rx is data from the codec (SDO pin on LM49352)
@ -100,6 +119,9 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
if fifo_depth < 8:
fifo_depth = 8
print("I2S warning: fifo depth less than 8 selected; truncating to 8")
if sample_width > 32:
sample_width = 32
print("I2S warning: sample width greater than 32 bits. truncating to 32")
# Connect pins, synchronizers, and edge detectors
if hasattr(pads, 'tx'):
@ -108,6 +130,15 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
if hasattr(pads, 'rx'):
rx_pin = Signal()
self.specials += MultiReg(pads.rx, rx_pin)
fifo_data_width = sample_width
if concatenate_channels:
if sample_width <= 16:
fifo_data_width = sample_width * 2
else:
concatenate_channels = False
print("I2S warning: sample width greater than 16 bits. your channels can't be glued")
sync_pin = Signal()
self.specials += MultiReg(pads.sync, sync_pin)
@ -123,7 +154,7 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
self.bus = bus = wishbone.Interface()
rd_ack = Signal()
wr_ack = Signal()
self.comb +=[
self.comb += [
If(bus.we,
bus.ack.eq(wr_ack),
).Else(
@ -131,6 +162,34 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
)
]
if master == True:
if bits_per_channel < sample_width and frame_format == I2S_FORMAT.I2S_STANDARD:
bits_per_channel = sample_width + 1
print("I2S warning: bits per channel can't be smaller than sample_width. Setting bits per channel to {}".format(sample_width + 1))
# implementing LRCK signal
lrck_period = int(lrck_ref_freq / (lrck_freq * 2))
lrck_counter = Signal(16)
self.sync += [
If((lrck_counter == lrck_period),
lrck_counter.eq(0),
pads.sync.eq(~pads.sync),
).Else(
lrck_counter.eq(lrck_counter + 1)
)
]
# implementing SCLK signal
sclk_period = int(lrck_period / (bits_per_channel * 2))
sclk_counter = Signal(16)
self.sync += [
If((sclk_counter == sclk_period),
sclk_counter.eq(0),
pads.clk.eq(~pads.clk),
).Else(
sclk_counter.eq(sclk_counter + 1)
)
]
# Interrupts
self.submodules.ev = EventManager()
if hasattr(pads, 'rx'):
@ -141,10 +200,9 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
self.ev.tx_error = EventSourcePulse(description="Indicates a Tx error has happened (over/underflow")
self.ev.finalize()
# build the RX subsystem
if hasattr(pads, 'rx'):
rx_rd_d = Signal(32)
rx_rd_d = Signal(fifo_data_width)
rx_almostfull = Signal()
rx_almostempty = Signal()
rx_full = Signal()
@ -154,7 +212,7 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
rx_wrerr = Signal()
rx_wrcount = Signal(9)
rx_rden = Signal()
rx_wr_d = Signal(32)
rx_wr_d = Signal(fifo_data_width)
rx_wren = Signal()
self.rx_ctl = CSRStorage(description="Rx data path control",
@ -164,13 +222,20 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
])
self.rx_stat = CSRStatus(description="Rx data path status",
fields=[
CSRField("overflow", size=1, description="Rx overflow"),
CSRField("overflow", size=1, description="Rx overflow"),
CSRField("underflow", size=1, description="Rx underflow"),
CSRField("dataready", size=1, description="{} words of data loaded and ready to read".format(fifo_depth)),
CSRField("empty", size=1, description="No data available in FIFO to read"), # next flags probably never used
CSRField("wrcount", size=9, description="Write count"),
CSRField("rdcount", size=9, description="Read count"),
CSRField("fifo_depth", size=9, description="FIFO depth as synthesized")
CSRField("fifo_depth", size=9, description="FIFO depth as synthesized"),
CSRField("concatenate_channels", size=1, reset=concatenate_channels, description="Receive and send both channels atomically")
])
self.rx_conf = CSRStatus(description="Rx configuration",
fields=[
CSRField("format", size=2, reset=frame_format.value, description="I2S sample format. {} is left-justified, {} is I2S standard".format(I2S_FORMAT.I2S_LEFT_JUSTIFIED, I2S_FORMAT.I2S_STANDARD)),
CSRField("sample_width", size=6, reset=sample_width, description="Single sample width"),
CSRField("lrck_freq", size=24, reset=lrck_freq, description="Audio sampling rate frequency"),
])
self.comb += self.rx_stat.fields.fifo_depth.eq(fifo_depth)
@ -189,11 +254,12 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
)
)
]
# At a width of 32 bits, an 18kiB fifo is 512 entries deep
self.specials += Instance("FIFO_SYNC_MACRO",
p_DEVICE = "7SERIES",
p_FIFO_SIZE = "18Kb",
p_DATA_WIDTH = 32,
p_DATA_WIDTH = fifo_data_width,
p_ALMOST_EMPTY_OFFSET = 8,
p_ALMOST_FULL_OFFSET = (512 - fifo_depth),
p_DO_REG = 0,
@ -213,7 +279,6 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
o_WRERR = rx_wrerr,
)
self.comb += [ # Wire up the status signals and interrupts
self.rx_stat.fields.overflow.eq(rx_wrerr),
self.rx_stat.fields.underflow.eq(rx_rderr),
self.rx_stat.fields.dataready.eq(rx_almostfull),
self.rx_stat.fields.wrcount.eq(rx_wrcount),
@ -244,23 +309,33 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
),
rd_ack.eq(rd_ack_pipe),
]
rx_cnt_width = math.ceil(math.log(fifo_data_width,2))
rx_cnt = Signal(rx_cnt_width)
rx_delay_cnt = Signal()
rx_delay_val = 1 if frame_format == I2S_FORMAT.I2S_STANDARD else 0
rx_cnt = Signal(5)
self.submodules.rxi2s = rxi2s = FSM(reset_state="IDLE")
rxi2s.act("IDLE",
NextValue(rx_wr_d, 0),
If(self.rx_ctl.fields.enable,
# Wait_sync guarantees we start at the beginning of a left frame, and not in
# the middle
If(rising_edge & sync_pin,
NextState("WAIT_SYNC")
If(rising_edge & (~sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else sync_pin),
NextState("WAIT_SYNC"),
NextValue(rx_delay_cnt, rx_delay_val)
)
)
),
rxi2s.act("WAIT_SYNC",
If(rising_edge & ~sync_pin,
NextState("LEFT"),
NextValue(rx_cnt, 16)
If(rising_edge & (~sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else sync_pin),
If(rx_delay_cnt > 0,
NextValue(rx_delay_cnt, rx_delay_cnt - 1),
NextState("WAIT_SYNC")
).Else(
NextState("LEFT"),
NextValue(rx_delay_cnt, rx_delay_val),
NextValue(rx_cnt, sample_width)
)
),
)
rxi2s.act("LEFT",
@ -272,20 +347,58 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
NextState("LEFT_WAIT")
)
)
rxi2s.act("LEFT_WAIT",
If(~self.rx_ctl.fields.enable,
NextState("IDLE")
).Else(
If(rising_edge,
If((rx_cnt == 0) & sync_pin,
NextValue(rx_cnt, 16),
NextState("RIGHT")
).Elif(rx_cnt > 0,
NextState("LEFT")
if concatenate_channels:
rxi2s.act("LEFT_WAIT",
If(~self.rx_ctl.fields.enable,
NextState("IDLE")
).Else(
If(rising_edge,
If((rx_cnt == 0),
If((sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else ~sync_pin),
If(rx_delay_cnt == 0,
NextValue(rx_cnt, sample_width),
NextValue(rx_delay_cnt,rx_delay_val),
NextState("RIGHT"),
).Else(
NextValue(rx_delay_cnt, rx_delay_cnt - 1),
NextState("LEFT_WAIT")
)
).Else(
NextState("LEFT_WAIT")
)
).Elif(rx_cnt > 0,
NextState("LEFT")
)
)
)
)
)
else:
rxi2s.act("LEFT_WAIT",
If(~self.rx_ctl.fields.enable,
NextState("IDLE")
).Else(
If(rising_edge,
If((rx_cnt == 0),
If((sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else ~sync_pin),
If(rx_delay_cnt == 0,
NextValue(rx_cnt, sample_width),
NextValue(rx_delay_cnt,rx_delay_val),
NextState("RIGHT"),
rx_wren.eq(1) # Pulse rx_wren to write the current data word
).Else(
NextValue(rx_delay_cnt, rx_delay_cnt - 1),
NextState("LEFT_WAIT")
)
).Else(
NextState("LEFT_WAIT")
)
).Elif(rx_cnt > 0,
NextState("LEFT")
)
)
)
)
rxi2s.act("RIGHT",
If(~self.rx_ctl.fields.enable,
NextState("IDLE")
@ -300,10 +413,16 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
NextState("IDLE")
).Else(
If(rising_edge,
If((rx_cnt == 0) & ~sync_pin,
NextValue(rx_cnt, 16),
NextState("LEFT"),
rx_wren.eq(1) # Pulse rx_wren to write the current data word
If((rx_cnt == 0) & (~sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else sync_pin),
If(rx_delay_cnt == 0,
NextValue(rx_cnt, sample_width),
NextValue(rx_delay_cnt,rx_delay_val),
NextState("LEFT"),
rx_wren.eq(1) # Pulse rx_wren to write the current data word
).Else(
NextValue(rx_delay_cnt, rx_delay_cnt - 1),
NextState("RIGHT_WAIT")
)
).Elif(rx_cnt > 0,
NextState("RIGHT")
)
@ -314,7 +433,7 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
# Build the TX subsystem
if hasattr(pads, 'tx'):
tx_rd_d = Signal(32)
tx_rd_d = Signal(fifo_data_width)
tx_almostfull = Signal()
tx_almostempty = Signal()
tx_full = Signal()
@ -324,7 +443,7 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
tx_wrerr = Signal()
tx_wrcount = Signal(9)
tx_rden = Signal()
tx_wr_d = Signal(32)
tx_wr_d = Signal(fifo_data_width)
tx_wren = Signal()
self.tx_ctl = CSRStorage(description="Tx data path control",
@ -334,7 +453,7 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
])
self.tx_stat = CSRStatus(description="Tx data path status",
fields=[
CSRField("overflow", size=1, description="Tx overflow"),
CSRField("overflow", size=1, description="Tx overflow"),
CSRField("underflow", size=1, description="Tx underflow"),
CSRField("free", size=1, description="At least {} words of space free".format(fifo_depth)),
CSRField("almostfull", size=1, description="Less than 8 words space available"), # the next few flags should be rarely used
@ -342,6 +461,13 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
CSRField("empty", size=1, description="FIFO is empty"),
CSRField("wrcount", size=9, description="Tx write count"),
CSRField("rdcount", size=9, description="Tx read count"),
CSRField("concatenate_channels", size=1, reset=concatenate_channels, description="Receive and send both channels atomically")
])
self.tx_conf = CSRStatus(description="TX configuration",
fields=[
CSRField("format", size=2, reset=frame_format.value, description="I2S sample format. {} is left-justified, {} is I2S standard".format(I2S_FORMAT.I2S_LEFT_JUSTIFIED, I2S_FORMAT.I2S_STANDARD)),
CSRField("sample_width", size=6, reset=sample_width, description="Single sample width"),
CSRField("lrck_freq", size=24, reset=lrck_freq, description="Audio sampling rate frequency"),
])
tx_rst_cnt = Signal(3)
@ -359,11 +485,12 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
)
)
]
# At a width of 32 bits, an 18kiB fifo is 512 entries deep
self.specials += Instance("FIFO_SYNC_MACRO",
p_DEVICE = "7SERIES",
p_FIFO_SIZE = "18Kb",
p_DATA_WIDTH = 32,
p_DATA_WIDTH = fifo_data_width,
p_ALMOST_EMPTY_OFFSET = fifo_depth,
p_ALMOST_FULL_OFFSET = 8,
p_DO_REG = 0,
@ -384,7 +511,6 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
)
self.comb += [ # Wire up the status signals and interrupts
self.tx_stat.fields.overflow.eq(tx_wrerr),
self.tx_stat.fields.underflow.eq(tx_rderr),
self.tx_stat.fields.free.eq(tx_almostempty),
self.tx_stat.fields.almostfull.eq(tx_almostfull),
@ -397,7 +523,7 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
]
self.sync += [
# This is the bus responder -- need to check how this interacts with uncached memory
# region
# region
If(bus.cyc & bus.stb & bus.we & ~bus.ack,
If(~tx_full,
tx_wr_d.eq(bus.dat_w),
@ -413,53 +539,86 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
)
]
tx_cnt = Signal(5)
tx_buf = Signal(32)
tx_buf_width = fifo_data_width + 1 if frame_format == I2S_FORMAT.I2S_STANDARD else fifo_data_width
sample_width = sample_width + 1 if frame_format == I2S_FORMAT.I2S_STANDARD else sample_width
offset = [0] if frame_format == I2S_FORMAT.I2S_STANDARD else []
tx_cnt_width = math.ceil(math.log(fifo_data_width,2))
tx_cnt = Signal(tx_cnt_width)
tx_buf = Signal(tx_buf_width)
sample_msb = fifo_data_width - 1
self.submodules.txi2s = txi2s = FSM(reset_state="IDLE")
txi2s.act("IDLE",
If(self.tx_ctl.fields.enable,
If(falling_edge & sync_pin,
If(falling_edge & (~sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else sync_pin),
NextState("WAIT_SYNC"),
)
)
),
txi2s.act("WAIT_SYNC",
If(falling_edge & ~sync_pin,
If(falling_edge & (~sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else sync_pin),
NextState("LEFT"),
NextValue(tx_cnt, 16),
NextValue(tx_buf, tx_rd_d),
tx_rden.eq(1)
NextValue(tx_cnt, sample_width),
NextValue(tx_buf, Cat(tx_rd_d, offset)),
tx_rden.eq(1),
)
)
txi2s.act("LEFT",
If(~self.tx_ctl.fields.enable,
NextState("IDLE")
).Else(
NextValue(tx_pin, tx_buf[31]),
NextValue(tx_pin, tx_buf[sample_msb]),
NextValue(tx_buf, Cat(0, tx_buf[:-1])),
NextValue(tx_cnt, tx_cnt - 1),
NextState("LEFT_WAIT")
)
)
txi2s.act("LEFT_WAIT",
If(~self.tx_ctl.fields.enable,
NextState("IDLE")
).Else(
If(falling_edge,
If((tx_cnt == 0) & sync_pin,
NextValue(tx_cnt, 16),
NextState("RIGHT")
).Elif(tx_cnt > 0,
NextState("LEFT")
if concatenate_channels:
txi2s.act("LEFT_WAIT",
If(~self.tx_ctl.fields.enable,
NextState("IDLE")
).Else(
If(falling_edge,
If((tx_cnt == 0),
If((sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else ~sync_pin),
NextValue(tx_cnt, sample_width),
NextState("RIGHT"),
).Else(
NextState("LEFT_WAIT"),
)
).Elif(tx_cnt > 0,
NextState("LEFT"),
)
)
)
)
)
else:
txi2s.act("LEFT_WAIT",
If(~self.tx_ctl.fields.enable,
NextState("IDLE")
).Else(
If(falling_edge,
If((tx_cnt == 0),
If((sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else ~sync_pin),
NextValue(tx_cnt, sample_width),
NextState("RIGHT"),
NextValue(tx_buf, Cat(tx_rd_d,offset)),
tx_rden.eq(1),
).Else(
NextState("LEFT_WAIT"),
)
).Elif(tx_cnt > 0,
NextState("LEFT"),
)
)
)
)
txi2s.act("RIGHT",
If(~self.tx_ctl.fields.enable,
NextState("IDLE")
).Else(
NextValue(tx_pin, tx_buf[31]),
NextValue(tx_pin, tx_buf[sample_msb]),
NextValue(tx_buf, Cat(0, tx_buf[:-1])),
NextValue(tx_cnt, tx_cnt - 1),
NextState("RIGHT_WAIT")
@ -470,10 +629,10 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
NextState("IDLE")
).Else(
If(falling_edge,
If((tx_cnt == 0) & ~sync_pin,
NextValue(tx_cnt, 16),
If((tx_cnt == 0) & (~sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else sync_pin),
NextValue(tx_cnt, sample_width),
NextState("LEFT"),
NextValue(tx_buf, tx_rd_d),
NextValue(tx_buf, Cat(tx_rd_d,offset)),
tx_rden.eq(1)
).Elif(tx_cnt > 0,
NextState("RIGHT")

View File

@ -5,11 +5,11 @@ import unittest
from migen import *
from litex.soc.cores.i2s import S7I2SSlave
from litex.soc.cores.i2s import S7I2S
class TestI2S(unittest.TestCase):
def test_s7i2sslave_syntax(self):
i2s_pads = Record([("rx", 1), ("tx", 1), ("sync", 1), ("clk", 1)])
i2s = S7I2SSlave(pads=i2s_pads, fifo_depth=256)
i2s = S7I2S(pads=i2s_pads, fifo_depth=256)