openspacehdl 0.0.1__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.
- openspacehdl-0.0.1/CHANGELOG.md +10 -0
- openspacehdl-0.0.1/LICENSE +21 -0
- openspacehdl-0.0.1/MANIFEST.in +3 -0
- openspacehdl-0.0.1/PKG-INFO +77 -0
- openspacehdl-0.0.1/README.md +51 -0
- openspacehdl-0.0.1/pyproject.toml +44 -0
- openspacehdl-0.0.1/requirements-dev.txt +4 -0
- openspacehdl-0.0.1/setup.cfg +4 -0
- openspacehdl-0.0.1/src/openspacehdl/__init__.py +3 -0
- openspacehdl-0.0.1/src/openspacehdl/__main__.py +7 -0
- openspacehdl-0.0.1/src/openspacehdl/cli.py +75 -0
- openspacehdl-0.0.1/src/openspacehdl/data/family.json +158 -0
- openspacehdl-0.0.1/src/openspacehdl/family.py +71 -0
- openspacehdl-0.0.1/src/openspacehdl.egg-info/PKG-INFO +77 -0
- openspacehdl-0.0.1/src/openspacehdl.egg-info/SOURCES.txt +18 -0
- openspacehdl-0.0.1/src/openspacehdl.egg-info/dependency_links.txt +1 -0
- openspacehdl-0.0.1/src/openspacehdl.egg-info/entry_points.txt +2 -0
- openspacehdl-0.0.1/src/openspacehdl.egg-info/top_level.txt +1 -0
- openspacehdl-0.0.1/tests/test_cli.py +137 -0
- openspacehdl-0.0.1/tools/family_data.py +91 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes of the package. Versions follow [semantic versioning](https://semver.org).
|
|
4
|
+
|
|
5
|
+
## 0.0.1 - 2026-10-22
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `openspacehdl --version` and `openspacehdl info [project]`: the projects of the family as the website shows them
|
|
10
|
+
(website commit 1b3d619).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Julian Schneider
|
|
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,77 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openspacehdl
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Open Space HDL, open-source VHDL IP cores for spacecraft on-board data handling: early release that lists the family
|
|
5
|
+
Author-email: Julian Schneider <julian.schneider@openspacehdl.org>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://openspacehdl.org
|
|
8
|
+
Keywords: vhdl,fpga,ip-core,space,spacewire,spacefibre,ccsds,ecss
|
|
9
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# openspacehdl
|
|
28
|
+
|
|
29
|
+
Python package of [Open Space HDL](https://openspacehdl.org), a family of open-source VHDL IP cores for spacecraft
|
|
30
|
+
on-board data handling.
|
|
31
|
+
|
|
32
|
+
This is an early release. It lists the projects of the family as the website shows them; it does not contain the
|
|
33
|
+
cores or any tools yet.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```shell
|
|
38
|
+
pip install openspacehdl
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The package needs Python 3.10 or later and has no dependencies. On a machine without network access, copy the wheel
|
|
42
|
+
and install it from the file:
|
|
43
|
+
|
|
44
|
+
```shell
|
|
45
|
+
pip install openspacehdl-<version>-py3-none-any.whl
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Use
|
|
49
|
+
|
|
50
|
+
```shell
|
|
51
|
+
openspacehdl --version # version of the package
|
|
52
|
+
openspacehdl info # every project of the family: standard, summary, TRL, documentation and source
|
|
53
|
+
openspacehdl info OpenFibre # one project, by name or repository name
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`python -m openspacehdl` works the same way.
|
|
57
|
+
|
|
58
|
+
The list comes from the project cards of [openspacehdl.org](https://openspacehdl.org) at the time of the release.
|
|
59
|
+
A project that is not yet public is listed as "Coming soon", without links.
|
|
60
|
+
|
|
61
|
+
## Development
|
|
62
|
+
|
|
63
|
+
```shell
|
|
64
|
+
pip install -r requirements-dev.txt
|
|
65
|
+
pip install -e .
|
|
66
|
+
pytest
|
|
67
|
+
python tools/family_data.py --site <clone of open-space-hdl.github.io> --check
|
|
68
|
+
python -m build
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`tools/family_data.py` writes `src/openspacehdl/data/family.json` from `_data/projects.yml` and
|
|
72
|
+
`_data/project_groups.yml` of the website repository; `--check` only compares.
|
|
73
|
+
|
|
74
|
+
## Licence
|
|
75
|
+
|
|
76
|
+
The package is released under the [MIT License](LICENSE). The IP cores of the family have their own licence (PSI HDL
|
|
77
|
+
Library License), stated in each repository.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# openspacehdl
|
|
2
|
+
|
|
3
|
+
Python package of [Open Space HDL](https://openspacehdl.org), a family of open-source VHDL IP cores for spacecraft
|
|
4
|
+
on-board data handling.
|
|
5
|
+
|
|
6
|
+
This is an early release. It lists the projects of the family as the website shows them; it does not contain the
|
|
7
|
+
cores or any tools yet.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```shell
|
|
12
|
+
pip install openspacehdl
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The package needs Python 3.10 or later and has no dependencies. On a machine without network access, copy the wheel
|
|
16
|
+
and install it from the file:
|
|
17
|
+
|
|
18
|
+
```shell
|
|
19
|
+
pip install openspacehdl-<version>-py3-none-any.whl
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Use
|
|
23
|
+
|
|
24
|
+
```shell
|
|
25
|
+
openspacehdl --version # version of the package
|
|
26
|
+
openspacehdl info # every project of the family: standard, summary, TRL, documentation and source
|
|
27
|
+
openspacehdl info OpenFibre # one project, by name or repository name
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`python -m openspacehdl` works the same way.
|
|
31
|
+
|
|
32
|
+
The list comes from the project cards of [openspacehdl.org](https://openspacehdl.org) at the time of the release.
|
|
33
|
+
A project that is not yet public is listed as "Coming soon", without links.
|
|
34
|
+
|
|
35
|
+
## Development
|
|
36
|
+
|
|
37
|
+
```shell
|
|
38
|
+
pip install -r requirements-dev.txt
|
|
39
|
+
pip install -e .
|
|
40
|
+
pytest
|
|
41
|
+
python tools/family_data.py --site <clone of open-space-hdl.github.io> --check
|
|
42
|
+
python -m build
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`tools/family_data.py` writes `src/openspacehdl/data/family.json` from `_data/projects.yml` and
|
|
46
|
+
`_data/project_groups.yml` of the website repository; `--check` only compares.
|
|
47
|
+
|
|
48
|
+
## Licence
|
|
49
|
+
|
|
50
|
+
The package is released under the [MIT License](LICENSE). The IP cores of the family have their own licence (PSI HDL
|
|
51
|
+
Library License), stated in each repository.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "openspacehdl"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Open Space HDL, open-source VHDL IP cores for spacecraft on-board data handling: early release that lists the family"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
authors = [{ name = "Julian Schneider", email = "julian.schneider@openspacehdl.org" }]
|
|
14
|
+
keywords = ["vhdl", "fpga", "ip-core", "space", "spacewire", "spacefibre", "ccsds", "ecss"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Intended Audience :: Science/Research",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Programming Language :: Python :: 3.14",
|
|
28
|
+
"Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://openspacehdl.org"
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
openspacehdl = "openspacehdl.cli:main"
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.dynamic]
|
|
38
|
+
version = { attr = "openspacehdl.__version__" }
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.package-data]
|
|
41
|
+
openspacehdl = ["data/*.json"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Command line of openspacehdl."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
import textwrap
|
|
8
|
+
|
|
9
|
+
from openspacehdl import __version__
|
|
10
|
+
from openspacehdl.family import Family, Project, load
|
|
11
|
+
|
|
12
|
+
WIDTH = 100
|
|
13
|
+
INDENT = " "
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _wrap(text: str, indent: str) -> list[str]:
|
|
17
|
+
return textwrap.wrap(text, WIDTH, initial_indent=indent, subsequent_indent=indent)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _project_lines(family: Family, p: Project) -> list[str]:
|
|
21
|
+
state = "Coming soon" if not p.public else (f"TRL {p.trl}" if p.trl else "")
|
|
22
|
+
head = " ".join(x for x in (p.name, state) if x)
|
|
23
|
+
lines = [f" {head}"]
|
|
24
|
+
if p.tagline:
|
|
25
|
+
lines.append(f"{INDENT}{', '.join(p.tagline)}")
|
|
26
|
+
lines += _wrap(p.summary, INDENT)
|
|
27
|
+
if p.public:
|
|
28
|
+
if p.trl and p.trl_status:
|
|
29
|
+
lines += _wrap(f"Status: {p.trl_status}", INDENT)
|
|
30
|
+
docs = family.docs_url(p)
|
|
31
|
+
if docs:
|
|
32
|
+
lines.append(f"{INDENT}Documentation: {docs}")
|
|
33
|
+
lines.append(f"{INDENT}Source: {family.source_url(p)}")
|
|
34
|
+
return lines
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def info(family: Family, name: str | None) -> int:
|
|
38
|
+
if name is not None:
|
|
39
|
+
p = family.find(name)
|
|
40
|
+
if p is None:
|
|
41
|
+
names = ", ".join(x.name for x in family.projects)
|
|
42
|
+
print(f"openspacehdl: no project named {name!r}; the family has {names}", file=sys.stderr)
|
|
43
|
+
return 2
|
|
44
|
+
print("\n".join(_project_lines(family, p)))
|
|
45
|
+
return 0
|
|
46
|
+
|
|
47
|
+
out = ["Open Space HDL: open-source VHDL IP cores for spacecraft on-board data handling", family.site]
|
|
48
|
+
for g in family.groups:
|
|
49
|
+
members = family.members(g)
|
|
50
|
+
if not members:
|
|
51
|
+
continue
|
|
52
|
+
out += ["", g.title, *_wrap(g.summary, " ")]
|
|
53
|
+
for p in members:
|
|
54
|
+
out += ["", *_project_lines(family, p)]
|
|
55
|
+
print("\n".join(out))
|
|
56
|
+
return 0
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def main(argv: list[str] | None = None) -> int:
|
|
60
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
61
|
+
sys.stdout.reconfigure(errors="replace")
|
|
62
|
+
parser = argparse.ArgumentParser(
|
|
63
|
+
prog="openspacehdl",
|
|
64
|
+
description="Open Space HDL: open-source VHDL IP cores for spacecraft on-board data handling.",
|
|
65
|
+
)
|
|
66
|
+
parser.add_argument("--version", action="version", version=f"openspacehdl {__version__}")
|
|
67
|
+
sub = parser.add_subparsers(dest="command", metavar="<command>")
|
|
68
|
+
p_info = sub.add_parser("info", help="list the projects of the family, as the website shows them")
|
|
69
|
+
p_info.add_argument("project", nargs="?", help="show only this project (name or repository)")
|
|
70
|
+
args = parser.parse_args(argv)
|
|
71
|
+
|
|
72
|
+
if args.command == "info":
|
|
73
|
+
return info(load(), args.project)
|
|
74
|
+
parser.print_help()
|
|
75
|
+
return 0
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source": {
|
|
3
|
+
"repository": "open-space-hdl/open-space-hdl.github.io",
|
|
4
|
+
"commit": "1b3d619cb4b4cef3fe7a3893a0d0040c1d4049bb"
|
|
5
|
+
},
|
|
6
|
+
"site": "https://openspacehdl.org",
|
|
7
|
+
"github": "https://github.com/open-space-hdl",
|
|
8
|
+
"groups": [
|
|
9
|
+
{
|
|
10
|
+
"id": "interfaces-protocols",
|
|
11
|
+
"title": "Interfaces & Protocols",
|
|
12
|
+
"summary": "SpaceWire and SpaceFibre links, CAN and 1553 buses, and the protocols that run over them."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "processing-modules",
|
|
16
|
+
"title": "Processing Modules",
|
|
17
|
+
"summary": "Cores that process payload data on board, such as image compression and encryption."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "system-infrastructure",
|
|
21
|
+
"title": "System Infrastructure",
|
|
22
|
+
"summary": "Cores that move data between the memories and the peripherals of an on-board system."
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "building-blocks",
|
|
26
|
+
"title": "Building Blocks",
|
|
27
|
+
"summary": "The fault-tolerant Open Logic entities that every core is built on."
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"projects": [
|
|
31
|
+
{
|
|
32
|
+
"name": "OpenWire",
|
|
33
|
+
"group": "interfaces-protocols",
|
|
34
|
+
"tagline": [
|
|
35
|
+
"ECSS-E-ST-50-12C Rev.1"
|
|
36
|
+
],
|
|
37
|
+
"summary": "SpaceWire port with node interface: Data-Strobe encoding, link control, packets, time-codes and distributed interrupts.",
|
|
38
|
+
"public": true,
|
|
39
|
+
"repo": "openwire",
|
|
40
|
+
"docs": true,
|
|
41
|
+
"trl": 3,
|
|
42
|
+
"trl_status": "Fully verified by simulation. Not yet tested on hardware, no flight heritage."
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "OpenFibre",
|
|
46
|
+
"group": "interfaces-protocols",
|
|
47
|
+
"tagline": [
|
|
48
|
+
"ECSS-E-ST-50-11C"
|
|
49
|
+
],
|
|
50
|
+
"summary": "SpaceFibre port with 1 to 4 lanes, up to 32 virtual channels, quality of service, broadcast and error recovery.",
|
|
51
|
+
"public": true,
|
|
52
|
+
"repo": "openfibre",
|
|
53
|
+
"docs": true,
|
|
54
|
+
"trl": 3,
|
|
55
|
+
"trl_status": "Fully verified by simulation. Not yet tested on hardware, no flight heritage."
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "OpenRMAP",
|
|
59
|
+
"group": "interfaces-protocols",
|
|
60
|
+
"tagline": [
|
|
61
|
+
"ECSS-E-ST-50-52C"
|
|
62
|
+
],
|
|
63
|
+
"summary": "RMAP target and initiator for reading and writing remote memory over SpaceWire and SpaceFibre networks.",
|
|
64
|
+
"public": true,
|
|
65
|
+
"repo": "openrmap",
|
|
66
|
+
"docs": true,
|
|
67
|
+
"trl": 3,
|
|
68
|
+
"trl_status": "Fully verified by simulation. Not yet tested on hardware, no flight heritage."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "OpenSPP",
|
|
72
|
+
"group": "interfaces-protocols",
|
|
73
|
+
"tagline": [
|
|
74
|
+
"CCSDS 133.0-B-2"
|
|
75
|
+
],
|
|
76
|
+
"summary": "Space Packet service with APID routing and loss detection for up to 16 send and 16 receive ports, over SpaceWire, SpaceFibre or other links.",
|
|
77
|
+
"public": true,
|
|
78
|
+
"repo": "openspp",
|
|
79
|
+
"docs": true,
|
|
80
|
+
"trl": 3,
|
|
81
|
+
"trl_status": "Fully verified by simulation. Not yet tested on hardware, no flight heritage."
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"name": "OpenCAN",
|
|
85
|
+
"group": "interfaces-protocols",
|
|
86
|
+
"tagline": [
|
|
87
|
+
"ECSS-E-ST-50-15C"
|
|
88
|
+
],
|
|
89
|
+
"summary": "CAN network core with nominal and redundant bus, bus redundancy management, CANopen services and time distribution.",
|
|
90
|
+
"public": false
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "Open1553",
|
|
94
|
+
"group": "interfaces-protocols",
|
|
95
|
+
"tagline": [
|
|
96
|
+
"ECSS-E-ST-50-13C"
|
|
97
|
+
],
|
|
98
|
+
"summary": "Bus controller, remote terminal and bus monitor for the dual-redundant 1553 data bus, with the Manchester II codec in the core.",
|
|
99
|
+
"public": false
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"name": "OpenCompact",
|
|
103
|
+
"group": "processing-modules",
|
|
104
|
+
"tagline": [
|
|
105
|
+
"CCSDS 123.0-B-2"
|
|
106
|
+
],
|
|
107
|
+
"summary": "Lossless and near-lossless compressor for multispectral and hyperspectral images, with all predictors and entropy coders of the standard.",
|
|
108
|
+
"public": true,
|
|
109
|
+
"repo": "opencompact",
|
|
110
|
+
"docs": true,
|
|
111
|
+
"trl": 3,
|
|
112
|
+
"trl_status": "Fully verified by simulation. Not yet tested on hardware, no flight heritage."
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "OpenCrypto",
|
|
116
|
+
"group": "processing-modules",
|
|
117
|
+
"tagline": [
|
|
118
|
+
"CCSDS 352.0-B-2"
|
|
119
|
+
],
|
|
120
|
+
"summary": "Authenticated encryption and decryption of real-time data streams with AES-GCM, key table and IV generation.",
|
|
121
|
+
"public": true,
|
|
122
|
+
"repo": "opencrypto",
|
|
123
|
+
"docs": true,
|
|
124
|
+
"trl": 3,
|
|
125
|
+
"trl_status": "Fully verified by simulation. Not yet tested on hardware, no flight heritage."
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"name": "OpenDMA",
|
|
129
|
+
"group": "system-infrastructure",
|
|
130
|
+
"tagline": [
|
|
131
|
+
"AXI4",
|
|
132
|
+
"AXI4-Stream",
|
|
133
|
+
"Scatter/Gather"
|
|
134
|
+
],
|
|
135
|
+
"summary": "DMA core between AXI4 memory and AXI4-Stream peripherals, register-compatible with the AMD AXI DMA so that its drivers run unchanged.",
|
|
136
|
+
"public": true,
|
|
137
|
+
"repo": "opendma",
|
|
138
|
+
"docs": true,
|
|
139
|
+
"trl": 3,
|
|
140
|
+
"trl_status": "Fully verified by simulation. Not yet tested on hardware, no flight heritage."
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"name": "Open Logic FT",
|
|
144
|
+
"group": "building-blocks",
|
|
145
|
+
"tagline": [
|
|
146
|
+
"ECC",
|
|
147
|
+
"TMR",
|
|
148
|
+
"Safe FSMs"
|
|
149
|
+
],
|
|
150
|
+
"summary": "Fault-tolerant fork of Open Logic: RAMs and FIFOs with SECDED ECC, clock crossings with triplicated synchronisers.",
|
|
151
|
+
"public": true,
|
|
152
|
+
"repo": "open-logic-ft",
|
|
153
|
+
"docs": true,
|
|
154
|
+
"trl": 3,
|
|
155
|
+
"trl_status": "Fully verified by simulation. Not yet tested on hardware, no flight heritage."
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""The projects of the family, as the website shows them (data/family.json, written by tools/family_data.py)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from importlib import resources
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class Group:
|
|
13
|
+
id: str
|
|
14
|
+
title: str
|
|
15
|
+
summary: str
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class Project:
|
|
20
|
+
name: str
|
|
21
|
+
group: str
|
|
22
|
+
tagline: tuple[str, ...]
|
|
23
|
+
summary: str
|
|
24
|
+
public: bool
|
|
25
|
+
repo: str | None = None
|
|
26
|
+
docs: bool = False
|
|
27
|
+
trl: int | None = None
|
|
28
|
+
trl_status: str | None = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class Family:
|
|
33
|
+
site: str
|
|
34
|
+
github: str
|
|
35
|
+
source_commit: str
|
|
36
|
+
groups: tuple[Group, ...]
|
|
37
|
+
projects: tuple[Project, ...]
|
|
38
|
+
|
|
39
|
+
def docs_url(self, project: Project) -> str | None:
|
|
40
|
+
"""Documentation site of a public project, or None."""
|
|
41
|
+
return f"{self.site}/{project.repo}/" if project.public and project.docs else None
|
|
42
|
+
|
|
43
|
+
def source_url(self, project: Project) -> str | None:
|
|
44
|
+
"""Repository of a public project, or None."""
|
|
45
|
+
return f"{self.github}/{project.repo}" if project.public else None
|
|
46
|
+
|
|
47
|
+
def members(self, group: Group) -> list[Project]:
|
|
48
|
+
return [p for p in self.projects if p.group == group.id]
|
|
49
|
+
|
|
50
|
+
def find(self, name: str) -> Project | None:
|
|
51
|
+
"""The project with this name or repository name, ignoring case, spaces and hyphens."""
|
|
52
|
+
key = _key(name)
|
|
53
|
+
for p in self.projects:
|
|
54
|
+
if key in (_key(p.name), _key(p.repo or "")):
|
|
55
|
+
return p
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _key(text: str) -> str:
|
|
60
|
+
return re.sub(r"[^a-z0-9]", "", text.lower())
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def load() -> Family:
|
|
64
|
+
data = json.loads(resources.files("openspacehdl").joinpath("data/family.json").read_text(encoding="utf-8"))
|
|
65
|
+
return Family(
|
|
66
|
+
site=data["site"],
|
|
67
|
+
github=data["github"],
|
|
68
|
+
source_commit=data["source"]["commit"],
|
|
69
|
+
groups=tuple(Group(**g) for g in data["groups"]),
|
|
70
|
+
projects=tuple(Project(**{**p, "tagline": tuple(p["tagline"])}) for p in data["projects"]),
|
|
71
|
+
)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openspacehdl
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Open Space HDL, open-source VHDL IP cores for spacecraft on-board data handling: early release that lists the family
|
|
5
|
+
Author-email: Julian Schneider <julian.schneider@openspacehdl.org>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://openspacehdl.org
|
|
8
|
+
Keywords: vhdl,fpga,ip-core,space,spacewire,spacefibre,ccsds,ecss
|
|
9
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# openspacehdl
|
|
28
|
+
|
|
29
|
+
Python package of [Open Space HDL](https://openspacehdl.org), a family of open-source VHDL IP cores for spacecraft
|
|
30
|
+
on-board data handling.
|
|
31
|
+
|
|
32
|
+
This is an early release. It lists the projects of the family as the website shows them; it does not contain the
|
|
33
|
+
cores or any tools yet.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```shell
|
|
38
|
+
pip install openspacehdl
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The package needs Python 3.10 or later and has no dependencies. On a machine without network access, copy the wheel
|
|
42
|
+
and install it from the file:
|
|
43
|
+
|
|
44
|
+
```shell
|
|
45
|
+
pip install openspacehdl-<version>-py3-none-any.whl
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Use
|
|
49
|
+
|
|
50
|
+
```shell
|
|
51
|
+
openspacehdl --version # version of the package
|
|
52
|
+
openspacehdl info # every project of the family: standard, summary, TRL, documentation and source
|
|
53
|
+
openspacehdl info OpenFibre # one project, by name or repository name
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`python -m openspacehdl` works the same way.
|
|
57
|
+
|
|
58
|
+
The list comes from the project cards of [openspacehdl.org](https://openspacehdl.org) at the time of the release.
|
|
59
|
+
A project that is not yet public is listed as "Coming soon", without links.
|
|
60
|
+
|
|
61
|
+
## Development
|
|
62
|
+
|
|
63
|
+
```shell
|
|
64
|
+
pip install -r requirements-dev.txt
|
|
65
|
+
pip install -e .
|
|
66
|
+
pytest
|
|
67
|
+
python tools/family_data.py --site <clone of open-space-hdl.github.io> --check
|
|
68
|
+
python -m build
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`tools/family_data.py` writes `src/openspacehdl/data/family.json` from `_data/projects.yml` and
|
|
72
|
+
`_data/project_groups.yml` of the website repository; `--check` only compares.
|
|
73
|
+
|
|
74
|
+
## Licence
|
|
75
|
+
|
|
76
|
+
The package is released under the [MIT License](LICENSE). The IP cores of the family have their own licence (PSI HDL
|
|
77
|
+
Library License), stated in each repository.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
CHANGELOG.md
|
|
2
|
+
LICENSE
|
|
3
|
+
MANIFEST.in
|
|
4
|
+
README.md
|
|
5
|
+
pyproject.toml
|
|
6
|
+
requirements-dev.txt
|
|
7
|
+
src/openspacehdl/__init__.py
|
|
8
|
+
src/openspacehdl/__main__.py
|
|
9
|
+
src/openspacehdl/cli.py
|
|
10
|
+
src/openspacehdl/family.py
|
|
11
|
+
src/openspacehdl.egg-info/PKG-INFO
|
|
12
|
+
src/openspacehdl.egg-info/SOURCES.txt
|
|
13
|
+
src/openspacehdl.egg-info/dependency_links.txt
|
|
14
|
+
src/openspacehdl.egg-info/entry_points.txt
|
|
15
|
+
src/openspacehdl.egg-info/top_level.txt
|
|
16
|
+
src/openspacehdl/data/family.json
|
|
17
|
+
tests/test_cli.py
|
|
18
|
+
tools/family_data.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
openspacehdl
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Tests of the command line: --version and info."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
from importlib import resources
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from openspacehdl import __version__
|
|
12
|
+
from openspacehdl.cli import main
|
|
13
|
+
from openspacehdl.family import Family, Group, Project, load
|
|
14
|
+
|
|
15
|
+
URL = re.compile(r"https?://")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def data() -> dict:
|
|
19
|
+
return json.loads(resources.files("openspacehdl").joinpath("data/family.json").read_text(encoding="utf-8"))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def blocks(text: str) -> dict[str, str]:
|
|
23
|
+
"""The text of each project in the output of info, keyed by its name."""
|
|
24
|
+
names = [p["name"] for p in data()["projects"]]
|
|
25
|
+
found = {}
|
|
26
|
+
for block in text.split("\n\n"):
|
|
27
|
+
first = block.splitlines()[0].strip()
|
|
28
|
+
for n in names:
|
|
29
|
+
if first == n or first.startswith(n + " "):
|
|
30
|
+
found[n] = block
|
|
31
|
+
return found
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_version(capsys):
|
|
35
|
+
with pytest.raises(SystemExit) as e:
|
|
36
|
+
main(["--version"])
|
|
37
|
+
assert e.value.code == 0
|
|
38
|
+
assert capsys.readouterr().out.strip() == f"openspacehdl {__version__}"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_version_is_semantic():
|
|
42
|
+
assert re.fullmatch(r"\d+\.\d+\.\d+", __version__)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_python_m():
|
|
46
|
+
r = subprocess.run([sys.executable, "-m", "openspacehdl", "--version"], capture_output=True, text=True)
|
|
47
|
+
assert r.returncode == 0
|
|
48
|
+
assert r.stdout.strip() == f"openspacehdl {__version__}"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_no_command_prints_help(capsys):
|
|
52
|
+
assert main([]) == 0
|
|
53
|
+
assert "info" in capsys.readouterr().out
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_info_lists_every_card_in_group_order(capsys):
|
|
57
|
+
assert main(["info"]) == 0
|
|
58
|
+
out = capsys.readouterr().out
|
|
59
|
+
d = data()
|
|
60
|
+
assert set(blocks(out)) == {p["name"] for p in d["projects"]}
|
|
61
|
+
# Groups in the order of project_groups.yml, projects in the order of projects.yml within a group.
|
|
62
|
+
expected = [p["name"] for g in d["groups"] for p in d["projects"] if p["group"] == g["id"]]
|
|
63
|
+
assert [n for n in sorted(expected, key=out.index)] == expected
|
|
64
|
+
for g in d["groups"]:
|
|
65
|
+
if any(p["group"] == g["id"] for p in d["projects"]):
|
|
66
|
+
assert g["title"] in out
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_public_projects_show_trl_and_links(capsys):
|
|
70
|
+
main(["info"])
|
|
71
|
+
out = blocks(capsys.readouterr().out)
|
|
72
|
+
for p in data()["projects"]:
|
|
73
|
+
if not p["public"]:
|
|
74
|
+
continue
|
|
75
|
+
b = out[p["name"]]
|
|
76
|
+
assert f"Source: https://github.com/open-space-hdl/{p['repo']}" in b
|
|
77
|
+
assert ("Documentation: https://openspacehdl.org/" + p["repo"] + "/" in b) == p["docs"]
|
|
78
|
+
if p.get("trl"):
|
|
79
|
+
assert f"TRL {p['trl']}" in b.splitlines()[0]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_coming_soon_projects_have_no_links_and_no_trl(capsys):
|
|
83
|
+
main(["info"])
|
|
84
|
+
out = blocks(capsys.readouterr().out)
|
|
85
|
+
hidden = [p for p in data()["projects"] if not p["public"]]
|
|
86
|
+
assert hidden, "the data has no Coming soon card to test"
|
|
87
|
+
for p in hidden:
|
|
88
|
+
b = out[p["name"]]
|
|
89
|
+
assert "Coming soon" in b.splitlines()[0]
|
|
90
|
+
assert not URL.search(b)
|
|
91
|
+
assert "TRL" not in b
|
|
92
|
+
assert "Source:" not in b and "Documentation:" not in b and "Status:" not in b
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_coming_soon_data_keeps_only_card_fields():
|
|
96
|
+
for p in data()["projects"]:
|
|
97
|
+
if not p["public"]:
|
|
98
|
+
assert set(p) == {"name", "group", "tagline", "summary", "public"}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@pytest.mark.parametrize("name", ["OpenFibre", "openfibre", "OPENFIBRE", "Open Logic FT", "open-logic-ft"])
|
|
102
|
+
def test_info_one_project(capsys, name):
|
|
103
|
+
assert main(["info", name]) == 0
|
|
104
|
+
out = capsys.readouterr().out
|
|
105
|
+
expected = load().find(name)
|
|
106
|
+
assert expected is not None
|
|
107
|
+
assert out.splitlines()[0].strip().startswith(expected.name)
|
|
108
|
+
assert len(blocks(out)) == 1
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_info_unknown_project(capsys):
|
|
112
|
+
assert main(["info", "NoSuchCore"]) == 2
|
|
113
|
+
err = capsys.readouterr().err
|
|
114
|
+
assert "NoSuchCore" in err
|
|
115
|
+
assert "OpenFibre" in err
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_data_names_the_website_commit():
|
|
119
|
+
d = data()
|
|
120
|
+
assert d["source"]["repository"] == "open-space-hdl/open-space-hdl.github.io"
|
|
121
|
+
assert re.fullmatch(r"[0-9a-f]{40}", d["source"]["commit"])
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_find_by_repository_name():
|
|
125
|
+
g = Group("g", "G", "")
|
|
126
|
+
p = Project(name="Example Core", group="g", tagline=(), summary="", public=True, repo="excore")
|
|
127
|
+
f = Family(
|
|
128
|
+
site="https://example.org", github="https://example.org/gh", source_commit="0" * 40, groups=(g,), projects=(p,)
|
|
129
|
+
)
|
|
130
|
+
assert f.find("excore") is p
|
|
131
|
+
assert f.find("example-core") is p
|
|
132
|
+
assert f.find("other") is None
|
|
133
|
+
assert f.docs_url(p) is None
|
|
134
|
+
assert f.source_url(p) == "https://example.org/gh/excore"
|
|
135
|
+
hidden = Project(name="Hidden", group="g", tagline=(), summary="", public=False, repo="hidden", docs=True)
|
|
136
|
+
assert f.source_url(hidden) is None
|
|
137
|
+
assert f.docs_url(hidden) is None
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Writes src/openspacehdl/data/family.json from the project cards of the website.
|
|
2
|
+
|
|
3
|
+
The cards come from _data/projects.yml and _data/project_groups.yml of the website repository
|
|
4
|
+
(open-space-hdl/open-space-hdl.github.io) at one commit. The file keeps only what a card shows: a project that is
|
|
5
|
+
not public keeps its name, group, tagline and summary, without repository, links or TRL, as its "Coming soon" card.
|
|
6
|
+
|
|
7
|
+
python tools/family_data.py --site <clone of the website> [--commit <rev>] [--check]
|
|
8
|
+
|
|
9
|
+
--commit defaults to origin/main. --check compares the file with what the commit gives and writes nothing; the exit
|
|
10
|
+
status is 1 when they differ.
|
|
11
|
+
|
|
12
|
+
Needs PyYAML (development only; the package itself has no dependencies).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import json
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
import yaml
|
|
24
|
+
|
|
25
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
26
|
+
OUT = ROOT / "src" / "openspacehdl" / "data" / "family.json"
|
|
27
|
+
SITE_REPO = "open-space-hdl/open-space-hdl.github.io"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def git(site: Path, *args: str) -> str:
|
|
31
|
+
return subprocess.run(
|
|
32
|
+
["git", "-C", str(site), *args], check=True, capture_output=True, text=True, encoding="utf-8"
|
|
33
|
+
).stdout
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def card(project: dict) -> dict:
|
|
37
|
+
"""The fields a card of the website shows (_includes/project-card.html)."""
|
|
38
|
+
out = {
|
|
39
|
+
"name": project["name"],
|
|
40
|
+
"group": project["group"],
|
|
41
|
+
"tagline": list(project.get("tagline") or []),
|
|
42
|
+
"summary": project["summary"],
|
|
43
|
+
"public": bool(project.get("public")),
|
|
44
|
+
}
|
|
45
|
+
if out["public"]:
|
|
46
|
+
out["repo"] = project["repo"]
|
|
47
|
+
out["docs"] = bool(project.get("docs"))
|
|
48
|
+
if project.get("trl"):
|
|
49
|
+
out["trl"] = int(project["trl"])
|
|
50
|
+
out["trl_status"] = project.get("trl_status", "")
|
|
51
|
+
return out
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def build(site: Path, commit: str) -> dict:
|
|
55
|
+
sha = git(site, "rev-parse", "--verify", f"{commit}^{{commit}}").strip()
|
|
56
|
+
config = yaml.safe_load(git(site, "show", f"{sha}:_config.yml"))
|
|
57
|
+
groups = yaml.safe_load(git(site, "show", f"{sha}:_data/project_groups.yml"))
|
|
58
|
+
projects = yaml.safe_load(git(site, "show", f"{sha}:_data/projects.yml"))
|
|
59
|
+
ids = [g["id"] for g in groups]
|
|
60
|
+
return {
|
|
61
|
+
"source": {"repository": SITE_REPO, "commit": sha},
|
|
62
|
+
"site": config["url"],
|
|
63
|
+
"github": config["github_org"],
|
|
64
|
+
"groups": [{"id": g["id"], "title": g["title"], "summary": g["summary"]} for g in groups],
|
|
65
|
+
# The start page shows a project only under a known group, in the order of projects.yml.
|
|
66
|
+
"projects": [card(p) for p in projects if p.get("group") in ids],
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def main(argv: list[str] | None = None) -> int:
|
|
71
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
72
|
+
parser.add_argument("--site", type=Path, required=True, help="clone of the website repository")
|
|
73
|
+
parser.add_argument("--commit", default="origin/main", help="revision of the website (default origin/main)")
|
|
74
|
+
parser.add_argument("--check", action="store_true", help="compare only, write nothing")
|
|
75
|
+
args = parser.parse_args(argv)
|
|
76
|
+
|
|
77
|
+
text = json.dumps(build(args.site, args.commit), indent=2, ensure_ascii=False) + "\n"
|
|
78
|
+
if args.check:
|
|
79
|
+
current = OUT.read_text(encoding="utf-8") if OUT.exists() else ""
|
|
80
|
+
if current != text:
|
|
81
|
+
print(f"{OUT.relative_to(ROOT)} differs from {args.commit} of the website", file=sys.stderr)
|
|
82
|
+
return 1
|
|
83
|
+
print(f"{OUT.relative_to(ROOT)} is current")
|
|
84
|
+
return 0
|
|
85
|
+
OUT.write_text(text, encoding="utf-8", newline="\n")
|
|
86
|
+
print(f"wrote {OUT.relative_to(ROOT)}")
|
|
87
|
+
return 0
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
sys.exit(main())
|