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 ---------------------------------------------------------------------------------------- # UART Bone ----------------------------------------------------------------------------------------
CMD_WRITE = 0x01 CMD_WRITE_BURST_INCR = 0x01
CMD_READ = 0x02 CMD_READ_BURST_INCR = 0x02
CMD_WRITE_BURST_FIXED = 0x03
CMD_READ_BURST_FIXED = 0x04
class Stream2Wishbone(Module): 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.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) cmd = Signal(8, reset_less=True)
incr = Signal()
length = Signal(8, reset_less=True) length = Signal(8, reset_less=True)
address = Signal(address_width, reset_less=True) address = Signal(address_width, reset_less=True)
data = Signal(data_width, reset_less=True) data = Signal(data_width, reset_less=True)
@ -277,25 +282,27 @@ class Stream2Wishbone(Module):
fsm.act("RECEIVE-CMD", fsm.act("RECEIVE-CMD",
NextValue(bytes_count, 0), NextValue(bytes_count, 0),
NextValue(words_count, 0), NextValue(words_count, 0),
If(phy.source.valid, If(sink.valid,
NextValue(cmd, phy.source.data), NextValue(cmd, sink.data),
NextState("RECEIVE-LENGTH") NextState("RECEIVE-LENGTH")
) )
) )
fsm.act("RECEIVE-LENGTH", fsm.act("RECEIVE-LENGTH",
If(phy.source.valid, If(sink.valid,
NextValue(length, phy.source.data), NextValue(length, sink.data),
NextState("RECEIVE-ADDRESS") NextState("RECEIVE-ADDRESS")
) )
) )
fsm.act("RECEIVE-ADDRESS", fsm.act("RECEIVE-ADDRESS",
If(phy.source.valid, If(sink.valid,
NextValue(address, Cat(phy.source.data, address)), NextValue(address, Cat(sink.data, address)),
NextValue(bytes_count, bytes_count + 1), NextValue(bytes_count, bytes_count + 1),
If(bytes_count_done, 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") 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") NextState("READ-DATA")
).Else( ).Else(
NextState("RECEIVE-CMD") NextState("RECEIVE-CMD")
@ -304,8 +311,8 @@ class Stream2Wishbone(Module):
) )
) )
fsm.act("RECEIVE-DATA", fsm.act("RECEIVE-DATA",
If(phy.source.valid, If(sink.valid,
NextValue(data, Cat(phy.source.data, data)), NextValue(data, Cat(sink.data, data)),
NextValue(bytes_count, bytes_count + 1), NextValue(bytes_count, bytes_count + 1),
If(bytes_count_done, If(bytes_count_done,
NextState("WRITE-DATA") NextState("WRITE-DATA")
@ -323,7 +330,7 @@ class Stream2Wishbone(Module):
self.wishbone.cyc.eq(1), self.wishbone.cyc.eq(1),
If(self.wishbone.ack, If(self.wishbone.ack,
NextValue(words_count, words_count + 1), NextValue(words_count, words_count + 1),
NextValue(address, address + 1), NextValue(address, address + incr),
If(words_count_done, If(words_count_done,
NextState("RECEIVE-CMD") NextState("RECEIVE-CMD")
).Else( ).Else(
@ -342,15 +349,15 @@ class Stream2Wishbone(Module):
) )
cases = {} cases = {}
for i, n in enumerate(reversed(range(data_width//8))): 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) self.comb += Case(bytes_count, cases)
fsm.act("SEND-DATA", fsm.act("SEND-DATA",
phy.sink.valid.eq(1), source.valid.eq(1),
If(phy.sink.ready, If(source.ready,
NextValue(bytes_count, bytes_count + 1), NextValue(bytes_count, bytes_count + 1),
If(bytes_count_done, If(bytes_count_done,
NextValue(words_count, words_count + 1), NextValue(words_count, words_count + 1),
NextValue(address, address + 1), NextValue(address, address + incr),
If(words_count_done, If(words_count_done,
NextState("RECEIVE-CMD") NextState("RECEIVE-CMD")
).Else( ).Else(
@ -359,9 +366,9 @@ class Stream2Wishbone(Module):
) )
) )
) )
self.comb += phy.sink.last.eq(bytes_count_done & words_count_done) self.comb += source.last.eq(bytes_count_done & words_count_done)
if hasattr(phy.sink, "length"): if hasattr(source, "length"):
self.comb += phy.sink.length.eq((data_width//8)*length) self.comb += source.length.eq((data_width//8)*length)
class UARTBone(Stream2Wishbone): class UARTBone(Stream2Wishbone):

View File

@ -36,11 +36,12 @@ class RemoteClient(EtherboneIPC, CSRBuilder):
self.socket.close() self.socket.close()
del self.socket 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 length_int = 1 if length is None else length
# prepare packet # prepare packet
record = EtherboneRecord() 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) record.rcount = len(record.reads)
# send packet # 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 EtherbonePacket, EtherboneRecord, EtherboneWrites
from litex.tools.remote.etherbone import EtherboneIPC 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 """Sequential reads merger
Take a list of read addresses as input and merge the sequential reads in (base, length) tuples: Take a list of read addresses as input and merge the sequential/fixed reads in (base, length, burst) tuples:
Example: [0x0, 0x4, 0x10, 0x14] input will return [(0x0,2), (0x10,2)]. 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 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 most of the access delay and allows minimizing number of commands by grouping them in UARTBone
packets. packets.
""" """
base = None assert "incr" in bursts
length = 0 burst_base = addrs[0]
for addr in addrs: burst_length = 1
if (addr - (4*length) != base) or (length == max_length): burst_type = "incr"
if base is not None: for addr in addrs[1:]:
yield (base, length) merged = False
base = addr # Try to merge to a "fixed" burst if supported
length = 0 if ("fixed" in bursts):
length += 1 # If current burst matches
yield (base, length) 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): class RemoteServer(EtherboneIPC):
def __init__(self, comm, bind_ip, bind_port=1234): def __init__(self, comm, bind_ip, bind_port=1234):
@ -101,9 +125,14 @@ class RemoteServer(EtherboneIPC):
"CommUART": 256, "CommUART": 256,
"CommUDP": 4, "CommUDP": 4,
}.get(self.comm.__class__.__name__, 1) }.get(self.comm.__class__.__name__, 1)
bursts = {
"CommUART": ["incr", "fixed"]
}.get(self.comm.__class__.__name__, ["incr"])
reads = [] reads = []
for addr, length in _read_merger(record.reads.get_addrs(), max_length=max_length): for addr, length, burst in _read_merger(record.reads.get_addrs(),
reads += self.comm.read(addr, length) max_length = max_length,
bursts = bursts):
reads += self.comm.read(addr, length, burst)
record = EtherboneRecord() record = EtherboneRecord()
record.writes = EtherboneWrites(datas=reads) record.writes = EtherboneWrites(datas=reads)

View File

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

View File

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

View File

@ -29,7 +29,8 @@ class CommUDP:
self.socket.close() self.socket.close()
del self.socket 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 length_int = 1 if length is None else length
record = EtherboneRecord() record = EtherboneRecord()
record.reads = EtherboneReads(addrs=[addr+4*j for j in range(length_int)]) record.reads = EtherboneReads(addrs=[addr+4*j for j in range(length_int)])

View File

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