Skip to main content

Stephen Leary's hardware/FPGA bench tools (bin2mif, bin2vrlg, mkzorro, hp1660 logic-analyser interface)

Project description

tflab-tools

PyPI Python tests License: GPL v2+

A small collection of hardware / FPGA bench tools by Stephen J. Leary.

Nine command-line utilities and a Python library for talking to HP 1660-series logic analysers. The package is organised so the core install is pure-Python and dependency-free; transport- and EDA-specific dependencies live in optional extras.

Install

The package is published on PyPI as tflab-tools.

Recommended — isolated install with uv or pipx

For CLI use, install into an isolated environment so the commands land on your $PATH without touching your system Python:

# uv (fastest)
uv tool install 'tflab-tools[serial,gpib,eagle]'

# or pipx
pipx install 'tflab-tools[serial,gpib,eagle]'

After that, bin2mif, bin2vrlg, mkzorro, hp1661-vcd, hp1661-list, eagle-netlist, eagle-pcf and eagle-pdf all run as ordinary commands from any shell.

Plain pip in a venv

python3 -m venv ~/.venvs/tflab && source ~/.venvs/tflab/bin/activate
pip install 'tflab-tools[serial,gpib,eagle]'

Picking your extras

pip install tflab-tools                        # core: bin2mif, bin2vrlg, mkzorro, hp1661-vcd, hp1661-list (LAN)
pip install 'tflab-tools[serial]'              # + RS-232 transport for the hp1661-* tools
pip install 'tflab-tools[gpib]'                # + GPIB/USBTMC transport (PyVISA)
pip install 'tflab-tools[eagle]'               # + eagle-netlist, eagle-pcf, eagle-pdf
pip install 'tflab-tools[serial,gpib,eagle]'   # everything

From source / for development

git clone https://github.com/terriblefire/tflab-tools
cd tflab-tools
pip install -e '.[serial,gpib,eagle,dev]'
pytest

The [eagle] extra includes a vendored copy of a patched eagle2svg (Bus rendering, Plain section fix, multi-line text fix, 5% margin) under tflab._eagle2svg. See src/tflab/_eagle2svg/NOTICE.md for the BSD attribution.

Commands

After install, the following commands are available on $PATH:

Command Description
bin2mif Convert binary file(s) to MIF-style hex output (configurable word width and endianness).
bin2vrlg Convert a binary file to a Verilog bootrom module.
mkzorro Generate Amiga Zorro AutoConfig ROM nibbles (Z2/Z3, configurable size, manufacturer ID, serial, product code).
hp1661-vcd Capture data from an HP 1660-series logic analyser and write a VCD file.
hp1661-list Capture data from an HP 1660-series logic analyser and print a tabular text listing.
eagle-netlist Generate a netlist from an EagleCAD .sch file. (extra: [eagle])
eagle-pcf Convert an Eagle netlist to an FPGA PCF (pin constraints) file. (extra: [eagle])
eagle-pdf Render an EagleCAD schematic to multi-page PDF. (extra: [eagle])

Each command supports --help (the Eagle tools print a usage line when invoked without arguments).


bin2mif

Convert one or more binary files into MIF-style hex (one word per line, lower case, fixed width). Useful for initialising on-chip ROM/RAM contents in FPGA toolchains that consume MIF or compatible formats.

bin2mif [-h] [-w {8,16,32,64}] [-e ENDIAN] FILE [FILE ...]
Flag Default Description
-w, --width 8 Word width in bits.
-e, --endian big Byte order. Accepts big/b or little/l.

Output is written to stdout, one word per line, padded to the full width. Any trailing partial word is silently dropped.

$ printf '\x00\x01\x02\xff' > rom.bin
$ bin2mif -w 8 rom.bin
00
01
02
ff
$ bin2mif -w 16 -e little rom.bin
0100
ff02
$ bin2mif -w 16 -e big rom.bin > rom.mif

bin2vrlg

Convert a binary file into a synthesisable Verilog bootrom module — a clocked address → data ROM with one case arm per non-zero byte.

bin2vrlg [-h] -f FILENAME

Output goes to stdout. The address bus width is calculated from the file size (ceil(log2(size))); zero bytes are emitted via the default arm to keep the source file compact.

$ printf '\x00\x01\x02\xff' > rom.bin
$ bin2vrlg -f rom.bin
module bootrom
(
        input           clk,            // bus clock
        input [1:0]     address,        // address in
        output reg [7:0] data           // data out
);

