Compare commits

...

93 Commits

Author SHA1 Message Date
ole00 6095042e5a img: updated afterburner image 2024-04-27 13:37:42 +01:00
ole00 99ccaa302f
Update README.md - additions for PCB 3.2 and afterburner v. 0.6.0 2024-04-27 13:08:13 +01:00
ole00 e400da6b10 PC app release 0.6.0 2024-04-27 11:39:55 +01:00
ole00 6c5ffab5be version increased to v.0.6 2024-04-27 11:37:10 +01:00
ole00 b9690e6945 added support for SPI RAM 2024-04-27 11:19:17 +01:00
ole00 1ac80bbb01 updated BOM for PCB 3.2b 2024-04-27 11:12:57 +01:00
ole00 28d8737780 new PCB design v. 3.2
* Optional voltage booster on the board which can replace
  the mt3608 module. If mt3608 module is used the area on
  the board under the module is left unsoldered.
* Optional SPI RAM for future use to support bigger GALs/CPLDs
  on the UNO board. Not currently used by any GAL.
* Support for 3.3V Arduinos by splitting the 5V power rail and
  IOREF power rail. Very old Arduino UNOs R2 without IOREF
  header pin can use a solder bridge JP1 (marked as Vx on the
  bottom of the board) to join the 5V rail and IOREF power rail.
* HW support for internal AREF which is used to measure VPP.
  There is an extra footprint on the bottom of the board
  to solder another attenuation resistor (R19). For example
  ESP32-S2-Mini based Arduinos with 2.5V internal analog
  reference can use 100k resistor as R19 to reduce the max.
  3V sensing value to 2.5V.
* ZIF18 and ZIF19 connected to the shift register output pins.
  This is for supporting other GALS and CPLDs (via adapter).
* added 100K pull down resitor R18 to ZIF15 (to complement the 10k
  pull-up). This is to ensure the pin is held low when the On
  switch (SW1) is opened. It is used for detection the On switch
  is closed when programming ATF150X via JTAG.
2024-04-26 16:30:47 +01:00
ole00 998b1329c8 jtag: improve detection of power switch being turned off 2024-04-24 23:16:14 +01:00
ole00 a3e6f11b0f jtag: added debugging pp symbol to ignore failed matches
This can help printing data register when a mismatch
occurs.
2024-04-24 23:12:20 +01:00
ole00 7c0a32cba3 jtag: fix compilation warning 2024-04-24 23:09:01 +01:00
ole00 cf34c1dfee Sketch: support Arduino based on ESP32-S2 MCU
Because of the internal reference of 2.5V on Analogue pins
of ESP32-S2, the R6 must be modified from 20k Ohm to 16.67k Ohm.
This can be achieved by placing a new 100k Ohm resistor
in parallel with R6 resistor. Use this mod only when Arduino
is ESP32-S2 based.
2024-04-21 00:12:15 +01:00
ole00 babba5bca9 PC app: jtag: better handling of new line characters
Previously the new line characters were expected to be
part of the feed request message. This is not so on
ESP32 based Arduino library implementation. The new
line characters arrive later on separately after a small
pause This code change fixes 'corrupted feed request'
warning messages on such Arduino platforms.
2024-04-20 16:02:33 +01:00
ole00 afae1b2166 jtag: fuseconv.py: added support for ATF150XASV (3.3V)
The converted code ID check will match either AS or ASL or ASV
flavours of the ATF150X ICs.
2024-04-20 15:46:44 +01:00
ole00 9656fbadd3 xsvf: added support for ATF150XASV (3.3V)
The IDCODE check mask was updated to support:
ATF1502ASL: 0150203F or 0150303F
ATF1502ASV: 0151203F or 0151303F
ATF1504ASL: 0150403F or 0150503F
ATF1504ASV: 0151403F or 0151503F
The ID codes were found on https://bsdl.info
2024-04-20 15:31:53 +01:00
ole00 29df84d42e PC APP: released updated app with ATF150X support 2024-04-13 00:06:13 +01:00
ole00 e745ae9919 Image: added ATF150X adapter image 2024-04-13 00:02:44 +01:00
ole00 6230e7c55e PCB: updated ATF150X adapter
Previously the TDO pull-up was not properly connected
to VCC. The Rev.2 adapter can still be used, either
without the pull-up or with a small mod-wire to
connect the pull-up with the VCC."
2024-04-13 00:02:44 +01:00
ole00 18b3cea6a2 added JTAG utils for .jed to .xsvf conversion 2024-04-13 00:02:39 +01:00
ole00 7d20d778c6 jtag player: implement XWAITSTATE and XTRST
These new instructions are used by svf2xsvf OpenOCD tool.
2024-04-12 22:41:59 +01:00
ole00 fb22c14e9f jtag player: reduce serial buffer size to 62 bytes
Values bigger than 63 bytes seem to cause issue on AVR MCUs.
2024-04-12 22:41:49 +01:00
ole00 316d103456 PC app: JTAG player - decrease initial over-buffering
This change improves XSVF upload reliability.
2024-04-12 22:41:42 +01:00
ole00 cce891c513 PC app: play JTAG file: improved progress bar updates
When the chunk sizes were not power of 2 the progress bar
updates were irregular. This change ensures that any
upload progress is regularly updated."
2024-04-12 22:41:33 +01:00
ole00 ad9308e291 PC app: print file size of JED or XSVF in verbose mode 2024-04-12 22:41:19 +01:00
ole00 32468f6dd8 jtag: optimise memory usage 2024-04-09 21:07:33 +01:00
ole00 2b83233399 jtag: removed initialisation within the struct 2024-04-09 21:04:05 +01:00
ole00 56b687017a PC app: close serial port at the end of xsvf playback 2024-04-04 23:33:28 +01:00
ole00 c205579e9b Gerbers: added ATF150xAS and ATF150xASL adapter 2024-04-03 22:16:46 +01:00
ole00 7a3981a9b3 PCB: added ATF150xAS and ATF150xASL adapter 2024-04-03 22:16:46 +01:00
ole00 54600a153a XSVF: added xsvf files for ATF150X id and erase 2024-04-03 22:16:46 +01:00
ole00 a8ecb518cf PC APP: released updated app with ATF150X support 2024-04-03 22:16:46 +01:00
ole00 eac70f6c68 PC app: serial - add support for NO_CLOSE on Windows 2024-04-03 22:16:46 +01:00
ole00 0367a8b127 sketch: added JTAG XSVF player 2024-04-03 22:16:46 +01:00
ole00 718a908a9d sketch: improve pinmuxing when inactive
Previously unused pins were always set as output low.
Now, they are output low when active operation is in
progress, and they are set as input when inactive.
2024-04-03 22:16:46 +01:00
ole00 0e99f07370 PC app: added support for JTAG and ATF1502 and ATF1504 2024-04-03 22:16:46 +01:00
ole00 5621df2f1f PC app: format the list of GAL names
Up to 8 GAL names on the same line.
2024-04-03 22:16:46 +01:00
ole00 b733cd5e6a PC app: added progress bar update function 2024-04-03 22:16:46 +01:00
ole00 0761907067 PC App: fixed help text: serial port information 2024-04-03 21:43:32 +01:00
ole00 a3968f5c00 PC app release v.0.5.8 - updated 2024-03-23 16:20:30 +00:00
ole00 3b4921daa2 build script for OSX: compile with preprocessor _OSX_
The preprocessor symbol helps to distinguish the serial port
detection rule (OSX/Linux).
2024-03-23 16:16:46 +00:00
ole00 4238800190 PC App: guesses the serial port device name
When the serial device name is not passed with paramerer -d
then the PC app will try to find an existing serial device.

* On Windows: selects the COM device with the highest number.
* On Linux: selects the serial usb device in any order, but
       priority is given to devices with Arduino name.
* ON MAC OS: selects the last  /dev/tty.* or /dev/tty/wchusb*
       device in the order that OS lists them.

This serial port detection should work OK if you have only one
Arduino attached. If you have (and need to have) several
Arduinos or serial ports connected at the same time then pass
the '-d' parameter to specify which one to use.
2024-03-23 16:08:29 +00:00
ole00 e9aaf41c48 PC app release v.0.5.8 - updated 2024-03-23 11:04:36 +00:00
ole00 a48de726bf PC app: fix progress bar compatibility
The proress bar cusros hiding/revealing sequence was not
compatible with some Windows versions / shells.
This fix removes the escaped sequences and ensures the progress
bar is fully printed at the end of each iteration, which prevents
the visual artefacts caused by the jumping cursor.
2024-03-23 11:01:00 +00:00
ole00 1589119e7c sketch: setPinMux: unset internal pull-up on DOUT when inactive
See issue #61
2024-03-22 07:14:51 +00:00
ole00 37f93be2d3 PC app release v.0.5.8 - updated 2024-03-19 21:05:47 +00:00
ole00 757e13e55f build scripts: pass git commit hash as preprocessor symbol 2024-03-19 21:05:47 +00:00
ole00 52466f64c6 PC app: added git commit to version info 2024-03-19 21:05:47 +00:00
ole00 a9bf98b719 Sketch: reduced duration on VPP ramp-up
The VPP ramp-up was too long after the change of the calibration
scale in recent commits.
2024-03-19 21:05:32 +00:00
ole00 dabfcf5aad PC app: increase maximum time to wait for VPP measurement 2024-03-19 20:43:23 +00:00
ole00 79e33ab2a5 PC app: fix serial port read buffer overflow
Overflow happened when verbose printouts were enabled
in the Arduino sketch.
2024-03-19 20:41:43 +00:00
ole00 2754ff5c47 PC app: fixed typos in help text 2024-03-19 18:58:25 +00:00
ole00 49342b2174
README.md updated info about tap indices during calibration
The latest calibration indices are 2x higher than  in previous version.
2024-03-18 20:23:50 +00:00
ole00 e6d9a6ea2e
README.md: re-calibration needed for ver. 0.5.8 2024-03-17 21:32:57 +00:00
ole00 941142df71 PC app release v.0.5.8 2024-03-17 21:24:54 +00:00
ole00 733ea3c8e3 version increased to v.0.5.8 2024-03-17 21:15:05 +00:00
ole00 3ee0147d88 sketch: improved calibration, mcp4151 uses full tap scale
This change stores the digi pot indices in the scale 1-255
(previously 1 -128) so the stored values for calibration
are no longer valid after this change is uploaded to Arduino.

Please re-run the full calibration on your Afterburner!
2024-03-17 21:15:05 +00:00
ole00 103a99ab7a pc app: calibration takes longer time, wait longer
This is because the voltage settle time delay was increased
and also now 255 taps are scanned.
2024-03-17 21:15:05 +00:00
ole00 460d0f2caa improve calibration offset resolution and range
range: -0.32V to +0.32V
steps: by 0.01V
2024-03-17 21:14:52 +00:00
ole00 a559e10612 mt3608 wire: added resistance test indicators 2024-03-16 11:06:09 +00:00
ole00 e5ffe7ec19
README.md - added safety step when installing the pot wire 2024-03-16 11:01:23 +00:00
ole00 6b6bbd76e2
Update README.md - ATF750C is now supported
AFT750C support ported over from Michael D. afterburner's fork.
2024-02-16 20:06:48 +00:00
ole00 0b865d4892 PC app release v.0.5.7 2024-02-16 19:57:43 +00:00
ole00 cc64a29953 version increased to 0.5.7 2024-02-16 19:55:41 +00:00
ole00 7ef0038209 sketch: ensure sparse fusemap is enabled only for ATF750C 2024-02-16 19:44:52 +00:00
ole00 5f2105680f PC app: increase maximum wait time for operations
This fixes reading erased ATF750C when using sparse fusemap.
2024-02-16 19:44:52 +00:00
ole00 e99494df09 sketch: implemented read write verify for ATF750C
The implementation is based on Afterburner's fork by Michael D.
https://github.com/nospam2000/afterburner.git
2024-02-16 19:44:52 +00:00
ole00 053c914fae PC app: increase buffer sizes to fit ATF750C data 2024-02-16 19:44:52 +00:00
ole00 dc16f92a2e sparse fusemap: optimise access when block bits are all 1 2024-02-16 19:44:52 +00:00
ole00 7ff02e0a8d pc app: wait a bit longer when processing fuses 2024-02-16 19:44:52 +00:00
ole00 ba3caf3a15 sparse fusemap: added support for compacting
Compacting searches for fuse blocks where all 32 bits are set
to 1. If such block is found then it is removed from the
fuse array (reducing its top position) and then the fuse type
of such block is changed to 3.
Note that the fuse bit is never changed from 1 to 0, therefore
there is no need to cover these cases in the code (ie. converting
block type 3 to block type 1).

Compacting happens after every 256 fuse writes.

Compacting is required to be able to read erased GALs where
all fuse bits are initialised to 1.
2024-02-16 19:44:52 +00:00
ole00 f3147397f9 sparse fusemap: use 32 bit fuse blocks
Previously 16 bit fuse blocks were used and block types were stored in 1 bit.
That did not allow to compact fusemaps where all bits were 1 - which is
typically after the fuses are erased. So fuses of an erased chip would
not fit into the sparse memory array. The solution was to extend the fuse
block type storage from 1 bit to 2 bits and use that extra space to mark
blocks as sparse when all bits in the block are 1's. Such block is now
block type 3. To keep the fuseType array the same size as it was (128
bytes) the size of the block was extended from 16 bits to 32 bits.

The block type is now:
0 - all bits in the fuse block are 0, no block data in the fuse array
3 - all bits in the fuse block are 1, no block data in the fuse array
1 - any combination of bits in the fuse block, data are stored in the fuse array
2 - reserved for future use.
2024-02-16 19:44:52 +00:00
ole00 77a5d7d236 added support for sparse fusemap 2024-02-16 19:44:52 +00:00
ole00 b216b16c9a Initial support for ATF750C based on nospam2000 fork
Setup and PES reading works.

Based on fork: https://github.com/nospam2000/afterburner
By Michael D. - nospam2000
2024-02-16 19:44:48 +00:00
ole00 bd19c7c776 sketch: small fixes to setVpp() function
- ATF22V10C uses lower VPP when reading PES
- PES reading VPP for ATF was raised from 10V to 10.5V
- out-of-band VPP was set to actual 12V (previously 10V by mistake)
2024-02-16 19:26:17 +00:00
ole00 fab07e57fd Sketch: adjust VPP for all GALs
Previously all GALs had their VPP (programming voltage) reduced by 2V.
That was based on old Afterburner design which did not have variable VPP
control. It was trivial to adjust and experiment with VPP by turning the POT.
The software for the new design uses the VPP information encoded in PES
to set the VPP voltage (except Atmel GALs where the VPP value was hardcoded
to be effectively 10V) programmatically. The reduction of VPP by 2V on
the new design works, but is just on the very edge of correct functionality.
For example undervolting the 10V VPP to 9.87V made the Atmel chip programming
unreliable (many verification errors). This change reduces the voltage
by 1V for all GAL chips, so for example Atmel GALs are programmed by 11V VPP
(instead of 10V as it was before). It should improve writing reliability
for all GAL ICs.

I have re-tested all types of GAL IC I have available, all worked OK
with this change. That means all operation worked correctly: erase, info,
read, write and verify. The IC that were tested are as follows:

GAL6001B-30LP
GAL6002B-15LPN
GAL20V8B-15LPN
GAL26V12C-15LPN
GAL22V10B-25LP
GAL22V10D-10LPN
GAL18V10B-15LP
GAL18V10B-20LP
GAL16V8D-25LP
GAL16V8B-15LJI
ATF750C-7PX
ATF22V10C-15PU
ATF22V10CZQ-20PU
ATF16V8BQL-15PU
ATF16V8B-15PU
ATF16V8C-7PU
ATF16V8CZ-15SU
2024-02-05 23:15:12 +00:00
ole00 0cb6434aea
Updated mt3608 module modification to README.md 2024-02-04 12:30:12 +00:00
ole00 31578a6eec images: updated info about mt3608 modules 2024-02-04 12:11:51 +00:00
ole00 00dc5cf8f6 PC app releases v.0.5.6 2024-02-04 11:34:25 +00:00
ole00 28c4a104d1 version increased to 0.5.6 2024-02-04 11:26:07 +00:00
HubertH 8ae5b495f8 Fixed B9 command bug
Fixed: Command 'B9' (Calibration Offset = 0,25V) doesn't work
Note: Also requires elimination of a bug in the PC program afterburner.ino
Added: Sending B4, if b /wo -co is executed
2024-02-04 11:13:47 +00:00
HubertH fcd7b15fe4 Update aftb_mcp4131.h
Minor change
2024-02-04 08:56:39 +00:00
HubertH 0cd9ac9b35 Minor changes in aftb_vpp.h
Added a dummy conversation after setting the analog reference
Print out pot type (4131/4151) if VPP_VERBOSE is set
2024-02-04 08:53:50 +00:00
HubertH 19662a1939 Fixed B9 command bug
Fixed B9 command bug
Fixed a typo
2024-02-04 08:52:48 +00:00
ole00 5748ad601b updated BOM for PCB rev 3.1 2024-01-29 21:56:51 +00:00
ole00 62d66b216b
README.md: fixed reference to the compensation pot R9
Found by AJROMR. Thanks!
2024-01-28 21:29:17 +00:00
ole00 2b1b34f48c mcp4131: fixed reading pot value from mcp4151
Discovered and fixed by Hubert H. Thanks!
See issue #48
2024-01-28 21:07:49 +00:00
ole00 de1d601992 PC app releases v 0.5.5 2023-11-22 21:42:18 +00:00
ole00 24494accde version increased to 0.5.5 2023-11-22 21:39:05 +00:00
ole00 cefd435b28
Merge pull request #45 from rhgndf/20ra10
Add 20RA10 support
2023-11-22 21:24:38 +00:00
rhgndf ec1e26464f GAL20RA10B works 2023-11-22 21:59:47 +08:00
rhgndf 50bc140928 Transpose table to fit better 2023-11-22 14:36:01 +08:00
rhgndf c7f063b2a9 Revamp GAL support list 2023-11-22 12:32:06 +08:00
rhgndf 5c2cd50d47 Support GAL20RA10 2023-11-22 12:30:52 +08:00
rhgndf 79594dc863 Use library functions for memcpy from flash 2023-11-22 11:45:17 +08:00
41 changed files with 8277 additions and 695 deletions

129
README.md
View File

