ebyst 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.
Files changed (134) hide show
  1. ebyst-0.1.0/.gitignore +3 -0
  2. ebyst-0.1.0/LICENSE +19 -0
  3. ebyst-0.1.0/Makefile +15 -0
  4. ebyst-0.1.0/PKG-INFO +87 -0
  5. ebyst-0.1.0/README.md +72 -0
  6. ebyst-0.1.0/pyproject.toml +26 -0
  7. ebyst-0.1.0/src/ebyst/__init__.py +4 -0
  8. ebyst-0.1.0/src/ebyst/bsdl.py +125 -0
  9. ebyst-0.1.0/src/ebyst/device.py +178 -0
  10. ebyst-0.1.0/src/ebyst/drivers/__init__.py +2 -0
  11. ebyst-0.1.0/src/ebyst/drivers/driver.py +53 -0
  12. ebyst-0.1.0/src/ebyst/drivers/ft2232h.py +185 -0
  13. ebyst-0.1.0/src/ebyst/drivers/sim.py +164 -0
  14. ebyst-0.1.0/src/ebyst/interfaces/__init__.py +1 -0
  15. ebyst-0.1.0/src/ebyst/interfaces/i2c.py +116 -0
  16. ebyst-0.1.0/src/ebyst/tap_controller.py +336 -0
  17. ebyst-0.1.0/tests/bsdl/BSDLLCMXO2-256HCQFN32.BSM +353 -0
  18. ebyst-0.1.0/tests/bsdl/MPF100Tfcg484.bsdl +2388 -0
  19. ebyst-0.1.0/tests/bsdl/XA3S400_FG456.bsdl +1570 -0
  20. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-1200hccsbga132.BSM +683 -0
  21. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-1200hctqfp100.BSM +612 -0
  22. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-1200hctqfp144.BSM +699 -0
  23. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-1200uhcftbga256.BSM +1176 -0
  24. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-1200zehecsbga132.BSM +683 -0
  25. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-1200zehetqfp100.BSM +612 -0
  26. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-1200zehetqfp144.BSM +699 -0
  27. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-1200zewlcsp25.BSM +441 -0
  28. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-2000hccabga256.BSM +1177 -0
  29. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-2000hccsbga132.BSM +901 -0
  30. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-2000hcftbga256.BSM +1177 -0
  31. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-2000hctqfp100.BSM +830 -0
  32. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-2000hctqfp144.BSM +923 -0
  33. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-2000uhcfpbga484.BSM +1643 -0
  34. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-2000zehecabga256.BSM +1177 -0
  35. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-2000zehecsbga132.BSM +901 -0
  36. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-2000zeheftbga256.BSM +1177 -0
  37. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-2000zehetqfp100.BSM +830 -0
  38. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-2000zehetqfp144.BSM +923 -0
  39. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-256hccsbga132.BSM +505 -0
  40. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-256hcqfn32.BSM +353 -0
  41. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-256hctqfp100.BSM +473 -0
  42. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-256hcucbga64.BSM +421 -0
  43. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-256zehecsbga132.BSM +505 -0
  44. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-256zehetqfp100.BSM +473 -0
  45. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-256zeheucbga64.BSM +421 -0
  46. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-256zeqfn32.BSM +353 -0
  47. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-4000hccabga256.BSM +1305 -0
  48. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-4000hccabga332.BSM +1484 -0
  49. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-4000hccsbga132.BSM +1029 -0
  50. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-4000hcfpbga484.BSM +1642 -0
  51. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-4000hcftbga256.BSM +1305 -0
  52. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-4000hctqfp144.BSM +1056 -0
  53. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-4000zehecabga256.BSM +1305 -0
  54. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-4000zehecabga332.BSM +1484 -0
  55. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-4000zehecsbga132.BSM +1029 -0
  56. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-4000zehecsbga184.BSM +1151 -0
  57. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-4000zehefpbga484.BSM +1642 -0
  58. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-4000zeheftbga256.BSM +1305 -0
  59. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-4000zehetqfp144.BSM +1056 -0
  60. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-640hccsbga132.BSM +589 -0
  61. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-640hctqfp100.BSM +555 -0
  62. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-640uhctqfp144.BSM +699 -0
  63. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-640zehecsbga132.BSM +589 -0
  64. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-640zehetqfp100.BSM +555 -0
  65. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-7000hccabga256.BSM +1417 -0
  66. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-7000hccabga332.BSM +1602 -0
  67. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-7000hcfpbga484.BSM +1838 -0
  68. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-7000hcftbga256.BSM +1417 -0
  69. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-7000zehecabga256.BSM +1417 -0
  70. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-7000zehecabga332.BSM +1602 -0
  71. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-7000zehefpbga484.BSM +1838 -0
  72. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-7000zeheftbga256.BSM +1417 -0
  73. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2-7000zehetqfp144.BSM +1168 -0
  74. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2_1200hc_qfn32.bsm +457 -0
  75. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2_1200ze_qfn32.bsm +457 -0
  76. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2_4000hc_qfn84.bsm +923 -0
  77. ebyst-0.1.0/tests/bsdl/lattice/bsdllcmxo2_4000ze_qfn84.bsm +923 -0
  78. ebyst-0.1.0/tests/bsdl/lattice/fpga-md-02013-1-04-lcmxo2-1200ze-bsdl-wlcsp36.bsm +470 -0
  79. ebyst-0.1.0/tests/bsdl/lattice/fpga-md-02014-1-04-lcmxo2-4000ze-bsdl-wlcsp81.bsm +912 -0
  80. ebyst-0.1.0/tests/bsdl/lattice/fpga-md-02033-1-04-lcmxo2-7000hc-tqfp144.bsm +1171 -0
  81. ebyst-0.1.0/tests/bsdl/lattice/lcmxo2_2000ze_wlcsp49.bsm +714 -0
  82. ebyst-0.1.0/tests/bsdl/lattice/lcmxo2_256hc_qfn48.bsm +395 -0
  83. ebyst-0.1.0/tests/bsdl/lattice/lcmxo2_256ze_qfn48.bsm +395 -0
  84. ebyst-0.1.0/tests/bsdl/lattice/lcmxo2_640hc_qfn48.bsm +443 -0
  85. ebyst-0.1.0/tests/bsdl/lattice/lcmxo2_640ze_qfn48.bsm +443 -0
  86. ebyst-0.1.0/tests/bsdl/nxp/mpc5/mpc567xr_416_r1.bsdl +839 -0
  87. ebyst-0.1.0/tests/bsdl/nxp/mpc5/mpc567xr_516_r1.bsdl +872 -0
  88. ebyst-0.1.0/tests/bsdl/st/stm32f3/CortexMx.bsd +103 -0
  89. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F301_F302_LQFP48.bsd +368 -0
  90. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F301_F302_LQFP64.bsd +403 -0
  91. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F301_F302_UFQFPN32.bsd +340 -0
  92. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F301_F302_WLCSP49.bsd +374 -0
  93. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F302_F303_B_C_LQFP100.bsd +572 -0
  94. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F302_F303_B_C_LQFP48.bsd +470 -0
  95. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F302_F303_B_C_LQFP64.bsd +500 -0
  96. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F302_F303_D_E_LQFP100.bsd +656 -0
  97. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F302_F303_D_E_LQFP144.bsd +718 -0
  98. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F302_F303_D_E_LQFP64.bsd +583 -0
  99. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F302_F303_D_E_UFBGA100.bsd +659 -0
  100. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F303_F334_LQFP32.bsd +333 -0
  101. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F303_F334_LQFP48.bsd +361 -0
  102. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F303_F334_LQFP64.bsd +389 -0
  103. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F318_UFQFPN32.bsd +331 -0
  104. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F318_WLCSP49.bsd +358 -0
  105. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F328_LQFP48 .bsd +361 -0
  106. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F358_LQFP100.bsd +572 -0
  107. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F358_LQFP48.bsd +470 -0
  108. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F358_LQFP64.bsd +500 -0
  109. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F373_LQFP100.bsd +565 -0
  110. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F373_LQFP48.bsd +466 -0
  111. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F373_LQFP64.bsd +497 -0
  112. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F373_UFBGA100.bsd +565 -0
  113. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F378_LQFP100.bsd +565 -0
  114. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F378_LQFP48.bsd +466 -0
  115. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F378_LQFP64.bsd +497 -0
  116. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F378_UFBGA100.bsd +565 -0
  117. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F378_WLCSP66.bsd +497 -0
  118. ebyst-0.1.0/tests/bsdl/st/stm32f3/STM32F398_LQFP100.bsd +658 -0
  119. ebyst-0.1.0/tests/bsdl/st/stm32f3/readme.txt +71 -0
  120. ebyst-0.1.0/tests/bsdl/st/stm32f4/CortexMx.bsd +103 -0
  121. ebyst-0.1.0/tests/bsdl/st/stm32f4/STM32F405_415_407_417_LQFP100.bsd +717 -0
  122. ebyst-0.1.0/tests/bsdl/st/stm32f4/STM32F405_415_407_417_LQFP144.bsd +783 -0
  123. ebyst-0.1.0/tests/bsdl/st/stm32f4/STM32F405_415_407_417_LQFP176.bsd +836 -0
  124. ebyst-0.1.0/tests/bsdl/st/stm32f4/STM32F405_415_407_417_LQFP64.bsd +653 -0
  125. ebyst-0.1.0/tests/bsdl/st/stm32f4/STM32F405_415_407_417_UFBGA176.bsd +839 -0
  126. ebyst-0.1.0/tests/bsdl/st/stm32f4/STM32F405_415_407_417_WLCSP90.bsd +701 -0
  127. ebyst-0.1.0/tests/bsdl/st/stm32f4/readme.txt +50 -0
  128. ebyst-0.1.0/tests/te0790_blink.py +41 -0
  129. ebyst-0.1.0/tests/test_async.py +34 -0
  130. ebyst-0.1.0/tests/test_bsdl.py +25 -0
  131. ebyst-0.1.0/tests/test_chain.py +33 -0
  132. ebyst-0.1.0/tests/test_fsm.py +36 -0
  133. ebyst-0.1.0/tests/test_ftdi.py +16 -0
  134. ebyst-0.1.0/tests/test_i2c.py +38 -0