always @(posedge clk) begin
        case (address)
                2'd1:   data    <=      8'h01;
                2'd2:   data    <=      8'h02;
                2'd3:   data    <=      8'hff;
                default: data   <=      8'd0;
        endcase
end

endmodule

mkzorro

Generate the AutoConfig ROM nibble stream for an Amiga Zorro II/III expansion card. Output is one hex nibble per line on stdout, with the nibble inversion required by the AutoConfig protocol applied automatically (every nibble after the ER_TYPE byte is bit-inverted).

mkzorro [-h] [--version {Z2,Z3}] [--size {64K,128K,256K,512K,1M,2M,4M,8M}]
        [--manuid MANUID] [--serial SERIAL] [--product PRODUCT]
        [--flags FLAGS] [--rom-vector ROM_VECTOR]
Flag Default Description
--version Z2 Zorro bus version (Z2 = 0xC0, Z3 = 0x80 in ER_TYPE).
--size 64K Card memory size code.
--manuid 5080 Manufacturer ID. Accepts decimal or 0x....
--serial 4060 Serial number. Accepts decimal or 0x....
--product 148 Product code.
--flags 0x80 ER_FLAGS byte.
--rom-vector 0 Diag/init ROM offset.

Running with no arguments produces the default Terrible Fire control card ROM:

$ mkzorro | tr '\n' ' '
C 1 6 B 7 F F F E C 2 7 F F F F F 0 2 3 F F F F F F F F F F F F

For a Z3 card with 1 MB of address space:

$ mkzorro --version Z3 --size 1M | head -2
8
5

hp1661-vcd

Captures acquisition data from an HP 1660C/CS/CP-series logic analyser, discovers labels automatically, and writes a standard VCD file consumable by GTKWave, ModelSim, Surfer, etc.

hp1661-vcd [-h] [--lan LAN] [--serial SERIAL] [--baud BAUD] [--gpib]
           [--save-config SAVE_CONFIG] [--load-config LOAD_CONFIG]
           [output]

output defaults to capture.vcd.

Transport options

# GPIB via USBTMC (fastest — ~5s, requires UsbGpib or similar adapter)
hp1661-vcd --gpib capture.vcd

# LAN via TCP port 5025 (~3s)
hp1661-vcd --lan 192.168.10.10 capture.vcd

# Serial (~80s at 19200 baud)
hp1661-vcd --serial /dev/tty.usbserial-1440 --baud 19200 capture.vcd

Saving/loading label configurations

Label discovery hits the analyser with a chain of SCPI queries; if you're iterating on captures it's worth saving the result and reusing it.

# Save the current LA label config to JSON
hp1661-vcd --gpib --save-config la_config.json capture.vcd

# Reuse saved labels (skip label discovery)
hp1661-vcd --gpib --load-config la_config.json capture.vcd

GPIB setup

Requires a USB-GPIB adapter such as UsbGpib.

  1. Flash the TestAndMeasurement.bin firmware to the adapter
  2. Connect GPIB cable between adapter and LA
  3. Set LA controller to HP-IB (front panel: System > External I/O > Controller > HP-IB)
  4. Install the gpib extra: pip install '.[gpib]'

The adapter enumerates as a USBTMC device. PyVISA discovers it automatically.

Note: The HP 1660-series requires SELECT 1 before machine-level commands over GPIB. The driver handles this automatically.


hp1661-list

Same data path as hp1661-vcd, different output: prints the current acquisition as a tabular text listing — one row per stored sample, one column per discovered label, with the trigger row marked. Works for both STATE and TIMING acquisitions and shares all transport options (--lan, --serial, --gpib).

hp1661-list [-h] [--lan LAN] [--serial SERIAL] [--baud BAUD] [--gpib]
            [--machine {1,2}] [--base {hex,bin,dec,oct}]
            [--start START] [--count COUNT] [--time]
            [output]

output defaults to stdout; pass - or omit it to print, or pass a path to write to a file.

Flag Default Description
--machine auto Pick machine 1 or 2. Defaults to the active machine; warns if both are active.
--base hex Number base for multi-bit labels: hex / bin / dec / oct.
--start full range First line number relative to the trigger (negative = pre-trigger).
--count all Maximum number of lines to emit.
--time off Add a leading time/tag column. For TIMING captures this is real time relative to the trigger; for STATE captures it's the state count.
# Quick look at the most recent capture, around the trigger
hp1661-list --gpib --start -8 --count 16

