interconnect/avalon: Switch to directory/python package and split mm/st.

Similarly to what is done for AXI and will avoid too complex/large files.
This commit is contained in:
Florent Kermarrec 2023-05-08 09:25:16 +02:00
parent 7071304b10
commit 8e1a3880d3
6 changed files with 341 additions and 312 deletions

View File

@ -1,312 +0,0 @@
#
# This file is part of LiteX.
#
# Copyright (c) 2019-2020 Florent Kermarrec <florent@enjoy-digital.fr>
# Copyright (c) 2023 Hans Baier <hansfbaier@gmail.com>
# 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)

View File

@ -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

View File

@ -0,0 +1,133 @@
#
# This file is part of LiteX.
#
# Copyright (c) 2023 Hans Baier <hansfbaier@gmail.com>
# Copyright (c) 2023 Florent Kermarrec <florent@enjoy-digital.fr>
# 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)

View File

@ -0,0 +1,149 @@
#
# This file is part of LiteX.
#
# Copyright (c) 2023 Hans Baier <hansfbaier@gmail.com>
# Copyright (c) 2023 Florent Kermarrec <florent@enjoy-digital.fr>
# 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"))
)

View File

@ -0,0 +1,53 @@
#
# This file is part of LiteX.
#
# Copyright (c) 2019-2020 Florent Kermarrec <florent@enjoy-digital.fr>
# 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)