devlab-fpga 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mrju10
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,2 @@
1
+ include README.md
2
+ include LICENSE
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.4
2
+ Name: devlab-fpga
3
+ Version: 0.1.0
4
+ Summary: FPGA development helper for installing OSS CAD Suite and running build/flash flows.
5
+ Author-email: Mrju10 <name@email.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/unit-electronics/unit_devlab_lib
8
+ Project-URL: Source, https://github.com/unit-electronics/unit_devlab_lib
9
+ Keywords: fpga,yosys,oss-cad-suite,openfpgaloader,devlab,devlab-fpga
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Operating System :: Microsoft :: Windows
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Topic :: Software Development :: Build Tools
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: tomli>=2.0.1; python_version < "3.11"
23
+ Dynamic: license-file
24
+
25
+ # devlab
26
+
27
+ `devlab` is a Python CLI package for FPGA development. It installs the
28
+ matching OSS CAD Suite build for the current operating system, creates a small
29
+ FPGA project, and runs build/flash commands from `devlab.toml`.
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ pip install devlab-fpga
35
+ ```
36
+
37
+ For local development from this repository:
38
+
39
+ ```bash
40
+ pip install -e .
41
+ ```
42
+
43
+ ## Commands
44
+
45
+ ```bash
46
+ devlab doctor
47
+ devlab install
48
+ devlab new blink
49
+ devlab new blink-vhdl --hdl vhdl
50
+ cd blink
51
+ devlab build
52
+ devlab flash
53
+ ```
54
+
55
+ `devlab install` downloads OSS CAD Suite release `2026-07-06` from
56
+ YosysHQ. The installer selects the correct asset for:
57
+
58
+ - Linux x64
59
+ - Linux arm64
60
+ - macOS x64
61
+ - macOS arm64
62
+ - Windows x64
63
+
64
+ The default install location is `~/.devlab`. Set `DEVLAB_HOME` to use a
65
+ different directory.
66
+
67
+ ## OSS CAD Suite Source
68
+
69
+ Default release:
70
+
71
+ https://github.com/YosysHQ/oss-cad-suite-build/releases/tag/2026-07-06
72
+
73
+ Example assets:
74
+
75
+ ```text
76
+ https://github.com/YosysHQ/oss-cad-suite-build/releases/download/2026-07-06/oss-cad-suite-linux-arm64-20260706.tgz
77
+ https://github.com/YosysHQ/oss-cad-suite-build/releases/download/2026-07-06/oss-cad-suite-windows-x64-20260706.exe
78
+ ```
79
+
80
+ Downloaded archives are verified with the SHA-256 digest published by the
81
+ GitHub release API.
82
+
83
+ ## Project Format
84
+
85
+ `devlab new blink` creates:
86
+
87
+ ```text
88
+ blink/
89
+ devlab.toml
90
+ pins.cst
91
+ src/top.v
92
+ ```
93
+
94
+ Use `--hdl vhdl` to create `src/top.vhd` instead:
95
+
96
+ ```bash
97
+ devlab new blink-vhdl --hdl vhdl
98
+ ```
99
+
100
+ Default `devlab.toml`:
101
+
102
+ ```toml
103
+ [fpga]
104
+ family = "GW1N-9C"
105
+ device = "GW1NR-LV9QN88PC6/I5"
106
+ cst = "pins.cst"
107
+
108
+ [build]
109
+ top = "top"
110
+ sources = ["src/top.v"]
111
+ constraints = "pins.cst"
112
+ build_dir = "build"
113
+
114
+ [flash]
115
+ board = "tangnano9k"
116
+ mode = "sram"
117
+ verify = false
118
+ ```
119
+
120
+ For Gowin, `family` is the FPGA series used by the packer and place-and-route
121
+ flow, for example `GW1N-9C`. `device` is the complete part number, for example
122
+ `GW1NR-LV9QN88PC6/I5`. `pins.cst` is the Gowin constraints file.
123
+
124
+ Update `pins.cst` and `[flash].board` for the real FPGA board before building
125
+ and flashing hardware.
126
+
127
+ ## Build Flows
128
+
129
+ For Gowin `GW1N-9C` / `GW1NR-LV9QN88PC6/I5`, `devlab build` runs:
130
+
131
+ ```bash
132
+ yosys
133
+ nextpnr-himbaechel
134
+ gowin_pack
135
+ ```
136
+
137
+ The iCE40 and ECP5 flows are still available by setting `family = "ice40"` or
138
+ `family = "ecp5"` in `devlab.toml`.
139
+
140
+ For iCE40, it runs:
141
+
142
+ ```bash
143
+ yosys
144
+ nextpnr-ice40
145
+ icepack
146
+ ```
147
+
148
+ `devlab flash` uses `openFPGALoader`. By default it writes SRAM, so the FPGA
149
+ loses the design after power cycling. Use flash mode to write the bitstream to
150
+ non-volatile memory:
151
+
152
+ ```bash
153
+ devlab flash --mode flash
154
+ ```
155
+
156
+ Before writing flash on board variants where flash may or may not be populated,
157
+ run detection:
158
+
159
+ ```bash
160
+ devlab flash --detect
161
+ ```
162
+
163
+ Persistent flash can also be configured in `devlab.toml`:
164
+
165
+ ```toml
166
+ [flash]
167
+ board = "tangnano9k"
168
+ mode = "flash"
169
+ verify = false
170
+ # external_flash = true
171
+ # offset = "0"
172
+ ```
173
+
174
+ For Tang Nano 9K/Gowin, flash write verification may print
175
+ `writing verification not supported` and fail the CRC check even when the flash
176
+ write completed. Keep `verify = false` unless the selected board/programming
177
+ path explicitly supports flash verification.
178
+
179
+ When sources end in `.vhd` or `.vhdl`, `devlab build` runs Yosys with the
180
+ GHDL plugin before synthesis. OSS CAD Suite is expected to provide that plugin.
181
+
182
+ Use `--dry-run` to print the commands without executing them:
183
+
184
+ ```bash
185
+ devlab build --dry-run
186
+ devlab flash --dry-run --board tangnano9k
187
+ devlab flash --dry-run --mode flash
188
+ ```
@@ -0,0 +1,164 @@
1
+ # devlab
2
+
3
+ `devlab` is a Python CLI package for FPGA development. It installs the
4
+ matching OSS CAD Suite build for the current operating system, creates a small
5
+ FPGA project, and runs build/flash commands from `devlab.toml`.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install devlab-fpga
11
+ ```
12
+
13
+ For local development from this repository:
14
+
15
+ ```bash
16
+ pip install -e .
17
+ ```
18
+
19
+ ## Commands
20
+
21
+ ```bash
22
+ devlab doctor
23
+ devlab install
24
+ devlab new blink
25
+ devlab new blink-vhdl --hdl vhdl
26
+ cd blink
27
+ devlab build
28
+ devlab flash
29
+ ```
30
+
31
+ `devlab install` downloads OSS CAD Suite release `2026-07-06` from
32
+ YosysHQ. The installer selects the correct asset for:
33
+
34
+ - Linux x64
35
+ - Linux arm64
36
+ - macOS x64
37
+ - macOS arm64
38
+ - Windows x64
39
+
40
+ The default install location is `~/.devlab`. Set `DEVLAB_HOME` to use a
41
+ different directory.
42
+
43
+ ## OSS CAD Suite Source
44
+
45
+ Default release:
46
+
47
+ https://github.com/YosysHQ/oss-cad-suite-build/releases/tag/2026-07-06
48
+
49
+ Example assets:
50
+
51
+ ```text
52
+ https://github.com/YosysHQ/oss-cad-suite-build/releases/download/2026-07-06/oss-cad-suite-linux-arm64-20260706.tgz
53
+ https://github.com/YosysHQ/oss-cad-suite-build/releases/download/2026-07-06/oss-cad-suite-windows-x64-20260706.exe
54
+ ```
55
+
56
+ Downloaded archives are verified with the SHA-256 digest published by the
57
+ GitHub release API.
58
+
59
+ ## Project Format
60
+
61
+ `devlab new blink` creates:
62
+
63
+ ```text
64
+ blink/
65
+ devlab.toml
66
+ pins.cst
67
+ src/top.v
68
+ ```
69
+
70
+ Use `--hdl vhdl` to create `src/top.vhd` instead:
71
+
72
+ ```bash
73
+ devlab new blink-vhdl --hdl vhdl
74
+ ```
75
+
76
+ Default `devlab.toml`:
77
+
78
+ ```toml
79
+ [fpga]
80
+ family = "GW1N-9C"
81
+ device = "GW1NR-LV9QN88PC6/I5"
82
+ cst = "pins.cst"
83
+
84
+ [build]
85
+ top = "top"
86
+ sources = ["src/top.v"]
87
+ constraints = "pins.cst"
88
+ build_dir = "build"
89
+
90
+ [flash]
91
+ board = "tangnano9k"
92
+ mode = "sram"
93
+ verify = false
94
+ ```
95
+
96
+ For Gowin, `family` is the FPGA series used by the packer and place-and-route
97
+ flow, for example `GW1N-9C`. `device` is the complete part number, for example
98
+ `GW1NR-LV9QN88PC6/I5`. `pins.cst` is the Gowin constraints file.
99
+
100
+ Update `pins.cst` and `[flash].board` for the real FPGA board before building
101
+ and flashing hardware.
102
+
103
+ ## Build Flows
104
+
105
+ For Gowin `GW1N-9C` / `GW1NR-LV9QN88PC6/I5`, `devlab build` runs:
106
+
107
+ ```bash
108
+ yosys
109
+ nextpnr-himbaechel
110
+ gowin_pack
111
+ ```
112
+
113
+ The iCE40 and ECP5 flows are still available by setting `family = "ice40"` or
114
+ `family = "ecp5"` in `devlab.toml`.
115
+
116
+ For iCE40, it runs:
117
+
118
+ ```bash
119
+ yosys
120
+ nextpnr-ice40
121
+ icepack
122
+ ```
123
+
124
+ `devlab flash` uses `openFPGALoader`. By default it writes SRAM, so the FPGA
125
+ loses the design after power cycling. Use flash mode to write the bitstream to
126
+ non-volatile memory:
127
+
128
+ ```bash
129
+ devlab flash --mode flash
130
+ ```
131
+
132
+ Before writing flash on board variants where flash may or may not be populated,
133
+ run detection:
134
+
135
+ ```bash
136
+ devlab flash --detect
137
+ ```
138
+
139
+ Persistent flash can also be configured in `devlab.toml`:
140
+
141
+ ```toml
142
+ [flash]
143
+ board = "tangnano9k"
144
+ mode = "flash"
145
+ verify = false
146
+ # external_flash = true
147
+ # offset = "0"
148
+ ```
149
+
150
+ For Tang Nano 9K/Gowin, flash write verification may print
151
+ `writing verification not supported` and fail the CRC check even when the flash
152
+ write completed. Keep `verify = false` unless the selected board/programming
153
+ path explicitly supports flash verification.
154
+
155
+ When sources end in `.vhd` or `.vhdl`, `devlab build` runs Yosys with the
156
+ GHDL plugin before synthesis. OSS CAD Suite is expected to provide that plugin.
157
+
158
+ Use `--dry-run` to print the commands without executing them:
159
+
160
+ ```bash
161
+ devlab build --dry-run
162
+ devlab flash --dry-run --board tangnano9k
163
+ devlab flash --dry-run --mode flash
164
+ ```
@@ -0,0 +1,6 @@
1
+ """Python helpers for the devlab FPGA command line tools."""
2
+
3
+ __all__ = ["__version__"]
4
+
5
+ __version__ = "0.1.0"
6
+
@@ -0,0 +1,5 @@
1
+ from .cli import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ raise SystemExit(main())
@@ -0,0 +1,170 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import platform
5
+ from pathlib import Path
6
+
7
+ from . import __version__
8
+ from .errors import DevlabError
9
+ from .platforms import current_platform
10
+ from .project import build_project, create_project, detect_flash, doctor, flash_project
11
+ from .toolchain import (
12
+ OSS_CAD_SUITE_RELEASE_URL,
13
+ archive_path,
14
+ devlab_home,
15
+ install_oss_cad_suite,
16
+ install_path,
17
+ select_asset,
18
+ )
19
+
20
+
21
+ def build_parser() -> argparse.ArgumentParser:
22
+ parser = argparse.ArgumentParser(
23
+ prog="devlab",
24
+ description="Install FPGA toolchains and run local FPGA build flows.",
25
+ )
26
+ parser.add_argument("--version", action="version", version=f"devlab {__version__}")
27
+
28
+ commands = parser.add_subparsers(dest="command", required=True)
29
+
30
+ doctor_parser = commands.add_parser("doctor", help="Check local FPGA tools.")
31
+ doctor_parser.add_argument(
32
+ "--strict",
33
+ action="store_true",
34
+ help="Exit 1 when a tool is missing.",
35
+ )
36
+ doctor_parser.set_defaults(func=_doctor)
37
+
38
+ install_parser = commands.add_parser("install", help="Install OSS CAD Suite.")
39
+ install_parser.add_argument("--force", action="store_true", help="Re-download and reinstall.")
40
+ install_parser.add_argument(
41
+ "--run-installer",
42
+ action="store_true",
43
+ help="On Windows, execute the downloaded OSS CAD Suite installer.",
44
+ )
45
+ install_parser.set_defaults(func=_install)
46
+
47
+ new_parser = commands.add_parser("new", help="Create a new FPGA project.")
48
+ new_parser.add_argument("name", help="Project directory name.")
49
+ new_parser.add_argument("--dir", type=Path, help="Target directory. Defaults to NAME.")
50
+ new_parser.add_argument("--force", action="store_true", help="Overwrite template files.")
51
+ new_parser.add_argument(
52
+ "--hdl",
53
+ choices=("verilog", "vhdl"),
54
+ default="verilog",
55
+ help="Source template language. Defaults to verilog.",
56
+ )
57
+ new_parser.set_defaults(func=_new)
58
+
59
+ build = commands.add_parser("build", help="Build the current FPGA project.")
60
+ build.add_argument("-c", "--config", type=Path, help="Path to devlab.toml.")
61
+ build.add_argument(
62
+ "--dry-run",
63
+ action="store_true",
64
+ help="Print commands without running them.",
65
+ )
66
+ build.set_defaults(func=_build)
67
+
68
+ flash = commands.add_parser("flash", help="Flash the built FPGA bitstream.")
69
+ flash.add_argument("-c", "--config", type=Path, help="Path to devlab.toml.")
70
+ flash.add_argument("--board", help="openFPGALoader board name.")
71
+ flash.add_argument("--artifact", type=Path, help="Bitstream path.")
72
+ flash.add_argument(
73
+ "--mode",
74
+ choices=("sram", "flash"),
75
+ help="Write target. sram is temporary; flash is persistent after reboot.",
76
+ )
77
+ flash.add_argument(
78
+ "--detect",
79
+ action="store_true",
80
+ help="Detect FPGA and flash support without writing a bitstream.",
81
+ )
82
+ flash.add_argument(
83
+ "--verify",
84
+ action="store_true",
85
+ help="Verify flash write when supported by the board/programming path.",
86
+ )
87
+ flash.add_argument(
88
+ "--no-verify",
89
+ action="store_true",
90
+ help="Skip flash verification when using --mode flash.",
91
+ )
92
+ flash.add_argument(
93
+ "--external-flash",
94
+ action="store_true",
95
+ help="Use external flash when the device has internal and external storage.",
96
+ )
97
+ flash.add_argument("--offset", help="Flash offset in bytes.")
98
+ flash.add_argument("--dry-run", action="store_true", help="Print command without running it.")
99
+ flash.set_defaults(func=_flash)
100
+
101
+ return parser
102
+
103
+
104
+ def main(argv: list[str] | None = None) -> int:
105
+ parser = build_parser()
106
+ args = parser.parse_args(argv)
107
+ try:
108
+ return args.func(args)
109
+ except DevlabError as exc:
110
+ parser.exit(2, f"devlab: error: {exc}\n")
111
+
112
+
113
+ def _doctor(args: argparse.Namespace) -> int:
114
+ platform_id = current_platform()
115
+ asset = select_asset(platform_id)
116
+ print(f"devlab: {__version__}")
117
+ print(f"python: {platform.python_version()} ({platform.python_implementation()})")
118
+ print(f"platform: {platform_id.key}")
119
+ print(f"home: {devlab_home()}")
120
+ print(f"release: {OSS_CAD_SUITE_RELEASE_URL}")
121
+ print(f"asset: {asset.name}")
122
+ print(f"archive: {archive_path(asset)}")
123
+ print(f"toolchain: {install_path(asset)}")
124
+ return doctor(strict=args.strict)
125
+
126
+
127
+ def _install(args: argparse.Namespace) -> int:
128
+ destination = install_oss_cad_suite(
129
+ force=args.force,
130
+ run_windows_installer=args.run_installer,
131
+ )
132
+ print(f"OSS CAD Suite installed at {destination}")
133
+ return 0
134
+
135
+
136
+ def _new(args: argparse.Namespace) -> int:
137
+ root = create_project(args.name, directory=args.dir, force=args.force, hdl=args.hdl)
138
+ print(f"Created FPGA project at {root}")
139
+ return 0
140
+
141
+
142
+ def _build(args: argparse.Namespace) -> int:
143
+ artifact = build_project(config_path=args.config, dry_run=args.dry_run)
144
+ print(f"Build artifact: {artifact}")
145
+ return 0
146
+
147
+
148
+ def _flash(args: argparse.Namespace) -> int:
149
+ if args.verify and args.no_verify:
150
+ raise DevlabError("--verify and --no-verify cannot be used together.")
151
+
152
+ if args.detect:
153
+ detect_flash(
154
+ config_path=args.config,
155
+ board=args.board,
156
+ dry_run=args.dry_run,
157
+ )
158
+ return 0
159
+
160
+ flash_project(
161
+ config_path=args.config,
162
+ board=args.board,
163
+ artifact=args.artifact,
164
+ mode=args.mode,
165
+ verify=True if args.verify else False if args.no_verify else None,
166
+ external_flash=True if args.external_flash else None,
167
+ offset=args.offset,
168
+ dry_run=args.dry_run,
169
+ )
170
+ return 0
@@ -0,0 +1,3 @@
1
+ class DevlabError(RuntimeError):
2
+ """User-facing error raised by devlab commands."""
3
+
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ import platform
4
+ from dataclasses import dataclass
5
+
6
+ from .errors import DevlabError
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class PlatformId:
11
+ os_name: str
12
+ arch: str
13
+
14
+ @property
15
+ def key(self) -> str:
16
+ return f"{self.os_name}-{self.arch}"
17
+
18
+
19
+ def current_platform() -> PlatformId:
20
+ system = platform.system().lower()
21
+ machine = platform.machine().lower()
22
+
23
+ if system == "linux":
24
+ os_name = "linux"
25
+ elif system == "darwin":
26
+ os_name = "darwin"
27
+ elif system in {"windows", "msys", "cygwin"}:
28
+ os_name = "windows"
29
+ else:
30
+ raise DevlabError(f"Unsupported operating system: {platform.system()}")
31
+
32
+ if machine in {"x86_64", "amd64"}:
33
+ arch = "x64"
34
+ elif machine in {"aarch64", "arm64"}:
35
+ arch = "arm64"
36
+ else:
37
+ raise DevlabError(f"Unsupported CPU architecture: {platform.machine()}")
38
+
39
+ return PlatformId(os_name=os_name, arch=arch)
40
+