mirror of
				https://github.com/YosysHQ/yosys
				synced 2025-10-31 11:42:30 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			432 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			432 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #
 | |
| # 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}]"
 | |
| 
 | |
|     def __eq__(self, other):
 | |
|         return self.sort_key == other.sort_key
 | |
| 
 | |
|     def __hash__(self):
 | |
|         return hash(self.sort_key)
 | |
| 
 | |
|     def __lt__(self, other):
 | |
|         return self.sort_key < other.sort_key
 | |
| 
 | |
| 
 | |
| def coalesce_signals(signals, bits=None):
 | |
|     if bits is None:
 | |
|         bits = {}
 | |
|     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
 | |
| 
 | |
|     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
 | |
| 
 | |
| 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, skip_x=False):
 | |
|         if not self.header_written:
 | |
|             self.write_header()
 | |
| 
 | |
|         packed = values.pack(self.sigmap)
 | |
|         if skip_x:
 | |
|             packed = packed.replace('x', '?')
 | |
|         self.out.value({"bits": packed})
 | |
| 
 | |
|         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"]]
 | |
| 
 | |
|     def skip_x(self):
 | |
|         self.bits = [step.replace('x', '?') for step in self.bits]
 | |
| 
 | |
|     def init_step(self):
 | |
|         return self.step(0)
 | |
|     
 | |
|     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])
 | |
|     
 | |
|     def first_step(self):
 | |
|         values = WitnessValues()
 | |
|         # 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():])
 | |
|         return values
 | |
| 
 | |
|     def step(self, t):
 | |
|         values = WitnessValues()
 | |
|         values.unpack(self.sigmap, self.bits[t])
 | |
|         return values
 | |
| 
 | |
|     def steps(self, start=0):
 | |
|         for i in range(start, len(self.bits)):
 | |
|             yield i, self.step(i)
 | |
| 
 | |
|     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)
 | |
| 
 | |
|     def __len__(self):
 | |
|         return len(self.bits)
 |