#     line          DATA  RW  AS  <-- TRIGGER on line 0
# --------------------------------
#       -8          ff10   1   0
#       -7          ff12   1   0
#       …
#        0          0040   0   0  <-- TRIGGER
#        1          0042   0   0
#       …

# Save a full capture to disk in binary, with a time column
hp1661-list --gpib --base bin --time capture.txt

The active machine is auto-detected via HP1660.get_machines(), so passing --machine is only needed when both machines are configured and you want the inactive default.


eagle-netlist

Extract a flat netlist from an EagleCAD schematic (.sch) file. Output format matches Eagle's native netlist export, so it slots straight into downstream tools that already understand that format (including eagle-pcf below).

eagle-netlist <input.sch> <output.net>

Works without Eagle installed — parses the XML directly with lxml. Library, deviceset, device and gate/pin → pad mappings are resolved from the embedded library definitions in the schematic.


eagle-pcf

Convert an Eagle netlist (as produced by eagle-netlist or Eagle itself) into a Lattice / Project IceStorm PCF (Physical Constraints File) suitable for nextpnr, arachne-pnr, etc.

eagle-pcf <input.net> <output.pcf> <target_chip>

The third positional argument is the FPGA part being targeted; it's used to look up the package pin map.


eagle-pdf

Render an EagleCAD schematic to a multi-page PDF (one PDF page per schematic sheet). Uses a vendored, patched eagle2svg (under tflab._eagle2svg) to convert each sheet to SVG, then svglib + reportlab to assemble the PDF.

eagle-pdf <input.sch> <output.pdf> [sheet_number]

