From aef5a2094ee029210b0995ab8e8a4cdb0a060b36 Mon Sep 17 00:00:00 2001 From: inc <87362+inc@users.noreply.github.com> Date: Wed, 10 Jul 2024 15:21:51 +0200 Subject: [PATCH 01/27] soc/cores/video: Add additional color formats --- litex/soc/cores/video.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/litex/soc/cores/video.py b/litex/soc/cores/video.py index bb637b649..b9469280b 100644 --- a/litex/soc/cores/video.py +++ b/litex/soc/cores/video.py @@ -653,7 +653,10 @@ class VideoFrameBuffer(LiteXModule): self.depth = depth = { "rgb888" : 32, - "rgb565" : 16 + "rgb565" : 16, + "rgb332" : 8, + "mono8" : 8, + "mono1" : 1, }[format] # # # @@ -728,16 +731,34 @@ class VideoFrameBuffer(LiteXModule): if (depth == 32): self.comb += [ - source.r.eq(video_pipe_source.data[ 0: 8]), - source.g.eq(video_pipe_source.data[ 8:16]), - source.b.eq(video_pipe_source.data[16:24]), + source.r.eq(video_pipe_source.data[ 0: 8]), + source.g.eq(video_pipe_source.data[ 8:16]), + source.b.eq(video_pipe_source.data[16:24]), ] - else: # depth == 16 + elif (depth == 16): self.comb += [ source.r.eq(Cat(Signal(3, reset = 0), video_pipe_source.data[11:16])), source.g.eq(Cat(Signal(2, reset = 0), video_pipe_source.data[ 5:11])), source.b.eq(Cat(Signal(3, reset = 0), video_pipe_source.data[ 0: 5])), ] + elif (depth == 8 and format == "rgb332"): + self.comb += [ + source.r.eq(Cat(Signal(5, reset = 0), video_pipe_source.data[5:8])), + source.g.eq(Cat(Signal(5, reset = 0), video_pipe_source.data[2:5])), + source.b.eq(Cat(Signal(6, reset = 0), video_pipe_source.data[0:2])), + ] + elif (depth == 8 and format == "mono8"): + self.comb += [ + source.r.eq(video_pipe_source.data[0:8]), + source.g.eq(video_pipe_source.data[0:8]), + source.b.eq(video_pipe_source.data[0:8]), + ] + else: # depth == 1 + self.comb += [ + source.r.eq(Cat(Signal(7, reset = 0), video_pipe_source.data[0:1])), + source.g.eq(Cat(Signal(7, reset = 0), video_pipe_source.data[0:1])), + source.b.eq(Cat(Signal(7, reset = 0), video_pipe_source.data[0:1])), + ] # Underflow. self.comb += self.underflow.eq(~source.valid) From a014c4f07c9e9535a3c71f711b496b2370f3d114 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Thu, 18 Jul 2024 12:16:23 +0200 Subject: [PATCH 02/27] tools/litex_sim: Cleanup imports. --- litex/tools/litex_sim.py | 44 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/litex/tools/litex_sim.py b/litex/tools/litex_sim.py index 1baa4ffb4..3407fc85d 100755 --- a/litex/tools/litex_sim.py +++ b/litex/tools/litex_sim.py @@ -15,35 +15,35 @@ import argparse from migen import * from litex.build.generic_platform import * -from litex.build.sim import SimPlatform -from litex.build.sim.config import SimConfig +from litex.build.sim import SimPlatform +from litex.build.sim.config import SimConfig -from litex.soc.integration.common import * +from litex.soc.integration.common import * from litex.soc.integration.soc_core import * -from litex.soc.integration.builder import * -from litex.soc.integration.soc import * -from litex.soc.cores.bitbang import * -from litex.soc.cores.gpio import GPIOTristate -from litex.soc.cores.cpu import CPUS +from litex.soc.integration.builder import * +from litex.soc.integration.soc import * -from litedram import modules as litedram_modules -from litedram.modules import parse_spd_hexdump +from litex.soc.cores.bitbang import * +from litex.soc.cores.gpio import GPIOTristate +from litex.soc.cores.cpu import CPUS +from litex.soc.cores.video import VideoGenericPHY + +from litedram import modules as litedram_modules +from litedram.modules import parse_spd_hexdump from litedram.phy.model import sdram_module_nphases, get_sdram_phy_settings from litedram.phy.model import SDRAMPHYModel -from liteeth.phy.gmii import LiteEthPHYGMII -from liteeth.phy.xgmii import LiteEthPHYXGMII -from liteeth.phy.model import LiteEthPHYModel -from liteeth.mac import LiteEthMAC -from liteeth.core.arp import LiteEthARP -from liteeth.core.ip import LiteEthIP -from liteeth.core.udp import LiteEthUDP -from liteeth.core.icmp import LiteEthICMP -from liteeth.core import LiteEthUDPIPCore +from liteeth.common import * +from liteeth.phy.gmii import LiteEthPHYGMII +from liteeth.phy.xgmii import LiteEthPHYXGMII +from liteeth.phy.model import LiteEthPHYModel +from liteeth.mac import LiteEthMAC +from liteeth.core.arp import LiteEthARP +from liteeth.core.ip import LiteEthIP +from liteeth.core.udp import LiteEthUDP +from liteeth.core.icmp import LiteEthICMP +from liteeth.core import LiteEthUDPIPCore from liteeth.frontend.etherbone import LiteEthEtherbone -from liteeth.common import * - -from litex.soc.cores.video import VideoGenericPHY from litescope import LiteScopeAnalyzer From 9dc3eefb7dad9be7a200a2c0408e95f80a905c96 Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Fri, 13 Jan 2023 13:37:48 +1100 Subject: [PATCH 03/27] 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 04/27] 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 05/27] 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 06/27] 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 07/27] 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 08/27] 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 09/27] 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 10/27] 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 11/27] 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 12/27] 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 13/27] 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 14/27] 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 15/27] 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 16/27] 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 17/27] 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 18/27] 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 19/27] 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() From ecd0f0e5486ee542d1aca96ec4082f6443394f3e Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 22 Jul 2024 14:24:34 +0200 Subject: [PATCH 20/27] cores/ram/lattice_nx: Revert #1906 since not working with RAM combining multiple SP512K. --- litex/soc/cores/ram/lattice_nx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litex/soc/cores/ram/lattice_nx.py b/litex/soc/cores/ram/lattice_nx.py index 20eada5da..4eab1172d 100644 --- a/litex/soc/cores/ram/lattice_nx.py +++ b/litex/soc/cores/ram/lattice_nx.py @@ -79,10 +79,10 @@ class NXLRAM(LiteXModule): wren = Signal() self.comb += [ datain.eq(self.bus.dat_w[32*w:32*(w+1)]), - self.bus.dat_r[32*w:32*(w+1)].eq(dataout), If(self.bus.adr[14:14+self.depth_cascading.bit_length()] == d, cs.eq(1), - wren.eq(self.bus.we & self.bus.stb & self.bus.cyc) + wren.eq(self.bus.we & self.bus.stb & self.bus.cyc), + self.bus.dat_r[32*w:32*(w+1)].eq(dataout), ), ] lram_block = Instance("SP512K", From b8cb6da2b99a4a8cf6b7da580e0f41be8042a279 Mon Sep 17 00:00:00 2001 From: Gwenhael Goavec-Merou Date: Mon, 22 Jul 2024 15:11:40 +0200 Subject: [PATCH 21/27] soc/cores/clock/lattice_nx.py: added clk contraints for OSCA output --- litex/soc/cores/clock/lattice_nx.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/litex/soc/cores/clock/lattice_nx.py b/litex/soc/cores/clock/lattice_nx.py index 6702461f1..8d8126c9d 100644 --- a/litex/soc/cores/clock/lattice_nx.py +++ b/litex/soc/cores/clock/lattice_nx.py @@ -32,7 +32,7 @@ class NXOSCA(LiteXModule): clk_hf_freq_range = (1.76, 450e6) clk_hf_freq = 450e6 - def __init__(self): + def __init__(self, platform=None): self.logger = logging.getLogger("NXOSCA") self.logger.info("Creating NXOSCA.") @@ -40,6 +40,7 @@ class NXOSCA(LiteXModule): self.hfsdc_clk_out = {} self.lf_clk_out = None self.params = {} + self.platform = platform def create_hf_clk(self, cd, freq, margin=.05): """450 - 1.7 Mhz Clk""" @@ -86,21 +87,29 @@ class NXOSCA(LiteXModule): def do_finalize(self): if self.hf_clk_out: - divisor = self.compute_divisor(self.hf_clk_out[1], self.hf_clk_out[2]) + clk_freq = self.hf_clk_out[1] + divisor = self.compute_divisor(clk_freq, self.hf_clk_out[2]) self.params["i_HFOUTEN"] = 0b1 self.params["p_HF_CLK_DIV"] = divisor self.params["o_HFCLKOUT"] = self.hf_clk_out[0] self.params["p_HF_OSC_EN"] = "ENABLED" + if self.platform: + self.platform.add_platform_command("create_clock -period {} -name OSCA_HFCLKOUT [get_pins OSCA.OSCA_inst/HFCLKOUT]".format(str(1e9/clk_freq))) if self.hfsdc_clk_out: - divisor = self.compute_divisor(self.hfsdc_clk_out[1], self.hfsdc_clk_out[2]) + clk_freq = self.hf_clk_out[1] + divisor = self.compute_divisor(clk_freq, self.hfsdc_clk_out[2]) self.params["i_HFSDSCEN"] = 0b1 self.params["p_HF_SED_SEC_DIV"] = divisor self.params["o_HFSDCOUT"] = self.hfsdc_clk_out[0] + if self.platform: + self.platform.add_platform_command("create_clock -period {} -name OSCA_HFSDCOUT [get_pins OSCA.OSCA_inst/HFSDCOUT]".format(str(1e9/clk_freq))) if self.lf_clk_out is not None: self.params["o_LFCLKOUT"] = self.lf_clk_out[0] self.params["p_LF_OUTPUT_EN"] = "ENABLED" + if self.platform: + self.platform.add_platform_command("create_clock -period {} -name OSCA_LF_OUTPUT_EN [get_pins OSCA.OSCA_inst/LF_OUTPUT_EN]".format(str(1e9/128e3))) self.specials += Instance("OSCA", **self.params) From c51d22074f6815679942ed9968b7194be33d4f8a Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 22 Jul 2024 16:22:29 +0200 Subject: [PATCH 22/27] soc/integration/soc/add_uart: Allow directly passing uart_pads. Useful for test purpose when testing multiple UART peripherals without having to expose them on IOs. --- litex/soc/integration/soc.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/litex/soc/integration/soc.py b/litex/soc/integration/soc.py index 31d964032..6c286355f 100644 --- a/litex/soc/integration/soc.py +++ b/litex/soc/integration/soc.py @@ -1512,7 +1512,7 @@ class LiteXSoC(SoC): self.add_config(name, identifier) # Add UART ------------------------------------------------------------------------------------- - def add_uart(self, name="uart", uart_name="serial", baudrate=115200, fifo_depth=16): + def add_uart(self, name="uart", uart_name="serial", uart_pads=None, baudrate=115200, fifo_depth=16): # Imports. from litex.soc.cores.uart import UART, UARTCrossover @@ -1529,8 +1529,9 @@ class LiteXSoC(SoC): "usb_acm", "serial(x)", ] - uart_pads_name = "serial" if uart_name == "sim" else uart_name - uart_pads = self.platform.request(uart_pads_name, loose=True) + if uart_pads is None: + uart_pads_name = "serial" if uart_name == "sim" else uart_name + uart_pads = self.platform.request(uart_pads_name, loose=True) uart_phy = None uart = None uart_kwargs = { From dc04949d782f03a56fa139b438bab315489ea4d2 Mon Sep 17 00:00:00 2001 From: Gwenhael Goavec-Merou Date: Thu, 25 Jul 2024 14:11:06 +0200 Subject: [PATCH 23/27] build/altera/common,platform: added ddrinput/ddrout primitives --- litex/build/altera/common.py | 71 +++++++++++++++++++++++++++++++++- litex/build/altera/platform.py | 2 + 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/litex/build/altera/common.py b/litex/build/altera/common.py index 3ed0947ba..f0f246edb 100644 --- a/litex/build/altera/common.py +++ b/litex/build/altera/common.py @@ -99,7 +99,6 @@ class AlteraDDROutputImpl(Module): o_dataout = o, ) - class AlteraDDROutput: @staticmethod def lower(dr): @@ -147,3 +146,73 @@ altera_special_overrides = { SDROutput: AlteraSDROutput, SDRInput: AlteraSDRInput, } + +# Agilex5 DDROutput -------------------------------------------------------------------------------- + +class Agilex5DDROutputImpl(Module): + def __init__(self, i1, i2, o, clk): + self.specials += Instance("tennm_ph2_ddio_out", + p_mode = "MODE_DDR", + p_asclr_ena = "ASCLR_ENA_NONE", + p_sclr_ena = "SCLR_ENA_NONE", + o_dataout = o, + i_datainlo = i2, + i_datainhi = i1, + i_clk = clk, + i_ena = Constant(1, 1), + i_areset = Constant(1, 1), + i_sreset = Constant(1, 1), + ) + +class Agilex5DDROutput: + @staticmethod + def lower(dr): + return Agilex5DDROutputImpl(dr.i1, dr.i2, dr.o, dr.clk) + +# Agilex5 DDRInput --------------------------------------------------------------------------------- + +class Agilex5DDRInputImpl(Module): + def __init__(self, i, o1, o2, clk): + self.specials += Instance("tennm_ph2_ddio_in", + p_mode = "MODE_DDR", + p_asclr_ena = "ASCLR_ENA_NONE", + p_sclr_ena = "SCLR_ENA_NONE", + i_clk = clk, + i_datain = i, + o_regouthi = o1, + o_regoutlo = o2, + i_ena = Constant(1, 1), + i_areset = Constant(1, 1), + i_sreset = Constant(1, 1), + ) + +class Agilex5DDRInput: + @staticmethod + def lower(dr): + return Agilex5DDRInputImpl(dr.i, dr.o1, dr.o2, dr.clk) + +# Agilex5 SDROutput -------------------------------------------------------------------------------- + +class Agilex5SDROutput: + @staticmethod + def lower(dr): + return Agilex5DDROutputImpl(dr.i, dr.i, dr.o, dr.clk) + +# Agilex5 SDRInput --------------------------------------------------------------------------------- + +class Agilex5SDRInput: + @staticmethod + def lower(dr): + return Agilex5DDRInputImpl(dr.i, dr.o, Signal(), dr.clk) + +# Agilex5 Special Overrides ------------------------------------------------------------------------ + +agilex5_special_overrides = { + AsyncResetSynchronizer: AlteraAsyncResetSynchronizer, + DifferentialInput: AlteraDifferentialInput, + DifferentialOutput: AlteraDifferentialOutput, + DDROutput: Agilex5DDROutput, + DDRInput: Agilex5DDRInput, + SDROutput: Agilex5SDROutput, + SDRInput: Agilex5SDRInput, +} diff --git a/litex/build/altera/platform.py b/litex/build/altera/platform.py index e023d2ced..f7b3b0a8e 100644 --- a/litex/build/altera/platform.py +++ b/litex/build/altera/platform.py @@ -34,6 +34,8 @@ class AlteraPlatform(GenericPlatform): def get_verilog(self, *args, special_overrides=dict(), **kwargs): so = dict(common.altera_special_overrides) + if self.device[:3] == "A5E": + so.update(common.agilex5_special_overrides) so.update(special_overrides) return GenericPlatform.get_verilog(self, *args, special_overrides = so, From 5c5bc82f22614868f8bbe5d26a2dd68ad12e4010 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Fri, 26 Jul 2024 11:52:17 +0200 Subject: [PATCH 24/27] interconnect/packet/PacketFIFO: Fix payload_fifo.sink.valid. Needs to be filtered on param_fifo.sink.ready and not payload_fifo.sink.ready. --- litex/soc/interconnect/packet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litex/soc/interconnect/packet.py b/litex/soc/interconnect/packet.py index 123a587e1..762fcd8d9 100644 --- a/litex/soc/interconnect/packet.py +++ b/litex/soc/interconnect/packet.py @@ -386,7 +386,7 @@ class PacketFIFO(LiteXModule): sink.connect(param_fifo.sink, keep=set([e[0] for e in param_layout])), sink.connect(payload_fifo.sink, keep=set([e[0] for e in payload_layout] + ["last"])), param_fifo.sink.valid.eq(sink.valid & sink.last), - payload_fifo.sink.valid.eq(sink.valid & payload_fifo.sink.ready), + payload_fifo.sink.valid.eq(sink.valid & param_fifo.sink.ready), sink.ready.eq(param_fifo.sink.ready & payload_fifo.sink.ready), ] From ba8830e6cda85a1ba732d4080f24118c41c9307e Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Fri, 26 Jul 2024 12:56:50 +0200 Subject: [PATCH 25/27] global: Remove @trabucayre's tracers :) --- litex/build/efinix/common.py | 12 ++++++------ litex/build/efinix/efinity.py | 4 ++-- litex/build/efinix/ifacewriter.py | 2 +- litex/build/gowin/gowin.py | 2 +- litex/soc/cores/clock/efinix.py | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/litex/build/efinix/common.py b/litex/build/efinix/common.py index 0197e2911..c52db5ee9 100644 --- a/litex/build/efinix/common.py +++ b/litex/build/efinix/common.py @@ -162,21 +162,21 @@ class EfinixSDRTristate(Module): class EfinixDifferentialOutputImpl(Module): def __init__(self, platform, i, o_p, o_n): - # only keep _p + # only keep _p io_name = platform.get_pin_name(o_p) io_pad = platform.get_pad_name(o_p) # need real pad name io_prop = platform.get_pin_properties(o_p) if platform.family == "Titanium": # _p has _P_ and _n has _N_ followed by an optional function - # lvds block needs _PN_ + # lvds block needs _PN_ pad_split = io_pad.split('_') assert pad_split[1] == 'P' io_pad = f"{pad_split[0]}_PN_{pad_split[2]}" else: assert "TXP" in io_pad # diff output pins are TXPYY and TXNYY - # lvds block needs TXYY + # lvds block needs TXYY io_pad = io_pad.replace("TXP", "TX") platform.add_extension([(io_name, 0, Pins(1))]) @@ -206,21 +206,21 @@ class EfinixDifferentialOutput: class EfinixDifferentialInputImpl(Module): def __init__(self, platform, i_p, i_n, o): - # only keep _p + # only keep _p io_name = platform.get_pin_name(i_p) io_pad = platform.get_pad_name(i_p) # need real pad name io_prop = platform.get_pin_properties(i_p) if platform.family == "Titanium": # _p has _P_ and _n has _N_ followed by an optional function - # lvds block needs _PN_ + # lvds block needs _PN_ pad_split = io_pad.split('_') assert pad_split[1] == 'P' io_pad = f"{pad_split[0]}_PN_{pad_split[2]}" else: assert "RXP" in io_pad # diff input pins are RXPYY and RXNYY - # lvds block needs RXYY + # lvds block needs RXYY io_pad = io_pad.replace("RXP", "RX") platform.add_extension([ diff --git a/litex/build/efinix/efinity.py b/litex/build/efinix/efinity.py index a7791bf96..0f782ac6b 100644 --- a/litex/build/efinix/efinity.py +++ b/litex/build/efinix/efinity.py @@ -286,11 +286,11 @@ class EfinityToolchain(GenericToolchain): # Some IO blocks don't have Python API so we need to configure them # directly in the peri.xml file - # We also need to configure the bank voltage here + # We also need to configure the bank voltage here if self.ifacewriter.xml_blocks or self.platform.iobank_info: self.ifacewriter.generate_xml_blocks() - # Because the Python API is sometimes bugged, we need to tweak the generated xml + # Because the Python API is sometimes bugged, we need to tweak the generated xml if self.ifacewriter.fix_xml: self.ifacewriter.fix_xml_values() diff --git a/litex/build/efinix/ifacewriter.py b/litex/build/efinix/ifacewriter.py index e5fbe9318..95cd66c8a 100644 --- a/litex/build/efinix/ifacewriter.py +++ b/litex/build/efinix/ifacewriter.py @@ -329,7 +329,7 @@ design.create("{2}", "{3}", "./../gateware", overwrite=True) cmd += 'design.set_property("{}", "FEEDBACK_MODE", "{}", "PLL")\n'.format(name, "LOCAL" if feedback_clk < 1 else "CORE") cmd += 'design.set_property("{}", "FEEDBACK_CLK", "CLK{}", "PLL")\n'.format(name, 0 if feedback_clk < 1 else feedback_clk) - # auto_calc_pll_clock is always working with Titanium and only working when feedback is unused for Trion + # auto_calc_pll_clock is always working with Titanium and only working when feedback is unused for Trion if block["feedback"] == -1 or block["version"] == "V3": cmd += "target_freq = {\n" for i, clock in enumerate(block["clk_out"]): diff --git a/litex/build/gowin/gowin.py b/litex/build/gowin/gowin.py index 7129db38e..eef6b3cf3 100644 --- a/litex/build/gowin/gowin.py +++ b/litex/build/gowin/gowin.py @@ -78,7 +78,7 @@ class GowinToolchain(GenericToolchain): for name, pin, other in flat_sc: if pin != "X": - t_name = name.split('[') # avoid index pins + t_name = name.split('[') # avoid index pins tmp_name = t_name[0] if tmp_name[-2:] == "_p": pn = tmp_name[:-2] + "_n" diff --git a/litex/soc/cores/clock/efinix.py b/litex/soc/cores/clock/efinix.py index 63cca6839..ba2a9e70d 100644 --- a/litex/soc/cores/clock/efinix.py +++ b/litex/soc/cores/clock/efinix.py @@ -158,10 +158,10 @@ class EFINIXPLL(LiteXModule): # Pre-Divider (N). # ----------------- # F_PFD is between 10e6 and 100e6 - # so limit search to only acceptable factors + # so limit search to only acceptable factors N_min = int(math.ceil(clk_in_freq / pfd_range[1])) N_max = int(math.floor(clk_in_freq / pfd_range[0])) - ## limit + ## limit ### when fin is below FPLL_MAX min is < 1 if N_min < 1: N_min = 1 @@ -200,7 +200,7 @@ class EFINIXPLL(LiteXModule): params_list = [] for n in range(N_min, N_max + 1): fpfd_tmp = clk_in_freq / n - # limit range using FVCO_MAX & FVCO_MIN + # limit range using FVCO_MAX & FVCO_MIN # fVCO = fPFD * M * O * Cfbk # so: # fVCO From 1f6673c6ebcc27ce3918bbdfb82642d5d7592e43 Mon Sep 17 00:00:00 2001 From: Gwenhael Goavec-Merou Date: Tue, 30 Jul 2024 16:32:54 +0200 Subject: [PATCH 26/27] build/altera/common.py: implement SDRTristate for Agilex5 family --- litex/build/altera/common.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/litex/build/altera/common.py b/litex/build/altera/common.py index f0f246edb..d4a2c3c77 100644 --- a/litex/build/altera/common.py +++ b/litex/build/altera/common.py @@ -205,6 +205,35 @@ class Agilex5SDRInput: def lower(dr): return Agilex5DDRInputImpl(dr.i, dr.o, Signal(), dr.clk) +# Agilex5 SDRTristate ------------------------------------------------------------------------------ + +class Agilex5SDRTristateImpl(Module): + def __init__(self, io, o, oe, i, clk): + _i = Signal() + _o = Signal() + _oe = Signal() + self.specials += [ + SDRIO(o, _o, clk), + SDRIO(oe, _oe, clk), + SDRIO(_i, i, clk), + Instance("tennm_ph2_io_ibuf", + p_bus_hold = "BUS_HOLD_OFF", + io_i = io, # FIXME: its an input but io is needed to have correct dir at top module + o_o = _i, + ), + Instance("tennm_ph2_io_obuf", + p_open_drain = "OPEN_DRAIN_OFF", + i_i = _o, + i_oe = _oe, + io_o = io, # FIXME: its an output but io is needed to have correct dir at top module + ), + ] + +class Agilex5SDRTristate(Module): + @staticmethod + def lower(dr): + return Agilex5SDRTristateImpl(dr.io, dr.o, dr.oe, dr.i, dr.clk) + # Agilex5 Special Overrides ------------------------------------------------------------------------ agilex5_special_overrides = { @@ -215,4 +244,5 @@ agilex5_special_overrides = { DDRInput: Agilex5DDRInput, SDROutput: Agilex5SDROutput, SDRInput: Agilex5SDRInput, + SDRTristate: Agilex5SDRTristate, } From f855417afccf5a7af5018fdd0e70a1c6bd82e65e Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Wed, 31 Jul 2024 14:55:10 +0200 Subject: [PATCH 27/27] README.md: Be more positive and shorter in moral precisions :). --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index becbbe4de..05cb4a61e 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,9 @@ The framework is also far from perfect and we'll be happy to have your [feedback Have fun! :wink: -**Moral precisions**: The project is shared with a permissive BSD 2-Clause License and we are encouraged to continue sharing it this way thanks to the awesome community and clients willing to support the project! -If the projet is useful for your research, hobby or commercial projects, we are just asking you to be coherent and behave with integrity: Don't expect free support or that the community will be welcoming if your spent your time complaining about the project (and then direspect developers) or don't pay the custom developments you asked for... (While it's probably natural for 99% of users/clients, it does seems useful to add this for the 1% remaining that are eating lots of our energy/time). +We share this project under a permissive BSD 2-Clause License, inspired by our fantastic community and supportive clients. If LiteX benefits your research, hobby, or commercial projects, we kindly ask for your positive collaboration and respect for the effort involved. +Thank you for helping us improve LiteX and being part of our community! # Typical LiteX design flow: ```