switchboard-hw 0.3.0__cp314-cp314-macosx_11_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. _switchboard.cpython-314-darwin.so +0 -0
  2. switchboard/__init__.py +24 -0
  3. switchboard/ams.py +668 -0
  4. switchboard/apb.py +278 -0
  5. switchboard/autowrap.py +1000 -0
  6. switchboard/axi.py +571 -0
  7. switchboard/axil.py +348 -0
  8. switchboard/bitvector.py +112 -0
  9. switchboard/cmdline.py +142 -0
  10. switchboard/cpp/Makefile +13 -0
  11. switchboard/cpp/bitutil.h +39 -0
  12. switchboard/cpp/pagemap.h +91 -0
  13. switchboard/cpp/pciedev.h +86 -0
  14. switchboard/cpp/router.cc +89 -0
  15. switchboard/cpp/spsc_queue.h +267 -0
  16. switchboard/cpp/switchboard.hpp +257 -0
  17. switchboard/cpp/switchboard_pcie.hpp +234 -0
  18. switchboard/cpp/switchboard_tlm.hpp +98 -0
  19. switchboard/cpp/umilib.h +144 -0
  20. switchboard/cpp/umilib.hpp +113 -0
  21. switchboard/cpp/umisb.hpp +364 -0
  22. switchboard/cpp/xyce.hpp +90 -0
  23. switchboard/deps/__init__.py +0 -0
  24. switchboard/deps/verilog_axi.py +23 -0
  25. switchboard/dpi/__init__.py +0 -0
  26. switchboard/dpi/switchboard_dpi.cc +119 -0
  27. switchboard/dpi/switchboard_dpi.py +13 -0
  28. switchboard/dpi/xyce_dpi.cc +43 -0
  29. switchboard/gpio.py +108 -0
  30. switchboard/icarus.py +85 -0
  31. switchboard/loopback.py +157 -0
  32. switchboard/network.py +714 -0
  33. switchboard/pytest_plugin.py +11 -0
  34. switchboard/sbdesign.py +55 -0
  35. switchboard/sbdut.py +744 -0
  36. switchboard/sbtcp.py +345 -0
  37. switchboard/sc/__init__.py +0 -0
  38. switchboard/sc/morty/__init__.py +0 -0
  39. switchboard/sc/morty/uniquify.py +67 -0
  40. switchboard/sc/sed/__init__.py +0 -0
  41. switchboard/sc/sed/sed_remove.py +47 -0
  42. switchboard/sc/standalone_netlist_flow.py +25 -0
  43. switchboard/switchboard.py +53 -0
  44. switchboard/test_util.py +46 -0
  45. switchboard/uart_xactor.py +66 -0
  46. switchboard/umi.py +793 -0
  47. switchboard/util.py +131 -0
  48. switchboard/verilator/__init__.py +0 -0
  49. switchboard/verilator/config.vlt +13 -0
  50. switchboard/verilator/testbench.cc +143 -0
  51. switchboard/verilator/verilator.py +13 -0
  52. switchboard/verilator_run.py +31 -0
  53. switchboard/verilog/__init__.py +0 -0
  54. switchboard/verilog/common/__init__.py +0 -0
  55. switchboard/verilog/common/common.py +26 -0
  56. switchboard/verilog/common/switchboard.vh +429 -0
  57. switchboard/verilog/common/uart_xactor.sv +247 -0
  58. switchboard/verilog/common/umi_gpio.v +236 -0
  59. switchboard/verilog/fpga/__init__.py +0 -0
  60. switchboard/verilog/fpga/axi_reader.sv +82 -0
  61. switchboard/verilog/fpga/axi_writer.sv +111 -0
  62. switchboard/verilog/fpga/config_registers.sv +249 -0
  63. switchboard/verilog/fpga/fpga.py +21 -0
  64. switchboard/verilog/fpga/include/sb_queue_regmap.vh +21 -0
  65. switchboard/verilog/fpga/include/spsc_queue.vh +7 -0
  66. switchboard/verilog/fpga/memory_fault.sv +40 -0
  67. switchboard/verilog/fpga/sb_fpga_queues.sv +416 -0
  68. switchboard/verilog/fpga/sb_rx_fpga.sv +303 -0
  69. switchboard/verilog/fpga/sb_tx_fpga.sv +294 -0
  70. switchboard/verilog/fpga/umi_fpga_queues.sv +146 -0
  71. switchboard/verilog/sim/__init__.py +0 -0
  72. switchboard/verilog/sim/auto_stop_sim.sv +25 -0
  73. switchboard/verilog/sim/perf_meas_sim.sv +97 -0
  74. switchboard/verilog/sim/queue_to_sb_sim.sv +176 -0
  75. switchboard/verilog/sim/queue_to_umi_sim.sv +66 -0
  76. switchboard/verilog/sim/sb_apb_m.sv +146 -0
  77. switchboard/verilog/sim/sb_axi_m.sv +199 -0
  78. switchboard/verilog/sim/sb_axil_m.sv +180 -0
  79. switchboard/verilog/sim/sb_axil_s.sv +180 -0
  80. switchboard/verilog/sim/sb_clk_gen.sv +89 -0
  81. switchboard/verilog/sim/sb_jtag_rbb_sim.sv +148 -0
  82. switchboard/verilog/sim/sb_rx_sim.sv +55 -0
  83. switchboard/verilog/sim/sb_to_queue_sim.sv +196 -0
  84. switchboard/verilog/sim/sb_tx_sim.sv +55 -0
  85. switchboard/verilog/sim/switchboard_sim.py +49 -0
  86. switchboard/verilog/sim/umi_rx_sim.sv +61 -0
  87. switchboard/verilog/sim/umi_to_queue_sim.sv +66 -0
  88. switchboard/verilog/sim/umi_tx_sim.sv +61 -0
  89. switchboard/verilog/sim/xyce_intf.sv +67 -0
  90. switchboard/vpi/switchboard_vpi.cc +431 -0
  91. switchboard/vpi/xyce_vpi.cc +200 -0
  92. switchboard/warn.py +14 -0
  93. switchboard/xyce.py +27 -0
  94. switchboard_hw-0.3.0.dist-info/METADATA +303 -0
  95. switchboard_hw-0.3.0.dist-info/RECORD +99 -0
  96. switchboard_hw-0.3.0.dist-info/WHEEL +6 -0
  97. switchboard_hw-0.3.0.dist-info/entry_points.txt +6 -0
  98. switchboard_hw-0.3.0.dist-info/licenses/LICENSE +190 -0
  99. switchboard_hw-0.3.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,23 @@
