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.
- [x] 10-bit I2C address support (Needs poking at the Pico SDK, as it only
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
taken from the DLN2 Linux drivers, except better (dynamic interface
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
- https://github.com/cyphunk/JTAGenum
- 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
OpenOCD.
- 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://github.com/Xilinx/XilinxVirtualCable/
- https://github.com/derekmulcahy/xvcpi
- OpenOCD as XVC client??
- [x] SUMP logic analyzer mode?
- see also [this](https://github.com/perexg/picoprobe-sump)
- [ ] runtime config options for overclocking, logging

3
host/.gitignore vendored
View File

@ -2,3 +2,6 @@ build/
dist/
*.egg-info/
.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)