diff --git a/litex/soc/interconnect/avalon.py b/litex/soc/interconnect/avalon.py deleted file mode 100644 index 6e2c44ff2..000000000 --- a/litex/soc/interconnect/avalon.py +++ /dev/null @@ -1,312 +0,0 @@ -# -# This file is part of LiteX. -# -# Copyright (c) 2019-2020 Florent Kermarrec -# Copyright (c) 2023 Hans Baier -# SPDX-License-Identifier: BSD-2-Clause - -"""Avalon support for LiteX""" - -from migen import * - -from litex.soc.interconnect import stream -from litex.soc.interconnect import wishbone - - -# Avalon MM Layout --------------------------------------------------------------------------------- - -_layout = [ - ("address", "adr_width", DIR_M_TO_S), - ("writedata", "data_width", DIR_M_TO_S), - ("readdata", "data_width", DIR_S_TO_M), - ("readdatavalid", 1, DIR_S_TO_M), - ("byteenable", "sel_width", DIR_M_TO_S), - ("read", 1, DIR_M_TO_S), - ("write", 1, DIR_M_TO_S), - ("waitrequest", 1, DIR_S_TO_M), - ("burstbegin", 1, DIR_M_TO_S), # Optional. - ("burstcount", 8, DIR_M_TO_S), - ("chipselect", 1, DIR_M_TO_S), # Optional. -] - -# Avalon MM Interface ------------------------------------------------------------------------------ - -class AvalonMMInterface(Record): - def __init__(self, data_width=32, adr_width=30, **kwargs): - self.data_width = data_width - if kwargs.get("adr_width", False): - adr_width = kwargs["adr_width"] - int(log2(data_width//8)) - self.adr_width = adr_width - Record.__init__(self, set_layout_parameters(_layout, - adr_width = adr_width, - data_width = data_width, - sel_width = data_width//8)) - self.address.reset_less = True - self.writedata.reset_less = True - self.readdata.reset_less = True - self.byteenable.reset_less = True - - @staticmethod - def like(other): - return AvalonMMInterface(len(other.writedata)) - - def get_ios(self, bus_name="avl"): - subsignals = [] - for name, width, direction in self.layout: - subsignals.append(Subsignal(name, Pins(width))) - ios = [(bus_name , 0) + tuple(subsignals)] - return ios - - def connect_to_pads(self, pads, mode="master"): - assert mode in ["slave", "master"] - r = [] - for name, width, direction in self.layout: - sig = getattr(self, name) - pad = getattr(pads, name) - if mode == "master": - if direction == DIR_M_TO_S: - r.append(pad.eq(sig)) - else: - r.append(sig.eq(pad)) - else: - if direction == DIR_S_TO_M: - r.append(pad.eq(sig)) - else: - r.append(sig.eq(pad)) - return r - - def bus_read(self, address, byteenable=None, burstcount=1, chipselect=None): - if byteenable is None: - byteenable = 2**len(self.byteenable) - 1 - yield self.address.eq(address) - yield self.write.eq(0) - yield self.read.eq(1) - yield self.byteenable.eq(byteenable) - if burstcount != 1: - yield self.burstcount.eq(burstcount) - if chipselect is not None: - yield self.chipselect.eq(chipselect) - yield - while (yield self.waitrequest): - yield - yield self.read.eq(0) - # Actually don't care outside of a transaction this makes the traces look neater. - yield self.byteenable.eq(0) - if burstcount != 1: - yield self.burstcount.eq(0) - if chipselect is not None: - yield self.chipselect.eq(0) - - while not (yield self.readdatavalid): - yield - return (yield self.readdata) - - def continue_read_burst(self): - yield - return (yield self.readdata) - - def bus_write(self, address, writedata, byteenable=None, chipselect=None): - if not isinstance(writedata, list): - writedata = [ writedata ] - burstcount = len(writedata) - if byteenable is None: - byteenable = 2**len(self.byteenable) - 1 - yield self.address.eq(address) - yield self.write.eq(1) - yield self.read.eq(0) - yield self.byteenable.eq(byteenable) - if burstcount is not None: - yield self.burstcount.eq(burstcount) - if chipselect is not None: - yield self.chipselect.eq(chipselect) - for data in writedata: - yield self.writedata.eq(data) - yield - while (yield self.waitrequest): - yield - yield self.burstcount.eq(0) - yield self.writedata.eq(0) - yield self.write.eq(0) - # actually don't care outside of a transaction - # this makes the traces look neater - yield self.byteenable.eq(0) - if chipselect is not None: - yield self.chipselect.eq(0) - -# Avalon MM <--> Wishbone Bridge ------------------------------------------------------------------- - -class AvalonMM2Wishbone(Module): - def __init__(self, data_width=32, address_width=32, wishbone_base_address=0x0, wishbone_extend_address_bits=0, avoid_combinatorial_loop=True): - word_width = data_width // 8 - word_width_bits = log2_int(word_width) - wishbone_address_width = address_width - word_width_bits + wishbone_extend_address_bits - - self.a2w_wb = wb = wishbone.Interface(data_width=data_width, adr_width=wishbone_address_width, bursting=True) - self.a2w_avl = avl = AvalonMMInterface (data_width=data_width, adr_width=address_width) - - read_access = Signal() - readdatavalid = Signal() - readdata = Signal(data_width) - - last_burst_cycle = Signal() - burst_cycle = Signal() - burst_counter = Signal.like(avl.burstcount) - burst_address = Signal(address_width) - burst_read = Signal() - burst_sel = Signal.like(avl.byteenable) - - self.sync += last_burst_cycle.eq(burst_cycle) - - # Some designs might have trouble with the combinatorial loop created - # by wb.ack, so cut it, incurring one clock cycle of overhead on each - # bus transaction - if avoid_combinatorial_loop: - self.sync += [ - If(wb.ack | wb.err, - read_access.eq(0) - ).Elif(avl.read, - read_access.eq(1) - ), - readdata.eq(wb.dat_r), - readdatavalid.eq((wb.ack | wb.err) & read_access), - ] - else: - self.comb += [ - read_access.eq(avl.read), - readdata.eq(wb.dat_r), - readdatavalid.eq((wb.ack | wb.err) & read_access), - ] - - # Wishbone -> Avalon - self.comb += [ - avl.waitrequest.eq(~(wb.ack | wb.err) | burst_read), - avl.readdata.eq(readdata), - avl.readdatavalid.eq(readdatavalid), - ] - - # Avalon -> Wishbone - self.comb += [ - # Avalon is byte addresses, Wishbone word addressed - If(burst_cycle & last_burst_cycle, - wb.adr.eq(burst_address[word_width_bits:] + wishbone_base_address) - ).Else( - wb.adr.eq(avl.address[word_width_bits:] + wishbone_base_address) - ), - wb.dat_w.eq(avl.writedata), - wb.we.eq(avl.write), - wb.cyc.eq(read_access | avl.write | burst_cycle), - wb.stb.eq(read_access | avl.write), - wb.bte.eq(0b00), - ] - - self.submodules.fsm = fsm = FSM(reset_state="SINGLE") - fsm.act("SINGLE", - burst_cycle.eq(0), - wb.sel.eq(avl.byteenable), - If(avl.burstcount > 1, - wb.cti.eq(wishbone.CTI_BURST_INCREMENTING) - ).Else( - wb.cti.eq(wishbone.CTI_BURST_NONE) - ), - If(~avl.waitrequest & (avl.burstcount > 1), - burst_cycle.eq(1), - NextValue(burst_counter, avl.burstcount - 1), - NextValue(burst_address, avl.address + word_width), - NextValue(burst_sel, avl.byteenable), - If(avl.write, - NextState("BURST-WRITE")), - If(avl.read, - NextValue(burst_read, 1), - NextState("BURST-READ")) - ) - ) - fsm.act("BURST-WRITE", - burst_cycle.eq(1), - wb.sel.eq(burst_sel), - If(burst_counter > 1, - wb.cti.eq(wishbone.CTI_BURST_INCREMENTING) - ).Else( - If(burst_counter == 1, - wb.cti.eq(wishbone.CTI_BURST_END) - ).Else( - wb.cti.eq(wishbone.CTI_BURST_NONE) - ) - ), - If(~avl.waitrequest, - NextValue(burst_address, burst_address + word_width), - NextValue(burst_counter, burst_counter - 1)), - If(burst_counter == 0, - burst_cycle.eq(0), - wb.sel.eq(avl.byteenable), - NextValue(burst_sel, 0), - NextState("SINGLE") - ) - ) - fsm.act("BURST-READ", # TODO - burst_cycle.eq(1), - wb.stb.eq(1), - wb.sel.eq(burst_sel), - If(burst_counter > 1, - wb.cti.eq(wishbone.CTI_BURST_INCREMENTING), - ).Else( - If(burst_counter == 1, - wb.cti.eq(wishbone.CTI_BURST_END) - ).Else( - wb.cti.eq(wishbone.CTI_BURST_NONE) - ) - ), - If(wb.ack, - avl.readdatavalid.eq(1), - NextValue(burst_address, burst_address + word_width), - NextValue(burst_counter, burst_counter - 1) - ), - If(burst_counter == 0, - wb.cyc.eq(0), - wb.stb.eq(0), - wb.sel.eq(avl.byteenable), - NextValue(burst_sel, 0), - NextValue(burst_read, 0), - NextState("SINGLE")) - ) - -# Avalon-ST to/from native LiteX's stream ---------------------------------------------------------- - -# In native LiteX's streams, ready signal has no latency (similar to AXI). In Avalon-ST streams the -# ready signal has a latency: If ready is asserted on cycle n, then cycle n + latency is a "ready" -# in the LiteX/AXI's sense) cycle. This means that: -# - when converting to Avalon-ST, we need to add this latency on datas. -# - when converting from Avalon-ST, we need to make sure we are able to store datas for "latency" -# cycles after ready deassertion on the native interface. - -class Native2AvalonST(Module): - """Native LiteX's stream to Avalon-ST stream""" - def __init__(self, layout, latency=2): - self.sink = sink = stream.Endpoint(layout) - self.source = source = stream.Endpoint(layout) - - # # # - - _from = sink - for n in range(latency): - _to = stream.Endpoint(layout) - self.sync += _from.connect(_to, omit={"ready"}) - if n == 0: - self.sync += _to.valid.eq(sink.valid & source.ready) - _from = _to - self.comb += _to.connect(source, omit={"ready"}) - self.comb += sink.ready.eq(source.ready) - - -class AvalonST2Native(Module): - """Avalon-ST Stream to native LiteX's stream""" - def __init__(self, layout, latency=2): - self.sink = sink = stream.Endpoint(layout) - self.source = source = stream.Endpoint(layout) - - # # # - - buf = stream.SyncFIFO(layout, latency) - self.submodules += buf - self.comb += sink.connect(buf.sink, omit={"ready"}) - self.comb += sink.ready.eq(source.ready) - self.comb += buf.source.connect(source) diff --git a/litex/soc/interconnect/avalon/__init__.py b/litex/soc/interconnect/avalon/__init__.py new file mode 100644 index 000000000..1dac2b0b6 --- /dev/null +++ b/litex/soc/interconnect/avalon/__init__.py @@ -0,0 +1,6 @@ +# Avalon MM. +from litex.soc.interconnect.avalon.avalon_mm import AvalonMMInterface +from litex.soc.interconnect.avalon.avalon_mm_to_wishbone import AvalonMM2Wishbone + +# Avalon ST. +from litex.soc.interconnect.avalon.avalon_st import Native2AvalonST, AvalonST2Native \ No newline at end of file diff --git a/litex/soc/interconnect/avalon/avalon_mm.py b/litex/soc/interconnect/avalon/avalon_mm.py new file mode 100644 index 000000000..1fef0a9fe --- /dev/null +++ b/litex/soc/interconnect/avalon/avalon_mm.py @@ -0,0 +1,133 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2023 Hans Baier +# Copyright (c) 2023 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +"""Avalon support for LiteX""" + +from migen import * + +from litex.soc.interconnect import stream +from litex.soc.interconnect import wishbone + +# Avalon MM Layout --------------------------------------------------------------------------------- + +_layout = [ + ("address", "adr_width", DIR_M_TO_S), + ("writedata", "data_width", DIR_M_TO_S), + ("readdata", "data_width", DIR_S_TO_M), + ("readdatavalid", 1, DIR_S_TO_M), + ("byteenable", "sel_width", DIR_M_TO_S), + ("read", 1, DIR_M_TO_S), + ("write", 1, DIR_M_TO_S), + ("waitrequest", 1, DIR_S_TO_M), + ("burstbegin", 1, DIR_M_TO_S), # Optional. + ("burstcount", 8, DIR_M_TO_S), + ("chipselect", 1, DIR_M_TO_S), # Optional. +] + +# Avalon MM Interface ------------------------------------------------------------------------------ + +class AvalonMMInterface(Record): + def __init__(self, data_width=32, adr_width=30, **kwargs): + self.data_width = data_width + if kwargs.get("adr_width", False): + adr_width = kwargs["adr_width"] - int(log2(data_width//8)) + self.adr_width = adr_width + Record.__init__(self, set_layout_parameters(_layout, + adr_width = adr_width, + data_width = data_width, + sel_width = data_width//8)) + self.address.reset_less = True + self.writedata.reset_less = True + self.readdata.reset_less = True + self.byteenable.reset_less = True + + @staticmethod + def like(other): + return AvalonMMInterface(len(other.writedata)) + + def get_ios(self, bus_name="avl"): + subsignals = [] + for name, width, direction in self.layout: + subsignals.append(Subsignal(name, Pins(width))) + ios = [(bus_name , 0) + tuple(subsignals)] + return ios + + def connect_to_pads(self, pads, mode="master"): + assert mode in ["slave", "master"] + r = [] + for name, width, direction in self.layout: + sig = getattr(self, name) + pad = getattr(pads, name) + if mode == "master": + if direction == DIR_M_TO_S: + r.append(pad.eq(sig)) + else: + r.append(sig.eq(pad)) + else: + if direction == DIR_S_TO_M: + r.append(pad.eq(sig)) + else: + r.append(sig.eq(pad)) + return r + + def bus_read(self, address, byteenable=None, burstcount=1, chipselect=None): + if byteenable is None: + byteenable = 2**len(self.byteenable) - 1 + yield self.address.eq(address) + yield self.write.eq(0) + yield self.read.eq(1) + yield self.byteenable.eq(byteenable) + if burstcount != 1: + yield self.burstcount.eq(burstcount) + if chipselect is not None: + yield self.chipselect.eq(chipselect) + yield + while (yield self.waitrequest): + yield + yield self.read.eq(0) + # Actually don't care outside of a transaction this makes the traces look neater. + yield self.byteenable.eq(0) + if burstcount != 1: + yield self.burstcount.eq(0) + if chipselect is not None: + yield self.chipselect.eq(0) + + while not (yield self.readdatavalid): + yield + return (yield self.readdata) + + def continue_read_burst(self): + yield + return (yield self.readdata) + + def bus_write(self, address, writedata, byteenable=None, chipselect=None): + if not isinstance(writedata, list): + writedata = [ writedata ] + burstcount = len(writedata) + if byteenable is None: + byteenable = 2**len(self.byteenable) - 1 + yield self.address.eq(address) + yield self.write.eq(1) + yield self.read.eq(0) + yield self.byteenable.eq(byteenable) + if burstcount is not None: + yield self.burstcount.eq(burstcount) + if chipselect is not None: + yield self.chipselect.eq(chipselect) + for data in writedata: + yield self.writedata.eq(data) + yield + while (yield self.waitrequest): + yield + yield self.burstcount.eq(0) + yield self.writedata.eq(0) + yield self.write.eq(0) + # actually don't care outside of a transaction + # this makes the traces look neater + yield self.byteenable.eq(0) + if chipselect is not None: + yield self.chipselect.eq(0) diff --git a/litex/soc/interconnect/avalon/avalon_mm_to_wishbone.py b/litex/soc/interconnect/avalon/avalon_mm_to_wishbone.py new file mode 100644 index 000000000..9d2dcce0b --- /dev/null +++ b/litex/soc/interconnect/avalon/avalon_mm_to_wishbone.py @@ -0,0 +1,149 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2023 Hans Baier +# Copyright (c) 2023 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +"""Avalon support for LiteX""" + +from migen import * + +from litex.soc.interconnect import wishbone +from litex.soc.interconnect.avalon import AvalonMMInterface + +# Avalon MM <--> Wishbone Bridge ------------------------------------------------------------------- + +class AvalonMM2Wishbone(Module): + def __init__(self, data_width=32, address_width=32, wishbone_base_address=0x0, wishbone_extend_address_bits=0, avoid_combinatorial_loop=True): + word_width = data_width // 8 + word_width_bits = log2_int(word_width) + wishbone_address_width = address_width - word_width_bits + wishbone_extend_address_bits + + self.a2w_wb = wb = wishbone.Interface(data_width=data_width, adr_width=wishbone_address_width, bursting=True) + self.a2w_avl = avl = AvalonMMInterface (data_width=data_width, adr_width=address_width) + + read_access = Signal() + readdatavalid = Signal() + readdata = Signal(data_width) + + last_burst_cycle = Signal() + burst_cycle = Signal() + burst_counter = Signal.like(avl.burstcount) + burst_address = Signal(address_width) + burst_read = Signal() + burst_sel = Signal.like(avl.byteenable) + + self.sync += last_burst_cycle.eq(burst_cycle) + + # Some designs might have trouble with the combinatorial loop created + # by wb.ack, so cut it, incurring one clock cycle of overhead on each + # bus transaction + if avoid_combinatorial_loop: + self.sync += [ + If(wb.ack | wb.err, + read_access.eq(0) + ).Elif(avl.read, + read_access.eq(1) + ), + readdata.eq(wb.dat_r), + readdatavalid.eq((wb.ack | wb.err) & read_access), + ] + else: + self.comb += [ + read_access.eq(avl.read), + readdata.eq(wb.dat_r), + readdatavalid.eq((wb.ack | wb.err) & read_access), + ] + + # Wishbone -> Avalon + self.comb += [ + avl.waitrequest.eq(~(wb.ack | wb.err) | burst_read), + avl.readdata.eq(readdata), + avl.readdatavalid.eq(readdatavalid), + ] + + # Avalon -> Wishbone + self.comb += [ + # Avalon is byte addresses, Wishbone word addressed + If(burst_cycle & last_burst_cycle, + wb.adr.eq(burst_address[word_width_bits:] + wishbone_base_address) + ).Else( + wb.adr.eq(avl.address[word_width_bits:] + wishbone_base_address) + ), + wb.dat_w.eq(avl.writedata), + wb.we.eq(avl.write), + wb.cyc.eq(read_access | avl.write | burst_cycle), + wb.stb.eq(read_access | avl.write), + wb.bte.eq(0b00), + ] + + self.submodules.fsm = fsm = FSM(reset_state="SINGLE") + fsm.act("SINGLE", + burst_cycle.eq(0), + wb.sel.eq(avl.byteenable), + If(avl.burstcount > 1, + wb.cti.eq(wishbone.CTI_BURST_INCREMENTING) + ).Else( + wb.cti.eq(wishbone.CTI_BURST_NONE) + ), + If(~avl.waitrequest & (avl.burstcount > 1), + burst_cycle.eq(1), + NextValue(burst_counter, avl.burstcount - 1), + NextValue(burst_address, avl.address + word_width), + NextValue(burst_sel, avl.byteenable), + If(avl.write, + NextState("BURST-WRITE")), + If(avl.read, + NextValue(burst_read, 1), + NextState("BURST-READ")) + ) + ) + fsm.act("BURST-WRITE", + burst_cycle.eq(1), + wb.sel.eq(burst_sel), + If(burst_counter > 1, + wb.cti.eq(wishbone.CTI_BURST_INCREMENTING) + ).Else( + If(burst_counter == 1, + wb.cti.eq(wishbone.CTI_BURST_END) + ).Else( + wb.cti.eq(wishbone.CTI_BURST_NONE) + ) + ), + If(~avl.waitrequest, + NextValue(burst_address, burst_address + word_width), + NextValue(burst_counter, burst_counter - 1)), + If(burst_counter == 0, + burst_cycle.eq(0), + wb.sel.eq(avl.byteenable), + NextValue(burst_sel, 0), + NextState("SINGLE") + ) + ) + fsm.act("BURST-READ", # TODO + burst_cycle.eq(1), + wb.stb.eq(1), + wb.sel.eq(burst_sel), + If(burst_counter > 1, + wb.cti.eq(wishbone.CTI_BURST_INCREMENTING), + ).Else( + If(burst_counter == 1, + wb.cti.eq(wishbone.CTI_BURST_END) + ).Else( + wb.cti.eq(wishbone.CTI_BURST_NONE) + ) + ), + If(wb.ack, + avl.readdatavalid.eq(1), + NextValue(burst_address, burst_address + word_width), + NextValue(burst_counter, burst_counter - 1) + ), + If(burst_counter == 0, + wb.cyc.eq(0), + wb.stb.eq(0), + wb.sel.eq(avl.byteenable), + NextValue(burst_sel, 0), + NextValue(burst_read, 0), + NextState("SINGLE")) + ) diff --git a/litex/soc/interconnect/avalon/avalon_st.py b/litex/soc/interconnect/avalon/avalon_st.py new file mode 100644 index 000000000..726ef39af --- /dev/null +++ b/litex/soc/interconnect/avalon/avalon_st.py @@ -0,0 +1,53 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2019-2020 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +"""Avalon ST support for LiteX""" + +from migen import * + +from litex.soc.interconnect import stream + +# Avalon-ST to/from native LiteX's stream ---------------------------------------------------------- + +# In native LiteX's streams, ready signal has no latency (similar to AXI). In Avalon-ST streams the +# ready signal has a latency: If ready is asserted on cycle n, then cycle n + latency is a "ready" +# in the LiteX/AXI's sense) cycle. This means that: +# - when converting to Avalon-ST, we need to add this latency on datas. +# - when converting from Avalon-ST, we need to make sure we are able to store datas for "latency" +# cycles after ready deassertion on the native interface. + +class Native2AvalonST(Module): + """Native LiteX's stream to Avalon-ST stream""" + def __init__(self, layout, latency=2): + self.sink = sink = stream.Endpoint(layout) + self.source = source = stream.Endpoint(layout) + + # # # + + _from = sink + for n in range(latency): + _to = stream.Endpoint(layout) + self.sync += _from.connect(_to, omit={"ready"}) + if n == 0: + self.sync += _to.valid.eq(sink.valid & source.ready) + _from = _to + self.comb += _to.connect(source, omit={"ready"}) + self.comb += sink.ready.eq(source.ready) + + +class AvalonST2Native(Module): + """Avalon-ST Stream to native LiteX's stream""" + def __init__(self, layout, latency=2): + self.sink = sink = stream.Endpoint(layout) + self.source = source = stream.Endpoint(layout) + + # # # + + buf = stream.SyncFIFO(layout, latency) + self.submodules += buf + self.comb += sink.connect(buf.sink, omit={"ready"}) + self.comb += sink.ready.eq(source.ready) + self.comb += buf.source.connect(source) diff --git a/test/test_avalon.py b/test/test_avalon_mm.py similarity index 100% rename from test/test_avalon.py rename to test/test_avalon_mm.py