From b44ca6d61ae03f3d7dd2c6f7c87dd41afc929c08 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Fri, 28 Aug 2020 09:42:38 +0200 Subject: [PATCH] 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. --- litex/soc/cores/uart.py | 51 ++++++++++++++++------------ litex/tools/litex_client.py | 5 +-- litex/tools/litex_server.py | 59 ++++++++++++++++++++++++--------- litex/tools/remote/comm_pcie.py | 3 +- litex/tools/remote/comm_uart.py | 24 +++++++++----- litex/tools/remote/comm_udp.py | 3 +- litex/tools/remote/comm_usb.py | 3 +- 7 files changed, 98 insertions(+), 50 deletions(-) diff --git a/litex/soc/cores/uart.py b/litex/soc/cores/uart.py index 3e8e22707..7cf79cd22 100644 --- a/litex/soc/cores/uart.py +++ b/litex/soc/cores/uart.py @@ -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): diff --git a/litex/tools/litex_client.py b/litex/tools/litex_client.py index 0ed7a085f..dceda94c7 100644 --- a/litex/tools/litex_client.py +++ b/litex/tools/litex_client.py @@ -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 diff --git a/litex/tools/litex_server.py b/litex/tools/litex_server.py index 7d5f72393..1fe0b780c 100755 --- a/litex/tools/litex_server.py +++ b/litex/tools/litex_server.py @@ -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) diff --git a/litex/tools/remote/comm_pcie.py b/litex/tools/remote/comm_pcie.py index 52c937d95..0d024a858 100644 --- a/litex/tools/remote/comm_pcie.py +++ b/litex/tools/remote/comm_pcie.py @@ -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): diff --git a/litex/tools/remote/comm_uart.py b/litex/tools/remote/comm_uart.py index 7fd08cde4..4747abc3f 100644 --- a/litex/tools/remote/comm_uart.py +++ b/litex/tools/remote/comm_uart.py @@ -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"))) diff --git a/litex/tools/remote/comm_udp.py b/litex/tools/remote/comm_udp.py index 388737903..9035de842 100644 --- a/litex/tools/remote/comm_udp.py +++ b/litex/tools/remote/comm_udp.py @@ -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)]) diff --git a/litex/tools/remote/comm_usb.py b/litex/tools/remote/comm_usb.py index 87432dfac..9acead30c 100644 --- a/litex/tools/remote/comm_usb.py +++ b/litex/tools/remote/comm_usb.py @@ -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):