Skip to content

Commit 29389c9

Browse files
committed
Pad Not TCP with zeros
In testing on real hardware (Fomu), I was missing a lot of "tail end" packets... like, a *lot* of them. This prevented clients from getting the full body, hanging up, etc etc. This seems to be related to the 64B packet size of the USB ACM CDC TLA WTF protocol we're using under the hood. I haven't checked the gateware, but it's *acting like* it doesn't send the packet until there's >64B in the buffer. (Or maybe 64 exactly, and I was doing my math wrong before.) Luckily, we haven't been using "Stream ID zero", so we can redefine Not TCP such that every packet begins with a nonzero byte (stream ID), and zero bytes between packets are ignored. Then, pad on the device side, and *wham*, working. (Mostly. The LED endpoint doesn't always want to finish its job; needs more investigation.)
1 parent 35f05d4 commit 29389c9

4 files changed

Lines changed: 89 additions & 32 deletions

File tree

not_tcp/host.py

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def from_header(cls, header: Header, body: bytes) -> "Packet":
7979
)
8080

8181
def __len__(self):
82-
return len(Header) + len(self.body)
82+
return Header.length() + len(self.body)
8383

8484
def header(self) -> Header:
8585
assert self.stream_id >= 0
@@ -92,6 +92,11 @@ def to_bytes(self) -> bytes:
9292

9393
@classmethod
9494
def from_bytes(cls, buf: bytes) -> (Optional["Packet"], bytes):
95+
# Trim null bytes that prefix a packet.
96+
# This is (retroactively) why we start with stream ID 1!
97+
while len(buf) > 0 and buf[0] == 0:
98+
buf = buf[1:]
99+
95100
if len(buf) < Header.length():
96101
return None, buf
97102
header = Header.from_bytes(buf[:Header.length()])
@@ -171,17 +176,30 @@ async def run_outbound(self, number: int, writer: StreamWriter):
171176
consumed = (buffer_len - len(buffer))
172177
total_bytes += consumed
173178
if p is None:
174-
continue
175-
buffer = rem
176-
if packet_count == 0:
177-
assert p.start
178-
packet_count += 1
179-
if not p.to_host:
180-
# Ignore the packet
181-
continue
182-
writer.write(p.body)
183-
await writer.drain()
184-
if p.end:
185-
break
179+
if consumed > 0:
180+
olog.debug(
181+
f"consumed {consumed} padding zeros")
182+
183+
# No packet to consume. Get more data.
184+
rcvd = self.recv() # Has its own timeout, but isn't async. So:
185+
await asyncio.sleep(0)
186+
buffer += rcvd
187+
else:
188+
olog.debug(f"consumed {consumed} bytes")
189+
190+
if packet_count == 0:
191+
assert p.start
192+
packet_count += 1
193+
if not p.to_host:
194+
# Ignore the packet
195+
continue
196+
writer.write(p.body)
197+
await writer.drain()
198+
if p.end:
199+
break
200+
olog.info(
201+
f"device closed outbound connection for client {number}")
202+
203+
# TODO: Handle multiple streams.
186204
writer.close()
187205
await writer.wait_closed()

not_tcp/not_tcp.py

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import session
1616
from stream_utils import LimitForwarder
1717

18+
BUFFER_SIZE = 256
19+
1820

