mirror of
				https://github.com/YosysHQ/yosys
				synced 2025-11-04 05:19:11 +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)
 |