Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
334 changes: 281 additions & 53 deletions doc/source/reference/formats/ncdb-format.rst

Large diffs are not rendered by default.

20 changes: 18 additions & 2 deletions src/ucis/mem/mem_cover_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,21 @@ def setCoverFlags(self, flags: int):
"""Set cover flags."""
if self.data:
self.data.flags = flags



def setAttribute(self, key: str, value: str):
"""Set a user-defined attribute on this coveritem."""
if not hasattr(self, '_attributes'):
self._attributes = {}
self._attributes[key] = value

def getAttribute(self, key: str):
"""Get a user-defined attribute by key."""
if not hasattr(self, '_attributes'):
return None
return self._attributes.get(key)

def getAttributes(self):
"""Get all user-defined attributes as a dict."""
if not hasattr(self, '_attributes'):
return {}
return dict(self._attributes)
22 changes: 8 additions & 14 deletions src/ucis/mem/mem_cover_index_iterator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,17 @@ class MemCoverIndexIterator(object):

def __init__(self, coveritems : List[MemCoverIndex], mask : CoverTypeT):
self.coveritems = coveritems
self.mask = mask
self.mask = int(mask)
self.idx = 0

def __iter__(self):
return self

def __next__(self):
next = None

while self.idx < len(self.coveritems) and next is None:
mask = self.mask
while self.idx < len(self.coveritems):
n = self.coveritems[self.idx]

if (n.data.type & self.mask) != 0:
next = n
self.idx += 1

if next is None:
raise StopIteration

return next
if (int(n.data.type) & mask) != 0:
return n
raise StopIteration
20 changes: 18 additions & 2 deletions src/ucis/mem/mem_history_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,5 +253,21 @@ def setStringProperty(self, coverindex: int, property, value: str):
}
if property in _map:
setattr(self, _map[property], value)



def setAttribute(self, key: str, value: str):
"""Set a user-defined attribute on this history node."""
if not hasattr(self, '_attributes'):
self._attributes = {}
self._attributes[key] = value

def getAttribute(self, key: str):
"""Get a user-defined attribute by key."""
if not hasattr(self, '_attributes'):
return None
return self._attributes.get(key)

