soc/core/uart: add fixed burst support to UARTBone.

Allows speeding-up consecutives accesses on the same address. This is currently
used by LiteDRAM bench to speed-up the logging of the BIOS over the crossover UART,
but could be useful for other purposes.
This commit is contained in:
Florent Kermarrec 2020-08-28 09:42:38 +02:00
parent 1fb48d308e
commit b44ca6d61a
7 changed files with 98 additions and 50 deletions

View File

@ -249,17 +249,22 @@ class UART(Module, AutoCSR, UARTInterface):
# UART Bone ----------------------------------------------------------------------------------------
CMD_WRITE = 0x01
CMD_READ = 0x02
CMD_WRITE_BURST_INCR = 0x01
CMD_READ_BURST_INCR = 0x02
CMD_WRITE_BURST_FIXED = 0x03
CMD_READ_BURST_FIXED = 0x04
class Stream2Wishbone(Module):
def __init__(self, phy, clk_freq, data_width=32, address_width=32):
def __init__(self, phy=None, clk_freq=None, data_width=32, address_width=32):
self.sink = sink = stream.Endpoint([("data", 8)]) if phy is None else phy.source
self.source = source = stream.Endpoint([("data", 8)]) if phy is None else phy.sink
self.wishbone = wishbone.Interface()
self.comb += phy.source.ready.eq(1) # Always accept incoming stream.
self.comb += sink.ready.eq(1) # Always accept incoming stream.
# # #
cmd = Signal(8, reset_less=True)
incr = Signal()
length = Signal(8, reset_less=True)
address = Signal(address_width, reset_less=True)
data = Signal(data_width, reset_less=True)
@ -277,25 +282,27 @@ class Stream2Wishbone(Module):
fsm.act("RECEIVE-CMD",
NextValue(bytes_count, 0),
NextValue(words_count, 0),
If(phy.source.valid,
NextValue(cmd, phy.source.data),
If(sink.valid,
NextValue(cmd, sink.data),
NextState("RECEIVE-LENGTH")
)
)
fsm.act("RECEIVE-LENGTH",
If(phy.source.valid,
NextValue(length, phy.source.data),
If(sink.valid,
NextValue(length, sink.data),
NextState("RECEIVE-ADDRESS")
)
)
fsm.act("RECEIVE-ADDRESS",
If(phy.source.valid,
NextValue(address, Cat(phy.source.data, address)),
If(sink.valid,
NextValue(address, Cat(sink.data, address)),
NextValue(bytes_count, bytes_count + 1),
If(bytes_count_done,
If(cmd == CMD_WRITE,
If((cmd == CMD_WRITE_BURST_INCR) | (cmd == CMD_WRITE_BURST_FIXED),
NextValue(incr, cmd == CMD_WRITE_BURST_INCR),
NextState("RECEIVE-DATA")
).Elif(cmd == CMD_READ,
).Elif((cmd == CMD_READ_BURST_INCR) | (cmd == CMD_READ_BURST_FIXED),
NextValue(incr, cmd == CMD_READ_BURST_INCR),
NextState("READ-DATA")
).Else(
NextState("RECEIVE-CMD")
@ -304,8 +311,8 @@ class Stream2Wishbone(Module):
)
)
fsm.act("RECEIVE-DATA",
If(phy.source.valid,
NextValue(data, Cat(phy.source.data, data)),
If(sink.valid,
NextValue(data, Cat(sink.data, data)),
NextValue(bytes_count, bytes_count + 1),
If(bytes_count_done,
NextState("WRITE-DATA")
@ -323,7 +330,7 @@ class Stream2Wishbone(Module):
self.wishbone.cyc.eq(1),
If(self.wishbone.ack,
NextValue(words_count, words_count + 1),
NextValue(address, address + 1),
NextValue(address, address + incr),
If(words_count_done,
NextState("RECEIVE-CMD")
).Else(
@ -342,15 +349,15 @@ class Stream2Wishbone(Module):
)
cases = {}
for i, n in enumerate(reversed(range(data_width//8))):
cases[i] = phy.sink.data.eq(data[8*n:])
cases[i] = source.data.eq(data[8*n:])
self.comb += Case(bytes_count, cases)
fsm.act("SEND-DATA",
phy.sink.valid.eq(1),
If(phy.sink.ready,
source.valid.eq(1),
If(source.ready,
NextValue(bytes_count, bytes_count + 1),
If(bytes_count_done,
NextValue(words_count, words_count + 1),
NextValue(address, address + 1),
NextValue(address, address + incr),
If(words_count_done,
NextState("RECEIVE-CMD")
).Else(
@ -359,9 +366,9 @@ class Stream2Wishbone(Module):
)
)
)
self.comb += phy.sink.last.eq(bytes_count_done & words_count_done)
if hasattr(phy.sink, "length"):
self.comb += phy.sink.length.eq((data_width//8)*length)
self.comb += source.last.eq(bytes_count_done & words_count_done)
if hasattr(source, "length"):
self.comb += source.length.eq((data_width//8)*length)
class UARTBone(Stream2Wishbone):

View File

@ -36,11 +36,12 @@ class RemoteClient(EtherboneIPC, CSRBuilder):
self.socket.close()
del self.socket
def read(self, addr, length=None):
def read(self, addr, length=None, burst="incr"):
length_int = 1 if length is None else length
# prepare packet
record = EtherboneRecord()
record.reads = EtherboneReads(addrs=[self.base_address + addr + 4*j for j in range(length_int)])
incr = (burst == "incr")
record.reads = EtherboneReads(addrs=[self.base_address + addr + 4*incr*j for j in range(length_int)])
record.rcount = len(record.reads)
# send packet

View File

@ -18,27 +18,51 @@ import threading
from litex.tools.remote.etherbone import EtherbonePacket, EtherboneRecord, EtherboneWrites
from litex.tools.remote.etherbone import EtherboneIPC
def _read_merger(addrs, max_length=256):
def _read_merger(addrs, max_length=256, bursts=["incr", "fixed"]):
"""Sequential reads merger
Take a list of read addresses as input and merge the sequential reads in (base, length) tuples:
Example: [0x0, 0x4, 0x10, 0x14] input will return [(0x0,2), (0x10,2)].
Take a list of read addresses as input and merge the sequential/fixed reads in (base, length, burst) tuples:
Example: [0x0, 0x4, 0x10, 0x14, 0x20, 0x20] input will return [(0x0,2, "incr"), (0x10,2, "incr"), (0x20,2, "fixed")].
This is useful for UARTBone/Etherbone where command/response roundtrip delay is responsible for
most of the access delay and allows minimizing number of commands by grouping them in UARTBone
packets.
"""
base = None
length = 0
for addr in addrs:
if (addr - (4*length) != base) or (length == max_length):
if base is not None:
yield (base, length)
base = addr
length = 0
length += 1
yield (base, length)
assert "incr" in bursts
burst_base = addrs[0]
burst_length = 1
burst_type = "incr"
for addr in addrs[1:]:
merged = False
# Try to merge to a "fixed" burst if supported
if ("fixed" in bursts):
# If current burst matches
if (burst_type in [None, "fixed"]) or (burst_length == 1):
# If addr matches
if (addr == burst_base):
if (burst_length != max_length):
burst_type = "fixed"
burst_length += 1
merged = True
# Try to merge to an "incr" burst if supported
if ("incr" in bursts):
# If current burst matches
if (burst_type in [None, "incr"]) or (burst_length == 1):
# If addr matches
if (addr == burst_base + (4 * burst_length)):
if (burst_length != max_length):
burst_type = "incr"
burst_length += 1
merged = True
# Generate current burst if addr has not able to merge
if not merged:
yield (burst_base, burst_length, burst_type)
burst_base = addr
burst_length = 1
burst_type = "incr"
yield (burst_base, burst_length, burst_type)
class RemoteServer(EtherboneIPC):
def __init__(self, comm, bind_ip, bind_port=1234):
@ -101,9 +125,14 @@ class RemoteServer(EtherboneIPC):
"CommUART": 256,
"CommUDP": 4,
}.get(self.comm.__class__.__name__, 1)
bursts = {
"CommUART": ["incr", "fixed"]
}.get(self.comm.__class__.__name__, ["incr"])
reads = []
for addr, length in _read_merger(record.reads.get_addrs(), max_length=max_length):
reads += self.comm.read(addr, length)
for addr, length, burst in _read_merger(record.reads.get_addrs(),
max_length = max_length,
bursts = bursts):
reads += self.comm.read(addr, length, burst)
record = EtherboneRecord()
record.writes = EtherboneWrites(datas=reads)

View File

@ -26,7 +26,8 @@ class CommPCIe:
del self.file
self.mmap.close()
def read(self, addr, length=None):
def read(self, addr, length=None, burst="incr"):
assert burst == "incr"
data = []
length_int = 1 if length is None else length
for i in range(length_int):

View File

@ -7,12 +7,12 @@
import serial
import struct
CMD_WRITE_BURST_INCR = 0x01
CMD_READ_BURST_INCR = 0x02
CMD_WRITE_BURST_FIXED = 0x03
CMD_READ_BURST_FIXED = 0x04
class CommUART:
msg_type = {
"write": 0x01,
"read": 0x02
}
def __init__(self, port, baudrate=115200, debug=False):
self.port = port
self.baudrate = str(baudrate)
@ -48,11 +48,15 @@ class CommUART:
if self.port.inWaiting() > 0:
self.port.read(self.port.inWaiting())
def read(self, addr, length=None):
def read(self, addr, length=None, burst="incr"):
self._flush()
data = []
length_int = 1 if length is None else length
self._write([self.msg_type["read"], length_int])
cmd = {
"incr" : CMD_READ_BURST_INCR,
"fixed": CMD_READ_BURST_FIXED,
}[burst]
self._write([cmd, length_int])
self._write(list((addr//4).to_bytes(4, byteorder="big")))
for i in range(length_int):
value = int.from_bytes(self._read(4), "big")
@ -63,14 +67,18 @@ class CommUART:
data.append(value)
return data
def write(self, addr, data):
def write(self, addr, data, burst="incr"):
self._flush()
data = data if isinstance(data, list) else [data]
length = len(data)
offset = 0
while length:
size = min(length, 8)
self._write([self.msg_type["write"], size])
cmd = {
"incr" : CMD_WRITE_BURST_INCR,
"fixed": CMD_WRITE_BURST_FIXED,
}[burst]
self._write([cmd, size])
self._write(list(((addr+offset)//4).to_bytes(4, byteorder="big")))
for i, value in enumerate(data[offset:offset+size]):
self._write(list(value.to_bytes(4, byteorder="big")))

View File

@ -29,7 +29,8 @@ class CommUDP:
self.socket.close()
del self.socket
def read(self, addr, length=None):
def read(self, addr, length=None, burst="incr"):
assert burst == "incr"
length_int = 1 if length is None else length
record = EtherboneRecord()
record.reads = EtherboneReads(addrs=[addr+4*j for j in range(length_int)])

View File

@ -90,7 +90,8 @@ class CommUSB:
return
del self.dev
def read(self, addr, length=None):
def read(self, addr, length=None, burst="incr"):
assert burst == "incr"
data = []
length_int = 1 if length is None else length
for i in range(length_int):