1921
class Flags(Struct):
2022
"""
@@ -119,7 +121,7 @@ def elaborate(self, platform):
119121
m = Module()
120122
connected = self.connected
121123
input_buffer = m.submodules.input_buffer = SyncFIFOBuffered(
122-
width=8, depth=256)
124+
width=8, depth=BUFFER_SIZE)
123125
m.d.comb += [
124126
self.stop.data.payload.eq(input_buffer.r_stream.payload),
125127
self.stop.data.valid.eq(input_buffer.r_stream.valid),
@@ -236,9 +238,8 @@ def __init__(self, stream_id):
236238
def elaborate(self, platform):
237239
m = Module()
238240

239-
# Each of these is big enough to buffer one full packet.
240241
output_buffer = m.submodules.output_buffer = SyncFIFOBuffered(
241-
width=8, depth=256)
242+
width=8, depth=BUFFER_SIZE)
242243

243244
m.d.comb += [
244245
output_buffer.w_stream.payload.eq(self.stop.data.payload),
@@ -254,23 +255,33 @@ def elaborate(self, platform):
254255
output_limiter.start.eq(0),
255256
self.bus.valid.eq(0)
256257
]
257-
connect(m, output_buffer.r_stream, output_limiter.inbound)
258-
258+
m.d.comb += [
259+
self.bus.payload.eq(output_limiter.outbound.payload),
260+
self.bus.valid.eq(output_limiter.outbound.valid),
261+
output_limiter.outbound.ready.eq(self.bus.ready),
262+
]
259263
flags_layout = UnionLayout({"bytes": unsigned(8), "flags": Flags})
260264

261265
# Flags for outbound packet:
262266
send_flags = Signal(flags_layout)
263267
m.d.sync += send_flags.flags.to_host.eq(1)
264268
send_len = Signal(8)
269+
# Pad up to 64 bytes of zeros, to ensure packet delivery.
270+
pad_len = Signal(8)
271+
272+
# Invariants:
273+
# - End is set iff ~active and the buffer is empty.
274+
# - We enter disconnected iff End is clear,
275+
# i.e. End has been sent.
265276

266277
# Cases in which we want to send a packet:
267278
with m.FSM(name="write"):
268279
with m.State("disconnected"):
280+
m.d.comb += Assert(~send_flags.flags.end)
269281
m.next = "disconnected"
270282
with m.If(self.stop.active):
271283
# Immediately send a "start" packet.
272284
m.d.sync += send_flags.flags.start.eq(1)
273-
m.d.sync += send_flags.flags.end.eq(0)
274285
m.d.sync += self.connected.eq(1)
275286
m.next = "write-stream"
276287

@@ -287,17 +298,27 @@ def elaborate(self, platform):
287298
# Lock in the level as the length of this packet.
288299
# We may send a short (zero-length) packet
289300
# to start or end the connection.
290-
m.d.sync += send_len.eq(output_buffer.r_level)
301+
m.d.sync += send_len.eq(output_buffer.level)
291302
# We send an explicit empty END packet.
292303
m.d.sync += send_flags.flags.end.eq(
293304
~self.stop.active &
294-
(output_buffer.r_level == Const(0)))
305+
(output_buffer.level == Const(0)))
295306
with m.If(self.bus.ready):
296307
m.next = "write-len"
297308
with m.State("write-len"):
298309
m.next = "write-len"
299310
m.d.comb += self.bus.payload.eq(send_len)
300311
m.d.comb += self.bus.valid.eq(1)
312+
313+
# Precompute padding on a cycle where we don't otherwise
314+
# have much to do.
315+
# In theory we only need to pad to 64...
316+
# but that still doesn't get us the stop byte...
317+
# so, double-padding?
318+
m.d.sync += [
319+
pad_len.eq(128 - ((3 + send_len) % 64))
320+
]
321+
301322
with m.If(self.bus.ready):
302323
m.next = "write-flags"
303324
with m.State("write-flags"):
@@ -314,22 +335,35 @@ def elaborate(self, platform):
314335
m.next = "write-body"
315336
with m.State("write-body"):
316337
m.next = "write-body"
338+
connect(m, output_buffer.r_stream, output_limiter.inbound)
339+
340+
with m.If(output_limiter.done):
341+
m.next = "zero-pad"
342+
m.d.comb += [
343+
output_limiter.count.eq(pad_len),
344+
output_limiter.start.eq(1),
345+
output_limiter.inbound.payload.eq(0),
346+
output_limiter.inbound.valid.eq(1),
347+
]
348+
349+
with m.State("zero-pad"):
350+
m.next = "zero-pad"
317351
m.d.comb += [
318-
self.bus.payload.eq(output_limiter.outbound.payload),
319-
self.bus.valid.eq(output_limiter.outbound.valid),
320-
output_limiter.outbound.ready.eq(self.bus.ready),
352+
output_limiter.inbound.payload.eq(0),
353+
output_limiter.inbound.valid.eq(1),
321354
]
322355

323356
with m.If(output_limiter.done):
324-
m.d.sync += [
325-
send_flags.flags.start.eq(0),
326-
send_flags.flags.end.eq(0),
327-
]
328357
with m.If(send_flags.flags.end):
329358
m.d.sync += self.connected.eq(0)
330359
m.next = "disconnected"
331360
with m.Else():
332361
m.next = "write-stream"
362+
# In either branch, we've sent a start packet.
363+
m.d.sync += [
364+
send_flags.flags.start.eq(0),
365+
send_flags.flags.end.eq(0),
366+
]
333367

334368
return m
335369

not_tcp/not_tcp_test.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,9 @@ async def driver(ctx):
7878
while len(rcvd) > 0:
7979
# All data should be packetized.
8080
(p, remainder) = Packet.from_bytes(rcvd)
81-
assert p is not None, f"remaining data: {rcvd}"
82-
packets += [p]
81+
assert (p is not None) or (len(remainder) == 0)
82+
if p is not None:
83+
packets += [p]
8384
rcvd = remainder
8485
bodies = bytes()
8586
for i in range(len(packets)):

stream_fixtures.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,21 @@ def collect_queue(self, q: queue.Queue[bytes], batch_size: int = 100,
8181
stream = self._stream
8282

8383
async def collector(ctx):
84-
ctx.set(stream.ready, 1)
84+
ready = self.is_ready()
85+
ctx.set(stream.ready, ready)
8586
countup = 0
8687
batch = bytes()
8788

8889
async for clk_edge, rst_value, valid, payload in ctx.tick().sample(
8990
stream.valid, stream.payload):
9091
if rst_value or (not clk_edge):
9192
continue
92-
if valid == 1:
93+
if ready == 1 and valid == 1:
9394
# We just transferred a payload byte.
9495
batch += bytes([payload])
96+
ready = self.is_ready()
97+
else:
98+
ready = ready | self.is_ready()
9599
countup += 1
96100

97101
batch_exceeded = len(batch) >= batch_size

0 commit comments

Comments
 (0)