@ -14,24 +14,56 @@ from various manufcaturers. It is based on work of several other people:
Yorck Thiele:
https://www.ythiee.com/2021/06/06/galmate-hardware/
Michael Dreher:
https://github.com/nospam2000/afterburner.git
Marcelo Roberto Jimenez: (JTAG player)
https://github.com/mrjimenez/JTAG
whitequark · it: (ATF150X jed to svf tool)
https://github.com/whitequark/prjbureau
OpenOCD: (svf to xsvf tool)
https://github.com/arduino/OpenOCD/blob/master/contrib/xsvf_tools/svf2xsvf.py
who did the most of the hard work of deciphering and publishing the programming
protocol of these chips. Some of their early programs were Windows based and relied on
presence of parallel port (LPT). Afterburner was written for Linux OS
(also works on Win32/64, Mac OSX64), and requires serial connection to
Arduino UNO, which does the programming of the GAL chip.
Supported GAL chips:
**Update: ver.0.6.0 added experimental support for ATF1502AS and ATF1504AS. Only
identify, erase and write commands are supported. Read function is unsupported.
Verification is usually done automatically - it is a part of the .xsvf JTAG file
which is used when writing the design. See Discussions for more information**
* Atmel ATF16V8B, ATF16V8BQL, ATF16V8C, ATF20V8B, ATF22V10B, ATF22V10C, ATF22V10CQZ
* Lattice GAL16V8A, GAL16V8B, GAL16V8D
* Lattice GAL18V10B (requires PCB v.3.1 or modified PCB v.3.0 - see Troubleshooting )
* Lattice GAL22V10B, GAL22V10D
* National GAL16V8
* ST Microsystems GAL16V8
* Lattice GAL20V8B (no adapter needed)
* Lattice GAL20XV10B
* Lattice GAL6001B, GAL6002B
* Lattice GAL26CV12B, GAL26V12C (requires adapter - see gerbers, pcb and img directory)
**Update: ver.0.5.8 improved calibration alogrithm and resolution for mcp4151 digi pot.
Please re-calibrate your Afterburner as the previsouly stored calibration data are invalid.**
Supported GAL chips:
---------------------
| | Atmel | Lattice | National | ST |
| --- | --- | --- | --- | --- |
| 16V8 | ATF16V8B, ATF16V8BQL, ATF16V8C | GAL16V8A, GAL16V8B, GAL16V8D | GAL16V8 | GAL16V8 |
| 18V10 | - | GAL18V10, GAL18V10B[1] | - | - |
| 20V8 | ATF20V8B | GAL20V8B | GAL20V8 | - |
| 20RA10 | - | GAL20RA10, GAL20RA10B | - | - |
| 20XV10 | - | GAL20XV10B | - | - |
| 22V10 | ATF22V10B, ATF22V10C, ATF22V10CQZ | GAL22V10B, GAL22V10D | - | - |
| 6001 | - | GAL6001B | - | - |
| 6002 | - | GAL6002B | - | - |
| 26CV12 | - | GAL26CV12B[2] | - | - |
| 26V12 | - | GAL26V12C[2] | - | - |
| 750 | ATF750C | - | - | - |
| 150X | ATF1502AS, ATF1502ASL, ATF1504AS, ATF1504ASL[2][3] | - | - | - |
[1]: requires PCB v.3.1 or modified PCB v.3.0 - see Troubleshooting
[2]: requires adapter - see gerbers, pcb and img directory
[3]: also supports 3.3V ATF1502ASV and ATF1504ASV when Arduino IOREF is 3.3V (ARM or ESP32 based Arduinos or Arduinos with IOREF 3.3V switch)
[-]: - represents either this combination does not exist or hasn't been tested yet. Testers are welcome to report their findings.
**This is a new Afterburner design with variable programming voltage control and with single ZIF socket for 20 and 24 pin GAL chips.**
The PC software is backward compatible with the older Afterburner desgin/boards.
@ -64,8 +96,11 @@ Setup:
./compile.sh to do that. Alternatively use the precompiled binaries in the 'releases' directory.
* Calibrate the variable voltage. This needs to be done only once, before you start using Afterburner for programming GAL chips.
Calibration procedure differs a little bit when using MT3608 module or when using on board voltage booster.
* **Calibration step 1)** Turn the small potentiometer (R8) on the Afterburner to the middle position. This pot acts as compensation resistor for the digital pot.
**When using MT3608 module**
* **Calibration step 1)** Turn the small potentiometer (R9) on the Afterburner to the middle position. This pot acts as compensation resistor for the digital pot.
* **Calibration step 2)** Set the programming voltage (VPP) to 16.5V: Check the programming voltage (VPP) without the GAL chip being inserted / connected to Afterburner. Test the voltage on MT3608 module VOUT- and VOUT+ pins while running the following command:
<pre>
@ -78,8 +113,8 @@ Setup:
<pre>
./afterburner b
</pre>
You will see several messages on the console. Check the one with '*Index for VPP 900 is'. This is the lowest supported VPP of 9V and the index should ideally be between 15 and 35.
If you see a different index value (lower or higher) move the Afterburner's compensation pot (R8) either a bit lower or higher (depending on the VPP 900 index value) and go back to Calibration step 2). Repeat the Calibration steps 2) and 3) until you find the good value on VPP 900 index. If everything goes OK the last VPP index (VPP 1650) should be 128.
You will see several messages on the console. Check the one with '*Index for VPP 900 is'. This is the lowest supported VPP of 9V and the index should ideally be between 30 and 70, but if it a bit less or a bit higher (let's say from 20 to 90) the calibration should still be valid.
If you see a significantly different index value (lower or higher) move the Afterburner's compensation pot (R9) either a bit lower or higher (depending on the VPP 900 index value) and go back to Calibration step 2). Repeat the Calibration steps 2) and 3) until you find the good value on VPP 900 index. If everything goes OK the last VPP index (VPP 1650) should be 255. If it is not exactly 255, but at least above 250 then it is fine.
* **Calibration step 4)** Measure the actual VPP to verify the value read by Arduino is correct. Run the following command while measuring the VPP on your multimeter:
<pre>
@ -89,11 +124,22 @@ Setup:
<pre>
./afterburner b -co X
</pre>
Where X is a number from -20 (representing -0.2V offset) to value 25 (representing +0.25V offset). If your multimeter reads 12.1V and the reading on the text console shows 12.00V you need to set positive offset of +0.1V ('-co 10'). If your multimeter reads 11.85V and the reading on the text console shows 12.05V you need to set negative offset of -0.2V ('-co -20'). After setting the calibration offset, the readings on your multimeter should read the same values as the text on the console (+/- 0.05V). The calibration is then done. If (when specifying negative offset value) the calibration fails, turn the MT3608 Pot about 10-15 degrees counter-clockwise (to rise the VPP a tiny bit) and re-do the Calibration step 4.
Where X is a number from -32 (representing -0.32V offset) to value 32 (representing +0.32V offset). If your multimeter reads 12.1V and the reading on the text console shows 12.00V you need to set positive offset of +0.1V ('-co 10'). If your multimeter reads 11.85V and the reading on the text console shows 12.05V you need to set negative offset of -0.2V ('-co -20'). After setting the calibration offset, the readings on your multimeter should ideally read the same values as the text on the console (+/- 0.05V). The calibration is then done. If (when specifying negative offset value) the calibration fails, turn the MT3608 Pot about 10-15 degrees counter-clockwise (to rise the VPP a tiny bit) and re-do the Calibration step 3.
* Note that if you use your calibrated Afterburner board with a different Arduino (made by a different company or slightly different design), you may need to re-do the calibration.
**When using on-board voltage booster:**
Calibration steps are the same as for MT3608 module with these differences
* **Calibration Step 2)** We can't turn the extra pot on the MT3608 module, but we can
adjust the voltage by turning the R9 pot on the Afterburner PCB. It's OK to have
the voltage a bit higher like 16.6V or so in this calibration step.
* **Calibration Step 3)** Because of the feedback resistance difference compared to MT3608
module the calibration index for 9V will be around value 150.
**GAL chip operations:**
* With the GAL chip inserted and power button pressed (or in ON position) check the chip identification by running the following command:
<pre>
./afterburner i -t [GAL_type]
@ -164,7 +210,7 @@ PCB:
The new design no longer has an etched PCB design available. The most convenient way to get the PCB is to order it online on jlcpcb.com, pcbway.com, allpcb.com or other online services. Use the zip archive stored in the gerbers directory and upload it to the manufacturer's site of your choice.
Upload the afterburner_fab_3_0.zip and set the following parameters (if required).
* Dimensions are 79x54 mm
* Dimensions are 85x54 mm
* 2 layer board
* PCB Thickness: 1.6, or 1.2
* Copper Weight: 1
@ -178,17 +224,41 @@ Soldering steps:
* start with the smallest parts, solder the resistors and small capacitors.
* solder the two ICS: U1 (digital pot) and U2 (shift register)
* solder the LED, the switch and the big capacitors
**When using MT3608 module**
* do **not** solder any components in voltage booster area
* **special step** solder a thin wire between the MT3608 module and the PCB hole marked as POT. See the image bellow.
* **Important** after you solder the wire measure the resistance between the wire's connection on Afterburner PCB and ground (use TP GND hole) - see red dots
on the picture beelow. It must be around 12.5 kilo Ohm. If it is very low value then you made a short while soldering the wire. Fix/remove the short
or else the MT3608 will be damaged when it is turned on on.
![POT wire image.](https://github.com/ole00/afterburner/raw/master/img/mt3608_wire.jpg "POT wire")
* solder the MT3608 module - the POT connection wire must be already soldered!
**When using on-board voltage booster**
* solder the parts in the voltage booster area. Start with the U3 IC - the SOT-23-6 package - so that you have plenty
of room for soldering the small IC. Use a flux to ensure the solder melts nicely on the pads.
* solder the inductor L1 and diode D2. Ensure D2 polarity is correct - the stripe is on the left side (see photos if unsure).
* solder the C10 cap and the resistors. There is no need to solder R10, R11, R12 if you are using the alternative booster IC
with SOT-23-5 package (without OVP)
* no extra wire is required (the wire is only required when using MT3608 module)
**After soldering the voltage booster**
* calibrate the board. See calibration steps in the Setup section.
* solder the ZIF socket
![POT wire image.](https://github.com/ole00/afterburner/raw/master/img/mt3608_wire.jpg "POT wire")
MT3608 modules:
---------------
* On PCB version 3.2 (or higher) the MT3608 can be replaced by discrete parts soldered on the board. Therefore, if you want to avoid possible issues with
MT3608 modules, solder the discreate parts instead of the module. If you are not comfortable soldering SOT-23-6 SMT IC package, then use the MT3608 module.
* There is a report some of the MT3608 modules / clones are not compatible with Afterburner. Thanks @meup for the information.
* The incompatible MT3608 'clone' causes calibration issue and basically breaks the variable voltage functionality.
* Bellow is the image of the good and bad MT3608 modules. If you happen to have the incompatible module, see issue #31 for possible solution.
* The incompatible MT3608 'clone' causes calibration issue and basically breaks the variable voltage functionality. This can be fixed by replacing the 2k2 resistor located on the module by a 15k resistor.
* Bellow is the image of the old (compatible without a mod) and new (require the resistor mod) MT3608 modules. If you happen to have the incompatible module here are the steps to replace the resistor:
- heat up a soldering iron to ~350 deg C and put a medium sized solder blob on its tip
- touch the tip of the iron in the middle of the resistor for a second
- gently press on the resistor and slide it off the PCB.
- the resistor footprint allows to solder new SMT or through hole resistor
![mt3608_modules](https://github.com/ole00/afterburner/raw/master/img/mt3608_modules.jpg "mt3608_modules")
Troubleshooting:
@ -281,6 +351,14 @@ Troubleshooting:
- I have the older Afterburner PCB design, can I use the new PC software and Arduino sketch?
* Yes, both programs are compatible with the old Afterburner boards (1.X and 2.X).
- how do I program ATF150X ICs? They do not fit into the ZIF Socket.
There are 2 options. Either use PLCC44 IC package along with the ZIF socket adapter (see the gerber and
pcb directory). Or you can program the ATF150X on your target board when using JTAG interface.
See this schematics for information about JTAPG pins on ATF150X ICs:
http://matthieu.benoit.free.fr/all03/adp/HiLo_ADP-ATF1504.PDF
The OGI pin in the schematic is VPP (or EDIT) pin on afterburner. See afterburner schematics
how to connect JTAG pins from the ZIF socket into the JTAG pins on your board.
- what are the .jed files and how to produce them
* Use WinCUPL software from Atmel. Works under Wine as well.
@ -290,6 +368,17 @@ Troubleshooting:
* WinCUPL User's manual: http://ww1.microchip.com/downloads/en/DeviceDoc/doc0737.pdf
* Try GAL Asm to produce .jed files - see link bellow.
- can I use .jed files with ATF150X IC?
* Not directly. You have to convert the .jed file into .xsvf format. Use the python tools located in the utils/jtag
subdirectory to do that. See readme.txt in that directory for more info. Once you convert the .jed to .xsvf
you can use it with afterburner like that:
<pre>
./afterburner -t ATF1502AS -f mydesign.xsvf ew
</pre>
which will erase the chip and then write your design into the IC.
See discussion #64 (ATF1502AS(L) and ATF1504AS(L) support) for more inofrmation.
Other GAL related links:
------------------------

View File

@ -1,5 +1,10 @@
//MCP4131 digital pot (bitbanged control) for Afterburner GAL project.
// * compatible with MCP4151 (resolution of the wiper is halved to match MCP4131)
//MCP4151 digital pot (bitbanged control) for Afterburner GAL project.
// * compatible with MCP4131 (resolution of the wiper is divided by 2)
// * the storage for tap indices is 8bit wide, therefore we must not use index 256.
// 2024-02-02 Minor change
#ifndef __AFTB_MCP4131_H__
#define __AFTB_MCP4131_H__
//set default pins
#ifndef POT_CS
@ -36,7 +41,7 @@
#define mcp4131_read(A) mcp4131_reg((A),0,1)
#define mcp4131_write(A,V) mcp4131_reg((A),(V),0)
static uint8_t mcp4151_detected;
static uint8_t mcp4131_detected;
// read or write the mcp4131 register
static uint16_t mcp4131_reg(uint8_t address, uint16_t value, uint8_t read_reg) {
@ -45,8 +50,9 @@ static uint16_t mcp4131_reg(uint8_t address, uint16_t value, uint8_t read_reg)
r <<= 12;
if (mcp4151_detected && address == ADDR_WIPER) {
value <<= 1; //multiply the wiper value by 2
if (mcp4131_detected && address == ADDR_WIPER) {
value >>= 1; //divide the wiper value by 2
value++; // ensure we use the last tap (index 128)
}
if (address == ADDR_INCREMENT) {
@ -91,8 +97,9 @@ static uint16_t mcp4131_reg(uint8_t address, uint16_t value, uint8_t read_reg)
pinMode(POT_DAT, OUTPUT);
}
if (mcp4151_detected && address == ADDR_WIPER && read_reg) {
value >>= 1; //divide the wiper value by 2
if (mcp4131_detected && address == ADDR_WIPER && read_reg) {
r--;
r >>= 1; //multiply the wiper value by 2
}
//disable IC
@ -113,7 +120,7 @@ static void mcp4131_init(void) {
static uint8_t mcp4131_detect(void) {
uint16_t r;
mcp4151_detected = 0;
mcp4131_detected = 0;
mcp4131_disableWiper();
@ -139,16 +146,16 @@ static uint8_t mcp4131_detect(void) {
//detect MCP4151 by incrementing again - MCP4131 clamps the value to 128, MCP4151 increments to 129
mcp4131_write(ADDR_INCREMENT, 0);
r = mcp4131_read(ADDR_WIPER);
#if 0
Serial.print(F("MCP41541 detect: "));
#if 0
Serial.print(F("MCP4151 detect: "));
Serial.println(r == 129 ? 1 : 0, DEC);
#endif
if (r == 129) {
mcp4151_detected = 1;
if (r == 128) {
mcp4131_detected = 1;
} else
if (r != 128) {
return 0; //error - the value should be clamped to 128
}
if (r != 129) {
return 0; //error - the value should be either 128 (clamped by mcp4313) or 129 (mcp5151)
}
mcp4131_write(ADDR_WIPER, POT_DEFAULT_VALUE);
#if POT_WIPER_ENABLED
@ -156,4 +163,4 @@ static uint8_t mcp4131_detect(void) {
#endif
return 1;
}
#endif

195
aftb_seram.h Normal file
View File

@ -0,0 +1,195 @@
#ifndef _AFTB_SERAM_
#define _AFTB_SERAM_
/* Serial RAM functions for Afterburner GAL project.
Uses 23LC512 or 23LC1024 RAM IC
3 devices are connected to the serial bus: digi-pot, shit register and this serial RAM.
Digi pot and shift register have their own dedicated CS pins. Serial RAM CS is active (low)
when no other device is selected. Therefore, the serial RAM is always selected unless any other
device is explicitely selected (in that case serial RAM is de-selected by onboard HW)
Reading or Writing of 1 byte takes ~ 620 uSec for 24bit addressing and ~ 500 uSec for 16bit addressing
*/
//set default pins
#ifndef SHR_CS
#define SHR_CS A2
#endif
#ifndef RAM_CLK
#define RAM_CLK A4
#endif
#ifndef RAM_DAT
#define RAM_DAT A5
#endif
#define CS_DELAY_US 16
#define OPCODE_WRITE 2
#define OPCODE_READ 3
#define OPCODE_RDMR 5
#define OPCODE_WRMR 1
#ifndef RAM_BIG
#define seRamInit() 0
#else /* RAM_BIG */
uint8_t ramAddrBits24 = 0;
static void seRamWriteData(uint16_t data, uint8_t bitLen ) {
uint16_t mask = (1 << (bitLen-1));
while (bitLen) {
bitLen--;
//set data bit
digitalWrite(RAM_DAT, (data & mask) ? 1 : 0 );
//raise the clock
digitalWrite(RAM_CLK, 1);
//do some operation
data <<= 1;
//lower the clock
digitalWrite(RAM_CLK, 0);
}
}
static uint8_t seRamReadData(void) {
uint8_t bitLen = 8;
uint8_t result = 0;
while (bitLen) {
result <<= 1;
//raise the clock
digitalWrite(RAM_CLK, 1);
//set data bit
result |= digitalRead(RAM_DAT);
//do some operation
bitLen--;
//lower the clock
digitalWrite(RAM_CLK, 0);
}
return result;
}
static void seRamWrite(uint16_t addr, uint8_t data ) {
//ensure clock is low
digitalWrite(RAM_CLK, 0);
// toggle the SHR CS to reset the bus for serial RAM
digitalWrite(SHR_CS, 0);
delayMicroseconds(CS_DELAY_US);
digitalWrite(SHR_CS, 1);
seRamWriteData(OPCODE_WRITE, 8); // 8 bits of WRITE opcode
if (ramAddrBits24) {
seRamWriteData(0, 8); // top 8 bit of address are 0
}
seRamWriteData(addr, 16); // 16 bits of address
seRamWriteData(data, 8); // 8 bits of actual data
}
static uint8_t seRamRead(uint16_t addr) {
uint8_t data;
//ensure clock is low
digitalWrite(RAM_CLK, 0);
// toggle the SHR CS to reset the bus for serial RAM
digitalWrite(SHR_CS, 0);
delayMicroseconds(CS_DELAY_US);
digitalWrite(SHR_CS, 1);
seRamWriteData(OPCODE_READ, 8); // 8 bits of READ opcode
if (ramAddrBits24) {
seRamWriteData(0, 8); // top 8 bit of address are 0
}
seRamWriteData(addr, 16); // 16 bits of address
pinMode(RAM_DAT, INPUT);
data = seRamReadData();
pinMode(RAM_DAT, OUTPUT);
return data;
}
static void seRamSetupMode(void) {
uint8_t data;
//ensure clock is low
digitalWrite(RAM_CLK, 0);
// toggle the SHR CS to reset the bus for serial RAM
digitalWrite(SHR_CS, 0);
delayMicroseconds(CS_DELAY_US);
digitalWrite(SHR_CS, 1);
seRamWriteData(OPCODE_RDMR, 8); // 8 bits of Read Mode register
pinMode(RAM_DAT, INPUT);
data = seRamReadData();
pinMode(RAM_DAT, OUTPUT);
#if 0
Serial.print(F("RAM mode:"));
Serial.println(data, DEC);
#endif
if (data == 0) {
return;
}
//switch to byte mode
// toggle the SHR CS to reset the bus for serial RAM
digitalWrite(SHR_CS, 0);
delayMicroseconds(CS_DELAY_US);
digitalWrite(SHR_CS, 1);
seRamWriteData(OPCODE_WRMR, 8); // 8 bits of Read Mode register
seRamWriteData(0, 8); //write mode 0
}
static uint8_t seRamInit(void) {
uint8_t r;
#if 0
pinMode(SHR_CS, OUTPUT);
pinMode(RAM_CLK, OUTPUT);
pinMode(RAM_DAT, OUTPUT);
#endif
seRamSetupMode();
//try 16bit addressing mode (64kb RAM)
ramAddrBits24 = 0;
// detect SRAM presence by writing and reading data
seRamWrite(0, 0x5A);
r = seRamRead(0);
#if 0
Serial.print("r:");
Serial.println(r, DEC);
#endif
if (r != 0x5A) {
// try 24 bit addressing mode (128kb RAM)
ramAddrBits24 = 1;
seRamWrite(0, 0x5A);
r = seRamRead(0);
if (r != 0x5A) {
return 0;
}
}
seRamWrite(0xFFFF, 0xA5);
r = seRamRead(0xFFFF);
if (r != 0xA5) {
return 0;
}
//verify the data at address 0 still exists
r = seRamRead(0);
return (r == 0x5A) ? (ramAddrBits24 + 1) : 0;
}
#endif /* RAM_BIG */
#endif /*_AFTB_SERAM_*/

266
aftb_sparse.h Normal file
View File

@ -0,0 +1,266 @@
/*
* Sparse fusemap functions for Afterburner GAL project.
*
* The idea of sparse fuse map is to store non-zero fuse bits only.
* The reason is to fit big fusemaps into a size-limited SRAM.
* The fusemap is divided into groups of 16 bits and each group
* has a 'type' bit stored in fuseTypes array. Group type 0 has
* all 16 fuesmap bits 0 and is not stored in fusemap array.
* Group type 1 has at least 1 fusemap bit set and is stored in
* fusemap array. Position of group type '1' in the fusemap array
* may vary based on which fuses are written.
*
* Sparse fusemap supports:
* - random reads and writes
* - a simple cache to speed up index look-ups.
*/
#ifdef USE_SPARSE_FUSEMAP
// compacting statistics - disabled by default
#define COMPACT_STAT 0
#define SPFUSES 128
unsigned char fuseType[SPFUSES]; //sparse fuses index
uint16_t sparseFusemapStat = 0; //bit 15: use sparse fusemaps, bits 0-11 : sparse fusemap size in bytes
uint8_t sparseCompactCounter = 0;
uint16_t sparseCacheBitPos = 0;
uint16_t sparseCacheOffset = 0;
uint16_t sparseCacheHit = 0;
uint8_t sparseCacheIndex = 0;
#if COMPACT_STAT
uint8_t sparseCompactRun = 0;
uint8_t sparseCompactAct = 0;
#endif
// reverse search of the fuse group index based on the byte position in the sparse array
// returns the group index
static uint16_t getFuseGroupIndex(uint16_t fuseOffsetBytePos) {
uint16_t groupPos = 0;
uint16_t i = 0;
uint16_t fuseOffset = 0;
//calculate fusemap offset
while (1) {
uint8_t rec = fuseType[i];
// speed optimised special case: all 8 bits are 0 (4 * 32 bits)
if (rec == 0) {
groupPos += 4; //4 groups in the byte
} else {
uint8_t j = 0;
//4 types per byte
while (j < 4) {
if ((rec & 0b11) == 1) { // type 0 & 3 - no byte stored in fusemap
if (fuseOffset == fuseOffsetBytePos) {
return groupPos;
}
fuseOffset += 4; // 32 bits in the group, fuse byte offset advances by 4 bytes
}
groupPos++; //check next group
rec >>= 2;
j++;
}
}
i++; //next byte from the fuseTypes
}
}
// get position of the fuse bit in the sparse array
static uint16_t getFusePositionAndType(uint16_t bitPos) {
uint16_t counter = 0;
uint8_t i = 0;
uint8_t type;
uint16_t fuseOffset = (bitPos & 0b11000) >> 3; //set odd / even byte of the fuse offset
if (bitPos <= sparseCacheBitPos) {
sparseCacheBitPos = 0;
sparseCacheOffset = 0;
sparseCacheIndex = 0;
} else {
counter = sparseCacheBitPos;
fuseOffset += sparseCacheOffset & 0xFFC;
i = sparseCacheIndex;
sparseCacheHit++;
}
//calculate fusemap offset
while (1) {
uint8_t rec = fuseType[i];
// speed optimised special case: all 8 bits are 0 or all are 1 (4 * 32 bits)
if (rec == 0 || rec == 0xFF) {
counter += 128;
if (counter > bitPos) {
return (fuseOffset << 2) | (rec & 0b11); // type is 0 or 3
}
sparseCacheBitPos = counter;
sparseCacheOffset = fuseOffset;
sparseCacheIndex = i + 1;
}
else {
uint8_t j = 0;
//4 fuse types per byte
while (j < 4) {
counter += 32;
type = rec & 0b11;
if (counter > bitPos) {
return (fuseOffset << 2) | type;
}
if (type == 1) { // type 0 & 3 - no byte stored in fusemap
fuseOffset += 4;
}
rec >>= 2;
j++;
}
}
i++; //next byte from the fuseTypes
}
}
static void insertFuseGroup(uint16_t dataPos, uint16_t bitPos) {
int16_t i = bitPos >> 5; //group index
uint16_t totalFuseBytes = sparseFusemapStat & 0x7FF; // max is 2048 bytes
fuseType[i >> 2] |= (1 << ((i & 0b11) << 1)); // set type 1 at the fuse group record
//shift all data in the fuse map starting at data pos by 4 bytes (32 bits)
if (dataPos < totalFuseBytes) {
for (i = totalFuseBytes - 1; i >= dataPos; i--) {
fusemap[i + 4] = fusemap[i];
}
}
sparseFusemapStat = totalFuseBytes + 4; // we can ignore the sparse bit
//clean the emptied fusemap data
fusemap[dataPos++] = 0;
fusemap[dataPos++] = 0;
fusemap[dataPos++] = 0;
fusemap[dataPos] = 0;
}
static void sparseCompactFuseMap(void) {
uint16_t i, j;
uint16_t total = (sparseFusemapStat & 0x7FF) >> 2; // max is 2048 bytes, and convert to the group index
uint32_t* fuses = (uint32_t*) fusemap;
if (total < 2) {
return;
}
#if COMPACT_STAT
sparseCompactRun++; //statistics
#endif
i = total - 1;
while(i) {
// remove 4 fusemap bytes at a position when the bits are all 1's
if (fuses[i] == 0xFFFFFFFF) {
if (i < total - 1) {
uint16_t fuseGroup = getFuseGroupIndex(i << 2); //ensure the int32 index is converted to int8/byte index
#if COMPACT_STAT
sparseCompactAct++; //statistics
#endif
/// shift the fuses by 4 bytes to the left
for (j = i; j < total - 1 ; j++) {
fuses[j] = fuses[j + 1];
}
total--;
fuseType[fuseGroup >> 2] |= (3 << ((fuseGroup & 0b11) << 1)); //set type 3 at the fuse group record
sparseFusemapStat -= 4; // fuse map total size reduced by 4 bytes
}
}
i--;
}
//destory cache
sparseCacheBitPos = 0;
sparseCacheOffset = 0;
sparseCacheIndex = 0;
#if COMPACT_STAT
Serial.print(F("sp comp:"));
Serial.print(sparseCompactRun, DEC);
Serial.print(F(" total:"));
Serial.println(sparseFusemapStat & 0x7FF, DEC);
#endif
}
static inline uint16_t sparseSetFuseBit(uint16_t bitPos) {
uint8_t type;
uint16_t pos;
//try to reduce the size of the fuse map by finding and removing blocks with all 1's
sparseCompactCounter ++;
if (sparseCompactCounter == 255) {
sparseCompactFuseMap();
}
pos = getFusePositionAndType(bitPos);
type = pos & 0b11;
pos >>= 2; //trim the type to get the byte position in fuse map
if (type == 0) { //we need to write the bit into a group that has all bits 0 so far
insertFuseGroup(pos & 0x7FC, bitPos);
}
return pos;
}
static inline uint16_t sparseGetFuseBit(uint16_t bitPos) {
uint8_t type;
uint16_t pos = getFusePositionAndType(bitPos);
type = pos & 0b11;
if (!type) { //type is 0 - the block contains all zero bits
return 0xFF00;
}
if (type == 3) { // type is 3 - the block contains all 1 bits
return 0xFF01;
}
pos >>= 2; //trim the type to get byte position in fuse map
return pos;
}
static void sparsePrintStat() {
Serial.print(F("sp bytes="));
Serial.println(sparseFusemapStat & 0x7FF, DEC);
Serial.print(F("c_hit="));
Serial.println(sparseCacheHit, DEC);
#if COMPACT_STAT
Serial.print(F("compact run="));
Serial.print(sparseCompactRun, DEC);
Serial.print(F(" cnt="));
Serial.println(sparseCompactAct, DEC);
#endif
}
static void sparseInit(char clearArray) {
if (clearArray) {
uint8_t i;
for (i = 0; i < SPFUSES; i++) {
fuseType[i] = 0;
}
}
sparseFusemapStat = (1 << 15);
sparseCompactCounter = 0;
sparseCacheBitPos = 0;
sparseCacheOffset = 0;
sparseCacheIndex = 0;
#if COMPACT_STAT
sparseCompactRun = 0;
sparseCompactAct = 0;
#endif
}
static inline void sparseDisable(void) {
sparseFusemapStat = 0;
}
#else /* ! USE_SPARSE_FUSEMAP */
#define sparseDisable()
#define sparseInit(X)
#define sparseGetFuseBit(X) 0
#define sparseSetFuseBit(X) 0
#define sparsePrintStat()
#define sparseFusemapStat 0
#endif

View File

@ -1,7 +1,10 @@
/*
* Variable voltage functions for Afterburner GAL project.
*
* 2024-02-02 Minor changes in varVppInit()
*/
#ifndef __AFTB_VPP_H__
#define __AFTB_VPP_H__
#include <EEPROM.h>
@ -11,6 +14,20 @@
#define POT_DAT A5
#define VPP A0
#if CONFIG_IDF_TARGET_ESP32S2 == 1
// ESP32-S2
#include "driver/adc.h"
#define ADC_PIN ADC2_CHANNEL_3
#define EEPROM_BEGIN() EEPROM.begin(128)
#define EEPROM_UPDATE(A,V) if ((V) != EEPROM.read((A))) EEPROM.write((A),(V))
#define EEPROM_END() EEPROM.end()
#else
// AVR
#define EEPROM_BEGIN()
#define EEPROM_UPDATE(A,V) EEPROM.update((A),(V))
#define EEPROM_END()
#endif
#include "aftb_mcp4131.h"
#ifndef FAIL
#define FAIL 0
@ -54,12 +71,15 @@
//pot wiper indices for the voltages
uint8_t vppWiper[MAX_WIPER] = {0};
// VPP must ramp-up to prevent voltage spikes and possibly resting arduino
#define varVppSetMax() varVppSetVppIndex(0x40); \
varVppSetVppIndex(0x70); \
varVppSetVppIndex(0x7c); \
varVppSetVppIndex(0x7e); \
varVppSetVppIndex(0x80);
// VPP must ramp-up to prevent voltage spikes and possibly resetting arduino
// These values are for mcp4151 (for mcp4131 they are divided by 2)
#define varVppSetMax() varVppSetVppIndex(0x80); \
varVppSetVppIndex(0xE0); \
varVppSetVppIndex(0xF4); \
varVppSetVppIndex(0xFA); \
varVppSetVppIndex(0xFF);
#define varVppSetMin() varVppSetVppIndex(0x0);
uint8_t wiperStat = 0; //enabled / disabled
@ -67,22 +87,25 @@ int8_t calOffset = 0; // VPP calibration offset: value 10 is 0.1V, value -10 is
static void varVppReadCalib(void) {
uint8_t i;
EEPROM_BEGIN();
//calibration not found
if (EEPROM.read(0) != 0xAF || EEPROM.read(1) != 0xCA) {
vppWiper[0] = 0;
Serial.println(F("No calibration data in EEPROM"));
EEPROM_END();
return;
}
calOffset = (int8_t) EEPROM.read(2);
for (i = 0; i < MAX_WIPER; i++) {
vppWiper[i] = EEPROM.read(i + 3);
#if 0
#if 0
Serial.print(F("Calib "));
Serial.print(i);
Serial.print(F(":"));
Serial.println(vppWiper[i]);
#endif
#endif
}
EEPROM_END();
}
// internal use only - set the wiper value on the digital pot
@ -116,31 +139,25 @@ static void varVppSetVppIndex(uint8_t value) {
static void varVppSet(uint8_t value) {
uint8_t v;
int8_t inc;
int8_t incMin;
if (value == VPP_5V0 || value >= MAX_WIPER) {
varVppSetVppIndex(0);
return;
}
#if VPP_VERBOSE
#if VPP_VERBOSE
Serial.print(F("varSetVpp "));
Serial.print(value);
Serial.print(F(":"));
Serial.println(vppWiper[value]);
#endif
//ramp up to prevent massive voltage overshoots
v = vppWiper[value] / 2;
v -= 2;
inc = 16;
incMin = 2;
if (value > VPP_13V0) {
incMin = 1;
}
v = vppWiper[value] / 3;
inc = v >> 2;
while (v < vppWiper[value]) {
varVppSetVppIndex(v);
v+= inc + (inc / 2);
inc -= inc / 2;
if (inc < incMin) {
inc = incMin;
v+= (inc << 1);
inc -= (inc >> 1);
if (inc < 2) {
inc = 2;
}
}
varVppSetVppIndex(vppWiper[value]);
@ -154,6 +171,13 @@ static void varVppSet(uint8_t value) {
// SAMPLE_SHIFT moves the ADC gain error up/down
#define SAMPLE_SHIFT -45;
// ESP32-S2 (VREF 2.5V)
#elif CONFIG_IDF_TARGET_ESP32S2 == 1
#define SAMPLE_CNT 18
#define SAMPLE_DIVIDER 10
#define SAMPLE_MULTIPLIER 1
#define SAMPLE_OFFSET 5
//AVR based Arduinos (no ADC gain errors measured)
#else
#define SAMPLE_CNT 14
@ -206,7 +230,7 @@ static uint8_t varVppCalibrateVpp(void) {
int16_t v = 900; //starting at 9.00 V
int16_t r1 = 0;
int16_t r2;
int16_t minDif = 5000;
int16_t minDif;
Serial.print(F("VPP calib. offset: "));
Serial.println(calOffset);
@ -215,34 +239,50 @@ static uint8_t varVppCalibrateVpp(void) {
delay(300); //settle voltage
while (1) {
while (i <= 0x80) {
// reset the 'minimal difference' variable every time we search for a new index
minDif = 9000;
//the 'vppWiper' storage for tap indices is 8bit wide, therefore we must not use tap index 256.
while (i <= 0xFF) {
int16_t d1,d2;
varVppSetVppIndex(i);
delay(50); //let the voltage settle
#if VPP_VERBOSE
Serial.print(i);
Serial.print(F(") "));
#endif
delay(100); //let the voltage settle
r2 = varVppMeasureVpp(0);
// Sanity check: the previous voltage can't be higher than the voltage just measured
// because we are ramping up the voltage. Therefore it must be an ADC measurement
// glitch. In that case use the previous value as the measured one.
// This can happen at lower tap indices where the voltage differences are very small (like 0.01V).
if (r1 > r2) {
r2 = r1;
}
d1 = r1 - v;
d2 = r2 - v;
d1 = ABS(d1);
d2 = ABS(d2);
#if VPP_VERBOSE
Serial.print(i);
Serial.print(F(") r2="));
Serial.print(r2, DEC);
Serial.print(F(" d1="));
Serial.print(d1, DEC);
Serial.print(F(" d2="));
Serial.print(d2, DEC);
Serial.print(F(" md="));
Serial.println(minDif, DEC);
#endif
if (r2 <= 100) { // less than 1V ? Failure
r1 = FAIL;
goto ret;
}
if (d2 < minDif) {
if (d2 <= minDif) {
minDif = d2;
vppWiper[vppIndex] = i;
//check last value / voltage
if (i == 0x80) {
if (i == 0xFF) {
if (v >= 1620 && v <= 1670) {
#if 1 || VPP_VERBOSE
Serial.println(F("*Index for VPP 1650 is 128"));
#endif
Serial.println(F("*Index for VPP 1650 is 255"));
r1 = OK;
goto ret;
}
@ -252,12 +292,10 @@ static uint8_t varVppCalibrateVpp(void) {
} else {
i--;
minDif = 5000;
#if 1 || VPP_VERBOSE
Serial.print(F("*Index for VPP "));
Serial.print(v);
Serial.print(F(" is "));
Serial.println(i);
#endif
break;
}
@ -273,7 +311,7 @@ static uint8_t varVppCalibrateVpp(void) {
r1 = OK;
goto ret;
}
v += 50;
v += 50; //next voltage to search for is 0.5V higher
}
ret:
@ -282,33 +320,50 @@ ret:
}
static void varVppStoreWiperCalib() {
uint8_t i = 0;
//sanity check
if (vppWiper[0] == 0) {
#ifdef VPP_VERBOSE
Serial.println(F("VPP wiper is 0"));
#endif
return;
}
#ifdef VPP_VERBOSE
Serial.println(F("VPP storing calibration"));
#endif
EEPROM_BEGIN();
//write Afterburner calibration header
EEPROM.update(0, 0xAF);
EEPROM.update(1, 0xCA);
EEPROM.update(2, (uint8_t) calOffset);
EEPROM_UPDATE(0, 0xAF);
EEPROM_UPDATE(1, 0xCA);
EEPROM_UPDATE(2, (uint8_t) calOffset);
while (i < MAX_WIPER) {
EEPROM.update(3 + i, vppWiper[i]);
EEPROM_UPDATE(3 + i, vppWiper[i]);
i++;
}
EEPROM_END();
}
#if CONFIG_IDF_TARGET_ESP32S2 == 1
static void analogReference(uint8_t ref) {
analogReadResolution(10);
adc2_config_channel_atten(ADC_PIN, ADC_ATTEN_DB_11); // AREF 2.5V
}
#endif
//return 1 on success (variable VPP functionality present), 0 on failure (VPP not detected on board)
static int8_t varVppInit(void) {
analogReference(ANALOG_REF_EXTERNAL); //use 3V3 external reference
analogRead(VPP); // Perform a dummy conversion referring to the datasheet
wiperStat = 0; //wiper disabled
mcp4131_init();
if (mcp4131_detect()) {
#if VPP_VERBOSE
Serial.println(F("POT found"));
Serial.print(mcp4131_detected ? F("MCP4131") : F("MCP4151"));
Serial.println(F(" POT found"));
#endif
return OK;
} else {
@ -347,7 +402,30 @@ static int8_t varVppCheckCalibration(void) {
return OK;
}
// only for VPP testing and debugging
static void varrVppTestRamp(void) {
uint8_t i = 1;
while (i < 256) {
varVppSetVppIndex(i);
delay(400);
Serial.println(i, DEC);
varVppMeasureVpp(1);
i++;
}
delay(2000);
varVppSetVppIndex(0);
}
static int8_t varVppCalibrate(void) {
#if 0
// only for testing and debugging
varrVppTestRamp();
return OK;
#endif
if (varVppCalibrateVpp()) {
varVppStoreWiperCalib();
} else {
@ -356,4 +434,4 @@ static int8_t varVppCalibrate(void) {
}
return OK;
}
#endif

File diff suppressed because it is too large Load Diff

32
bom.txt
View File

@ -1,8 +1,8 @@
BOM for Afterburner 3.0
=======================
BOM for Afterburner PCB 3.2b
============================
* (R1 - 5x) Resistor 4k7 (TH or SMT 0805) 0.25W, 5% or better tolerance - 5x
* (R8, R4) Resistor 10k (TH or SMT 0805) 1% or better tolerance - 2x
* (R5) Resistor 100k (TH or SMT 0805) 1% or better tolerance - 1x
* (R5, R18) Resistor 100k (TH or SMT 0805) 1% or better tolerance - 2x
* (R6) Resistor 20k (TH or SMT 0805) 1% or better tolerance - 1x
* (R7, R3, R2) Resistor 3k3 (TH or SMT 0805) 1% or better tolerance - 3x
* (R9) Cermet trimmer (TH Suntan TSR-3362P-202R 2k) - 1x
@ -26,3 +26,29 @@ BOM for Afterburner 3.0
* 40 Pin Male Single Row Strip 2.54mm Pin Header Connector 0.1" Straight - 1x
* Arduino UNO
OPTIONAL features
-----------------
on-board voltage booster (replacement for MT3608 module)
* (R10, R13 ,R14) Resistor 100k (TH or SMT 0805) 1% or better tolerance - 3x
* (R11) Resistor 4k7 (TH or SMT 0805) 0.25W, 1% or better tolerance - 1x
* (R12) Resistor 3k3 (TH or SMT 0805) 1% or better tolerance - 1x
* (C10) Capacitor ceramic 1uF, 9V or higher (TH or SMT 0805) - 1x
* (D2) Diode Schottky SK14, 1A 40V (SMT package DO-214AC-2 )
or alternative TH Schottky diode 1N5818
* (L1) Inductor 10uH, NLCV32T-100K-PF (SMT package 1210)
or alternative TH: AICC-02-100K-T Axial inductor
* (U3) Voltage booster MIC2619 (SOT-23-6) with over voltage protection (OVP)
or alternative MIC2250 (SOT-23-5) without OVP, R10, R11 and R12 not required
or possibly TPS61040DBVR (SOT-23-5) without OVP - not tested yet
SPI-RAM: extra memory for UNO (not used ATM. Can be soldered later if needed)
* (R15, R16) Resistor 10k (TH or SMT 0805) 5% or better tolerance - 2x
* (R17) Resistor 4k7 (TH or SMT 0805) 0.25W, 5% or better tolerance - 1x
* (C11) Capacitor ceramic 100nF 10V or higher (TH or SMT 0805) - 1x
* (Q2,Q3) N-MOSFET (SMT: BSS138P - SOT23 or TH: BS170G - TO92, or similar) - 2x
* (U4) SPI-RAM 5V, 23LC1024, DIP or SOIC-8, alternative 23LC512 or 23LCV512 or 23LCV1024

View File

@ -1 +1,5 @@
gcc -g3 -O0 -DNO_CLOSE -o afterburner src_pc/afterburner.c
GCOM=`git rev-parse --short HEAD`
gcc -g3 -O0 -DNO_CLOSE -DGCOM="\"g${GCOM}\"" -o afterburner src_pc/afterburner.c

View File

@ -1,4 +1,7 @@
# path to your Osx cross-compiler
CC=~/opt/osxcross/bin/o64-clang
$CC -g3 -O0 -DNO_CLOSE -o afterburner_osx src_pc/afterburner.c
GCOM=`git rev-parse --short HEAD`
$CC -g3 -O0 -D_OSX_ -DNO_CLOSE -DGCOM="\"g${GCOM}\"" -o afterburner_osx src_pc/afterburner.c

View File

@ -1,4 +1,7 @@
# path to your Win64 cross-compiler
CC=~/opt/mingw64/bin/x86_64-w64-mingw32-gcc
$CC -g3 -O0 -o afterburner_w64.exe src_pc/afterburner.c -D_USE_WIN_API_ -DNO_CLOSE
GCOM=`git rev-parse --short HEAD`
$CC -g3 -O0 -o afterburner_w64.exe src_pc/afterburner.c -D_USE_WIN_API_ -DNO_CLOSE -DGCOM="\"g${GCOM}\""

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 774 KiB

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
img/atf150x_adapter.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 438 KiB

After

Width:  |  Height:  |  Size: 440 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 493 KiB

After

Width:  |  Height:  |  Size: 875 KiB

996
jtag_xsvf_player.h Normal file
View File

@ -0,0 +1,996 @@
#ifndef _JTAG_XSVF_PLAYER_H_
#define _JTAG_XSVF_PLAYER_H_
/*
Arduino JTAG Player for Afterburner GAL project
---------------------------------------
Adapted from JTAG library 1.0.15 by Marcelo Jimenez
https://github.com/mrjimenez/JTAG
This port:
* improves flash size on AVR MCU (about 6.5kb vs 11kb)
* allows to allocate JTAG internal buffers temporarily within a shared
global buffer (heap). Define XSVF_HEAP to enable such feature:
uint8_t heap[900];
#define XSVF_HEAP heap
#include "jtag_xsvf_player.h"
* reduces the code to a single .h file
Use the original JTAG libray python scripts to upload XSVF files
from your PC:
./xsvf -p /dev/ttyACM0 my_file.xsvf
Arduino usage:
jtag_port_t jport;
Serial.begin(115200);
//assign jtag pins (vref pin checks the cable is plugged in)
jport.tms = 12;
jport.tdi = 2;
jport.tdo = 4;
jport.tck = 3;
jport.vref = 10;
//process XSVF data received from serial port
jtag_play_xsvf(&jport);
*/
//value bigger than 63 may cause reading errors on AVR MCUs.
#define XSVF_BUF_SIZE 62
#define XSVF_DEBUG 0
#define XSVF_CALC_CSUM 1
#define XSVF_IGNORE_NOMATCH 0
#define XCOMPLETE 0
#define XTDOMASK 1
#define XSIR 2
#define XSDR 3
#define XRUNTEST 4
#define XRESERVED_5 5
#define XRESERVED_6 6
#define XREPEAT 7
#define XSDRSIZE 8
#define XSDRTDO 9
#define XSETSDRMASKS 10
#define XSDRINC 11
#define XSDRB 12
#define XSDRC 13
#define XSDRE 14
#define XSDRTDOB 15
#define XSDRTDOC 16
#define XSDRTDOE 17
#define XSTATE 18
#define XENDIR 19
#define XENDDR 20
#define XSIR2 21
#define XCOMMENT 22
#define XWAIT 23
#define XWAITSTATE 24
#define XTRST 28
#define S_MAX_CHAIN_SIZE_BYTES 129
#define S_MAX_CHAIN_SIZE_BITS (S_MAX_CHAIN_SIZE_BYTES * 8)
#define STATE_RUN_TEST_IDLE 1
#define STATE_PAUSE_DR 6
#define STATE_PAUSE_IR 13
#define ERR_IO 1
#define ERR_XSIR_SIZE 2
#define ERR_XSDRSIZE 3
#define ERR_XENDIR 4
#define ERR_XENDDR 5
#define ERR_XSDR 6
#define ERR_INSTR_NOT_IMPLEMENTED 99
#define ERR_DR_CHECK_FAILED 101
/*
* Low nibble : TMS == 0
* High nibble: TMS == 1
*/
#define TMS_T(TMS_HIGH_STATE, TMS_LOW_STATE) (((TMS_HIGH_STATE) << 4) | (TMS_LOW_STATE))
#define XSTATE_TEST_LOGIC_RESET 0
#define XSTATE_RUN_TEST_IDLE 1
#define XSTATE_SELECT_DR_SCAN 2
#define XSTATE_CAPTURE_DR 3
#define XSTATE_SHIFT_DR 4
#define XSTATE_EXIT1_DR 5
#define XSTATE_PAUSE_DR 6
#define XSTATE_EXIT2_DR 7
#define XSTATE_UPDATE_DR 8
#define XSTATE_SELECT_IR_SCAN 9
#define XSTATE_CAPTURE_IR 10
#define XSTATE_SHIFT_IR 11
#define XSTATE_EXIT1_IR 12
#define XSTATE_PAUSE_IR 13
#define XSTATE_EXIT2_IR 14
#define XSTATE_UPDATE_IR 15
#define TMS_T00 /* STATE_TEST_LOGIC_RESET */ TMS_T(XSTATE_TEST_LOGIC_RESET, XSTATE_RUN_TEST_IDLE)
#define TMS_T01 /* STATE_RUN_TEST_IDLE */ TMS_T(XSTATE_SELECT_DR_SCAN, XSTATE_RUN_TEST_IDLE)
#define TMS_T02 /* STATE_SELECT_DR_SCAN */ TMS_T(XSTATE_SELECT_IR_SCAN, XSTATE_CAPTURE_DR)
#define TMS_T03 /* STATE_CAPTURE_DR */ TMS_T(XSTATE_EXIT1_DR, XSTATE_SHIFT_DR)
#define TMS_T04 /* STATE_SHIFT_DR */ TMS_T(XSTATE_EXIT1_DR, XSTATE_SHIFT_DR)
#define TMS_T05 /* STATE_EXIT1_DR */ TMS_T(XSTATE_UPDATE_DR, XSTATE_PAUSE_DR)
#define TMS_T06 /* STATE_PAUSE_DR */ TMS_T(XSTATE_EXIT2_DR, XSTATE_PAUSE_DR)
#define TMS_T07 /* STATE_EXIT2_DR */ TMS_T(XSTATE_UPDATE_DR, XSTATE_SHIFT_DR)
#define TMS_T08 /* STATE_UPDATE_DR */ TMS_T(XSTATE_SELECT_DR_SCAN, XSTATE_RUN_TEST_IDLE)
#define TMS_T09 /* STATE_SELECT_IR_SCAN */ TMS_T(XSTATE_TEST_LOGIC_RESET, XSTATE_CAPTURE_IR)
#define TMS_T10 /* STATE_CAPTURE_IR */ TMS_T(XSTATE_EXIT1_IR, XSTATE_SHIFT_IR)
#define TMS_T11 /* STATE_SHIFT_IR */ TMS_T(XSTATE_EXIT1_IR, XSTATE_SHIFT_IR)
#define TMS_T12 /* STATE_EXIT1_IR */ TMS_T(XSTATE_UPDATE_IR, XSTATE_PAUSE_IR)
#define TMS_T13 /* STATE_PAUSE_IR */ TMS_T(XSTATE_EXIT2_IR, XSTATE_PAUSE_IR)
#define TMS_T14 /* STATE_EXIT2_IR */ TMS_T(XSTATE_UPDATE_IR, XSTATE_SHIFT_IR)
#define TMS_T15 /* STATE_UPDATE_IR */ TMS_T(XSTATE_SELECT_DR_SCAN, XSTATE_RUN_TEST_IDLE)
#define BITSTR(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P) ( \
((uint16_t)(A) << 15) | \
((uint16_t)(B) << 14) | \
((uint16_t)(C) << 13) | \
((uint16_t)(D) << 12) | \
((uint16_t)(E) << 11) | \
((uint16_t)(F) << 10) | \
((uint16_t)(G) << 9) | \
((uint16_t)(H) << 8) | \
((uint16_t)(I) << 7) | \
((uint16_t)(J) << 6) | \
((uint16_t)(K) << 5) | \
((uint16_t)(L) << 4) | \
((uint16_t)(M) << 3) | \
((uint16_t)(N) << 2) | \
((uint16_t)(O) << 1) | \
((uint16_t)(P) << 0) )
/*
* The index of this vector is the current state. The i-th bit tells you the
* value TMS must assume in order to go to state "i".
------------------------------------------------------------------------------------------------------------
| | || F | E | D | C || B | A | 9 | 8 || 7 | 6 | 5 | 4 || 3 | 2 | 1 | 0 || HEX |
------------------------------------------------------------------------------------------------------------
| STATE_TEST_LOGIC_RESET | 0 || 0 | 0 | 0 | 0 || 0 | 0 | 0 | 0 || 0 | 0 | 0 | 0 || 0 | 0 | 0 | 1 || 0x0001 |
| STATE_RUN_TEST_IDLE | 1 || 1 | 1 | 1 | 1 || 1 | 1 | 1 | 1 || 1 | 1 | 1 | 1 || 1 | 1 | 0 | 1 || 0xFFFD |
| STATE_SELECT_DR_SCAN | 2 || 1 | 1 | 1 | 1 || 1 | 1 | 1 | 0 || 0 | 0 | 0 | 0 || 0 | x | 1 | 1 || 0xFE03 |
| STATE_CAPTURE_DR | 3 || 1 | 1 | 1 | 1 || 1 | 1 | 1 | 1 || 1 | 1 | 1 | 0 || x | 1 | 1 | 1 || 0xFFE7 |
| STATE_SHIFT_DR | 4 || 1 | 1 | 1 | 1 || 1 | 1 | 1 | 1 || 1 | 1 | 1 | 0 || 1 | 1 | 1 | 1 || 0xFFEF |
| STATE_EXIT1_DR | 5 || 1 | 1 | 1 | 1 || 1 | 1 | 1 | 1 || 0 | 0 | x | 0 || 1 | 1 | 1 | 1 || 0xFF0F |
| STATE_PAUSE_DR | 6 || 1 | 1 | 1 | 1 || 1 | 1 | 1 | 1 || 1 | 0 | 1 | 1 || 1 | 1 | 1 | 1 || 0xFFBF |
| STATE_EXIT2_DR | 7 || 1 | 1 | 1 | 1 || 1 | 1 | 1 | 1 || x | 0 | 0 | 0 || 1 | 1 | 1 | 1 || 0xFF0F |
| STATE_UPDATE_DR | 8 || 1 | 1 | 1 | 1 || 1 | 1 | 1 | x || 1 | 1 | 1 | 1 || 1 | 1 | 0 | 1 || 0xFEFD |
| STATE_SELECT_IR_SCAN | 9 || 0 | 0 | 0 | 0 || 0 | 0 | x | 1 || 1 | 1 | 1 | 1 || 1 | 1 | 1 | 1 || 0x01FF |
| STATE_CAPTURE_IR | A || 1 | 1 | 1 | 1 || 0 | x | 1 | 1 || 1 | 1 | 1 | 1 || 1 | 1 | 1 | 1 || 0xF3FF |
| STATE_SHIFT_IR | B || 1 | 1 | 1 | 1 || 0 | 1 | 1 | 1 || 1 | 1 | 1 | 1 || 1 | 1 | 1 | 1 || 0xF7FF |
| STATE_EXIT1_IR | C || 1 | 0 | 0 | x || 0 | 1 | 1 | 1 || 1 | 1 | 1 | 1 || 1 | 1 | 1 | 1 || 0x87FF |
| STATE_PAUSE_IR | D || 1 | 1 | 0 | 1 || 1 | 1 | 1 | 1 || 1 | 1 | 1 | 1 || 1 | 1 | 1 | 1 || 0xDFFF |
| STATE_EXIT2_IR | E || 1 | x | 0 | 0 || 0 | 1 | 1 | 1 || 1 | 1 | 1 | 1 || 1 | 1 | 1 | 1 || 0x87FF |
| STATE_UPDATE_IR | F || x | 1 | 1 | 1 || 1 | 1 | 1 | 1 || 1 | 1 | 1 | 1 || 1 | 1 | 0 | 1 || 0x7FFD |
------------------------------------------------------------------------------------------------------------
*/
#define BS00 /* STATE_TEST_LOGIC_RESET */ BITSTR( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 )
#define BS01 /* STATE_RUN_TEST_IDLE */ BITSTR( 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1 )
#define BS02 /* STATE_SELECT_DR_SCAN */ BITSTR( 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1 )
#define BS03 /* STATE_CAPTURE_DR */ BITSTR( 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1 )
#define BS04 /* STATE_SHIFT_DR */ BITSTR( 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1 )
#define BS05 /* STATE_EXIT1_DR */ BITSTR( 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1 )
#define BS06 /* STATE_PAUSE_DR */ BITSTR( 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1 )
#define BS07 /* STATE_EXIT2_DR */ BITSTR( 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1 )
#define BS08 /* STATE_UPDATE_DR */ BITSTR( 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1 )
#define BS09 /* STATE_SELECT_IR_SCAN */ BITSTR( 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 )
#define BS10 /* STATE_CAPTURE_IR */ BITSTR( 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 )
#define BS11 /* STATE_SHIFT_IR */ BITSTR( 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 )
#define BS12 /* STATE_EXIT1_IR */ BITSTR( 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 )
#define BS13 /* STATE_PAUSE_IR */ BITSTR( 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 )
#define BS14 /* STATE_EXIT2_IR */ BITSTR( 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 )
#define BS15 /* STATE_UPDATE_IR */ BITSTR( 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1 )
typedef struct xsvf_t {
uint8_t* xsvf_tdo_mask;
uint8_t* xsvf_tdi;
uint8_t* xsvf_tdo;
uint8_t* xsvf_tdo_expected;
uint8_t* xsvf_address_mask;
uint8_t* xsvf_data_mask;
uint32_t rdpos;
uint32_t wrpos;
#if XSVF_CALC_CSUM
uint32_t csum;
#endif
uint16_t instruction_counter;
uint8_t error;
uint8_t xcomplete;
uint16_t sirsize_bits;
uint16_t sirsize_bytes;
uint32_t sdrsize_bits;
uint32_t sdrsize_bytes;
uint32_t runtest;
uint8_t repeat;
uint8_t next_state;
uint8_t endir_state;
uint8_t enddr_state;
uint32_t wait_time_usecs;
uint8_t wait_start_state;
uint8_t wait_end_state;
uint8_t jtag_current_state;
} xsvf_t;
#ifdef XSVF_HEAP
// variables will be allocated on heap
uint8_t* xsvf_buf;
xsvf_t* xsvf;
uint8_t* xsvf_tms_transitions;
uint16_t* xsvf_tms_map;
#else /* XSVF_HEAP */
// variables allocated globally
uint8_t xsvf_buf[XSVF_BUF_SIZE];
uint8_t xsvf_tdo_mask[S_MAX_CHAIN_SIZE_BYTES];
uint8_t xsvf_tdi[S_MAX_CHAIN_SIZE_BYTES];
uint8_t xsvf_tdo[S_MAX_CHAIN_SIZE_BYTES];
uint8_t xsvf_tdo_expected[S_MAX_CHAIN_SIZE_BYTES];
uint8_t xsvf_address_mask[S_MAX_CHAIN_SIZE_BYTES];
uint8_t xsvf_data_mask[S_MAX_CHAIN_SIZE_BYTES];
xsvf_t xsvf_context;
xsvf_t* xsvf = &xsvf_context;
static const uint8_t xsvf_tms_transitions[] = {
TMS_T00, TMS_T01, TMS_T02, TMS_T03, TMS_T04, TMS_T05, TMS_T06, TMS_T07,
TMS_T08, TMS_T09, TMS_T10, TMS_T11, TMS_T12, TMS_T13, TMS_T14, TMS_T15,
};
static const uint16_t xsvf_tms_map[] = {
BS00, BS01, BS02, BS03, BS04, BS05, BS06, BS07,
BS08, BS09, BS10, BS11, BS12, BS13, BS14, BS15
};
#endif
typedef struct jtag_port_t {
uint8_t tms;
uint8_t tdi;
uint8_t tdo;
uint8_t tck;
uint8_t vref;
} jtag_port_t;
static void jtag_port_init(jtag_port_t* port) {
pinMode(port->tms, OUTPUT);
pinMode(port->tdi, OUTPUT);
pinMode(port->tck, OUTPUT);
pinMode(port->tdo, INPUT);
pinMode(port->vref, INPUT);
}
static void jtag_port_pulse_clock(jtag_port_t* port) {
digitalWrite(port->tck, 0);
delayMicroseconds(1);
digitalWrite(port->tck, 1);
}
static uint8_t jtag_port_pulse_clock_read_tdo(jtag_port_t* port) {
uint8_t val;
digitalWrite(port->tck, 0);
delayMicroseconds(1);
val = digitalRead(port->tdo);
digitalWrite(port->tck, 1);
return val;
}
static inline void jtag_port_set_tms(jtag_port_t* port, uint8_t val) {
digitalWrite(port->tms, val);
}
static inline void jtag_port_set_tdi(jtag_port_t* port, uint8_t val) {
digitalWrite(port->tdi, val);
}
static inline uint8_t jtag_port_get_veref(jtag_port_t* port) {
return digitalRead(port->vref);
}
static uint8_t xsvf_player_next_byte(void) {
uint8_t retry = 16;
uint8_t pos = xsvf->rdpos % XSVF_BUF_SIZE;
if (xsvf->wrpos == xsvf->rdpos) {
size_t r = 0;
while (r == 0) {
#if XSVF_DEBUG
Serial.println("D<<< req read"); // request to receive BUF size bytes
#endif
Serial.println(F("$062")); // request to receive BUF size bytes
r = Serial.readBytes(xsvf_buf + pos, XSVF_BUF_SIZE - pos);
#if XSVF_DEBUG
Serial.print("D<<< read "); // request to receive BUF size bytes
Serial.println(r, DEC); // request to receive BUF size bytes
#endif
if (r == 0) {
retry --;
if (retry == 0) {
xsvf->error = 1;
return 0;
}
delay(1);
} else {
xsvf->wrpos += r;
}
}
}
xsvf->rdpos++;
#if XSVF_DEBUG
Serial.print(F("D BYTE "));
Serial.print(xsvf_buf[pos], DEC);
Serial.print(F(" 0x"));
Serial.println(xsvf_buf[pos], HEX);
#endif
#if XSVF_CALC_CSUM
xsvf->csum += xsvf_buf[pos];
#endif
return xsvf_buf[pos];
}
static uint8_t xsvf_player_get_next_byte(void) {
return xsvf_player_next_byte();
}
/*
static uint16_t xsvf_player_get_next_word(void) {
uint16_t i = xsvf_player_next_byte();
i <<= 8;
i |= xsvf_player_next_byte();
return i;
}
*/
static uint32_t xsvf_player_get_next_long(void) {
uint32_t i = xsvf_player_next_byte();
i <<= 8;
i |= xsvf_player_next_byte();
i <<= 8;
i |= xsvf_player_next_byte();
i <<= 8;
i |= xsvf_player_next_byte();
return i;
}
static void xsvf_player_get_next_bytes(uint8_t* data, uint32_t count) {
while(count--) {
*data++ = xsvf_player_next_byte();
}
}
#ifdef XSVF_HEAP
static uint32_t xsvf_heap_pos(uint32_t* pos, uint16_t size) {
uint32_t heap_pos = *pos;
//allocate on 4 byte boundaries
heap_pos = (heap_pos + 3) & 0xFFFFFFFC;
*pos = heap_pos + size;
return heap_pos;
}
#endif
static void xsvf_clear() {
uint16_t i;
uint8_t* d = (uint8_t*) xsvf;
//clear the xsvf data in RAM
i = sizeof(xsvf_t);
while(i) {
i--;
d[i] = 0;
}
}
static void xsvf_player_init(jtag_port_t* port) {
jtag_port_init(port);
#ifdef XSVF_HEAP
{
// variables allocated on the heap
uint32_t heap_pos = (uint32_t) XSVF_HEAP;
xsvf = (xsvf_t*) xsvf_heap_pos(&heap_pos, sizeof(xsvf_t));
xsvf_buf = (uint8_t*) xsvf_heap_pos(&heap_pos, XSVF_BUF_SIZE);
xsvf_clear();
xsvf->xsvf_tdo_mask = (uint8_t*) xsvf_heap_pos(&heap_pos, S_MAX_CHAIN_SIZE_BYTES);
xsvf->xsvf_tdi = (uint8_t*) xsvf_heap_pos(&heap_pos, S_MAX_CHAIN_SIZE_BYTES);
xsvf->xsvf_tdo = (uint8_t*) xsvf_heap_pos(&heap_pos, S_MAX_CHAIN_SIZE_BYTES);
xsvf->xsvf_tdo_expected = (uint8_t*) xsvf_heap_pos(&heap_pos, S_MAX_CHAIN_SIZE_BYTES);
xsvf->xsvf_address_mask = (uint8_t*) xsvf_heap_pos(&heap_pos, S_MAX_CHAIN_SIZE_BYTES);
xsvf->xsvf_data_mask = (uint8_t*) xsvf_heap_pos(&heap_pos, S_MAX_CHAIN_SIZE_BYTES);
xsvf_tms_transitions = (uint8_t*) xsvf_heap_pos(&heap_pos, 16);
xsvf_tms_map = (uint16_t*) xsvf_heap_pos(&heap_pos, 32);
if (heap_pos - ((uint32_t)XSVF_HEAP) > sizeof(XSVF_HEAP)) {
Serial.print(F("Q-1,ERROR: Heap is small:"));
Serial.println(heap_pos - ((uint32_t)XSVF_HEAP), DEC);
return;
}
//set up TM transitions
xsvf_tms_transitions[0] = TMS_T00;
xsvf_tms_transitions[1] = TMS_T01;
xsvf_tms_transitions[2] = TMS_T02;
xsvf_tms_transitions[3] = TMS_T03;
xsvf_tms_transitions[4] = TMS_T04;
xsvf_tms_transitions[5] = TMS_T05;
xsvf_tms_transitions[6] = TMS_T06;
xsvf_tms_transitions[7] = TMS_T07;
xsvf_tms_transitions[8] = TMS_T08;
xsvf_tms_transitions[9] = TMS_T09;
xsvf_tms_transitions[10] = TMS_T10;
xsvf_tms_transitions[11] = TMS_T11;
xsvf_tms_transitions[12] = TMS_T12;
xsvf_tms_transitions[13] = TMS_T13;
xsvf_tms_transitions[14] = TMS_T14;
xsvf_tms_transitions[15] = TMS_T15;
//set up bitstream map
xsvf_tms_map[0] = BS00;
xsvf_tms_map[1] = BS01;
xsvf_tms_map[2] = BS02;
xsvf_tms_map[3] = BS03;
xsvf_tms_map[4] = BS04;
xsvf_tms_map[5] = BS05;
xsvf_tms_map[6] = BS06;
xsvf_tms_map[7] = BS07;
xsvf_tms_map[8] = BS08;
xsvf_tms_map[9] = BS09;
xsvf_tms_map[10] = BS10;
xsvf_tms_map[11] = BS11;
xsvf_tms_map[12] = BS12;
xsvf_tms_map[13] = BS13;
xsvf_tms_map[14] = BS14;
xsvf_tms_map[15] = BS15;
}
#else
{
xsvf_clear();
xsvf->xsvf_tdo_mask = xsvf_tdo_mask;
xsvf->xsvf_tdi = xsvf_tdi;
xsvf->xsvf_tdo = xsvf_tdo;
xsvf->xsvf_tdo_expected = xsvf_tdo_expected;
xsvf->xsvf_address_mask = xsvf_address_mask;
xsvf->xsvf_data_mask = xsvf_data_mask;
}
#endif
xsvf->repeat = 32;
xsvf->endir_state = XSTATE_RUN_TEST_IDLE;
xsvf->enddr_state = STATE_RUN_TEST_IDLE;
}
static void xsvf_jtagtap_state_ack(uint8_t tms) {
tms <<= 2; // either 0 or 4
xsvf->jtag_current_state = (xsvf_tms_transitions[xsvf->jtag_current_state] >> tms) & 0xf;
}
static void xsvf_jtagtap_shift_td(
jtag_port_t* port,
uint8_t *input_data,
uint8_t *output_data,
uint32_t data_bits,
uint8_t must_end)
{
uint32_t i, j;
uint32_t bit_count = data_bits;
uint32_t byte_count = (data_bits+ 7) >> 3;
for (i = 0; i < byte_count; ++i) {
uint8_t byte_out = input_data[byte_count - 1 - i];
uint8_t tdo_byte = 0;
for (j = 0; j < 8 && bit_count-- > 0; ++j) {
uint8_t tdo;
if (bit_count == 0 && must_end) {
jtag_port_set_tms(port, 1);
xsvf_jtagtap_state_ack(1);
}
jtag_port_set_tdi(port, byte_out & 1);
byte_out >>= 1;
tdo = jtag_port_pulse_clock_read_tdo(port);
tdo_byte |= tdo << j;
}
output_data[byte_count - 1 - i] = tdo_byte;
}
}
static void xsvf_jtagtap_state_step(jtag_port_t* port, uint8_t tms) {
jtag_port_set_tms(port, tms);
jtag_port_pulse_clock(port);
xsvf_jtagtap_state_ack(tms);
}
static void xsvf_jtagtap_state_goto(jtag_port_t* port, uint8_t state) {
if (xsvf->error) {
return;
}
if (state == XSTATE_TEST_LOGIC_RESET) {
uint8_t i;
for (i = 0; i < 5; ++i) {
xsvf_jtagtap_state_step(port, 1);
}
} else {
while (xsvf->jtag_current_state != state) {
xsvf_jtagtap_state_step(port, (xsvf_tms_map[xsvf->jtag_current_state] >> state) & 1);
}
}
}
static void xsvf_jtagtap_wait_time(jtag_port_t* port, uint32_t microseconds, uint8_t wait_clock) {
uint32_t until;
if (xsvf->error) {
return;
}
until = micros() + microseconds;
if (wait_clock) {
while (microseconds--) {
jtag_port_pulse_clock(port);
}
}
while (micros() < until) {
jtag_port_pulse_clock(port);
}
}
static void xsvf_jtag_sir(jtag_port_t* port) {
if (xsvf->error) {
return;
}
xsvf_jtagtap_state_goto(port, XSTATE_SHIFT_IR);
xsvf_jtagtap_shift_td(port, xsvf->xsvf_tdi, xsvf->xsvf_tdo, xsvf->sirsize_bits, 1);
if (xsvf->runtest) {
xsvf_jtagtap_state_goto(port, xsvf->endir_state);
} else {
xsvf_jtagtap_state_goto(port, XSTATE_RUN_TEST_IDLE);
xsvf_jtagtap_wait_time(port, xsvf->runtest, 1);
}
}
static uint8_t xsvf_jtag_is_tdo_as_expected(uint8_t use_mask)
{
uint32_t i;
for (i = 0; i < xsvf->sdrsize_bytes; ++i) {
uint8_t expected = xsvf->xsvf_tdo_expected[i];
uint8_t actual = xsvf->xsvf_tdo[i];
if (use_mask) {
uint8_t mask = xsvf->xsvf_tdo_mask[i];
expected &= mask;
actual &= mask;
}
#if XSVF_IGNORE_NOMATCH != 1
if (expected != actual) {
#if XSVF_DEBUG
Serial.println(F("D...NO MATCH!"));
#endif
return 0;
}
#endif
}
#if XSVF_DEBUG
Serial.println(F("D...match!"));
#endif
return 1;
}
#define SDR_MUST_BEGIN (flags & 0b1000)
#define SDR_MUST_CHECK (flags & 0b0100)
#define SDR_USE_MASK (flags & 0b0010)
#define SDR_MUST_END (flags & 0b0001)
static uint8_t xsvf_jtag_sdr(jtag_port_t* port, uint8_t flags)
{
int16_t attempts_left = xsvf->repeat;
uint8_t matched = 0;
uint8_t must_end = SDR_MUST_END;
uint8_t must_check = SDR_MUST_CHECK;
uint8_t use_mask = SDR_USE_MASK;
if (xsvf->error) {
return 0;
}
if (SDR_MUST_BEGIN) {
xsvf_jtagtap_state_goto(port, XSTATE_SHIFT_DR);
}
while (!matched && attempts_left-- >= 0) {
xsvf_jtagtap_shift_td(port, xsvf->xsvf_tdi, xsvf->xsvf_tdo, xsvf->sdrsize_bits, must_end);
if (!must_check) {
break;
}
matched = xsvf_jtag_is_tdo_as_expected(use_mask);
if (!matched) {
// XAP058, page 14
xsvf_jtagtap_state_goto(port, XSTATE_PAUSE_DR);
xsvf_jtagtap_state_goto(port, XSTATE_SHIFT_DR);
xsvf_jtagtap_state_goto(port, XSTATE_RUN_TEST_IDLE);
xsvf_jtagtap_wait_time(port, xsvf->runtest, 1);
//
xsvf_jtagtap_state_goto(port, XSTATE_SHIFT_DR);
#if XSVF_DEBUG
if (attempts_left >= 0) {
Serial.print(F("D...repeating: "));
Serial.println(xsvf->repeat - attempts_left, DEC);
}
#endif
}
}
if (must_check && !matched) {
xsvf->error = ERR_DR_CHECK_FAILED;
Serial.println(F("D!DR check failed!"));
}
if (must_end && matched) {
if (!xsvf->runtest) {
xsvf_jtagtap_state_goto(port, xsvf->enddr_state);
} else {
xsvf_jtagtap_state_goto(port, XSTATE_RUN_TEST_IDLE);
xsvf_jtagtap_wait_time(port, xsvf->runtest, 1);
}
}
return !must_check || (must_check && matched);
}
/*
* Reads the next instruction from the serial port. Also reads any
* remaining instruction parameters into the instruction buffer.
*/
static uint8_t xsvf_player_handle_next_instruction(jtag_port_t* port) {
uint8_t instruction = xsvf_player_next_byte();
if (xsvf->error) {
return ERR_IO; // failure
}
xsvf->instruction_counter++;
#if XSVF_DEBUG
Serial.print(F("D INSTR "));
Serial.print(xsvf->instruction_counter, DEC);
Serial.print(F(" (0x"));
Serial.print(instruction, HEX);
Serial.print(F("): "));
#endif
//do not use switch as it uses RAM
// ---[COMPLETE ] --------------------------------------------
if (instruction == XCOMPLETE) {
#if XSVF_DEBUG
Serial.println(F("XCOMPLETE"));
#endif
xsvf->xcomplete = 1;
} else
// ---[TDO MASK] --------------------------------------------
if (instruction == XTDOMASK) {
#if XSVF_DEBUG
Serial.println(F("XTDOMASK"));
#endif
xsvf_player_get_next_bytes(xsvf->xsvf_tdo_mask, xsvf->sdrsize_bytes);
} else
// ---[SIR SIR2] --------------------------------------------
if (instruction == XSIR || instruction == XSIR2) {
#if XSVF_DEBUG
Serial.println(instruction == XSIR ? F("XSIR") : F("XSIR2"));
#endif
xsvf->sirsize_bits = xsvf_player_get_next_byte();
if (instruction == XSIR2) {
xsvf->sirsize_bits <= 8;
xsvf->sirsize_bits |= xsvf_player_get_next_byte();
}
xsvf->sirsize_bytes = (xsvf->sirsize_bits + 7) >> 3;
if (xsvf->sirsize_bytes > S_MAX_CHAIN_SIZE_BYTES) {
return ERR_XSIR_SIZE;
}
xsvf_player_get_next_bytes(xsvf->xsvf_tdi, xsvf->sirsize_bytes);
xsvf_jtag_sir(port);
} else
// ---[SDR ] --------------------------------------------
if (instruction == XSDR || (instruction >= XSDRB && instruction <= XSDRE)) {
uint8_t flags = 0b1111;
#if XSVF_DEBUG
Serial.println(F("XSDRx"));
#endif
xsvf_player_get_next_bytes(xsvf->xsvf_tdi, xsvf->sdrsize_bytes);
if (instruction != XSDR) {
flags = (instruction == XSDRB) ? 0b1000 : (instruction == XSDRC) ? 0b0000 : 0b0001;
}
if (!xsvf_jtag_sdr(port, flags)) {
xsvf->error = ERR_XSDR;
}
} else
// ---[RUN TEST ] --------------------------------------------
if (instruction == XRUNTEST) {
#if XSVF_DEBUG
Serial.println(F("XRUNTEST"));
#endif
xsvf->runtest = xsvf_player_get_next_long();
} else
// ---[REPEAT ] --------------------------------------------
if (instruction == XREPEAT) {
#if XSVF_DEBUG
Serial.println(F("XREPEAT"));
#endif
xsvf->repeat = xsvf_player_get_next_byte();
} else
// ---[SDRSIZE ] --------------------------------------------
if (instruction == XSDRSIZE) {
#if XSVF_DEBUG
Serial.println(F("XSDRSIZE"));
#endif
xsvf->sdrsize_bits = xsvf_player_get_next_long();
xsvf->sdrsize_bytes = (xsvf->sdrsize_bits + 7) >> 3;
if (xsvf->sdrsize_bytes > S_MAX_CHAIN_SIZE_BYTES) {
return ERR_XSDRSIZE;
}
} else
// ---[SDRTDO ] --------------------------------------------
if (instruction == XSDRTDO || (instruction >= XSDRTDOB && instruction <= XSDRTDOE)) {
uint8_t flags = 0b1111;
#if XSVF_DEBUG
Serial.println(F("XSDRTDOx"));
#endif
xsvf_player_get_next_bytes(xsvf->xsvf_tdi, xsvf->sdrsize_bytes);
xsvf_player_get_next_bytes(xsvf->xsvf_tdo_expected, xsvf->sdrsize_bytes);
if (instruction != XSDRTDO) {
flags = (instruction == XSDRTDOB) ? 0b1100 : (instruction == XSDRTDOC) ? 0b0100 : 0b0101;
}
if (!xsvf_jtag_sdr(port, flags)) {
xsvf->error = ERR_XSDR;
}
} else
// ---[SET SDR MASKS ] --------------------------------------------
if (instruction == XSETSDRMASKS) {
#if XSVF_DEBUG
Serial.println(F("XSETSDRMASKS"));
#endif
xsvf_player_get_next_bytes(xsvf->xsvf_address_mask, xsvf->sdrsize_bytes);
xsvf_player_get_next_bytes(xsvf->xsvf_data_mask, xsvf->sdrsize_bytes);
} else
// ---[SDR INC ] --------------------------------------------
if (instruction == XSDRINC) {
#if XSVF_DEBUG
Serial.println(F("XSDRINC"));
#endif
xsvf_player_get_next_bytes(xsvf->xsvf_tdi, xsvf->sdrsize_bytes);
// TODO - check: return false?
} else
// ---[STATE ] --------------------------------------------
if (instruction == XSTATE) {
#if XSVF_DEBUG
Serial.println(F("XSTATE"));
#endif
xsvf->next_state = xsvf_player_get_next_byte();
xsvf_jtagtap_state_goto(port, xsvf->next_state);
} else
// ---[END IR ] --------------------------------------------
if (instruction == XENDIR) {
uint8_t s;
#if XSVF_DEBUG
Serial.println(F("XENDIR"));
#endif
s = xsvf_player_get_next_byte();
if (s == 0) {
xsvf->endir_state = STATE_RUN_TEST_IDLE;
} else
if (s == 1) {
xsvf->endir_state = STATE_PAUSE_IR;
} else {
return ERR_XENDIR;
}
} else
// ---[END DR ] --------------------------------------------
if (instruction == XENDDR) {
uint8_t s;
#if XSVF_DEBUG
Serial.println(F("XENDDR"));
#endif
s = xsvf_player_get_next_byte();
if (s == 0) {
xsvf->enddr_state = STATE_RUN_TEST_IDLE;
} else
if (s == 1) {
xsvf->enddr_state = STATE_PAUSE_DR;
} else {
return ERR_XENDDR;
}
} else
// ---[COMMENT ] --------------------------------------------
if (instruction == XCOMMENT) {
uint8_t c;
#if XSVF_DEBUG
Serial.println(F("XCOMMENT"));
#endif
Serial.print(F("D"));//debug message preamble
//read the comment bytes
do {
c = xsvf_player_get_next_byte();
// special feature: dump the TDO data
if (c == '#') {
uint8_t cnt = 0;
uint8_t size = xsvf_player_get_next_byte() - '0';
//dump the tdo buffer bytes
while(cnt < size) {
char t[4];
uint8_t v = xsvf->xsvf_tdo[cnt];
uint8_t x1 = v >> 4;
v &= 0xF;
// DEC to HEX conversion with leading zero
t[0] = (char) (x1 < 10 ? '0' + x1 : 55 + x1 );
t[1] = (char) (v < 10 ? '0' + v : 55 + v );
t[2] = 0;
Serial.print(t);
cnt++;
}
} else if (c) {
Serial.print((char)c);
}
} while(c);
Serial.println();
} else
// ---[WAIT ] --------------------------------------------
if (instruction == XWAIT || instruction == XWAITSTATE) {
uint32_t clock_cnt = 0;
uint8_t wait_clock = 1;
#if XSVF_DEBUG
Serial.println(instruction == XWAIT ? F("XWAIT") : F("XWAITSTATE"));
#endif
//TOOD - do we need these states to be global?
xsvf->wait_start_state = xsvf_player_get_next_byte();
xsvf->wait_end_state = xsvf_player_get_next_byte();
if (instruction == XWAITSTATE) {
clock_cnt = xsvf_player_get_next_long();
wait_clock = clock_cnt > 0 ? 1 : 0;
}
#if XSVF_DEBUG
Serial.print(F("Dclock:"));
Serial.println(clock_cnt, DEC);
#endif
xsvf->wait_time_usecs = xsvf_player_get_next_long();
#if XSVF_DEBUG
Serial.print(F("Dmicros:"));
Serial.println( xsvf->wait_time_usecs, DEC);
#endif
xsvf_jtagtap_state_goto(port, xsvf->wait_start_state);
// happens only during XWAITSTATE
while (clock_cnt) {
jtag_port_pulse_clock(port);
clock_cnt--;
}
xsvf_jtagtap_wait_time(port, xsvf->wait_time_usecs, wait_clock);
xsvf_jtagtap_state_goto(port, xsvf->wait_end_state);
} else
// ---[TRST - test line reset] --------------------------------------------
if (instruction == XTRST) {
#if XSVF_DEBUG
Serial.println(F("XTRST"));
#endif
//read test reset mode (0-on, 1-off, 2-Z, 3-Absent)
xsvf_player_get_next_byte();
} else
// ---[UNKNOWN ] --------------------------------------------
{
#if XSVF_DEBUG
Serial.print(F("XUNKNOWN:"));
Serial.println(instruction, DEC);
#endif
//unimplemented instruction
return ERR_INSTR_NOT_IMPLEMENTED;
}
if (xsvf->error) {
return xsvf->error; // failure
}
return 0;
}
static void jtag_play_xsvf(jtag_port_t* port)
{
uint32_t n = 0;
uint8_t ret;
xsvf_player_init(port);
//check xref is high
if (!jtag_port_get_veref(port)) {
Serial.println(F("Q-255,JTAG not connected"));
return;
}
Serial.println(F("RXSVF")); //announce ready to receive XSVF stream
while(1) {
n++;
ret = xsvf_player_handle_next_instruction(port);
if (ret) {
Serial.print(F("Q-"));
Serial.print(ret, DEC );
Serial.println(F(",Fail"));
break;
} else {
if (xsvf->xcomplete) {
Serial.println(F("!Success"));
break;
}
}
}
Serial.print(F("!Processed instr:"));
Serial.println(xsvf->instruction_counter, DEC);
#if XSVF_CALC_CSUM
Serial.print(F("!sum: 0x"));
// print leading zeros in the check sum hex value
{
uint32_t i = 0xF0000000;
while((!(xsvf->csum & i)) && i) {
Serial.print(F("0"));
i >>= 4;
}
}
Serial.print(xsvf->csum, HEX);
Serial.print(F("/"));
Serial.println(xsvf->rdpos, DEC);
#endif /* XSVF_CALC_CSUM */
if (xsvf->xcomplete) {
Serial.println(F("Q-0,OK"));
}
//the 3 pins must be low or else the vref might be triggered next time
digitalWrite(port->tms, 0);
digitalWrite(port->tdi, 0);
digitalWrite(port->tck, 0);
delay(100);
// put the jtag port pins into High-Z (vref already is input)
pinMode(port->tms, INPUT);
pinMode(port->tdi, INPUT);
pinMode(port->tck, INPUT);
pinMode(port->tdo, INPUT);
}
#endif /*_JTAG_XSVF_PLAYER_H_*/

1854
pcb/adapter1502_r3.pcb Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -32,9 +32,12 @@ Changelog:
* use 'git log'
This is the PC part that communicates with Arduino UNO by serial line.
To compile: gcc -g3 -O0 afterburner afterburner.c
*/
To compile: gcc -g3 -O0 -o afterburner afterburner.c
* 2024-02-02 Fixed: Command 'B9' (Calibration Offset = 0,25V) doesn't work
Note: Also requires elimination of a bug in the PC program afterburner.ino
Added: Sending B4, if b /wo -co is executed
*/
#include <unistd.h>
#include <stdlib.h>
@ -45,18 +48,28 @@ To compile: gcc -g3 -O0 afterburner afterburner.c
#include "serial_port.h"
#define VERSION "v.0.5.3"
#define VERSION "v.0.6.0"
#define MAX_LINE 1024
#ifdef GCOM
#define VERSION_EXTENDED VERSION "-" GCOM
#else
#define VERSION_EXTENDED VERSION
#endif
#define MAX_LINE (16*1024)
#define MAXFUSES 30000
#define GALBUFSIZE (256 * 1024)
#define JTAG_ID 0xFF
#define MAXFUSES 10000
#define GALBUFSIZE 16384
typedef enum {
UNKNOWN,
GAL16V8,
GAL18V10,
GAL20V8,
GAL20RA10,
GAL20XV10,
GAL22V10,
GAL26CV12,
@ -66,7 +79,11 @@ typedef enum {
ATF16V8B,
ATF20V8B,
ATF22V10B,
ATF22V10C
ATF22V10C,
ATF750C,
//jtag based PLDs at the end: they do not have a gal type in MCU software
ATF1502AS,
ATF1504AS,
} Galtype;
@ -94,6 +111,7 @@ galinfo[] = {
{GAL16V8, 0x00, 0x1A, "GAL16V8", 2194, 20, 32, 64, 32, 2056, 8, 63, 54, 58, 8, 60, 82},
{GAL18V10, 0x50, 0x51, "GAL18V10", 3540, 20, 36, 96, 36, 3476, 8, 61, 60, 58, 10, 16, 20},
{GAL20V8, 0x20, 0x3A, "GAL20V8", 2706, 24, 40, 64, 40, 2568, 8, 63, 59, 58, 8, 60, 82},
{GAL20RA10, 0x60, 0x61, "GAL20RA10", 3274, 24, 40, 80, 40, 3210, 8, 61, 60, 58, 10, 16, 10},
{GAL20XV10, 0x65, 0x66, "GAL20XV10", 1671, 24, 40, 40, 44, 1631, 5, 61, 60, 58, 5, 16, 31},
{GAL22V10, 0x48, 0x49, "GAL22V10", 5892, 24, 44, 132, 44, 5828, 8, 61, 60, 58, 10, 16, 20},
{GAL26CV12, 0x58, 0x59, "GAL26CV12", 6432, 28, 52, 122, 52, 6368, 8, 61, 60, 58, 12, 16},
@ -104,6 +122,9 @@ galinfo[] = {
{ATF20V8B, 0x00, 0x00, "ATF20V8B", 2706, 24, 40, 64, 40, 2568, 8, 63, 59, 58, 8, 60, 82},
{ATF22V10B, 0x00, 0x00, "ATF22V10B", 5892, 24, 44, 132, 44, 5828, 8, 61, 60, 58, 10, 16, 20},
{ATF22V10C, 0x00, 0x00, "ATF22V10C", 5892, 24, 44, 132, 44, 5828, 8, 61, 60, 58, 10, 16, 20},
{ATF750C, 0x00, 0x00, "ATF750C", 14499, 24, 84, 171, 84, 14435, 8, 61, 60, 127, 10, 16, 71},
{ATF1502AS, JTAG_ID, JTAG_ID, "ATF1502AS", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0},
{ATF1504AS, JTAG_ID, JTAG_ID, "ATF1504AS", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0},
};
char verbose = 0;
@ -120,8 +141,9 @@ char fusemap[MAXFUSES];
char noGalCheck = 0;
char varVppExists = 0;
char printSerialWhileWaiting = 0;
int calOffset = 0xFFFF; //calibration offset is not applied
int calOffset = 0; //no calibration offset is applied
char enableSecurity = 0;
char bigRam = 0;
char opRead = 0;
char opWrite = 0;
@ -143,6 +165,9 @@ static char sendGenericCommand(const char* command, const char* errorText, int m
static void printGalTypes() {
int i;
for (i = 1; i < sizeof(galinfo) / sizeof(galinfo[0]); i++) {
if (i % 8 == 1) {
printf("\n\t");
} else
if (i > 1) {
printf(" ");
}
@ -151,7 +176,7 @@ static void printGalTypes() {
}
static void printHelp() {
printf("Afterburner " VERSION " a GAL programming tool for Arduino based programmer\n");
printf("Afterburner " VERSION_EXTENDED " a GAL programming tool for Arduino based programmer\n");
printf("more info: https://github.com/ole00/afterburner\n");
printf("usage: afterburner command(s) [options]\n");
printf("commands: ierwvsbm\n");
@ -170,8 +195,8 @@ static void printHelp() {
printGalTypes();
printf("\n");
printf(" -f <file> : JEDEC fuse map file\n");
printf(" -d <serial_device> : name of the serial device. Default is: %s\n", DEFAULT_SERIAL_DEVICE_NAME);
printf(" serial params are: 38400, 8N1\n");
printf(" -d <serial_device> : name of the serial device. Without this option the device is guessed.\n");
printf(" serial params are: 57600, 8N1\n");
printf(" -nc : do not check device GAL type before operation: force the GAL type set on command line\n");
printf(" -sec: enable security - protect the chip. Use with 'w' or 'v' commands.\n");
printf(" -co <offset>: Set calibration offset. Use with 'b' command. Value: -20 (-0.2V) to 25 (+0.25V)\n");
@ -190,7 +215,7 @@ static void printHelp() {
printf(" of the chip. If the programing voltage is unknown use 10V.\n");
printf(" - known VPP voltages as tested on Afterburner with Arduino UNO: \n");
printf(" Lattice GAL16V8D, GAL20V8B, GAL22V10D: 12V \n");
printf(" Atmel ATF16V8B, AFT16V8C, ATF22V10C: 10V \n");
printf(" Atmel ATF16V8B, ATF16V8C, ATF22V10C: 11V \n");
}
static int8_t verifyArgs(char* type) {
@ -211,10 +236,6 @@ static int8_t verifyArgs(char* type) {
printf("Error: VPP functions can not be conbined with read/write/verify operations\n");
return -1;
}
if (0 == filename && (opWrite == 1 || opVerify == 1)) {
printf("Error: missing JED filename\n");
return -1;
}
if (0 == type && (opWrite || opRead || opErase || opVerify || opInfo || opWritePes)) {
printf("Error: missing GAL type. Use -t <type> to specify.\n");
return -1;
@ -233,6 +254,10 @@ static int8_t verifyArgs(char* type) {
return -1;
}
}
if (0 == filename && (opWrite == 1 || opVerify == 1)) {
printf("Error: missing %s filename (param: -f fname)\n", galinfo[gal].id0 == JTAG_ID ? ".xsvf" : ".jed");
return -1;
}
return 0;
}
@ -268,19 +293,19 @@ static int8_t checkArgs(int argc, char** argv) {
} else if (strcmp("-co", param) == 0) {
i++;
calOffset = atoi(argv[i]);
if (calOffset < -20 || calOffset > 25) {
printf("Calibration offset out of range (-20..25 inclusive).\n");
if (calOffset < -32 || calOffset > 32) {
printf("Calibration offset out of range (-32..32 inclusive).\n");
}
if (calOffset < -32) {
calOffset = -32;
} else if (calOffset > 32) {
calOffset = 32;
}
}
else if (param[0] != '-') {
modes = param;
}
}
if (calOffset < -20) {
calOffset = -20;
} else if (calOffset > 25) {
calOffset = 25;
}
i = 0;
while (modes != 0 && modes[i] != 0) {
@ -505,11 +530,11 @@ static int parseFuseMap(char *ptr) {
if (
(lastfuse == 0 ||
galinfo[i].fuses == lastfuse ||
galinfo[i].uesfuse == lastfuse && galinfo[i].uesfuse + 8 * galinfo[i].uesbytes == galinfo[i].fuses)
(galinfo[i].uesfuse == lastfuse && galinfo[i].uesfuse + 8 * galinfo[i].uesbytes == galinfo[i].fuses))
&&
(pins == 0 ||
galinfo[i].pins == pins ||
galinfo[i].pins == 24 && pins == 28)
(galinfo[i].pins == 24 && pins == 28))
) {
if (gal == 0) {
type = i;
@ -535,11 +560,14 @@ static int parseFuseMap(char *ptr) {
return n;
}
static char readJedec(void) {
static char readFile(int* fileSize) {
FILE* f;
int size;
f = fopen(filename, "r");
if (verbose) {
printf("opening file: '%s'\n", filename);
}
f = fopen(filename, "rb");
if (f) {
size = fread(galbuffer, 1, GALBUFSIZE, f);
fclose(f);
@ -548,9 +576,20 @@ static char readJedec(void) {
printf("Error: failed to open file: %s\n", filename);
return -1;
}
if (fileSize != NULL) {
*fileSize = size;
if (verbose) {
printf("file size: %d'\n", size);
}
}
return 0;
}
static char checkForString(char* buf, int start, const char* key) {
int labelPos = strstr(buf + start, key) - buf;
return (labelPos > 0 && labelPos < 500) ? 1 : 0;
}
static int openSerial(void) {
char buf[512] = {0};
char devName[256] = {0};
@ -559,6 +598,9 @@ static int openSerial(void) {
//open device name
if (deviceName == 0) {
serialDeviceGuessName(&deviceName);
}
snprintf(devName, sizeof(devName), "%s", (deviceName == 0) ? DEFAULT_SERIAL_DEVICE_NAME : deviceName);
serialDeviceCheckName(devName, sizeof(devName));
@ -584,14 +626,17 @@ static int openSerial(void) {
//check we are communicating with Afterburner programmer
labelPos = strstr(buf, "AFTerburner v.") - buf;
bigRam = 0;
if (labelPos >= 0 && labelPos < 500 && buf[total - 3] == '>') {
// check for new board desgin: variable VPP
labelPos = strstr(buf + labelPos, " varVpp ") - buf;
if (labelPos > 0 && labelPos < 500) {
if (verbose) {
printf("variable VPP board detected\n");
}
varVppExists = 1;
varVppExists = checkForString(buf, labelPos, " varVpp ");
if (verbose && varVppExists) {
printf("variable VPP board detected\n");
}
// check for Big Ram
bigRam = checkForString(buf, labelPos, " RAM-BIG");
if (verbose & bigRam) {
printf("MCU Big RAM detected\n");
}
//all OK
return 0;
@ -707,6 +752,10 @@ static int waitForSerialPrompt(char* buf, int bufSize, int maxDelay) {
} else {
buf += readSize;
bufSize -= readSize;
if (bufSize <= 0) {
printf("ERROR: serial port read buffer is too small!\nAre you dumping large amount of data?\n");
return -1;
}
}
if (printSerialWhileWaiting) {
bufPrint = printBuffer(bufPrint, readSize);
@ -719,25 +768,22 @@ static int waitForSerialPrompt(char* buf, int bufSize, int maxDelay) {
maxDelay -= 10;
#else
maxDelay -= 30;
#endif
#endif
if(maxDelay <= 0 && verbose) {
printf("waitForSerialPrompt timed out\n");
}
}
}
return bufPos;
}
static int sendLine(char* buf, int bufSize, int maxDelay) {
static int sendBuffer(char* buf) {
int total;
int writeSize;
char* obuf = buf;
if (serialF == INVALID_HANDLE) {
return -1;
}
if (buf == 0) {
return -1;
}
total = strlen(buf);
// write the query into the serial port's file
// file is opened non blocking so we have to ensure all contents is written
@ -750,8 +796,26 @@ static int sendLine(char* buf, int bufSize, int maxDelay) {
buf += writeSize;
total -= writeSize;
}
return 0;
}
static int sendLine(char* buf, int bufSize, int maxDelay) {
int total;
char* obuf = buf;
if (serialF == INVALID_HANDLE) {
return -1;
}
total = sendBuffer(buf);
if (total) {
return total;
}
total = waitForSerialPrompt(obuf, bufSize, (maxDelay < 0) ? 6 : maxDelay);
if (total < 0) {
return total;
}
obuf[total] = 0;
obuf = stripPrompt(obuf);
if (verbose) {
@ -761,6 +825,17 @@ static int sendLine(char* buf, int bufSize, int maxDelay) {
return total;
}
static void updateProgressBar(char* label, int current, int total) {
int done = ((current + 1) * 40) / total;
if (current >= total) {
printf("%s%5d/%5d |########################################|\n", label, total, total);
} else {
printf("%s%5d/%5d |", label, current, total);
printf("%.*s%*s|\r", done, "########################################", 40 - done, "");
fflush(stdout); //flush the text out so that the animation of the progress bar looks smooth
}
}
// Upload fusemap in byte format (as opposed to bit format used in JEDEC file).
static char upload() {
char fuseSet;
@ -788,7 +863,6 @@ static char upload() {
fuseSet = 0;
printf("Uploading fuse map...\n");
printf("\e[?25l");
for (i = 0; i < totalFuses;) {
unsigned char f = 0;
if (i % 32 == 0) {
@ -817,12 +891,9 @@ static char upload() {
sprintf(line, "%02X", f);
strcat(buf, line);
printf("%4d/%4d |", i + 1, totalFuses);
int done = ((i + 1) * 40) / totalFuses;
printf("%.*s%*s|\r", done, "########################################", 40 - done, "");
updateProgressBar("", i, totalFuses);
}
printf("%4d/%4d |########################################|\n", totalFuses, totalFuses);
printf("\e[?25h");
updateProgressBar("", totalFuses, totalFuses);
// send last unfinished fuse line
if (fuseSet) {
@ -877,7 +948,7 @@ static char operationWriteOrVerify(char doWrite) {
char result;
if (readJedec()) {
if (readFile(NULL)) {
return -1;
}
@ -902,7 +973,7 @@ static char operationWriteOrVerify(char doWrite) {
// write command
if (doWrite) {
result = sendGenericCommand("w\r", "write failed ?", 4000, 0);
result = sendGenericCommand("w\r", "write failed ?", 8000, 0);
if (result) {
goto finish;
}
@ -910,7 +981,7 @@ static char operationWriteOrVerify(char doWrite) {
// verify command
if (opVerify) {
result = sendGenericCommand("v\r", "verify failed ?", 4000, 0);
result = sendGenericCommand("v\r", "verify failed ?", 8000, 0);
}
finish:
closeSerial();
@ -968,20 +1039,18 @@ static char operationTestVpp(void) {
static char operationCalibrateVpp(void) {
char result;
char cmd [8] = {0};
char val = (char)('0' + (calOffset + 32));
if (openSerial() != 0) {
return -1;
}
if (calOffset != 0xFFFF) {
char cmd [8] = {0};
char val = (char)('0' + (calOffset + 20) / 5);
sprintf(cmd, "B%c\r", val);
if (verbose) {
printf("sending 'B%c' command...\n", val);
}
result = sendGenericCommand(cmd, "VPP cal. offset failed", 4000, 1);
sprintf(cmd, "B%c\r", val);
if (verbose) {
printf("sending 'B%c' command...\n", val);
}
result = sendGenericCommand(cmd, "VPP cal. offset failed", 4000, 1);
if (verbose) {
printf("sending 'b' command...\n");
@ -989,7 +1058,7 @@ static char operationCalibrateVpp(void) {
printf("VPP voltages are scanned - this might take a while...\n");
printSerialWhileWaiting = 1;
result = sendGenericCommand("b\r", "VPP calibration failed", 16000, 1);
result = sendGenericCommand("b\r", "VPP calibration failed", 34000, 1);
printSerialWhileWaiting = 0;
closeSerial();
@ -1009,7 +1078,7 @@ static char operationMeasureVpp(void) {
//print the measured voltages if the feature is available
printSerialWhileWaiting = 1;
result = sendGenericCommand("m\r", "VPP measurement failed", 22000, 1);
result = sendGenericCommand("m\r", "VPP measurement failed", 40000, 1);
printSerialWhileWaiting = 0;
closeSerial();
@ -1149,7 +1218,7 @@ static char operationReadFuses(void) {
//READ_FUSE command
sprintf(buf, "r\r");
readSize = sendLine(buf, GALBUFSIZE, 5000);
readSize = sendLine(buf, GALBUFSIZE, 12000);
if (readSize < 0) {
return -1;
}
@ -1165,6 +1234,299 @@ static char operationReadFuses(void) {
return 0;
}
static int readJtagSerialLine(char* buf, int bufSize, int maxDelay, int* feedRequest) {
char* bufStart = buf;
int readSize;
int bufPos = 0;
memset(buf, 0, bufSize);
while (maxDelay > 0) {
readSize = serialDeviceRead(serialF, buf, 1);
if (readSize > 0) {
bufPos += readSize;
buf[1] = 0;
//handle the feed request
if (buf[0] == '$') {
char tmp[5];
bufPos -= readSize;
buf[0] = 0;
//extra 5 bytes should be present: 3 bytes of size, 2 new line chars
readSize = serialDeviceRead(serialF, tmp, 3);
if (readSize == 3) {
int retry = 1000;
tmp[3] = 0;
*feedRequest = atoi(tmp);
maxDelay = 0; //force exit
//read the extra 2 characters (new line chars)
while (retry && readSize != 2) {
readSize = serialDeviceRead(serialF, tmp, 2);
retry--;
}
if (readSize != 2 || tmp[0] != '\r' || tmp[1] != '\n') {
printf("Warning: corrupted feed request ! %d \n", readSize);
}
} else {
printf("Warning: corrupted feed request! %d \n", readSize);
}
//printf("***\n");
} else
if (buf[0] == '\r') {
readSize = serialDeviceRead(serialF, buf, 1); // read \n coming from Arduino
//printf("-%c-\n", buf[0] == '\n' ? 'n' : 'r');
buf[0] = 0;
bufPos++;
maxDelay = 0; //force exit
} else {
//printf("(0x%02x %d) \n", buf[0], (int) buf[0]);
buf += readSize;
if (bufPos == bufSize) {
printf("ERROR: serial port read buffer is too small!\nAre you dumping large amount of data?\n");
return -1;
}
}
}
if (maxDelay > 0) {
/* WIN_API handles timeout itself */
#ifndef _USE_WIN_API_
usleep(1 * 1000);
maxDelay -= 10;
#else
maxDelay -= 30;
#endif
}
}
return bufPos;
}
static int playJtagFile(char* label, int fSize, int vpp, int showProgress) {
char buf[MAX_LINE] = {0};
int sendPos = 0;
int lastSendPos = 0;
char ready = 0;
int result = 0;
unsigned int csum = 0;
int feedRequest = 0;
// support for XCOMMENT messages which might be interrupted by a feed request
int continuePrinting = 0;
if (openSerial() != 0) {
return -1;
}
//compute check sum
if (verbose) {
int i;
for (i = 0; i < fSize; i++) {
csum += (unsigned char) galbuffer[i];
}
}
// send start-JTAG-player command
sprintf(buf, "j%d\r", vpp ? 1: 0);
sendBuffer(buf);
// read response from MCU and feed the XSVF player with data
while(1) {
int readBytes;
feedRequest = 0;
buf[0] = 0;
readBytes = readJtagSerialLine(buf, MAX_LINE, 3000, &feedRequest);
//printf(">> read %d len=%d cp=%d '%s'\n", readBytes, (int) strlen(buf), continuePrinting, buf);
//request to send more data was received
if (feedRequest > 0) {
if (ready) {
int chunkSize = fSize - sendPos;
if (chunkSize > feedRequest) {
chunkSize = feedRequest;
// make the initial chunk big so the data are buffered by the OS
if (sendPos == 0) {
chunkSize *= 2;
if (chunkSize > fSize) {
chunkSize = fSize;
}
}
}
if (chunkSize > 0) {
// send the data over serial line
int w = serialDeviceWrite(serialF, galbuffer + sendPos, chunkSize);
sendPos += w;
// print progress / file position
if (showProgress && (sendPos - lastSendPos >= 1024 || sendPos == fSize)) {
lastSendPos = sendPos;
updateProgressBar(label, sendPos, fSize);
}
}
}
if (readBytes > 2) {
continuePrinting = 1;
}
}
// when the feed request was detected, there might be still some data in the buffer
if (buf[0] != 0) {
//prevous line had a feed request - this is a continuation
if (feedRequest == 0 && continuePrinting) {
continuePrinting = 0;
printf("%s\n", buf);
} else
//print debug messages
if (buf[0] == 'D') {
if (feedRequest) { // the rest of the message will follow
printf("%s", buf + 1);
} else {
printf("%s\n", buf + 1);
}
}
// quit
if (buf[0] == 'Q') {
result = atoi(buf + 1);
//print error result
if (result != 0) {
printf("%s\n", buf + 1);
} else
// when all is OK and verbose mode is on, then print the checksum for comparison
if (verbose) {
printf("PC : 0x%08X\n", csum);
}
break;
} else
// ready to receive anouncement
if (strcmp("RXSVF", buf) == 0) {
ready = 1;
} else
// print important messages
if (buf[0] == '!') {
// in verbose mode print all messages, otherwise print only success or fail messages
if (verbose || 0 == strcmp("!Success", buf) || 0 == strcmp("!Fail", buf)) {
printf("%s\n", buf + 1);
}
}
#if 0
//print all the rest
else if (verbose) {
printf("'%s'\n", buf);
}
#endif
} else
// the buffer is empty but there was a feed request just before - print a new line
if (readBytes > 0 && continuePrinting) {
printf("\n");
continuePrinting = 0;
}
}
readJtagSerialLine(buf, MAX_LINE, 1000, &feedRequest);
closeSerial();
return result;
}
static int processJtagInfo(void) {
int result;
int fSize = 0;
char tmp[256];
if (!opInfo) {
return 0;
}
if (!(gal == ATF1502AS || gal == ATF1504AS)) {
printf("error: infor command is unsupported");
return 1;
}
// Use default .xsvf file for erase if no file is provided.
// if the file is provided while write operation is also requested
// then the file is specified for writing -> do not use it for erasing
sprintf(tmp, "xsvf/id_ATF150X.xsvf");
filename = tmp;
result = readFile(&fSize);
if (result) {
return result;
}
//play the info file and use high VPP
return playJtagFile("", fSize, 1, 0);
}
static int processJtagErase(void) {
int result;
int fSize = 0;
char tmp[256];
char* originalFname = filename;
if (!opErase) {
return 0;
}
// Use default .xsvf file for erase.
sprintf(tmp, "xsvf/erase_%s.xsvf", galinfo[gal].name);
filename = tmp;
result = readFile(&fSize);
if (result) {
filename = originalFname;
return result;
}
filename = originalFname;
//play the erase file and use high VPP
return playJtagFile("erase ", fSize, 1, 1);
}
static int processJtagWrite(void) {
int result;
int fSize = 0;
if (!opWrite) {
return 0;
}
// paranoid: this condition should be already checked during argument's check
if (0 == filename) {
return -1;
}
result = readFile(&fSize);
if (result) {
return result;
}
//play the file and use low VPP
return playJtagFile("write ", fSize, 0, 1);
}
static int processJtag(void) {
int result;
if (verbose) {
printf("JTAG\n");
}
if ((gal == ATF1502AS || gal == ATF1504AS) && (opRead || opVerify)) {
printf("error: read and verify operation is not supported\n");
return 1;
}
result = processJtagInfo();
if (result) {
return result;
}
result = processJtagErase();
if (result) {
return result;
}
result = processJtagWrite();
if (result) {
return result;
}
return 0;
}
int main(int argc, char** argv) {
char result = 0;
int i;
@ -1177,6 +1539,12 @@ int main(int argc, char** argv) {
printf("Afterburner " VERSION " \n");
}
// process JTAG operations
if (gal != 0 && galinfo[gal].id0 == JTAG_ID && galinfo[gal].id1 == JTAG_ID) {
result = processJtag();
goto finish;
}
result = operationSetGalCheck();
if (gal != UNKNOWN && 0 == result) {
@ -1219,6 +1587,7 @@ int main(int argc, char** argv) {
}
}
finish:
if (verbose) {
printf("result=%i\n", (char)result);
}

View File

@ -2,6 +2,8 @@
#define _SERIAL_PORT_H_
char guessedSerialDevice[512] = {0};
#ifdef _USE_WIN_API_
#include <windows.h>
@ -10,11 +12,61 @@
#define DEFAULT_SERIAL_DEVICE_NAME "COM1"
#define INVALID_HANDLE INVALID_HANDLE_VALUE
#ifdef NO_CLOSE
static SerialDeviceHandle serH = INVALID_HANDLE;
#endif
// ideas: https://stackoverflow.com/questions/1388871/how-do-i-get-a-list-of-available-serial-ports-in-win32
static void serialDeviceGuessName(char** deviceName) {
char buf[64 * 1024] = {0};
int size = QueryDosDevice(NULL, buf, 64 * 1024);
int topComNum = 0;
// buffer was filled in
if (size > 0) {
char* text = buf;
int pos = 0;
int start = 0;
// search the received buffer for COM string
while (pos < size) {
int nameLen;
start = pos;
// find the string terminator
while(pos < size && buf[pos] != 0) {
pos++;
}
nameLen = pos - start;
// COM port found
if (nameLen >= 4 && nameLen <=6 && 0 == strncmp(text, "COM", 3) && text[3] >= '0' && text[3] <= '9') {
int comNum = atoi(text + 3);
if (comNum > topComNum) {
topComNum = comNum;
strcpy(guessedSerialDevice, text);
}
}
pos++;
text = &buf[pos];
}
// if we have found a COM port then pass it back as the result
if (topComNum > 0) {
*deviceName = guessedSerialDevice;
}
}
}
// https://www.xanthium.in/Serial-Port-Programming-using-Win32-API
static inline SerialDeviceHandle serialDeviceOpen(char* deviceName) {
SerialDeviceHandle h;
#ifdef NO_CLOSE
if (serH != INVALID_HANDLE) {
return serH;
}
#endif
h = CreateFile(
deviceName, //port name
GENERIC_READ | GENERIC_WRITE, //Read/Write
@ -67,7 +119,9 @@ static inline SerialDeviceHandle serialDeviceOpen(char* deviceName) {
}
//ensure no leftover bytes exist on the serial line
result = PurgeComm(h, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
#ifdef NO_CLOSE
serH = h;
#endif
return h;
} else {
return INVALID_HANDLE;
@ -100,7 +154,9 @@ void serialDeviceCheckName(char* name, int maxSize) {
}
static inline void serialDeviceClose(SerialDeviceHandle deviceHandle) {
#ifndef NO_CLOSE
CloseHandle(deviceHandle);
#endif
}
static inline int serialDeviceWrite(SerialDeviceHandle deviceHandle, char* buffer, int bytesToWrite) {
@ -128,11 +184,58 @@ static inline int serialDeviceRead(SerialDeviceHandle deviceHandle, char* buffer
#define DEFAULT_SERIAL_DEVICE_NAME "/dev/ttyUSB0"
#define INVALID_HANDLE -1
#ifdef NO_CLOSE
static SerialDeviceHandle serH = INVALID_HANDLE;
#endif
#ifdef _OSX_
#define CHECK_SERIAL() (text != NULL)
#define LIST_DEVICES "ls -1 /dev/tty.usb* /dev/tty.wchusb* 2>/dev/null"
#else
#define CHECK_SERIAL() (text != NULL && 0 == strncmp(text + 18, "usb-", 4))
#define LIST_DEVICES "ls -1 /dev/serial/by-id/* 2>/dev/null"
#endif
// ideas: https://unix.stackexchange.com/questions/647235/debian-how-to-identify-usb-devices-with-similar-dev-tty-file
// https://pubs.opengroup.org/onlinepubs/009696799/functions/popen.html
static void serialDeviceGuessName(char** deviceName) {
int i;
char text[512];
int topTtyNum = 0;
FILE* f = popen(LIST_DEVICES, "r");
if (f == NULL) {
return;
}
i = 1;
while (fgets(text, 512, f) != NULL) {
// filer out non USB devices and prioritise those with Arduino name
if (CHECK_SERIAL()) {
int ttyNum = i;
int textLen = strlen(text);
// prefer Arduino over generic USB serial ports (does not work on OSX)
if (textLen > 29 && 0 == strncmp(text + 18, "usb-Arduino", 11)) {
ttyNum += 1000;
}
if (ttyNum > topTtyNum) {
topTtyNum = ttyNum;
strcpy(guessedSerialDevice, text);
//strip the new-line trailing markers
if (guessedSerialDevice[textLen - 1] == '\n' || guessedSerialDevice[textLen - 1] == '\r') {
guessedSerialDevice[textLen - 1] = 0;
}
}
}
i++;
}
pclose(f);
if (topTtyNum > 0) {
*deviceName = guessedSerialDevice;
}
}
static inline SerialDeviceHandle serialDeviceOpen(char* deviceName) {
SerialDeviceHandle h;

310
utils/jtag/device.py Normal file
View File

@ -0,0 +1,310 @@
# Be careful modifying this file: line ranges from it are included in docs/jtag/as.rst.
import enum
from bitarray import bitarray
__all__ = ['ATF15xxInstr', 'ATF1502ASDevice', 'ATF1504ASDevice', 'ATF1508ASDevice']
class ATF15xxInstr(enum.IntEnum):
EXTEST = 0x000
SAMPLE = 0x055
IDCODE = 0x059
ISC_READ_UES = 0x270
ISC_CONFIG = 0x280
ISC_READ = 0x28c
ISC_DATA = 0x290
ISC_PROGRAM_ERASE = 0x29e
ISC_ADDRESS = 0x2a1
ISC_LATCH_ERASE = 0x2b3
ISC_UNKNOWN = 0x2bf
BYPASS = 0x3ff
class ATF15xxDevice:
idcode = None # int
fuse_count = None # int
data_width = None # dict(range/tuple,range)
@classmethod
def word_size(cls, svf_row):
for svf_rows, svf_cols in cls.data_width.items():
if svf_row in svf_rows:
return len(svf_cols)
assert False
@staticmethod
def jed_to_svf_coords(jed_index):
raise NotImplementedError
@classmethod
def jed_to_svf(cls, jed_bits):
svf_bits = {}
for jed_index, jed_bit in enumerate(jed_bits):
svf_index = cls.jed_to_svf_coords(jed_index)
if svf_index is None: continue
svf_row, svf_col = svf_index
if svf_row not in svf_bits:
svf_bits[svf_row] = bitarray(cls.word_size(svf_row))
svf_bits[svf_row].setall(1)
svf_bits[svf_row][svf_col] = jed_bit
return svf_bits
@staticmethod
def svf_to_jed_coords(svf_row, svf_col):
raise NotImplementedError
@classmethod
def svf_to_jed(cls, svf_bits):
jed_bits = bitarray(cls.fuse_count)
jed_bits.setall(0)
for svf_row, svf_word in svf_bits.items():
for svf_col, svf_bit in enumerate(svf_word):
jed_index = cls.svf_to_jed_coords(svf_row, svf_col)
if jed_index is None: continue
jed_bits[jed_index] = svf_bit
return jed_bits
class ATF1502ASDevice(ATF15xxDevice):
idcode = 0x0150203f
fuse_count = 16808
data_width = {
range( 0, 108): range(86),
range(128, 229): range(86),
(256,): range(32),
(512,): range(4),
(768,): range(16),
}
@staticmethod
def jed_to_svf_coords(jed_index):
if jed_index in range( 0, 7680):
return 12 + (jed_index - 0) % 96, 79 - (jed_index - 0) // 96
if jed_index in range( 7680, 15360):
return 128 + (jed_index - 7680) % 96, 79 - (jed_index - 7680) // 96
if jed_index in range(15360, 16320):
return 0 + (jed_index - 15360) // 80, 79 - (jed_index - 15360) % 80
if jed_index in range(16320, 16720):
return 224 + (jed_index - 16320) % 5, 79 - (jed_index - 16320) // 5
if jed_index in range(16720, 16750):
return 224 + (jed_index - 16320) % 5, 85 - (jed_index - 16320) // 5 + 80
if jed_index in range(16750, 16782):
return 256, 31 - (jed_index - 16750)
if jed_index in range(16782, 16786):
return 512, 3 - (jed_index - 16782)
if jed_index in range(16786, 16802):
return 768, 15 - (jed_index - 16786)
if jed_index in range(16802, 16808):
return # reserved
assert False
@staticmethod
def svf_to_jed_coords(svf_row, svf_col):
if svf_row in range( 0, 12):
if svf_col in range(0, 80):
return 15360 + (svf_row - 0) * 80 + (79 - svf_col)
else:
return # always 1
if svf_row in range( 12, 108):
if svf_col in range(0, 80):
return 0 + (svf_row - 12) + (79 - svf_col) * 96
else:
return # always 1
if svf_row in range(128, 224):
if svf_col in range(0, 80):
return 7680 + (svf_row - 128) + (79 - svf_col) * 96
else:
return # always 1
if svf_row in range(224, 229):
if svf_col in range(0, 80):
return 16320 + (svf_row - 224) + (79 - svf_col) * 5
else:
return 16720 + (svf_row - 224) + (85 - svf_col) * 5
if svf_row == 256:
return 16750 + (31 - svf_col)
if svf_row == 512:
return 16782 + ( 3 - svf_col)
if svf_row == 768:
return 16786 + (15 - svf_col)
assert False
class ATF1504ASDevice(ATF15xxDevice):
idcode = 0x0150403f
fuse_count = 34192
data_width = {
range( 0, 108): range(166),
range(128, 233): range(166),
(256,): range(32),
(512,): range(4),
(768,): range(16),
}
@staticmethod
def jed_to_svf_coords(jed_index):
if jed_index in range( 0, 15360):
return 12 + (jed_index - 0) % 96, 165 - (jed_index - 0) // 96
if jed_index in range(15360, 30720):
return 128 + (jed_index - 15360) % 96, 165 - (jed_index - 15360) // 96
if jed_index in range(30720, 32640):
return 0 + (jed_index - 30720) // 160, 165 - (jed_index - 30720) % 160
if jed_index in range(32640, 34134):
return 224 + (jed_index - 32640) % 9, 165 - (jed_index - 32640) // 9
if jed_index in range(34134, 34166):
return 256, 31 - (jed_index - 34134)
if jed_index in range(34166, 34170):
return 512, 3 - (jed_index - 34166)
if jed_index in range(34170, 34186):
return 768, 15 - (jed_index - 34170)
if jed_index in range(34186, 34192):
return # reserved
assert False
@staticmethod
def svf_to_jed_coords(svf_row, svf_col):
if svf_row in range( 0, 12):
if svf_col in range(6, 166):
return 30720 + (svf_row - 0) * 160 + (165 - svf_col)
else:
return # always 1
if svf_row in range( 12, 108):
if svf_col in range(6, 166):
return 0 + (svf_row - 12) + (165 - svf_col) * 96
else:
return # always 1
if svf_row in range(128, 224):
if svf_col in range(6, 166):
return 15360 + (svf_row - 128) + (165 - svf_col) * 96
else:
return # always 1
if svf_row in range(224, 233):
if svf_col in range(0, 166):
return 32640 + (svf_row - 224) + (165 - svf_col) * 9
else:
return # always 1
if svf_row == 256:
return 34134 + (31 - svf_col)
if svf_row == 512:
return 34166 + ( 3 - svf_col)
if svf_row == 768:
return 34170 + (15 - svf_col)
assert False
class ATF1508ASDevice(ATF15xxDevice):
idcode = 0x0150803f
fuse_count = 74136
data_width = {
range( 0, 108): range(326),
range(128, 251): range(326),
(256,): range(32),
(512,): range(4),
(768,): range(16),
}
@staticmethod
def jed_to_svf_coords(jed_index):
if jed_index in range( 0, 30720):
return 12 + (jed_index - 0) % 96, 325 - (jed_index - 0) // 96
if jed_index in range(30720, 61440):
return 128 + (jed_index - 30720) % 96, 325 - (jed_index - 30720) // 96
if jed_index in range(61440, 65280):
return 0 + (jed_index - 61440) // 320, 325 - (jed_index - 61440) % 320
if jed_index in range(65280, 74082):
return 224 + (jed_index - 65280) % 27, 325 - (jed_index - 65280) // 27
if jed_index in range(74082, 74114):
return 256, 31 - (jed_index - 74082)
if jed_index in range(74114, 74118):
return 512, 3 - (jed_index - 74114)
if jed_index in range(74118, 74134):
return 768, 15 - (jed_index - 74118)
if jed_index in range(74134, 74136):
return # reserved
assert False
@staticmethod
def svf_to_jed_coords(svf_row, svf_col):
if svf_row in range( 0, 12):
if svf_col in range(6, 326):
return 61440 + (svf_row - 0) * 320 + (325 - svf_col)
else:
return # always 1
if svf_row in range( 12, 108):
if svf_col in range(6, 326):
return 0 + (svf_row - 12) + (325 - svf_col) * 96
else:
return # always 1
if svf_row in range(128, 224):
if svf_col in range(6, 326):
return 30720 + (svf_row - 128) + (325 - svf_col) * 96
else:
return # always 1
if svf_row in range(224, 251):
if svf_col in range(0, 326):
return 65280 + (svf_row - 224) + (325 - svf_col) * 27
else:
return # always 1
if svf_row == 256:
return 74082 + (31 - svf_col)
if svf_row == 512:
return 74114 + ( 3 - svf_col)
if svf_row == 768:
return 74118 + (15 - svf_col)
assert False
if __name__ == '__main__':
with open('atf1502as_svf2jed.csv', 'w') as f:
f.write('SVF ROW,SVF COL,JED\n')
for svf_rows, svf_cols in ATF1502ASDevice.data_width.items():
for svf_row in svf_rows:
for svf_col in svf_cols:
jed_index = ATF1502ASDevice.svf_to_jed_coords(svf_row, svf_col)
if jed_index is None: jed_index = 0x7fff
f.write('{},{},{}\n'.format(svf_row, svf_col, jed_index))
with open('atf1502as_jed2svf.csv', 'w') as f:
f.write('JED,SVF ROW,SVF COL\n')
for jed_index in range(ATF1502ASDevice.fuse_count):
svf_index = ATF1502ASDevice.jed_to_svf_coords(jed_index)
if svf_index is None: continue
f.write('{},{},{}\n'.format(jed_index, *svf_index))
with open('atf1504as_svf2jed.csv', 'w') as f:
f.write('SVF ROW,SVF COL,JED\n')
for svf_rows, svf_cols in ATF1504ASDevice.data_width.items():
for svf_row in svf_rows:
for svf_col in svf_cols:
jed_index = ATF1504ASDevice.svf_to_jed_coords(svf_row, svf_col)
if jed_index is None: jed_index = 0xffff
f.write('{},{},{}\n'.format(svf_row, svf_col, jed_index))
with open('atf1504as_jed2svf.csv', 'w') as f:
f.write('JED,SVF ROW,SVF COL\n')
for jed_index in range(ATF1504ASDevice.fuse_count):
svf_index = ATF1504ASDevice.jed_to_svf_coords(jed_index)
if svf_index is None: continue
f.write('{},{},{}\n'.format(jed_index, *svf_index))
with open('atf1508as_svf2jed.csv', 'w') as f:
f.write('SVF ROW,SVF COL,JED\n')
for svf_rows, svf_cols in ATF1508ASDevice.data_width.items():
for svf_row in svf_rows:
for svf_col in svf_cols:
jed_index = ATF1508ASDevice.svf_to_jed_coords(svf_row, svf_col)
if jed_index is None: jed_index = 0x1ffff
f.write('{},{},{}\n'.format(svf_row, svf_col, jed_index))
with open('atf1508as_jed2svf.csv', 'w') as f:
f.write('JED,SVF ROW,SVF COL\n')
for jed_index in range(ATF1508ASDevice.fuse_count):
svf_index = ATF1508ASDevice.jed_to_svf_coords(jed_index)
if svf_index is None: continue
f.write('{},{},{}\n'.format(jed_index, *svf_index))

24
utils/jtag/example_jed2xsvf.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/bash
set -e
# specify name of your PLD design and device to run on
APP=counter
DEV=ATF1502AS
PKG=PLCC44
SPD=15
## compile Verilog design by yosys
#../../../yosys/atf15xx_yosys/run_yosys.sh $APP > $APP.log
## use Atmel fitter to produce .jed file
#../../yosys/atf15xx_yosys/run_fitter.sh -d $DEV -p $PKG -s $SPD $APP -preassign keep -tdi_pullup on -tms_pullup on -output_fast off -xor_synthesis on $*
# convert jed to svf
python3 ./fuseconv.py -d $DEV $APP.jed $APP.svf
# convert svf to xsvf
python3 .//svf2xsvf.py $APP.svf $APP.xsvf
date
echo "done!"

223
utils/jtag/fuseconv.py Normal file
View File

@ -0,0 +1,223 @@
import argparse
import textwrap
from bitarray import bitarray
from jesd3 import JESD3Parser
from svf import SVFParser, SVFEventHandler
from device import *
def read_jed(file):
parser = JESD3Parser(file.read())
parser.parse()
return parser.fuse, parser.design_spec
def write_jed(file, jed_bits, *, comment):
assert '*' not in comment
file.write("\x02{}*\n".format(comment))
file.write("QF{}* F0*\n".format(len(jed_bits)))
chunk_size = 64
for start in range(0, len(jed_bits), chunk_size):
file.write("L{:05d} {}*\n".format(start, jed_bits[start:start+chunk_size].to01()))
file.write("\x030000\n")
class ATFSVFEventHandler(SVFEventHandler):
def ignored(self, *args, **kwargs):
pass
svf_frequency = ignored
svf_trst = ignored
svf_state = ignored
svf_endir = ignored
svf_enddr = ignored
svf_hir = ignored
svf_sir = ignored
svf_tir = ignored
svf_hdr = ignored
svf_sdr = ignored
svf_tdr = ignored
svf_runtest = ignored
svf_piomap = ignored
svf_pio = ignored
def __init__(self):
self.ir = None
self.erase = False
self.addr = 0
self.data = b''
self.bits = {}
def svf_sir(self, tdi, smask, tdo, mask):
self.ir = int.from_bytes(tdi.tobytes(), 'little')
if self.ir == ATF15xxInstr.ISC_LATCH_ERASE:
self.erase = True
if self.ir == ATF15xxInstr.ISC_DATA:
self.erase = False
def svf_sdr(self, tdi, smask, tdo, mask):
if self.ir == ATF15xxInstr.ISC_ADDRESS:
self.addr = int.from_bytes(tdi.tobytes(), 'little')
if (self.ir & ~0x3) == ATF15xxInstr.ISC_DATA:
self.data = tdi
def svf_runtest(self, run_state, run_count, run_clock, min_time, max_time, end_state):
if not self.erase and self.ir == ATF15xxInstr.ISC_PROGRAM_ERASE:
self.bits[self.addr] = self.data
def read_svf(file):
handler = ATFSVFEventHandler()
parser = SVFParser(file.read(), handler)
parser.parse_file()
return handler.bits, ''
def _bitarray_to_hex(input_bits):
bits = bitarray(input_bits, endian="little")
bits.bytereverse()
bits.reverse()
return bits.tobytes().hex()
def write_svf(file, svf_bits, device, *, comment):
# This code is kind of awful.
def emit_header():
for comment_line in comment.splitlines():
file.write("// {}\n".format(comment_line))
file.write("TRST ABSENT;\n")
file.write("ENDIR IDLE;\n")
file.write("ENDDR IDLE;\n")
file.write("HDR 0;\n")
file.write("HIR 0;\n")
file.write("TDR 0;\n")
file.write("TIR 0;\n")
file.write("STATE RESET;\n")
def emit_check_idcode(idcode):
file.write("// Check IDCODE\n")
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.IDCODE))
file.write("SDR 32 TDI (fffeefff)\n\tTDO ({:08x})\n\tMASK (fffeefff);\n".format(idcode))
def emit_enable():
file.write("// ISC enable\n")
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_CONFIG))
file.write("SDR 10 TDI ({:03x});\n".format(0x1b9)) # magic constant?
file.write("STATE IDLE;\n")
def emit_disable():
file.write("// ISC disable\n")
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_CONFIG))
file.write("SDR 10 TDI ({:03x});\n".format(0x000))
file.write("STATE IDLE;\n")
def emit_unknown():
# ATMISP does this for unknown reasons. DR seems to be just BYPASS. Removing this
# doesn't do anything (and shouldn't do anything, since ATMISP doesn't go through RTI
# or capture/update DR), but let's keep it for now. Vendor tools wouldn't emit SIR
# without any reason whatsoever, right? Right??
file.write("// ISC unknown\n")
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_UNKNOWN))
def emit_erase():
file.write("// ISC erase\n")
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_LATCH_ERASE))
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_PROGRAM_ERASE))
file.write("RUNTEST IDLE 210E-3 SEC;\n")
emit_unknown()
def emit_program(address, data):
file.write("// ISC program word\n")
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_ADDRESS))
file.write("SDR 11 TDI ({:03x});\n".format(address))
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_DATA | (address >> 8)))
file.write("SDR {} TDI ({:0{}x});\n".format(len(data),
int(data.to01()[::-1], 2), len(data) // 4))
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_PROGRAM_ERASE))
file.write("RUNTEST IDLE 30E-3 SEC;\n")
emit_unknown()
def emit_verify(address, data):
file.write("// ISC verify word\n")
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_ADDRESS))
file.write("SDR 11 TDI ({:03x});\n".format(address))
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_READ))
file.write("RUNTEST IDLE 20E-3 SEC;\n")
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_DATA | (address >> 8)))
file.write("SDR {} TDI ({:0{}x})\n\tTDO ({:0{}x})\n\tMASK ({:0{}x});\n".format(len(data),
int(data.to01()[::-1], 2), len(data) // 4,
int(data.to01()[::-1], 2), len(data) // 4,
(1 << len(data)) - 1, len(data) // 4))
emit_header()
emit_check_idcode(device.idcode)
emit_enable()
emit_erase()
for svf_row in svf_bits:
emit_program(svf_row, svf_bits[svf_row])
for svf_row in svf_bits:
emit_verify(svf_row, svf_bits[svf_row])
emit_disable()
class ATFFileType(argparse.FileType):
def __call__(self, value):
file = super().__call__(value)
filename = file.name.lower()
if not (filename.endswith('.jed') or filename.endswith('.svf')):
raise argparse.ArgumentTypeError('{} is not a JED or SVF file'.format(filename))
return file
def arg_parser():
parser = argparse.ArgumentParser(description=textwrap.dedent("""
Convert between ATF15xx JED and SVF files. The type of the file is determined by the extension
(``.jed`` or ``.svf``, respectively).
If an SVF file is provided as an input, it is used to drive the JTAG programming state machine
of a simulated device. The state machine is greatly simplified and only functions correctly
when driven with vectors that program every non-reserved bit at least once.
"""))
parser.add_argument(
'-d', '--device', metavar='DEVICE',
choices=('ATF1502AS', 'ATF1504AS', 'ATF1508AS'), default='ATF1502AS',
help='Select the device to use.')
parser.add_argument(
'input', metavar='INPUT', type=ATFFileType('r'),
help='Read fuses from file INPUT.')
parser.add_argument(
'output', metavar='OUTPUT', type=ATFFileType('w'),
help='Write fuses to file OUTPUT.')
return parser
def main():
args = arg_parser().parse_args()
if args.device == 'ATF1502AS':
device = ATF1502ASDevice
elif args.device == 'ATF1504AS':
device = ATF1504ASDevice
elif args.device == 'ATF1508AS':
device = ATF1508ASDevice
else:
assert False
jed_bits = svf_bits = None
if args.input.name.lower().endswith('.jed'):
jed_bits, comment = read_jed(args.input)
if device.fuse_count != len(jed_bits):
raise SystemExit(f"Device has {device.fuse_count} fuses, JED file "
f"has {len(jed_bits)}; wrong --device option?")
elif args.input.name.lower().endswith('.svf'):
svf_bits, comment = read_svf(args.input)
else:
assert False
if args.output.name.lower().endswith('.jed'):
if jed_bits is None:
jed_bits = device.svf_to_jed(svf_bits)
write_jed(args.output, jed_bits, comment=comment)
elif args.output.name.lower().endswith('.svf'):
if svf_bits is None:
svf_bits = device.jed_to_svf(jed_bits)
write_svf(args.output, svf_bits, device, comment=comment)
else:
assert False
if __name__ == '__main__':
main()

300
utils/jtag/jesd3.py Normal file
View File

@ -0,0 +1,300 @@
# Vendored from glasgow.protocol.jesd3
# Ref: JEDEC JESD3-C
# Accession: G00029
import re
from bitarray import bitarray
__all__ = ["JESD3Parser", "JESD3ParsingError"]
class JESD3ParsingError(Exception):
pass
class JESD3Lexer:
"""
A JESD3 (JED) lexer.
:type buffer: str
:attr buffer:
Input buffer.
:type position: int
:attr position:
Offset into buffer from which the next field will be read.
"""
# This follows the JESD3-C grammar, with the exception that spaces are more permissive.
# As described, only 0x0D is allowed in between fields, which is absurd.
_fields = (
(r"N", r"[ \r\n]*(.*?)"),
(r"D", r".*?"),
(r"QF", r"([0-9]+)"),
(r"QP", r"([0-9]+)"),
(r"QV", r"([0-9]+)"),
(r"F", r"([01])"),
(r"L", r"([0-9]+)[ \r\n]+([01 \r\n]+)"),
(r"C", r"([0-9A-F]{4})"),
(r"EH", r"([0-9A-F]+)"),
(r"E", r"([01]+)"),
(r"UA", r"([\r\n\x20-\x29\x2B-\x7E]+)"),
(r"UH", r"([0-9A-F]+)"),
(r"U", r"([01]+)"),
(r"J", r"([0-9]+)[ \r\n]+([0-9]+)"),
(r"G", r"([01])"),
(r"X", r"([01])"),
(r"P", r"([ \r\n]*[0-9]+)+"),
(r"V", r"([0-9]+)[ \r\n]+([0-9BCDFHTUXZ]+)"),
(r"S", r"([01]+)"),
(r"R", r"([0-9A-F]{8})"),
(r"T", r"([0-9]+)"),
(r"A", r"([\r\n\x20-\x29\x2B-\x7E]*)([0-9]+)"),
)
_stx_spec_re = re.compile(r"\x02(.*?)\*[ \r\n]*", re.A|re.S)
_stx_quirk_re = re.compile(r"\x02()[ \r\n]*", re.A|re.S)
_etx_re = re.compile(r"\x03([0-9A-F]{4})", re.A|re.S)
_ident_re = re.compile(r"|".join(ident for ident, args in _fields), re.A|re.S)
_field_res = {ident: re.compile(ident + args + r"[ \r\n]*\*[ \r\n]*", re.A|re.S)
for ident, args in _fields}
def __init__(self, buffer, quirk_no_design_spec=False):
self.buffer = buffer
self.position = 0
self.checksum = 0
self._state = "start"
if quirk_no_design_spec:
self._stx_re = self._stx_quirk_re
else:
self._stx_re = self._stx_spec_re
def line_column(self, position=None):
"""
Return a ``(line, column)`` tuple for the given or, if not specified, current position.
Both the line and the column start at 1.
"""
line = len(re.compile(r"\n").findall(self.buffer, endpos=self.position))
if line > 1:
column = self.position - self.buffer.rindex("\n", 0, self.position)
else:
column = self.position
return line + 1, column + 1
def __iter__(self):
return self
def __next__(self):
"""Return the next token and advance the position."""
if self._state == "start":
match = self._stx_re.search(self.buffer, self.position)
if not match:
raise JESD3ParsingError("could not find STX marker")
else:
token = "start"
self._state = "fields"
self.checksum += sum(map(ord, match.group(0)))
elif self._state == "fields":
match = self._ident_re.match(self.buffer, self.position)
if match:
token = match.group(0)
match = self._field_res[token].match(self.buffer, self.position)
if not match:
raise JESD3ParsingError("field %s has invalid format at line %d, column %d"
% (token, *self.line_column()))
else:
self.checksum += sum(map(ord, match.group(0)))
else:
match = self._etx_re.match(self.buffer, self.position)
if not match:
raise JESD3ParsingError("unrecognized field at line %d, column %d (%r...)"
% (*self.line_column(),
self.buffer[self.position:self.position + 16]))
else:
token = "end"
self._state = "end"
self.checksum += 0x03
elif self._state == "end":
raise StopIteration
self.position = match.end()
return token, match.start(), match.groups()
class JESD3Parser:
def __init__(self, buffer, **kwargs):
self._lexer = JESD3Lexer(buffer, **kwargs)
self._position = 0
self.design_spec = ""
self.notes = []
self.fuse = None
self._fuse_default = None
self._fuse_bit_count = 0
self.electrical_fuse = None
self.user_fuse = None
self.security_fuse = None
self.device_id = None
def _parse_error(self, error):
raise JESD3ParsingError("%s at line %d, column %d"
% (error, *self._lexer.line_column(self._position)))
def parse(self):
for token, position, args in self._lexer:
self._position = position
# print("lexem: %r %r" % (token, args))
getattr(self, "_on_" + token)(*args)
def _on_start(self, design_spec):
"""Start marker and design specification"""
self.design_spec = design_spec
def _on_N(self, note):
"""Note"""
self.notes.append(note)
def _on_D(self):
"""Device (obsolete)"""
def _on_QF(self, count):
"""Fuse count"""
if self.fuse is not None:
self._parse_error("fuse count specified more than once")
self.fuse = bitarray(int(count, 10), endian="little")
def _on_QP(self, count):
"""Pin count (unsupported and ignored)"""
def _on_QV(self, count):
"""Test vector count (unsupported)"""
if int(count, 10) > 0:
self._parse_error("test vectors are unsupported")
def _on_F(self, state):
"""Fuse default state"""
if self.fuse is None:
self._parse_error("fuse default state specified before fuse count")
if self._fuse_default is not None:
self._parse_error("fuse default state specified more than once")
if self._fuse_bit_count > 0:
self._parse_error("fuse default state specified after fuse list")
self._fuse_default = int(state, 2)
self.fuse.setall(self._fuse_default)
def _on_L(self, index, values):
"""Fuse list"""
if self.fuse is None:
self._parse_error("fuse list specified before fuse count")
index = int(index, 10)
values = bitarray(re.sub(r"[ \r\n]", "", values), endian="little")
if index + len(values) > len(self.fuse):
self._parse_error("fuse list specifies range [%d:%d] beyond last fuse %d"
% (index, index + len(values), len(self.fuse)))
self.fuse[index:index + len(values)] = values
self._fuse_bit_count += len(values)
def _on_C(self, checksum):
"""Fuse checksum"""
expected_checksum = int(checksum, 16)
actual_checksum = sum(self.fuse.tobytes()) & 0xffff
if expected_checksum != actual_checksum:
self._parse_error("fuse checksum mismatch: expected %04X, actual %04X"
% (expected_checksum, actual_checksum))
def _set_electrical_fuse(self, value):
if self.electrical_fuse is not None:
self._parse_error("electrical fuse specified more than once")
self.electrical_fuse = value
def _on_EH(self, value):
"""Electrical fuse, hex"""
self._set_electrical_fuse(int(value, 16))
def _on_E(self, value):
"""Electrical fuse, binary"""
self._set_electrical_fuse(int(value, 2))
def _set_user_fuse(self, value):
if self.user_fuse is not None:
self._parse_error("user fuse specified more than once")
self.user_fuse = value
def _on_UA(self, value):
"""User fuse, 7-bit ASCII"""
int_value = 0
for char in reversed(value):
int_value <<= 7
int_value |= ord(char)
self._set_user_fuse(int_value)
def _on_UH(self, value):
"""User fuse, hex"""
self._set_user_fuse(int(value, 16))
def _on_U(self, value):
"""User fuse, binary"""
self._set_user_fuse(int(value, 2))
def _on_J(self, arch_code, pinout_code):
"""Device identification"""
if self.device_id is not None:
self._parse_error("device identification specified more than once")
self.device_id = (int(arch_code, 10), int(pinout_code, 10))
def _on_G(self, value):
"""Security fuse"""
if self.security_fuse is not None:
self._parse_error("security fuse specified more than once")
self.security_fuse = int(value, 2)
def _on_X(self, value):
"""Default test condition (unsupported and ignored)"""
def _on_P(self, pin_numbers):
"""Pin list (unsupported and ignored)"""
def _on_V(self, vector_number, test_conditions):
"""Test vector (unsupported and ignored)"""
def _on_S(self, test_condition):
"""Signature analysis starting vector (unsupported)"""
self._parse_error("signature analysis is not supported")
def _on_R(self, test_sum):
"""Signature analysis resulting vector (unsupported and ignored)"""
def _on_T(self, test_cycles):
"""Signature analysis test cycle count (unsupported and ignored)"""
def _on_A(self, subfield, delay):
"""Propagation delay for test vectors (unsupported and ignored)"""
def _on_end(self, checksum):
"""End marker and checksum"""
expected_checksum = int(checksum, 16)
if expected_checksum == 0x0000:
return
actual_checksum = self._lexer.checksum & 0xffff
if expected_checksum != actual_checksum:
self._parse_error("transmission checksum mismatch: expected %04X, actual %04X"
% (expected_checksum, actual_checksum))
if self._fuse_default is None and self._fuse_bit_count < len(self.fuse):
self._parse_error("fuse default state is not specified, and only %d out of %d fuse "
"bits are explicitly defined"
% (self._fuse_bit_count, len(self.fuse)))
if __name__ == "__main__":
import sys
with open(sys.argv[1], "r") as f:
parser = JESD3Parser(f.read(), quirk_no_design_spec=False)
parser.parse()
for i in range(0, len(parser.fuse) + 63, 64):
print("%08x: %s" % (i, parser.fuse[i:i + 64].to01()))

22
utils/jtag/readme.txt Normal file
View File

@ -0,0 +1,22 @@
JTAG conversion tools
---------------------------------------
fuseconv.py : converts ATF150X .jed file into .svf file
The origin of the conversion tool is this git repo:
https://github.com/whitequark/prjbureau
svf2xsvf.py : converts .svf file into .xsvf file
The origin of the tool is this git repo:
https://github.com/arduino/OpenOCD/tree/master/contrib/xsvf_tools
Typically you produce a .jed file for your ATF150X device either by
WinCUPL or by ATF15XX_Yosys (https://github.com/hoglet67/atf15xx_yosys/)
and then run fuseconv.py and svf2xsvf.py to produce .xsvf file that can be
used by Aftereburner's JTAG player.
See example_jed2xsvf.sh for more information how to run these tools.

906
utils/jtag/svf.py Normal file
View File

@ -0,0 +1,906 @@
# Vendored from glasgow.protocol.jtag_svf
# Ref: https://www.asset-intertech.com/eresources/svf-serial-vector-format-specification-jtag-boundary-scan
# Accession: G00022
# Ref: http://www.jtagtest.com/pdf/svf_specification.pdf
# Accession: G00023
import re
from abc import ABCMeta, abstractmethod
from bitarray import bitarray
__all__ = ["SVFParser", "SVFEventHandler"]
def _hex_to_bitarray(input_nibbles):
byte_len = (len(input_nibbles) + 1) // 2
input_bytes = bytes.fromhex(input_nibbles.rjust(byte_len * 2, "0"))
bits = bitarray(endian="little")
bits.frombytes(input_bytes)
bits.reverse()
bits.bytereverse()
return bits
_commands = (
"ENDDR", "ENDIR", "FREQUENCY", "HDR", "HIR", "PIO", "PIOMAP", "RUNTEST",
"SDR", "SIR", "STATE", "TDR", "TIR", "TRST",
)
_parameters = (
"ENDSTATE", "HZ", "MASK", "MAXIMUM", "SCK", "SEC", "SMASK", "TCK", "TDI", "TDO",
)
_trst_modes = (
"ON", "OFF", "Z", "ABSENT"
)
_tap_states = (
"RESET", "IDLE", "DRSELECT", "DRCAPTURE", "DRSHIFT", "DREXIT1", "DRPAUSE",
"DREXIT2", "DRUPDATE", "IRSELECT", "IRCAPTURE", "IRSHIFT", "IREXIT1", "IRPAUSE",
"IREXIT2", "IRUPDATE",
)
_tap_stable_states = (
"RESET", "IDLE", "IRPAUSE", "DRPAUSE"
)
class SVFParsingError(Exception):
pass
class SVFLexer:
"""
A Serial Vector Format lexer.
Comments (``! comment``, ``// comment``) are ignored.
The following tokens are recognized:
* Keyword (``HIR``, ``SIR``, ``TIO``, ..., ``;``), returned as Python ``str``;
* Integer (``8``, ``16``, ...), returned as Python ``int``;
* Real (``1E0``, ``1E+0``, ``1E-0``, ...), returned as Python ``float``;
* Bit array (``(0)``, ``(1234)``, ``(F00F)``, ...), returned as Python ``bitarray``;
* Literal (``(HLUDXZHHLL)``, ``(IN FOO)``, ...), returned as Python ``tuple(str,)``;
* End of file, returned as Python ``None``.
:type buffer: str
:attr buffer:
Input buffer.
:type position: int
:attr position:
Offset into buffer from which the next token will be read.
"""
_keywords = _commands + _parameters + _trst_modes + _tap_states + (";",)
_scanner = tuple((re.compile(src, re.A|re.I|re.M), act) for src, act in (
(r"\s+",
None),
(r"(?:!|//)([^\n]*)(?:\n|\Z)",
None),
(r"({})(?=\s+|[;()]|\Z)".format("|".join(_keywords)),
lambda m: m[1]),
(r"(\d+)(?=[^0-9\.E])",
lambda m: int(m[1])),
(r"(\d+(?:\.\d+)?(?:E[+-]?\d+)?)",
lambda m: float(m[1])),
(r"\(\s*([0-9A-F\s]+)\s*\)",
lambda m: _hex_to_bitarray(re.sub(r"\s+", "", m[1]))),
(r"\(\s*(.+?)\s*\)",
lambda m: (m[1],)),
(r"\Z",
lambda m: None),
))
def __init__(self, buffer):
self.buffer = buffer
self.position = 0
def line_column(self, position=None):
"""
Return a ``(line, column)`` tuple for the given or, if not specified, current position.
Both the line and the column start at 1.
"""
line = len(re.compile(r"\n").findall(self.buffer, endpos=self.position))
if line > 1:
column = self.position - self.buffer.rindex("\n", 0, self.position)
else:
column = self.position
return line + 1, column + 1
def _lex(self):
while True:
for token_re, action in self._scanner:
match = token_re.match(self.buffer, self.position)
# print(token_re, match)
if match:
if action is None:
self.position = match.end()
break
else:
return action(match), match.end()
else:
raise SVFParsingError("unrecognized SVF data at line %d, column %d (%s...)"
% (*self.line_column(),
self.buffer[self.position:self.position + 16]))
def peek(self):
"""Return the next token without advancing the position."""
token, _ = self._lex()
return token
def next(self):
"""Return the next token and advance the position."""
token, next_pos = self._lex()
self.position = next_pos
return token
def __iter__(self):
return self
def __next__(self):
token = self.next()
if token is None:
raise StopIteration
return token
class SVFParser:
"""
A Serial Vector Format streaming parser.
This parser maintains and allows querying lexical state (e.g. "sticky" ``TDI`` is
automatically tracked), and invokes the SVF event handler for all commands so that
any necessary action may be taken.
"""
def __init__(self, buffer, handler):
self._lexer = SVFLexer(buffer)
self._handler = handler
self._position = 0
self._token = None
self._cmd_pos = 0
self._param_tdi = \
{"HIR": None, "HDR": None, "SIR": None, "SDR": None, "TIR": None, "TDR": None}
self._param_mask = \
{"HIR": None, "HDR": None, "SIR": None, "SDR": None, "TIR": None, "TDR": None}
self._param_smask = \
{"HIR": None, "HDR": None, "SIR": None, "SDR": None, "TIR": None, "TDR": None}
self._param_run_state = "IDLE"
self._param_end_state = "IDLE"
def _try(self, action, *args):
try:
old_position = self._lexer.position
return action(*args)
except SVFParsingError as e:
self._lexer.position = old_position
return None
def _parse_token(self):
self._position = self._lexer.position
self._token = self._lexer.next()
# print("token %s @ %d" % (self._token, self._position))
return self._token
def _parse_error(self, error):
raise SVFParsingError("%s at line %d, column %d"
% (error, *self._lexer.line_column(self._position)))
def _parse_unexpected(self, expected, valid=()):
if isinstance(self._token, str):
actual = self._token
elif isinstance(self._token, int):
actual = "integer"
elif isinstance(self._token, float):
actual = "real"
elif isinstance(self._token, bitarray):
actual = "scan data"
elif isinstance(self._token, tuple):
actual = "(%s)" % (*self._token,)
elif self._token is None:
actual = "end of file"
else:
assert False
if valid:
self._parse_error("expected %s (one of %s), found %s"
% (expected, ", ".join(valid), actual))
else:
self._parse_error("expected %s, found %s"
% (expected, actual))
def _parse_keyword(self, keyword):
if self._parse_token() == keyword:
return self._token
else:
self._parse_unexpected("semicolon" if keyword == ";" else keyword)
def _parse_keywords(self, keywords):
if self._parse_token() in keywords:
return self._token
else:
self._parse_unexpected("one of {}".format(", ".join(keywords)))
def _parse_value(self, kind):
if isinstance(self._parse_token(), kind):
return self._token
else:
if kind == int:
expected = "integer"
elif kind == float:
expected = "real"
elif kind == (int, float):
expected = "number"
elif kind == bitarray:
expected = "scan data"
elif kind == tuple:
expected = "data"
else:
assert False
self._parse_unexpected(expected)
def _parse_trst_mode(self):
if self._parse_token() in _trst_modes:
return self._token
else:
self._parse_unexpected("TRST mode", _trst_modes)
def _parse_tap_state(self):
if self._parse_token() in _tap_states:
return self._token
else:
self._parse_unexpected("TAP state", _tap_states)
def _parse_tap_stable_state(self):
if self._parse_token() in _tap_stable_states:
return self._token
else:
self._parse_unexpected("stable TAP state", _tap_stable_states)
def _parse_scan_data(self, length):
value = self._parse_value(bitarray)
if value[length:].count(1) != 0:
residue = value[length:]
residue.reverse()
self._parse_error("scan data length %d exceeds command length %d"
% (len(value), length))
if length > len(value):
padding = bitarray(length - len(value), endian="little")
padding.setall(0)
value.extend(padding)
return value
else:
return value[:length]
def parse_command(self):
self._cmd_pos = self._lexer.position
command = self._parse_token()
if command is None:
return False
elif command == "FREQUENCY":
cycles = self._try(self._parse_value, (int, float))
if cycles is not None:
self._parse_keyword("HZ")
self._parse_keyword(";")
result = self._handler.svf_frequency(frequency=cycles)
elif command == "TRST":
mode = self._parse_trst_mode()
self._parse_keyword(";")
result = self._handler.svf_trst(mode=mode)
elif command == "STATE":
states = []
while True:
state = self._try(self._parse_tap_state)
if state is None: break
states.append(state)
self._parse_keyword(";")
if not states:
self._parse_error("at least one state required")
if states[-1] not in _tap_stable_states:
self._parse_error("last state must be a stable state")
*path_states, stable_state = states
result = self._handler.svf_state(state=stable_state, path=path_states)
elif command in ("ENDIR", "ENDDR"):
stable_state = self._parse_tap_stable_state()
self._parse_keyword(";")
if command == "ENDIR":
result = self._handler.svf_endir(state=stable_state)
if command == "ENDDR":
result = self._handler.svf_enddr(state=stable_state)
elif command in ("HIR", "SIR", "TIR", "HDR", "SDR", "TDR"):
length = self._parse_value(int)
if self._param_mask[command] is None or len(self._param_mask[command]) != length:
self._param_mask[command] = bitarray(length, endian="little")
self._param_mask[command].setall(1)
if self._param_smask[command] is None or len(self._param_smask[command]) != length:
self._param_smask[command] = bitarray(length, endian="little")
self._param_smask[command].setall(1)
param_tdi = self._param_tdi[command]
param_tdo = None
param_mask = self._param_mask[command]
param_smask = self._param_smask[command]
parameters = set()
while True:
parameter = self._try(self._parse_keywords, ("TDI", "TDO", "MASK", "SMASK"))
if parameter is None: break
value = self._parse_scan_data(length)
if parameter in parameters:
self._parse_error("parameter %s specified twice" % parameter)
parameters.add(parameter)
if parameter == "TDI":
self._param_tdi[command] = value
param_tdi = value
if parameter == "TDO":
param_tdo = value
if parameter == "MASK":
self._param_mask[command] = value
param_mask = value
if parameter == "SMASK":
self._param_smask[command] = value
param_smask = value
self._parse_keyword(";")
if param_tdi is None and length == 0:
param_tdi = bitarray("", endian="little")
elif param_tdi is None:
self._parse_error("initial value for parameter TDI required")
if len(param_tdi) != length:
self._parse_error("parameter TDI needs to be specified again because "
"the length changed")
if param_tdo is None:
# Make it a bit easier for downstream; set MASK (but not remembered MASK)
# to "all don't care" if there's no TDO specified.
param_mask = bitarray(param_mask)
param_mask.setall(0)
if command == "HIR":
result = self._handler.svf_hir(tdi=param_tdi, smask=param_smask,
tdo=param_tdo, mask=param_mask)
if command == "SIR":
result = self._handler.svf_sir(tdi=param_tdi, smask=param_smask,
tdo=param_tdo, mask=param_mask)
if command == "TIR":
result = self._handler.svf_tir(tdi=param_tdi, smask=param_smask,
tdo=param_tdo, mask=param_mask)
if command == "HDR":
result = self._handler.svf_hdr(tdi=param_tdi, smask=param_smask,
tdo=param_tdo, mask=param_mask)
if command == "SDR":
result = self._handler.svf_sdr(tdi=param_tdi, smask=param_smask,
tdo=param_tdo, mask=param_mask)
if command == "TDR":
result = self._handler.svf_tdr(tdi=param_tdi, smask=param_smask,
tdo=param_tdo, mask=param_mask)
elif command == "RUNTEST":
run_state = self._try(self._parse_tap_stable_state)
run_params = self._try(lambda:
(self._parse_value(int), self._parse_keywords(("TCK", "SCK"))))
if run_params is None:
run_count, run_clock = None, "TCK"
min_time, _ = \
self._parse_value((int, float)), self._parse_keyword("SEC")
else:
run_count, run_clock = run_params
min_time, _ = self._try(lambda:
(self._parse_value((int, float)), self._parse_keyword("SEC"))) \
or (None, None)
if self._try(self._parse_keyword, "MAXIMUM"):
max_time, _ = \
self._parse_value((int, float)), self._parse_keyword("SEC")
else:
max_time = None
if self._try(self._parse_keyword, "ENDSTATE"):
end_state = self._parse_tap_stable_state()
else:
end_state = None
self._parse_keyword(";")
if run_state is None:
run_state = self._param_run_state
else:
self._param_run_state = run_state
if end_state is None:
end_state = run_state
if end_state is None:
end_state = self._param_end_state
else:
self._param_end_state = end_state
if run_clock is None:
run_clock = "TCK"
if max_time is not None and min_time is not None and max_time < min_time:
self._parse_error("maximum time must be greater than minimum time")
result = self._handler.svf_runtest(run_state=run_state,
run_count=run_count, run_clock=run_clock,
min_time =min_time, max_time=max_time,
end_state=end_state)
elif command == "PIOMAP":
mapping, = self._parse_value(tuple)
self._parse_keyword(";")
result = self._handler.svf_piomap(mapping=mapping)
elif command == "PIO":
vector, = self._parse_value(tuple)
self._parse_keyword(";")
result = self._handler.svf_pio(vector=vector)
else:
self._parse_unexpected("command", _commands)
return result or True
def last_command(self):
return self._lexer.buffer[self._cmd_pos:self._lexer.position]
def parse_file(self):
while self.parse_command(): pass
class SVFEventHandler(metaclass=ABCMeta):
"""
An abstract base class for Serial Vector Format parsing events.
The methods of this class are called when a well-formed SVF command is encountered.
The parser takes care of maintaining all lexical state (e.g. "sticky" parameters),
but all logical state is maintained by the event handler.
"""
@abstractmethod
def svf_frequency(self, frequency):
"""Called when the ``FREQUENCY`` command is encountered."""
@abstractmethod
def svf_trst(self, mode):
"""Called when the ``TRST`` command is encountered."""
@abstractmethod
def svf_state(self, state, path):
"""Called when the ``STATE`` command is encountered."""
@abstractmethod
def svf_endir(self, state):
"""Called when the ``ENDIR`` command is encountered."""
@abstractmethod
def svf_enddr(self, state):
"""Called when the ``ENDDR`` command is encountered."""
@abstractmethod
def svf_hir(self, tdi, smask, tdo, mask):
"""Called when the ``HIR`` command is encountered."""
@abstractmethod
def svf_sir(self, tdi, smask, tdo, mask):
"""Called when the ``SIR`` command is encountered."""
@abstractmethod
def svf_tir(self, tdi, smask, tdo, mask):
"""Called when the ``TIR`` command is encountered."""
@abstractmethod
def svf_hdr(self, tdi, smask, tdo, mask):
"""Called when the ``HDR`` command is encountered."""
@abstractmethod
def svf_sdr(self, tdi, smask, tdo, mask):
"""Called when the ``SDR`` command is encountered."""
@abstractmethod
def svf_tdr(self, tdi, smask, tdo, mask):
"""Called when the ``TDR`` command is encountered."""
@abstractmethod
def svf_runtest(self, run_state, run_count, run_clock, min_time, max_time, end_state):
"""Called when the ``RUNTEST`` command is encountered."""
@abstractmethod
def svf_piomap(self, mapping):
"""Called when the ``PIOMAP`` command is encountered."""
@abstractmethod
def svf_pio(self, vector):
"""Called when the ``PIO`` command is encountered."""
# -------------------------------------------------------------------------------------------------
import unittest
class SVFLexerTestCase(unittest.TestCase):
def assertLexes(self, source, tokens):
self.lexer = SVFLexer(source)
self.assertEqual(list(self.lexer), tokens)
def test_eof(self):
self.assertLexes("", [])
def test_comment(self):
self.assertLexes("!foo",
[])
self.assertLexes("//foo",
[])
self.assertLexes("//foo\n!bar\n",
[])
self.assertLexes("//foo\n!bar\nTRST",
["TRST"])
def test_keyword(self):
self.assertLexes("TRST",
["TRST"])
self.assertLexes("TRST OFF;",
["TRST", "OFF", ";"])
def test_integer(self):
self.assertLexes("8", [8])
self.assertLexes("12", [12])
def test_real(self):
self.assertLexes("1E6", [1e6])
self.assertLexes("1E+6", [1e6])
self.assertLexes("1E-6", [1e-6])
self.assertLexes("1.1E6", [1.1e6])
self.assertLexes("1.1", [1.1])
def test_bitarray(self):
self.assertLexes("(0)", [bitarray("00000000")])
self.assertLexes("(1)", [bitarray("10000000")])
self.assertLexes("(F)", [bitarray("11110000")])
self.assertLexes("(f)", [bitarray("11110000")])
self.assertLexes("(0F)", [bitarray("11110000")])
self.assertLexes("(A\n5)", [bitarray("10100101")]) # Test literals split over two lines
self.assertLexes("(A\n\t5)", [bitarray("10100101")]) # With potential whitespace
self.assertLexes("(A\n 5)", [bitarray("10100101")])
self.assertLexes("(A\r\n5)", [bitarray("10100101")]) # Support both LF & LFCR
self.assertLexes("(A\r\n\t5)", [bitarray("10100101")])
self.assertLexes("(A\r\n 5)", [bitarray("10100101")])
self.assertLexes("(FF)", [bitarray("11111111")])
self.assertLexes("(1AA)", [bitarray("0101010110000000")])
def test_literal(self):
self.assertLexes("(HHZZL)", [("HHZZL",)])
self.assertLexes("(IN FOO)", [("IN FOO",)])
def test_error(self):
with self.assertRaises(SVFParsingError):
SVFLexer("XXX").next()
class SVFMockEventHandler:
def __init__(self):
self.events = []
def __getattr__(self, name):
if name.startswith("svf_"):
def svf_event(**kwargs):
self.events.append((name, kwargs))
return svf_event
else:
return super().__getattr__(name)
class SVFParserTestCase(unittest.TestCase):
def setUp(self):
self.maxDiff = None
def assertParses(self, source, events):
self.handler = SVFMockEventHandler()
self.parser = SVFParser(source, self.handler)
self.parser.parse_file()
self.assertEqual(self.handler.events, events)
def assertErrors(self, source, error):
with self.assertRaisesRegex(SVFParsingError, r"^{}".format(re.escape(error))):
self.handler = SVFMockEventHandler()
self.parser = SVFParser(source, self.handler)
self.parser.parse_file()
def test_frequency(self):
self.assertParses("FREQUENCY;",
[("svf_frequency", {"frequency": None})])
self.assertParses("FREQUENCY 1E6 HZ;",
[("svf_frequency", {"frequency": 1e6})])
self.assertParses("FREQUENCY 1000 HZ;",
[("svf_frequency", {"frequency": 1000})])
self.assertErrors("FREQUENCY 1E6;",
"expected HZ")
def test_trst(self):
self.assertParses("TRST ON;",
[("svf_trst", {"mode": "ON"})])
self.assertParses("TRST OFF;",
[("svf_trst", {"mode": "OFF"})])
self.assertParses("TRST Z;",
[("svf_trst", {"mode": "Z"})])
self.assertParses("TRST ABSENT;",
[("svf_trst", {"mode": "ABSENT"})])
self.assertErrors("TRST HZ;",
"expected TRST mode")
def test_state(self):
self.assertParses("STATE IDLE;",
[("svf_state", {"state": "IDLE", "path": []})])
self.assertParses("STATE IRUPDATE IDLE;",
[("svf_state", {"state": "IDLE", "path": ["IRUPDATE"]})])
self.assertParses("STATE IREXIT2 IRUPDATE IDLE;",
[("svf_state", {"state": "IDLE", "path": ["IREXIT2", "IRUPDATE"]})])
self.assertErrors("STATE;",
"at least one state required")
self.assertErrors("STATE IRSHIFT;",
"last state must be a stable state")
self.assertErrors("STATE RESET IRSHIFT;",
"last state must be a stable state")
def test_endir_enddr(self):
for command, event in [
("ENDIR", "svf_endir"),
("ENDDR", "svf_enddr")
]:
self.assertParses("{c} IRPAUSE;".format(c=command),
[(event, {"state": "IRPAUSE"})])
self.assertErrors("{c} IRSHIFT;".format(c=command),
"expected stable TAP state")
self.assertErrors("{c};".format(c=command),
"expected stable TAP state")
def test_hir_sir_tir_hdr_sdr_tdr(self):
for command, event in [
("HIR", "svf_hir"),
("SIR", "svf_sir"),
("TIR", "svf_tir"),
("HDR", "svf_hdr"),
("SDR", "svf_sdr"),
("TDR", "svf_tdr"),
]:
self.assertParses("{c} 0;".format(c=command), [
(event, {
"tdi": bitarray(""),
"smask": bitarray(""),
"tdo": None,
"mask": bitarray(""),
}),
])
self.assertParses("{c} 8 TDI(a);".format(c=command), [
(event, {
"tdi": bitarray("01010000"),
"smask": bitarray("11111111"),
"tdo": None,
"mask": bitarray("00000000"),
}),
])
self.assertParses("{c} 6 TDI(0a);".format(c=command), [
(event, {
"tdi": bitarray("010100"),
"smask": bitarray("111111"),
"tdo": None,
"mask": bitarray("000000"),
}),
])
self.assertParses("{c} 8 TDI(a); {c} 8;".format(c=command), [
(event, {
"tdi": bitarray("01010000"),
"smask": bitarray("11111111"),
"tdo": None,
"mask": bitarray("00000000"),
}),
(event, {
"tdi": bitarray("01010000"),
"smask": bitarray("11111111"),
"tdo": None,
"mask": bitarray("00000000"),
}),
])
self.assertParses("{c} 8 TDI(a) SMASK(3); {c} 8; {c} 12 TDI(b);"
.format(c=command), [
(event, {
"tdi": bitarray("01010000"),
"smask": bitarray("11000000"),
"tdo": None,
"mask": bitarray("00000000"),
}),
(event, {
"tdi": bitarray("01010000"),
"smask": bitarray("11000000"),
"tdo": None,
"mask": bitarray("00000000"),
}),
(event, {
"tdi": bitarray("110100000000"),
"smask": bitarray("111111111111"),
"tdo": None,
"mask": bitarray("000000000000"),
}),
])
self.assertParses("{c} 8 TDI(0) TDO(a) MASK(3); {c} 8; "
"{c} 8 TDO(1); {c} 12 TDI(0) TDO(b);"
.format(c=command), [
(event, {
"tdi": bitarray("00000000"),
"smask": bitarray("11111111"),
"tdo": bitarray("01010000"),
"mask": bitarray("11000000"),
}),
(event, {
"tdi": bitarray("00000000"),
"smask": bitarray("11111111"),
"tdo": None,
"mask": bitarray("00000000"),
}),
(event, {
"tdi": bitarray("00000000"),
"smask": bitarray("11111111"),
"tdo": bitarray("10000000"),
"mask": bitarray("11000000"),
}),
(event, {
"tdi": bitarray("000000000000"),
"smask": bitarray("111111111111"),
"tdo": bitarray("110100000000"),
"mask": bitarray("111111111111"),
}),
])
self.assertErrors("{c} 8 TDI(aaa);".format(c=command),
"scan data length 12 exceeds command length 8")
self.assertErrors("{c} 8 TDI(0) TDI(0);".format(c=command),
"parameter TDI specified twice")
self.assertErrors("{c} 8;".format(c=command),
"initial value for parameter TDI required")
self.assertErrors("{c} 8 TDI(aa); {c} 12;".format(c=command),
"parameter TDI needs to be specified again because "
"the length changed")
def test_runtest(self):
self.assertParses("RUNTEST 20000 TCK;", [
("svf_runtest", {
"run_state": "IDLE", "run_count": 20000, "run_clock": "TCK",
"min_time": None, "max_time": None, "end_state": "IDLE"
}),
])
self.assertParses("RUNTEST 20000 TCK 1E3 SEC;", [
("svf_runtest", {
"run_state": "IDLE", "run_count": 20000, "run_clock": "TCK",
"min_time": 1e3, "max_time": None, "end_state": "IDLE"
}),
])
self.assertParses("RUNTEST 20000 TCK 1E3 SEC MAXIMUM 1E6 SEC;", [
("svf_runtest", {
"run_state": "IDLE", "run_count": 20000, "run_clock": "TCK",
"min_time": 1e3, "max_time": 1e6, "end_state": "IDLE"
}),
])
self.assertParses("RUNTEST 20000 TCK 1E3 SEC MAXIMUM 1E6 SEC ENDSTATE RESET;", [
("svf_runtest", {
"run_state": "IDLE", "run_count": 20000, "run_clock": "TCK",
"min_time": 1e3, "max_time": 1e6, "end_state": "RESET"
}),
])
self.assertParses("RUNTEST 20000 TCK 1E3 SEC MAXIMUM 1E6 SEC ENDSTATE RESET;", [
("svf_runtest", {
"run_state": "IDLE", "run_count": 20000, "run_clock": "TCK",
"min_time": 1e3, "max_time": 1e6, "end_state": "RESET"
}),
])
self.assertParses("RUNTEST 20000 TCK ENDSTATE RESET; RUNTEST 100 TCK;", [
("svf_runtest", {
"run_state": "IDLE", "run_count": 20000, "run_clock": "TCK",
"min_time": None, "max_time": None, "end_state": "RESET"
}),
("svf_runtest", {
"run_state": "IDLE", "run_count": 100, "run_clock": "TCK",
"min_time": None, "max_time": None, "end_state": "RESET"
}),
])
self.assertParses("RUNTEST RESET 20000 TCK ENDSTATE RESET; RUNTEST IDLE 100 TCK;", [
("svf_runtest", {
"run_state": "RESET", "run_count": 20000, "run_clock": "TCK",
"min_time": None, "max_time": None, "end_state": "RESET"
}),
("svf_runtest", {
"run_state": "IDLE", "run_count": 100, "run_clock": "TCK",
"min_time": None, "max_time": None, "end_state": "IDLE"
}),
])
self.assertParses("RUNTEST 20000 SCK;", [
("svf_runtest", {
"run_state": "IDLE", "run_count": 20000, "run_clock": "SCK",
"min_time": None, "max_time": None, "end_state": "IDLE"
}),
])
self.assertParses("RUNTEST 1 SEC;", [
("svf_runtest", {
"run_state": "IDLE", "run_count": None, "run_clock": "TCK",
"min_time": 1, "max_time": None, "end_state": "IDLE"
}),
])
self.assertParses("RUNTEST 1 SEC MAXIMUM 2 SEC;", [
("svf_runtest", {
"run_state": "IDLE", "run_count": None, "run_clock": "TCK",
"min_time": 1, "max_time": 2, "end_state": "IDLE"
}),
])
self.assertParses("RUNTEST 200 TCK ENDSTATE RESET; RUNTEST 1 SEC;", [
("svf_runtest", {
"run_state": "IDLE", "run_count": 200, "run_clock": "TCK",
"min_time": None, "max_time": None, "end_state": "RESET"
}),
("svf_runtest", {
"run_state": "IDLE", "run_count": None, "run_clock": "TCK",
"min_time": 1, "max_time": None, "end_state": "RESET"
}),
])
self.assertErrors("RUNTEST;",
"expected number")
self.assertErrors("RUNTEST 2 SEC MAXIMUM 1 SEC;",
"maximum time must be greater than minimum time")
def test_piomap(self):
self.assertParses("PIOMAP (IN FOO OUT BAR);",
[("svf_piomap", {"mapping": "IN FOO OUT BAR"})])
self.assertErrors("PIOMAP;",
"expected data")
def test_pio(self):
self.assertParses("PIO (LHZX);",
[("svf_pio", {"vector": "LHZX"})])
self.assertErrors("PIO;",
"expected data")
def test_last_command(self):
handler = SVFMockEventHandler()
parser = SVFParser(" TRST OFF; SIR 8 TDI (aa); ", handler)
parser.parse_command()
self.assertEqual(parser.last_command(), " TRST OFF;")
parser.parse_command()
self.assertEqual(parser.last_command(), " SIR 8 TDI (aa);")
# -------------------------------------------------------------------------------------------------
class SVFPrintingEventHandler:
def __getattr__(self, name):
if name.startswith("svf_"):
def svf_event(**kwargs):
print((name, kwargs))
return svf_event
else:
return super().__getattr__(name)
if __name__ == "__main__":
import sys
with open(sys.argv[1]) as f:
SVFParser(f.read(), SVFPrintingEventHandler()).parse_file()

729
utils/jtag/svf2xsvf.py Normal file
View File

@ -0,0 +1,729 @@
#!/usr/bin/python3.0
# Copyright 2008, SoftPLC Corporation http://softplc.com
# Dick Hollenbeck dick@softplc.com
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, you may find one here:
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# or you may search the http://www.gnu.org website for the version 2 license,
# or you may write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
# A python program to convert an SVF file to an XSVF file. There is an
# option to include comments containing the source file line number from the origin
# SVF file before each outputted XSVF statement.
#
# We deviate from the XSVF spec in that we introduce a new command called
# XWAITSTATE which directly flows from the SVF RUNTEST command. Unfortunately
# XRUNSTATE was ill conceived and is not used here. We also add support for the
# three Lattice extensions to SVF: LCOUNT, LDELAY, and LSDR. The xsvf file
# generated from this program is suitable for use with the xsvf player in
# OpenOCD with my modifications to xsvf.c.
#
# This program is written for python 3.0, and it is not easy to change this
# back to 2.x. You may find it easier to use python 3.x even if that means
# building it.
import re
import sys
import struct
# There are both ---<Lexer>--- and ---<Parser>--- sections to this program
if len( sys.argv ) < 3:
print("usage %s <svf_filename> <xsvf_filename>" % sys.argv[0])
exit(1)
inputFilename = sys.argv[1]
outputFilename = sys.argv[2]
#doCOMMENTs = True # Save XCOMMENTs in the output xsvf file
doCOMMENTs = False # Save XCOMMENTs in the output xsvf file
# pick your file encoding
file_encoding = 'ISO-8859-1'
#file_encoding = 'utf-8'
xrepeat = 0 # argument to XREPEAT, gives retry count for masked compares
#-----< Lexer >---------------------------------------------------------------
StateBin = (RESET,IDLE,
DRSELECT,DRCAPTURE,DRSHIFT,DREXIT1,DRPAUSE,DREXIT2,DRUPDATE,
IRSELECT,IRCAPTURE,IRSHIFT,IREXIT1,IRPAUSE,IREXIT2,IRUPDATE) = range(16)
# Any integer index into this tuple will be equal to its corresponding StateBin value
StateTxt = ("RESET","IDLE",
"DRSELECT","DRCAPTURE","DRSHIFT","DREXIT1","DRPAUSE","DREXIT2","DRUPDATE",
"IRSELECT","IRCAPTURE","IRSHIFT","IREXIT1","IRPAUSE","IREXIT2","IRUPDATE")
(XCOMPLETE,XTDOMASK,XSIR,XSDR,XRUNTEST,hole0,hole1,XREPEAT,XSDRSIZE,XSDRTDO,
XSETSDRMASKS,XSDRINC,XSDRB,XSDRC,XSDRE,XSDRTDOB,XSDRTDOC,
XSDRTDOE,XSTATE,XENDIR,XENDDR,XSIR2,XCOMMENT,XWAIT,XWAITSTATE,
LCOUNT,LDELAY,LSDR,XTRST) = range(29)
#Note: LCOUNT, LDELAY, and LSDR are Lattice extensions to SVF and provide a way to loop back
# and check a completion status, essentially waiting on a part until it signals that it is done.
# For example below: loop 25 times, each time through the loop do a LDELAY (same as a true RUNTEST)
# and exit loop when LSDR compares match.
"""
LCOUNT 25;
! Step to DRPAUSE give 5 clocks and wait for 1.00e+000 SEC.
LDELAY DRPAUSE 5 TCK 1.00E-003 SEC;
! Test for the completed status. Match means pass.
! Loop back to LDELAY line if not match and loop count less than 25.
LSDR 1 TDI (0)
TDO (1);
"""
#XTRST is an opcode Xilinx seemed to have missed and it comes from the SVF TRST statement.
LineNumber = 1
def s_ident(scanner, token): return ("ident", token.upper(), LineNumber)
def s_hex(scanner, token):
global LineNumber
LineNumber = LineNumber + token.count('\n')
token = ''.join(token.split())
return ("hex", token[1:-1], LineNumber)
def s_int(scanner, token): return ("int", int(token), LineNumber)
def s_float(scanner, token): return ("float", float(token), LineNumber)
#def s_comment(scanner, token): return ("comment", token, LineNumber)
def s_semicolon(scanner, token): return ("semi", token, LineNumber)
def s_nl(scanner,token):
global LineNumber
LineNumber = LineNumber + 1
#print( 'LineNumber=', LineNumber, file=sys.stderr )
return None
#2.00E-002
scanner = re.Scanner([
(r"[a-zA-Z]\w*", s_ident),
# (r"[-+]?[0-9]+[.]?[0-9]*([eE][-+]?[0-9]+)?", s_float),
(r"[-+]?[0-9]+(([.][0-9eE+-]*)|([eE]+[-+]?[0-9]+))", s_float),
(r"\d+", s_int),
(r"\(([0-9a-fA-F]|\s)*\)", s_hex),
(r"(!|//).*$", None),
(r";", s_semicolon),
(r"\n",s_nl),
(r"\s*", None),
],
re.MULTILINE
)
# open the file using the given encoding
file = open( sys.argv[1], encoding=file_encoding )
# read all svf file input into string "input"
input = file.read()
file.close()
# Lexer:
# create a list of tuples containing (tokenType, tokenValue, LineNumber)
tokens = scanner.scan( input )[0]
input = None # allow gc to reclaim memory holding file
#for tokenType, tokenValue, ln in tokens: print( "line %d: %s" % (ln, tokenType), tokenValue )
#-----<parser>-----------------------------------------------------------------
tokVal = tokType = tokLn = None
tup = iter( tokens )
def nextTok():
"""
Function to read the next token from tup into tokType, tokVal, tokLn (linenumber)
which are globals.
"""
global tokType, tokVal, tokLn, tup
tokType, tokVal, tokLn = tup.__next__()
class ParseError(Exception):
"""A class to hold a parsing error message"""
def __init__(self, linenumber, token, message):
self.linenumber = linenumber
self.token = token
self.message = message
def __str__(self):
global inputFilename
return "Error in file \'%s\' at line %d near token %s\n %s" % (
inputFilename, self.linenumber, repr(self.token), self.message)
class MASKSET(object):
"""
Class MASKSET holds a set of bit vectors, all of which are related, will all
have the same length, and are associated with one of the seven shiftOps:
HIR, HDR, TIR, TDR, SIR, SDR, LSDR. One of these holds a mask, smask, tdi, tdo, and a
size.
"""
def __init__(self, name):
self.empty()
self.name = name
def empty(self):
self.mask = bytearray()
self.smask = bytearray()
self.tdi = bytearray()
self.tdo = bytearray()
self.size = 0
def syncLengths( self, sawTDI, sawTDO, sawMASK, sawSMASK, newSize ):
"""
Set all the lengths equal in the event some of the masks were
not seen as part of the last change set.
"""
if self.size == newSize:
return
if newSize == 0:
self.empty()
return
# If an SIR was given without a MASK(), then use a mask of all zeros.
# this is not consistent with the SVF spec, but it makes sense because
# it would be odd to be testing an instruction register read out of a
# tap without giving a mask for it. Also, lattice seems to agree and is
# generating SVF files that comply with this philosophy.
if self.name == 'SIR' and not sawMASK:
self.mask = bytearray( newSize )
if newSize != len(self.mask):
self.mask = bytearray( newSize )
if self.name == 'SDR': # leave mask for HIR,HDR,TIR,TDR,SIR zeros
for i in range( newSize ):
self.mask[i] = 1
if newSize != len(self.tdo):
self.tdo = bytearray( newSize )
if newSize != len(self.tdi):
self.tdi = bytearray( newSize )
if newSize != len(self.smask):
self.smask = bytearray( newSize )
self.size = newSize
#-----</MASKSET>-----
def makeBitArray( hexString, bitCount ):
"""
Converts a packed sequence of hex ascii characters into a bytearray where
each element in the array holds exactly one bit. Only "bitCount" bits are
scanned and these must be the least significant bits in the hex number. That
is, it is legal to have some unused bits in the must significant hex nibble
of the input "hexString". The string is scanned starting from the backend,
then just before returning we reverse the array. This way the append()
method can be used, which I assume is faster than an insert.
"""
global tokLn
a = bytearray()
length = bitCount
hexString = list(hexString)
hexString.reverse()
#print(hexString)
for c in hexString:
if length <= 0:
break;
c = int(c, 16)
for mask in [1,2,4,8]:
if length <= 0:
break;
length = length - 1
a.append( (c & mask) != 0 )
if length > 0:
raise ParseError( tokLn, hexString, "Insufficient hex characters for given length of %d" % bitCount )
a.reverse()
#print(a)
return a
def makeXSVFbytes( bitarray ):
"""
Make a bytearray which is contains the XSVF bits which will be written
directly to disk. The number of bytes needed is calculated from the size
of the argument bitarray.
"""
bitCount = len(bitarray)
byteCount = (bitCount+7)//8
ba = bytearray( byteCount )
firstBit = (bitCount % 8) - 1
if firstBit == -1:
firstBit = 7
bitNdx = 0
for byteNdx in range(byteCount):
mask = 1<<firstBit
byte = 0
while mask:
if bitarray[bitNdx]:
byte |= mask;
mask = mask >> 1
bitNdx = bitNdx + 1
ba[byteNdx] = byte
firstBit = 7
return ba
def writeComment( outputFile, shiftOp_linenum, shiftOp ):
"""
Write an XCOMMENT record to outputFile
"""
comment = "%s @%d\0" % (shiftOp, shiftOp_linenum) # \0 is terminating nul
ba = bytearray(1)
ba[0] = XCOMMENT
ba += comment.encode()
outputFile.write( ba )
def combineBitVectors( trailer, meat, header ):
"""
Combine the 3 bit vectors comprizing a transmission. Since the least
significant bits are sent first, the header is put onto the list last so
they are sent first from that least significant position.
"""
ret = bytearray()
ret.extend( trailer )
ret.extend( meat )
ret.extend( header )
return ret
def writeRUNTEST( outputFile, run_state, end_state, run_count, min_time, tokenTxt ):
"""
Write the output for the SVF RUNTEST command.
run_count - the number of clocks
min_time - the number of seconds
tokenTxt - either RUNTEST or LDELAY
"""
# convert from secs to usecs
min_time = int( min_time * 1000000)
# the SVF RUNTEST command does NOT map to the XSVF XRUNTEST command. Check the SVF spec, then
# read the XSVF command. They are not the same. Use an XSVF XWAITSTATE to
# implement the required behavior of the SVF RUNTEST command.
if doCOMMENTs:
writeComment( output, tokLn, tokenTxt )
if tokenTxt == 'RUNTEST':
obuf = bytearray(11)
obuf[0] = XWAITSTATE
obuf[1] = run_state
obuf[2] = end_state
struct.pack_into(">i", obuf, 3, run_count ) # big endian 4 byte int to obuf
struct.pack_into(">i", obuf, 7, min_time ) # big endian 4 byte int to obuf
outputFile.write( obuf )
else: # == 'LDELAY'
obuf = bytearray(10)
obuf[0] = LDELAY
obuf[1] = run_state
# LDELAY has no end_state
struct.pack_into(">i", obuf, 2, run_count ) # big endian 4 byte int to obuf
struct.pack_into(">i", obuf, 6, min_time ) # big endian 4 byte int to obuf
outputFile.write( obuf )
output = open( outputFilename, mode='wb' )
hir = MASKSET('HIR')
hdr = MASKSET('HDR')
tir = MASKSET('TIR')
tdr = MASKSET('TDR')
sir = MASKSET('SIR')
sdr = MASKSET('SDR')
expecting_eof = True
# one of the commands that take the shiftParts after the length, the parse
# template for all of these commands is identical
shiftOps = ('SDR', 'SIR', 'LSDR', 'HDR', 'HIR', 'TDR', 'TIR')
# the order must correspond to shiftOps, this holds the MASKSETS. 'LSDR' shares sdr with 'SDR'
shiftSets = (sdr, sir, sdr, hdr, hir, tdr, tir )
# what to expect as parameters to a shiftOp, i.e. after a SDR length or SIR length
shiftParts = ('TDI', 'TDO', 'MASK', 'SMASK')
# the set of legal states which can trail the RUNTEST command
run_state_allowed = ('IRPAUSE', 'DRPAUSE', 'RESET', 'IDLE')
enddr_state_allowed = ('DRPAUSE', 'IDLE')
endir_state_allowed = ('IRPAUSE', 'IDLE')
trst_mode_allowed = ('ON', 'OFF', 'Z', 'ABSENT')
enddr_state = IDLE
endir_state = IDLE
frequency = 1.00e+006 # HZ;
# change detection for xsdrsize and xtdomask
xsdrsize = -1 # the last one sent, send only on change
xtdomask = bytearray() # the last one sent, send only on change
# we use a number of single byte writes for the XSVF command below
cmdbuf = bytearray(1)
# Save the XREPEAT setting into the file as first thing.
obuf = bytearray(2)
obuf[0] = XREPEAT
obuf[1] = xrepeat
output.write( obuf )
try:
while 1:
expecting_eof = True
nextTok()
expecting_eof = False
# print( tokType, tokVal, tokLn )
if tokVal in shiftOps:
shiftOp_linenum = tokLn
shiftOp = tokVal
set = shiftSets[shiftOps.index(shiftOp)]
# set flags false, if we see one later, set that one true later
sawTDI = sawTDO = sawMASK = sawSMASK = False
nextTok()
if tokType != 'int':
raise ParseError( tokLn, tokVal, "Expecting 'int' giving %s length, got '%s'" % (shiftOp, tokType) )
length = tokVal
nextTok()
while tokVal != ';':
if tokVal not in shiftParts:
raise ParseError( tokLn, tokVal, "Expecting TDI, TDO, MASK, SMASK, or ';'")
shiftPart = tokVal
nextTok()
if tokType != 'hex':
raise ParseError( tokLn, tokVal, "Expecting hex bits" )
bits = makeBitArray( tokVal, length )
if shiftPart == 'TDI':
sawTDI = True
set.tdi = bits
elif shiftPart == 'TDO':
sawTDO = True
set.tdo = bits
elif shiftPart == 'MASK':
sawMASK = True
set.mask = bits
elif shiftPart == 'SMASK':
sawSMASK = True
set.smask = bits
nextTok()
set.syncLengths( sawTDI, sawTDO, sawMASK, sawSMASK, length )
# process all the gathered parameters and generate outputs here
if shiftOp == 'SIR':
if doCOMMENTs:
writeComment( output, shiftOp_linenum, 'SIR' )
tdi = combineBitVectors( tir.tdi, sir.tdi, hir.tdi )
if len(tdi) > 255:
obuf = bytearray(3)
obuf[0] = XSIR2
struct.pack_into( ">h", obuf, 1, len(tdi) )
else:
obuf = bytearray(2)
obuf[0] = XSIR
obuf[1] = len(tdi)
output.write( obuf )
obuf = makeXSVFbytes( tdi )
output.write( obuf )
elif shiftOp == 'SDR':
if doCOMMENTs:
writeComment( output, shiftOp_linenum, shiftOp )
if not sawTDO:
# pass a zero filled bit vector for the sdr.mask
mask = combineBitVectors( tdr.mask, bytearray(sdr.size), hdr.mask )
tdi = combineBitVectors( tdr.tdi, sdr.tdi, hdr.tdi )
if xsdrsize != len(tdi):
xsdrsize = len(tdi)
cmdbuf[0] = XSDRSIZE
output.write( cmdbuf )
obuf = bytearray(4)
struct.pack_into( ">i", obuf, 0, xsdrsize ) # big endian 4 byte int to obuf
output.write( obuf )
if xtdomask != mask:
xtdomask = mask
cmdbuf[0] = XTDOMASK
output.write( cmdbuf )
obuf = makeXSVFbytes( mask )
output.write( obuf )
cmdbuf[0] = XSDR
output.write( cmdbuf )
obuf = makeXSVFbytes( tdi )
output.write( obuf )
else:
mask = combineBitVectors( tdr.mask, sdr.mask, hdr.mask )
tdi = combineBitVectors( tdr.tdi, sdr.tdi, hdr.tdi )
tdo = combineBitVectors( tdr.tdo, sdr.tdo, hdr.tdo )
if xsdrsize != len(tdi):
xsdrsize = len(tdi)
cmdbuf[0] = XSDRSIZE
output.write( cmdbuf )
obuf = bytearray(4)
struct.pack_into(">i", obuf, 0, xsdrsize ) # big endian 4 byte int to obuf
output.write( obuf )
if xtdomask != mask:
xtdomask = mask
cmdbuf[0] = XTDOMASK
output.write( cmdbuf )
obuf = makeXSVFbytes( mask )
output.write( obuf )
cmdbuf[0] = XSDRTDO
output.write( cmdbuf )
obuf = makeXSVFbytes( tdi )
output.write( obuf )
obuf = makeXSVFbytes( tdo )
output.write( obuf )
#print( "len(tdo)=", len(tdo), "len(tdr.tdo)=", len(tdr.tdo), "len(sdr.tdo)=", len(sdr.tdo), "len(hdr.tdo)=", len(hdr.tdo) )
elif shiftOp == 'LSDR':
if doCOMMENTs:
writeComment( output, shiftOp_linenum, shiftOp )
mask = combineBitVectors( tdr.mask, sdr.mask, hdr.mask )
tdi = combineBitVectors( tdr.tdi, sdr.tdi, hdr.tdi )
tdo = combineBitVectors( tdr.tdo, sdr.tdo, hdr.tdo )
if xsdrsize != len(tdi):
xsdrsize = len(tdi)
cmdbuf[0] = XSDRSIZE
output.write( cmdbuf )
obuf = bytearray(4)
struct.pack_into(">i", obuf, 0, xsdrsize ) # big endian 4 byte int to obuf
output.write( obuf )
if xtdomask != mask:
xtdomask = mask
cmdbuf[0] = XTDOMASK
output.write( cmdbuf )
obuf = makeXSVFbytes( mask )
output.write( obuf )
cmdbuf[0] = LSDR
output.write( cmdbuf )
obuf = makeXSVFbytes( tdi )
output.write( obuf )
obuf = makeXSVFbytes( tdo )
output.write( obuf )
#print( "len(tdo)=", len(tdo), "len(tdr.tdo)=", len(tdr.tdo), "len(sdr.tdo)=", len(sdr.tdo), "len(hdr.tdo)=", len(hdr.tdo) )
elif tokVal == 'RUNTEST' or tokVal == 'LDELAY':
# e.g. from lattice tools:
# "RUNTEST IDLE 5 TCK 1.00E-003 SEC;"
saveTok = tokVal
nextTok()
min_time = 0
run_count = 0
max_time = 600 # ten minutes
if tokVal in run_state_allowed:
run_state = StateTxt.index(tokVal)
end_state = run_state # bottom of page 17 of SVF spec
nextTok()
if tokType != 'int' and tokType != 'float':
raise ParseError( tokLn, tokVal, "Expecting 'int' or 'float' after RUNTEST [run_state]")
timeval = tokVal;
nextTok()
if tokVal != 'TCK' and tokVal != 'SEC' and tokVal != 'SCK':
raise ParseError( tokLn, tokVal, "Expecting 'TCK' or 'SEC' or 'SCK' after RUNTEST [run_state] (run_count|min_time)")
if tokVal == 'TCK' or tokVal == 'SCK':
run_count = int( timeval )
else:
min_time = timeval
nextTok()
if tokType == 'int' or tokType == 'float':
min_time = tokVal
nextTok()
if tokVal != 'SEC':
raise ParseError( tokLn, tokVal, "Expecting 'SEC' after RUNTEST [run_state] run_count min_time")
nextTok()
if tokVal == 'MAXIMUM':
nextTok()
if tokType != 'int' and tokType != 'float':
raise ParseError( tokLn, tokVal, "Expecting 'max_time' after RUNTEST [run_state] min_time SEC MAXIMUM")
max_time = tokVal
nextTok()
if tokVal != 'SEC':
raise ParseError( tokLn, tokVal, "Expecting 'max_time' after RUNTEST [run_state] min_time SEC MAXIMUM max_time")
nextTok()
if tokVal == 'ENDSTATE':
nextTok()
if tokVal not in run_state_allowed:
raise ParseError( tokLn, tokVal, "Expecting 'run_state' after RUNTEST .... ENDSTATE")
end_state = StateTxt.index(tokVal)
nextTok()
if tokVal != ';':
raise ParseError( tokLn, tokVal, "Expecting ';' after RUNTEST ....")
# print( "run_count=", run_count, "min_time=", min_time,
# "max_time=", max_time, "run_state=", State[run_state], "end_state=", State[end_state] )
writeRUNTEST( output, run_state, end_state, run_count, min_time, saveTok )
elif tokVal == 'LCOUNT':
nextTok()
if tokType != 'int':
raise ParseError( tokLn, tokVal, "Expecting integer 'count' after LCOUNT")
loopCount = tokVal
nextTok()
if tokVal != ';':
raise ParseError( tokLn, tokVal, "Expecting ';' after LCOUNT count")
if doCOMMENTs:
writeComment( output, tokLn, 'LCOUNT' )
obuf = bytearray(5)
obuf[0] = LCOUNT
struct.pack_into(">i", obuf, 1, loopCount ) # big endian 4 byte int to obuf
output.write( obuf )
elif tokVal == 'ENDDR':
nextTok()
if tokVal not in enddr_state_allowed:
raise ParseError( tokLn, tokVal, "Expecting 'stable_state' after ENDDR. (one of: DRPAUSE, IDLE)")
enddr_state = StateTxt.index(tokVal)
nextTok()
if tokVal != ';':
raise ParseError( tokLn, tokVal, "Expecting ';' after ENDDR stable_state")
if doCOMMENTs:
writeComment( output, tokLn, 'ENDDR' )
obuf = bytearray(2)
obuf[0] = XENDDR
# Page 10 of the March 1999 SVF spec shows that RESET is also allowed here.
# Yet the XSVF spec has no provision for that, and uses a non-standard, i.e.
# boolean argument to XENDDR which only handles two of the 3 intended states.
obuf[1] = 1 if enddr_state == DRPAUSE else 0
output.write( obuf )
elif tokVal == 'ENDIR':
nextTok()
if tokVal not in endir_state_allowed:
raise ParseError( tokLn, tokVal, "Expecting 'stable_state' after ENDIR. (one of: IRPAUSE, IDLE)")
endir_state = StateTxt.index(tokVal)
nextTok()
if tokVal != ';':
raise ParseError( tokLn, tokVal, "Expecting ';' after ENDIR stable_state")
if doCOMMENTs:
writeComment( output, tokLn, 'ENDIR' )
obuf = bytearray(2)
obuf[0] = XENDIR
# Page 10 of the March 1999 SVF spec shows that RESET is also allowed here.
# Yet the XSVF spec has no provision for that, and uses a non-standard, i.e.
# boolean argument to XENDDR which only handles two of the 3 intended states.
obuf[1] = 1 if endir_state == IRPAUSE else 0
output.write( obuf )
elif tokVal == 'STATE':
nextTok()
ln = tokLn
while tokVal != ';':
if tokVal not in StateTxt:
raise ParseError( tokLn, tokVal, "Expecting 'stable_state' after STATE")
stable_state = StateTxt.index( tokVal )
if doCOMMENTs and ln != -1:
writeComment( output, ln, 'STATE' )
ln = -1 # save comment only once
obuf = bytearray(2)
obuf[0] = XSTATE
obuf[1] = stable_state
output.write( obuf )
nextTok()
elif tokVal == 'FREQUENCY':
nextTok()
if tokVal != ';':
if tokType != 'int' and tokType != 'float':
raise ParseError( tokLn, tokVal, "Expecting 'cycles HZ' after FREQUENCY")
frequency = tokVal
nextTok()
if tokVal != 'HZ':
raise ParseError( tokLn, tokVal, "Expecting 'HZ' after FREQUENCY cycles")
nextTok()
if tokVal != ';':
raise ParseError( tokLn, tokVal, "Expecting ';' after FREQUENCY cycles HZ")
elif tokVal == 'TRST':
nextTok()
if tokVal not in trst_mode_allowed:
raise ParseError( tokLn, tokVal, "Expecting 'ON|OFF|Z|ABSENT' after TRST")
trst_mode = tokVal
nextTok()
if tokVal != ';':
raise ParseError( tokLn, tokVal, "Expecting ';' after TRST trst_mode")
if doCOMMENTs:
writeComment( output, tokLn, 'TRST %s' % trst_mode )
obuf = bytearray( 2 )
obuf[0] = XTRST
obuf[1] = trst_mode_allowed.index( trst_mode ) # use the index as the binary argument to XTRST opcode
output.write( obuf )
else:
raise ParseError( tokLn, tokVal, "Unknown token '%s'" % tokVal)
except StopIteration:
if not expecting_eof:
print( "Unexpected End of File at line ", tokLn )
except ParseError as pe:
print( "\n", pe )
finally:
# print( "closing file" )
cmdbuf[0] = XCOMPLETE
output.write( cmdbuf )
output.close()

BIN
xsvf/erase_ATF1502AS.xsvf Normal file

Binary file not shown.

BIN
xsvf/erase_ATF1504AS.xsvf Normal file

Binary file not shown.

BIN
xsvf/id_ATF150X.xsvf Normal file

Binary file not shown.