jig-dsl 0.1.0a1__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.
Files changed (160) hide show
  1. jig_dsl-0.1.0a1/LICENSE +21 -0
  2. jig_dsl-0.1.0a1/PKG-INFO +181 -0
  3. jig_dsl-0.1.0a1/README.md +120 -0
  4. jig_dsl-0.1.0a1/pyproject.toml +192 -0
  5. jig_dsl-0.1.0a1/setup.cfg +4 -0
  6. jig_dsl-0.1.0a1/src/jig/__init__.py +13 -0
  7. jig_dsl-0.1.0a1/src/jig/__main__.py +7 -0
  8. jig_dsl-0.1.0a1/src/jig/artifacts.py +77 -0
  9. jig_dsl-0.1.0a1/src/jig/cli.py +439 -0
  10. jig_dsl-0.1.0a1/src/jig/contracts.py +220 -0
  11. jig_dsl-0.1.0a1/src/jig/errors.py +61 -0
  12. jig_dsl-0.1.0a1/src/jig/ext/__init__.py +2 -0
  13. jig_dsl-0.1.0a1/src/jig/ext/airhdl/__init__.py +189 -0
  14. jig_dsl-0.1.0a1/src/jig/ext/cosim/__init__.py +390 -0
  15. jig_dsl-0.1.0a1/src/jig/ext/cosim/templates/jig_axil_bram.sv +114 -0
  16. jig_dsl-0.1.0a1/src/jig/ext/cosim/templates/jig_axil_byte_lanes.sv +100 -0
  17. jig_dsl-0.1.0a1/src/jig/ext/cosim/templates/jig_axil_decoder.sv +203 -0
  18. jig_dsl-0.1.0a1/src/jig/ext/cosim/templates/jig_axil_regs.sv +99 -0
  19. jig_dsl-0.1.0a1/src/jig/ext/cosim/templates/jig_dp_bram.sv +49 -0
  20. jig_dsl-0.1.0a1/src/jig/ext/cosim/templates/sim_main.cpp.in +75 -0
  21. jig_dsl-0.1.0a1/src/jig/ext/csp/__init__.py +2 -0
  22. jig_dsl-0.1.0a1/src/jig/ext/csp/cmp.py +113 -0
  23. jig_dsl-0.1.0a1/src/jig/ext/csp/dsl.py +620 -0
  24. jig_dsl-0.1.0a1/src/jig/ext/csp/extension.py +31 -0
  25. jig_dsl-0.1.0a1/src/jig/ext/csp/node.py +618 -0
  26. jig_dsl-0.1.0a1/src/jig/ext/csp/observer.py +90 -0
  27. jig_dsl-0.1.0a1/src/jig/ext/csp/protocol.py +328 -0
  28. jig_dsl-0.1.0a1/src/jig/ext/csp/rdp.py +390 -0
  29. jig_dsl-0.1.0a1/src/jig/ext/csp/settings.py +166 -0
  30. jig_dsl-0.1.0a1/src/jig/ext/zynq7000/__init__.py +2 -0
  31. jig_dsl-0.1.0a1/src/jig/ext/zynq7000/clocks.py +171 -0
  32. jig_dsl-0.1.0a1/src/jig/ext/zynq7000/driver.py +403 -0
  33. jig_dsl-0.1.0a1/src/jig/ext/zynq7000/ps7init.py +133 -0
  34. jig_dsl-0.1.0a1/src/jig/lang/__init__.py +114 -0
  35. jig_dsl-0.1.0a1/src/jig/lang/grammar/jig.tx +322 -0
  36. jig_dsl-0.1.0a1/src/jig/lang/interpreter.py +1512 -0
  37. jig_dsl-0.1.0a1/src/jig/lang/library.py +747 -0
  38. jig_dsl-0.1.0a1/src/jig/lang/parser.py +363 -0
  39. jig_dsl-0.1.0a1/src/jig/lang/pygments_lexer.py +201 -0
  40. jig_dsl-0.1.0a1/src/jig/lang/stdlib/console.jig +35 -0
  41. jig_dsl-0.1.0a1/src/jig/lang/stdlib/records.jig +9 -0
  42. jig_dsl-0.1.0a1/src/jig/lang/stdlib/text.jig +24 -0
  43. jig_dsl-0.1.0a1/src/jig/lang/values.py +213 -0
  44. jig_dsl-0.1.0a1/src/jig/models/Arm/CortexA9GlobalTimer.cs +290 -0
  45. jig_dsl-0.1.0a1/src/jig/models/Arm/CortexA9PrivateTimerWatchdog.cs +283 -0
  46. jig_dsl-0.1.0a1/src/jig/models/Arm/GicDistributorHooks.cs +99 -0
  47. jig_dsl-0.1.0a1/src/jig/models/Arm/PL310.cs +134 -0
  48. jig_dsl-0.1.0a1/src/jig/models/Cadence/CadenceSPI.cs +384 -0
  49. jig_dsl-0.1.0a1/src/jig/models/Cadence/CadenceUART.cs +658 -0
  50. jig_dsl-0.1.0a1/src/jig/models/Cadence/CadenceWDT.cs +184 -0
  51. jig_dsl-0.1.0a1/src/jig/models/Contracts/ContractQueries.cs +63 -0
  52. jig_dsl-0.1.0a1/src/jig/models/Contracts/IAnalogInputs.cs +31 -0
  53. jig_dsl-0.1.0a1/src/jig/models/Contracts/IPowerCycled.cs +24 -0
  54. jig_dsl-0.1.0a1/src/jig/models/Contracts/IRealTimeClock.cs +27 -0
  55. jig_dsl-0.1.0a1/src/jig/models/Contracts/ISpiNorFlash.cs +27 -0
  56. jig_dsl-0.1.0a1/src/jig/models/Contracts/IStorage.cs +28 -0
  57. jig_dsl-0.1.0a1/src/jig/models/Harness/AirhdlRegisterBlock.cs +232 -0
  58. jig_dsl-0.1.0a1/src/jig/models/Harness/Bridge.cs +758 -0
  59. jig_dsl-0.1.0a1/src/jig/models/Harness/CanBus.cs +487 -0
  60. jig_dsl-0.1.0a1/src/jig/models/Harness/RegisterProgram.cs +123 -0
  61. jig_dsl-0.1.0a1/src/jig/models/Harness/Stepping.cs +68 -0
  62. jig_dsl-0.1.0a1/src/jig/models/Harness/UartPort.cs +221 -0
  63. jig_dsl-0.1.0a1/src/jig/models/ST/STM32U5_ADC4.cs +116 -0
  64. jig_dsl-0.1.0a1/src/jig/models/ST/STM32U5_FDCAN.cs +1314 -0
  65. jig_dsl-0.1.0a1/src/jig/models/ST/STM32U5_LPTIM.cs +537 -0
  66. jig_dsl-0.1.0a1/src/jig/models/ST/STM32U5_RCC.cs +419 -0
  67. jig_dsl-0.1.0a1/src/jig/models/ST/STM32U5_RTC.cs +903 -0
  68. jig_dsl-0.1.0a1/src/jig/models/ST/STM32U5_TAMP.cs +176 -0
  69. jig_dsl-0.1.0a1/src/jig/models/Xilinx/XilinxAxiCdma.cs +232 -0
  70. jig_dsl-0.1.0a1/src/jig/models/Xilinx/XilinxAxiGpio.cs +174 -0
  71. jig_dsl-0.1.0a1/src/jig/models/Xilinx/XilinxAxiQuadSpi.cs +339 -0
  72. jig_dsl-0.1.0a1/src/jig/models/Xilinx/Zynq7000_BootROM.cs +435 -0
  73. jig_dsl-0.1.0a1/src/jig/models/Xilinx/Zynq7000_CAN.cs +750 -0
  74. jig_dsl-0.1.0a1/src/jig/models/Xilinx/Zynq7000_DDRC.cs +65 -0
  75. jig_dsl-0.1.0a1/src/jig/models/Xilinx/Zynq7000_DevC.cs +431 -0
  76. jig_dsl-0.1.0a1/src/jig/models/Xilinx/Zynq7000_EFuse.cs +37 -0
  77. jig_dsl-0.1.0a1/src/jig/models/Xilinx/Zynq7000_GPIO.cs +326 -0
  78. jig_dsl-0.1.0a1/src/jig/models/Xilinx/Zynq7000_PlDomain.cs +113 -0
  79. jig_dsl-0.1.0a1/src/jig/models/Xilinx/Zynq7000_QSPI.cs +545 -0
  80. jig_dsl-0.1.0a1/src/jig/models/Xilinx/Zynq7000_SLCR.cs +368 -0
  81. jig_dsl-0.1.0a1/src/jig/platforms/st/stm32u5.repl +254 -0
  82. jig_dsl-0.1.0a1/src/jig/platforms/st/stm32u575.repl +49 -0
  83. jig_dsl-0.1.0a1/src/jig/platforms/st/stm32u595.repl +52 -0
  84. jig_dsl-0.1.0a1/src/jig/platforms/xilinx/zynq7000.repl +160 -0
  85. jig_dsl-0.1.0a1/src/jig/plugin.py +359 -0
  86. jig_dsl-0.1.0a1/src/jig/project.py +357 -0
  87. jig_dsl-0.1.0a1/src/jig/py.typed +0 -0
  88. jig_dsl-0.1.0a1/src/jig/renode/__init__.py +17 -0
  89. jig_dsl-0.1.0a1/src/jig/renode/__main__.py +7 -0
  90. jig_dsl-0.1.0a1/src/jig/renode/assets.py +117 -0
  91. jig_dsl-0.1.0a1/src/jig/renode/bridge.py +217 -0
  92. jig_dsl-0.1.0a1/src/jig/renode/commands.py +547 -0
  93. jig_dsl-0.1.0a1/src/jig/renode/discover.py +615 -0
  94. jig_dsl-0.1.0a1/src/jig/renode/doctor.py +309 -0
  95. jig_dsl-0.1.0a1/src/jig/renode/install.py +492 -0
  96. jig_dsl-0.1.0a1/src/jig/renode/models.py +381 -0
  97. jig_dsl-0.1.0a1/src/jig/renode/platforms.py +42 -0
  98. jig_dsl-0.1.0a1/src/jig/renode/process.py +349 -0
  99. jig_dsl-0.1.0a1/src/jig/renode/session.py +398 -0
  100. jig_dsl-0.1.0a1/src/jig/renode/toolchain.py +137 -0
  101. jig_dsl-0.1.0a1/src/jig/repl.py +59 -0
  102. jig_dsl-0.1.0a1/src/jig/report/__init__.py +1 -0
  103. jig_dsl-0.1.0a1/src/jig/report/html.py +209 -0
  104. jig_dsl-0.1.0a1/src/jig/report/report.py +351 -0
  105. jig_dsl-0.1.0a1/src/jig/report/summary.py +122 -0
  106. jig_dsl-0.1.0a1/src/jig/runtime/__init__.py +1 -0
  107. jig_dsl-0.1.0a1/src/jig/runtime/environment.py +274 -0
  108. jig_dsl-0.1.0a1/src/jig/runtime/recorder.py +111 -0
  109. jig_dsl-0.1.0a1/src/jig/runtime/scheduler.py +375 -0
  110. jig_dsl-0.1.0a1/src/jig/system/__init__.py +1 -0
  111. jig_dsl-0.1.0a1/src/jig/system/boards.py +750 -0
  112. jig_dsl-0.1.0a1/src/jig/system/registry.py +411 -0
  113. jig_dsl-0.1.0a1/src/jig/system/renode_shim.py +45 -0
  114. jig_dsl-0.1.0a1/src/jig/system/socs/__init__.py +189 -0
  115. jig_dsl-0.1.0a1/src/jig/system/socs/stm32.py +331 -0
  116. jig_dsl-0.1.0a1/src/jig/system/system.py +805 -0
  117. jig_dsl-0.1.0a1/src/jig/testing/__init__.py +493 -0
  118. jig_dsl-0.1.0a1/src/jig/transports/__init__.py +1 -0
  119. jig_dsl-0.1.0a1/src/jig/transports/can.py +125 -0
  120. jig_dsl-0.1.0a1/src/jig/transports/uart.py +82 -0
  121. jig_dsl-0.1.0a1/src/jig_dsl.egg-info/PKG-INFO +181 -0
  122. jig_dsl-0.1.0a1/src/jig_dsl.egg-info/SOURCES.txt +158 -0
  123. jig_dsl-0.1.0a1/src/jig_dsl.egg-info/dependency_links.txt +1 -0
  124. jig_dsl-0.1.0a1/src/jig_dsl.egg-info/entry_points.txt +9 -0
  125. jig_dsl-0.1.0a1/src/jig_dsl.egg-info/requires.txt +35 -0
  126. jig_dsl-0.1.0a1/src/jig_dsl.egg-info/top_level.txt +1 -0
  127. jig_dsl-0.1.0a1/tests/test_boards.py +301 -0
  128. jig_dsl-0.1.0a1/tests/test_bridge.py +148 -0
  129. jig_dsl-0.1.0a1/tests/test_cli.py +119 -0
  130. jig_dsl-0.1.0a1/tests/test_cosim_build.py +146 -0
  131. jig_dsl-0.1.0a1/tests/test_csp.py +117 -0
  132. jig_dsl-0.1.0a1/tests/test_csp_ext.py +299 -0
  133. jig_dsl-0.1.0a1/tests/test_discover.py +238 -0
  134. jig_dsl-0.1.0a1/tests/test_dsl.py +405 -0
  135. jig_dsl-0.1.0a1/tests/test_environment.py +99 -0
  136. jig_dsl-0.1.0a1/tests/test_framework.py +243 -0
  137. jig_dsl-0.1.0a1/tests/test_hdl.py +131 -0
  138. jig_dsl-0.1.0a1/tests/test_install.py +299 -0
  139. jig_dsl-0.1.0a1/tests/test_language.py +239 -0
  140. jig_dsl-0.1.0a1/tests/test_model_sets.py +156 -0
  141. jig_dsl-0.1.0a1/tests/test_multiboard.py +141 -0
  142. jig_dsl-0.1.0a1/tests/test_nodes.py +104 -0
  143. jig_dsl-0.1.0a1/tests/test_observer.py +95 -0
  144. jig_dsl-0.1.0a1/tests/test_plugin.py +319 -0
  145. jig_dsl-0.1.0a1/tests/test_project.py +254 -0
  146. jig_dsl-0.1.0a1/tests/test_public_hygiene.py +248 -0
  147. jig_dsl-0.1.0a1/tests/test_pygments_lexer.py +161 -0
  148. jig_dsl-0.1.0a1/tests/test_rdp.py +149 -0
  149. jig_dsl-0.1.0a1/tests/test_renode_bridge.py +382 -0
  150. jig_dsl-0.1.0a1/tests/test_renode_commands.py +212 -0
  151. jig_dsl-0.1.0a1/tests/test_renode_reports.py +67 -0
  152. jig_dsl-0.1.0a1/tests/test_renode_smoke.py +46 -0
  153. jig_dsl-0.1.0a1/tests/test_renode_system.py +55 -0
  154. jig_dsl-0.1.0a1/tests/test_reports.py +399 -0
  155. jig_dsl-0.1.0a1/tests/test_runtime.py +160 -0
  156. jig_dsl-0.1.0a1/tests/test_simulation.py +727 -0
  157. jig_dsl-0.1.0a1/tests/test_systems.py +347 -0
  158. jig_dsl-0.1.0a1/tests/test_toolchain.py +37 -0
  159. jig_dsl-0.1.0a1/tests/test_zedboard_example.py +76 -0
  160. jig_dsl-0.1.0a1/tests/test_zynq7000.py +125 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Graham Power
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,181 @@
1
+ Metadata-Version: 2.4
2
+ Name: jig-dsl
3
+ Version: 0.1.0a1
4
+ Summary: A test language and harness for firmware running on the Renode emulator
5
+ Author: Graham Power
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/GNPower/Jig
8
+ Project-URL: Documentation, https://jig-dsl.readthedocs.io
9
+ Project-URL: Repository, https://github.com/GNPower/Jig
10
+ Project-URL: Issues, https://github.com/GNPower/Jig/issues
11
+ Project-URL: Changelog, https://github.com/GNPower/Jig/blob/main/CHANGELOG.md
12
+ Keywords: renode,emulation,firmware,testing,embedded,zynq,stm32,csp
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Operating System :: Microsoft :: Windows
18
+ Classifier: Operating System :: POSIX :: Linux
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Programming Language :: Python :: 3.14
25
+ Classifier: Topic :: Software Development :: Embedded Systems
26
+ Classifier: Topic :: Software Development :: Testing
27
+ Classifier: Topic :: System :: Emulators
28
+ Classifier: Typing :: Typed
29
+ Requires-Python: >=3.11
30
+ Description-Content-Type: text/markdown
31
+ License-File: LICENSE
32
+ Requires-Dist: textX<5,>=4
33
+ Requires-Dist: PyYAML<7,>=6
34
+ Requires-Dist: platformdirs<5,>=4
35
+ Requires-Dist: packaging>=23
36
+ Provides-Extra: test
37
+ Requires-Dist: pytest<10,>=8; extra == "test"
38
+ Requires-Dist: pytest-cov<8,>=6; extra == "test"
39
+ Provides-Extra: docs
40
+ Requires-Dist: sphinx<10,>=8.2; extra == "docs"
41
+ Requires-Dist: sphinx-rtd-theme<4,>=3.0; extra == "docs"
42
+ Requires-Dist: myst-parser<6,>=4.0; extra == "docs"
43
+ Provides-Extra: lint
44
+ Requires-Dist: ruff==0.17.0; extra == "lint"
45
+ Requires-Dist: pymarkdownlnt==0.9.40; extra == "lint"
46
+ Requires-Dist: zizmor==1.30.1; extra == "lint"
47
+ Provides-Extra: typecheck
48
+ Requires-Dist: jig-dsl[test]; extra == "typecheck"
49
+ Requires-Dist: mypy==2.4.0; extra == "typecheck"
50
+ Requires-Dist: types-PyYAML==6.0.12.20260906; extra == "typecheck"
51
+ Requires-Dist: nox==2026.8.17; extra == "typecheck"
52
+ Provides-Extra: release
53
+ Requires-Dist: build==1.6.1; extra == "release"
54
+ Requires-Dist: twine==7.0.0; extra == "release"
55
+ Requires-Dist: bump-my-version==1.5.1; extra == "release"
56
+ Provides-Extra: audit
57
+ Requires-Dist: pip-audit==2.10.1; extra == "audit"
58
+ Provides-Extra: dev
59
+ Requires-Dist: jig-dsl[lint,release,test,typecheck]; extra == "dev"
60
+ Dynamic: license-file
61
+
62
+ # Jig
63
+
64
+ [![PyPI version](https://img.shields.io/pypi/v/jig-dsl.svg)](https://pypi.org/project/jig-dsl/)
65
+ [![Python versions](https://img.shields.io/pypi/pyversions/jig-dsl.svg)](https://pypi.org/project/jig-dsl/)
66
+ [![Tests](https://github.com/GNPower/Jig/actions/workflows/ci.yml/badge.svg)](https://github.com/GNPower/Jig/actions/workflows/ci.yml)
67
+ [![Coverage](https://codecov.io/gh/GNPower/Jig/branch/main/graph/badge.svg)](https://codecov.io/gh/GNPower/Jig)
68
+ [![Documentation](https://readthedocs.org/projects/jig-dsl/badge/?version=latest)](https://jig-dsl.readthedocs.io/en/latest/)
69
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/GNPower/Jig/blob/main/LICENSE)
70
+
71
+ **A test language and harness for firmware running on the Renode
72
+ emulator.**
73
+
74
+ Full documentation: <https://jig-dsl.readthedocs.io>
75
+
76
+ ## Overview
77
+
78
+ Jig runs test plans, written as `.jig` files, against a users firmware
79
+ on [Renode](https://renode.io). A plan declares the boards it needs, the
80
+ buse interfaces between them and the communication protocols the harness uses, then
81
+ drives the system over those interfaces in simulated time:
82
+
83
+ ```jig
84
+ import "zed.jig"
85
+
86
+ system "two ZedBoards and a host computer" {
87
+ board a: "zed-a"
88
+ board b: "zed-b"
89
+ bus can0: can(bitrate: 1 Mbit/s, members: [a.can, b.can])
90
+ node host: csp(address: 10, link: a.kiss)
91
+ }
92
+
93
+ setup {
94
+ parallel {
95
+ task zed_a { booted(a, 1) }
96
+ task zed_b { booted(b, 2) }
97
+ }
98
+ }
99
+
100
+ test "the host computer reaches zed-b through zed-a" {
101
+ check host.ping(b) < 50 ms
102
+ check can0.traffic().routes["10->2:1"] >= 1
103
+ }
104
+ ```
105
+
106
+ This is the start of `examples/zedboard/plans/multiboard.jig`. The setup
107
+ waits, on both boards at once, for the boot banner and the first prompt
108
+ (`booted` comes from the example's plan library, `lib/zed.jig`). The test
109
+ then pings zed-b from the host computer through zed-a using the lightweight
110
+ network protocol [LibCSP](https://github.com/libcsp/libcsp).
111
+
112
+ Every check's result is printed in a report with the simulated time it ran
113
+ at. Each run gets a fingerprint, a SHA-256 digest of everything it decided
114
+ and measured in simulated time, so `jig run --repeat 3` proves a plan gives
115
+ the same result three times, on any host.
116
+
117
+ Boards are data: a `board.yaml` names the platform (in this case the
118
+ [ZedBoard][zedboard], running a Zynq-7020 SoC), the Renode platform
119
+ description, the firmware and the interfaces plans use. Users must bring
120
+ their own C# models and Python plugins for any peripherals not directly
121
+ supported by Jig.
122
+
123
+ [zedboard]: https://www.avnet.com/americas/products/avnet-boards/avnet-board-families/zedboard/
124
+
125
+ ## Requirements
126
+
127
+ - Python 3.11 or later.
128
+ - Renode 1.17.0. Jig finds an installed copy, or `jig renode install`
129
+ downloads the release from Renode's GitHub page after you agree, and
130
+ checks its size and SHA-256 digest.
131
+ - For the ZedBoard example's firmware, `arm-none-eabi-gcc`.
132
+ - For RTL co-simulation, Linux with Verilator.
133
+
134
+ ## Installation
135
+
136
+ ```bash
137
+ python -m pip install jig-dsl
138
+ jig renode install
139
+ jig doctor
140
+ ```
141
+
142
+ ## Quick Start
143
+
144
+ The repository's ZedBoard example runs two Zynq-7020 boards on one CAN
145
+ bus, with a host computer on a serial link to the first:
146
+
147
+ ```bash
148
+ git clone https://github.com/GNPower/Jig.git
149
+ cd Jig
150
+ python examples/zedboard/firmware/build.py
151
+ jig run examples/zedboard/plans/multiboard.jig --html out/report.html
152
+ ```
153
+
154
+ `build.py` needs `arm-none-eabi-gcc` on the `PATH`. The tutorials in the
155
+ documentation build plans like this one step by step.
156
+
157
+ ## Features
158
+
159
+ - **A plan language for firmware tests**: consoles, network requests, checks,
160
+ waits, loops, parallel tasks, timelines and recorded values, all in
161
+ simulated time.
162
+ - **Several boards in one emulation**, on bus interfaces the harness carries
163
+ and watches, with repeatable timing between them.
164
+ - **Zynq-7000 and STM32 drivers**: JTAG-style and Quad-SPI boots through
165
+ Jig's Zynq-7000 models, and ELF boots on Renode's STM32 platforms.
166
+ - **CSP in libcsp 1.6's wire format**: KISS and CAN links, RDP
167
+ connections, and a bus observer that reassembles every packet.
168
+ - **Board plugins** through `jig.plugin`, a versioned API for plan
169
+ functions, board members, decoders and device drivers.
170
+ - **C# models compiled by Renode at start-up**, with interfaces that board
171
+ chip models implement so plans can drive them.
172
+ - **Reports for people and CI**: an HTML page with plots, JUnit XML, a JSON
173
+ summary, run directories with every console transcript and packet, and
174
+ exit codes that differentiate failed tests from broken setups.
175
+ - **RTL co-simulation** of programmable-logic blocks with Verilator.
176
+ - **A localhost bridge to Renode** with a token per run, in place of
177
+ Renode's network-facing Robot Framework server.
178
+
179
+ ## License
180
+
181
+ MIT: see [LICENSE](https://github.com/GNPower/Jig/blob/main/LICENSE).
@@ -0,0 +1,120 @@
1
+ # Jig
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/jig-dsl.svg)](https://pypi.org/project/jig-dsl/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/jig-dsl.svg)](https://pypi.org/project/jig-dsl/)
5
+ [![Tests](https://github.com/GNPower/Jig/actions/workflows/ci.yml/badge.svg)](https://github.com/GNPower/Jig/actions/workflows/ci.yml)
6
+ [![Coverage](https://codecov.io/gh/GNPower/Jig/branch/main/graph/badge.svg)](https://codecov.io/gh/GNPower/Jig)
7
+ [![Documentation](https://readthedocs.org/projects/jig-dsl/badge/?version=latest)](https://jig-dsl.readthedocs.io/en/latest/)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/GNPower/Jig/blob/main/LICENSE)
9
+
10
+ **A test language and harness for firmware running on the Renode
11
+ emulator.**
12
+
13
+ Full documentation: <https://jig-dsl.readthedocs.io>
14
+
15
+ ## Overview
16
+
17
+ Jig runs test plans, written as `.jig` files, against a users firmware
18
+ on [Renode](https://renode.io). A plan declares the boards it needs, the
19
+ buse interfaces between them and the communication protocols the harness uses, then
20
+ drives the system over those interfaces in simulated time:
21
+
22
+ ```jig
23
+ import "zed.jig"
24
+
25
+ system "two ZedBoards and a host computer" {
26
+ board a: "zed-a"
27
+ board b: "zed-b"
28
+ bus can0: can(bitrate: 1 Mbit/s, members: [a.can, b.can])
29
+ node host: csp(address: 10, link: a.kiss)
30
+ }
31
+
32
+ setup {
33
+ parallel {
34
+ task zed_a { booted(a, 1) }
35
+ task zed_b { booted(b, 2) }
36
+ }
37
+ }
38
+
39
+ test "the host computer reaches zed-b through zed-a" {
40
+ check host.ping(b) < 50 ms
41
+ check can0.traffic().routes["10->2:1"] >= 1
42
+ }
43
+ ```
44
+
45
+ This is the start of `examples/zedboard/plans/multiboard.jig`. The setup
46
+ waits, on both boards at once, for the boot banner and the first prompt
47
+ (`booted` comes from the example's plan library, `lib/zed.jig`). The test
48
+ then pings zed-b from the host computer through zed-a using the lightweight
49
+ network protocol [LibCSP](https://github.com/libcsp/libcsp).
50
+
51
+ Every check's result is printed in a report with the simulated time it ran
52
+ at. Each run gets a fingerprint, a SHA-256 digest of everything it decided
53
+ and measured in simulated time, so `jig run --repeat 3` proves a plan gives
54
+ the same result three times, on any host.
55
+
56
+ Boards are data: a `board.yaml` names the platform (in this case the
57
+ [ZedBoard][zedboard], running a Zynq-7020 SoC), the Renode platform
58
+ description, the firmware and the interfaces plans use. Users must bring
59
+ their own C# models and Python plugins for any peripherals not directly
60
+ supported by Jig.
61
+
62
+ [zedboard]: https://www.avnet.com/americas/products/avnet-boards/avnet-board-families/zedboard/
63
+
64
+ ## Requirements
65
+
66
+ - Python 3.11 or later.
67
+ - Renode 1.17.0. Jig finds an installed copy, or `jig renode install`
68
+ downloads the release from Renode's GitHub page after you agree, and
69
+ checks its size and SHA-256 digest.
70
+ - For the ZedBoard example's firmware, `arm-none-eabi-gcc`.
71
+ - For RTL co-simulation, Linux with Verilator.
72
+
73
+ ## Installation
74
+
75
+ ```bash
76
+ python -m pip install jig-dsl
77
+ jig renode install
78
+ jig doctor
79
+ ```
80
+
81
+ ## Quick Start
82
+
83
+ The repository's ZedBoard example runs two Zynq-7020 boards on one CAN
84
+ bus, with a host computer on a serial link to the first:
85
+
86
+ ```bash
87
+ git clone https://github.com/GNPower/Jig.git
88
+ cd Jig
89
+ python examples/zedboard/firmware/build.py
90
+ jig run examples/zedboard/plans/multiboard.jig --html out/report.html
91
+ ```
92
+
93
+ `build.py` needs `arm-none-eabi-gcc` on the `PATH`. The tutorials in the
94
+ documentation build plans like this one step by step.
95
+
96
+ ## Features
97
+
98
+ - **A plan language for firmware tests**: consoles, network requests, checks,
99
+ waits, loops, parallel tasks, timelines and recorded values, all in
100
+ simulated time.
101
+ - **Several boards in one emulation**, on bus interfaces the harness carries
102
+ and watches, with repeatable timing between them.
103
+ - **Zynq-7000 and STM32 drivers**: JTAG-style and Quad-SPI boots through
104
+ Jig's Zynq-7000 models, and ELF boots on Renode's STM32 platforms.
105
+ - **CSP in libcsp 1.6's wire format**: KISS and CAN links, RDP
106
+ connections, and a bus observer that reassembles every packet.
107
+ - **Board plugins** through `jig.plugin`, a versioned API for plan
108
+ functions, board members, decoders and device drivers.
109
+ - **C# models compiled by Renode at start-up**, with interfaces that board
110
+ chip models implement so plans can drive them.
111
+ - **Reports for people and CI**: an HTML page with plots, JUnit XML, a JSON
112
+ summary, run directories with every console transcript and packet, and
113
+ exit codes that differentiate failed tests from broken setups.
114
+ - **RTL co-simulation** of programmable-logic blocks with Verilator.
115
+ - **A localhost bridge to Renode** with a token per run, in place of
116
+ Renode's network-facing Robot Framework server.
117
+
118
+ ## License
119
+
120
+ MIT: see [LICENSE](https://github.com/GNPower/Jig/blob/main/LICENSE).
@@ -0,0 +1,192 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0.3"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "jig-dsl"
7
+ version = "0.1.0a1"
8
+ description = "A test language and harness for firmware running on the Renode emulator"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Graham Power" }]
14
+ keywords = ["renode", "emulation", "firmware", "testing", "embedded", "zynq", "stm32", "csp"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Environment :: Console",
18
+ "Intended Audience :: Developers",
19
+ "Operating System :: MacOS",
20
+ "Operating System :: Microsoft :: Windows",
21
+ "Operating System :: POSIX :: Linux",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3 :: Only",
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 :: Software Development :: Embedded Systems",
29
+ "Topic :: Software Development :: Testing",
30
+ "Topic :: System :: Emulators",
31
+ "Typing :: Typed",
32
+ ]
33
+ dependencies = [
34
+ "textX>=4,<5",
35
+ "PyYAML>=6,<7",
36
+ "platformdirs>=4,<5",
37
+ "packaging>=23",
38
+ ]
39
+
40
+ [project.urls]
41
+ Homepage = "https://github.com/GNPower/Jig"
42
+ Documentation = "https://jig-dsl.readthedocs.io"
43
+ Repository = "https://github.com/GNPower/Jig"
44
+ Issues = "https://github.com/GNPower/Jig/issues"
45
+ Changelog = "https://github.com/GNPower/Jig/blob/main/CHANGELOG.md"
46
+
47
+ # The tool sets noxfile.py installs per session, CI and `dev` use the same pins.
48
+ [project.optional-dependencies]
49
+ test = [
50
+ "pytest>=8,<10",
51
+ "pytest-cov>=6,<8",
52
+ ]
53
+ docs = [
54
+ "sphinx>=8.2,<10",
55
+ "sphinx-rtd-theme>=3.0,<4",
56
+ "myst-parser>=4.0,<6",
57
+ ]
58
+ lint = [
59
+ "ruff==0.17.0",
60
+ "pymarkdownlnt==0.9.40",
61
+ "zizmor==1.30.1",
62
+ ]
63
+ typecheck = [
64
+ "jig-dsl[test]",
65
+ "mypy==2.4.0",
66
+ "types-PyYAML==6.0.12.20260906",
67
+ "nox==2026.8.17",
68
+ ]
69
+ release = [
70
+ "build==1.6.1",
71
+ "twine==7.0.0",
72
+ "bump-my-version==1.5.1",
73
+ ]
74
+ audit = [
75
+ "pip-audit==2.10.1",
76
+ ]
77
+ dev = [
78
+ "jig-dsl[test,lint,typecheck,release]",
79
+ ]
80
+
81
+ [project.scripts]
82
+ jig = "jig.cli:main"
83
+
84
+ # SoC families: a board's `soc:` names one (board.yaml `soc_driver:` overrides).
85
+ [project.entry-points."jig.socs"]
86
+ zynq7000 = "jig.ext.zynq7000.driver:Zynq7000Driver"
87
+ stm32 = "jig.system.socs.stm32:Stm32Driver"
88
+
89
+ # Highlighting for .jig plans in Sphinx, Pygments and any tool built on them.
90
+ [project.entry-points."pygments.lexers"]
91
+ jig = "jig.lang.pygments_lexer:JigLexer"
92
+
93
+ [tool.setuptools.packages.find]
94
+ where = ["src"]
95
+
96
+ [tool.setuptools.package-data]
97
+ jig = [
98
+ "py.typed",
99
+ "lang/grammar/*.tx",
100
+ "lang/stdlib/*.jig",
101
+ "models/**/*.cs",
102
+ "platforms/**/*.repl",
103
+ "ext/cosim/templates/*",
104
+ ]
105
+
106
+ [tool.ruff]
107
+ line-length = 100
108
+ target-version = "py311"
109
+ # Markdown keeps to 80 columns (PyMarkdown, .markdownlint.json). The Python in
110
+ # its code blocks is wrapped by hand, so ruff leaves .md files alone.
111
+ extend-exclude = ["*.md"]
112
+
113
+ [tool.ruff.lint]
114
+ select = ["E", "F", "W", "I", "B", "UP", "SIM", "RUF", "PTH", "N"]
115
+
116
+ [tool.ruff.lint.per-file-ignores]
117
+ # Statement and expression handlers are named after the grammar's rules (_s_If, _e_Sum).
118
+ "src/jig/lang/interpreter.py" = ["N802"]
119
+
120
+ [tool.mypy]
121
+ python_version = "3.11"
122
+ strict = true
123
+ warn_unreachable = true
124
+ mypy_path = "src"
125
+ packages = ["jig"]
126
+
127
+ [[tool.mypy.overrides]]
128
+ module = ["textx.*", "pygments.*"]
129
+ ignore_missing_imports = true
130
+
131
+ [tool.pytest.ini_options]
132
+ # The example's plugin tests need no Renode, so they run with the unit tests.
133
+ testpaths = ["tests", "examples/zedboard/tests"]
134
+ # The Renode tier starts Renode; run it with -m renode (JIG_RENODE set).
135
+ addopts = "-m 'not renode and not firmware and not slow' --strict-markers"
136
+ markers = [
137
+ "renode: needs Renode (JIG_RENODE or a managed install)",
138
+ "firmware: needs built firmware artifacts",
139
+ "slow: takes more than a few seconds",
140
+ ]
141
+
142
+ [tool.coverage.run]
143
+ source_pkgs = ["jig"]
144
+ branch = true
145
+
146
+ [tool.coverage.report]
147
+ show_missing = true
148
+ skip_covered = true
149
+ exclude_also = [
150
+ "if TYPE_CHECKING:",
151
+ "raise NotImplementedError",
152
+ "if __name__ == .__main__.:",
153
+ ]
154
+
155
+ # PyMarkdown (`nox -s lint`) with the rules of .markdownlint.json: every rule
156
+ # on, and a heading may repeat one outside its own section.
157
+ [tool.pymarkdown]
158
+ plugins.md024.siblings_only = true
159
+
160
+ # One static version, here: jig.__version__ and the docs read it from the
161
+ # installed metadata. bump-my-version neither commits nor tags. The release
162
+ # commit and the tag vX.Y.Z are signed by hand. PEP 440 forms:
163
+ # 0.1.0.dev0 -> (pre_l) 0.1.0a1 -> (pre_n) 0.1.0a2 -> (pre_l) 0.1.0b1 ->
164
+ # 0.1.0rc1 -> 0.1.0 -> (patch) 0.1.1.dev1.
165
+ [tool.bumpversion]
166
+ current_version = "0.1.0a1"
167
+ parse = """(?x)
168
+ (?P<major>0|[1-9]\\d*)\\.
169
+ (?P<minor>0|[1-9]\\d*)\\.
170
+ (?P<patch>0|[1-9]\\d*)
171
+ (?:(?P<pre_l>\\.dev|a|b|rc)(?P<pre_n>0|[1-9]\\d*))?
172
+ """
173
+ serialize = [
174
+ "{major}.{minor}.{patch}{pre_l}{pre_n}",
175
+ "{major}.{minor}.{patch}",
176
+ ]
177
+ commit = false
178
+ tag = false
179
+ allow_dirty = true
180
+
181
+ [tool.bumpversion.parts.pre_l]
182
+ values = [".dev", "a", "b", "rc", "final"]
183
+ optional_value = "final"
184
+
185
+ [tool.bumpversion.parts.pre_n]
186
+ first_value = "1"
187
+
188
+ [[tool.bumpversion.files]]
189
+ filename = "pyproject.toml"
190
+ regex = true
191
+ search = '^version = "{current_version}"'
192
+ replace = 'version = "{new_version}"'
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,13 @@
1
+ """Jig: a test language and harness for firmware running on Renode.
2
+
3
+ The package is board-agnostic. Boards are described by data (``board.yaml``),
4
+ peripheral models are C# sources Renode loads, and test plans are ``.jig``
5
+ scripts.
6
+ """
7
+
8
+ from importlib.metadata import PackageNotFoundError, version
9
+
10
+ try:
11
+ __version__ = version("jig-dsl")
12
+ except PackageNotFoundError: # a source tree that was never installed
13
+ __version__ = "0+unknown"
@@ -0,0 +1,7 @@
1
+ """``python -m jig``."""
2
+
3
+ import sys
4
+
5
+ from jig.cli import main
6
+
7
+ sys.exit(main())
@@ -0,0 +1,77 @@
1
+ """Content-addressed cache for generated artifacts.
2
+
3
+ Register maps, register programs and flash images are derived from files in
4
+ the repository (RTL, XSA, ELFs). Each artifact lives in
5
+ ``<cache>/artifacts/<kind>-<digest>/``, where the digest covers the contents of every
6
+ input file and the build parameters, so a changed input produces a new entry
7
+ and an unchanged one is reused. Entries are built in a temporary directory and
8
+ renamed into place, so an interrupted build never leaves a half-written entry.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import hashlib
14
+ import json
15
+ import logging
16
+ import shutil
17
+ import tempfile
18
+ from collections.abc import Callable, Iterable
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+ from jig import project
23
+ from jig.errors import ConfigError
24
+
25
+ log = logging.getLogger(__name__)
26
+
27
+ _COMPLETE = ".complete"
28
+
29
+
30
+ def cache_root() -> Path:
31
+ return project.artifacts_dir()
32
+
33
+
34
+ def digest(inputs: Iterable[Path], params: dict[str, Any] | None = None) -> str:
35
+ hasher = hashlib.sha256()
36
+ for path in inputs:
37
+ if not path.is_file():
38
+ raise ConfigError(f"artifact input {path} does not exist")
39
+ hasher.update(path.name.encode())
40
+ with path.open("rb") as handle:
41
+ for block in iter(lambda: handle.read(1 << 20), b""):
42
+ hasher.update(block)
43
+ hasher.update(json.dumps(params or {}, sort_keys=True, default=str).encode())
44
+ return hasher.hexdigest()[:16]
45
+
46
+
47
+ def cached(
48
+ kind: str,
49
+ inputs: Iterable[Path],
50
+ build: Callable[[Path], None],
51
+ params: dict[str, Any] | None = None,
52
+ ) -> Path:
53
+ """Directory holding the artifact; ``build(directory)`` fills a new one."""
54
+ input_list = list(inputs)
55
+ entry = cache_root() / f"{kind}-{digest(input_list, params)}"
56
+ if (entry / _COMPLETE).exists():
57
+ return entry
58
+ entry.parent.mkdir(parents=True, exist_ok=True)
59
+ staging = Path(tempfile.mkdtemp(prefix=f"{kind}-", dir=entry.parent))
60
+ try:
61
+ log.info("building %s", entry.name)
62
+ build(staging)
63
+ (staging / _COMPLETE).write_text(
64
+ json.dumps(
65
+ {"inputs": [str(p) for p in input_list], "params": params or {}},
66
+ default=str,
67
+ indent=2,
68
+ ),
69
+ encoding="utf-8",
70
+ )
71
+ if entry.exists():
72
+ shutil.rmtree(entry)
73
+ staging.rename(entry)
74
+ except BaseException:
75
+ shutil.rmtree(staging, ignore_errors=True)
76
+ raise
77
+ return entry