XVC implementation that talks to a cmsis-dap device
This commit is contained in:
parent
e67c4ea8e6
commit
637833ae35
|
@ -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
|
||||
|
|
|
@ -2,3 +2,6 @@ build/
|
|||
dist/
|
||||
*.egg-info/
|
||||
.venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
pyocd>=0.31.0
|
|
@ -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)
|
||||
|
Loading…
Reference in New Issue