From ce49990084726fd43dcf16d79a1bfcfc4dc3d08f Mon Sep 17 00:00:00 2001 From: Pawel Sagan Date: Thu, 12 Mar 2020 14:54:41 +0100 Subject: [PATCH] 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. --- litex/soc/cores/i2s.py | 315 +++++++++++++++++++++++++++++++---------- test/test_i2s.py | 4 +- 2 files changed, 239 insertions(+), 80 deletions(-) diff --git a/litex/soc/cores/i2s.py b/litex/soc/cores/i2s.py index f0554d5f..2bffa757 100644 --- a/litex/soc/cores/i2s.py +++ b/litex/soc/cores/i2s.py @@ -1,36 +1,56 @@ # This file is Copyright (c) 2020 bunnie +# This file is Copyright (c) 2020 Antmicro # 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 - - -class S7I2SSlave(Module, AutoCSR, AutoDoc): - def __init__(self, pads, fifo_depth=256): +from enum import Enum +import math +class I2S_FORMAT(Enum): + I2S_STANDARD = 1 + I2S_LEFT_JUSTIFIED = 2 + +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. + + 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. - 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! + 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") diff --git a/test/test_i2s.py b/test/test_i2s.py index 06b66021..80700461 100644 --- a/test/test_i2s.py +++ b/test/test_i2s.py @@ -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) -- 2.30.2