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:
parent
7071304b10
commit
8e1a3880d3
|
@ -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)
|
|
|
@ -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
|
|
@ -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)
|
|
@ -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"))
|
||||||
|
)
|
|
@ -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)
|
Loading…
Reference in New Issue