def getAttributes(self):
"""Get all user-defined attributes as a dict."""
if not hasattr(self, '_attributes'):
return {}
return dict(self._attributes)
13 changes: 7 additions & 6 deletions src/ucis/mem/mem_instance_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,16 @@ def createScope(self,
source : SourceT,
type : ScopeTypeT,
flags : FlagsT) -> 'Scope':
if (type & ScopeTypeT.COVERGROUP) != 0:
itype = int(type)
if (itype & int(ScopeTypeT.COVERGROUP)) != 0:
ret = MemCovergroup(self, name, srcinfo, weight, source)
elif (type & ScopeTypeT.BLOCK) != 0:
elif (itype & int(ScopeTypeT.BLOCK)) != 0:
ret = MemBlockScope(self, name, srcinfo, weight, source, flags)
elif (type & ScopeTypeT.BRANCH) != 0:
elif (itype & int(ScopeTypeT.BRANCH)) != 0:
ret = MemBranchScope(self, name, srcinfo, weight, source, flags)
elif (type & ScopeTypeT.TOGGLE) != 0:
elif (itype & int(ScopeTypeT.TOGGLE)) != 0:
ret = MemToggleScope(self, name, srcinfo, weight, source, flags)
elif (type & ScopeTypeT.FSM) != 0:
elif (itype & int(ScopeTypeT.FSM)) != 0:
from ucis.mem.mem_fsm_scope import MemFSMScope
ret = MemFSMScope(self, name, srcinfo, weight, source, flags)
else:
Expand Down Expand Up @@ -96,4 +97,4 @@ def createToggle(self,
def getIthCoverItem(self, i)->CoverItem:
return self.m_cover_item_l[i]



22 changes: 8 additions & 14 deletions src/ucis/mem/mem_scope_iterator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,19 @@ class MemScopeIterator(object):
def __init__(self, nodes : List['MemScope'], mask):
self.nodes = nodes
self.idx = 0
self.mask = mask
self.mask = int(mask)

def __iter__(self):
return self

def __next__(self):
next = None
mask = self.mask

while next is None and self.idx < len(self.nodes):
while self.idx < len(self.nodes):
n = self.nodes[self.idx]
# TODO: qualify mask
if (n.getScopeType() & self.mask) != 0:
next = n

self.idx += 1

if next is not None:
break
if (int(n.getScopeType()) & mask) != 0:
return n

if next is None:
raise StopIteration
return next
raise StopIteration
Binary file modified src/ucis/ncdb/_accel/_ncdb_accel.o
Binary file not shown.
129 changes: 118 additions & 11 deletions src/ucis/ncdb/attrs.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,100 @@
"""
attrs.json — user-defined attribute serialization.
attrs.bin — user-defined attribute serialization.

Format: JSON object
Format v1 (legacy): JSON object
{"version": 1, "entries": [{"idx": <int>, "attrs": {<key>: <val>}}, ...]}

Only scopes that have at least one attribute are included (sparse).
Format v2 (current): JSON object with sections for scopes, coveritems,
history nodes, and global attrs.
{"version": 2,
"scopes": [{"idx": <int>, "attrs": {<key>: <val>}}, ...],
"coveritems": [{"scope_idx": <int>, "ci_idx": <int>, "attrs": {...}}, ...],
"history": [{"idx": <int>, "attrs": {...}}, ...],
"global": {<key>: <val>}}
"""

import json

from .dfs_util import dfs_scope_list
from ucis.history_node_kind import HistoryNodeKind

_VERSION = 1
_VERSION = 2
_COVER_ALL = 0xFFFFFFFF


class AttrsWriter:
"""Serialize user-defined scope attributes to attrs.json bytes."""
"""Serialize user-defined attributes to attrs.bin bytes."""

def serialize(self, db) -> bytes:
scopes = dfs_scope_list(db)
entries = []
scope_entries = []
for idx, scope in enumerate(scopes):
if not hasattr(scope, 'getAttributes'):
continue
attrs = scope.getAttributes()
if attrs:
entries.append({"idx": idx, "attrs": attrs})
payload = {"version": _VERSION, "entries": entries}
scope_entries.append({"idx": idx, "attrs": attrs})

ci_entries = []
for idx, scope in enumerate(scopes):
try:
items = list(scope.coverItems(_COVER_ALL))
except Exception:
continue
for ci_idx, ci in enumerate(items):
if not hasattr(ci, 'getAttributes'):
continue
attrs = ci.getAttributes()
if attrs:
ci_entries.append({
"scope_idx": idx, "ci_idx": ci_idx, "attrs": attrs
})

hist_entries = []
for kind in (HistoryNodeKind.TEST, HistoryNodeKind.MERGE):
try:
nodes = list(db.historyNodes(kind))
except Exception:
continue
for hi, node in enumerate(nodes):
if not hasattr(node, 'getAttributes'):
continue
attrs = node.getAttributes()
if attrs:
hist_entries.append({
"idx": hi, "kind": kind.name, "attrs": attrs
})

global_attrs = {}
if hasattr(db, 'getAttributes'):
global_attrs = db.getAttributes()

payload = {
"version": _VERSION,
"scopes": scope_entries,
"coveritems": ci_entries,
"history": hist_entries,
"global": global_attrs,
}
return json.dumps(payload, separators=(',', ':')).encode()


class AttrsReader:
"""Deserialize attrs.json bytes and apply attributes to scope tree."""
"""Deserialize attrs.bin bytes and apply attributes."""

def deserialize(self, data: bytes, db) -> None:
if not data:
return
payload = json.loads(data.decode())
if payload.get("version") != _VERSION:
raise ValueError(f"Unsupported attrs.json version: {payload.get('version')}")
version = payload.get("version", 1)

if version == 1:
self._deserialize_v1(payload, db)
elif version == 2:
self._deserialize_v2(payload, db)

def _deserialize_v1(self, payload, db):
"""Legacy v1: scope attrs only."""
entries = payload.get("entries", [])
if not entries:
return
Expand All @@ -50,3 +106,54 @@ def deserialize(self, data: bytes, db) -> None:
for key, val in entry.get("attrs", {}).items():
if hasattr(scope, 'setAttribute'):
scope.setAttribute(key, val)

def _deserialize_v2(self, payload, db):
"""V2: scopes + coveritems + history + global."""
scopes = dfs_scope_list(db)

for entry in payload.get("scopes", []):
idx = entry["idx"]
if idx < len(scopes):
scope = scopes[idx]
for key, val in entry.get("attrs", {}).items():
if hasattr(scope, 'setAttribute'):
scope.setAttribute(key, val)

for entry in payload.get("coveritems", []):
scope_idx = entry["scope_idx"]
ci_idx = entry["ci_idx"]
if scope_idx < len(scopes):
scope = scopes[scope_idx]
try:
items = list(scope.coverItems(_COVER_ALL))
if ci_idx < len(items):
ci = items[ci_idx]
for key, val in entry.get("attrs", {}).items():
if hasattr(ci, 'setAttribute'):
ci.setAttribute(key, val)
except Exception:
pass

hist_nodes = {}
for kind in (HistoryNodeKind.TEST, HistoryNodeKind.MERGE):
try:
hist_nodes[kind.name] = list(db.historyNodes(kind))
except Exception:
pass
for entry in payload.get("history", []):
kind_name = entry.get("kind", "TEST")
idx = entry["idx"]
nodes = hist_nodes.get(kind_name, [])
if idx < len(nodes):
node = nodes[idx]
for key, val in entry.get("attrs", {}).items():
if hasattr(node, 'setAttribute'):
node.setAttribute(key, val)

for key, val in payload.get("global", {}).items():
if hasattr(db, 'setAttribute'):
db.setAttribute(key, val)

def apply(self, db, data: bytes) -> None:
"""Alias for deserialize (matches other readers' API)."""
self.deserialize(data, db)
33 changes: 18 additions & 15 deletions src/ucis/ncdb/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
PRESENCE_WEIGHT = 0x04 # has non-default weight (≠1)
PRESENCE_AT_LEAST = 0x08 # coveritem at_least override at scope level
PRESENCE_CVG_OPTS = 0x10 # has covergroup options
PRESENCE_GOAL = 0x20 # has non-default scope goal (≠-1)
PRESENCE_SOURCE_TYPE = 0x40 # has explicit SourceT enum

# ── counts.bin encoding modes ─────────────────────────────────────────────

Expand All @@ -65,21 +67,21 @@
# Used by reader to reconstruct coveritem objects without per-item storage.

COVER_TYPE_DEFAULTS: dict = {
CoverTypeT.TOGGLEBIN: (0, 0, 1),
CoverTypeT.STMTBIN: (0, 0, 1),
CoverTypeT.BRANCHBIN: (0, 0, 1),
CoverTypeT.CONDBIN: (0, 0, 1),
CoverTypeT.EXPRBIN: (0, 0, 1),
CoverTypeT.FSMBIN: (0, 0, 1),
CoverTypeT.CVGBIN: (0, 1, 1),
CoverTypeT.DEFAULTBIN: (0, 0, 1),
CoverTypeT.IGNOREBIN: (0, 0, 1),
CoverTypeT.ILLEGALBIN: (0, 0, 1),
CoverTypeT.BLOCKBIN: (0, 0, 1),
CoverTypeT.COVERBIN: (0, 0, 1),
CoverTypeT.ASSERTBIN: (0, 0, 1),
CoverTypeT.PASSBIN: (0, 0, 1),
CoverTypeT.FAILBIN: (0, 0, 1),
CoverTypeT.TOGGLEBIN: (0x01, 0, 1),
CoverTypeT.STMTBIN: (0x01, 0, 1),
CoverTypeT.BRANCHBIN: (0x01, 0, 1),
CoverTypeT.CONDBIN: (0x01, 0, 1),
CoverTypeT.EXPRBIN: (0x01, 0, 1),
CoverTypeT.FSMBIN: (0x01, 0, 1),
CoverTypeT.CVGBIN: (0x19, 1, 1),
CoverTypeT.DEFAULTBIN: (0x01, 0, 1),
CoverTypeT.IGNOREBIN: (0x01, 0, 1),
CoverTypeT.ILLEGALBIN: (0x01, 0, 1),
CoverTypeT.BLOCKBIN: (0x01, 0, 1),
CoverTypeT.COVERBIN: (0x01, 0, 1),
CoverTypeT.ASSERTBIN: (0x01, 0, 1),
CoverTypeT.PASSBIN: (0x01, 0, 1),
CoverTypeT.FAILBIN: (0x01, 0, 1),
}

# ── Scope-type → implicit child cover type mapping ────────────────────────
Expand All @@ -104,3 +106,4 @@
ScopeTypeT.COVER: CoverTypeT.COVERBIN,
ScopeTypeT.ASSERT: CoverTypeT.ASSERTBIN,
}
MEMBER_COVERITEM_FLAGS = "coveritem_flags.bin"
Loading