Merge sequential reads for the UART litex_server backend
authorChristian Klarhorst <cklarhor@techfak.uni-bielefeld.de>
Sun, 26 Jul 2020 11:19:32 +0000 (13:19 +0200)
committerChristian Klarhorst <cklarhor@techfak.uni-bielefeld.de>
Sun, 26 Jul 2020 11:19:32 +0000 (13:19 +0200)
The UART backend writes [read identifier, num_reads, addr] for
every read request.
Etherbone packets are able to include multiple read requests.
Therefore, it is beneficial to merge sequential read requests to reduce writes
(and possible latency overhead).

Benchmark:
A typical litescope fetch script with the following
signals [ddrphy.dfi,cpu.ibus.cyc,cpu.ibus.stb] results in 1 read for the
data_valid register and 24 sequential reads for the scope data per timestamp.
Fetching data for a capture length of 512 over a 921600 baud UART (arty board)
took:
205s (current master branch)
 18s (with this merge function)

The proposed merger only merges read requests from one etherbone packet
at a time and doesn't change the read order.

litex/tools/litex_server.py

index b35bda5d64fb8be6629c6541769bd22ef8c5234b..b7843c40a47bbcdda351fc75f69cdfef1f4a4dd2 100755 (executable)
@@ -15,6 +15,22 @@ import threading
 from litex.tools.remote.etherbone import EtherbonePacket, EtherboneRecord, EtherboneWrites
 from litex.tools.remote.etherbone import EtherboneIPC
 
+# Merges sequential reads:
+# input:  list of addresses
+# output: list of (start_addr, read_size)
+# example: [0x0, 0x4, 0x10, 0x14] -> [(0x0,2), (0x10,2)]
+def _read_merger(addrs):
+    addr_start = addrs[0]
+    num_reads  = 1
+    for addr in addrs[1:]:
+        if addr_start+4*num_reads != addr:
+            yield (addr_start, num_reads)
+            addr_start = addr
+            num_reads   = 1
+        else:
+            num_reads += 1
+    yield (addr_start, num_reads)
+
 
 class RemoteServer(EtherboneIPC):
     def __init__(self, comm, bind_ip, bind_port=1234):
@@ -74,8 +90,12 @@ class RemoteServer(EtherboneIPC):
                     # handle reads
                     if record.reads != None:
                         reads = []
-                        for addr in record.reads.get_addrs():
-                            reads.append(self.comm.read(addr))
+                        if "CommUART" == self.comm.__class__.__name__:
+                            for base_addr, num_reads in _read_merger(record.reads.get_addrs()):
+                                reads += self.comm.read(base_addr, num_reads)
+                        else:
+                            for addr in record.reads.get_addrs():
+                                reads.append(self.comm.read(addr))
 
                         record = EtherboneRecord()
                         record.writes = EtherboneWrites(datas=reads)