ebyst-0.1.0/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ venv
2
+ dist
3
+ **/__pycache__
ebyst-0.1.0/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2024 Sijmen Woutersen
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
ebyst-0.1.0/Makefile ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/bash
2
+
3
+ default:
4
+ @echo Usage: make [dist|venv]
5
+
6
+ .PHONY: dist
7
+ dist:
8
+ python3 -m build .
9
+
10
+ .PHONY: venv
11
+ venv:
12
+ python3 -m venv venv
13
+ venv/bin/pip install --upgrade pip
14
+ venv/bin/pip install build
15
+ venv/bin/pip install -e .
ebyst-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.3
2
+ Name: ebyst
3
+ Version: 0.1.0
4
+ Summary: Boundary scan test library
5
+ Project-URL: Homepage, https://github.com/swolix/ebyst
6
+ Project-URL: Issues, https://github.com/swolix/ebyst/issues
7
+ Author-email: Sijmen Woutersen <sijmen.woutersen@gmail.com>
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python :: 3
11
+ Requires-Python: >=3.8
12
+ Requires-Dist: bitarray
13
+ Requires-Dist: pyftdi
14
+ Description-Content-Type: text/markdown
15
+
16
+ # EByST
17
+ Boundary scan test framework for board validation
18
+
19
+ # Basic example
20
+ ```python
21
+ # initialize JTAG interface driver (currently only FT2232H is supported)
22
+ drv = ebyst.drivers.FT2232H(ebyst.drivers.FT2232H.list_devices()[0])
23
+ ctl = ebyst.TapController(drv)
24
+ ctl.detect_chain()
25
+
26
+ # Add device(s) to chain
27
+ dev = ebyst.Device.from_bsdl("bsdl/BSDLLCMXO2-256HCQFN32.BSM")
28
+ ctl.add_device(dev)
29
+ ctl.validate_chain()
30
+
31
+ # Start test
32
+ ctl.extest()
33
+
34
+ # Loopback test (assuming loopback on pins
35
+ dev.pinmap['O'].output_enable(True)
36
+ dev.pinmap['I'].output_enable(False)
37
+ dev.pinmap['O'].set_value(1)
38
+ await ctl.cycle() # drive output
39
+ await ctl.cycle() # sample input
40
+ print(dev.pinmap['I'].get_value())
41
+ dev.pinmap['O'].set_value(0)
42
+ await ctl.cycle() # drive output
43
+ await ctl.cycle() # sample input
44
+ print(dev.pinmap['I'].get_value())
45
+
46
+ # I2C test
47
+ i2c = ebyst.interfaces.I2C(ctl, dev.pinmap['PB9A'], dev.pinmap['PB4B'])
48
+ await i2c.init()
49
+ dev_address = 0xa0
50
+ reg_address = 0x10
51
+ data = 0xa5
52
+ print(f"Writing {dev_address:02x}:{reg_address:02x} <= {data:02x}")
53
+ await i2c.write(0xa0, 0x10, 0xa5)
54
+ print(f"Reading {dev_address:02x}:{reg_address:02x} => ", end='')
55
+ await x = i2c.read(0xa0, 0x10)
56
+ print(f"{x:02x}")
57
+
58
+ ctl.reset()
59
+ ```
60
+ # Async
61
+ The library uses `asyncio` to allow running multiple tests in parallel.
62
+ When the loopback test and I2C test above are put in different tasks, they share the same boundary scan cycles,
63
+ meaning they run completely parallel
64
+ (Note that this only works when they are not using different pins, if pins are shared between tests, make sure to
65
+ schedule them appropriately)
66
+
67
+ Example;
68
+ ```python
69
+ async def main():
70
+ drv = ebyst.drivers.FT2232H(ebyst.drivers.FT2232H.list_devices()[0])
71
+ dev = ebyst.Device.from_bsdl("bsdl/BSDLLCMXO2-256HCQFN32.BSM")
72
+ ctl = ebyst.TapController(drv)
73
+ ctl.detect_chain()
74
+ ctl.add_device(dev)
75
+ ctl.validate_chain()
76
+ ctl.extest()
77
+ async with asyncio.TaskGroup() as tg:
78
+ tg.create_task(loopback_test(ctl, dev.pinmap['PB2C'], dev.pinmap['PB2A']))
79
+ tg.create_task(loopback_test(ctl, dev.pinmap['PB4C'], dev.pinmap['PB4D']))
80
+
81
+ if __name__ == "__main__":
82
+ logging.basicConfig()
83
+ logging.getLogger().setLevel(logging.INFO)
84
+ asyncio.run(main())
85
+ ```
86
+
87
+ see also `tests/test_async.py`
ebyst-0.1.0/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # EByST
2
+ Boundary scan test framework for board validation
3
+
4
+ # Basic example
5
+ ```python
6
+ # initialize JTAG interface driver (currently only FT2232H is supported)
7
+ drv = ebyst.drivers.FT2232H(ebyst.drivers.FT2232H.list_devices()[0])
8
+ ctl = ebyst.TapController(drv)
9
+ ctl.detect_chain()
10
+
11
+ # Add device(s) to chain
12
+ dev = ebyst.Device.from_bsdl("bsdl/BSDLLCMXO2-256HCQFN32.BSM")
13
+ ctl.add_device(dev)
14
+ ctl.validate_chain()
15
+
16
+ # Start test
17
+ ctl.extest()
18
+
19
+ # Loopback test (assuming loopback on pins
20
+ dev.pinmap['O'].output_enable(True)
21
+ dev.pinmap['I'].output_enable(False)
22
+ dev.pinmap['O'].set_value(1)
23
+ await ctl.cycle() # drive output
24
+ await ctl.cycle() # sample input
25
+ print(dev.pinmap['I'].get_value())
26
+ dev.pinmap['O'].set_value(0)
27
+ await ctl.cycle() # drive output
28
+ await ctl.cycle() # sample input
29
+ print(dev.pinmap['I'].get_value())
30
+
31
+ # I2C test
32
+ i2c = ebyst.interfaces.I2C(ctl, dev.pinmap['PB9A'], dev.pinmap['PB4B'])
33
+ await i2c.init()
34
+ dev_address = 0xa0
35
+ reg_address = 0x10
36
+ data = 0xa5
37
+ print(f"Writing {dev_address:02x}:{reg_address:02x} <= {data:02x}")
38
+ await i2c.write(0xa0, 0x10, 0xa5)
39
+ print(f"Reading {dev_address:02x}:{reg_address:02x} => ", end='')
40
+ await x = i2c.read(0xa0, 0x10)
41
+ print(f"{x:02x}")
42
+
43
+ ctl.reset()
44
+ ```
45
+ # Async
46
+ The library uses `asyncio` to allow running multiple tests in parallel.
47
+ When the loopback test and I2C test above are put in different tasks, they share the same boundary scan cycles,
48
+ meaning they run completely parallel
49
+ (Note that this only works when they are not using different pins, if pins are shared between tests, make sure to
50
+ schedule them appropriately)
51
+
52
+ Example;
53
+ ```python
54
+ async def main():
55
+ drv = ebyst.drivers.FT2232H(ebyst.drivers.FT2232H.list_devices()[0])
56
+ dev = ebyst.Device.from_bsdl("bsdl/BSDLLCMXO2-256HCQFN32.BSM")
57
+ ctl = ebyst.TapController(drv)
58
+ ctl.detect_chain()
59
+ ctl.add_device(dev)
60
+ ctl.validate_chain()
61
+ ctl.extest()
62
+ async with asyncio.TaskGroup() as tg:
63
+ tg.create_task(loopback_test(ctl, dev.pinmap['PB2C'], dev.pinmap['PB2A']))
64
+ tg.create_task(loopback_test(ctl, dev.pinmap['PB4C'], dev.pinmap['PB4D']))
65
+
66
+ if __name__ == "__main__":
67
+ logging.basicConfig()
68
+ logging.getLogger().setLevel(logging.INFO)
69
+ asyncio.run(main())
70
+ ```
71
+
72
+ see also `tests/test_async.py`
@@ -0,0 +1,26 @@
1
+ [project]
2
+ name = "ebyst"
3
+ version = "0.1.0"
4
+ dependencies = [
5
+ "pyftdi",
6
+ "bitarray",
7
+ ]
8
+ authors = [
9
+ { name="Sijmen Woutersen", email="sijmen.woutersen@gmail.com" },
10
+ ]
11
+ description = "Boundary scan test library"
12
+ readme = "README.md"
13
+ requires-python = ">=3.8"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ ]
19
+
20
+ [project.urls]
21
+ Homepage = "https://github.com/swolix/ebyst"
22
+ Issues = "https://github.com/swolix/ebyst/issues"
23
+
24
+ [build-system]
25
+ requires = ["hatchling"]
26
+ build-backend = "hatchling.build"
@@ -0,0 +1,4 @@
1
+ from . import drivers
2
+ from . import interfaces
3
+ from .tap_controller import TapController, State as JtagState
4
+ from .device import Device
@@ -0,0 +1,125 @@
1
+ # Copyright (c) 2024 Sijmen Woutersen
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+ import logging
21
+ import pyparsing as pp
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ class BSDLFile:
26
+ """BSDL parser"""
27
+
28
+ class Declaration:
29
+ def __init__(self, name, type_, value=None, direction=None, range_start=None, range_end=None, owner=None):
30
+ self.name = name
31
+ self.type_ = type_
32
+ self.value = value
33
+ self.direction = direction
34
+ self.range_start = range_start
35
+ self.range_end = range_end
36
+ self.owner = owner
37
+
38
+ def __init__(self, name):
39
+ self.name = name
40
+ self.generics = {}
41
+ self.ports = {}
42
+ self.constants = {}
43
+ self.attributes = {}
44
+
45
+ @classmethod
46
+ def parse(cls, f):
47
+ comments = "--" + pp.SkipTo(pp.LineEnd())
48
+ identifier = pp.Word(init_chars=pp.srange("[a-zA-Z]"), body_chars=pp.srange("[a-zA-Z0-9_]"))
49
+ string_literal = pp.Combine(pp.QuotedString("\"") +
50
+ pp.ZeroOrMore(pp.Suppress(pp.Literal("&")) + pp.QuotedString("\"")), adjacent=False)
51
+ numeric_literal = pp.Or((pp.pyparsing_common.sci_real, pp.pyparsing_common.integer))
52
+ primary = pp.Forward()
53
+ enumeration_literal = (pp.Suppress(pp.Literal("(")) + primary +
54
+ pp.ZeroOrMore(pp.Suppress(pp.Literal(",")) + primary) + pp.Suppress(pp.Literal(")")))
55
+ literal = pp.Or((numeric_literal, enumeration_literal, string_literal))
56
+ primary <<= pp.Or((identifier, literal))
57
+ expression = primary
58
+ mode = pp.Or((pp.CaselessKeyword("in"), pp.CaselessKeyword("out"), pp.CaselessKeyword("inout"),
59
+ pp.CaselessKeyword("buffer"), pp.CaselessKeyword("linkage")))
60
+ range = (pp.Suppress(pp.Literal("(")) + pp.pyparsing_common.integer +
61
+ pp.Or((pp.CaselessKeyword("to"), pp.CaselessKeyword("downto"))) +
62
+ pp.pyparsing_common.integer + pp.Suppress(pp.Literal(")")))
63
+ subtype_indication = identifier + pp.Optional(range)
64
+ declaration = pp.Group(identifier + pp.Suppress(pp.Literal(":")) +
65
+ pp.Optional(mode) + subtype_indication +
66
+ pp.Optional(pp.Suppress(pp.Literal(":=")) + expression))
67
+ interface_list = declaration + pp.ZeroOrMore(pp.Suppress(pp.Literal(";")) + declaration)
68
+ generic_clause = pp.Group(pp.CaselessKeyword("generic") + pp.Suppress(pp.Literal("(")) + interface_list +
69
+ pp.Suppress(pp.Literal(")")) + pp.Suppress(pp.Literal(";")))
70
+ port_clause = pp.Group(pp.CaselessKeyword("port") + pp.Suppress(pp.Literal("(")) + interface_list +
71
+ pp.Suppress(pp.Literal(")")) + pp.Suppress(pp.Literal(";")))
72
+ use_clause = pp.Group(pp.CaselessKeyword("use") + identifier + pp.Literal(".") + identifier +
73
+ pp.ZeroOrMore(pp.Literal(",") + identifier) +
74
+ pp.Suppress(pp.Literal(";")))
75
+ class_ = pp.Or((pp.CaselessKeyword("entity"), pp.CaselessKeyword("signal")))
76
+ attribute_specification = pp.Group(pp.CaselessKeyword("attribute") + identifier +
77
+ pp.Suppress(pp.CaselessKeyword("of")) + identifier +
78
+ pp.Suppress(pp.Literal(":")) + class_ +
79
+ pp.Suppress(pp.CaselessKeyword("is")) + expression +
80
+ pp.Suppress(pp.Literal(";")))
81
+ constant_declaration = pp.Group(pp.CaselessKeyword("constant") + declaration + pp.Suppress(pp.Literal(";")))
82
+ entity_header = pp.Optional(generic_clause) + pp.Optional(port_clause)
83
+ entity_declarative_item = pp.Or((use_clause, attribute_specification, constant_declaration))
84
+ entity_declaration = (pp.Suppress(pp.CaselessKeyword("entity")) + identifier + pp.Suppress(pp.CaselessKeyword("is")) +
85
+ entity_header + pp.ZeroOrMore(entity_declarative_item) +
86
+ pp.Suppress(pp.CaselessKeyword("end") + pp.Optional(pp.CaselessKeyword("entity")) +
87
+ pp.Optional(identifier)) +
88
+ pp.Suppress(pp.Literal(";")))
89
+ bsdl_file = entity_declaration + pp.StringEnd()
90
+
91
+ bsdl_file.ignore(comments)
92
+
93
+ parsed = bsdl_file.parse_string(f.read())
94
+ # parsed.pprint()
95
+ r = cls(parsed[0])
96
+ for item in parsed[1:]:
97
+ if item[0] == "generic":
98
+ for generic in item[1:]:
99
+ assert len(generic) == 3
100
+ generic = BSDLFile.Declaration(generic[0], generic[1], value=generic[2])
101
+ r.generics[generic.name] = generic
102
+ elif item[0] == "port":
103
+ for port in item[1:]:
104
+ if len(port) == 3:
105
+ port = BSDLFile.Declaration(port[0], port[2], direction=port[1])
106
+ elif len(port) == 6:
107
+ port = BSDLFile.Declaration(port[0], port[2], direction=port[1],
108
+ range_start=port[3], range_end=port[5])
109
+ r.ports[port.name] = port
110
+ elif item[0] == "use":
111
+ pass
112
+ elif item[0] == "constant":
113
+ assert len(item) == 2
114
+ assert len(item[1]) == 3
115
+ constant = BSDLFile.Declaration(name=item[1][0], type_=item[1][1], value=item[1][2])
116
+ r.constants[constant.name] = constant
117
+ elif item[0] == "attribute":
118
+ assert len(item) >= 5
119
+ # TODO fix tuples
120
+ attribute = BSDLFile.Declaration(name=item[1], type_ = item[3], owner=item[2], value=item[4])
121
+ r.attributes[attribute.name] = attribute
122
+ else:
123
+ assert False
124
+
125
+ return r
@@ -0,0 +1,178 @@
1
+ # Copyright (c) 2024 Sijmen Woutersen
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+ import logging
21
+ import re
22
+ from pprint import pprint
23
+
24
+ from bitarray import bitarray
25
+
26
+ from .bsdl import BSDLFile
27
+
28
+ SPACE = "[ \r\n\t]*"
29
+
30
+ RE_OPCODE = re.compile(f"{SPACE}(?P<instruction>[A-Za-z][A-Za-z_0-9]*){SPACE}\\((?P<opcode>[01]+(,{SPACE}[01]+)*)\\){SPACE}(,)?")
31
+ RE_CELL = re.compile(f"{SPACE}(?P<index>[0-9]+){SPACE}\\((?P<format>([^\\)\\(]*(\\([^\\)]*\\))*)*)\\)({SPACE},)?")
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+ class StdLogicPattern:
36
+ """Bit pattern supporting std_logic values"""
37
+ def __init__(self, pattern):
38
+ for c in pattern.upper():
39
+ if not c in "01X": raise Exception(f"{c} not supported in bit pattern")
40
+ self.pattern = pattern.upper()
41
+
42
+ def __eq__(self, other):
43
+ if len(self.pattern) != len(other): return False
44
+ for c1, c2 in zip(self.pattern, other):
45
+ if c1 != "X" and int(c1) != int(c2): return False
46
+ return True
47
+
48
+ def __str__(self):
49
+ return f"StdLogicPattern('{self.pattern}')"
50
+
51
+ def to_bitarray(self):
52
+ return bitarray(self.pattern.replace("X", "0"))
53
+
54
+ class Cell:
55
+ """Represents a boundary scan cell"""
56
+ def __init__(self, num, cell, port, function, safe, ctl_cell=None, out_dis_ctl=None, out_dis_val=None):
57
+ self.num = num
58
+ self.cell = cell
59
+ self.port = port
60
+ self.function = function
61
+ self.safe = safe
62
+ self.ctl_cell = int(ctl_cell) if not ctl_cell is None else None
63
+ self.out_dis_ctl = int(out_dis_ctl) if not out_dis_ctl is None else None
64
+ self.out_dis_val = out_dis_val
65
+ self.in_value = None
66
+ self.out_value = 0
67
+
68
+ self.set_safe()
69
+
70
+ def set_safe(self):
71
+ if self.safe.upper() != 'X':
72
+ self.out_value = int(self.safe)
73
+
74
+ def __repr__(self):
75
+ return f"{self.cell} @ {self.num}"
76
+
77
+ @classmethod
78
+ def parse(cls, num, parameters):
79
+ return cls(num, *[p.strip() for p in parameters.split(",")])
80
+
81
+ class Pin:
82
+ """Represents a pin with control & data cell"""
83
+ def __init__(self, name, data_cell, control_cell):
84
+ self.name = name
85
+ self.data_cell = data_cell
86
+ self.control_cell = control_cell
87
+
88
+ def output_enabled(self):
89
+ if self.data_cell.cell != "BC_7" or self.control_cell.cell != "BC_2": raise Exception("Not supported")
90
+ return self.control_cell.out_value != self.data_cell.out_dis_ctl
91
+
92
+ def output_enable(self, enable=True):
93
+ if self.data_cell.cell != "BC_7" or self.control_cell.cell != "BC_2": raise Exception("Not supported")
94
+ if enable:
95
+ self.control_cell.out_value = [1, 0][self.data_cell.out_dis_ctl]
96
+ else:
97
+ self.control_cell.out_value = self.data_cell.out_dis_ctl
98
+
99
+ def set_value(self, value):
100
+ if self.data_cell.cell != "BC_7" or self.control_cell.cell != "BC_2": raise Exception("Not supported")
101
+ self.data_cell.out_value = 1 if value else 0
102
+
103
+ def get_value(self):
104
+ if self.data_cell.cell != "BC_7" or self.control_cell.cell != "BC_2": raise Exception("Not supported")
105
+ return self.data_cell.out_value if self.output_enabled() else self.data_cell.in_value
106
+
107
+ def __repr__(self):
108
+ if self.output_enabled():
109
+ return f"<PIN {self.name}: output: {self.data_cell.out_value}>"
110
+ else:
111
+ return f"<PIN {self.name}: input>: {self.data_cell.in_value}>"
112
+
113
+ class Device:
114
+ def __init__(self, irlen, idcode=None, opcodes=None, cells=[]):
115
+ self.irlen = irlen
116
+ self.idcode = idcode
117
+ if opcodes is None: opcodes = {'BYPASS': bitarray('1' * irlen)}
118
+ if not 'BYPASS' in opcodes: raise ValueError("BYPASS command is required")
119
+ self.opcodes = opcodes
120
+ self.cells = cells
121
+ self.pinmap = {}
122
+ for cell in self.cells:
123
+ if cell.port != "*":
124
+ pin = Pin(cell.port, cell, self.cells[cell.ctl_cell] if not cell.ctl_cell is None else None)
125
+ self.pinmap[pin.name] = pin
126
+
127
+ def update_br(self, br):
128
+ if len(br) != len(self.cells): raise ValueError("Invalid br length")
129
+ for i, v in enumerate(br):
130
+ self.cells[i].in_value = v
131
+
132
+ def generate_br(self):
133
+ r = bitarray()
134
+ for cell in self.cells:
135
+ r.append(cell.out_value)
136
+ return r
137
+
138
+ @staticmethod
139
+ def from_bsdl(fn):
140
+ with open(fn, "rt") as f:
141
+ bsdi_file = BSDLFile.parse(f)
142
+
143
+ irlen = int(bsdi_file.attributes['INSTRUCTION_LENGTH'].value)
144
+ idcode = StdLogicPattern(bsdi_file.attributes['IDCODE_REGISTER'].value[::-1])
145
+
146
+ opcodes = {}
147
+ opcode_str = bsdi_file.attributes['INSTRUCTION_OPCODE'].value
148
+ while True:
149
+ m = RE_OPCODE.match(opcode_str)
150
+ if m:
151
+ opcode = m['opcode'].split(",")
152
+ if len(opcode) == 1:
153
+ ba = bitarray(opcode[0].strip())
154
+ ba.reverse()
155
+ opcodes[m['instruction'].upper()] = ba
156
+ else:
157
+ # not supported
158
+ pass
159
+ opcode_str = opcode_str[m.end():]
160
+ else:
161
+ break
162
+ if len(opcode_str) != 0: raise Exception("Invalid INSTRUCTION_OPCODE format")
163
+
164
+ brlen = int(bsdi_file.attributes['BOUNDARY_LENGTH'].value)
165
+
166
+ cells = [None] * brlen
167
+ cell_str = bsdi_file.attributes['BOUNDARY_REGISTER'].value.strip()
168
+ while True:
169
+ m = RE_CELL.match(cell_str)
170
+ if m:
171
+ cell = Cell.parse(int(m['index']), m['format'])
172
+ cells[cell.num] = cell
173
+ cell_str = cell_str[m.end():]
174
+ else:
175
+ break
176
+ if len(cell_str) != 0: raise Exception("Invalid BOUNDARY_REGISTER format")
177
+
178
+ return Device(irlen=irlen, idcode=idcode, opcodes=opcodes, cells=cells)
@@ -0,0 +1,2 @@
1
+ from .sim import Sim, SimChain
2
+ from .ft2232h import FT2232H
@@ -0,0 +1,53 @@
1
+ # Copyright (c) 2024 Sijmen Woutersen
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+ from bitarray import bitarray
21
+
22
+ class Driver:
23
+ def reset(self):
24
+ self.transmit_tms_str(bitarray('11111'))
25
+
26
+ def transfer(self, tms: int, tdi: int) -> int:
27
+ raise NotImplementedError()
28
+
29
+ def transmit_tms_str(self, tms_str: bitarray, tdi=0):
30
+ for tms in tms_str:
31
+ self.transfer(tms, tdi)
32
+
33
+ def transfer_tdi_tdo_str(self, tdi_str: bitarray, first_tms=0, last_tms=0) -> bitarray:
34
+ r = bitarray()
35
+ for tdi in tdi_str[:-1]:
36
+ r.append(self.transfer(first_tms, tdi))
37
+ r.append(self.transfer(last_tms, tdi_str[-1]))
38
+ return r
39
+
40
+ def transmit_tdi_str(self, tdi_str: bitarray, first_tms=0, last_tms=0):
41
+ self.transfer_tdi_tdo_str(tdi_str, first_tms, last_tms)
42
+
43
+ def receive_tdo_str(self, n, first_tms=0, first_tdi=0, last_tms=None, last_tdi=None) -> bitarray:
44
+ if last_tms is None: last_tms = first_tms
45
+ if last_tdi is None: last_tdi = first_tdi
46
+ if n < 1: raise ValueError("n must be > 0")
47
+ if n == 1 and first_tms != last_tms: raise ValueError("last_tms must be first_tms when n == 1")
48
+ if n == 1 and first_tdi != last_tdi: raise ValueError("last_tdi must be first_tdi when n == 1")
49
+ r = bitarray()
50
+ for i in range(n-1):
51
+ r.append(self.transfer(first_tms, first_tdi))
52
+ r.append(self.transfer(last_tms, last_tdi))
53
+ return r