2022-08-02 09:49:36 -05:00
|
|
|
#
|
|
|
|
# yosys -- Yosys Open SYnthesis Suite
|
|
|
|
#
|
|
|
|
# Copyright (C) 2022 Jannis Harder <jix@yosyshq.com> <me@jix.one>
|
|
|
|
#
|
|
|
|
# Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
# purpose with or without fee is hereby granted, provided that the above
|
|
|
|
# copyright notice and this permission notice appear in all copies.
|
|
|
|
#
|
|
|
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
#
|
|
|
|
|
|
|
|
import json, re
|
|
|
|
|
|
|
|
from functools import total_ordering
|
|
|
|
|
|
|
|
|
|
|
|
class PrettyJson:
|
|
|
|
def __init__(self, f):
|
|
|
|
self.f = f
|
|
|
|
self.indent = 0
|
|
|
|
self.state = ["value"]
|
|
|
|
|
|
|
|
def line(self):
|
|
|
|
indent = len(self.state) - bool(self.state and self.state[-1] == "value")
|
|
|
|
print("\n", end=" " * (2 * indent), file=self.f)
|
|
|
|
|
|
|
|
def raw(self, str):
|
|
|
|
print(end=str, file=self.f)
|
|
|
|
|
|
|
|
def begin_object(self):
|
|
|
|
self.begin_value()
|
|
|
|
self.raw("{")
|
|
|
|
self.state.append("object_first")
|
|
|
|
|
|
|
|
def begin_array(self):
|
|
|
|
self.begin_value()
|
|
|
|
self.raw("[")
|
|
|
|
self.state.append("array_first")
|
|
|
|
|
|
|
|
def end_object(self):
|
|
|
|
state = self.state.pop()
|
|
|
|
if state == "object":
|
|
|
|
self.line()
|
|
|
|
else:
|
|
|
|
assert state == "object_first"
|
|
|
|
self.raw("}")
|
|
|
|
self.end_value()
|
|
|
|
|
|
|
|
def end_array(self):
|
|
|
|
state = self.state.pop()
|
|
|
|
if state == "array":
|
|
|
|
self.line()
|
|
|
|
else:
|
|
|
|
assert state == "array_first"
|
|
|
|
self.raw("]")
|
|
|
|
self.end_value()
|
|
|
|
|
|
|
|
def name(self, name):
|
|
|
|
if self.state[-1] == "object_first":
|
|
|
|
self.state[-1] = "object"
|
|
|
|
else:
|
|
|
|
self.raw(",")
|
|
|
|
self.line()
|
|
|
|
json.dump(str(name), self.f)
|
|
|
|
self.raw(": ")
|
|
|
|
self.state.append("value")
|
|
|
|
|
|
|
|
def begin_value(self):
|
|
|
|
if self.state[-1] == "array_first":
|
|
|
|
self.line()
|
|
|
|
self.state[-1] = "array"
|
|
|
|
elif self.state[-1] == "array":
|
|
|
|
self.raw(",")
|
|
|
|
self.line()
|
|
|
|
else:
|
|
|
|
assert self.state.pop() == "value"
|
|
|
|
|
|
|
|
def end_value(self):
|
|
|
|
if not self.state:
|
|
|
|
print(file=self.f, flush=True)
|
|
|
|
|
|
|
|
def value(self, value):
|
|
|
|
self.begin_value()
|
|
|
|
json.dump(value, self.f)
|
|
|
|
self.end_value()
|
|
|
|
|
|
|
|
def entry(self, name, value):
|
|
|
|
self.name(name)
|
|
|
|
self.value(value)
|
|
|
|
|
|
|
|
def object(self, entries=None):
|
|
|
|
if isinstance(entries, dict):
|
|
|
|
entries = dict.items()
|
|
|
|
self.begin_object()
|
|
|
|
for name, value in entries:
|
|
|
|
self.entry(name, value)
|
|
|
|
self.end_object()
|
|
|
|
|
|
|
|
def array(self, values=None):
|
|
|
|
self.begin_array()
|
|
|
|
for value in values:
|
|
|
|
self.value(value)
|
|
|
|
self.end_array()
|
|
|
|
|
|
|
|
|
|
|
|
addr_re = re.compile(r'\\\[[0-9]+\]$')
|
|
|
|
public_name_re = re.compile(r"\\([a-zA-Z_][a-zA-Z0-9_]*(\[[0-9]+\])?|\[[0-9]+\])$")
|
|
|
|
|
|
|
|
def pretty_name(id):
|
|
|
|
if public_name_re.match(id):
|
|
|
|
return id.lstrip("\\")
|
|
|
|
else:
|
|
|
|
return id
|
|
|
|
|
|
|
|
def pretty_path(path):
|
|
|
|
out = ""
|
|
|
|
for name in path:
|
|
|
|
name = pretty_name(name)
|
|
|
|
if name.startswith("["):
|
|
|
|
out += name
|
|
|
|
continue
|
|
|
|
if out:
|
|
|
|
out += "."
|
|
|
|
if name.startswith("\\") or name.startswith("$"):
|
|
|
|
out += name + " "
|
|
|
|
else:
|
|
|
|
out += name
|
|
|
|
|
|
|
|
return out
|
|
|
|
|
|
|
|
@total_ordering
|
|
|
|
class WitnessSig:
|
|
|
|
def __init__(self, path, offset, width=1, init_only=False):
|
|
|
|
path = tuple(path)
|
|
|
|
self.path, self.width, self.offset, self.init_only = path, width, offset, init_only
|
|
|
|
|
|
|
|
self.memory_path = None
|
|
|
|
self.memory_addr = None
|
|
|
|
|
|
|
|
sort_path = path
|
|
|
|
sort_id = -1
|
|
|
|
if path and addr_re.match(path[-1]):
|
|
|
|
self.memory_path = sort_path = path[:-1]
|
|
|
|
self.memory_addr = sort_id = int(path[-1][2:-1])
|
|
|
|
|
|
|
|
self.sort_key = (init_only, sort_path, sort_id, offset, width)
|
|
|
|
|
|
|
|
def bits(self):
|
|
|
|
return ((self.path, i) for i in range(self.offset, self.offset + self.width))
|
|
|
|
|
|
|
|
def rev_bits(self):
|
|
|
|
return ((self.path, i) for i in reversed(range(self.offset, self.offset + self.width)))
|
|
|
|
|
|
|
|
def pretty(self):
|
|
|
|
if self.width > 1:
|
|
|
|
last_offset = self.offset + self.width - 1
|
|
|
|
return f"{pretty_path(self.path)}[{last_offset}:{self.offset}]"
|
|
|
|
else:
|
|
|
|
return f"{pretty_path(self.path)}[{self.offset}]"
|
|
|
|
|
2023-05-21 17:13:17 -05:00
|
|
|
def __eq__(self, other):
|
|
|
|
return self.sort_key == other.sort_key
|
2022-08-02 09:49:36 -05:00
|
|
|
|
|
|
|
def __hash__(self):
|
|
|
|
return hash(self.sort_key)
|
|
|
|
|
|
|
|
def __lt__(self, other):
|
|
|
|
return self.sort_key < other.sort_key
|
|
|
|
|
|
|
|
|
2023-01-03 07:45:41 -06:00
|
|
|
def coalesce_signals(signals, bits=None):
|
|
|
|
if bits is None:
|
|
|
|
bits = {}
|
2022-08-02 09:49:36 -05:00
|
|
|
for sig in signals:
|
|
|
|
for bit in sig.bits():
|
|
|
|
if sig.init_only:
|
|
|
|
bits.setdefault(bit, False)
|
|
|
|
else:
|
|
|
|
bits[bit] = True
|
|
|
|
|
|
|
|
active = None
|
|
|
|
|
|
|
|
out = []
|
|
|
|
|
|
|
|
for bit, not_init in sorted(bits.items()):
|
|
|
|
if active:
|
|
|
|
if active[0] == bit[0] and active[2] == bit[1] and active[3] == not_init:
|
|
|
|
active[2] += 1
|
|
|
|
else:
|
|
|
|
out.append(WitnessSig(active[0], active[1], active[2] - active[1], not active[3]))
|
|
|
|
active = None
|
|
|
|
|
|
|
|
if active is None:
|
|
|
|
active = [bit[0], bit[1], bit[1] + 1, not_init]
|
|
|
|
|
|
|
|
if active:
|
|
|
|
out.append(WitnessSig(active[0], active[1], active[2] - active[1], not active[3]))
|
|
|
|
|
|
|
|
return sorted(out)
|
|
|
|
|
|
|
|
|
|
|
|
class WitnessSigMap:
|
|
|
|
def __init__(self, signals=[]):
|
|
|
|
self.signals = []
|
|
|
|
|
|
|
|
self.id_to_bit = []
|
|
|
|
self.bit_to_id = {}
|
|
|
|
self.bit_to_sig = {}
|
|
|
|
|
|
|
|
for sig in signals:
|
|
|
|
self.add_signal(sig)
|
|
|
|
|
|
|
|
def add_signal(self, sig):
|
|
|
|
self.signals.append(sig)
|
|
|
|
for bit in sig.bits():
|
|
|
|
self.add_bit(bit)
|
|
|
|
self.bit_to_sig[bit] = sig
|
|
|
|
|
|
|
|
def add_bit(self, bit, id=None):
|
|
|
|
if id is None:
|
|
|
|
id = len(self.id_to_bit)
|
|
|
|
self.id_to_bit.append(bit)
|
|
|
|
else:
|
|
|
|
if len(self.id_to_bit) <= id:
|
|
|
|
self.id_to_bit += [None] * (id - len(self.id_to_bit) + 1)
|
|
|
|
self.id_to_bit[id] = bit
|
|
|
|
self.bit_to_id[bit] = id
|
|
|
|
|
|
|
|
|
|
|
|
class WitnessValues:
|
|
|
|
def __init__(self):
|
|
|
|
self.values = {}
|
|
|
|
|
|
|
|
def __setitem__(self, key, value):
|
|
|
|
if isinstance(key, tuple) and len(key) == 2:
|
|
|
|
if value != "?":
|
|
|
|
assert isinstance(value, str)
|
|
|
|
assert len(value) == 1
|
|
|
|
self.values[key] = value
|
|
|
|
else:
|
|
|
|
assert isinstance(key, WitnessSig)
|
|
|
|
assert key.width == len(value)
|
|
|
|
if isinstance(value, str):
|
|
|
|
value = reversed(value)
|
|
|
|
for bit, bit_value in zip(key.bits(), value):
|
|
|
|
if bit_value != "?":
|
|
|
|
self.values[bit] = bit_value
|
|
|
|
|
|
|
|
def __getitem__(self, key):
|
|
|
|
if isinstance(key, tuple) and len(key) == 2:
|
|
|
|
return self.values.get(key, "?")
|
|
|
|
else:
|
|
|
|
assert isinstance(key, WitnessSig)
|
|
|
|
return "".join([self.values.get(bit, "?") for bit in key.rev_bits()])
|
|
|
|
|
|
|
|
def pack_present(self, sigmap):
|
|
|
|
missing = []
|
|
|
|
|
|
|
|
max_id = max((sigmap.bit_to_id.get(bit, -1) for bit in self.values), default=-1)
|
|
|
|
|
|
|
|
vector = ["?"] * (max_id + 1)
|
|
|
|
for bit, bit_value in self.values.items():
|
|
|
|
id = sigmap.bit_to_id.get(bit, - 1)
|
|
|
|
if id < 0:
|
|
|
|
missing.append(bit)
|
|
|
|
else:
|
|
|
|
vector[max_id - sigmap.bit_to_id[bit]] = bit_value
|
|
|
|
|
|
|
|
return "".join(vector), missing
|
|
|
|
|
|
|
|
def pack(self, sigmap):
|
|
|
|
packed, missing = self.pack_present(sigmap)
|
|
|
|
if missing:
|
|
|
|
raise RuntimeError(f"Cannot pack bits {missing!r}")
|
|
|
|
return packed
|
|
|
|
|
|
|
|
def unpack(self, sigmap, bits):
|
|
|
|
for i, bit_value in enumerate(reversed(bits)):
|
|
|
|
if bit_value != "?":
|
|
|
|
self.values[sigmap.id_to_bit[i]] = bit_value
|
|
|
|
|
|
|
|
def present_signals(self, sigmap):
|
|
|
|
signals = set(sigmap.bit_to_sig.get(bit) for bit in self.values)
|
|
|
|
missing_signals = None in signals
|
|
|
|
if missing_signals:
|
|
|
|
signals.discard(None)
|
|
|
|
|
|
|
|
return sorted(signals), missing_signals
|
|
|
|
|
2023-05-21 17:13:17 -05:00
|
|
|
def __add__(self, other: "WitnessValues"):
|
|
|
|
new = WitnessValues()
|
|
|
|
new += self
|
|
|
|
new += other
|
|
|
|
return new
|
|
|
|
|
|
|
|
def __iadd__(self, other: "WitnessValues"):
|
|
|
|
for key, value in other.values.items():
|
|
|
|
self.values.setdefault(key, value)
|
|
|
|
return self
|
2022-08-02 09:49:36 -05:00
|
|
|
|
|
|
|
class WriteWitness:
|
|
|
|
def __init__(self, f, generator):
|
|
|
|
self.out = PrettyJson(f)
|
|
|
|
self.t = 0
|
|
|
|
self.header_written = False
|
|
|
|
self.clocks = []
|
|
|
|
self.signals = []
|
|
|
|
|
|
|
|
self.out.begin_object()
|
|
|
|
self.out.entry("format", "Yosys Witness Trace")
|
|
|
|
self.out.entry("generator", generator)
|
|
|
|
|
|
|
|
def add_clock(self, path, offset, edge):
|
|
|
|
assert not self.header_written
|
|
|
|
self.clocks.append({
|
|
|
|
"path": path,
|
|
|
|
"edge": edge,
|
|
|
|
"offset": offset,
|
|
|
|
})
|
|
|
|
|
|
|
|
def add_sig(self, path, offset, width=1, init_only=False):
|
|
|
|
assert not self.header_written
|
|
|
|
sig = WitnessSig(path, offset, width, init_only)
|
|
|
|
self.signals.append(sig)
|
|
|
|
return sig
|
|
|
|
|
|
|
|
def write_header(self):
|
|
|
|
assert not self.header_written
|
|
|
|
self.header_written = True
|
|
|
|
self.out.name("clocks")
|
|
|
|
self.out.array(self.clocks)
|
|
|
|
|
|
|
|
self.signals = coalesce_signals(self.signals)
|
|
|
|
self.sigmap = WitnessSigMap(self.signals)
|
|
|
|
|
|
|
|
self.out.name("signals")
|
|
|
|
self.out.array({
|
|
|
|
"path": sig.path,
|
|
|
|
"width": sig.width,
|
|
|
|
"offset": sig.offset,
|
|
|
|
"init_only": sig.init_only,
|
|
|
|
} for sig in self.signals)
|
|
|
|
|
|
|
|
self.out.name("steps")
|
|
|
|
self.out.begin_array()
|
|
|
|
|
|
|
|
def step(self, values):
|
|
|
|
if not self.header_written:
|
|
|
|
self.write_header()
|
|
|
|
|
|
|
|
self.out.value({"bits": values.pack(self.sigmap)})
|
|
|
|
|
|
|
|
self.t += 1
|
|
|
|
|
|
|
|
def end_trace(self):
|
|
|
|
if not self.header_written:
|
|
|
|
self.write_header()
|
|
|
|
self.out.end_array()
|
|
|
|
self.out.end_object()
|
|
|
|
|
|
|
|
|
|
|
|
class ReadWitness:
|
|
|
|
def __init__(self, f):
|
|
|
|
data = json.load(f)
|
|
|
|
if not isinstance(data, dict):
|
|
|
|
data = {}
|
|
|
|
|
|
|
|
data_format = data.get("format", "Unknown Format")
|
|
|
|
|
|
|
|
if data_format != "Yosys Witness Trace":
|
|
|
|
raise ValueError(f"unsupported format {data_format!r}")
|
|
|
|
|
|
|
|
self.clocks = data["clocks"]
|
|
|
|
for clock in self.clocks:
|
|
|
|
clock["path"] = tuple(clock["path"])
|
|
|
|
|
|
|
|
self.signals = [
|
|
|
|
WitnessSig(sig["path"], sig["offset"], sig["width"], sig["init_only"])
|
|
|
|
for sig in data["signals"]
|
|
|
|
]
|
|
|
|
|
|
|
|
self.sigmap = WitnessSigMap(self.signals)
|
|
|
|
|
|
|
|
self.bits = [step["bits"] for step in data["steps"]]
|
|
|
|
|
2023-05-21 17:13:17 -05:00
|
|
|
def init_step(self):
|
|
|
|
return self.step(0)
|
|
|
|
|
2023-05-21 18:44:19 -05:00
|
|
|
def non_init_bits(self):
|
|
|
|
if len(self) > 1:
|
|
|
|
return len(self.bits[1])
|
|
|
|
else:
|
|
|
|
return sum([sig.width for sig in self.signals if not sig.init_only])
|
|
|
|
|
2023-05-21 17:13:17 -05:00
|
|
|
def first_step(self):
|
|
|
|
values = WitnessValues()
|
2023-05-21 18:44:19 -05:00
|
|
|
# may have issues when non_init_bits is 0
|
|
|
|
values.unpack(WitnessSigMap([sig for sig in self.signals if not sig.init_only]), self.bits[0][-self.non_init_bits():])
|
2023-05-21 17:13:17 -05:00
|
|
|
return values
|
|
|
|
|
2022-08-02 09:49:36 -05:00
|
|
|
def step(self, t):
|
|
|
|
values = WitnessValues()
|
|
|
|
values.unpack(self.sigmap, self.bits[t])
|
|
|
|
return values
|
|
|
|
|
2023-05-21 17:13:17 -05:00
|
|
|
def steps(self, start=0):
|
|
|
|
for i in range(start, len(self.bits)):
|
2022-08-02 09:49:36 -05:00
|
|
|
yield i, self.step(i)
|
|
|
|
|
2023-05-21 18:44:19 -05:00
|
|
|
def append_steps(self, t):
|
|
|
|
if not t:
|
|
|
|
pass
|
|
|
|
elif t < 0:
|
|
|
|
self.bits = self.bits[:t]
|
|
|
|
else:
|
|
|
|
self.bits.extend(["0"*self.non_init_bits()]*t)
|
|
|
|
|
2022-08-02 09:49:36 -05:00
|
|
|
def __len__(self):
|
|
|
|
return len(self.bits)
|