tools/litex_server: enable read_merger with CommUDP.
[litex.git] / litex / tools / litex_server.py
1 #!/usr/bin/env python3
2
3 # This file is Copyright (c) 2015-2019 Florent Kermarrec <florent@enjoy-digital.fr>
4 # This file is Copyright (c) 2019 Sean Cross <sean@xobs.io>
5 # This file is Copyright (c) 2018 Felix Held <felix-github@felixheld.de>
6 # License: BSD
7
8 import argparse
9
10 import sys
11 import socket
12 import time
13 import threading
14
15 from litex.tools.remote.etherbone import EtherbonePacket, EtherboneRecord, EtherboneWrites
16 from litex.tools.remote.etherbone import EtherboneIPC
17
18 def _read_merger(addrs, max_length=256):
19 """Sequential reads merger
20
21 Take a list of read addresses as input and merge the sequential reads in (base, length) tuples:
22 Example: [0x0, 0x4, 0x10, 0x14] input will return [(0x0,2), (0x10,2)].
23
24 This is useful for UARTBone/Etherbone where command/response roundtrip delay is responsible for
25 most of the access delay and allows minimizing number of commands by grouping them in UARTBone
26 packets.
27 """
28 base = None
29 length = 0
30 for addr in addrs:
31 if (addr - (4*length) != base) or (length == max_length):
32 if base is not None:
33 yield (base, length)
34 base = addr
35 length = 0
36 length += 1
37 yield (base, length)
38
39
40 class RemoteServer(EtherboneIPC):
41 def __init__(self, comm, bind_ip, bind_port=1234):
42 self.comm = comm
43 self.bind_ip = bind_ip
44 self.bind_port = bind_port
45 self.lock = False
46
47 def open(self):
48 if hasattr(self, "socket"):
49 return
50 self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
51 if hasattr(socket, "SO_REUSEADDR"):
52 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
53 if hasattr(socket, "SO_REUSEPORT"):
54 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
55 self.socket.bind((self.bind_ip, self.bind_port))
56 print("tcp port: {:d}".format(self.bind_port))
57 self.socket.listen(1)
58 self.comm.open()
59
60 def close(self):
61 self.comm.close()
62 if not hasattr(self, "socket"):
63 return
64 self.socket.close()
65 del self.socket
66
67 def _serve_thread(self):
68 while True:
69 client_socket, addr = self.socket.accept()
70 print("Connected with " + addr[0] + ":" + str(addr[1]))
71 try:
72 while True:
73 try:
74 packet = self.receive_packet(client_socket)
75 if packet == 0:
76 break
77 except:
78 break
79 packet = EtherbonePacket(packet)
80 packet.decode()
81
82 record = packet.records.pop()
83
84 # wait for lock
85 while self.lock:
86 time.sleep(0.01)
87
88 # set lock
89 self.lock = True
90
91 # handle writes:
92 if record.writes != None:
93 self.comm.write(record.writes.base_addr, record.writes.get_datas())
94
95 # handle reads
96 if record.reads != None:
97 max_length = {
98 "CommUART": 256,
99 "CommUDP": 4,
100 }.get(self.comm.__class__.__name__, 1)
101 reads = []
102 for addr, length in _read_merger(record.reads.get_addrs(), max_length=max_length):
103 reads += self.comm.read(addr, length)
104
105 record = EtherboneRecord()
106 record.writes = EtherboneWrites(datas=reads)
107 record.wcount = len(record.writes)
108
109 packet = EtherbonePacket()
110 packet.records = [record]
111 packet.encode()
112 self.send_packet(client_socket, packet)
113
114 # release lock
115 self.lock = False
116
117 finally:
118 print("Disconnect")
119 client_socket.close()
120
121 def start(self, nthreads):
122 for i in range(nthreads):
123 self.serve_thread = threading.Thread(target=self._serve_thread)
124 self.serve_thread.setDaemon(True)
125 self.serve_thread.start()
126
127
128 def main():
129 print("LiteX remote server")
130 parser = argparse.ArgumentParser()
131 # Common arguments
132 parser.add_argument("--bind-ip", default="localhost",
133 help="Host bind address")
134 parser.add_argument("--bind-port", default=1234,
135 help="Host bind port")
136
137 # UART arguments
138 parser.add_argument("--uart", action="store_true",
139 help="Select UART interface")
140 parser.add_argument("--uart-port", default=None,
141 help="Set UART port")
142 parser.add_argument("--uart-baudrate", default=115200,
143 help="Set UART baudrate")
144
145 # UDP arguments
146 parser.add_argument("--udp", action="store_true",
147 help="Select UDP interface")
148 parser.add_argument("--udp-ip", default="192.168.1.50",
149 help="Set UDP remote IP address")
150 parser.add_argument("--udp-port", default=1234,
151 help="Set UDP remote port")
152
153 # PCIe arguments
154 parser.add_argument("--pcie", action="store_true",
155 help="Select PCIe interface")
156 parser.add_argument("--pcie-bar", default=None,
157 help="Set PCIe BAR")
158
159 # USB arguments
160 parser.add_argument("--usb", action="store_true",
161 help="Select USB interface")
162 parser.add_argument("--usb-vid", default=None,
163 help="Set USB vendor ID")
164 parser.add_argument("--usb-pid", default=None,
165 help="Set USB product ID")
166 parser.add_argument("--usb-max-retries", default=10,
167 help="Number of times to try reconnecting to USB")
168 args = parser.parse_args()
169
170
171 if args.uart:
172 from litex.tools.remote.comm_uart import CommUART
173 if args.uart_port is None:
174 print("Need to specify --uart-port, exiting.")
175 exit()
176 uart_port = args.uart_port
177 uart_baudrate = int(float(args.uart_baudrate))
178 print("[CommUART] port: {} / baudrate: {} / ".format(uart_port, uart_baudrate), end="")
179 comm = CommUART(uart_port, uart_baudrate)
180 elif args.udp:
181 from litex.tools.remote.comm_udp import CommUDP
182 udp_ip = args.udp_ip
183 udp_port = int(args.udp_port)
184 print("[CommUDP] ip: {} / port: {} / ".format(udp_ip, udp_port), end="")
185 comm = CommUDP(udp_ip, udp_port)
186 elif args.pcie:
187 from litex.tools.remote.comm_pcie import CommPCIe
188 pcie_bar = args.pcie_bar
189 if args.pcie_bar is None:
190 print("Need to speficy --pcie-bar, exiting.")
191 exit()
192 print("[CommPCIe] bar: {} / ".format(args.pcie_bar), end="")
193 comm = CommPCIe(args.pcie_bar)
194 elif args.usb:
195 from litex.tools.remote.comm_usb import CommUSB
196 if args.usb_pid is None and args.usb_vid is None:
197 print("Need to speficy --usb-vid or --usb-pid, exiting.")
198 exit()
199 print("[CommUSB] vid: {} / pid: {} / ".format(args.usb_vid, args.usb_pid), end="")
200 pid = args.usb_pid
201 if pid is not None:
202 pid = int(pid, base=0)
203 vid = args.usb_vid
204 if vid is not None:
205 vid = int(vid, base=0)
206 comm = CommUSB(vid=vid, pid=pid, max_retries=args.usb_max_retries)
207 else:
208 parser.print_help()
209 exit()
210
211 server = RemoteServer(comm, args.bind_ip, int(args.bind_port))
212 server.open()
213 server.start(4)
214 try:
215 import time
216 while True: time.sleep(100)
217 except KeyboardInterrupt:
218 pass
219
220 if __name__ == "__main__":
221 main()