mirror of
https://github.com/openhwgroup/cvw
synced 2025-02-11 06:05:49 +00:00
convert debug script to TCL interface, remove telnetlib dependency
This commit is contained in:
parent
c3243caacf
commit
7f63daa49c
@ -1,429 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
#########################################################################################
|
||||
# hw_interface.py
|
||||
#
|
||||
# Written: matthew.n.otto@okstate.edu
|
||||
# Created: 19 April 2024
|
||||
#
|
||||
# Purpose: Send debugging commands to OpenOCD via local telnet connection
|
||||
#
|
||||
# A component of the CORE-V-WALLY configurable RISC-V project.
|
||||
# https:#github.com/openhwgroup/cvw
|
||||
#
|
||||
# Copyright (C) 2021-24 Harvey Mudd College & Oklahoma State University
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1
|
||||
#
|
||||
# Licensed under the Solderpad Hardware License v 2.1 (the “License”); you may not use this file
|
||||
# except in compliance with the License, or, at your option, the Apache License version 2.0. You
|
||||
# may obtain a copy of the License at
|
||||
#
|
||||
# https:#solderpad.org/licenses/SHL-2.1/
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, any work distributed under the
|
||||
# License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
# either express or implied. See the License for the specific language governing permissions
|
||||
# and limitations under the License.
|
||||
#########################################################################################
|
||||
|
||||
# This script uses python to send text commands to OpenOCD via telnet
|
||||
# OpenOCD also supports tcl commands directly
|
||||
|
||||
import atexit
|
||||
import re
|
||||
import time
|
||||
from telnetlib import Telnet
|
||||
|
||||
debug = False
|
||||
|
||||
# TODO: if JTAG clk is fast enough, need to check for busy between absract commands
|
||||
|
||||
def dump_GPR():
|
||||
gpr = {}
|
||||
for i in range(1,32):
|
||||
addr = f"X{i}"
|
||||
gpr[addr] = read_data(addr)
|
||||
# DM will assert Abstract Command Err if GPR X16-X31 isn't implemented (CMDERR_EXCEPTION)
|
||||
# This will clear that error and return early.
|
||||
if i == 16:
|
||||
abstractcs = int(read_dmi("0x16"), 16)
|
||||
cmderr = (abstractcs & 0x700) >> 8
|
||||
if cmderr == 3:
|
||||
clear_abstrcmd_err()
|
||||
break
|
||||
return gpr
|
||||
|
||||
|
||||
|
||||
def write_data(register, data):
|
||||
"""Writes data of width XLEN to specified register"""
|
||||
# Translate register alias to DM regno
|
||||
regno = int(register_translations[register], 16)
|
||||
# Write data to 32 bit message registers
|
||||
data = int(data, 16)
|
||||
write_dmi("0x4", hex(data & 0xffffffff))
|
||||
if XLEN == 64:
|
||||
write_dmi("0x5", hex((data >> 32) & 0xffffffff))
|
||||
if XLEN == 128:
|
||||
write_dmi("0x6", hex((data >> 64) & 0xffffffff))
|
||||
write_dmi("0x7", hex((data >> 96) & 0xffffffff))
|
||||
# Transfer data from msg registers to target register
|
||||
access_register(write=True, regno=regno, addr_size=XLEN)
|
||||
# Check that operations completed without error
|
||||
if acerr := check_absrtcmderr():
|
||||
raise Exception(acerr)
|
||||
|
||||
|
||||
def read_data(register):
|
||||
"""Read data of width XLEN from specified register"""
|
||||
# Translate register alias to DM regno
|
||||
regno = int(register_translations[register], 16)
|
||||
# Transfer data from target register to msg registers
|
||||
access_register(write=False, regno=regno, addr_size=XLEN)
|
||||
# Read data from 32 bit message registers
|
||||
data = ""
|
||||
data = read_dmi("0x4").replace("0x", "").zfill(8)
|
||||
if XLEN >= 64:
|
||||
data = read_dmi("0x5").replace("0x", "").zfill(8) + data
|
||||
if XLEN == 128:
|
||||
data = read_dmi("0x6").replace("0x", "").zfill(8) + data
|
||||
data = read_dmi("0x7").replace("0x", "").zfill(8) + data
|
||||
# Check that operations completed without error
|
||||
if acerr := check_absrtcmderr():
|
||||
raise Exception(acerr)
|
||||
return f"0x{data}"
|
||||
|
||||
|
||||
def access_register(write, regno, addr_size):
|
||||
"""3.7.1.1
|
||||
Before starting an abstract command, a debugger must ensure that haltreq, resumereq, and
|
||||
ackhavereset are all 0."""
|
||||
addr = "0x17"
|
||||
data = 1 << 17 # transfer bit always set
|
||||
if addr_size == 32:
|
||||
data += 2 << 20
|
||||
elif addr_size == 64:
|
||||
data += 3 << 20
|
||||
elif addr_size == 128:
|
||||
data += 4 << 20
|
||||
else:
|
||||
raise Exception("must provide valid register access size (32, 64, 128). See: 3.7.1.1 aarsize")
|
||||
if write:
|
||||
data += 1<<16
|
||||
data += regno
|
||||
data = hex(data)
|
||||
write_dmi(addr, data)
|
||||
|
||||
|
||||
def halt():
|
||||
write_dmi("0x10", "0x80000001")
|
||||
check_errors()
|
||||
|
||||
|
||||
def resume():
|
||||
write_dmi("0x10", "0x40000001")
|
||||
check_errors()
|
||||
|
||||
|
||||
def step():
|
||||
write_dmi("0x10", "0xC0000001")
|
||||
check_errors()
|
||||
|
||||
|
||||
def set_haltonreset():
|
||||
write_dmi("0x10", "0x9")
|
||||
|
||||
|
||||
def clear_haltonreset():
|
||||
write_dmi("0x10", "0x5")
|
||||
|
||||
|
||||
def reset_hart():
|
||||
write_dmi("0x10", "0x3")
|
||||
write_dmi("0x10", "0x1")
|
||||
|
||||
|
||||
def status():
|
||||
dmstatus = int(read_dmi("0x11"), 16)
|
||||
print("Core status:::")
|
||||
print(f"Running: {bool((dmstatus >> 11) & 0x1)}")
|
||||
print(f"Halted: {bool((dmstatus >> 9) & 0x1)}")
|
||||
|
||||
|
||||
def check_errors():
|
||||
# TODO: update this
|
||||
"""Checks various status bits and reports any potential errors
|
||||
Returns true if any errors are found"""
|
||||
# check dtmcs
|
||||
dtmcs = int(read_dtmcs(), 16)
|
||||
errinfo = (dtmcs & 0x1C0000) >> 18
|
||||
dmistat = (dtmcs & 0xC00) >> 10
|
||||
if errinfo > 0 and errinfo < 4:
|
||||
print(f"DTM Error: {errinfo_translations[errinfo]}")
|
||||
return True
|
||||
if dmistat:
|
||||
print(f"DMI status error: {op_translations[dmistat]}")
|
||||
return True
|
||||
# check if DM is inactive
|
||||
dm_active = int(read_dmi("0x10"), 16) & 0x1
|
||||
if not dm_active:
|
||||
print("DMControl Error: Debug module is not active")
|
||||
return True
|
||||
# check abstract command error
|
||||
abstractcs = int(read_dmi("0x16"), 16)
|
||||
busy = (abstractcs & 0x1000) >> 12
|
||||
cmderr = (abstractcs & 0x700) >> 8
|
||||
if not busy and cmderr:
|
||||
print(f"Abstract Command Error: {cmderr_translations[cmderr]}")
|
||||
return True
|
||||
|
||||
|
||||
def check_busy():
|
||||
"""If an Abstract Command OP is attempted while busy, an abstrcmderr will be asserted"""
|
||||
abstractcs = int(read_dmi("0x16"), 16)
|
||||
return bool((abstractcs & 0x1000) >> 12)
|
||||
|
||||
|
||||
def check_absrtcmderr():
|
||||
"""These errors must be cleared using clear_abstrcmd_err() before another OP can be executed"""
|
||||
abstractcs = int(read_dmi("0x16"), 16)
|
||||
# CmdErr is only valid if Busy is 0
|
||||
busy = bool((abstractcs & 0x1000) >> 12)
|
||||
while busy:
|
||||
time.sleep(0.05)
|
||||
abstractcs = int(read_dmi("0x16"), 16)
|
||||
busy = bool((abstractcs & 0x1000) >> 12)
|
||||
return cmderr_translations[(abstractcs & 0x700) >> 8]
|
||||
|
||||
|
||||
def clear_abstrcmd_err():
|
||||
write_dmi("0x16", "0x700")
|
||||
|
||||
|
||||
def reset_dm():
|
||||
deactivate_dm()
|
||||
activate_dm()
|
||||
|
||||
|
||||
def activate_dm():
|
||||
write_dmi("0x10", "0x1")
|
||||
return int(read_dmi("0x10"), 16) & 0x1
|
||||
|
||||
|
||||
def deactivate_dm():
|
||||
write_dmi("0x10", "0x0")
|
||||
return not int(read_dmi("0x10"), 16) & 0x1
|
||||
|
||||
|
||||
def dmi_reset():
|
||||
"""Reset sticky dmi error status in DTM"""
|
||||
write_dtmcs(dmireset=True)
|
||||
check_errors()
|
||||
|
||||
|
||||
def write_dmi(address, data):
|
||||
cmd = f"riscv dmi_write {address} {data}"
|
||||
rsp = execute(cmd)
|
||||
if "Failed" in rsp:
|
||||
print(rsp)
|
||||
|
||||
|
||||
def read_dmi(address):
|
||||
cmd = f"riscv dmi_read {address}"
|
||||
return execute(cmd)
|
||||
|
||||
|
||||
def write_dtmcs(dtmhardreset=False, dmireset=False):
|
||||
data = 0
|
||||
if dtmhardreset:
|
||||
data += 0x1 << 17
|
||||
if dmireset:
|
||||
data += 0x1 << 16
|
||||
execute(f"irscan {tapname} 0x10") # dtmcs instruction
|
||||
execute(f"drscan {tapname} 32 {hex(data)}")
|
||||
|
||||
|
||||
def read_dtmcs():
|
||||
execute(f"irscan {tapname} 0x10") # dtmcs instruction
|
||||
dtmcs = execute(f"drscan {tapname} 32 0x0")
|
||||
return dtmcs
|
||||
|
||||
|
||||
def trst():
|
||||
execute("pathmove RESET IDLE")
|
||||
|
||||
|
||||
def execute(cmd):
|
||||
write(cmd)
|
||||
return read()
|
||||
|
||||
|
||||
def write(cmd):
|
||||
if debug:
|
||||
print(f"Executing command: '{cmd}'")
|
||||
tn.write(cmd.encode('ascii') + b"\n")
|
||||
tn.read_until(b"\n")
|
||||
|
||||
|
||||
def read():
|
||||
data = b""
|
||||
data = tn.read_until(b"> ").decode('ascii')
|
||||
data = data.replace("\r", "").replace("\n", "").replace("> ", "")
|
||||
if debug:
|
||||
print(data)
|
||||
return data
|
||||
|
||||
|
||||
def interrogate():
|
||||
global XLEN
|
||||
global tapname
|
||||
write("scan_chain")
|
||||
raw = tn.read_until(b"> ").decode('ascii')
|
||||
scan_chain = raw.replace("\r", "").replace("> ", "")
|
||||
scan_chain = [tap for tap in scan_chain.split("\n")[2:] if tap]
|
||||
if len(scan_chain) > 1:
|
||||
print(f"Found multiple taps. Selecting tap #0\n{raw}")
|
||||
scan_chain = scan_chain[0]
|
||||
tapname = re.search("\d\s+(.+?)\s+", scan_chain).group(1)
|
||||
print(f"DM tapname: {tapname}")
|
||||
|
||||
write("riscv info")
|
||||
info = tn.read_until(b"> ").decode('ascii').replace("\r", "").replace("> ", "").split("\n")
|
||||
for line in info:
|
||||
if XLEN := re.search("hart.xlen\s+(\d+)", line).group(1):
|
||||
XLEN = int(XLEN)
|
||||
break
|
||||
print(f"XLEN: {XLEN}")
|
||||
|
||||
|
||||
def init():
|
||||
global tn
|
||||
tn = Telnet("127.0.0.1", 4444)
|
||||
atexit.register(cleanup)
|
||||
read() # clear welcome message from read buffer
|
||||
interrogate()
|
||||
activate_dm()
|
||||
# TODO: query gpr count
|
||||
|
||||
|
||||
def cleanup():
|
||||
tn.close()
|
||||
|
||||
|
||||
# 6.1.4 dtmcs errinfo translation table
|
||||
errinfo_translations = {
|
||||
0 : "not implemented",
|
||||
1 : "dmi error",
|
||||
2 : "communication error",
|
||||
3 : "device error",
|
||||
4 : "unknown",
|
||||
}
|
||||
|
||||
|
||||
# 6.1.5 DMI op translation table
|
||||
op_translations = {
|
||||
0 : "success",
|
||||
1 : "reserved",
|
||||
2 : "failed",
|
||||
3 : "busy",
|
||||
}
|
||||
|
||||
|
||||
# 3.14.6 Abstract command CmdErr value translation table
|
||||
cmderr_translations = {
|
||||
0 : None,
|
||||
1 : "busy",
|
||||
2 : "not supported",
|
||||
3 : "exception",
|
||||
4 : "halt/resume",
|
||||
5 : "bus",
|
||||
6 : "reserved",
|
||||
7 : "other",
|
||||
}
|
||||
|
||||
|
||||
# Register alias to regno translation table
|
||||
register_translations = {
|
||||
"MISA" : "0x0301",
|
||||
"TRAPM" : "0xC000",
|
||||
"PCM" : "0xC001",
|
||||
"INSTRM" : "0xC002",
|
||||
"MEMRWM" : "0xC003",
|
||||
"INSTRVALIDM" : "0xC004",
|
||||
"WRITEDATAM" : "0xC005",
|
||||
"IEUADRM" : "0xC006",
|
||||
"READDATAM" : "0xC007",
|
||||
"x0 (zero)" : "0x1000",
|
||||
"x1 (ra)" : "0x1001",
|
||||
"x2 (sp)" : "0x1002",
|
||||
"x3 (gp)" : "0x1003",
|
||||
"x4 (tp)" : "0x1004",
|
||||
"x5 (t0)" : "0x1005",
|
||||
"x6 (t1)" : "0x1006",
|
||||
"x7 (t2)" : "0x1007",
|
||||
"x8 (s0/fp)" : "0x1008",
|
||||
"x9 (s1)" : "0x1009",
|
||||
"x10 (a0)" : "0x100A",
|
||||
"x11 (a1)" : "0x100B",
|
||||
"x12 (a2)" : "0x100C",
|
||||
"x13 (a3)" : "0x100D",
|
||||
"x14 (a4)" : "0x100E",
|
||||
"x15 (a5)" : "0x100F",
|
||||
"x16 (a6)" : "0x1010",
|
||||
"x17 (a7)" : "0x1011",
|
||||
"x18 (s2)" : "0x1012",
|
||||
"x19 (s3)" : "0x1013",
|
||||
"x20 (s4)" : "0x1014",
|
||||
"x21 (s5)" : "0x1015",
|
||||
"x22 (s6)" : "0x1016",
|
||||
"x23 (s7)" : "0x1017",
|
||||
"x24 (s8)" : "0x1018",
|
||||
"x25 (s9)" : "0x1019",
|
||||
"x26 (s10)" : "0x101A",
|
||||
"x27 (s11)" : "0x101B",
|
||||
"x28 (t3)" : "0x101C",
|
||||
"x29 (t4)" : "0x101D",
|
||||
"x30 (t5)" : "0x101E",
|
||||
"x31 (t6)" : "0x101F",
|
||||
"f0 (ft0)" : "0x1020",
|
||||
"f1 (ft1)" : "0x1021",
|
||||
"f2 (ft2)" : "0x1022",
|
||||
"f3 (ft3)" : "0x1023",
|
||||
"f4 (ft4)" : "0x1024",
|
||||
"f5 (ft5)" : "0x1025",
|
||||
"f6 (ft6)" : "0x1026",
|
||||
"f7 (ft7)" : "0x1027",
|
||||
"f8 (fs0)" : "0x1028",
|
||||
"f9 (fs1)" : "0x1029",
|
||||
"f10 (fa0)" : "0x102A",
|
||||
"f11 (fa1)" : "0x102B",
|
||||
"f12 (fa2)" : "0x102C",
|
||||
"f13 (fa3)" : "0x102D",
|
||||
"f14 (fa4)" : "0x102E",
|
||||
"f15 (fa5)" : "0x102F",
|
||||
"f16 (fa6)" : "0x1030",
|
||||
"f17 (fa7)" : "0x1031",
|
||||
"f18 (fs2)" : "0x1032",
|
||||
"f19 (fs3)" : "0x1033",
|
||||
"f20 (fs4)" : "0x1034",
|
||||
"f21 (fs5)" : "0x1035",
|
||||
"f22 (fs6)" : "0x1036",
|
||||
"f23 (fs7)" : "0x1037",
|
||||
"f24 (fs8)" : "0x1038",
|
||||
"f25 (fs9)" : "0x1039",
|
||||
"f26 (fs10)" : "0x103A",
|
||||
"f27 (fs11)" : "0x103B",
|
||||
"f28 (ft8)" : "0x103C",
|
||||
"f29 (ft9)" : "0x103D",
|
||||
"f30 (ft10)" : "0x103E",
|
||||
"f31 (ft11)" : "0x103F",
|
||||
}
|
||||
|
||||
nonstandard_register_lengths = {
|
||||
"TRAPM" : 1,
|
||||
"INSTRM" : 32,
|
||||
"MEMRWM" : 2,
|
||||
"INSTRVALIDM" : 1,
|
||||
"READDATAM" : 64
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
# Written: matthew.n.otto@okstate.edu
|
||||
# Created: 19 April 2024
|
||||
#
|
||||
# Purpose: Send test commands to OpenOCD via local telnet connection
|
||||
# Purpose: script to automate testing of hardware debug interface
|
||||
#
|
||||
# A component of the CORE-V-WALLY configurable RISC-V project.
|
||||
# https:#github.com/openhwgroup/cvw
|
||||
@ -30,98 +30,96 @@
|
||||
import random
|
||||
import time
|
||||
|
||||
import hw_debug_interface
|
||||
from hw_debug_interface import *
|
||||
from openocd_tcl_wrapper import OpenOCD
|
||||
|
||||
random_stimulus = False
|
||||
random_stimulus = True
|
||||
|
||||
def main():
|
||||
registers = dict.fromkeys(register_translations.keys(),[])
|
||||
reg_addrs = list(registers.keys())
|
||||
with OpenOCD() as cvw:
|
||||
registers = dict.fromkeys(cvw.register_translations.keys(),[])
|
||||
reg_addrs = list(registers.keys())
|
||||
|
||||
init()
|
||||
global XLEN
|
||||
XLEN = hw_debug_interface.XLEN
|
||||
reset_dm()
|
||||
reset_hart()
|
||||
global XLEN
|
||||
XLEN = cvw.LLEN
|
||||
global nonstandard_register_lengths
|
||||
nonstandard_register_lengths = cvw.nonstandard_register_lengths
|
||||
|
||||
time.sleep(70) # wait for OpenSBI
|
||||
cvw.reset_dm()
|
||||
cvw.reset_hart()
|
||||
|
||||
halt()
|
||||
status()
|
||||
time.sleep(70) # wait for OpenSBI
|
||||
|
||||
# dump data in all registers
|
||||
for r in reg_addrs:
|
||||
try:
|
||||
data = read_data(r)
|
||||
registers[r] = data
|
||||
print(f"{r}: {data}")
|
||||
except Exception as e:
|
||||
if e.args[0] == "exception": # Invalid register (not implemented)
|
||||
del registers[r]
|
||||
clear_abstrcmd_err()
|
||||
else:
|
||||
cvw.halt()
|
||||
|
||||
# dump data in all registers
|
||||
for r in reg_addrs:
|
||||
try:
|
||||
data = cvw.read_data(r)
|
||||
registers[r] = data
|
||||
print(f"{r}: {data}")
|
||||
except Exception as e:
|
||||
if e.args[0] == "exception": # Invalid register (not implemented)
|
||||
del registers[r]
|
||||
cvw.clear_abstrcmd_err()
|
||||
else:
|
||||
raise e
|
||||
input("Compare values to ILA, press any key to continue")
|
||||
|
||||
# Write random data to all registers
|
||||
reg_addrs = list(registers.keys())
|
||||
if random_stimulus:
|
||||
random.shuffle(reg_addrs)
|
||||
test_reg_data = {}
|
||||
for r in reg_addrs:
|
||||
test_data = random_hex(r)
|
||||
try:
|
||||
cvw.write_data(r, test_data)
|
||||
test_reg_data[r] = test_data
|
||||
print(f"Writing {test_data} to {r}")
|
||||
except Exception as e:
|
||||
if e.args[0] == "not supported": # Register is read only
|
||||
del registers[r]
|
||||
cvw.clear_abstrcmd_err()
|
||||
else:
|
||||
raise e
|
||||
|
||||
# GPR X0 is always 0
|
||||
test_reg_data["x0"] = "0x" + "0"*(cvw.LLEN//4)
|
||||
|
||||
# Confirm data was written correctly
|
||||
reg_addrs = list(registers.keys())
|
||||
if random_stimulus:
|
||||
random.shuffle(reg_addrs)
|
||||
for r in reg_addrs:
|
||||
try:
|
||||
rdata = cvw.read_data(r)
|
||||
except Exception as e:
|
||||
raise e
|
||||
input("Compare values to ILA, press any key to continue")
|
||||
|
||||
# Write random data to all registers
|
||||
reg_addrs = list(registers.keys())
|
||||
if random_stimulus:
|
||||
random.shuffle(reg_addrs)
|
||||
test_reg_data = {}
|
||||
for r in reg_addrs:
|
||||
test_data = random_hex(r)
|
||||
try:
|
||||
write_data(r, test_data)
|
||||
test_reg_data[r] = test_data
|
||||
print(f"Writing {test_data} to {r}")
|
||||
except Exception as e:
|
||||
if e.args[0] == "not supported": # Register is read only
|
||||
del registers[r]
|
||||
clear_abstrcmd_err()
|
||||
if rdata != test_reg_data[r]:
|
||||
print(f"Error: register {r} read did not return correct data: {rdata} != {test_reg_data[r]}")
|
||||
else:
|
||||
print(f"Reading {rdata} from {r}")
|
||||
|
||||
# Return all registers to original state
|
||||
reg_addrs = list(registers.keys())
|
||||
for r in reg_addrs:
|
||||
print(f"Writing {registers[r]} to {r}")
|
||||
try:
|
||||
cvw.write_data(r, registers[r])
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
check_errors()
|
||||
# Confirm data was written correctly
|
||||
for r in reg_addrs:
|
||||
try:
|
||||
rdata = cvw.read_data(r)
|
||||
except Exception as e:
|
||||
raise e
|
||||
if rdata != registers[r]:
|
||||
raise Exception(f"Register {r} read did not return correct data: {rdata} != {registers[r]}")
|
||||
print("All writes successful")
|
||||
|
||||
# GPR X0 is always 0
|
||||
test_reg_data["X0"] = "0x" + "0"*(XLEN//4)
|
||||
|
||||
# Confirm data was written correctly
|
||||
reg_addrs = list(registers.keys())
|
||||
if random_stimulus:
|
||||
random.shuffle(reg_addrs)
|
||||
for r in reg_addrs:
|
||||
try:
|
||||
rdata = read_data(r)
|
||||
except Exception as e:
|
||||
raise e
|
||||
if rdata != test_reg_data[r]:
|
||||
print(f"Error: register {r} read did not return correct data: {rdata} != {test_reg_data[r]}")
|
||||
else:
|
||||
print(f"Reading {rdata} from {r}")
|
||||
|
||||
# Return all registers to original state
|
||||
reg_addrs = list(registers.keys())
|
||||
for r in reg_addrs:
|
||||
print(f"Writing {registers[r]} to {r}")
|
||||
try:
|
||||
write_data(r, registers[r])
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
# Confirm data was written correctly
|
||||
for r in reg_addrs:
|
||||
try:
|
||||
rdata = read_data(r)
|
||||
except Exception as e:
|
||||
raise e
|
||||
if rdata != registers[r]:
|
||||
raise Exception(f"Register {r} read did not return correct data: {rdata} != {registers[r]}")
|
||||
print("All writes successful")
|
||||
|
||||
resume()
|
||||
status()
|
||||
cvw.resume()
|
||||
|
||||
|
||||
def random_hex(reg_name):
|
||||
|
392
bin/openocd_tcl_wrapper.py
Normal file
392
bin/openocd_tcl_wrapper.py
Normal file
@ -0,0 +1,392 @@
|
||||
#########################################################################################
|
||||
# openocd_tcl_wrapper.py
|
||||
#
|
||||
# Written: matthew.n.otto@okstate.edu
|
||||
# Created: 8 June 2024
|
||||
#
|
||||
# Purpose: Python wrapper library used to send debug commands to OpenOCD
|
||||
#
|
||||
# A component of the CORE-V-WALLY configurable RISC-V project.
|
||||
# https://github.com/openhwgroup/cvw
|
||||
#
|
||||
# Copyright (C) 2021-24 Harvey Mudd College & Oklahoma State University
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1
|
||||
#
|
||||
# Licensed under the Solderpad Hardware License v 2.1 (the “License”); you may not use this file
|
||||
# except in compliance with the License, or, at your option, the Apache License version 2.0. You
|
||||
# may obtain a copy of the License at
|
||||
#
|
||||
# https://solderpad.org/licenses/SHL-2.1/
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, any work distributed under the
|
||||
# License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
# either express or implied. See the License for the specific language governing permissions
|
||||
# and limitations under the License.
|
||||
#########################################################################################
|
||||
|
||||
import math
|
||||
import socket
|
||||
import time
|
||||
|
||||
ENDMSG = b'\x1a'
|
||||
|
||||
class OpenOCD:
|
||||
def __init__(self):
|
||||
self.tcl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
def __enter__(self):
|
||||
self.tcl.connect(("127.0.0.1", 6666))
|
||||
self.LLEN = 64 #TODO: find this
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
try:
|
||||
self.send("exit")
|
||||
finally:
|
||||
self.tcl.close()
|
||||
|
||||
def capture(self, cmd):
|
||||
return self.send(f"capture \"{cmd}\"")
|
||||
|
||||
def send(self, cmd):
|
||||
data = cmd.encode("ascii") + ENDMSG
|
||||
self.tcl.send(data)
|
||||
return self.receive()
|
||||
|
||||
def receive(self):
|
||||
data = bytes()
|
||||
while True:
|
||||
byte = self.tcl.recv(1)
|
||||
if byte == ENDMSG:
|
||||
break
|
||||
else:
|
||||
data += byte
|
||||
data = data.decode("ascii").rstrip()
|
||||
return data
|
||||
|
||||
def trst(self):
|
||||
self.send("pathmove RESET IDLE")
|
||||
|
||||
def write_dtmcs(self, dtmhardreset=False, dmireset=False):
|
||||
"""Send reset commands to DTMCS. Used to clear sticky DMI OP error status"""
|
||||
data = 0
|
||||
data |= dtmhardreset << 17
|
||||
data |= dmireset << 16
|
||||
if not data:
|
||||
print("Warning: not writing DTMCS (dtmhardreset and dmireset are both false)")
|
||||
return
|
||||
tapname = "cvw.cpu"
|
||||
self.send(f"irscan {tapname} 0x10") # dtmcs instruction
|
||||
self.send(f"drscan {tapname} 32 {hex(data)}")
|
||||
op = self.capture(f"drscan {tapname} 32 0x0")
|
||||
if (int(op) >> 10) & 0x3:
|
||||
raise Exception("Error: failed to reset DTMCS (nonzero dmistat)")
|
||||
|
||||
def write_dmi(self, address, data):
|
||||
cmd = f"riscv dmi_write {address} {data}"
|
||||
rsp = self.capture(cmd)
|
||||
if "Failed" in rsp:
|
||||
raise Exception(rsp)
|
||||
|
||||
def read_dmi(self, address):
|
||||
cmd = f"riscv dmi_read {address}"
|
||||
return self.capture(cmd)
|
||||
|
||||
def activate_dm(self):
|
||||
self.write_dmi("0x10", "0x1")
|
||||
dmstat = int(self.read_dmi("0x10"), 16)
|
||||
if not dmstat & 0x1:
|
||||
raise Exception("Error: failed to activate debug module")
|
||||
|
||||
def reset_dm(self):
|
||||
self.write_dmi("0x10", "0x0")
|
||||
dmstat = int(self.read_dmi("0x10"), 16)
|
||||
if dmstat & 0x1:
|
||||
raise Exception("Error: failed to deactivate debug module")
|
||||
self.activate_dm()
|
||||
|
||||
def reset_hart(self):
|
||||
self.write_dmi("0x10", "0x3")
|
||||
self.write_dmi("0x10", "0x1")
|
||||
dmstat = int(self.read_dmi("0x11"), 16) # check HaveReset
|
||||
if not ((dmstat >> 18) & 0x3):
|
||||
raise Exception("Error: Hart failed to reset")
|
||||
self.write_dmi("0x10", "0x10000001") # ack HaveReset
|
||||
|
||||
def set_haltonreset(self):
|
||||
self.write_dmi("0x10", "0x9")
|
||||
|
||||
def clear_haltonreset(self):
|
||||
self.write_dmi("0x10", "0x5")
|
||||
|
||||
def halt(self):
|
||||
self.write_dmi("0x10", "0x80000001")
|
||||
dmstat = int(self.read_dmi("0x11"), 16) # Check halted bit
|
||||
if not ((dmstat >> 8) & 0x3):
|
||||
raise Exception("Error: Hart failed to halt")
|
||||
|
||||
def resume(self):
|
||||
self.write_dmi("0x10", "0x40000001") # Send resume command
|
||||
dmstat = int(self.read_dmi("0x11"), 16) # Check resumeack bit
|
||||
if not ((dmstat >> 16) & 0x3):
|
||||
raise Exception("Error: Hart failed to resume")
|
||||
self.write_dmi("0x10", "0x40000001") # Clear resumeack bit
|
||||
|
||||
def step(self):
|
||||
self.write_dmi("0x10", "0xC0000001")
|
||||
# BOZO: checking resumeack after halt is pointless until sdext halt method is added
|
||||
dmstat = int(self.read_dmi("0x11"), 16)
|
||||
if not ((dmstat >> 16) & 0x3):
|
||||
raise Exception("Error: Hart failed to resume")
|
||||
|
||||
def access_register(self, write, regno, addr_size=None):
|
||||
data = 1 << 17 # transfer bit always set
|
||||
if not addr_size:
|
||||
addr_size = self.LLEN
|
||||
elif addr_size not in (32, 64, 128):
|
||||
raise Exception("must provide valid register access size (32, 64, 128). See: 3.7.1.1 aarsize")
|
||||
data += int(math.log2(addr_size // 8)) << 20
|
||||
data += write << 16
|
||||
data += regno
|
||||
self.write_dmi("0x17", hex(data))
|
||||
|
||||
def write_data(self, register, data):
|
||||
"""Write data to specified register"""
|
||||
# Write data to 32 bit message registers
|
||||
data = int(data, 16)
|
||||
self.write_dmi("0x4", hex(data & 0xffffffff))
|
||||
if self.LLEN >= 64:
|
||||
self.write_dmi("0x5", hex((data >> 32) & 0xffffffff))
|
||||
if self.LLEN == 128:
|
||||
self.write_dmi("0x6", hex((data >> 64) & 0xffffffff))
|
||||
self.write_dmi("0x7", hex((data >> 96) & 0xffffffff))
|
||||
# Translate register alias to DM regno
|
||||
regno = self.translate_regno(register)
|
||||
# Transfer data from msg registers to target register
|
||||
self.access_register(write=True, regno=regno)
|
||||
# Check that operations completed without error
|
||||
if acerr := self.check_abstrcmderr():
|
||||
raise Exception(acerr)
|
||||
|
||||
def read_data(self, register):
|
||||
"""Read data from specified register"""
|
||||
# Translate register alias to DM regno
|
||||
regno = self.translate_regno(register)
|
||||
# Transfer data from target register to msg registers
|
||||
self.access_register(write=False, regno=regno)
|
||||
# Read data from 32 bit message registers
|
||||
data = ""
|
||||
data = self.read_dmi("0x4").replace("0x", "").zfill(8)
|
||||
if self.LLEN >= 64:
|
||||
data = self.read_dmi("0x5").replace("0x", "").zfill(8) + data
|
||||
if self.LLEN == 128:
|
||||
data = self.read_dmi("0x6").replace("0x", "").zfill(8) + data
|
||||
data = self.read_dmi("0x7").replace("0x", "").zfill(8) + data
|
||||
# Check that operations completed without error
|
||||
if acerr := self.check_abstrcmderr():
|
||||
raise Exception(acerr)
|
||||
return f"0x{data}"
|
||||
|
||||
def translate_regno(self, register):
|
||||
if register not in self.register_translations:
|
||||
register = self.abi_translations[register]
|
||||
return int(self.register_translations[register], 16)
|
||||
|
||||
def check_abstrcmderr(self):
|
||||
"""These errors must be cleared using clear_abstrcmd_err() before another OP can be executed"""
|
||||
abstractcs = int(self.read_dmi("0x16"), 16)
|
||||
# CmdErr is only valid if Busy is 0
|
||||
while True:
|
||||
if not bool((abstractcs & 0x1000) >> 12): # if not Busy
|
||||
break
|
||||
time.sleep(0.05)
|
||||
abstractcs = int(self.read_dmi("0x16"), 16)
|
||||
return self.cmderr_translations[(abstractcs & 0x700) >> 8]
|
||||
|
||||
def clear_abstrcmd_err(self):
|
||||
self.write_dmi("0x16", "0x700")
|
||||
if self.check_abstrcmderr():
|
||||
raise Exception("Error: failed to clear AbstrCmdErr")
|
||||
|
||||
# 6.1.4 dtmcs errinfo translation table
|
||||
errinfo_translations = {
|
||||
0 : "not implemented",
|
||||
1 : "dmi error",
|
||||
2 : "communication error",
|
||||
3 : "device error",
|
||||
4 : "unknown",
|
||||
}
|
||||
|
||||
# 6.1.5 DMI op translation table
|
||||
op_translations = {
|
||||
0 : "success",
|
||||
1 : "reserved",
|
||||
2 : "failed",
|
||||
3 : "busy",
|
||||
}
|
||||
|
||||
# 3.14.6 Abstract command CmdErr value translation table
|
||||
cmderr_translations = {
|
||||
0 : None,
|
||||
1 : "busy",
|
||||
2 : "not supported",
|
||||
3 : "exception",
|
||||
4 : "halt/resume",
|
||||
5 : "bus",
|
||||
6 : "reserved",
|
||||
7 : "other",
|
||||
}
|
||||
|
||||
# Register alias to regno translation table
|
||||
register_translations = {
|
||||
"MISA" : "0x0301",
|
||||
"TRAPM" : "0xC000",
|
||||
"PCM" : "0xC001",
|
||||
"INSTRM" : "0xC002",
|
||||
"MEMRWM" : "0xC003",
|
||||
"INSTRVALIDM" : "0xC004",
|
||||
"WRITEDATAM" : "0xC005",
|
||||
"IEUADRM" : "0xC006",
|
||||
"READDATAM" : "0xC007",
|
||||
"x0" : "0x1000",
|
||||
"x1" : "0x1001",
|
||||
"x2" : "0x1002",
|
||||
"x3" : "0x1003",
|
||||
"x4" : "0x1004",
|
||||
"x5" : "0x1005",
|
||||
"x6" : "0x1006",
|
||||
"x7" : "0x1007",
|
||||
"x8" : "0x1008",
|
||||
"x9" : "0x1009",
|
||||
"x10" : "0x100A",
|
||||
"x11" : "0x100B",
|
||||
"x12" : "0x100C",
|
||||
"x13" : "0x100D",
|
||||
"x14" : "0x100E",
|
||||
"x15" : "0x100F",
|
||||
"x16" : "0x1010",
|
||||
"x17" : "0x1011",
|
||||
"x18" : "0x1012",
|
||||
"x19" : "0x1013",
|
||||
"x20" : "0x1014",
|
||||
"x21" : "0x1015",
|
||||
"x22" : "0x1016",
|
||||
"x23" : "0x1017",
|
||||
"x24" : "0x1018",
|
||||
"x25" : "0x1019",
|
||||
"x26" : "0x101A",
|
||||
"x27" : "0x101B",
|
||||
"x28" : "0x101C",
|
||||
"x29" : "0x101D",
|
||||
"x30" : "0x101E",
|
||||
"x31" : "0x101F",
|
||||
"f0" : "0x1020",
|
||||
"f1" : "0x1021",
|
||||
"f2" : "0x1022",
|
||||
"f3" : "0x1023",
|
||||
"f4" : "0x1024",
|
||||
"f5" : "0x1025",
|
||||
"f6" : "0x1026",
|
||||
"f7" : "0x1027",
|
||||
"f8" : "0x1028",
|
||||
"f9" : "0x1029",
|
||||
"f10" : "0x102A",
|
||||
"f11" : "0x102B",
|
||||
"f12" : "0x102C",
|
||||
"f13" : "0x102D",
|
||||
"f14" : "0x102E",
|
||||
"f15" : "0x102F",
|
||||
"f16" : "0x1030",
|
||||
"f17" : "0x1031",
|
||||
"f18" : "0x1032",
|
||||
"f19" : "0x1033",
|
||||
"f20" : "0x1034",
|
||||
"f21" : "0x1035",
|
||||
"f22" : "0x1036",
|
||||
"f23" : "0x1037",
|
||||
"f24" : "0x1038",
|
||||
"f25" : "0x1039",
|
||||
"f26" : "0x103A",
|
||||
"f27" : "0x103B",
|
||||
"f28" : "0x103C",
|
||||
"f29" : "0x103D",
|
||||
"f30" : "0x103E",
|
||||
"f31" : "0x103F",
|
||||
}
|
||||
|
||||
abi_translations = {
|
||||
"x0" : "zero",
|
||||
"x1" : "ra",
|
||||
"x2" : "sp",
|
||||
"x3" : "gp",
|
||||
"x4" : "tp",
|
||||
"x5" : "t0",
|
||||
"x6" : "t1",
|
||||
"x7" : "t2",
|
||||
"x8" : "s0/fp",
|
||||
"x9" : "s1",
|
||||
"x10" : "a0",
|
||||
"x11" : "a1",
|
||||
"x12" : "a2",
|
||||
"x13" : "a3",
|
||||
"x14" : "a4",
|
||||
"x15" : "a5",
|
||||
"x16" : "a6",
|
||||
"x17" : "a7",
|
||||
"x18" : "s2",
|
||||
"x19" : "s3",
|
||||
"x20" : "s4",
|
||||
"x21" : "s5",
|
||||
"x22" : "s6",
|
||||
"x23" : "s7",
|
||||
"x24" : "s8",
|
||||
"x25" : "s9",
|
||||
"x26" : "s10",
|
||||
"x27" : "s11",
|
||||
"x28" : "t3",
|
||||
"x29" : "t4",
|
||||
"x30" : "t5",
|
||||
"x31" : "t6",
|
||||
"f0" : "ft0",
|
||||
"f1" : "ft1",
|
||||
"f2" : "ft2",
|
||||
"f3" : "ft3",
|
||||
"f4" : "ft4",
|
||||
"f5" : "ft5",
|
||||
"f6" : "ft6",
|
||||
"f7" : "ft7",
|
||||
"f8" : "fs0",
|
||||
"f9" : "fs1",
|
||||
"f10" : "fa0",
|
||||
"f11" : "fa1",
|
||||
"f12" : "fa2",
|
||||
"f13" : "fa3",
|
||||
"f14" : "fa4",
|
||||
"f15" : "fa5",
|
||||
"f16" : "fa6",
|
||||
"f17" : "fa7",
|
||||
"f18" : "fs2",
|
||||
"f19" : "fs3",
|
||||
"f20" : "fs4",
|
||||
"f21" : "fs5",
|
||||
"f22" : "fs6",
|
||||
"f23" : "fs7",
|
||||
"f24" : "fs8",
|
||||
"f25" : "fs9",
|
||||
"f26" : "fs10",
|
||||
"f27" : "fs11",
|
||||
"f28" : "ft8",
|
||||
"f29" : "ft9",
|
||||
"f30" : "ft10",
|
||||
"f31" : "ft11",
|
||||
}
|
||||
abi_translations |= dict(map(reversed, abi_translations.items())) # two way translations
|
||||
|
||||
nonstandard_register_lengths = {
|
||||
"TRAPM" : 1,
|
||||
"INSTRM" : 32,
|
||||
"MEMRWM" : 2,
|
||||
"INSTRVALIDM" : 1,
|
||||
"READDATAM" : 64
|
||||
}
|
@ -6,7 +6,7 @@ adapter driver ftdi
|
||||
# when multiple adapters with the same vid_pid are connected (ex: arty-a7 and usb-jtag)
|
||||
# need to specify which usb port to drive
|
||||
# find numerical path using command "lsusb -t" (<bus>-<port>)
|
||||
adapter usb location 1-3
|
||||
adapter usb location 1-4
|
||||
|
||||
ftdi vid_pid 0x0403 0x6010
|
||||
ftdi channel 0
|
||||
|
Loading…
Reference in New Issue
Block a user