From 9dc3eefb7dad9be7a200a2c0408e95f80a905c96 Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Fri, 13 Jan 2023 13:37:48 +1100 Subject: [PATCH 01/17] soc/cores/i2c: import from misoc * unmodified - integration to follow * from: https://github.com/m-labs/misoc @ 26f039f Dec 2022 --- litex/soc/cores/i2c.py | 235 +++++++++++++++++++++++++++++++++++++++++ test/test_i2c.py | 119 +++++++++++++++++++++ 2 files changed, 354 insertions(+) create mode 100644 litex/soc/cores/i2c.py create mode 100644 test/test_i2c.py diff --git a/litex/soc/cores/i2c.py b/litex/soc/cores/i2c.py new file mode 100644 index 000000000..35f28d722 --- /dev/null +++ b/litex/soc/cores/i2c.py @@ -0,0 +1,235 @@ +from migen import * +from misoc.interconnect import wishbone + + +__all__ = [ + "I2CMaster", + "I2C_XFER_ADDR", "I2C_CONFIG_ADDR", + "I2C_ACK", "I2C_READ", "I2C_WRITE", "I2C_STOP", "I2C_START", "I2C_IDLE", +] + + +class I2CClockGen(Module): + def __init__(self, width): + self.load = Signal(width) + self.clk2x = Signal() + + cnt = Signal.like(self.load) + self.comb += [ + self.clk2x.eq(cnt == 0), + ] + self.sync += [ + If(self.clk2x, + cnt.eq(self.load), + ).Else( + cnt.eq(cnt - 1), + ), + ] + + +class I2CMasterMachine(Module): + def __init__(self, clock_width): + self.scl_o = Signal(reset=1) + self.sda_o = Signal(reset=1) + self.sda_i = Signal() + + self.submodules.cg = CEInserter()(I2CClockGen(clock_width)) + self.idle = Signal() + self.start = Signal() + self.stop = Signal() + self.write = Signal() + self.read = Signal() + self.ack = Signal() + self.data = Signal(8) + + ### + + busy = Signal() + bits = Signal(4) + + fsm = CEInserter()(FSM("IDLE")) + self.submodules += fsm + + fsm.act("IDLE", + If(self.start, + NextState("START0"), + ).Elif(self.stop & self.start, + NextState("RESTART0"), + ).Elif(self.stop, + NextState("STOP0"), + ).Elif(self.write, + NextValue(bits, 8), + NextState("WRITE0"), + ).Elif(self.read, + NextValue(bits, 8), + NextState("READ0"), + ) + ) + + fsm.act("START0", + NextValue(self.scl_o, 1), + NextState("START1")) + fsm.act("START1", + NextValue(self.sda_o, 0), + NextState("IDLE")) + + fsm.act("RESTART0", + NextValue(self.scl_o, 0), + NextState("RESTART1")) + fsm.act("RESTART1", + NextValue(self.sda_o, 1), + NextState("START0")) + + fsm.act("STOP0", + NextValue(self.scl_o, 0), + NextState("STOP1")) + fsm.act("STOP1", + NextValue(self.scl_o, 1), + NextValue(self.sda_o, 0), + NextState("STOP2")) + fsm.act("STOP2", + NextValue(self.sda_o, 1), + NextState("IDLE")) + + fsm.act("WRITE0", + NextValue(self.scl_o, 0), + If(bits == 0, + NextValue(self.sda_o, 1), + NextState("READACK0"), + ).Else( + NextValue(self.sda_o, self.data[7]), + NextState("WRITE1"), + ) + ) + fsm.act("WRITE1", + NextValue(self.scl_o, 1), + NextValue(self.data[1:], self.data[:-1]), + NextValue(bits, bits - 1), + NextState("WRITE0"), + ) + fsm.act("READACK0", + NextValue(self.scl_o, 1), + NextState("READACK1"), + ) + fsm.act("READACK1", + NextValue(self.ack, ~self.sda_i), + NextState("IDLE") + ) + + fsm.act("READ0", + NextValue(self.scl_o, 0), + NextValue(self.sda_o, 1), + NextState("READ1"), + ) + fsm.act("READ1", + NextValue(self.data[0], self.sda_i), + NextValue(self.scl_o, 0), + If(bits == 0, + NextValue(self.sda_o, ~self.ack), + NextState("WRITEACK0"), + ).Else( + NextValue(self.sda_o, 1), + NextState("READ2"), + ) + ) + fsm.act("READ2", + NextValue(self.scl_o, 1), + NextValue(self.data[1:], self.data[:-1]), + NextValue(bits, bits - 1), + NextState("READ1"), + ) + fsm.act("WRITEACK0", + NextValue(self.scl_o, 1), + NextState("IDLE"), + ) + + run = Signal() + self.comb += [ + run.eq(self.start | self.stop | self.write | self.read), + self.idle.eq(~run & fsm.ongoing("IDLE")), + self.cg.ce.eq(~self.idle), + fsm.ce.eq(run | self.cg.clk2x), + ] + +# Registers: +# config = Record([ +# ("div", 20), +# ]) +# xfer = Record([ +# ("data", 8), +# ("ack", 1), +# ("read", 1), +# ("write", 1), +# ("start", 1), +# ("stop", 1), +# ("idle", 1), +# ]) +class I2CMaster(Module): + def __init__(self, pads, bus=None): + if bus is None: + bus = wishbone.Interface(data_width=32) + self.bus = bus + + ### + + # Wishbone + self.submodules.i2c = i2c = I2CMasterMachine( + clock_width=20) + + self.sync += [ + # read + If(bus.adr[0], + bus.dat_r.eq(i2c.cg.load), + ).Else( + bus.dat_r.eq(Cat(i2c.data, i2c.ack, C(0, 4), i2c.idle)), + ), + + # write + i2c.read.eq(0), + i2c.write.eq(0), + i2c.start.eq(0), + i2c.stop.eq(0), + + bus.ack.eq(0), + If(bus.cyc & bus.stb & ~bus.ack, + bus.ack.eq(1), + If(bus.we, + If(bus.adr[0], + i2c.cg.load.eq(bus.dat_w), + ).Else( + i2c.data.eq(bus.dat_w[0:8]), + i2c.ack.eq(bus.dat_w[8]), + i2c.read.eq(bus.dat_w[9]), + i2c.write.eq(bus.dat_w[10]), + i2c.start.eq(bus.dat_w[11]), + i2c.stop.eq(bus.dat_w[12]), + ) + ) + ) + ] + + # I/O + self.scl_t = TSTriple() + self.specials += self.scl_t.get_tristate(pads.scl) + self.comb += [ + self.scl_t.oe.eq(~i2c.scl_o), + self.scl_t.o.eq(0), + ] + + self.sda_t = TSTriple() + self.specials += self.sda_t.get_tristate(pads.sda) + self.comb += [ + self.sda_t.oe.eq(~i2c.sda_o), + self.sda_t.o.eq(0), + i2c.sda_i.eq(self.sda_t.i), + ] + +I2C_XFER_ADDR, I2C_CONFIG_ADDR = range(2) +( + I2C_ACK, + I2C_READ, + I2C_WRITE, + I2C_START, + I2C_STOP, + I2C_IDLE, +) = (1 << i for i in range(8, 14)) diff --git a/test/test_i2c.py b/test/test_i2c.py new file mode 100644 index 000000000..f58e5aca9 --- /dev/null +++ b/test/test_i2c.py @@ -0,0 +1,119 @@ +import unittest + +from migen import * +from migen.fhdl.specials import Tristate + +from misoc.cores.i2c import * + + +class _MockPads: + def __init__(self): + self.scl = Signal() + self.sda = Signal() + + +class _MockTristateImpl(Module): + def __init__(self, t): + oe = Signal() + self.comb += [ + t.target.eq(t.o), + oe.eq(t.oe), + ] + + +class _MockTristate: + @staticmethod + def lower(t): + return _MockTristateImpl(t) + + +class TestI2C(unittest.TestCase): + def test_i2c(self): + pads = _MockPads() + dut = I2CMaster(pads) + + def check_trans(scl, sda): + scl_init, sda_init = (yield dut.i2c.scl_o), (yield dut.i2c.sda_o) + timeout = 0 + while True: + timeout += 1 + self.assertLess(timeout, 20) + scl_now, sda_now = (yield dut.i2c.scl_o), (yield dut.i2c.sda_o) + if scl_now != scl_init or sda_now != sda_init: + self.assertEqual(scl_now, scl) + self.assertEqual(sda_now, sda) + return + yield + + def wait_idle(do=lambda: ()): + timeout = 0 + while True: + timeout += 1 + self.assertLess(timeout, 20) + idle = ((yield from dut.bus.read(I2C_XFER_ADDR)) & I2C_IDLE) != 0 + if idle: + return + yield + + def write_bit(value): + yield from check_trans(scl=False, sda=value) + yield from check_trans(scl=True, sda=value) + + def write_ack(value): + yield from check_trans(scl=False, sda=not value) + yield from check_trans(scl=True, sda=not value) + yield from wait_idle() + + def read_bit(value): + yield from check_trans(scl=False, sda=True) + yield dut.sda_t.i.eq(value) + yield from check_trans(scl=True, sda=True) + + def read_ack(value): + yield from check_trans(scl=False, sda=True) + yield dut.sda_t.i.eq(not value) + yield from check_trans(scl=True, sda=True) + yield from wait_idle() + ack = ((yield from dut.bus.read(I2C_XFER_ADDR)) & I2C_ACK) != 0 + self.assertEqual(ack, value) + + def check(): + yield from dut.bus.write(I2C_CONFIG_ADDR, 4) + + yield from dut.bus.write(I2C_XFER_ADDR, I2C_START) + yield from check_trans(scl=True, sda=False) + yield from wait_idle() + + yield from dut.bus.write(I2C_XFER_ADDR, I2C_WRITE | 0x82) + for i in [True, False, False, False, False, False, True, False]: + yield from write_bit(i) + yield from read_ack(True) + + yield from dut.bus.write(I2C_XFER_ADDR, I2C_WRITE | 0x18) + for i in [False, False, False, True, True, False, False, False]: + yield from write_bit(i) + yield from read_ack(False) + + yield from dut.bus.write(I2C_XFER_ADDR, I2C_START | I2C_STOP) + yield from check_trans(scl=True, sda=False) + yield from wait_idle() + + yield from dut.bus.write(I2C_XFER_ADDR, I2C_READ) + for i in [False, False, False, True, True, False, False, False]: + yield from read_bit(i) + data = (yield from dut.bus.read(I2C_XFER_ADDR)) & 0xff + self.assertEqual(data, 0x18) + yield from write_ack(False) + + yield from dut.bus.write(I2C_XFER_ADDR, I2C_READ | I2C_ACK) + for i in [True, False, False, False, True, False, False, False]: + yield from read_bit(i) + data = (yield dut.i2c.data) + self.assertEqual(data, 0x88) + yield from write_ack(True) + + yield from dut.bus.write(I2C_XFER_ADDR, I2C_STOP) + yield from check_trans(scl=False, sda=False) + yield from wait_idle() + + run_simulation(dut, check(), special_overrides={Tristate: _MockTristate}) From a079da922a092619ac7de0c3e550c7534e1baec7 Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Fri, 13 Jan 2023 12:57:27 +1100 Subject: [PATCH 02/17] soc/cores: adapt misoc i2c to litex Also add misoc license information. --- litex/soc/cores/i2c.py | 17 +++++++++++++++-- test/test_i2c.py | 13 ++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/litex/soc/cores/i2c.py b/litex/soc/cores/i2c.py index 35f28d722..03eaff827 100644 --- a/litex/soc/cores/i2c.py +++ b/litex/soc/cores/i2c.py @@ -1,6 +1,19 @@ -from migen import * -from misoc.interconnect import wishbone +# +# This file is part of MiSoC and has been adapted/modified for Litex. +# +# Copyright 2007-2023 / M-Labs Ltd +# Copyright 2012-2015 / Enjoy-Digital +# Copyright from Misoc LICENCE file added above +# +# Copyright 2023 Andrew Dennison +# +# SPDX-License-Identifier: BSD-2-Clause +from migen import * +from litex.soc.interconnect import wishbone + + +# I2C----------------------------------------------------------------------------------------------- __all__ = [ "I2CMaster", diff --git a/test/test_i2c.py b/test/test_i2c.py index f58e5aca9..3c42ecfd3 100644 --- a/test/test_i2c.py +++ b/test/test_i2c.py @@ -1,9 +1,20 @@ +# +# This file is part of MiSoC and has been adapted/modified for Litex. +# +# Copyright 2007-2023 / M-Labs Ltd +# Copyright 2012-2015 / Enjoy-Digital +# Copyright from Misoc LICENCE file added above +# +# Copyright 2023 Andrew Dennison +# +# SPDX-License-Identifier: BSD-2-Clause + import unittest from migen import * from migen.fhdl.specials import Tristate -from misoc.cores.i2c import * +from litex.soc.cores.i2c import * class _MockPads: From 4ddab34714caf5a42aa15ff125065255a03843d5 Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Fri, 13 Jan 2023 15:48:06 +1100 Subject: [PATCH 03/17] test_i2c: generate i2c.vcd --- test/test_i2c.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_i2c.py b/test/test_i2c.py index 3c42ecfd3..5920afba9 100644 --- a/test/test_i2c.py +++ b/test/test_i2c.py @@ -127,4 +127,4 @@ class TestI2C(unittest.TestCase): yield from check_trans(scl=False, sda=False) yield from wait_idle() - run_simulation(dut, check(), special_overrides={Tristate: _MockTristate}) + run_simulation(dut, check(), special_overrides={Tristate: _MockTristate}, vcd_name="i2c.vcd") From ad37e17743ee94f2b7f7e3116a15fe345eb53b80 Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Tue, 17 Jan 2023 10:58:41 +1100 Subject: [PATCH 04/17] soc/cores/i2c: add interrupt --- litex/soc/cores/i2c.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/litex/soc/cores/i2c.py b/litex/soc/cores/i2c.py index 03eaff827..3c07a4c59 100644 --- a/litex/soc/cores/i2c.py +++ b/litex/soc/cores/i2c.py @@ -11,7 +11,7 @@ from migen import * from litex.soc.interconnect import wishbone - +from litex.soc.interconnect.csr_eventmanager import * # I2C----------------------------------------------------------------------------------------------- @@ -237,6 +237,12 @@ class I2CMaster(Module): i2c.sda_i.eq(self.sda_t.i), ] + # Event Manager. + self.submodules.ev = EventManager() + self.ev.idle = EventSourceLevel() + self.ev.finalize() + self.comb += self.ev.idle.trigger.eq(i2c.idle) + I2C_XFER_ADDR, I2C_CONFIG_ADDR = range(2) ( I2C_ACK, From 90128756f9e57cd8fca8c7bb564d2e4ec2ec4e51 Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Wed, 1 Feb 2023 10:53:09 +1100 Subject: [PATCH 05/17] test_i2c: test reading config --- test/test_i2c.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_i2c.py b/test/test_i2c.py index 5920afba9..a1865dd2a 100644 --- a/test/test_i2c.py +++ b/test/test_i2c.py @@ -90,6 +90,8 @@ class TestI2C(unittest.TestCase): def check(): yield from dut.bus.write(I2C_CONFIG_ADDR, 4) + data = (yield from dut.bus.read(I2C_CONFIG_ADDR)) & 0xff + self.assertEqual(data, 4) yield from dut.bus.write(I2C_XFER_ADDR, I2C_START) yield from check_trans(scl=True, sda=False) From b8b6ecef7cdfff7a6c5a9344357af6895696be12 Mon Sep 17 00:00:00 2001 From: Richard Tucker Date: Fri, 10 Feb 2023 12:49:57 +1100 Subject: [PATCH 06/17] soc/cores/i2c: fix CSR generation --- litex/soc/cores/i2c.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litex/soc/cores/i2c.py b/litex/soc/cores/i2c.py index 3c07a4c59..bf1b04bdd 100644 --- a/litex/soc/cores/i2c.py +++ b/litex/soc/cores/i2c.py @@ -177,7 +177,7 @@ class I2CMasterMachine(Module): # ("stop", 1), # ("idle", 1), # ]) -class I2CMaster(Module): +class I2CMaster(Module, AutoCSR): def __init__(self, pads, bus=None): if bus is None: bus = wishbone.Interface(data_width=32) From 5504cc626fe0588d0ce1b1f72c54a331df6e75c9 Mon Sep 17 00:00:00 2001 From: Richard Tucker Date: Fri, 10 Feb 2023 12:50:25 +1100 Subject: [PATCH 07/17] soc/cores/i2c: change ISR to rising edge of idle --- litex/soc/cores/i2c.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litex/soc/cores/i2c.py b/litex/soc/cores/i2c.py index bf1b04bdd..811e0f1bf 100644 --- a/litex/soc/cores/i2c.py +++ b/litex/soc/cores/i2c.py @@ -239,7 +239,7 @@ class I2CMaster(Module, AutoCSR): # Event Manager. self.submodules.ev = EventManager() - self.ev.idle = EventSourceLevel() + self.ev.idle = EventSourceProcess(edge="rising") self.ev.finalize() self.comb += self.ev.idle.trigger.eq(i2c.idle) From 64ccd6df1cd554ef92c3bf46e0dde4b80bd011ef Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Fri, 25 Aug 2023 15:25:37 +1000 Subject: [PATCH 08/17] test_i2c: allow unit test to run directly --- test/test_i2c.py | 4 ++++ 1 file changed, 4 insertions(+) mode change 100644 => 100755 test/test_i2c.py diff --git a/test/test_i2c.py b/test/test_i2c.py old mode 100644 new mode 100755 index a1865dd2a..6962789f4 --- a/test/test_i2c.py +++ b/test/test_i2c.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # # This file is part of MiSoC and has been adapted/modified for Litex. # @@ -130,3 +131,6 @@ class TestI2C(unittest.TestCase): yield from wait_idle() run_simulation(dut, check(), special_overrides={Tristate: _MockTristate}, vcd_name="i2c.vcd") + +if __name__ == "__main__": + unittest.main() From aef6cb3103843014cedff3005a6ba3072e251bf6 Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Thu, 24 Aug 2023 08:49:21 +1000 Subject: [PATCH 09/17] soc/cores/i2c: remove unnecessary code --- litex/soc/cores/i2c.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litex/soc/cores/i2c.py b/litex/soc/cores/i2c.py index 811e0f1bf..8d2fb50ad 100644 --- a/litex/soc/cores/i2c.py +++ b/litex/soc/cores/i2c.py @@ -141,7 +141,7 @@ class I2CMasterMachine(Module): NextValue(self.sda_o, ~self.ack), NextState("WRITEACK0"), ).Else( - NextValue(self.sda_o, 1), + #NextValue(self.sda_o, 1), must already be high NextState("READ2"), ) ) From c867d5647b41cdfb826ccd8d93ddec73d1e4791a Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Fri, 25 Aug 2023 15:24:03 +1000 Subject: [PATCH 10/17] soc/cores/i2c: only change SDA when SCL is stable Avoid changing SDA immediately in states WRITE0 and READ0 to guarantee SDA hold is > 0 --- litex/soc/cores/i2c.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/litex/soc/cores/i2c.py b/litex/soc/cores/i2c.py index 8d2fb50ad..6a7fbd331 100644 --- a/litex/soc/cores/i2c.py +++ b/litex/soc/cores/i2c.py @@ -232,11 +232,19 @@ class I2CMaster(Module, AutoCSR): self.sda_t = TSTriple() self.specials += self.sda_t.get_tristate(pads.sda) self.comb += [ - self.sda_t.oe.eq(~i2c.sda_o), self.sda_t.o.eq(0), i2c.sda_i.eq(self.sda_t.i), ] + # only change SDA when SCL is stable + self.scl_i_n = Signal() # previous scl_i + self.sync += [ + self.scl_i_n.eq(self.scl_t.i), + If(self.scl_i_n == i2c.scl_o, + self.sda_t.oe.eq(~i2c.sda_o), + ), + ] + # Event Manager. self.submodules.ev = EventManager() self.ev.idle = EventSourceProcess(edge="rising") From e36946b2511a69d400a4c56d30e2b023d1c70b79 Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Mon, 28 Aug 2023 12:57:19 +1000 Subject: [PATCH 11/17] soc/cores/i2c: convert to LiteXModule and name some components --- litex/soc/cores/i2c.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/litex/soc/cores/i2c.py b/litex/soc/cores/i2c.py index 6a7fbd331..56d325cbb 100644 --- a/litex/soc/cores/i2c.py +++ b/litex/soc/cores/i2c.py @@ -10,6 +10,7 @@ # SPDX-License-Identifier: BSD-2-Clause from migen import * +from litex.gen import * from litex.soc.interconnect import wishbone from litex.soc.interconnect.csr_eventmanager import * @@ -22,7 +23,7 @@ __all__ = [ ] -class I2CClockGen(Module): +class I2CClockGen(LiteXModule): def __init__(self, width): self.load = Signal(width) self.clk2x = Signal() @@ -40,13 +41,13 @@ class I2CClockGen(Module): ] -class I2CMasterMachine(Module): +class I2CMasterMachine(LiteXModule): def __init__(self, clock_width): self.scl_o = Signal(reset=1) self.sda_o = Signal(reset=1) self.sda_i = Signal() - self.submodules.cg = CEInserter()(I2CClockGen(clock_width)) + self.cg = CEInserter()(I2CClockGen(clock_width)) self.idle = Signal() self.start = Signal() self.stop = Signal() @@ -61,7 +62,7 @@ class I2CMasterMachine(Module): bits = Signal(4) fsm = CEInserter()(FSM("IDLE")) - self.submodules += fsm + self.fsm = fsm fsm.act("IDLE", If(self.start, @@ -177,7 +178,7 @@ class I2CMasterMachine(Module): # ("stop", 1), # ("idle", 1), # ]) -class I2CMaster(Module, AutoCSR): +class I2CMaster(LiteXModule): def __init__(self, pads, bus=None): if bus is None: bus = wishbone.Interface(data_width=32) @@ -186,7 +187,7 @@ class I2CMaster(Module, AutoCSR): ### # Wishbone - self.submodules.i2c = i2c = I2CMasterMachine( + self.i2c = i2c = I2CMasterMachine( clock_width=20) self.sync += [ @@ -223,14 +224,14 @@ class I2CMaster(Module, AutoCSR): # I/O self.scl_t = TSTriple() - self.specials += self.scl_t.get_tristate(pads.scl) + self.scl_tristate = self.scl_t.get_tristate(pads.scl) self.comb += [ self.scl_t.oe.eq(~i2c.scl_o), self.scl_t.o.eq(0), ] self.sda_t = TSTriple() - self.specials += self.sda_t.get_tristate(pads.sda) + self.sda_tristate = self.sda_t.get_tristate(pads.sda) self.comb += [ self.sda_t.o.eq(0), i2c.sda_i.eq(self.sda_t.i), @@ -246,8 +247,8 @@ class I2CMaster(Module, AutoCSR): ] # Event Manager. - self.submodules.ev = EventManager() - self.ev.idle = EventSourceProcess(edge="rising") + self.ev = EventManager() + self.ev.idle = EventSourceProcess(edge="rising") self.ev.finalize() self.comb += self.ev.idle.trigger.eq(i2c.idle) From dce152b348d19505ed2fcdee1adafa8b503cd050 Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Mon, 28 Aug 2023 14:26:59 +1000 Subject: [PATCH 12/17] soc/cores/i2c: change SDA 1 or 2 cycles earlier * update 'only change SDA when SCL is stable' to max 1 sys_clk delay --- litex/soc/cores/i2c.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/litex/soc/cores/i2c.py b/litex/soc/cores/i2c.py index 56d325cbb..4a932acc4 100644 --- a/litex/soc/cores/i2c.py +++ b/litex/soc/cores/i2c.py @@ -232,18 +232,22 @@ class I2CMaster(LiteXModule): self.sda_t = TSTriple() self.sda_tristate = self.sda_t.get_tristate(pads.sda) - self.comb += [ - self.sda_t.o.eq(0), - i2c.sda_i.eq(self.sda_t.i), - ] - # only change SDA when SCL is stable - self.scl_i_n = Signal() # previous scl_i + self.scl_i_n = Signal() # previous scl_t.i + self.sda_oe_n = Signal() # previous sda_t.oe self.sync += [ self.scl_i_n.eq(self.scl_t.i), + self.sda_oe_n.eq(self.sda_t.oe), + ] + + self.comb += [ + self.sda_t.oe.eq(self.sda_oe_n), + # only change SDA when SCL is stable If(self.scl_i_n == i2c.scl_o, self.sda_t.oe.eq(~i2c.sda_o), ), + self.sda_t.o.eq(0), + i2c.sda_i.eq(self.sda_t.i), ] # Event Manager. From b779933a5f5992cc2c4bb2ea49343ceafd049022 Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Mon, 28 Aug 2023 15:40:19 +1000 Subject: [PATCH 13/17] test_i2c: improve and document _MockTristate* Added i_mock for simulated external device: * when _oe = 0 _i = _i_mock * when _oe = 1 _i = _o --- test/test_i2c.py | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/test/test_i2c.py b/test/test_i2c.py index 6962789f4..eeb0aaf65 100755 --- a/test/test_i2c.py +++ b/test/test_i2c.py @@ -26,14 +26,48 @@ class _MockPads: class _MockTristateImpl(Module): def __init__(self, t): - oe = Signal() + t.i_mock = Signal(reset=True) self.comb += [ - t.target.eq(t.o), - oe.eq(t.oe), + If(t.oe, + t.target.eq(t.o), + t.i.eq(t.o), + ).Else( + t.target.eq(t.i_mock), + t.i.eq(t.i_mock), + ), ] class _MockTristate: + """A mock `Tristate` for simulation + + This simulation ensures the TriState input (_i) tracks the output (_o) when output enable + (_oe) = 1. A new i_mock `Signal` is added - this can be written to in the simulation to represent + input from the external device. + + Example usage: + + class TestMyModule(unittest.TestCase): + def test_mymodule(self): + dut = MyModule() + io = Signal() + dut.io_t = TSTriple() + self.io_tristate = self.io_t.get_tristate(io) + + dut.comb += [ + dut.io_t.oe.eq(signal_for_oe), + dut.io_t.o.eq(signal_for_o), + signal_for_i.eq(dut.io_t.i), + ] + + def generator() + yield dut.io_tristate.i_mock.eq(some_value) + if (yield dut.io_t.oe): + self.assertEqual((yield dut.scl_t.i), (yield dut.io_t.o)) + else: + self.assertEqual((yield dut.scl_t.i), some_value) + + """ @staticmethod def lower(t): return _MockTristateImpl(t) From 13811aeacbbe19a415a82ff9e17e3b8e94fa992e Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Mon, 28 Aug 2023 15:49:49 +1000 Subject: [PATCH 14/17] test_i2c: update to use improved _MockTristate * test now checks the actual i2c bus state, not the I2CMaster output * refactor to eliminate some copy/paste * tests now work again with this change: 'only change SDA when SCL is stable' --- test/test_i2c.py | 92 ++++++++++++++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 35 deletions(-) diff --git a/test/test_i2c.py b/test/test_i2c.py index eeb0aaf65..147ad063c 100755 --- a/test/test_i2c.py +++ b/test/test_i2c.py @@ -79,16 +79,19 @@ class TestI2C(unittest.TestCase): dut = I2CMaster(pads) def check_trans(scl, sda): - scl_init, sda_init = (yield dut.i2c.scl_o), (yield dut.i2c.sda_o) + scl, sda = int(scl), int(sda) + scl_init, sda_init = (yield dut.scl_t.i), (yield dut.sda_t.i) timeout = 0 while True: - timeout += 1 - self.assertLess(timeout, 20) - scl_now, sda_now = (yield dut.i2c.scl_o), (yield dut.i2c.sda_o) - if scl_now != scl_init or sda_now != sda_init: - self.assertEqual(scl_now, scl) - self.assertEqual(sda_now, sda) + scl_now, sda_now = (yield dut.scl_t.i), (yield dut.sda_t.i) + if scl_now == scl and sda_now == sda: return + timeout += 1 + self.assertLess(timeout, 20, + "\n*** timeout waiting for: " + + f"scl:{scl_now} checking:{scl_init}=>{scl} " + + f"sda:{sda_now} checking:{sda_init}=>{sda} ***" + ) yield def wait_idle(do=lambda: ()): @@ -102,28 +105,56 @@ class TestI2C(unittest.TestCase): yield def write_bit(value): + #print(f"write_bit:{value}") yield from check_trans(scl=False, sda=value) yield from check_trans(scl=True, sda=value) def write_ack(value): + #print(f"write_ack:{value}") yield from check_trans(scl=False, sda=not value) yield from check_trans(scl=True, sda=not value) yield from wait_idle() def read_bit(value): - yield from check_trans(scl=False, sda=True) - yield dut.sda_t.i.eq(value) - yield from check_trans(scl=True, sda=True) + #print(f"read_bit:{value}") + yield from check_trans(scl=False, sda=(yield dut.sda_tristate.i_mock)) + yield dut.sda_tristate.i_mock.eq(value) + yield from check_trans(scl=True, sda=value) + # need to restore i_mock elsewhere def read_ack(value): + #print(f"read_ack:{value}") yield from check_trans(scl=False, sda=True) - yield dut.sda_t.i.eq(not value) - yield from check_trans(scl=True, sda=True) + yield dut.sda_tristate.i_mock.eq(not value) + yield from check_trans(scl=True, sda=not value) yield from wait_idle() + yield dut.sda_tristate.i_mock.eq(True) ack = ((yield from dut.bus.read(I2C_XFER_ADDR)) & I2C_ACK) != 0 self.assertEqual(ack, value) + def i2c_write(value, ack=True): + value = int(value) + test_bin = '{0:08b}'.format(value) + #print(f"I2C_WRITE | {hex(value)}:0x{test_bin}") + yield from dut.bus.write(I2C_XFER_ADDR, I2C_WRITE | value) + for i in list(test_bin): + yield from write_bit(int(i)) + yield from read_ack(True) + + def i2c_read(value, ack=True): + value = int(value) + test_bin = '{0:08b}'.format(value) + #print(f"I2C_READ | {hex(value)}:0x{test_bin}") + yield from dut.bus.write(I2C_XFER_ADDR, I2C_READ | (I2C_ACK if ack else 0)) + for i in list(test_bin): + yield from read_bit(int(i)) + yield dut.sda_tristate.i_mock.eq(True) + data = (yield from dut.bus.read(I2C_XFER_ADDR)) & 0xff + self.assertEqual(data, value) + yield from write_ack(ack) + def check(): + yield from dut.bus.write(I2C_CONFIG_ADDR, 4) data = (yield from dut.bus.read(I2C_CONFIG_ADDR)) & 0xff self.assertEqual(data, 4) @@ -132,39 +163,30 @@ class TestI2C(unittest.TestCase): yield from check_trans(scl=True, sda=False) yield from wait_idle() - yield from dut.bus.write(I2C_XFER_ADDR, I2C_WRITE | 0x82) - for i in [True, False, False, False, False, False, True, False]: - yield from write_bit(i) - yield from read_ack(True) - - yield from dut.bus.write(I2C_XFER_ADDR, I2C_WRITE | 0x18) - for i in [False, False, False, True, True, False, False, False]: - yield from write_bit(i) - yield from read_ack(False) + yield from i2c_write(0x82) + yield from i2c_write(0x18, ack=False) yield from dut.bus.write(I2C_XFER_ADDR, I2C_START | I2C_STOP) yield from check_trans(scl=True, sda=False) yield from wait_idle() - yield from dut.bus.write(I2C_XFER_ADDR, I2C_READ) - for i in [False, False, False, True, True, False, False, False]: - yield from read_bit(i) - data = (yield from dut.bus.read(I2C_XFER_ADDR)) & 0xff - self.assertEqual(data, 0x18) - yield from write_ack(False) - - yield from dut.bus.write(I2C_XFER_ADDR, I2C_READ | I2C_ACK) - for i in [True, False, False, False, True, False, False, False]: - yield from read_bit(i) - data = (yield dut.i2c.data) - self.assertEqual(data, 0x88) - yield from write_ack(True) + yield from i2c_read(0x18, ack=False) + yield from i2c_read(0x88) yield from dut.bus.write(I2C_XFER_ADDR, I2C_STOP) yield from check_trans(scl=False, sda=False) yield from wait_idle() - run_simulation(dut, check(), special_overrides={Tristate: _MockTristate}, vcd_name="i2c.vcd") + clocks = { + "sys": 10, + "async": (10, 3), + } + generators = { + "sys": [ + check(), + ], + } + run_simulation(dut, generators, clocks, special_overrides={Tristate: _MockTristate}, vcd_name="i2c.vcd") if __name__ == "__main__": unittest.main() From f99658200edad158c1a00d9a899817ab4fe86721 Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Tue, 29 Aug 2023 17:31:28 +1000 Subject: [PATCH 15/17] soc/cores/i2c: rewrite state machine * Fix READ: was reading too many bits * CLeaner transitions between states: ACK=>IDLE with scl=0. Other to IDLE with scl=1 * Now cleanly supports RESTART * conceptual support for compound commands - not exposed yet * fix tests: now appears to be I2C compliant --- litex/soc/cores/i2c.py | 64 +++++++++++++++++++++++++++++------------- test/test_i2c.py | 62 +++++++++++++++++++++++++++------------- 2 files changed, 86 insertions(+), 40 deletions(-) diff --git a/litex/soc/cores/i2c.py b/litex/soc/cores/i2c.py index 4a932acc4..702b35c22 100644 --- a/litex/soc/cores/i2c.py +++ b/litex/soc/cores/i2c.py @@ -65,41 +65,57 @@ class I2CMasterMachine(LiteXModule): self.fsm = fsm fsm.act("IDLE", - If(self.start, - NextState("START0"), - ).Elif(self.stop & self.start, - NextState("RESTART0"), - ).Elif(self.stop, + # Valid combinations (lowest to highest priority): + # stop: lowest priority + # read (& optional stop with automatic NACK) + # write (& optional stop) + # start (indicates start or restart) + # start & write (& optional stop) + # start & write & read (& optional stop) + # lowest priority + # *** TODO: support compound commands with I2CMaster *** + If(self.stop & ~self.scl_o, + # stop is only valid after an ACK NextState("STOP0"), - ).Elif(self.write, + ), + If(self.read, + # post decrement so read first bit and shift in 7 + NextValue(bits, 8-1), + NextState("READ0"), + ), + If(self.write, NextValue(bits, 8), NextState("WRITE0"), - ).Elif(self.read, - NextValue(bits, 8), - NextState("READ0"), - ) + ), + # start could be requesting a restart + If(self.start, + NextState("RESTART0"), + ), + # highest priority: start only if scl is high + If(self.start & self.scl_o, + NextState("START0"), + ), ) fsm.act("START0", - NextValue(self.scl_o, 1), - NextState("START1")) - fsm.act("START1", + # Always entered with scl_o = 1 NextValue(self.sda_o, 0), NextState("IDLE")) fsm.act("RESTART0", - NextValue(self.scl_o, 0), + # Only entered from IDLE with scl_o = 0 + NextValue(self.sda_o, 1), NextState("RESTART1")) fsm.act("RESTART1", - NextValue(self.sda_o, 1), + NextValue(self.scl_o, 1), NextState("START0")) fsm.act("STOP0", - NextValue(self.scl_o, 0), + # Only entered from IDLE with scl_o = 0 + NextValue(self.sda_o, 0), NextState("STOP1")) fsm.act("STOP1", NextValue(self.scl_o, 1), - NextValue(self.sda_o, 0), NextState("STOP2")) fsm.act("STOP2", NextValue(self.sda_o, 1), @@ -126,13 +142,15 @@ class I2CMasterMachine(LiteXModule): NextState("READACK1"), ) fsm.act("READACK1", + # ACK => IDLE always with scl_o = 0 + NextValue(self.scl_o, 0), NextValue(self.ack, ~self.sda_i), NextState("IDLE") ) fsm.act("READ0", - NextValue(self.scl_o, 0), - NextValue(self.sda_o, 1), + # ACK => IDLE => READ0 always with scl_o = 0 + NextValue(self.scl_o, 1), NextState("READ1"), ) fsm.act("READ1", @@ -154,7 +172,13 @@ class I2CMasterMachine(LiteXModule): ) fsm.act("WRITEACK0", NextValue(self.scl_o, 1), - NextState("IDLE"), + NextState("WRITEACK1"), + ) + fsm.act("WRITEACK1", + # ACK => IDLE always with scl_o = 0 + NextValue(self.scl_o, 0), + NextValue(self.sda_o, 1), + NextState("IDLE") ) run = Signal() diff --git a/test/test_i2c.py b/test/test_i2c.py index 147ad063c..309357476 100755 --- a/test/test_i2c.py +++ b/test/test_i2c.py @@ -78,7 +78,7 @@ class TestI2C(unittest.TestCase): pads = _MockPads() dut = I2CMaster(pads) - def check_trans(scl, sda): + def check_trans(scl, sda, msg=""): scl, sda = int(scl), int(sda) scl_init, sda_init = (yield dut.scl_t.i), (yield dut.sda_t.i) timeout = 0 @@ -88,7 +88,7 @@ class TestI2C(unittest.TestCase): return timeout += 1 self.assertLess(timeout, 20, - "\n*** timeout waiting for: " + + f"\n*** {msg} timeout. Waiting for: " + f"scl:{scl_now} checking:{scl_init}=>{scl} " + f"sda:{sda_now} checking:{sda_init}=>{sda} ***" ) @@ -116,11 +116,11 @@ class TestI2C(unittest.TestCase): yield from wait_idle() def read_bit(value): - #print(f"read_bit:{value}") - yield from check_trans(scl=False, sda=(yield dut.sda_tristate.i_mock)) + print(f"read_bit:{value}") yield dut.sda_tristate.i_mock.eq(value) - yield from check_trans(scl=True, sda=value) - # need to restore i_mock elsewhere + yield from check_trans(scl=True, sda=value) + yield from check_trans(scl=False, sda=value) + yield dut.sda_tristate.i_mock.eq(True) def read_ack(value): #print(f"read_ack:{value}") @@ -132,6 +132,28 @@ class TestI2C(unittest.TestCase): ack = ((yield from dut.bus.read(I2C_XFER_ADDR)) & I2C_ACK) != 0 self.assertEqual(ack, value) + def i2c_restart(): + yield from check_trans(scl=False, sda=True, msg="checking restart precondition") + yield from dut.bus.write(I2C_XFER_ADDR, I2C_START) + yield from check_trans(scl=False, sda=True, msg="checking restart0") + yield from check_trans(scl=True, sda=True, msg="checking restart1") + yield from check_trans(scl=True, sda=False, msg="checking start0") + yield from wait_idle() + + def i2c_start(): + yield from check_trans(scl=True, sda=True, msg="checking start precondition") + yield from dut.bus.write(I2C_XFER_ADDR, I2C_START) + yield from check_trans(scl=True, sda=False, msg="checking start0") + yield from wait_idle() + + def i2c_stop(): + yield from check_trans(scl=False, sda=True, msg="checking stop after read or write") + yield from dut.bus.write(I2C_XFER_ADDR, I2C_STOP) + yield from check_trans(scl=False, sda=False, msg="checking STOP0") + yield from check_trans(scl=True, sda=False, msg="checking STOP1") + yield from check_trans(scl=True, sda=True, msg="checking STOP2") + yield from wait_idle() + def i2c_write(value, ack=True): value = int(value) test_bin = '{0:08b}'.format(value) @@ -144,7 +166,7 @@ class TestI2C(unittest.TestCase): def i2c_read(value, ack=True): value = int(value) test_bin = '{0:08b}'.format(value) - #print(f"I2C_READ | {hex(value)}:0x{test_bin}") + print(f"I2C_READ | {hex(value)}:0x{test_bin}") yield from dut.bus.write(I2C_XFER_ADDR, I2C_READ | (I2C_ACK if ack else 0)) for i in list(test_bin): yield from read_bit(int(i)) @@ -159,23 +181,23 @@ class TestI2C(unittest.TestCase): data = (yield from dut.bus.read(I2C_CONFIG_ADDR)) & 0xff self.assertEqual(data, 4) - yield from dut.bus.write(I2C_XFER_ADDR, I2C_START) - yield from check_trans(scl=True, sda=False) - yield from wait_idle() - - yield from i2c_write(0x82) + print("write 1 byte 0x18 to address 0x41") + yield from i2c_start() + yield from i2c_write(0x41<<1 | 0) yield from i2c_write(0x18, ack=False) + yield from i2c_stop() - yield from dut.bus.write(I2C_XFER_ADDR, I2C_START | I2C_STOP) - yield from check_trans(scl=True, sda=False) - yield from wait_idle() - + print("read 1 byte from address 0x41") + yield from i2c_start() + yield from i2c_write(0x41<<1 | 1) yield from i2c_read(0x18, ack=False) - yield from i2c_read(0x88) - yield from dut.bus.write(I2C_XFER_ADDR, I2C_STOP) - yield from check_trans(scl=False, sda=False) - yield from wait_idle() + print("read 2 bytes from address 0x55") + yield from i2c_restart() + yield from i2c_write(0x55<<1 | 1) + yield from i2c_read(0xDE, ack=True) + yield from i2c_read(0xAD, ack=False) + yield from i2c_stop() clocks = { "sys": 10, From 643f3f9a93b75330d1fa8204a2cfc658c010edcd Mon Sep 17 00:00:00 2001 From: Radek Pesina Date: Wed, 30 Aug 2023 09:58:06 +1000 Subject: [PATCH 16/17] test_i2c: add more commands --- test/test_i2c.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/test_i2c.py b/test/test_i2c.py index 309357476..c8f648eeb 100755 --- a/test/test_i2c.py +++ b/test/test_i2c.py @@ -192,6 +192,18 @@ class TestI2C(unittest.TestCase): yield from i2c_write(0x41<<1 | 1) yield from i2c_read(0x18, ack=False) + print("write 2 bytes 0x10 0x00 to address 0x11") + yield from i2c_restart() + yield from i2c_write(0x11 << 1 | 0) + yield from i2c_write(0x10, ack=True) + yield from i2c_write(0x00, ack=False) + yield from i2c_stop() + + print("read 1 byte from address 0x11") + yield from i2c_start() + yield from i2c_write(0x11 << 1 | 1) + yield from i2c_read(0x81, ack=False) + print("read 2 bytes from address 0x55") yield from i2c_restart() yield from i2c_write(0x55<<1 | 1) From 67e6614eb22b67b23a2b994c5df680d14b6c7083 Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Mon, 4 Sep 2023 11:16:49 +1000 Subject: [PATCH 17/17] test_i2c: whitespace cleanups --- test/test_i2c.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/test/test_i2c.py b/test/test_i2c.py index c8f648eeb..37f72abda 100755 --- a/test/test_i2c.py +++ b/test/test_i2c.py @@ -29,11 +29,11 @@ class _MockTristateImpl(Module): t.i_mock = Signal(reset=True) self.comb += [ If(t.oe, - t.target.eq(t.o), - t.i.eq(t.o), + t.target.eq(t.o), + t.i.eq(t.o), ).Else( - t.target.eq(t.i_mock), - t.i.eq(t.i_mock), + t.target.eq(t.i_mock), + t.i.eq(t.i_mock), ), ] @@ -68,6 +68,7 @@ class _MockTristate: self.assertEqual((yield dut.scl_t.i), some_value) """ + @staticmethod def lower(t): return _MockTristateImpl(t) @@ -105,14 +106,14 @@ class TestI2C(unittest.TestCase): yield def write_bit(value): - #print(f"write_bit:{value}") + # print(f"write_bit:{value}") yield from check_trans(scl=False, sda=value) - yield from check_trans(scl=True, sda=value) + yield from check_trans(scl=True, sda=value) def write_ack(value): - #print(f"write_ack:{value}") + # print(f"write_ack:{value}") yield from check_trans(scl=False, sda=not value) - yield from check_trans(scl=True, sda=not value) + yield from check_trans(scl=True, sda=not value) yield from wait_idle() def read_bit(value): @@ -156,8 +157,8 @@ class TestI2C(unittest.TestCase): def i2c_write(value, ack=True): value = int(value) - test_bin = '{0:08b}'.format(value) - #print(f"I2C_WRITE | {hex(value)}:0x{test_bin}") + test_bin = "{0:08b}".format(value) + # print(f"I2C_WRITE | {hex(value)}:0x{test_bin}") yield from dut.bus.write(I2C_XFER_ADDR, I2C_WRITE | value) for i in list(test_bin): yield from write_bit(int(i)) @@ -165,31 +166,30 @@ class TestI2C(unittest.TestCase): def i2c_read(value, ack=True): value = int(value) - test_bin = '{0:08b}'.format(value) + test_bin = "{0:08b}".format(value) print(f"I2C_READ | {hex(value)}:0x{test_bin}") yield from dut.bus.write(I2C_XFER_ADDR, I2C_READ | (I2C_ACK if ack else 0)) for i in list(test_bin): yield from read_bit(int(i)) yield dut.sda_tristate.i_mock.eq(True) - data = (yield from dut.bus.read(I2C_XFER_ADDR)) & 0xff + data = (yield from dut.bus.read(I2C_XFER_ADDR)) & 0xFF self.assertEqual(data, value) yield from write_ack(ack) def check(): - yield from dut.bus.write(I2C_CONFIG_ADDR, 4) - data = (yield from dut.bus.read(I2C_CONFIG_ADDR)) & 0xff + data = (yield from dut.bus.read(I2C_CONFIG_ADDR)) & 0xFF self.assertEqual(data, 4) print("write 1 byte 0x18 to address 0x41") yield from i2c_start() - yield from i2c_write(0x41<<1 | 0) + yield from i2c_write(0x41 << 1 | 0) yield from i2c_write(0x18, ack=False) yield from i2c_stop() print("read 1 byte from address 0x41") yield from i2c_start() - yield from i2c_write(0x41<<1 | 1) + yield from i2c_write(0x41 << 1 | 1) yield from i2c_read(0x18, ack=False) print("write 2 bytes 0x10 0x00 to address 0x11") @@ -206,7 +206,7 @@ class TestI2C(unittest.TestCase): print("read 2 bytes from address 0x55") yield from i2c_restart() - yield from i2c_write(0x55<<1 | 1) + yield from i2c_write(0x55 << 1 | 1) yield from i2c_read(0xDE, ack=True) yield from i2c_read(0xAD, ack=False) yield from i2c_stop()