XVC implementation that talks to a cmsis-dap device

This commit is contained in:
Triss 2021-07-30 01:12:40 +02:00
parent e67c4ea8e6
commit 637833ae35
4 changed files with 224 additions and 3 deletions

View File

@ -248,7 +248,7 @@ libco is licensed under the [ISC license](https://opensource.org/licenses/ISC)
parts do, but, laziness. parts do, but, laziness.
- [x] 10-bit I2C address support (Needs poking at the Pico SDK, as it only - [x] 10-bit I2C address support (Needs poking at the Pico SDK, as it only
supports 7-bit ones). supports 7-bit ones).
- [ ] Better USB interface stuff, because I2C-Tiny-USB sucks and serprog can only - [x] Better USB interface stuff, because I2C-Tiny-USB sucks and serprog can only
do flash chips instead of being a real spidev. General idea can probably be do flash chips instead of being a real spidev. General idea can probably be
taken from the DLN2 Linux drivers, except better (dynamic interface taken from the DLN2 Linux drivers, except better (dynamic interface
signalled in the protocol (eg. does the device actually have I2C/SPI/..?), signalled in the protocol (eg. does the device actually have I2C/SPI/..?),
@ -263,7 +263,7 @@ libco is licensed under the [ISC license](https://opensource.org/licenses/ISC)
- [ ] JTAG pinout detector - [ ] JTAG pinout detector
- https://github.com/cyphunk/JTAGenum - https://github.com/cyphunk/JTAGenum
- https://github.com/travisgoodspeed/goodfet/blob/master/firmware/apps/jscan/jscan.c - https://github.com/travisgoodspeed/goodfet/blob/master/firmware/apps/jscan/jscan.c
- [ ] Host-side script that is an XVC (or hw_server) cable and communicates - [x] Host-side script that is an XVC (or hw_server) cable and communicates
with the device to perform the JTAG commands, because Vivado no likey with the device to perform the JTAG commands, because Vivado no likey
OpenOCD. OpenOCD.
- CMSIS-DAP interface can be used directly, see CMSIS_5/CMSIS/DoxyGen/DAP/src/dap_USB_cmds.txt - CMSIS-DAP interface can be used directly, see CMSIS_5/CMSIS/DoxyGen/DAP/src/dap_USB_cmds.txt
@ -274,7 +274,6 @@ libco is licensed under the [ISC license](https://opensource.org/licenses/ISC)
- https://debugmo.de/2012/02/xvcd-the-xilinx-virtual-cable-daemon/ - https://debugmo.de/2012/02/xvcd-the-xilinx-virtual-cable-daemon/
- https://github.com/Xilinx/XilinxVirtualCable/ - https://github.com/Xilinx/XilinxVirtualCable/
- https://github.com/derekmulcahy/xvcpi - https://github.com/derekmulcahy/xvcpi
- OpenOCD as XVC client??
- [x] SUMP logic analyzer mode? - [x] SUMP logic analyzer mode?
- see also [this](https://github.com/perexg/picoprobe-sump) - see also [this](https://github.com/perexg/picoprobe-sump)
- [ ] runtime config options for overclocking, logging - [ ] runtime config options for overclocking, logging

3
host/.gitignore vendored
View File

@ -2,3 +2,6 @@ build/
dist/ dist/
*.egg-info/ *.egg-info/
.venv/ .venv/
__pycache__/
*.pyc
*.pyo

View File

@ -0,0 +1 @@
pyocd>=0.31.0

218
host/xvc2dap.py Executable file
View File

@ -0,0 +1,218 @@
#!/usr/bin/env python3
import argparse
import socket
import struct
from typing import *
from pyocd.probe.pydapaccess import *
class EndOfStreamException(Exception): pass
class JtagSeq(NamedTuple):
nbits: int
tms: int
tdi: int
def bigint2bytes(nbits: int, v: int) -> bytes:
r = bytearray((nbits + 7) // 8)
for i in range(nbits):
byteind = i // 8
bitind = i & 7
r[byteind] |= ((v >> i) & 1) << bitind
return r
def dap_split_jseq(nbits: int, tms: bytes, tdi: bytes) -> List[JtagSeq]:
assert len(tms) == len(tdi)
assert len(tms) == (nbits + 7) // 8
def bitat(l: bytes, i: int) -> int: # get bit at given index of a compacted bytes
byteind = i // 8
bitind = i & 7
return (l[byteind] >> bitind) & 1
if nbits == 1:
return [JtagSeq(nbits=1, tms=bitat(tms, 0), tdi=bitat(tdi, 1))]
res = []
tmsv = bitat(tms, 0)
tdiv = bitat(tdi, 0)
bitind = 1
for i in range(1, nbits):
if bitat(tms, i) != tmsv or bitind == 64:
#print("append nb=%d tms=%s tdi=%s" % (bitind, repr(tmsv), repr(tdiv)))
res.append(JtagSeq(nbits=bitind, tms=tmsv, tdi=tdiv))
tmsv = bitat(tms, i)
tdiv = 0
bitind = 0
tdiv |= bitat(tdi, i) << bitind
bitind += 1
if bitind != 0:
#print("append nb=%d tms=%s tdi=%s" % (bitind, repr(tmsv), repr(tdiv)))
res.append(JtagSeq(nbits=bitind, tms=tmsv, tdi=tdiv))
#print("sumbits=%d, nbits=%d" % (sum(s.nbits for s in res), nbits))
assert sum(s.nbits for s in res) == nbits
return res
def get_dap(serial: Optional[str]) -> DAPAccess:
if serial is not None:
try:
return DAPAccess.get_device(serial)
except Exception:# as e:
raise Exception("Could not find CMSIS-DAP device %s" % serial)
else:
devs = DAPAccess.get_connected_devices()
if len(devs) == 1:
return devs[0]
elif len(devs) == 0:
raise Exception("No CMSIS-DAP devices found.")
else:
raise Exception("Multiple CMSIS-DAP devices found, please specify"+\
" a serial number to connect to a specific one. "+\
"Devices found: %s" % ', '.join(d.unique_id for d in devs))
def xvc_read_cmd(f) -> bytes:
r = b''
while True:
bv = f.recv(1)
if bv == b':':
return r
elif len(bv) == 0:
raise EndOfStreamException()
else:
r += bv
def xvc_do_cmd(cmd, f, dap):
if cmd == b"getinfo":
# parameter is max vector len (in bits)
# we use some value here (2k should be good enough), though in reality
# we'll handle pretty much anything. except, pydapaccess can only do
# JTAG sequences in chunks of 64 bits, which is a bit small (and would
# cause some network overhead), so we'll do the splitting and combining
# ourselves.
# we only support v1.0, because CMSIS-DAP itself doesn't know much
# about memory address spaces, so we're just not going to bother here.
# we could technically use the rest of pyocd, but, meh
f.send(b'xvcServer_v1.0:%d\n' % (2048*8))
pass
elif cmd == b"settck":
inv = f.recv(4)
if len(inv) < 3: raise EndOfStreamException()
freq_wanted = struct.unpack('<I', inv)[0]
print("settck comand for %d ns" % freq_wanted)
dap.set_clock(50*1000) # 50 kHz for now
# not supported by CMSIS-DAP, so don't do much...
#f.send(b'\0\0\0\0') #f.write(struct.pack('<I', 0))
f.send(inv)
elif cmd == b"shift":
inv = f.recv(4)
if len(inv) < 3: raise EndOfStreamException()
nbits = struct.unpack('<I', inv)[0]
nbytes = (nbits + 7) // 8
print("shift command: 0x%x bits (0x%x bytes)" % (nbits, nbytes))
if nbytes == 0: return
tmsbytes = f.recv(nbytes)
if len(tmsbytes) < nbytes: raise EndOfStreamException()
tdibytes = f.recv(nbytes)
if len(tdibytes) < nbytes: raise EndOfStreamException()
#print("tms:", tmsbytes)
#print("tdi:", tdibytes)
# a CMSIS-DAP JTAG sequence has the following constraints:
# * max block length is 64 bits (8 bytes)
# * TMS must be constant over a single JTAG sequence
#
# so we now have to split the received bits into sequences usable for
# CMSIS-DAP
splitres = dap_split_jseq(nbits, tmsbytes, tdibytes)
print("split result:", splitres)
ntdo = 0
tdov = 0
for seq in splitres:
rv = dap.jtag_sequence(cycles=seq.nbits, tms=seq.tms, read_tdo=True, tdi=seq.tdi)
print("rv:", rv)
tdov |= rv << ntdo
ntdo += seq.nbits
assert ntdo == nbits
f.send(bigint2bytes(nbits, tdov)) # write zeros
else:
print("Unknown command!", cmd)
def xvc2dap_do(args: Any) -> int:
dap = get_dap(args.serial)
dap.open()
try:
dap.connect()
with socket.socket() as sock:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((args.address, args.port))
sock.listen()
print("up and running!")
while True:
print("waiting for conn")
f, addr = sock.accept()
print("got conn!", addr)
try:
while True:
cmd = xvc_read_cmd(f)
print("cmd:", cmd)
xvc_do_cmd(cmd, f, dap)
except EndOfStreamException:
pass # continue to next iteration
finally:
dap.close()
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument('--serial', type=str, default=None,
help="Connection string to the CMSIS-DAP device, as "+\
"a serial number, defaults to the first device found.")
parser.add_argument('address', type=str, default='localhost',
help="Host to bind to, for the XVC server, default "+\
"localhost")
parser.add_argument('port', type=int, default=2542, nargs='?',
help="port to bind to, for the XVC server, default 2542")
args = parser.parse_args()
return xvc2dap_do(args)
if __name__ == '__main__':
try:
exit(main() or 0)
except Exception:
import traceback
traceback.print_exc()
exit(1)