1
+ from siliconcompiler import Design
2
+
3
+
4
+ class VerilogAxi(Design):
5
+ def __init__(self):
6
+ super().__init__('verilog_axis')
7
+
8
+ self.set_dataroot(
9
+ name="verilog_axis",
10
+ path="git+https://github.com/alexforencich/verilog-axis.git@master",
11
+ tag="25912d48fec2abbf3565bbefe402c1cff99fe470"
12
+ )
13
+
14
+ path = "rtl"
15
+
16
+ files = [
17
+ f"{path}/arbiter.v",
18
+ ]
19
+
20
+ with self.active_fileset('rtl'):
21
+ for item in files:
22
+ self.add_file(item)
23
+
File without changes
@@ -0,0 +1,119 @@
1
+ // Copyright (c) 2024 Zero ASIC Corporation
2
+ // This code is licensed under Apache License 2.0 (see LICENSE for details)
3
+
4
+ #include <assert.h>
5
+ #include <chrono>
6
+ #include <memory>
7
+ #include <vector>
8
+
9
+ #include "svdpi.h"
10
+ #include "switchboard.hpp"
11
+
12
+ // function definitions
13
+ #ifdef __cplusplus
14
+ extern "C" {
15
+ #endif
16
+ extern void pi_sb_rx_init(int* id, const char* uri, int width);
17
+ extern void pi_sb_tx_init(int* id, const char* uri, int width);
18
+ extern void pi_sb_recv(int id, svBitVecVal* rdata, svBitVecVal* rdest, svBit* rlast, int* success);
19
+ extern void pi_sb_send(int id, const svBitVecVal* sdata, const svBitVecVal* sdest, svBit slast,
20
+ int* success);
21
+ extern void pi_time_taken(double* t);
22
+ #ifdef __cplusplus
23
+ }
24
+ #endif
25
+
26
+ static std::vector<std::unique_ptr<SBRX>> rxconn;
27
+ static std::vector<std::unique_ptr<SBTX>> txconn;
28
+ static std::vector<int> rxwidth;
29
+ static std::vector<int> txwidth;
30
+
31
+ void pi_sb_rx_init(int* id, const char* uri, int width) {
32
+ rxconn.push_back(std::unique_ptr<SBRX>(new SBRX()));
33
+ rxconn.back()->init(uri);
34
+
35
+ // record the width of this connection
36
+ rxwidth.push_back(width);
37
+
38
+ // assign the ID of this connection
39
+ *id = rxconn.size() - 1;
40
+ }
41
+
42
+ void pi_sb_tx_init(int* id, const char* uri, int width) {
43
+ txconn.push_back(std::unique_ptr<SBTX>(new SBTX()));
44
+ txconn.back()->init(uri);
45
+
46
+ // record the width of this connection
47
+ txwidth.push_back(width);
48
+
49
+ // assign the ID of this connection
50
+ *id = txconn.size() - 1;
51
+ }
52
+
53
+ void pi_sb_recv(int id, svBitVecVal* rdata, svBitVecVal* rdest, svBit* rlast, int* success) {
54
+ // make sure this is a valid id
55
+ assert(id < rxconn.size());
56
+
57
+ // try to receive an inbound packet
58
+ sb_packet p;
59
+ if (rxconn[id]->recv(p)) {
60
+ memcpy(rdata, p.data, rxwidth[id]);
61
+ *rdest = p.destination;
62
+ *rlast = p.last ? 1 : 0;
63
+ *success = 1;
64
+ } else {
65
+ *success = 0;
66
+ }
67
+ }
68
+
69
+ void pi_sb_send(int id, const svBitVecVal* sdata, const svBitVecVal* sdest, svBit slast,
70
+ int* success) {
71
+ // make sure this is a valid id
72
+ assert(id < txconn.size());
73
+
74
+ // form the outbound packet
75
+ sb_packet p;
76
+ memcpy(p.data, sdata, txwidth[id]);
77
+ p.destination = *sdest;
78
+ p.last = slast;
79
+
80
+ // try to send the packet
81
+ if (txconn[id]->send(p)) {
82
+ *success = 1;
83
+ } else {
84
+ *success = 0;
85
+ }
86
+ }
87
+
88
+ void pi_time_taken(double* t) {
89
+ static std::chrono::steady_clock::time_point start_time;
90
+ static std::chrono::steady_clock::time_point stop_time;
91
+
92
+ // compute time taken in seconds
93
+ stop_time = std::chrono::steady_clock::now();
94
+ *t = 1.0e-6 *
95
+ (std::chrono::duration_cast<std::chrono::microseconds>(stop_time - start_time).count());
96
+ start_time = std::chrono::steady_clock::now();
97
+ }
98
+
99
+ void pi_start_delay(double value) {
100
+ // WARNING: not tested yet since Icarus Verilog uses VPI and Verilator
101
+ // uses start_delay in main(), not through DPI
102
+ start_delay(value);
103
+ }
104
+
105
+ void pi_max_rate_tick(svBitVecVal* t_us_vec, svBitVecVal* min_period_us_vec) {
106
+ // WARNING: not tested yet since Icarus Verilog uses VPI and Verilator
107
+ // uses max_rate_tick in main(), not through DPI
108
+
109
+ // retrieve the previous timestamp and minimum period
110
+ long t_us, min_period_us;
111
+ memcpy(&t_us, t_us_vec, 8);
112
+ memcpy(&min_period_us, min_period_us_vec, 8);
113
+
114
+ // call the underlying switchboard function
115
+ max_rate_tick(t_us, min_period_us);
116
+
117
+ // store the new timestamp
118
+ memcpy(t_us_vec, &t_us, 8);
119
+ }
@@ -0,0 +1,13 @@
1
+ from siliconcompiler import Design
2
+
3
+ from switchboard import sb_path
4
+
5
+
6
+ class SwitchboardDPI(Design):
7
+ def __init__(self):
8
+ super().__init__("switchboard_dpi")
9
+
10
+ self.set_dataroot('localroot', sb_path() / "dpi")
11
+
12
+ with self.active_fileset('sim'):
13
+ self.add_file("switchboard_dpi.cc")
@@ -0,0 +1,43 @@
1
+ // Copyright (c) 2024 Zero ASIC Corporation
2
+ // This code is licensed under Apache License 2.0 (see LICENSE for details)
3
+
4
+ #include "svdpi.h"
5
+
6
+ #include <memory>
7
+ #include <string>
8
+ #include <vector>
9
+
10
+ #include <stdio.h>
11
+ #include <stdlib.h>
12
+
13
+ #include "xyce.hpp"
14
+
15
+ // function definitions
16
+ #ifdef __cplusplus
17
+ extern "C" {
18
+ #endif
19
+ extern void pi_sb_xyce_init(int* id, char* file);
20
+ extern void pi_sb_xyce_put(int id, char* name, double time, double value);
21
+ extern void pi_sb_xyce_get(int id, char* name, double time, double* val);
22
+ #ifdef __cplusplus
23
+ }
24
+ #endif
25
+
26
+ static std::vector<std::unique_ptr<XyceIntf>> xyceIntfs;
27
+
28
+ void pi_sb_xyce_init(int* id, char* file) {
29
+ // add a new Xyce interface
30
+ xyceIntfs.push_back(std::unique_ptr<XyceIntf>(new XyceIntf()));
31
+ xyceIntfs.back()->init(std::string(file));
32
+
33
+ // set ID
34
+ *id = xyceIntfs.size() - 1;
35
+ }
36
+
37
+ void pi_sb_xyce_put(int id, char* name, double time, double value) {
38
+ xyceIntfs[id]->put(std::string(name), time, value);
39
+ }
40
+
41
+ void pi_sb_xyce_get(int id, char* name, double time, double* value) {
42
+ xyceIntfs[id]->get(std::string(name), time, value);
43
+ }
switchboard/gpio.py ADDED
@@ -0,0 +1,108 @@
1
+ # Copyright (c) 2024 Zero ASIC Corporation
2
+ # This code is licensed under Apache License 2.0 (see LICENSE for details)
3
+
4
+ import numpy as np
5
+ from .bitvector import BitVector
6
+
7
+
8
+ class UmiGpioInput:
9
+ def __init__(self, width, dstaddr, srcaddr, max_bytes, umi):
10
+ self.width = width
11
+ self.dstaddr = dstaddr
12
+ self.srcaddr = srcaddr
13
+ self.max_bytes = max_bytes
14
+ self.umi = umi
15
+
16
+ def _read(self):
17
+ # determine the number of bytes to read
18
+ nbytes = (self.width // 8)
19
+ if (self.width % 8) != 0:
20
+ nbytes += 1
21
+
22
+ # read the bytes
23
+ return BitVector.frombytes(
24
+ self.umi.read(
25
+ addr=self.dstaddr,
26
+ num_or_dtype=nbytes,
27
+ dtype=np.uint8,
28
+ srcaddr=self.srcaddr,
29
+ max_bytes=self.max_bytes
30
+ )
31
+ )
32
+
33
+ def __str__(self):
34
+ return str(self._read())
35
+
36
+ def __int__(self):
37
+ return int(self[:])
38
+
39
+ def __getitem__(self, key):
40
+ bv = self._read()
41
+ return bv.__getitem__(key=key)
42
+
43
+
44
+ class UmiGpioOutput:
45
+ def __init__(self, width, init, dstaddr, srcaddr, posted, max_bytes, umi):
46
+ self.width = width
47
+ self.dstaddr = dstaddr
48
+ self.srcaddr = srcaddr
49
+ self.posted = posted
50
+ self.max_bytes = max_bytes
51
+ self.umi = umi
52
+
53
+ self.bv = BitVector(init)
54
+
55
+ if init is not None:
56
+ self._write()
57
+
58
+ def _write(self):
59
+ # determine the number of bytes to write
60
+ nbytes = (self.width // 8)
61
+ if (self.width % 8) != 0:
62
+ nbytes += 1
63
+
64
+ # write the bytes
65
+ self.umi.write(
66
+ addr=self.dstaddr,
67
+ data=self.bv.tobytes(n=nbytes),
68
+ srcaddr=self.srcaddr,
69
+ max_bytes=self.max_bytes,
70
+ posted=self.posted
71
+ )
72
+
73
+ def __setitem__(self, key, value):
74
+ self.bv.__setitem__(key=key, value=value)
75
+ self._write()
76
+
77
+ # read functions provided for convenience
78
+
79
+ def __str__(self):
80
+ return str(self.bv)
81
+
82
+ def __int__(self):
83
+ return int(self[:])
84
+
85
+ def __getitem__(self, key):
86
+ return self.bv.__getitem__(key=key)
87
+
88
+
89
+ class UmiGpio(object):
90
+ def __init__(self, iwidth, owidth, init, dstaddr, srcaddr, posted, max_bytes, umi):
91
+
92
+ self.i = UmiGpioInput(
93
+ width=iwidth,
94
+ dstaddr=dstaddr,
95
+ srcaddr=srcaddr,
96
+ max_bytes=max_bytes,
97
+ umi=umi
98
+ )
99
+
100
+ self.o = UmiGpioOutput(
101
+ width=owidth,
102
+ init=init,
103
+ dstaddr=dstaddr,
104
+ srcaddr=srcaddr,
105
+ posted=posted,
106
+ max_bytes=max_bytes,
107
+ umi=umi
108
+ )
switchboard/icarus.py ADDED
@@ -0,0 +1,85 @@
1
+ # Utilities for working with Icarus Verilog
2
+
3
+ # Copyright (c) 2024 Zero ASIC Corporation
4
+ # This code is licensed under Apache License 2.0 (see LICENSE for details)
5
+
6
+ # TODO: replace with SiliconCompiler functionality
7
+
8
+ from typing import Union, List
9
+ from pathlib import Path
10
+
11
+ from .util import plusargs_to_args, binary_run
12
+ from .switchboard import path as sb_path
13
+ from subprocess import check_output, STDOUT, CalledProcessError
14
+
15
+
16
+ def run(command: list, cwd: str = None) -> str:
17
+ return check_output(command, cwd=cwd, stderr=STDOUT).decode()
18
+
19
+
20
+ def icarus_build_vpi(
21
+ cwd: str = None,
22
+ name: str = 'switchboard',
23
+ cincludes: List[str] = None,
24
+ ldflags: List[str] = None
25
+ ) -> str:
26
+ if cincludes is None:
27
+ cincludes = []
28
+
29
+ if ldflags is None:
30
+ ldflags = []
31
+
32
+ sbdir = sb_path()
33
+ incdirs = cincludes + [f'{sbdir}/cpp']
34
+
35
+ cmd = []
36
+ cmd += ['iverilog-vpi']
37
+ cmd += [f'-I{incdir}' for incdir in incdirs]
38
+ cmd += ldflags
39
+ cmd += [str(sbdir / 'vpi' / f'{name}_vpi.cc')]
40
+
41
+ try:
42
+ run(cmd, cwd)
43
+ except CalledProcessError as e:
44
+ print(e.output)
45
+ raise
46
+
47
+
48
+ def icarus_find_vpi(cwd: Union[str, Path] = None, name: str = 'switchboard') -> Path:
49
+ path = Path(f'{name}_vpi.vpi')
50
+
51
+ if cwd is not None:
52
+ path = Path(cwd) / path
53
+
54
+ if path.exists():
55
+ return path
56
+ else:
57
+ return None
58
+
59
+
60
+ def icarus_run(vvp, plusargs=None, modules=None, extra_args=None, **kwargs):
61
+ args = []
62
+
63
+ args += ['-n']
64
+
65
+ mdirs = set()
66
+
67
+ if modules is not None:
68
+ if not isinstance(modules, list):
69
+ raise TypeError('modules must be a list')
70
+ for module in modules:
71
+ mdirs.add(str(Path(module.resolve().parent)))
72
+ args += ['-m', Path(module).stem]
73
+
74
+ for mdir in mdirs:
75
+ args += [f'-M{mdir}']
76
+
77
+ args += [vvp]
78
+ args += plusargs_to_args(plusargs)
79
+
80
+ if extra_args is not None:
81
+ if not isinstance(modules, list):
82
+ raise TypeError('extra_args must be a list')
83
+ args += extra_args
84
+
85
+ return binary_run(bin='vvp', args=args, **kwargs)
@@ -0,0 +1,157 @@
1
+ # Loopback test to check the behavior of blocks that split/merge UMI packets
2
+
3
+ # Copyright (c) 2024 Zero ASIC Corporation
4
+ # This code is licensed under Apache License 2.0 (see LICENSE for details)
5
+
6
+ from numbers import Integral
7
+ from typing import Iterable, Iterator, Union
8
+
9
+ try:
10
+ from tqdm import tqdm
11
+ except ModuleNotFoundError:
12
+ tqdm = None
13
+
14
+ from .umi import UmiTxRx, random_umi_packet
15
+
16
+
17
+ def umi_loopback(
18
+ umi: UmiTxRx,
19
+ packets: Union[Integral, Iterable, Iterator] = 10,
20
+ **kwargs
21
+ ):
22
+ """
23
+ Performs a loopback test by sending packets into a block and checking that
24
+ the packets received back are equivalent under the UMI split/merge rules.
25
+
26
+ Parameters
27
+ ----------
28
+ umi: UmiTxRx
29
+ packets:
30
+ Can be a number, a list of packets, or a generator.
31
+
32
+ - If this is a number, it represents the number of packets to send,
33
+ which are generated with random_umi_packet. Any remaining arguments
34
+ are passed directly to random_umi_packet.
35
+ - If this is an iterable (list, tuple, etc.), then it represents a list
36
+ of packets to use for the test. This is helpful if you want to use a very
37
+ specific sequence of transactions.
38
+ - This can also be an iterator, which might be convenient if you want to
39
+ send a very large number of packets without having to store them
40
+ all in memory at once, e.g. (random_umi_packet() for _ in range(1000000))
41
+
42
+ Raises
43
+ ------
44
+ ValueError
45
+ If the number of packets is not positive or if the `packets` argument is empty
46
+ Exception
47
+ If a received packet does not match the corresponding transmitted packet
48
+
49
+ """
50
+
51
+ # input validation
52
+
53
+ if isinstance(packets, Integral):
54
+ if packets <= 0:
55
+ raise ValueError(f'The number of packets must be positive (got packets={packets}).')
56
+ else:
57
+ total = packets
58
+ packets = (random_umi_packet(**kwargs) for _ in range(packets))
59
+ elif isinstance(packets, Iterable):
60
+ if isinstance(packets, (list, tuple)):
61
+ total = len(packets)
62
+ else:
63
+ total = float('inf')
64
+ packets = iter(packets)
65
+ elif isinstance(packets, Iterator):
66
+ total = float('inf')
67
+ else:
68
+ raise TypeError(f'Unsupported type for packets: {type(packets)}')
69
+
70
+ tx_sets = [] # kept for debug purposes
71
+ tx_hist = []
72
+
73
+ tx_set = None
74
+ tx_partial = None
75
+
76
+ rx_set = None # kept for debug purposes
77
+ rx_partial = None
78
+
79
+ if tqdm is not None:
80
+ pbar = tqdm(total=total)
81
+ else:
82
+ pbar = None
83
+
84
+ # get the first element
85
+ try:
86
+ txp = next(packets)
87
+ if pbar is not None:
88
+ pbar.update(0)
89
+ except StopIteration:
90
+ raise ValueError('The argument "packets" is empty.')
91
+
92
+ while (txp is not None) or (len(tx_hist) > 0):
93
+ # send data
94
+ if txp is not None:
95
+ if umi.send(txp, blocking=False):
96
+ if tx_partial is not None:
97
+ if not tx_partial.merge(txp):
98
+ tx_hist.append(tx_partial)
99
+ tx_sets.append(tx_set)
100
+ tx_start_new = True
101
+ else:
102
+ tx_set.append(txp)
103
+ tx_start_new = False
104
+ else:
105
+ tx_start_new = True
106
+
107
+ if tx_start_new:
108
+ tx_partial = txp
109
+ tx_set = [txp]
110
+
111
+ try:
112
+ txp = next(packets)
113
+ except StopIteration:
114
+ txp = None
115
+
116
+ if txp is None:
117
+ # if this is the last packet, add it to the history
118
+ # even if the merge was successful
119
+ tx_hist.append(tx_partial)
120
+ tx_sets.append(tx_set)
121
+
122
+ # receive data
123
+ if len(tx_hist) > 0:
124
+ rxp = umi.recv(blocking=False)
125
+ if rxp is not None:
126
+ # try to merge into an existing partial packet
127
+ if rx_partial is not None:
128
+ if not rx_partial.merge(rxp):
129
+ print('=== Mismatch detected ===')
130
+ for i, p in enumerate(tx_sets[0]):
131
+ print(f'* TX[{i}] *')
132
+ print(p)
133
+ print('---')
134
+ for i, p in enumerate(rx_set):
135
+ print(f'* RX[{i}] *')
136
+ print(p)
137
+ print('=========================')
138
+ raise Exception('Mismatch!')
139
+ else:
140
+ rx_set.append(rxp)
141
+ else:
142
+ rx_partial = rxp
143
+ rx_set = [rxp]
144
+
145
+ # at this point it is guaranteed there is something in
146
+ # rx_partial, so compare it to the expected outbound packet
147
+ if rx_partial == tx_hist[0]:
148
+ tx_hist.pop(0)
149
+ tx_sets.pop(0)
150
+ rx_partial = None
151
+ rx_set = None
152
+
153
+ if pbar is not None:
154
+ pbar.update()
155
+
156
+ if pbar is not None:
157
+ pbar.close()