Source code for chipscopy.api.ila.ila_probe

# Copyright 2021 Xilinx, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# 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 enum
from dataclasses import dataclass
from typing import NamedTuple, Any, Optional

from chipscopy.api import CoreType, dataclass_fields, filter_props
from chipscopy.api._detail.ltx import Ltx, LtxCore, LtxProbe
from chipscopy.client.axis_ila_core_client import (
    AxisIlaCoreClient as TCF_AxisIlaCoreClient,
    ILAPortType as TCF_ILAPortType,
)


class ILAMatchUnitType(enum.Enum):
    """
    Probe comparator types. Only LARGE is supported.

    ===========  =====================================================================
    Enum Value   Description
    ===========  =====================================================================
    SMALL        Not supported.
    MEDIUM       Not supported.
    LARGE        Supports operators: ['==', '!=', '>', '<', '>=', '<=', '||']
                  and bit values: '01xXrRfFnNbBtTlLsS\_'
    ===========  =====================================================================

    """

    SMALL = 0
    MEDIUM = 1
    LARGE = 2


class ILAProbeRadix(enum.Enum):
    """
    Radix for probe data values.

    =====================  ================================
    Enum Value             Description
    =====================  ================================
    BIN                    Binary string.
    ENUM                   Not supported.
    HEX                    Not supported.
    SIGNED                 Not supported.
    UNSIGNED               Not supported.
    =====================  ================================

    """

    BIN = 0
    ENUM = 1
    HEX = 2
    SIGNED = 3
    UNSIGNED = 4


