fpga-isv 0.1.0__tar.gz

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.
fpga_isv-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DFiant Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,151 @@
1
+ Metadata-Version: 2.4
2
+ Name: fpga-isv
3
+ Version: 0.1.0
4
+ Summary: fpga-isv (ISV = Interactive Sim Viewer): a config-driven graphical panel viewer for interactive-sim.
5
+ Author-email: Oron Port <oron.port@dfiant.works>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/DFiantWorks/interactive-sim-viewer
8
+ Project-URL: interactive-sim, https://github.com/DFiantWorks/interactive-sim
9
+ Keywords: fpga,simulation,viewer,hdl,interactive-sim,ulx3s
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: X11 Applications
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
14
+ Classifier: Programming Language :: Python :: 3
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: pillow
19
+ Dynamic: license-file
20
+
21
+ # fpga-isv — Interactive Sim Viewer
22
+
23
+ A cross-platform **graphical panel viewer** for
24
+ [interactive-sim](https://github.com/DFiantWorks/interactive-sim). It draws a board *photo* (or a
25
+ blank *panel* you mock up), overlays the LEDs and buttons at their pixel positions, and connects
26
+ that virtual panel to a running HDL simulation: **design-driven flags light the LEDs**, and
27
+ **clicking a button feeds a control back into the design**.
28
+
29
+ `fpga-isv` is a *pure client of the interactive-sim socket API* — it knows nothing about HDL or
30
+ simulators. Anything that speaks the same newline-delimited-JSON protocol works with it. The
31
+ [ULX3S](https://github.com/ulx3s/ulx3s) board ships as the bundled reference example.
32
+
33
+ ```sh
34
+ fpga-isv --example ulx3s
35
+ ```
36
+
37
+ ## How it connects
38
+
39
+ The viewer **listens** on a TCP port; the simulation **connects to it** (set
40
+ `INTERACTIVE_STREAM=host:port` for the sim). This means you can:
41
+
42
+ - **open and close the viewer at any time** — the sim's backend keeps reconnecting;
43
+ - **stop or restart the simulation** while the viewer stays up — on each (re)connect the sim
44
+ replays its full state (every component + last values), so the panel repopulates automatically.
45
+
46
+ ```sh
47
+ fpga-isv --example ulx3s --host 0.0.0.0 --port 7777
48
+ # then run a simulation with INTERACTIVE_STREAM=127.0.0.1:7777
49
+ ```
50
+
51
+ ## Install
52
+
53
+ Three ways, depending on whether you have Python:
54
+
55
+ | Method | Command | Needs |
56
+ |--------|---------|-------|
57
+ | **Standalone binary** (zero prereqs) | download from [Releases](https://github.com/DFiantWorks/interactive-sim-viewer/releases) | nothing |
58
+ | **pipx** (Python users) | `pipx install fpga-isv` | a Python that ships `tkinter` |
59
+ | **Homebrew** (macOS / Linux) | `brew tap DFiantWorks/fpga-isv https://github.com/DFiantWorks/homebrew-fpga-isv && brew install fpga-isv` | Homebrew |
60
+
61
+ The standalone binary bundles its own Python + Tcl/Tk + Pillow, so it runs with no prerequisites.
62
+ `pipx`/source installs need a Python with `tkinter` (stdlib, but a separate OS package on some
63
+ Linux distros, e.g. `apt install python3-tk`); Pillow is pulled in automatically and is required
64
+ for JPEG photos and cropped / non-integer-scaled images (PNG/GIF panels work on stdlib Tk alone).
65
+
66
+ > **macOS note:** the released binaries are currently **unsigned**. Launched by another process
67
+ > directly they run fine; launched via Finder/`open` from a browser-downloaded copy, Gatekeeper
68
+ > blocks them until you clear quarantine — `xattr -dr com.apple.quarantine ./fpga-isv` or
69
+ > right-click → Open once. **Homebrew** installs strip quarantine, so `brew install` avoids the
70
+ > prompt entirely; `pipx` has no binary to quarantine.
71
+
72
+ ## Config
73
+
74
+ A panel is described by one JSON file. All coordinates are in **original-image pixels**;
75
+ `image.scale` scales the photo/panel and every coordinate together for display, and `image.crop`
76
+ trims a photo to the board — the map stays in original-image pixels so it is independent of crop
77
+ and window size.
78
+
79
+ A **photo** panel:
80
+
81
+ ```jsonc
82
+ {
83
+ "title": "ULX3S",
84
+ "image": { "url": "board.png", "crop": [160, 195, 1650, 1010], "scale": 0.72 },
85
+ "leds": { "on_color": "#ff5a36", "shape": "rect", "w": 26, "h": 32,
86
+ "items": [ { "name": "leds", "bit": 0, "x": 632, "y": 480 }, /* ... */ ] },
87
+ "buttons":[ { "name": "btn_pwr", "shape": "circle", "x": 1375, "y": 401, "r": 42, "key": "p" } ]
88
+ }
89
+ ```
90
+
91
+ A **blank panel** mockup (no photo — lay controls out from scratch):
92
+
93
+ ```jsonc
94
+ {
95
+ "title": "My Panel",
96
+ "image": { "width": 1200, "height": 800, "bg": "#101418" },
97
+ "leds": { "on_color": "#56d364", "items": [ { "name": "led_err", "bit": 0, "x": 100, "y": 80 } ] },
98
+ "buttons":[ { "name": "btn_run", "shape": "rect", "x": 200, "y": 300, "w": 120, "h": 60, "toggle": true } ]
99
+ }
100
+ ```
101
+
102
+ - `image.url` may be a remote URL (optionally with `cache`), an absolute path, or a path
103
+ **relative to the config file**. Use `width`/`height`/`bg` instead of `url` for a blank panel.
104
+ - An LED **item** lights when bit `bit` of flag `name` is `1`, so an N-bit bus is N items with
105
+ different bit indices and a 1-bit flag is one item.
106
+ - A **button** sends `1` on press and `0` on release (momentary); `"toggle": true` flips and
107
+ latches. `"key"` mirrors it to a keyboard key (Tk keysym, e.g. `p`, `space`, `Up`).
108
+
109
+ ### Mapping a new photo
110
+
111
+ Run with `--calibrate` and click the image: each click prints the original-image pixel coordinate
112
+ (and marks it on the canvas), so you can read off the x/y for every LED and button.
113
+
114
+ ```sh
115
+ fpga-isv --config my_board.json --calibrate
116
+ ```
117
+
118
+ ## CLI
119
+
120
+ ```
121
+ fpga-isv (--example NAME | --config PATH) [--host 0.0.0.0] [--port 7777] [--refresh] [--calibrate]
122
+ fpga-isv --list-examples
123
+ fpga-isv --version
124
+ ```
125
+
126
+ ## Wire protocol
127
+
128
+ Newline-delimited JSON, one message per line (defined by interactive-sim; every sim→viewer
129
+ message carries `t`, the simulation time in µs):
130
+
131
+ ```
132
+ sim -> viewer {"ev":"reg", "t":1234.5,"name":"btn_run","kind":"ctrl","width":1}
133
+ {"ev":"flag", "t":1234.5,"name":"leds","val":42}
134
+ {"ev":"time", "t":1234.5}
135
+ {"ev":"close","t":1234.5,"name":"btn_run"}
136
+ viewer -> sim {"name":"btn_run","val":1}
137
+ ```
138
+
139
+ ## Develop
140
+
141
+ ```sh
142
+ pip install -e . pytest
143
+ python -m pytest -q # headless protocol/config/geometry tests
144
+ python -m fpga_isv --example ulx3s
145
+ pyinstaller packaging/fpga_isv.spec # build the standalone binary into dist/
146
+ ```
147
+
148
+ ## License
149
+
150
+ MIT (see [LICENSE](LICENSE)). The bundled ULX3S photo is from the
151
+ [ULX3S project](https://github.com/ulx3s/ulx3s).
@@ -0,0 +1,131 @@
1
+ # fpga-isv — Interactive Sim Viewer
2
+
3
+ A cross-platform **graphical panel viewer** for
4
+ [interactive-sim](https://github.com/DFiantWorks/interactive-sim). It draws a board *photo* (or a
5
+ blank *panel* you mock up), overlays the LEDs and buttons at their pixel positions, and connects
6
+ that virtual panel to a running HDL simulation: **design-driven flags light the LEDs**, and
7
+ **clicking a button feeds a control back into the design**.
8
+
9
+ `fpga-isv` is a *pure client of the interactive-sim socket API* — it knows nothing about HDL or
10
+ simulators. Anything that speaks the same newline-delimited-JSON protocol works with it. The
11
+ [ULX3S](https://github.com/ulx3s/ulx3s) board ships as the bundled reference example.
12
+
13
+ ```sh
14
+ fpga-isv --example ulx3s
15
+ ```
16
+
17
+ ## How it connects
18
+
19
+ The viewer **listens** on a TCP port; the simulation **connects to it** (set
20
+ `INTERACTIVE_STREAM=host:port` for the sim). This means you can:
21
+
22
+ - **open and close the viewer at any time** — the sim's backend keeps reconnecting;
23
+ - **stop or restart the simulation** while the viewer stays up — on each (re)connect the sim
24
+ replays its full state (every component + last values), so the panel repopulates automatically.
25
+
26
+ ```sh
27
+ fpga-isv --example ulx3s --host 0.0.0.0 --port 7777
28
+ # then run a simulation with INTERACTIVE_STREAM=127.0.0.1:7777
29
+ ```
30
+
31
+ ## Install
32
+
33
+ Three ways, depending on whether you have Python:
34
+
35
+ | Method | Command | Needs |
36
+ |--------|---------|-------|
37
+ | **Standalone binary** (zero prereqs) | download from [Releases](https://github.com/DFiantWorks/interactive-sim-viewer/releases) | nothing |
38
+ | **pipx** (Python users) | `pipx install fpga-isv` | a Python that ships `tkinter` |
39
+ | **Homebrew** (macOS / Linux) | `brew tap DFiantWorks/fpga-isv https://github.com/DFiantWorks/homebrew-fpga-isv && brew install fpga-isv` | Homebrew |
40
+
41
+ The standalone binary bundles its own Python + Tcl/Tk + Pillow, so it runs with no prerequisites.
42
+ `pipx`/source installs need a Python with `tkinter` (stdlib, but a separate OS package on some
43
+ Linux distros, e.g. `apt install python3-tk`); Pillow is pulled in automatically and is required
44
+ for JPEG photos and cropped / non-integer-scaled images (PNG/GIF panels work on stdlib Tk alone).
45
+
46
+ > **macOS note:** the released binaries are currently **unsigned**. Launched by another process
47
+ > directly they run fine; launched via Finder/`open` from a browser-downloaded copy, Gatekeeper
48
+ > blocks them until you clear quarantine — `xattr -dr com.apple.quarantine ./fpga-isv` or
49
+ > right-click → Open once. **Homebrew** installs strip quarantine, so `brew install` avoids the
50
+ > prompt entirely; `pipx` has no binary to quarantine.
51
+
52
+ ## Config
53
+
54
+ A panel is described by one JSON file. All coordinates are in **original-image pixels**;
55
+ `image.scale` scales the photo/panel and every coordinate together for display, and `image.crop`
56
+ trims a photo to the board — the map stays in original-image pixels so it is independent of crop
57
+ and window size.
58
+
59
+ A **photo** panel:
60
+
61
+ ```jsonc
62
+ {
63
+ "title": "ULX3S",
64
+ "image": { "url": "board.png", "crop": [160, 195, 1650, 1010], "scale": 0.72 },
65
+ "leds": { "on_color": "#ff5a36", "shape": "rect", "w": 26, "h": 32,
66
+ "items": [ { "name": "leds", "bit": 0, "x": 632, "y": 480 }, /* ... */ ] },
67
+ "buttons":[ { "name": "btn_pwr", "shape": "circle", "x": 1375, "y": 401, "r": 42, "key": "p" } ]
68
+ }
69
+ ```
70
+
71
+ A **blank panel** mockup (no photo — lay controls out from scratch):
72
+
73
+ ```jsonc
74
+ {
75
+ "title": "My Panel",
76
+ "image": { "width": 1200, "height": 800, "bg": "#101418" },
77
+ "leds": { "on_color": "#56d364", "items": [ { "name": "led_err", "bit": 0, "x": 100, "y": 80 } ] },
78
+ "buttons":[ { "name": "btn_run", "shape": "rect", "x": 200, "y": 300, "w": 120, "h": 60, "toggle": true } ]
79
+ }
80
+ ```
81
+
82
+ - `image.url` may be a remote URL (optionally with `cache`), an absolute path, or a path
83
+ **relative to the config file**. Use `width`/`height`/`bg` instead of `url` for a blank panel.
84
+ - An LED **item** lights when bit `bit` of flag `name` is `1`, so an N-bit bus is N items with
85
+ different bit indices and a 1-bit flag is one item.
86
+ - A **button** sends `1` on press and `0` on release (momentary); `"toggle": true` flips and
87
+ latches. `"key"` mirrors it to a keyboard key (Tk keysym, e.g. `p`, `space`, `Up`).
88
+
89
+ ### Mapping a new photo
90
+
91
+ Run with `--calibrate` and click the image: each click prints the original-image pixel coordinate
92
+ (and marks it on the canvas), so you can read off the x/y for every LED and button.
93
+
94
+ ```sh
95
+ fpga-isv --config my_board.json --calibrate
96
+ ```
97
+
98
+ ## CLI
99
+
100
+ ```
101
+ fpga-isv (--example NAME | --config PATH) [--host 0.0.0.0] [--port 7777] [--refresh] [--calibrate]
102
+ fpga-isv --list-examples
103
+ fpga-isv --version
104
+ ```
105
+
106
+ ## Wire protocol
107
+
108
+ Newline-delimited JSON, one message per line (defined by interactive-sim; every sim→viewer
109
+ message carries `t`, the simulation time in µs):
110
+
111
+ ```
112
+ sim -> viewer {"ev":"reg", "t":1234.5,"name":"btn_run","kind":"ctrl","width":1}
113
+ {"ev":"flag", "t":1234.5,"name":"leds","val":42}
114
+ {"ev":"time", "t":1234.5}
115
+ {"ev":"close","t":1234.5,"name":"btn_run"}
116
+ viewer -> sim {"name":"btn_run","val":1}
117
+ ```
118
+
119
+ ## Develop
120
+
121
+ ```sh
122
+ pip install -e . pytest
123
+ python -m pytest -q # headless protocol/config/geometry tests
124
+ python -m fpga_isv --example ulx3s
125
+ pyinstaller packaging/fpga_isv.spec # build the standalone binary into dist/
126
+ ```
127
+
128
+ ## License
129
+
130
+ MIT (see [LICENSE](LICENSE)). The bundled ULX3S photo is from the
131
+ [ULX3S project](https://github.com/ulx3s/ulx3s).
@@ -0,0 +1,7 @@
1
+ """fpga-isv -- Interactive Sim Viewer.
2
+
3
+ A config-driven graphical panel viewer that is a pure client of the interactive-sim
4
+ newline-delimited-JSON socket API. See viewer.py for the application and protocol.
5
+ """
6
+
7
+ __version__ = "0.1.0"
@@ -0,0 +1,6 @@
1
+ """Entry point for `python -m fpga_isv`."""
2
+
3
+ from fpga_isv.viewer import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,21 @@
1
+ # ULX3S reference example
2
+
3
+ The bundled reference panel for **fpga-isv**: a photo of the
4
+ [ULX3S](https://github.com/ulx3s/ulx3s) FPGA board with the 8 user LEDs and the
5
+ direction / fire / power buttons mapped to their positions on the board.
6
+
7
+ ```sh
8
+ fpga-isv --example ulx3s
9
+ ```
10
+
11
+ Then run a matching interactive-sim simulation (e.g. the `ulx3s_demo` in the
12
+ [interactive-sim](https://github.com/DFiantWorks/interactive-sim) repo) so the sim
13
+ connects to the viewer (`INTERACTIVE_STREAM=host:port`). The 8-bit `leds` flag lights
14
+ the LED row; clicking a button (or its mirrored key) sends a momentary `1`/`0`.
15
+
16
+ - `ulx3s.json` — the panel config (LED/button pixel map, in original-image pixels).
17
+ - `ULX3S_v303_top.png` — the board photo (from the ULX3S project), committed so the
18
+ example is self-contained and works offline.
19
+
20
+ To map a different photo, copy this config, point `image.url` at your image, and run
21
+ `fpga-isv --config your.json --calibrate` to read off pixel coordinates by clicking.
@@ -0,0 +1,39 @@
1
+ {
2
+ "_comment": "Reference example for fpga-isv: a photo of the ULX3S board with the 8 user LEDs and the buttons mapped. Coordinates are in ORIGINAL image pixels (this photo is 1852x1214); image.crop trims the photo and image.scale scales it, but the map stays in original-image pixels so it is independent of crop/scale. The image is the committed local PNG (resolved against this config's dir). Run with --calibrate and click the photo to read off pixel coordinates for retuning or mapping a different photo.",
3
+
4
+ "title": "ULX3S",
5
+
6
+ "image": {
7
+ "url": "ULX3S_v303_top.png",
8
+ "crop": [160, 195, 1650, 1010],
9
+ "scale": 0.72
10
+ },
11
+
12
+ "leds": {
13
+ "on_color": "#ff5a36",
14
+ "shape": "rect",
15
+ "w": 26,
16
+ "h": 32,
17
+ "glow": true,
18
+ "items": [
19
+ { "name": "leds", "bit": 0, "x": 632, "y": 480 },
20
+ { "name": "leds", "bit": 1, "x": 595, "y": 480 },
21
+ { "name": "leds", "bit": 2, "x": 558, "y": 480 },
22
+ { "name": "leds", "bit": 3, "x": 521, "y": 480 },
23
+ { "name": "leds", "bit": 4, "x": 484, "y": 480 },
24
+ { "name": "leds", "bit": 5, "x": 447, "y": 480 },
25
+ { "name": "leds", "bit": 6, "x": 410, "y": 480 },
26
+ { "name": "leds", "bit": 7, "x": 373, "y": 480 }
27
+ ]
28
+ },
29
+
30
+ "buttons": [
31
+ { "name": "btn_pwr", "shape": "circle", "x": 1375, "y": 401, "r": 42, "key": "p" },
32
+ { "name": "btn_fire1", "shape": "circle", "x": 406, "y": 623, "r": 34, "key": "space" },
33
+ { "name": "btn_fire2", "shape": "circle", "x": 572, "y": 628, "r": 34, "key": "Return" },
34
+ { "name": "btn_up", "shape": "circle", "x": 1210, "y": 777, "r": 36, "key": "Up" },
35
+ { "name": "btn_down", "shape": "circle", "x": 1206, "y": 907, "r": 36, "key": "Down" },
36
+ { "name": "btn_left", "shape": "circle", "x": 1039, "y": 906, "r": 36, "key": "Left" },
37
+ { "name": "btn_right", "shape": "circle", "x": 1377, "y": 910, "r": 36, "key": "Right" }
38
+ ]
39
+ }
@@ -0,0 +1,39 @@
1
+ """Locate the bundled example panels.
2
+
3
+ Each example is a subdirectory of ``examples/`` holding ``<name>.json`` (the board/panel
4
+ config) plus any assets it references (e.g. a board photo). The directory resolves three ways:
5
+
6
+ * frozen by PyInstaller -> ``<_MEIPASS>/fpga_isv/examples`` (see packaging/fpga_isv.spec)
7
+ * installed / run from src -> ``<this package dir>/examples``
8
+ """
9
+
10
+ import os
11
+ import sys
12
+
13
+
14
+ def examples_dir():
15
+ """Absolute path to the bundled examples directory."""
16
+ if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
17
+ return os.path.join(sys._MEIPASS, "fpga_isv", "examples")
18
+ return os.path.join(os.path.dirname(os.path.abspath(__file__)), "examples")
19
+
20
+
21
+ def list_examples():
22
+ """Sorted names of bundled examples (subdirs that contain ``<name>.json``)."""
23
+ root = examples_dir()
24
+ if not os.path.isdir(root):
25
+ return []
26
+ names = []
27
+ for name in sorted(os.listdir(root)):
28
+ if os.path.isfile(os.path.join(root, name, name + ".json")):
29
+ names.append(name)
30
+ return names
31
+
32
+
33
+ def example_config_path(name):
34
+ """Path to ``examples/<name>/<name>.json``; raises ValueError if it doesn't exist."""
35
+ path = os.path.join(examples_dir(), name, name + ".json")
36
+ if not os.path.isfile(path):
37
+ available = ", ".join(list_examples()) or "(none bundled)"
38
+ raise ValueError(f"unknown example {name!r}; available: {available}")
39
+ return path