280 lines
7.8 KiB
Python
Executable File
280 lines
7.8 KiB
Python
Executable File
import re
|
||
import pprint
|
||
import re
|
||
from collections import defaultdict
|
||
|
||
|
||
# =====================================================================
|
||
# Unified DynamicObject (replaces DynamicMObject + DynamicCppObject)
|
||
# =====================================================================
|
||
class DynamicObject(dict):
|
||
"""
|
||
A unified dynamic object that:
|
||
- Behaves like a dict
|
||
- Supports attribute access
|
||
- Auto-creates nested fields on demand
|
||
"""
|
||
def __getattr__(self, name):
|
||
if name not in self:
|
||
self[name] = DynamicObject()
|
||
return self[name]
|
||
|
||
def __setattr__(self, name, value):
|
||
self[name] = value
|
||
|
||
def ensure_path(self, path_parts):
|
||
obj = self
|
||
for part in path_parts:
|
||
if part not in obj:
|
||
obj[part] = DynamicObject()
|
||
obj = obj[part]
|
||
return obj
|
||
|
||
def __repr__(self):
|
||
return dict.__repr__(self)
|
||
|
||
|
||
# =====================================================================
|
||
# MATLAB FRD REG PARSER
|
||
# =====================================================================
|
||
def parse_oFRDReg_from_mfile(filename):
|
||
oFRDReg = DynamicObject()
|
||
|
||
# Regex to capture: oFRDReg.<path> = <value>;
|
||
pattern = re.compile(r"oFRDReg\.([A-Za-z0-9_.]+)\s*=\s*([^;]+);")
|
||
|
||
with open(filename, "r") as f:
|
||
for line in f:
|
||
match = pattern.search(line)
|
||
if match:
|
||
full_path = match.group(1)
|
||
value_raw = match.group(2).strip()
|
||
|
||
# Convert numeric/string values when possible
|
||
try:
|
||
value = eval(value_raw)
|
||
except:
|
||
value = value_raw
|
||
|
||
# Split path: e.g. MSI.vcm_sel → ["MSI", "vcm_sel"]
|
||
parts = full_path.split(".")
|
||
|
||
parts = [ p.upper() for p in parts ]
|
||
attr_name = parts[-1]
|
||
parent_path = parts[:-1]
|
||
|
||
# Create nested path
|
||
parent = oFRDReg.ensure_path(parent_path)
|
||
setattr(parent, attr_name, value)
|
||
|
||
return oFRDReg
|
||
|
||
# =====================================================================
|
||
# Macro expansion helper
|
||
# =====================================================================
|
||
def expand_macro_pattern(pattern, **kwargs):
|
||
"""
|
||
Expand a macro pattern containing tokens like:
|
||
CH1##_##address##_OFFSET
|
||
into the final register string.
|
||
kwargs maps token names → values, e.g. address="CFG".
|
||
"""
|
||
|
||
s = pattern
|
||
|
||
# Replace tokens inside patterns like address##_XYZ
|
||
for key, value in kwargs.items():
|
||
s = s.replace(key, value)
|
||
|
||
# Resolve concatenation '##'
|
||
s = s.replace("##", "")
|
||
|
||
# Clean up double underscores
|
||
while "__" in s:
|
||
s = s.replace("__", "_")
|
||
|
||
return s
|
||
|
||
|
||
# =====================================================================
|
||
# C MACRO PARSER
|
||
# =====================================================================
|
||
|
||
def remove_cpp_comments(text):
|
||
"""
|
||
Remove /* block comments */ and // line comments.
|
||
"""
|
||
# Remove multiline comments
|
||
text = re.sub(r"/\*.*?\*/", "", text, flags=re.DOTALL)
|
||
|
||
# Removedefines
|
||
text = re.sub(r"#define.*", "", text)
|
||
|
||
# Remove single-line comments
|
||
text = re.sub(r"//.*", "", text)
|
||
|
||
return text
|
||
|
||
def parse_c_macros(filename):
|
||
registers = DynamicObject()
|
||
|
||
# Macro → register expansion patterns
|
||
macro_definitions = {
|
||
# 3-argument write (slow/fast)
|
||
"SLOW_WRITE_FIELD": (
|
||
"CH1_##address##",
|
||
"CH1_##address##_##field##",
|
||
),
|
||
"FAST_WRITE_FIELD": (
|
||
"CH0_##address##",
|
||
"CH0_##address##_##field##",
|
||
),
|
||
|
||
# Read field, 2 arguments
|
||
"SLOW_READ_FIELD": (
|
||
"CH1_##address##",
|
||
"CH1_##address##_##field##",
|
||
),
|
||
"FAST_READ_FIELD": (
|
||
"CH0_##address##",
|
||
"CH0_##address##_##field##",
|
||
),
|
||
|
||
# Register read/write (1 or 2 arguments)
|
||
"SLOW_READ_REGISTER": (
|
||
"CH1_##address##",
|
||
),
|
||
"FAST_READ_REGISTER": (
|
||
"CH0_##address##",
|
||
),
|
||
"SLOW_WRITE_REGISTER": (
|
||
"CH1_##address##",
|
||
),
|
||
"FAST_WRITE_REGISTER": (
|
||
"CH0_##address##",
|
||
),
|
||
"FAST_WRITE_INHIBIT": (
|
||
"CH0##_##address1##_CH0_##address2##",
|
||
),
|
||
"SLOW_WRITE_INHIBIT": (
|
||
"CH1##_##address1##_CH0_##address2##",
|
||
)
|
||
}
|
||
|
||
# Generic regex for ALL macro calls (1–4 args)
|
||
|
||
call_regex = re.compile(
|
||
r"([A-Z_]+)\s*\(\s*([A-Za-z0-9_]+)" # FUN and arg1: address1
|
||
r"(?:\s*,\s*([A-Za-z0-9_]+))?" # arg2: field, address2
|
||
r"(?:\s*,\s*([A-Za-z0-9_+\-*/() ]+))?" # arg3: field, data
|
||
r"(?:\s*,\s*([A-Za-z0-9_+\-*/() ]+))?" # arg4: data (expression allowed)
|
||
r"\s*\)"
|
||
)
|
||
|
||
with open(filename, "r") as f:
|
||
text = f.read()
|
||
|
||
text = remove_cpp_comments(text)
|
||
|
||
for match in call_regex.finditer(text):
|
||
macro, a1, a2, a3, a4 = match.groups()
|
||
|
||
text = text.replace(match.group(0), "") # remove after processing
|
||
|
||
if macro not in macro_definitions or "READ" in macro:
|
||
continue
|
||
|
||
patterns = macro_definitions[macro]
|
||
|
||
# Build the macro variable map
|
||
kwargs = {}
|
||
if a4 == None:
|
||
if a1: kwargs["address"] = a1
|
||
if a2: kwargs["field"] = a2
|
||
if a3: kwargs["data"] = a3
|
||
else:
|
||
if a1: kwargs["address1"] = a1
|
||
if a2: kwargs["address2"] = a2
|
||
if a3: kwargs["field"] = a3
|
||
if a4: kwargs["data"] = a4
|
||
|
||
# Expand patterns into real register names
|
||
expanded = [expand_macro_pattern(p, **kwargs) for p in patterns]
|
||
|
||
# Store results
|
||
if a2 and a3: # WRITE_FIELD
|
||
registers[expanded[0].upper()] = {a2.upper(): a3}
|
||
else: # REGISTER read/write
|
||
registers[expanded[0].upper()] = a3
|
||
|
||
return registers
|
||
|
||
def upper_keys_recursive(d):
|
||
new_dict = {}
|
||
for k, v in d.items():
|
||
key_up = str(k).upper()
|
||
|
||
if isinstance(v, dict):
|
||
new_dict[key_up] = upper_keys_recursive(v)
|
||
else:
|
||
new_dict[key_up] = v
|
||
|
||
return new_dict
|
||
|
||
|
||
def diff_dicts(d1, d2, path="",dictnames = ["M","CPP"]):
|
||
"""
|
||
Recursively compare two dictionaries and print differences.
|
||
Shows:
|
||
- Keys only in d1
|
||
- Keys only in d2
|
||
- Keys in both where values differ
|
||
"""
|
||
|
||
|
||
|
||
# Keys present in both dictionaries
|
||
common_keys = d1.keys() & d2.keys()
|
||
|
||
# Keys only in first dict
|
||
only_d1 = d1.keys() - d2.keys()
|
||
for key in sorted(only_d1):
|
||
print(f"{path}{key}: only in {dictnames[0]}")
|
||
|
||
# Keys only in second dict
|
||
only_d2 = d2.keys() - d1.keys()
|
||
for key in sorted(only_d2):
|
||
print(f"{path}{key}: only in {dictnames[1]}")
|
||
|
||
# Compare common keys
|
||
for key in sorted(common_keys):
|
||
v1 = d1[key]
|
||
v2 = d2[key]
|
||
new_path = f"{path}{key}."
|
||
|
||
# If both values are dictionaries → recurse
|
||
if isinstance(v1, dict) and isinstance(v2, dict):
|
||
diff_dicts(v1, v2, new_path)
|
||
continue
|
||
|
||
# If values differ → print
|
||
if v1 != v2:
|
||
print(f"{path}{key}:")
|
||
print(f" {dictnames[0]} = {v1}")
|
||
print(f" {dictnames[1]} = {v2}")
|
||
# =====================================================================
|
||
# Example usage
|
||
# =====================================================================
|
||
|
||
|
||
# Example usage:
|
||
if __name__ == "__main__":
|
||
obj = parse_oFRDReg_from_mfile("startTau_generic_Generated.m")
|
||
pprint.pprint(obj)
|
||
|
||
regs = parse_c_macros("/home/lucas/git/mems_stable/trixel3_software/tic/source/trixel3_tic_mems_model_lbd001rrxab11.cpp")
|
||
|
||
pprint.pprint(regs)
|
||
|
||
diff_dicts(regs,obj)
|
||
print("Fin") |