[docs]@dataclass(frozen=True) class ILAPort: """Probe Port on the ILA Core""" bit_width: int """Number of port bits.""" data_bit_index: int """Start bit index in waveform data.""" index: int """Port index""" is_trigger: bool """Port is used for triggering.""" is_data: bool """Port is used for capturing data.""" match_unit_count: int """Number of compare match units, available to the port.""" match_unit_type: ILAMatchUnitType """Type of match unit."""
ILA_PORT_MEMBERS = dataclass_fields(ILAPort)
[docs]@dataclass(frozen=True) class ILAProbe: """Logical probe. Probes may share the same probe port.""" name: str """Name of probe.""" bit_width: int """Number of probe bits.""" is_data: bool """Probe is used to capture waveform data.""" is_trigger: bool """Probe can be used in trigger settings.""" map: str """How probe is mapped to ports.""" mu_count: int """Max number of compare match units, available to the probe.""" is_bus: bool = False """True for bus probes""" bus_left_index: Optional[int] = None """Bus left index. E.g. 5 in probe ``counter[5:0]``""" bus_right_index: Optional[int] = None """Bus right index. E.g. 0 in probe ``counter[5:0]``""" display_radix: ILAProbeRadix = ILAProbeRadix.HEX """Display radix, when exporting waveform data. Default is ILAProbeRadix.HEX"""
ILA_PROBE_MEMBERS = dataclass_fields(ILAProbe) ILA_MATCH_OPERATORS = ["==", "!=", ">", "<", ">=", "<=", "||"] """ Operators for probe compare values. ========= ================================================================= Operator Description ========= ================================================================= == Equal != Not equal < Less than <= Equal or less than > Greater than >= Equal or Greater than || Reduction OR ========= ================================================================= """ ILA_MATCH_BIT_VALUES = "01xXrRfFnNbBtTlLsS_" """ Binary bit values for probe compare values. ========= ================================================================= Bit Value Description ========= ================================================================= _ Underscore separator for readability. X Don't care bit matches any bit-value. 0 Zero 1 One F Falling. Transition 1 -> 0 R Rising. Transition 0 -> 1 L Laying. Opposite to R. S Staying. Opposite to F. B Either Falling or Rising. N No change. Opposite to B. ========= ================================================================= """
[docs]@dataclass class ILAProbeValues: """ A compare value consists of a pair: <operator> and <value> <value> may be of type int or binary/hex string of the correct bit_width. Hex values start with a '0x' prefix. An empty list is a don't-care value. Example: Test for LSB is '0' on a 4-bit probe. ['==', 'XXX0'] Example: Range check, assuming the global Trigger Condition is ILATriggerCondition.AND: ['>=', '0x3', '<=', '0x10'] Example: Range check, assuming the global Trigger Condition is ILATriggerCondition.AND: ['>=', 3, '<=', 10] Example: Test equal to any of 3 numbers, assuming the global Trigger Condition is ILATriggerCondition.OR: ['==', '0011', '==', '0111', '==', '1111'] Example: 4-bit dont-care value. ['==', 'XXXX'] Example: Dont-care value, written as an empty list. [] """ bit_width: int """Number of probe bits.""" trigger_value: [] """Trigger compare values, in a list.""" capture_value: [] """Basic capture compare value. A two item list with [<operator>, <value>]""" radix: ILAProbeRadix = ILAProbeRadix.BIN """Display radix, when exporting waveform data.""" def default_value(self): return ["==", "x" * self.bit_width]
[docs]class ILABitRange(NamedTuple): index: int """Bit index.""" length: int """Bit length."""
# # ports functions # def verify_ports(ltx: Ltx, uuid: str, ports: [ILAPort]) -> None: def get_width(port: ILAPort, trigger: bool, data: bool) -> int: return port.bit_width if port.is_trigger == trigger and port.is_data == data else 0 widths_by_type = { "DATA": [get_width(port, trigger=False, data=True) for port in ports], "DATA_TRIGGER": [get_width(port, trigger=True, data=True) for port in ports], "TRIGGER": [get_width(port, trigger=True, data=False) for port in ports], } ltx.verify_port_width_by_type(CoreType.AXIS_ILA, uuid, widths_by_type) def ports_from_tcf_props(props) -> [ILAPort]: ports = [] for p_index, p in enumerate(props["ports"]): port_info = filter_props(p, ILA_PORT_MEMBERS) port_info["index"] = p_index port_info["match_unit_count"] = len(p["mus"]) port_type = p["port_type"] port_info["is_data"] = port_type & TCF_ILAPortType.IS_DATA != 0 port_info["is_trigger"] = port_type & TCF_ILAPortType.IS_TRIGGER != 0 if port_type & TCF_ILAPortType.MU_EDGE_EQ_REL: port_info["match_unit_type"] = ILAMatchUnitType.LARGE elif port_type & TCF_ILAPortType.MU_EDGE_EQ_EDGE: port_info["match_unit_type"] = ILAMatchUnitType.MEDIUM else: port_info["match_unit_type"] = ILAMatchUnitType.SMALL ports.append(ILAPort(**port_info)) return ports # # probes functions # def verify_probe_value(probe: ILAProbe, value_list: [], is_trigger: bool): """ int values, binary string and hex strings are supported for now. Fuller verification is done by the cs_server. """ def get_hex_char_bit_len(ch: str) -> int: if ch in "x01": return 1 elif ch in "23": return 2 elif ch in "4567": return 3 else: return 4 def verify_one_hex_value(op: str, hex_number: str): valid_hex_chars = "x0123456789abcdef_" value_no_underscore = "".join([ch for ch in hex_number[2:].lower() if ch != "_"]) hex_val_len = len(value_no_underscore) if hex_val_len == 0: raise Exception( f'Probe "{probe.name}" has cannot be assigned invalid value "{hex_number}".' ) if not all([ch in valid_hex_chars for ch in value_no_underscore]): raise Exception( f'Probe "{probe.name}" value "{hex_number}" has invalid character(s).' f' Valid characters are {valid_hex_chars}".' ) required_hex_ch_len = (probe.bit_width + 3) // 4 if hex_val_len != required_hex_ch_len: raise Exception( f'Probe "{probe.name}" has bit width:{probe.bit_width} requiring {required_hex_ch_len} hex character(s)' f' but value "{hex_number}" has only {hex_val_len} hex character(s).' ) bin_val_len = (hex_val_len - 1) * 4 + get_hex_char_bit_len(value_no_underscore[0]) if bin_val_len > probe.bit_width: # Too many bits in first hex char. raise Exception( f'Probe "{probe.name}" has bit width:{probe.bit_width} and cannot be assigned' f' value "{hex_number}" which has bit width:{bin_val_len}.' ) if (op not in ["==", "!="]) and ("x" in value_no_underscore): raise Exception( f'Probe "{probe.name}" is assigned value "{op} {hex_number}". ' f"""The operator cannot be used when the value has a don't care "x".""" ) def verify_value_pair(op: str, number): if op not in ILA_MATCH_OPERATORS: raise Exception( f'Probe "{probe.name}" operator "{op}" is not one of the allowed operators: ' f"{ILA_MATCH_OPERATORS}." ) if isinstance(number, int): val_len = int.bit_length(number) if val_len > probe.bit_width: raise Exception( f'Probe "{probe.name}" has bit width:{probe.bit_width} and cannot be assigned' f' value "{number}" which has bit width:{val_len}.' ) elif isinstance(number, str) and (number.startswith("0x") or number.startswith("0X")): verify_one_hex_value(op, number) elif isinstance(number, str): # binary value if not all([ch in ILA_MATCH_BIT_VALUES for ch in number]): raise Exception( f'Probe "{probe.name}" value "{number}" has invalid character(s).' f' Valid characters are "{ILA_MATCH_BIT_VALUES}".' ) val_len = len(number) - number.count("_") if val_len != probe.bit_width: raise Exception( f'Probe "{probe.name}" has bit width:{probe.bit_width} and cannot be assigned' f' value "{number}" which has bit width:{val_len}.' ) if (op not in ["==", "!="]) and not all([ch in "01_" for ch in number]): raise Exception( f'Probe "{probe.name}" is assigned value "{op} {number}". ' f'The operator "{op}" can only be used with "0" and "1" bit values.' ) # Empty list are allowed. Used for DONT_CARE values. if not isinstance(value_list, list): raise Exception( f'Probe "{probe.name}" trigger/capture set value {value_list} is required to be a list.' ) if len(value_list) > 2 and not is_trigger: raise Exception( f'Probe "{probe.name}" capture set value {value_list} list length cannot be more than 2.' ) if len(value_list) % 2 != 0: raise Exception( f'Probe "{probe.name}" trigger/capture set value {value_list} is required to be a list' " with even number of items. Odd items are operators and even items are numbers." ) for idx in range(0, len(value_list), 2): verify_value_pair(value_list[idx], value_list[idx + 1]) def create_probes_from_ports_and_ltx( tcf_ila: TCF_AxisIlaCoreClient, ports: [ILAPort], ltx: Ltx, uuid: str ) -> ({}, {}, str): """ Returns: probes, map_to_port_seqs, cell_name """ def process_one_tcf_probe(attrs: {str, Any}) -> {}: res = { key: value for key, value in attrs.items() if key in {"bit_width", "mu_count", "map", "name"} } port_type = TCF_ILAPortType(attrs.get("port_type", 0)) res["is_data"] = TCF_ILAPortType.IS_DATA in port_type res["is_trigger"] = TCF_ILAPortType.IS_TRIGGER in port_type return res def process_one_tcf_probe_mapping(attrs: {str, Any}) -> [ILABitRange]: def create_bit_range(map_seq: ()) -> ILABitRange: port_idx, high_idx, low_idx = map_seq start = ports[port_idx].data_bit_index + low_idx length = abs(high_idx - low_idx) + 1 return ILABitRange(start, length) tcf_mapping = attrs.get("__map_seqs", []) mapping = [create_bit_range(map_seq) for map_seq in tcf_mapping] return mapping def ltx_probes_to_tcf_pdefs(ltx: LtxCore) -> [{str, str}]: return [ { "name": probe.name, "net_name": probe.name, "map": f"port{probe.port_index}[{probe.port_msb_index}:{probe.port_lsb_index}]", } for probe in ltx.probes if probe.probe_type != "DATA" ] def add_probe_bus_info(p_dicts: {}, ltx_probes: [LtxProbe]) -> None: for ltx_p in ltx_probes: if ltx_p.is_bus and ltx_p.name in p_dicts: p_dicts[ltx_p.name].update( { "is_bus": ltx_p.is_bus, "bus_left_index": ltx_p.bus_left_index, "bus_right_index": ltx_p.bus_right_index, } ) cell_name = "" tcf_ila.undefine_probe([]) ltx_core = ltx.get_core(CoreType.AXIS_ILA, uuid) if ltx else None if ltx and not ltx_core: raise Exception(f"Unable to find ILA core with uuid:{uuid} in LTX file.") if ltx_core: cell_name = ltx_core.cell_name probe_defs = ltx_probes_to_tcf_pdefs(ltx_core) tcf_ila.define_probe(probe_defs) else: tcf_ila.define_port_probes({"force": True}) probe_attrs = ["name", "bit_width", "mu_count", "port_type", "map", "__map_seqs"] tcf_probes = tcf_ila.get_probe([], probe_attrs) probe_dicts = {p_name: process_one_tcf_probe(attrs) for p_name, attrs in tcf_probes.items()} # Add bus information add_probe_bus_info(probe_dicts, ltx_core.probes) map_to_port_seqs = { attrs["map"]: process_one_tcf_probe_mapping(attrs) for p_name, attrs in tcf_probes.items() } probes = {p_name: ILAProbe(**probe) for p_name, probe in probe_dicts.items()} return probes, map_to_port_seqs, cell_name