Python bindings
PulsePins uses nanobind to provide Python bindings for the underlying C++ interface.
The Python binding tree lives in python/ and builds two modules:
pppp_impl
At the sequence-serialization level, Python now exposes the same practical formats as the core C++ layer:
- PulsePins text sequence format via
parse_sequence_text(...)andwrite_sequence_text(...) - VCD import/export via
Sequence.load_VCD(...)andSequence.write_VCD_file(...) - exact binary sequence import/export via
read_sequence_binary(...)andSequence.write_binary_file(...)
Text and binary sequence helpers preserve explicit final, trigger, replay, retrigger, and pseudo-random records exactly. VCD export is narrower: Sequence.write_VCD_file(...) only accepts deterministic regular sequences and rejects trigger/final/control-flow elements.
Supported build modes
There are two practical ways to build/test the Python bindings today:
- build on the DE10-Nano board - this is the supported production path
- build on a host machine - useful for syntax/import/API testing only
True cross-compilation of the Python bindings is not currently supported.
Board build
On the DE10-Nano, the normal workflow is:
cd python
pip3 install pytest
make
make test
The default build now uses -O2 and omits -g to reduce memory pressure on the board.
If you need debug symbols while developing the bindings, use:
make PY_DEBUG=1
Host-side testing
Host-side builds are still useful for checking that the binding code compiles and imports cleanly. That is helpful for contributor workflows without a board, but it should not be treated as a replacement for the board build.
The recommended host-side command pair is:
make -C python build
make -C python test-host
test-host intentionally skips tests marked hardware, which require /dev/mem, board-backed MMIO, or a live PulsePins runtime.
Sequence I/O examples
import pp
seq, force_trigger = pp.parse_sequence_text("d 3 0x12\nfinal 0x34\n")
text = pp.write_sequence_text(seq)
seq.write_VCD_file("capture.vcd")
seq.write_binary_file("capture.ppbin")
seq2, force_trigger2 = pp.read_sequence_binary("capture.ppbin")
Sequence element API
Python exposes the same sequence-element model as the C++ layer, but the supported construction paths now follow the flattened el design rather than the older raw (el_type, Counter, Value, control) constructor.
Supported pp.el(...) constructors include:
pp.el()- final element with the default final output valuepp.el(value)- final element with an explicit final output valuepp.el(count, value)- regularBITLOADelementpp.el(counter, value)- regularBITLOADelement with explicitCounterpolicypp.el(counter, value_wrapper)- regular element with explicitCounterandValuewrapper semanticspp.el(pattern, mask, final)- trigger elementpp.el(pp.Replay(), repetitions, length)- replay elementpp.el(pp.Retrig(), value=...)- retrigger elementpp.el(pp.PseudoRandom(), count)- pseudo-random element
Useful element inspectors and helpers now exposed in Python:
kind(),mode(),no_strobe()is_stored(),store_slot(),stored_in(...)trigger_pattern(),trigger_mask(),trigger_is_final()regular_token(),sequence_record()with_control(...),with_count(...),with_counter(...),with_regular_value(...),as_bitload_after(...)
The mutating compatibility methods store(...), set_control(...), set_count(...), and set_value(...) are still available, but new code should prefer the immutable helpers above.
Static reconstruction helpers are also bound:
pp.el.classify_control(...)pp.el.from_raw_triplet(...)pp.el.from_regular_token(...)pp.el.is_regular_token(...)
Example:
import pp
import pp_impl
e = pp.el(pp.NoStrobe(3), pp.BitXor(0x12))
assert e.mode() == pp_impl.BITXOR
converted = e.as_bitload_after(0x01)
assert converted.regular_token() == "dn"
assert converted.sequence_record() == "dn 3 0x13"
decoded = pp.el.from_regular_token("xr", 7, 0x55)
assert decoded.sequence_record() == "xr 7 0x55"
Bindings that wrap MMIO-backed hardware objects should still be treated as owning long-lived board resources even though the module now keeps the immediate mm/FPGA constructor arguments alive for the wrapper object.
The small helper scripts in python/pptool.py and tests/test2.py now use the same conservative defaults as the shared C++ workflow: 2s readback timeout protection and a 10s streamer-completion timeout, with timeout=0 still disabling the readback timeout.
Testing expectations
make -C python test currently runs pytest python/test.py.
Some Python tests exercise board-backed MMIO/FPGA behavior, so they should not be treated as a strictly hardware-free test battery.
See also:
python/READMEpython/README.develdocs/docs/build.md