If sheet_number is omitted every sheet in the schematic is rendered. The vendored eagle2svg adds, on top of upstream v0.1.5:

  • Bus rendering (the upstream's Bus class is a stub)
  • Plain-section parsing fix (text annotations, wires, circles etc. now appear)
  • Multi-line text alignment fix
  • 5% whitespace margin for breathing room

Eagle does not need to be installed — the schematic XML is parsed directly.


Library use

The tflab Python module exposes the HP 1660-series driver. The HP1660 class is transport-agnostic — pass any object with read(n)/write(data) methods (and optionally settimeout(t)/timeout) and it just works.

from tflab import HP1660, SocketTransport
import socket

# LAN
s = socket.socket()
s.connect(("192.168.10.10", 5025))
la = HP1660(SocketTransport(s))
# GPIB
import pyvisa
from tflab import HP1660, VisaTransport

rm = pyvisa.ResourceManager("@py")
inst = rm.open_resource(rm.list_resources("USB?*")[0])
la = HP1660(VisaTransport(inst))
# Serial
import serial
from tflab import HP1660

la = HP1660(serial.Serial("/dev/ttyUSB0", 19200, timeout=10))
# Use — same API regardless of transport
print(la.idn())

# Capture acquisition data + VCD
acq = la.acquire()
acq.to_vcd("capture.vcd")

# Read/write labels
la.set_label("DATA", "POSITIVE", 0, [0, 0, 65535, 65535, 0, 0])
la.remove_label("OLD")
labels = la.discover_labels()

# Read/write full format config
cfg = la.get_format()
cfg.save("config.json")
cfg = FormatConfig.load("config.json")
la.set_format(cfg)

# Read/write trigger config
trig = la.get_trigger()
trig.save("trigger.json")
la.set_trigger(TriggerConfig.load("trigger.json"))

# Individual trigger controls
la.set_term("A", "ADDR", "#HDD0000")
la.set_find(1, "A", "OCCURRENCE, 1")
la.set_sample_period("4E-9")
la.set_trigger_position("CENTER")
la.run()
la.stop()

Public classes

Symbol Purpose
HP1660 Main driver (transport-agnostic SCPI + acquisition + VCD export).
SocketTransport Wraps a socket.socket for the LAN transport.
VisaTransport Wraps a PyVISA resource for GPIB/USBTMC; uses VISA-native framing.
FormatConfig Channel labels + thresholds + pod assignment, JSON serialisable.
Label A single label (name, polarity, clock bits, pod masks, width).
TriggerConfig Full trigger setup (terms, sequence levels, ranges, timers, position), JSON serialisable.
TriggerTerm Pattern recogniser term AJ.
TriggerLevel One sequence level (find/branch/timer controls).
Acquisition Captured rows + sample period + trigger row + to_vcd(path).

Layout

tflab/
├── pyproject.toml
├── README.md
├── LICENSE                         GPL-2.0-or-later
└── src/tflab/
    ├── __init__.py                 re-exports the public API
    ├── hp1660.py                   logic-analyser driver (library)
    ├── hp1661_vcd.py               hp1661-vcd entry point
    ├── hp1661_list.py              hp1661-list entry point
    ├── bin2mif.py                  bin2mif entry point
    ├── bin2vrlg.py                 bin2vrlg entry point
    ├── mkzorro.py                  mkzorro entry point
    ├── eagle_netlist.py            eagle-netlist entry point
    ├── eagle_pcf.py                eagle-pcf entry point
    ├── eagle_pdf.py                eagle-pdf entry point
    └── _eagle2svg/                 vendored eagle2svg fork (BSD; see NOTICE.md)

Adding a new tool

  1. Drop a new src/tflab/foo.py with a def main(): that returns an int exit code.
  2. Add an entry to [project.scripts] in pyproject.toml:
    foo = "tflab.foo:main"
    
  3. pip install -e . — the foo command is now on $PATH.

If the tool needs third-party dependencies, add them to a new [project.optional-dependencies] extra rather than to dependencies, so the core install stays minimal.

Releasing

Releases are tag-driven. CI publishes via PyPI trusted publishing (OIDC) — no API tokens stored anywhere.

One-time setup (per environment)

Register a pending publisher on each index, then it becomes a normal publisher after the first upload.

Field Value
PyPI Project Name tflab-tools
Owner terriblefire
Repository name tflab-tools
Workflow name release.yml
Environment name pypi (PyPI) / testpypi (TestPyPI)

URLs:

Then create the matching environments in the GitHub repo (Settings → Environments → New environment): pypi and testpypi. Optional but recommended: protect the pypi environment with required reviewers so production releases need an approval click.

Cutting a release

# Dry-run via TestPyPI first
git tag v0.2.0-rc1 && git push --tags
# verify at https://test.pypi.org/p/tflab-tools

# Real release
git tag v0.2.0 && git push --tags
# lands at https://pypi.org/p/tflab-tools

The workflow routes *-rc* / *-test* tags to TestPyPI and clean vX.Y.Z tags to PyPI. Bump version in pyproject.toml and src/tflab/__init__.py before tagging.

License

Copyright (C) 2016-2026 S.J. Leary.

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. See LICENSE for the full text.

The vendored tflab._eagle2svg module is a copy of a patched eagle2svg and remains under its upstream BSD license — see src/tflab/_eagle2svg/NOTICE.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tflab_tools-0.4.2.tar.gz (97.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tflab_tools-0.4.2-py3-none-any.whl (52.1 kB view details)

Uploaded Python 3

File details

Details for the file tflab_tools-0.4.2.tar.gz.

File metadata

  • Download URL: tflab_tools-0.4.2.tar.gz
  • Upload date:
  • Size: 97.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tflab_tools-0.4.2.tar.gz
Algorithm Hash digest
SHA256 e196a41f83b9f66c8a36ddd2c8227086298a029bfb22007d17b9486683eb3517
MD5 c60a6e70958b862b0408a1ff25981855
BLAKE2b-256 a3117f6fb86ee2b96f726e7675a720a8613742a6dc27bb4d40807f04bb5a5394

See more details on using hashes here.

Provenance

The following attestation bundles were made for tflab_tools-0.4.2.tar.gz:

Publisher: release.yml on terriblefire/tflab-tools

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tflab_tools-0.4.2-py3-none-any.whl.

File metadata

  • Download URL: tflab_tools-0.4.2-py3-none-any.whl
  • Upload date:
  • Size: 52.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tflab_tools-0.4.2-py3-none-any.whl
Algorithm Hash digest
SHA256 3382adfd72b069fa519914a0883a97705101aa429f4bfbcd09e2db833ee61896
MD5 900e734d2e58fd29db5a5cbb8f443baf
BLAKE2b-256 f362614a2346e82d3f140831d8afee2d3e1e0147d0379bf22c9f765a4dd34c0a

See more details on using hashes here.

Provenance

The following attestation bundles were made for tflab_tools-0.4.2-py3-none-any.whl:

Publisher: release.yml on terriblefire/tflab-tools

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page