Merge pull request #516 from antmicro/i2s_support_arty
Add I2S support to Arty
This commit is contained in:
commit
9bdb063b3e
|
@ -1,36 +1,56 @@
|
||||||
# This file is Copyright (c) 2020 bunnie <bunnie@kosagi.com>
|
# This file is Copyright (c) 2020 bunnie <bunnie@kosagi.com>
|
||||||
|
# This file is Copyright (c) 2020 Antmicro <www.antmicro.com>
|
||||||
# License: BSD
|
# License: BSD
|
||||||
|
|
||||||
from migen.genlib.cdc import MultiReg
|
from migen.genlib.cdc import MultiReg
|
||||||
|
|
||||||
|
from litex.soc.cores.clock import *
|
||||||
from litex.soc.interconnect import wishbone
|
from litex.soc.interconnect import wishbone
|
||||||
from litex.soc.interconnect.csr_eventmanager import *
|
from litex.soc.interconnect.csr_eventmanager import *
|
||||||
|
|
||||||
from litex.soc.integration.doc import AutoDoc, ModuleDoc
|
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 S7I2S(Module, AutoCSR, AutoDoc):
|
||||||
class S7I2SSlave(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):
|
||||||
def __init__(self, pads, fifo_depth=256):
|
|
||||||
self.intro = ModuleDoc("""Intro
|
self.intro = ModuleDoc("""Intro
|
||||||
|
|
||||||
I2S slave creates a slave audio interface instance. Tx and Rx interfaces are inferred based
|
I2S master/slave creates a master/slave audio interface instance depending on a configured master variable.
|
||||||
upon the presence or absence of the respective pins in the "pads" argument.
|
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
|
I2S core supports two formats: standard and left-justified.
|
||||||
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
|
- Standard format requires a device to receive and send data with one bit offset for both channels.
|
||||||
to a CODEC without a programmable bit offset!
|
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
|
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` 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
|
`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.
|
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 updated on the falling edge
|
||||||
- Data is sampled on the rising edge
|
- Data is sampled on the rising edge
|
||||||
- Words are MSB-to-LSB, left-justified (**NOTE: this is a deviation from strict I2S, which
|
- Words are MSB-to-LSB,
|
||||||
offsets by 1 from the left**)
|
- Sync is an input or output based on configure mode,
|
||||||
- Sync is an input (FPGA is slave, codec is master): low => left channel, high => right channel
|
|
||||||
- Sync can be longer than the wordlen, extra bits are just ignored
|
- Sync can be longer than the wordlen, extra bits are just ignored
|
||||||
- Tx is data to the codec (SDI pin on LM49352)
|
- Tx is data to the codec (SDI pin on LM49352)
|
||||||
- Rx is data from the codec (SDO 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:
|
if fifo_depth < 8:
|
||||||
fifo_depth = 8
|
fifo_depth = 8
|
||||||
print("I2S warning: fifo depth less than 8 selected; truncating to 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
|
# Connect pins, synchronizers, and edge detectors
|
||||||
if hasattr(pads, 'tx'):
|
if hasattr(pads, 'tx'):
|
||||||
|
@ -108,6 +130,15 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
|
||||||
if hasattr(pads, 'rx'):
|
if hasattr(pads, 'rx'):
|
||||||
rx_pin = Signal()
|
rx_pin = Signal()
|
||||||
self.specials += MultiReg(pads.rx, rx_pin)
|
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()
|
sync_pin = Signal()
|
||||||
self.specials += MultiReg(pads.sync, sync_pin)
|
self.specials += MultiReg(pads.sync, sync_pin)
|
||||||
|
|
||||||
|
@ -123,7 +154,7 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
|
||||||
self.bus = bus = wishbone.Interface()
|
self.bus = bus = wishbone.Interface()
|
||||||
rd_ack = Signal()
|
rd_ack = Signal()
|
||||||
wr_ack = Signal()
|
wr_ack = Signal()
|
||||||
self.comb +=[
|
self.comb += [
|
||||||
If(bus.we,
|
If(bus.we,
|
||||||
bus.ack.eq(wr_ack),
|
bus.ack.eq(wr_ack),
|
||||||
).Else(
|
).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
|
# Interrupts
|
||||||
self.submodules.ev = EventManager()
|
self.submodules.ev = EventManager()
|
||||||
if hasattr(pads, 'rx'):
|
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.tx_error = EventSourcePulse(description="Indicates a Tx error has happened (over/underflow")
|
||||||
self.ev.finalize()
|
self.ev.finalize()
|
||||||
|
|
||||||
|
|
||||||
# build the RX subsystem
|
# build the RX subsystem
|
||||||
if hasattr(pads, 'rx'):
|
if hasattr(pads, 'rx'):
|
||||||
rx_rd_d = Signal(32)
|
rx_rd_d = Signal(fifo_data_width)
|
||||||
rx_almostfull = Signal()
|
rx_almostfull = Signal()
|
||||||
rx_almostempty = Signal()
|
rx_almostempty = Signal()
|
||||||
rx_full = Signal()
|
rx_full = Signal()
|
||||||
|
@ -154,7 +212,7 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
|
||||||
rx_wrerr = Signal()
|
rx_wrerr = Signal()
|
||||||
rx_wrcount = Signal(9)
|
rx_wrcount = Signal(9)
|
||||||
rx_rden = Signal()
|
rx_rden = Signal()
|
||||||
rx_wr_d = Signal(32)
|
rx_wr_d = Signal(fifo_data_width)
|
||||||
rx_wren = Signal()
|
rx_wren = Signal()
|
||||||
|
|
||||||
self.rx_ctl = CSRStorage(description="Rx data path control",
|
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",
|
self.rx_stat = CSRStatus(description="Rx data path status",
|
||||||
fields=[
|
fields=[
|
||||||
CSRField("overflow", size=1, description="Rx overflow"),
|
CSRField("overflow", size=1, description="Rx overflow"),
|
||||||
CSRField("underflow", size=1, description="Rx underflow"),
|
CSRField("underflow", size=1, description="Rx underflow"),
|
||||||
CSRField("dataready", size=1, description="{} words of data loaded and ready to read".format(fifo_depth)),
|
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("empty", size=1, description="No data available in FIFO to read"), # next flags probably never used
|
||||||
CSRField("wrcount", size=9, description="Write count"),
|
CSRField("wrcount", size=9, description="Write count"),
|
||||||
CSRField("rdcount", size=9, description="Read 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)
|
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
|
# At a width of 32 bits, an 18kiB fifo is 512 entries deep
|
||||||
self.specials += Instance("FIFO_SYNC_MACRO",
|
self.specials += Instance("FIFO_SYNC_MACRO",
|
||||||
p_DEVICE = "7SERIES",
|
p_DEVICE = "7SERIES",
|
||||||
p_FIFO_SIZE = "18Kb",
|
p_FIFO_SIZE = "18Kb",
|
||||||
p_DATA_WIDTH = 32,
|
p_DATA_WIDTH = fifo_data_width,
|
||||||
p_ALMOST_EMPTY_OFFSET = 8,
|
p_ALMOST_EMPTY_OFFSET = 8,
|
||||||
p_ALMOST_FULL_OFFSET = (512 - fifo_depth),
|
p_ALMOST_FULL_OFFSET = (512 - fifo_depth),
|
||||||
p_DO_REG = 0,
|
p_DO_REG = 0,
|
||||||
|
@ -213,7 +279,6 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
|
||||||
o_WRERR = rx_wrerr,
|
o_WRERR = rx_wrerr,
|
||||||
)
|
)
|
||||||
self.comb += [ # Wire up the status signals and interrupts
|
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.underflow.eq(rx_rderr),
|
||||||
self.rx_stat.fields.dataready.eq(rx_almostfull),
|
self.rx_stat.fields.dataready.eq(rx_almostfull),
|
||||||
self.rx_stat.fields.wrcount.eq(rx_wrcount),
|
self.rx_stat.fields.wrcount.eq(rx_wrcount),
|
||||||
|
@ -244,23 +309,33 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
|
||||||
),
|
),
|
||||||
rd_ack.eq(rd_ack_pipe),
|
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")
|
self.submodules.rxi2s = rxi2s = FSM(reset_state="IDLE")
|
||||||
rxi2s.act("IDLE",
|
rxi2s.act("IDLE",
|
||||||
NextValue(rx_wr_d, 0),
|
NextValue(rx_wr_d, 0),
|
||||||
If(self.rx_ctl.fields.enable,
|
If(self.rx_ctl.fields.enable,
|
||||||
# Wait_sync guarantees we start at the beginning of a left frame, and not in
|
# Wait_sync guarantees we start at the beginning of a left frame, and not in
|
||||||
# the middle
|
# the middle
|
||||||
If(rising_edge & sync_pin,
|
If(rising_edge & (~sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else sync_pin),
|
||||||
NextState("WAIT_SYNC")
|
NextState("WAIT_SYNC"),
|
||||||
|
NextValue(rx_delay_cnt, rx_delay_val)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
rxi2s.act("WAIT_SYNC",
|
rxi2s.act("WAIT_SYNC",
|
||||||
If(rising_edge & ~sync_pin,
|
If(rising_edge & (~sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else sync_pin),
|
||||||
NextState("LEFT"),
|
If(rx_delay_cnt > 0,
|
||||||
NextValue(rx_cnt, 16)
|
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",
|
rxi2s.act("LEFT",
|
||||||
|
@ -272,20 +347,58 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
|
||||||
NextState("LEFT_WAIT")
|
NextState("LEFT_WAIT")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
rxi2s.act("LEFT_WAIT",
|
if concatenate_channels:
|
||||||
If(~self.rx_ctl.fields.enable,
|
rxi2s.act("LEFT_WAIT",
|
||||||
NextState("IDLE")
|
If(~self.rx_ctl.fields.enable,
|
||||||
).Else(
|
NextState("IDLE")
|
||||||
If(rising_edge,
|
).Else(
|
||||||
If((rx_cnt == 0) & sync_pin,
|
If(rising_edge,
|
||||||
NextValue(rx_cnt, 16),
|
If((rx_cnt == 0),
|
||||||
NextState("RIGHT")
|
If((sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else ~sync_pin),
|
||||||
).Elif(rx_cnt > 0,
|
If(rx_delay_cnt == 0,
|
||||||
NextState("LEFT")
|
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",
|
rxi2s.act("RIGHT",
|
||||||
If(~self.rx_ctl.fields.enable,
|
If(~self.rx_ctl.fields.enable,
|
||||||
NextState("IDLE")
|
NextState("IDLE")
|
||||||
|
@ -300,10 +413,16 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
|
||||||
NextState("IDLE")
|
NextState("IDLE")
|
||||||
).Else(
|
).Else(
|
||||||
If(rising_edge,
|
If(rising_edge,
|
||||||
If((rx_cnt == 0) & ~sync_pin,
|
If((rx_cnt == 0) & (~sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else sync_pin),
|
||||||
NextValue(rx_cnt, 16),
|
If(rx_delay_cnt == 0,
|
||||||
NextState("LEFT"),
|
NextValue(rx_cnt, sample_width),
|
||||||
rx_wren.eq(1) # Pulse rx_wren to write the current data word
|
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,
|
).Elif(rx_cnt > 0,
|
||||||
NextState("RIGHT")
|
NextState("RIGHT")
|
||||||
)
|
)
|
||||||
|
@ -314,7 +433,7 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
|
||||||
|
|
||||||
# Build the TX subsystem
|
# Build the TX subsystem
|
||||||
if hasattr(pads, 'tx'):
|
if hasattr(pads, 'tx'):
|
||||||
tx_rd_d = Signal(32)
|
tx_rd_d = Signal(fifo_data_width)
|
||||||
tx_almostfull = Signal()
|
tx_almostfull = Signal()
|
||||||
tx_almostempty = Signal()
|
tx_almostempty = Signal()
|
||||||
tx_full = Signal()
|
tx_full = Signal()
|
||||||
|
@ -324,7 +443,7 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
|
||||||
tx_wrerr = Signal()
|
tx_wrerr = Signal()
|
||||||
tx_wrcount = Signal(9)
|
tx_wrcount = Signal(9)
|
||||||
tx_rden = Signal()
|
tx_rden = Signal()
|
||||||
tx_wr_d = Signal(32)
|
tx_wr_d = Signal(fifo_data_width)
|
||||||
tx_wren = Signal()
|
tx_wren = Signal()
|
||||||
|
|
||||||
self.tx_ctl = CSRStorage(description="Tx data path control",
|
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",
|
self.tx_stat = CSRStatus(description="Tx data path status",
|
||||||
fields=[
|
fields=[
|
||||||
CSRField("overflow", size=1, description="Tx overflow"),
|
CSRField("overflow", size=1, description="Tx overflow"),
|
||||||
CSRField("underflow", size=1, description="Tx underflow"),
|
CSRField("underflow", size=1, description="Tx underflow"),
|
||||||
CSRField("free", size=1, description="At least {} words of space free".format(fifo_depth)),
|
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
|
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("empty", size=1, description="FIFO is empty"),
|
||||||
CSRField("wrcount", size=9, description="Tx write count"),
|
CSRField("wrcount", size=9, description="Tx write count"),
|
||||||
CSRField("rdcount", size=9, description="Tx read 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)
|
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
|
# At a width of 32 bits, an 18kiB fifo is 512 entries deep
|
||||||
self.specials += Instance("FIFO_SYNC_MACRO",
|
self.specials += Instance("FIFO_SYNC_MACRO",
|
||||||
p_DEVICE = "7SERIES",
|
p_DEVICE = "7SERIES",
|
||||||
p_FIFO_SIZE = "18Kb",
|
p_FIFO_SIZE = "18Kb",
|
||||||
p_DATA_WIDTH = 32,
|
p_DATA_WIDTH = fifo_data_width,
|
||||||
p_ALMOST_EMPTY_OFFSET = fifo_depth,
|
p_ALMOST_EMPTY_OFFSET = fifo_depth,
|
||||||
p_ALMOST_FULL_OFFSET = 8,
|
p_ALMOST_FULL_OFFSET = 8,
|
||||||
p_DO_REG = 0,
|
p_DO_REG = 0,
|
||||||
|
@ -384,7 +511,6 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.comb += [ # Wire up the status signals and interrupts
|
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.underflow.eq(tx_rderr),
|
||||||
self.tx_stat.fields.free.eq(tx_almostempty),
|
self.tx_stat.fields.free.eq(tx_almostempty),
|
||||||
self.tx_stat.fields.almostfull.eq(tx_almostfull),
|
self.tx_stat.fields.almostfull.eq(tx_almostfull),
|
||||||
|
@ -397,7 +523,7 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
|
||||||
]
|
]
|
||||||
self.sync += [
|
self.sync += [
|
||||||
# This is the bus responder -- need to check how this interacts with uncached memory
|
# 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(bus.cyc & bus.stb & bus.we & ~bus.ack,
|
||||||
If(~tx_full,
|
If(~tx_full,
|
||||||
tx_wr_d.eq(bus.dat_w),
|
tx_wr_d.eq(bus.dat_w),
|
||||||
|
@ -413,53 +539,86 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
tx_cnt = Signal(5)
|
tx_buf_width = fifo_data_width + 1 if frame_format == I2S_FORMAT.I2S_STANDARD else fifo_data_width
|
||||||
tx_buf = Signal(32)
|
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")
|
self.submodules.txi2s = txi2s = FSM(reset_state="IDLE")
|
||||||
txi2s.act("IDLE",
|
txi2s.act("IDLE",
|
||||||
If(self.tx_ctl.fields.enable,
|
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"),
|
NextState("WAIT_SYNC"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
txi2s.act("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"),
|
NextState("LEFT"),
|
||||||
NextValue(tx_cnt, 16),
|
NextValue(tx_cnt, sample_width),
|
||||||
NextValue(tx_buf, tx_rd_d),
|
NextValue(tx_buf, Cat(tx_rd_d, offset)),
|
||||||
tx_rden.eq(1)
|
tx_rden.eq(1),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
txi2s.act("LEFT",
|
txi2s.act("LEFT",
|
||||||
If(~self.tx_ctl.fields.enable,
|
If(~self.tx_ctl.fields.enable,
|
||||||
NextState("IDLE")
|
NextState("IDLE")
|
||||||
).Else(
|
).Else(
|
||||||
NextValue(tx_pin, tx_buf[31]),
|
NextValue(tx_pin, tx_buf[sample_msb]),
|
||||||
NextValue(tx_buf, Cat(0, tx_buf[:-1])),
|
NextValue(tx_buf, Cat(0, tx_buf[:-1])),
|
||||||
NextValue(tx_cnt, tx_cnt - 1),
|
NextValue(tx_cnt, tx_cnt - 1),
|
||||||
NextState("LEFT_WAIT")
|
NextState("LEFT_WAIT")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
txi2s.act("LEFT_WAIT",
|
if concatenate_channels:
|
||||||
If(~self.tx_ctl.fields.enable,
|
txi2s.act("LEFT_WAIT",
|
||||||
NextState("IDLE")
|
If(~self.tx_ctl.fields.enable,
|
||||||
).Else(
|
NextState("IDLE")
|
||||||
If(falling_edge,
|
).Else(
|
||||||
If((tx_cnt == 0) & sync_pin,
|
If(falling_edge,
|
||||||
NextValue(tx_cnt, 16),
|
If((tx_cnt == 0),
|
||||||
NextState("RIGHT")
|
If((sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else ~sync_pin),
|
||||||
).Elif(tx_cnt > 0,
|
NextValue(tx_cnt, sample_width),
|
||||||
NextState("LEFT")
|
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",
|
txi2s.act("RIGHT",
|
||||||
If(~self.tx_ctl.fields.enable,
|
If(~self.tx_ctl.fields.enable,
|
||||||
NextState("IDLE")
|
NextState("IDLE")
|
||||||
).Else(
|
).Else(
|
||||||
NextValue(tx_pin, tx_buf[31]),
|
NextValue(tx_pin, tx_buf[sample_msb]),
|
||||||
NextValue(tx_buf, Cat(0, tx_buf[:-1])),
|
NextValue(tx_buf, Cat(0, tx_buf[:-1])),
|
||||||
NextValue(tx_cnt, tx_cnt - 1),
|
NextValue(tx_cnt, tx_cnt - 1),
|
||||||
NextState("RIGHT_WAIT")
|
NextState("RIGHT_WAIT")
|
||||||
|
@ -470,10 +629,10 @@ class S7I2SSlave(Module, AutoCSR, AutoDoc):
|
||||||
NextState("IDLE")
|
NextState("IDLE")
|
||||||
).Else(
|
).Else(
|
||||||
If(falling_edge,
|
If(falling_edge,
|
||||||
If((tx_cnt == 0) & ~sync_pin,
|
If((tx_cnt == 0) & (~sync_pin if frame_format == I2S_FORMAT.I2S_STANDARD else sync_pin),
|
||||||
NextValue(tx_cnt, 16),
|
NextValue(tx_cnt, sample_width),
|
||||||
NextState("LEFT"),
|
NextState("LEFT"),
|
||||||
NextValue(tx_buf, tx_rd_d),
|
NextValue(tx_buf, Cat(tx_rd_d,offset)),
|
||||||
tx_rden.eq(1)
|
tx_rden.eq(1)
|
||||||
).Elif(tx_cnt > 0,
|
).Elif(tx_cnt > 0,
|
||||||
NextState("RIGHT")
|
NextState("RIGHT")
|
||||||
|
|
|
@ -5,11 +5,11 @@ import unittest
|
||||||
|
|
||||||
from migen import *
|
from migen import *
|
||||||
|
|
||||||
from litex.soc.cores.i2s import S7I2SSlave
|
from litex.soc.cores.i2s import S7I2S
|
||||||
|
|
||||||
|
|
||||||
class TestI2S(unittest.TestCase):
|
class TestI2S(unittest.TestCase):
|
||||||
def test_s7i2sslave_syntax(self):
|
def test_s7i2sslave_syntax(self):
|
||||||
i2s_pads = Record([("rx", 1), ("tx", 1), ("sync", 1), ("clk", 1)])
|
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)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue