mirror of
https://github.com/openhwgroup/cvw
synced 2025-02-11 06:05:49 +00:00
396 lines
10 KiB
Python
Executable File
396 lines
10 KiB
Python
Executable File
#########################################################################################
|
|
# 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" : "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",
|
|
}
|
|
|
|
nonstandard_register_lengths = {
|
|
"TRAPM" : 1,
|
|
"INSTRM" : 32,
|
|
"MEMRWM" : 2,
|
|
"INSTRVALIDM" : 1,
|
|
#"READDATAM" : P.LLEN # TODO: find LLEN
|
|
}
|