Compare commits

..

No commits in common. 'devel' and 'main' have entirely different histories.
devel ... main

  1. 2
      LICENSE.txt
  2. 4
      docs/conf.py
  3. 1720
      examples/Introduction.ipynb
  4. 4
      pyproject.toml
  5. 2
      src/kyupy/__init__.py
  6. 42
      src/kyupy/bench.py
  7. 64
      src/kyupy/circuit.py
  8. 5
      src/kyupy/logic.py
  9. 525
      src/kyupy/logic_sim.py
  10. 12
      src/kyupy/sim.py
  11. 40
      src/kyupy/techlib.py
  12. 13
      src/kyupy/verilog.py
  13. 2
      tests/Makefile
  14. 91
      tests/all_kyupy_simprims.minimal.v
  15. 53
      tests/all_kyupy_simprims.v
  16. 34
      tests/kyupy_simprims.genlib
  17. 6
      tests/map_to_minimal.abc
  18. 5
      tests/minimal.genlib
  19. 9
      tests/test_circuit.py
  20. 147
      tests/test_logic_sim.py
  21. 6
      tests/test_wave_sim.py

2
LICENSE.txt

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020-2025 Stefan Holst
Copyright (c) 2020-2023 Stefan Holst
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

4
docs/conf.py

@ -20,11 +20,11 @@ sys.path.insert(0, os.path.abspath('../src')) @@ -20,11 +20,11 @@ sys.path.insert(0, os.path.abspath('../src'))
# -- Project information -----------------------------------------------------
project = 'KyuPy'
copyright = '2020-2025, Stefan Holst'
copyright = '2020-2023, Stefan Holst'
author = 'Stefan Holst'
# The full version, including alpha/beta/rc tags
release = '0.0.6'
release = '0.0.5'
# -- General configuration ---------------------------------------------------

1720
examples/Introduction.ipynb

File diff suppressed because it is too large Load Diff

4
pyproject.toml

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
[project]
name = "kyupy"
version = "0.0.6"
version = "0.0.5"
authors = [
{ name="Stefan Holst", email="mail@s-holst.de" },
]
@ -9,7 +9,7 @@ readme = "README.rst" @@ -9,7 +9,7 @@ readme = "README.rst"
requires_python = ">=3.8"
dependencies = [
"numpy>=1.17.0",
"lark>=1.3.0",
"lark-parser>=0.8.0",
]
classifiers = [
"Development Status :: 3 - Alpha",

2
src/kyupy/__init__.py

@ -293,6 +293,8 @@ if importlib.util.find_spec('numba') is not None: @@ -293,6 +293,8 @@ if importlib.util.find_spec('numba') is not None:
try:
list(numba.cuda.gpus)
from numba import cuda
from numba.core import config
config.CUDA_LOW_OCCUPANCY_WARNINGS = False
except CudaSupportError:
log.warn('Cuda unavailable. Falling back to pure Python.')
cuda = MockCuda()

42
src/kyupy/bench.py

@ -10,11 +10,7 @@ Besides loading these benchmarks, this module is also useful for easily construc @@ -10,11 +10,7 @@ Besides loading these benchmarks, this module is also useful for easily construc
from lark import Lark, Transformer
from .circuit import Circuit, Node, Line
from . import readtext, batchrange
def treeify(l, max_degree=4):
if len(l) <= max_degree: return l
return treeify([l[bo:bo+bs] for bo, bs in batchrange(len(l), max_degree)])
from . import readtext
class BenchTransformer(Transformer):
@ -25,39 +21,13 @@ class BenchTransformer(Transformer): @@ -25,39 +21,13 @@ class BenchTransformer(Transformer):
def start(self, _): return self.c
def parameters(self, args): return [self.c.get_or_add_fork(str(name)) for name in args if name is not None]
def parameters(self, args): return [self.c.get_or_add_fork(str(name)) for name in args]
def interface(self, args): self.c.io_nodes.extend(args[0])
def _cell_tree_inner(self, name, kind, inner_kind, drivers):
cell = Node(self.c, name, f'{kind}{len(drivers)}')
fork = self.c.get_or_add_fork(name)
Line(self.c, cell, fork)
for i, d in enumerate(drivers):
while isinstance(d, list) and len(d) == 1: d = d[0]
if isinstance(d, list):
d = self._cell_tree_inner(f'{name}~{i}', inner_kind, inner_kind, d)
Line(self.c, d, cell)
return fork
def cell_tree(self, name, kind, drivers):
root_kind = kind.upper()
inner_kind = root_kind
if root_kind == 'NAND': inner_kind = 'AND'
if root_kind == 'NOR': inner_kind = 'OR'
if root_kind == 'XNOR': inner_kind = 'XOR'
return self._cell_tree_inner(name, root_kind, inner_kind, treeify(drivers))
def assignment(self, args):
name, kind, drivers = args
if kind.upper() in ('AND', 'NAND', 'OR', 'NOR', 'XOR', 'XNOR'):
self.cell_tree(name, kind, drivers)
return
if kind.upper().startswith('BUF'):
kind = 'BUF1'
elif kind.upper().startswith('INV') or kind.upper().startswith('NOT'):
kind = 'INV1'
cell = Node(self.c, str(name), str(kind))
name, cell_type, drivers = args
cell = Node(self.c, str(name), str(cell_type))
Line(self.c, cell, self.c.get_or_add_fork(str(name)))
for d in drivers: Line(self.c, d, cell)
@ -74,14 +44,14 @@ GRAMMAR = r""" @@ -74,14 +44,14 @@ GRAMMAR = r"""
"""
def parse(text, name=None) -> Circuit:
def parse(text, name=None):
"""Parses the given ``text`` as ISCAS89 bench code.
:param text: A string with bench code.
:param name: The name of the circuit. Circuit names are not included in bench descriptions.
:return: A :class:`Circuit` object.
"""
return Lark(GRAMMAR, parser="lalr", transformer=BenchTransformer(name)).parse(text) # type: ignore
return Lark(GRAMMAR, parser="lalr", transformer=BenchTransformer(name)).parse(text)
def load(file, name=None):

64
src/kyupy/circuit.py

@ -14,12 +14,12 @@ from __future__ import annotations @@ -14,12 +14,12 @@ from __future__ import annotations
from collections import deque, defaultdict
import re
from typing import Union, Any
from typing import Union
import numpy as np
class GrowingList[T](list[T]):
class GrowingList(list):
def __setitem__(self, index, value):
if value is None: self.has_nones = True
if index == len(self): return super().append(value)
@ -28,13 +28,9 @@ class GrowingList[T](list[T]): @@ -28,13 +28,9 @@ class GrowingList[T](list[T]):
self.has_nones = True
super().__setitem__(index, value)
# Override __getitem__ to return None when reading beyond the list
# instead of throwing an exception. Type checker complains about the None return
# type, though. Probably not needed anyways.
#def __getitem__(self, index) -> list[T] | T | None:
# if isinstance(index, slice): return super().__getitem__(index)
# return super().__getitem__(index) if index < len(self) else None
def __getitem__(self, index):
if isinstance(index, slice): return super().__getitem__(index)
return super().__getitem__(index) if index < len(self) else None
@property
def free_idx(self):
@ -138,10 +134,10 @@ class Node: @@ -138,10 +134,10 @@ class Node:
This is ok, because (name, kind) is unique within a circuit.
"""
return self.name == other.name and self.kind == other.kind and self.circuit == other.circuit
return self.name == other.name and self.kind == other.kind
def __hash__(self):
return hash((self.name, self.kind, id(self.circuit)))
return hash((self.name, self.kind))
class Line:
@ -159,7 +155,7 @@ class Line: @@ -159,7 +155,7 @@ class Line:
Use the explicit case only if connections to specific pins are required.
It may overwrite any previous line references in the connection list of the nodes.
"""
def __init__(self, circuit: Circuit, driver: Node | tuple[Node, None|int], reader: Node | tuple[Node, None|int]):
def __init__(self, circuit: Circuit, driver: Union[Node, tuple[Node, int]], reader: Union[Node, tuple[Node, int]]):
self.circuit = circuit
"""The :class:`Circuit` object the line is part of.
"""
@ -172,20 +168,20 @@ class Line: @@ -172,20 +168,20 @@ class Line:
accessing it by :code:`my_data[l.index]` or simply by :code:`my_data[l]`.
"""
if not isinstance(driver, tuple): driver = (driver, driver.outs.free_idx)
self.driver: Node = driver[0]
self.driver = driver[0]
"""The :class:`Node` object that drives this line.
"""
self.driver_pin = driver[1] if driver[1] is not None else self.driver.outs.free_idx
self.driver_pin = driver[1]
"""The output pin position of the driver node this line is connected to.
This is the position in the list :py:attr:`Node.outs` of the driving node this line referenced from:
:code:`self.driver.outs[self.driver_pin] == self`.
"""
if not isinstance(reader, tuple): reader = (reader, reader.ins.free_idx)
self.reader: Node = reader[0]
self.reader = reader[0]
"""The :class:`Node` object that reads this line.
"""
self.reader_pin = reader[1] if reader[1] is not None else self.reader.ins.free_idx
self.reader_pin = reader[1]
"""The input pin position of the reader node this line is connected to.
This is the position in the list :py:attr:`Node.ins` of the reader node this line referenced from:
@ -207,6 +203,8 @@ class Line: @@ -207,6 +203,8 @@ class Line:
for i, l in enumerate(self.driver.outs): l.driver_pin = i
if self.reader is not None: self.reader.ins[self.reader_pin] = None
if self.circuit is not None: del self.circuit.lines[self.index]
self.driver = None
self.reader = None
self.circuit = None
def __index__(self):
@ -311,7 +309,7 @@ class Circuit: @@ -311,7 +309,7 @@ class Circuit:
"""
return self._locs(prefix, self.s_nodes)
def _locs(self, prefix, nodes:list[Node]) -> Node|list[Any]: # can return list[list[...]]
def _locs(self, prefix, nodes):
d_top = dict()
for i, n in enumerate(nodes):
if m := re.match(fr'({re.escape(prefix)}.*?)((?:[\d_\[\]])*$)', n.name):
@ -326,7 +324,7 @@ class Circuit: @@ -326,7 +324,7 @@ class Circuit:
def sorted_values(d): return [sorted_values(v) for k, v in sorted(d.items())] if isinstance(d, dict) else d
l = sorted_values(d_top)
while isinstance(l, list) and len(l) == 1: l = l[0]
return l #None if isinstance(l, list) and len(l) == 0 else l
return None if isinstance(l, list) and len(l) == 0 else l
@property
def stats(self):
@ -541,6 +539,9 @@ class Circuit: @@ -541,6 +539,9 @@ class Circuit:
for n in state['io_nodes']:
self.io_nodes.append(self.nodes[n])
def __eq__(self, other):
return self.nodes == other.nodes and self.lines == other.lines and self.io_nodes == other.io_nodes
def __repr__(self):
return f'{{name: "{self.name}", cells: {len(self.cells)}, forks: {len(self.forks)}, lines: {len(self.lines)}, io_nodes: {len(self.io_nodes)}}}'
@ -650,9 +651,9 @@ class Circuit: @@ -650,9 +651,9 @@ class Circuit:
region.append(n)
yield stem, region
def dot(self, format='svg', graph_attr={}, line_labels={}):
def dot(self, format='svg'):
from graphviz import Digraph
dot = Digraph(format=format, graph_attr={'rankdir': 'LR', 'splines': 'true', 'size': '10', 'ranksep': '0.1'} | graph_attr)
dot = Digraph(format=format, graph_attr={'rankdir': 'LR', 'splines': 'true'})
s_dict = dict((n, i) for i, n in enumerate(self.s_nodes))
node_level = np.zeros(len(self.nodes), dtype=np.uint32)
@ -665,24 +666,17 @@ class Circuit: @@ -665,24 +666,17 @@ class Circuit:
with dot.subgraph() as s:
s.attr(rank='same')
for n in level_nodes[lv]:
ins = '{' + '|'.join([f'<i{i}>{i}' for i in range(len(n.ins))]) + '}|' if len(n.ins) > 1 else ''
outs = '|{' + '|'.join([f'<o{i}>{i}' for i in range(len(n.outs))]) + '}' if len(n.outs) > 1 else ''
ins = '|'.join([f'<i{i}>{i}' for i in range(len(n.ins))])
outs = '|'.join([f'<o{i}>{i}' for i in range(len(n.outs))])
io = f' [{s_dict[n]}]' if n in s_dict else ''
color = '#f5f5f5' if n.kind == '__fork__' else '#cccccc'
kind = '' if n.kind == '__fork__' else fr'\n{n.kind}'
s.node(name=str(n.index), label = fr'{{{ins}{n.index}{io}{kind}\n{n.name}{outs}}}', shape='record', style='filled', fillcolor=color)
s.node(name=str(n.index), label = f'{{{{{ins}}}|{n.index}{io}\n{n.kind}\n{n.name}|{{{outs}}}}}', shape='record')
for l in self.lines:
driver = f'{l.driver.index}:o{l.driver_pin}' if len(l.driver.outs)>1 else f'{l.driver.index}'
reader = f'{l.reader.index}:i{l.reader_pin}' if len(l.reader.ins)>1 else f'{l.reader.index}'
label = str(line_labels.get(l, l.index))
if node_level[l.driver] == node_level[l.reader]:
s.node(f'_{l.index}_')
dot.edge(driver, f'_{l.index}_', style='dotted', label=label)
dot.edge(f'_{l.index}_', reader, style='dotted', label=label)
elif node_level[l.driver] > node_level[l.reader]:
dot.edge(driver, reader, style='dotted', label=label)
driver, reader = f'{l.driver.index}:o{l.driver_pin}', f'{l.reader.index}:i{l.reader_pin}'
if node_level[l.driver] >= node_level[l.reader]:
dot.edge(driver, reader, style='dotted', label=str(l.index))
pass
else:
dot.edge(driver, reader, label=label)
dot.edge(driver, reader, label=str(l.index))
return dot

5
src/kyupy/logic.py

@ -47,7 +47,6 @@ The functions in this module use the ``mv...`` and ``bp...`` prefixes to signify @@ -47,7 +47,6 @@ The functions in this module use the ``mv...`` and ``bp...`` prefixes to signify
from collections.abc import Iterable
import numpy as np
from numpy.typing import DTypeLike
from . import numba, hr_bytes
@ -115,7 +114,7 @@ def mvarray(*a): @@ -115,7 +114,7 @@ def mvarray(*a):
def mv_str(mva, delim='\n'):
"""Renders a given multi-valued array into a string.
"""
sa = np.choose(mva, np.array([*'0X-1PRFN'], dtype=np.str_))
sa = np.choose(mva, np.array([*'0X-1PRFN'], dtype=np.unicode_))
if not hasattr(mva, 'ndim') or mva.ndim == 0: return sa
if mva.ndim == 1: return ''.join(sa)
return delim.join([''.join(c) for c in sa.swapaxes(-1,-2)])
@ -434,7 +433,7 @@ def unpackbits(a : np.ndarray): @@ -434,7 +433,7 @@ def unpackbits(a : np.ndarray):
return np.unpackbits(a.view(np.uint8), bitorder='little').reshape(*a.shape, 8*a.itemsize)
def packbits(a, dtype:DTypeLike=np.uint8):
def packbits(a, dtype=np.uint8):
"""Packs the values of a boolean-valued array ``a`` along its last axis into bits.
Similar to ``np.packbits``, but returns an array of given dtype and the shape of ``a`` with the last axis removed.

525
src/kyupy/logic_sim.py

@ -10,8 +10,8 @@ import math @@ -10,8 +10,8 @@ import math
import numpy as np
from . import numba, logic, hr_bytes, sim, eng, cdiv, batchrange
from .circuit import Circuit, Line
from . import numba, logic, hr_bytes, sim, eng, cdiv
from .circuit import Circuit
class LogicSim(sim.SimOps):
@ -43,7 +43,6 @@ class LogicSim(sim.SimOps): @@ -43,7 +43,6 @@ class LogicSim(sim.SimOps):
Access this array to assign new values to the (P)PIs or read values from the (P)POs.
"""
self.s[:,:,1,:] = 255 # unassigned
self._full_mask = np.full(self.c.shape[-1], 255, dtype=np.uint8)
def __repr__(self):
return f'{{name: "{self.circuit.name}", sims: {self.sims}, m: {self.m}, c_bytes: {eng(self.c.nbytes)}}}'
@ -53,7 +52,7 @@ class LogicSim(sim.SimOps): @@ -53,7 +52,7 @@ class LogicSim(sim.SimOps):
"""
self.c[self.pippi_c_locs] = self.s[0, self.pippi_s_locs, :self.mdim]
def c_prop(self, sims=None, inject_cb=None, fault_line:Line|int=-1, fault_mask=None, fault_model=2):
def c_prop(self, sims=None, inject_cb=None, fault_line=-1, fault_mask=None, fault_model=2):
"""Propagate the input values through the combinational circuit towards the outputs.
Performs all logic operations in topological order.
@ -62,23 +61,21 @@ class LogicSim(sim.SimOps): @@ -62,23 +61,21 @@ class LogicSim(sim.SimOps):
:param inject_cb: A callback function for manipulating intermediate signal values.
This function is called with a line and its new logic values (in bit-parallel format) after
evaluation of a node. The callback may manipulate the given values in-place, the simulation
resumes with the manipulated values after the callback returns. Specifying this callback
may reduce performance as it disables jit compilation.
resumes with the manipulated values after the callback returns.
:type inject_cb: ``f(Line, ndarray)``
"""
fault_line = int(fault_line)
if fault_mask is None:
fault_mask = self._full_mask # default: full mask
else:
if len(fault_mask) < self.c.shape[-1]: # pad mask with 0's if necessary
fault_mask2 = np.full(self.c.shape[-1], 0, dtype=np.uint8)
fault_mask2[:len(fault_mask)] = fault_mask
fault_mask = fault_mask2
t0 = self.c_locs[self.tmp_idx]
t1 = self.c_locs[self.tmp2_idx]
if self.m == 2:
if inject_cb is None:
c_prop_2v_cpu(self.ops, self.c_locs, self.c, int(fault_line), fault_mask, int(fault_model))
if fault_mask is None:
fault_mask = np.full(self.c.shape[-1], 255, dtype=np.uint8)
else:
if len(fault_mask) < self.c.shape[-1]:
fault_mask2 = np.full(self.c.shape[-1], 0, dtype=np.uint8)
fault_mask2[:len(fault_mask)] = fault_mask
fault_mask = fault_mask2
_prop_cpu(self.ops, self.c_locs, self.c, int(fault_line), fault_mask, int(fault_model))
else:
for op, o0l, i0l, i1l, i2l, i3l in self.ops[:,:6]:
o0, i0, i1, i2, i3 = [self.c_locs[x] for x in (o0l, i0l, i1l, i2l, i3l)]
@ -193,15 +190,6 @@ class LogicSim(sim.SimOps): @@ -193,15 +190,6 @@ class LogicSim(sim.SimOps):
logic.bp4v_or(self.c[o0], self.c[t0], self.c[t1])
else: print(f'unknown op {op}')
if inject_cb is not None: inject_cb(o0l, self.c[o0])
if fault_line >= 0 and o0l == fault_line:
if fault_model == 0:
self.c[o0] = self.c[o0] & ~fault_mask[np.newaxis]
elif fault_model == 1:
self.c[o0] = self.c[o0] | fault_mask[np.newaxis]
else:
self.c[t0, 0] = ~(self.c[o0, 0] & self.c[o0, 1] & fault_mask)
self.c[o0, 1] = ~self.c[o0, 0] & ~self.c[o0, 1] & fault_mask
self.c[o0, 0] = self.c[t0, 0]
else:
for op, o0l, i0l, i1l, i2l, i3l in self.ops[:,:6]:
o0, i0, i1, i2, i3 = [self.c_locs[x] for x in (o0l, i0l, i1l, i2l, i3l)]
@ -316,96 +304,8 @@ class LogicSim(sim.SimOps): @@ -316,96 +304,8 @@ class LogicSim(sim.SimOps):
self.s_ppo_to_ppi()
class LogicSim2V(sim.SimOps):
"""A bit-parallel naïve combinational simulator for 2-valued logic.
:param circuit: The circuit to simulate.
:param sims: The number of parallel logic simulations to perform.
:param c_reuse: If True, intermediate signal values may get overwritten when not needed anymore to save memory.
:param strip_forks: If True, forks are not included in the simulation model to save memory and simulation time.
Caveat: faults on fanout branches will not be injected if forks are stripped.
"""
def __init__(self, circuit: Circuit, sims: int = 8, c_reuse: bool = False, strip_forks: bool = False):
super().__init__(circuit, c_reuse=c_reuse, strip_forks=strip_forks)
self.sims = sims
nbytes = cdiv(sims, 8)
self.c = np.zeros((self.c_len, 1, nbytes), dtype=np.uint8)
"""Logic values within the combinational portion of the circuit.
In bit-parallel (bp) storage format.
Storage locations are indirectly addressed.
Data for line `l` is in `self.c[self.c_locs[l]]`.
"""
self.s_assign = np.zeros((self.s_len, self.sims), dtype=np.uint8)
"""Logic values assigned to the ports and flip-flops of the circuit.
The simulator reads (P)PI values from here.
Values assigned to PO positions are ignored.
This field is a 2-dimensional array and expects values in the mv storage format.
First index is the port position (defined by `self.circuit.s_nodes`), second index is the pattern index (0 ... `self.sims-1`).
"""
self.s_result = np.zeros((self.s_len, self.sims), dtype=np.uint8)
"""Logic values at the ports and flip-flops of the circuit after simulation.
The simulator writes (P)PO values here.
Values assigned to PI positions are ignored.
This field is a 2-dimensional array and expects values in the mv storage format.
First index is the port position (defined by `self.circuit.s_nodes`), second index is the pattern index (0 ... `self.sims-1`).
"""
self._full_mask = np.full(self.c.shape[-1], 255, dtype=np.uint8)
def __repr__(self):
return f'{{name: "{self.circuit.name}", sims: {self.sims}, c_bytes: {eng(self.c.nbytes)}}}'
def s_to_c(self):
"""Assigns the values from ``self.s_assign`` to the inputs of the combinational portion.
"""
self.c[self.pippi_c_locs] = logic.mv_to_bp(self.s_assign[self.pippi_s_locs])[:,:1,:]
def c_prop(self, fault_line=-1, fault_model=2, fault_mask=None):
if fault_mask is None:
fault_mask = self._full_mask # default: full mask
else:
if len(fault_mask) < self.c.shape[-1]: # pad mask with 0's if necessary
fault_mask2 = np.full(self.c.shape[-1], 0, dtype=np.uint8)
fault_mask2[:len(fault_mask)] = fault_mask
fault_mask = fault_mask2
c_prop_2v_cpu(self.ops, self.c_locs, self.c, int(fault_line), fault_mask, int(fault_model))
def c_to_s(self):
"""Captures the results of the combinational portion into ``self.s_result``.
"""
self.s_result[self.poppo_s_locs] = logic.bp_to_mv(self.c[self.poppo_c_locs])[:,:self.sims] * logic.ONE
def c_ppo_to_ppi(self):
"""Copies the result data for all PPOs (flip-flops) to PPIs for a next simulation cycle.
"""
self.c[self.ppi_c_locs] = self.c[self.ppo_c_locs].copy() # copy prevents undefined behavior if (P)PIs are connected directly to (P)POs
def allocate(self):
"""Allocates a new pattern array with appropriate dimensions ``(self.s_len, self.sims)`` for one-pass simulation.
"""
return np.full((self.s_len, self.sims), logic.ZERO, dtype=np.uint8)
def simulate(self, patterns, responses = None, cycles:int = 1, fault_line=-1, fault_model=2, fault_mask=None):
assert cycles >= 1
for bo, bs in batchrange(patterns.shape[-1], self.sims):
self.s_assign[self.pippi_s_locs, :bs] = patterns[self.pippi_s_locs, bo:bo+bs]
self.s_to_c()
for cycle in range(cycles):
self.c_prop(fault_line=fault_line, fault_model=fault_model, fault_mask=fault_mask)
if cycle < (cycles-1): self.c_ppo_to_ppi()
self.c_to_s()
if responses is None:
patterns[self.poppo_s_locs, bo:bo+bs] = self.s_result[self.poppo_s_locs, :bs]
else:
responses[self.poppo_s_locs, bo:bo+bs] = self.s_result[self.poppo_s_locs, :bs]
return patterns if responses is None else responses
@numba.njit
def c_prop_2v_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model):
def _prop_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model):
for op, o0l, i0l, i1l, i2l, i3l in ops[:,:6]:
o0, i0, i1, i2, i3 = [c_locs[x] for x in (o0l, i0l, i1l, i2l, i3l)]
if op == sim.BUF1: c[o0]=c[i0]
@ -443,6 +343,7 @@ def c_prop_2v_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model): @@ -443,6 +343,7 @@ def c_prop_2v_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model):
elif op == sim.MUX21: c[o0] = (c[i0] & ~c[i2]) | (c[i1] & c[i2])
else: print(f'unknown op {op}')
if fault_line >= 0 and o0l == fault_line:
#n = len(fault_mask)
if fault_model == 0:
c[o0] = c[o0] & ~fault_mask
elif fault_model == 1:
@ -451,283 +352,9 @@ def c_prop_2v_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model): @@ -451,283 +352,9 @@ def c_prop_2v_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model):
c[o0] = c[o0] ^ fault_mask
class LogicSim4V(sim.SimOps):
"""A bit-parallel naïve combinational simulator for 4-valued logic.
Logic values: 0, 1, -, X
:param circuit: The circuit to simulate.
:param sims: The number of parallel logic simulations to perform.
:param c_reuse: If True, intermediate signal values may get overwritten when not needed anymore to save memory.
:param strip_forks: If True, forks are not included in the simulation model to save memory and simulation time.
"""
def __init__(self, circuit: Circuit, sims: int = 8, c_reuse: bool = False, strip_forks: bool = False):
super().__init__(circuit, c_reuse=c_reuse, strip_forks=strip_forks)
self.sims = sims
nbytes = cdiv(sims, 8)
self.c = np.zeros((self.c_len, 2, nbytes), dtype=np.uint8)
"""Logic values within the combinational portion of the circuit.
In bit-parallel (bp) storage format.
Storage locations are indirectly addressed.
Data for line `l` is in `self.c[self.c_locs[l]]`.
"""
self.s_assign = np.zeros((self.s_len, self.sims), dtype=np.uint8)
"""Logic values assigned to the ports and flip-flops of the circuit.
The simulator reads (P)PI values from here.
Values assigned to PO positions are ignored.
This field is a 2-dimensional array and expects values in the mv storage format.
First index is the port position (defined by `self.circuit.s_nodes`), second
index is the pattern index (0 ... `self.sims-1`).
"""
self.s_result = np.zeros((self.s_len, self.sims), dtype=np.uint8)
"""Logic values at the ports and flip-flops of the circuit after simulation.
The simulator writes (P)PO values here.
Values assigned to PI positions are ignored.
This field is a 2-dimensional array and expects values in the mv storage format.
First index is the port position (defined by `self.circuit.s_nodes`), second
index is the pattern index (0 ... `self.sims-1`).
"""
self._full_mask = np.full(self.c.shape[-1], 255, dtype=np.uint8)
def __repr__(self):
return f'{{name: "{self.circuit.name}", sims: {self.sims}, c_bytes: {eng(self.c.nbytes)}}}'
def s_to_c(self):
"""Assigns the values from ``self.s_assign`` to the inputs of the combinational portion.
"""
self.c[self.pippi_c_locs] = logic.mv_to_bp(self.s_assign[self.pippi_s_locs])[:,:2,:]
def c_prop(self, fault_line=-1, fault_model=2, fault_mask=None):
if fault_mask is None:
fault_mask = self._full_mask # default: full mask
else:
if len(fault_mask) < self.c.shape[-1]: # pad mask with 0's if necessary
fault_mask2 = np.full(self.c.shape[-1], 0, dtype=np.uint8)
fault_mask2[:len(fault_mask)] = fault_mask
fault_mask = fault_mask2
c_prop_4v_cpu(self.ops, self.c_locs, self.c, int(fault_line), fault_mask, int(fault_model), self.tmp_idx, self.tmp2_idx)
def c_to_s(self):
"""Captures the results of the combinational portion into ``self.s_result``.
"""
self.s_result[self.poppo_s_locs] = logic.bp_to_mv(self.c[self.poppo_c_locs])[:,:self.sims]
def c_ppo_to_ppi(self):
"""Copies the result data for all PPOs (flip-flops) to PPIs for a next simulation cycle.
"""
self.c[self.ppi_c_locs] = self.c[self.ppo_c_locs].copy() # copy prevents undefined behavior if (P)PIs are connected directly to (P)POs
def allocate(self):
"""Allocates a new pattern array with appropriate dimensions ``(self.s_len, self.sims)`` for one-pass simulation.
"""
return np.full((self.s_len, self.sims), logic.ZERO, dtype=np.uint8)
def simulate(self, patterns, responses = None, cycles:int = 1, fault_line=-1, fault_model=2, fault_mask=None):
assert cycles >= 1
for bo, bs in batchrange(patterns.shape[-1], self.sims):
self.s_assign[self.pippi_s_locs, :bs] = patterns[self.pippi_s_locs, bo:bo+bs]
self.s_to_c()
for cycle in range(cycles):
self.c_prop(fault_line=fault_line, fault_model=fault_model, fault_mask=fault_mask)
if cycle < (cycles-1): self.c_ppo_to_ppi()
self.c_to_s()
if responses is None:
patterns[self.poppo_s_locs, bo:bo+bs] = self.s_result[self.poppo_s_locs, :bs]
else:
responses[self.poppo_s_locs, bo:bo+bs] = self.s_result[self.poppo_s_locs, :bs]
return patterns if responses is None else responses
@numba.njit
def c_prop_4v_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model, t0_idx, t1_idx):
t0 = c[c_locs[t0_idx]]
t1 = c[c_locs[t1_idx]]
for op, o0l, i0l, i1l, i2l, i3l in ops[:,:6]:
o0, i0, i1, i2, i3 = [c[c_locs[x]] for x in (o0l, i0l, i1l, i2l, i3l)]
if op == sim.BUF1 or op == sim.INV1:
# i0[0,1] -> o0[0,1]
# 0 0 0 -> 0 0 0
# - 0 1 -> X 1 0
# X 1 0 -> X 1 0
# 1 1 1 -> 1 1 1
o0[0] = i0[0] | i0[1]
o0[1] = i0[0] & i0[1]
elif op == sim.AND2 or op == sim.NAND2:
# i1[0,1] * i0[0,1] -> o0[0,1]
# 0 0 0 0 0 0 -> 0 0 0
# 0 0 0 - 0 1 -> 0 0 0
# 0 0 0 X 1 0 -> 0 0 0
# 0 0 0 1 1 1 -> 0 0 0
# - 0 1 0 0 0 -> 0 0 0
# - 0 1 - 0 1 -> X 1 0
# - 0 1 X 1 0 -> X 1 0
# - 0 1 1 1 1 -> X 1 0
# X 1 0 0 0 0 -> 0 0 0
# X 1 0 - 0 1 -> X 1 0
# X 1 0 X 1 0 -> X 1 0
# X 1 0 1 1 1 -> X 1 0
# 1 1 1 0 0 0 -> 0 0 0
# 1 1 1 - 0 1 -> X 1 0
# 1 1 1 X 1 0 -> X 1 0
# 1 1 1 1 1 1 -> 1 1 1
o0[0] = (i0[0]|i0[1]) & (i1[0]|i1[1])
o0[1] = i0[0] & i0[1] & i1[0] & i1[1]
elif op == sim.AND3 or op == sim.NAND3:
# i2[0,1] * i1[0,1] * i0[0,1] -> o0[0,1]
o0[0] = (i0[0]|i0[1]) & (i1[0]|i1[1]) & (i2[0]|i2[1])
o0[1] = i0[0] & i0[1] & i1[0] & i1[1] & i2[0] & i2[1]
elif op == sim.AND4 or op == sim.NAND4:
# i3[0,1] * i2[0,1] * i1[0,1] * i0[0,1] -> o0[0,1]
o0[0] = (i0[0]|i0[1]) & (i1[0]|i1[1]) & (i2[0]|i2[1]) & (i3[0]|i3[1])
o0[1] = i0[0] & i0[1] & i1[0] & i1[1] & i2[0] & i2[1] & i3[0] & i3[1]
elif op == sim.OR2 or op == sim.NOR2:
# i1[0,1] + i0[0,1] -> o0[0,1]
# 0 0 0 0 0 0 -> 0 0 0
# 0 0 0 - 0 1 -> X 1 0
# 0 0 0 X 1 0 -> X 1 0
# 0 0 0 1 1 1 -> 1 1 1
# - 0 1 0 0 0 -> X 1 0
# - 0 1 - 0 1 -> X 1 0
# - 0 1 X 1 0 -> X 1 0
# - 0 1 1 1 1 -> 1 1 1
# X 1 0 0 0 0 -> X 1 0
# X 1 0 - 0 1 -> X 1 0
# X 1 0 X 1 0 -> X 1 0
# X 1 0 1 1 1 -> 1 1 1
# 1 1 1 0 0 0 -> 1 1 1
# 1 1 1 - 0 1 -> 1 1 1
# 1 1 1 X 1 0 -> 1 1 1
# 1 1 1 1 1 1 -> 1 1 1
o0[0] = i0[0] | i0[1] | i1[0] | i1[1]
o0[1] = (i0[0]&i0[1]) | (i1[0]&i1[1])
elif op == sim.OR3 or op == sim.NOR3:
# i2[0,1] + i1[0,1] + i0[0,1] -> o0[0,1]
o0[0] = i0[0] | i0[1] | i1[0] | i1[1] | i2[0] | i2[1]
o0[1] = (i0[0]&i0[1]) | (i1[0]&i1[1]) | (i2[0]&i2[1])
elif op == sim.OR4 or op == sim.NOR4:
# i3[0,1] + i2[0,1] + i1[0,1] + i0[0,1] -> o0[0,1]
o0[0] = i0[0] | i0[1] | i1[0] | i1[1] | i2[0] | i2[1] | i3[0] | i3[1]
o0[1] = (i0[0]&i0[1]) | (i1[0]&i1[1]) | (i2[0]&i2[1]) | (i3[0]&i3[1])
elif op == sim.XOR2 or op == sim.XNOR2:
# i1[0,1] ^ i0[0,1] -> o0[0,1]
# 0 0 0 0 0 0 -> 0 0 0
# 0 0 0 - 0 1 -> X 1 0
# 0 0 0 X 1 0 -> X 1 0
# 0 0 0 1 1 1 -> 1 1 1
# - 0 1 0 0 0 -> X 1 0
# - 0 1 - 0 1 -> X 1 0
# - 0 1 X 1 0 -> X 1 0
# - 0 1 1 1 1 -> X 1 0
# X 1 0 0 0 0 -> X 1 0
# X 1 0 - 0 1 -> X 1 0
# X 1 0 X 1 0 -> X 1 0
# X 1 0 1 1 1 -> X 1 0
# 1 1 1 0 0 0 -> 1 1 1
# 1 1 1 - 0 1 -> X 1 0
# 1 1 1 X 1 0 -> X 1 0
# 1 1 1 1 1 1 -> 0 0 0
o0[0] = (i0[0]|i0[1]|i1[0]|i1[1]) & (~i0[0]|~i0[1]|~i1[0]|~i1[1])
o0[1] = (~i0[0]&~i0[1]&i1[0]&i1[1]) | (i0[0]&i0[1]&~i1[0]&~i1[1])
elif op == sim.XOR3 or op == sim.XNOR3:
# i2[0,1] ^ i1[0,1] ^ i0[0,1] -> o0[0,1]
t0[0] = (i0[0]|i0[1]|i1[0]|i1[1]) & (~i0[0]|~i0[1]|~i1[0]|~i1[1])
t0[1] = (~i0[0]&~i0[1]&i1[0]&i1[1]) | (i0[0]&i0[1]&~i1[0]&~i1[1])
o0[0] = (t0[0]|t0[1]|i2[0]|i2[1]) & (~t0[0]|~t0[1]|~i2[0]|~i2[1])
o0[1] = (~t0[0]&~t0[1]&i2[0]&i2[1]) | (t0[0]&t0[1]&~i2[0]&~i2[1])
elif op == sim.XOR4 or op == sim.XNOR4:
# i3[0,1] ^ i2[0,1] ^ i1[0,1] ^ i0[0,1] -> o0[0,1]
t0[0] = (i0[0]|i0[1]|i1[0]|i1[1]) & (~i0[0]|~i0[1]|~i1[0]|~i1[1])
t0[1] = (~i0[0]&~i0[1]&i1[0]&i1[1]) | (i0[0]&i0[1]&~i1[0]&~i1[1])
t1[0] = (t0[0]|t0[1]|i2[0]|i2[1]) & (~t0[0]|~t0[1]|~i2[0]|~i2[1])
t1[1] = (~t0[0]&~t0[1]&i2[0]&i2[1]) | (t0[0]&t0[1]&~i2[0]&~i2[1])
o0[0] = (t1[0]|t1[1]|i3[0]|i3[1]) & (~t1[0]|~t1[1]|~i3[0]|~i3[1])
o0[1] = (~t1[0]&~t1[1]&i3[0]&i3[1]) | (t1[0]&t1[1]&~i3[0]&~i3[1])
elif op == sim.AO21 or op == sim.AOI21:
# (i0[0,1] * i1[0,1]) + i2[0,1] -> o0[0,1]
t0[0] = (i0[0]|i0[1]) & (i1[0]|i1[1])
t0[1] = i0[0] & i0[1] & i1[0] & i1[1]
o0[0] = t0[0] | t0[1] | i2[0] | i2[1]
o0[1] = (t0[0]&t0[1]) | (i2[0]&i2[1])
elif op == sim.OA21 or op == sim.OAI21:
# (i0[0,1] + i1[0,1]) * i2[0,1] -> o0[0,1]
t0[0] = i0[0] | i0[1] | i1[0] | i1[1]
t0[1] = (i0[0]&i0[1]) | (i1[0]&i1[1])
o0[0] = (t0[0]|t0[1]) & (i2[0]|i2[1])
o0[1] = t0[0] & t0[1] & i2[0] & i2[1]
elif op == sim.AO22 or op == sim.AOI22:
# (i0[0,1] * i1[0,1]) + (i2[0,1] * i3[0,1]) -> o0[0,1]
t0[0] = (i0[0]|i0[1]) & (i1[0]|i1[1])
t0[1] = i0[0] & i0[1] & i1[0] & i1[1]
t1[0] = (i2[0]|i2[1]) & (i3[0]|i3[1])
t1[1] = i2[0] & i2[1] & i3[0] & i3[1]
o0[0] = t0[0] | t0[1] | t1[0] | t1[1]
o0[1] = (t0[0]&t0[1]) | (t1[0]&t1[1])
elif op == sim.OA22 or op == sim.OAI22:
# (i0[0,1] + i1[0,1]) * (i2[0,1] + i3[0,1]) -> o0[0,1]
t0[0] = i0[0] | i0[1] | i1[0] | i1[1]
t0[1] = (i0[0]&i0[1]) | (i1[0]&i1[1])
t1[0] = i2[0] | i2[1] | i3[0] | i3[1]
t1[1] = (i2[0]&i2[1]) | (i3[0]&i3[1])
o0[0] = (t0[0]|t0[1]) & (t1[0]|t1[1])
o0[1] = t0[0] & t0[1] & t1[0] & t1[1]
elif op == sim.AO211 or op == sim.AOI211:
# (i0[0,1] * i1[0,1]) + i2[0,1] + i3[0,1] -> o0[0,1]
t0[0] = (i0[0]|i0[1]) & (i1[0]|i1[1])
t0[1] = i0[0] & i0[1] & i1[0] & i1[1]
o0[0] = t0[0] | t0[1] | i2[0] | i2[1] | i3[0] | i3[1]
o0[1] = (t0[0]&t0[1]) | (i2[0]&i2[1]) | (i3[0]&i3[1])
elif op == sim.OA211 or op == sim.OAI211:
# (i0[0,1] + i1[0,1]) * i2[0,1] * i3[0,1] -> o0[0,1]
t0[0] = i0[0] | i0[1] | i1[0] | i1[1]
t0[1] = (i0[0]&i0[1]) | (i1[0]&i1[1])
o0[0] = (t0[0]|t0[1]) & (i2[0]|i2[1]) & (i3[0]|i3[1])
o0[1] = t0[0] & t0[1] & i2[0] & i2[1] & i3[0] & i3[1]
elif op == sim.MUX21:
# t1[0,1] = ~i2[0,1]
t1[0] = ~i2[0] | ~i2[1]
t1[1] = ~i2[0] & ~i2[1]
# t0 = i0 * t1
t0[0] = (i0[0]|i0[1]) & (t1[0]|t1[1])
t0[1] = i0[0] & i0[1] & t1[0] & t1[1]
# t1 = i1 * i2
t1[0] = (i1[0]|i1[1]) & (i2[0]|i2[1])
t1[1] = i1[0] & i1[1] & i2[0] & i2[1]
# o0 = t0 | t1
o0[0] = t0[0] | t0[1] | t1[0] | t1[1]
o0[1] = (t0[0]&t0[1]) | (t1[0]&t1[1])
else: print(f'unknown op {op}')
if (op == sim.INV1 or
op == sim.NAND2 or op == sim.NAND3 or op == sim.NAND4 or
op == sim.NOR2 or op == sim.NOR3 or op == sim.NOR4 or
op == sim.XNOR2 or op == sim.XNOR3 or op == sim.XNOR4 or
op == sim.AOI21 or op == sim.OAI21 or
op == sim.AOI22 or op == sim.OAI22 or
op == sim.AOI211 or op == sim.OAI211):
# o0[0,1] -> o0[0,1]
# 0 0 0 -> 1 1 1
# - 0 1 -> X 1 0
# X 1 0 -> X 1 0
# 1 1 1 -> 0 0 0
t0[0] = ~o0[0] | ~o0[1]
o0[1] = ~o0[0] & ~o0[1]
o0[0] = t0[0]
if fault_line >= 0 and o0l == fault_line:
if fault_model == 0:
o0[...] = o0 & ~fault_mask
elif fault_model == 1:
o0[...] = o0 | fault_mask
else:
t0[0] = ~(o0[0] ^ o0[1]) # mask for 0 and 1
o0[...] = o0 ^ (fault_mask & t0[0]) # leaves X and - untouched
class LogicSim6V(sim.SimOps):
"""A bit-parallel naïve combinational simulator for 6-valued logic.
Logic values: 0, 1, R, F, N, P
:param circuit: The circuit to simulate.
:param sims: The number of parallel logic simulations to perform.
:param c_reuse: If True, intermediate signal values may get overwritten when not needed anymore to save memory.
@ -749,9 +376,6 @@ class LogicSim6V(sim.SimOps): @@ -749,9 +376,6 @@ class LogicSim6V(sim.SimOps):
Access this array to assign new values to the (P)PIs or read values from the (P)POs.
"""
self.s_assign = self.s[0]
self.s_result = self.s[1]
self._full_mask = np.full(self.c.shape[-1], 255, dtype=np.uint8)
def __repr__(self):
return f'{{name: "{self.circuit.name}", sims: {self.sims}, c_bytes: {eng(self.c.nbytes)}}}'
@ -761,49 +385,17 @@ class LogicSim6V(sim.SimOps): @@ -761,49 +385,17 @@ class LogicSim6V(sim.SimOps):
"""
self.c[self.pippi_c_locs] = logic.mv_to_bp(self.s[0, self.pippi_s_locs])
def c_prop(self, fault_line=-1, fault_model=2, fault_mask=None):
if fault_mask is None:
fault_mask = self._full_mask # default: full mask
else:
if len(fault_mask) < self.c.shape[-1]: # pad mask with 0's if necessary
fault_mask2 = np.full(self.c.shape[-1], 0, dtype=np.uint8)
fault_mask2[:len(fault_mask)] = fault_mask
fault_mask = fault_mask2
c_prop_6v_cpu(self.ops, self.c, self.c_locs, fault_line, fault_mask, fault_model, self.tmp_idx, self.tmp2_idx)
def c_prop(self):
c_prop_cpu(self.ops, self.c, self.c_locs, self.tmp_idx, self.tmp2_idx)
def c_to_s(self):
"""Captures the results of the combinational portion into ``s[1]``.
"""
self.s[1, self.poppo_s_locs] = logic.bp_to_mv(self.c[self.poppo_c_locs])[:,:self.sims]
def c_ppo_to_ppi(self):
"""Copies the result data for all PPOs (flip-flops) to PPIs for a next simulation cycle.
"""
self.c[self.ppi_c_locs] = self.c[self.ppo_c_locs].copy() # copy prevents undefined behavior if (P)PIs are connected directly to (P)POs
def allocate(self):
"""Allocates a new pattern array with appropriate dimensions ``(self.s_len, self.sims)`` for one-pass simulation.
"""
return np.full((self.s_len, self.sims), logic.ZERO, dtype=np.uint8)
def simulate(self, patterns, responses = None, cycles:int = 1, fault_line=-1, fault_model=2, fault_mask=None):
assert cycles >= 1
for bo, bs in batchrange(patterns.shape[-1], self.sims):
self.s_assign[self.pippi_s_locs, :bs] = patterns[self.pippi_s_locs, bo:bo+bs]
self.s_to_c()
for cycle in range(cycles):
self.c_prop(fault_line=fault_line, fault_model=fault_model, fault_mask=fault_mask)
if cycle < (cycles-1): self.c_ppo_to_ppi()
self.c_to_s()
if responses is None:
patterns[self.poppo_s_locs, bo:bo+bs] = self.s_result[self.poppo_s_locs, :bs]
else:
responses[self.poppo_s_locs, bo:bo+bs] = self.s_result[self.poppo_s_locs, :bs]
return patterns
@numba.njit
def c_prop_6v_cpu(ops, c, c_locs, fault_line, fault_mask, fault_model, tmp_idx, tmp2_idx):
def c_prop_cpu(ops, c, c_locs, tmp_idx, tmp2_idx):
t0 = c[c_locs[tmp_idx]]
t1 = c[c_locs[tmp2_idx]]
inv_op = np.array([255, 255, 0], dtype=np.uint8)[np.newaxis, :, np.newaxis]
@ -851,84 +443,6 @@ def c_prop_6v_cpu(ops, c, c_locs, fault_line, fault_mask, fault_model, tmp_idx, @@ -851,84 +443,6 @@ def c_prop_6v_cpu(ops, c, c_locs, fault_line, fault_mask, fault_model, tmp_idx,
o0[0] = i0[0] ^ i1[0]
o0[1] = i0[1] ^ i1[1]
o0[2] = i0[2] | i1[2]
elif op == sim.XOR3 or op == sim.XNOR3:
o0[0] = i0[0] ^ i1[0] ^ i2[0]
o0[1] = i0[1] ^ i1[1] ^ i2[1]
o0[2] = i0[2] | i1[2] | i2[2]
elif op == sim.XOR4 or op == sim.XNOR4:
o0[0] = i0[0] ^ i1[0] ^ i2[0] ^ i3[0]
o0[1] = i0[1] ^ i1[1] ^ i2[1] ^ i3[1]
o0[2] = i0[2] | i1[2] | i2[2] | i3[2]
elif op == sim.AO21 or op == sim.AOI21:
# (i0[0,1] * i1[0,1]) + i2[0,1] -> o0[0,1]
t0[0] = i0[0] & i1[0]
t0[1] = i0[1] & i1[1]
t0[2] = (i0[2]&(i1[0]|i1[1]|i1[2])|
i1[2]&(i0[0]|i0[1]|i0[2]))
o0[0] = t0[0] | i2[0]
o0[1] = t0[1] | i2[1]
o0[2] = (t0[2]&(~i2[0]|~i2[1]|i2[2])|
i2[2]&(~t0[0]|~t0[1]|t0[2]))
elif op == sim.OA21 or op == sim.OAI21:
# (i0[0,1] + i1[0,1]) * i2[0,1] -> o0[0,1]
t0[0] = i0[0] | i1[0]
t0[1] = i0[1] | i1[1]
t0[2] = (i0[2]&(~i1[0]|~i1[1]|i1[2])|
i1[2]&(~i0[0]|~i0[1]|i0[2]))
o0[0] = t0[0] & i2[0]
o0[1] = t0[1] & i2[1]
o0[2] = (t0[2]&(i2[0]|i2[1]|i2[2])|
i2[2]&(t0[0]|t0[1]|t0[2]))
elif op == sim.AO22 or op == sim.AOI22:
# (i0[0,1] * i1[0,1]) + (i2[0,1] * i3[0,1]) -> o0[0,1]
t0[0] = i0[0] & i1[0]
t0[1] = i0[1] & i1[1]
t0[2] = (i0[2]&(i1[0]|i1[1]|i1[2])|
i1[2]&(i0[0]|i0[1]|i0[2]))
t1[0] = i2[0] & i3[0]
t1[1] = i2[1] & i3[1]
t1[2] = (i2[2]&(i3[0]|i3[1]|i3[2])|
i3[2]&(i2[0]|i2[1]|i2[2]))
o0[0] = t0[0] | t1[0]
o0[1] = t0[1] | t1[1]
o0[2] = (t0[2]&(~t1[0]|~t1[1]|t1[2])|
t1[2]&(~t0[0]|~t0[1]|t0[2]))
elif op == sim.OA22 or op == sim.OAI22:
# (i0[0,1] + i1[0,1]) * (i2[0,1] + i3[0,1]) -> o0[0,1]
t0[0] = i0[0] | i1[0]
t0[1] = i0[1] | i1[1]
t0[2] = (i0[2]&(~i1[0]|~i1[1]|i1[2])|
i1[2]&(~i0[0]|~i0[1]|i0[2]))
t1[0] = i2[0] | i3[0]
t1[1] = i2[1] | i3[1]
t1[2] = (i2[2]&(~i3[0]|~i3[1]|i3[2])|
i3[2]&(~i2[0]|~i2[1]|i2[2]))
o0[0] = t0[0] & t1[0]
o0[1] = t0[1] & t1[1]
o0[2] = (t0[2]&(t1[0]|t1[1]|t1[2])|
t1[2]&(t0[0]|t0[1]|t0[2]))
elif op == sim.AO211 or op == sim.AOI211:
# (i0[0,1] * i1[0,1]) + i2[0,1] + i3[0,1] -> o0[0,1]
t0[0] = i0[0] & i1[0]
t0[1] = i0[1] & i1[1]
t0[2] = (i0[2]&(i1[0]|i1[1]|i1[2])|
i1[2]&(i0[0]|i0[1]|i0[2]))
o0[0] = t0[0] | i2[0] | i3[0]
o0[1] = t0[1] | i2[1] | i3[1]
o0[2] = (t0[2]&(~i2[0]|~i2[1]|i2[2])&(~i3[0]|~i3[1]|i3[2])|
i2[2]&(~t0[0]|~t0[1]|t0[2])&(~i3[0]|~i3[1]|i3[2])|
i3[2]&(~t0[0]|~t0[1]|t0[2])&(~i2[0]|~i2[1]|i2[2]))
elif op == sim.OA211 or op == sim.OAI211:
# (i0[0,1] + i1[0,1]) * i2[0,1] * i3[0,1] -> o0[0,1]
t0[0] = i0[0] | i1[0]
t0[1] = i0[1] | i1[1]
t0[2] = (i0[2]&(~i1[0]|~i1[1]|i1[2])|
i1[2]&(~i0[0]|~i0[1]|i0[2]))
o0[0] = t0[0] & i2[0] & i3[0]
o0[1] = t0[1] & i2[1] & i3[1]
o0[2] = (t0[2]&(i2[0]|i2[1]|i2[2])&(i3[0]|i3[1]|i3[2])|
i2[2]&(t0[0]|t0[1]|t0[2])&(i3[0]|i3[1]|i3[2])|
i3[2]&(t0[0]|t0[1]|t0[2])&(i2[0]|i2[1]|i2[2]))
elif op == sim.MUX21:
# t1 = ~i2
t1[...] = i2 ^ inv_op
@ -952,8 +466,5 @@ def c_prop_6v_cpu(ops, c, c_locs, fault_line, fault_mask, fault_model, tmp_idx, @@ -952,8 +466,5 @@ def c_prop_6v_cpu(ops, c, c_locs, fault_line, fault_mask, fault_model, tmp_idx,
if (op == sim.INV1 or
op == sim.NAND2 or op == sim.NAND3 or op == sim.NAND4 or
op == sim.NOR2 or op == sim.NOR3 or op == sim.NOR4 or
op == sim.XNOR2 or op == sim.XNOR3 or op == sim.XNOR4 or
op == sim.AOI21 or op == sim.OAI21 or
op == sim.AOI22 or op == sim.OAI22 or
op == sim.AOI211 or op == sim.OAI211):
op == sim.XNOR2):
o0[...] = o0 ^ inv_op

12
src/kyupy/sim.py

@ -4,7 +4,6 @@ from bisect import bisect, insort_left @@ -4,7 +4,6 @@ from bisect import bisect, insort_left
import numpy as np
from . import log
from .circuit import Circuit
BUF1 = np.uint16(0b1010_1010_1010_1010)
@ -188,10 +187,10 @@ class SimOps: @@ -188,10 +187,10 @@ class SimOps:
levels = []
ppio2idx = dict((n, i) for i, n in enumerate(circuit.s_nodes))
root_nodes = set([n for n in circuit.s_nodes if len(n.ins) > 0] + [n for n in circuit.nodes if len(n.outs) == 0]) # start from POs, PPOs, and any dangling nodes
readers = np.array([1 if l.reader in root_nodes else len(l.reader.outs) for l in circuit.lines], dtype=np.int32) # for ref-counting forks
ppos = set([n for n in circuit.s_nodes if len(n.ins) > 0])
readers = np.array([1 if l.reader in ppos else len(l.reader.outs) for l in circuit.lines], dtype=np.int32) # for ref-counting forks
level_lines = [n.ins[0] for n in root_nodes]
level_lines = [n.ins[0] for n in ppos] # start from PPOs
# FIXME: Should probably instanciate buffers for PPOs and attach DFF clocks
while len(level_lines) > 0: # traverse the circuit level-wise back towards (P)PIs
@ -200,8 +199,6 @@ class SimOps: @@ -200,8 +199,6 @@ class SimOps:
for l in level_lines:
n = l.driver
if len(n.ins) > 4:
log.warn(f'too many input pins: {n}')
in_idxs = [n.ins[x].index if len(n.ins) > x and n.ins[x] is not None else self.zero_idx for x in [0,1,2,3]]
if n in ppio2idx:
in_idxs[0] = self.ppi_offset + ppio2idx[n]
@ -226,7 +223,7 @@ class SimOps: @@ -226,7 +223,7 @@ class SimOps:
sp = prims[2]
break
if sp is None:
log.warn(f'ignored cell of unknown type: {n}')
print('unknown cell type', kind)
else:
level_ops.append((sp, l.index, *in_idxs, *a_ctrl[l]))
@ -236,7 +233,6 @@ class SimOps: @@ -236,7 +233,6 @@ class SimOps:
self.levels = [np.asarray(lv, dtype=np.int32) for lv in levels[::-1]]
level_sums = np.cumsum([0]+[len(lv) for lv in self.levels], dtype=np.int32)
self.level_starts, self.level_stops = level_sums[:-1], level_sums[1:]
# op format: [kind, out0, in0, in1, in2, in3, wsa_acc_pos, wsa_rise, wsa_fall]
self.ops = np.vstack(self.levels)
# create a map from fanout lines to stem lines for fork stripping

40
src/kyupy/techlib.py

@ -64,44 +64,6 @@ class TechLib: @@ -64,44 +64,6 @@ class TechLib:
return self.cells[kind][1][pin][1]
KYUPY = TechLib(r"""
BUF1 input(i0) output(o) o=BUF1(i0) ;
INV1 input(i0) output(o) o=INV1(i0) ;
AND2 input(i0,i1) output(o) o=AND2(i0,i1) ;
AND3 input(i0,i1,i2) output(o) o=AND3(i0,i1,i2) ;
AND4 input(i0,i1,i2,i3) output(o) o=AND4(i0,i1,i2,i3) ;
NAND2 input(i0,i1) output(o) o=NAND2(i0,i1) ;
NAND3 input(i0,i1,i2) output(o) o=NAND3(i0,i1,i2) ;
NAND4 input(i0,i1,i2,i3) output(o) o=NAND4(i0,i1,i2,i3);
OR2 input(i0,i1) output(o) o=OR2(i0,i1) ;
OR3 input(i0,i1,i2) output(o) o=OR3(i0,i1,i2) ;
OR4 input(i0,i1,i2,i3) output(o) o=OR4(i0,i1,i2,i3) ;
NOR2 input(i0,i1) output(o) o=NOR2(i0,i1) ;
NOR3 input(i0,i1,i2) output(o) o=NOR3(i0,i1,i2) ;
NOR4 input(i0,i1,i2,i3) output(o) o=NOR4(i0,i1,i2,i3) ;
XOR2 input(i0,i1) output(o) o=XOR2(i0,i1) ;
XOR3 input(i0,i1,i2) output(o) o=XOR3(i0,i1,i2) ;
XOR4 input(i0,i1,i2,i3) output(o) o=XOR4(i0,i1,i2,i3) ;
XNOR2 input(i0,i1) output(o) o=XNOR2(i0,i1) ;
XNOR3 input(i0,i1,i2) output(o) o=XNOR3(i0,i1,i2) ;
XNOR4 input(i0,i1,i2,i3) output(o) o=XNOR4(i0,i1,i2,i3) ;
AO21 input(i0,i1,i2) output(o) o=AO21(i0,i1,i2) ;
AO22 input(i0,i1,i2,i3) output(o) o=AO22(i0,i1,i2,i3) ;
OA21 input(i0,i1,i2) output(o) o=OA21(i0,i1,i2) ;
OA22 input(i0,i1,i2,i3) output(o) o=OA22(i0,i1,i2,i3) ;
AOI21 input(i0,i1,i2) output(o) o=AOI21(i0,i1,i2) ;
AOI22 input(i0,i1,i2,i3) output(o) o=AOI22(i0,i1,i2,i3) ;
OAI21 input(i0,i1,i2) output(o) o=OAI21(i0,i1,i2) ;
OAI22 input(i0,i1,i2,i3) output(o) o=OAI22(i0,i1,i2,i3) ;
AO211 input(i0,i1,i2,i3) output(o) o=AO211(i0,i1,i2,i3) ;
OA211 input(i0,i1,i2,i3) output(o) o=OA211(i0,i1,i2,i3) ;
AOI211 input(i0,i1,i2,i3) output(o) o=AOI211(i0,i1,i2,i3) ;
OAI211 input(i0,i1,i2,i3) output(o) o=OAI211(i0,i1,i2,i3) ;
MUX21 input(i0,i1,i2) output(o) o=MUX21(i0,i1,i2) ;
""")
"""A synthetic library of all KyuPy simulation primitives.
"""
GSC180 = TechLib(r"""
BUFX{1,3} input(A) output(Y) Y=BUF1(A) ;
CLKBUFX{1,2,3} input(A) output(Y) Y=BUF1(A) ;
@ -364,7 +326,7 @@ It defines all cells except: negative-edge flip-flops, tri-state, latches, clock @@ -364,7 +326,7 @@ It defines all cells except: negative-edge flip-flops, tri-state, latches, clock
SAED90 = TechLib(r"""
NBUFFX{2,4,8,16,32}$ input(INP) output(Z) Z=BUF1(INP) ;
AOBUFX{1,2,4}$ input(INP) output(Z) Z=BUF1(INP) ;
DELLN{1,2,3}X2$ input(INP) output(Z) Z=BUF1(INP) ;
DELLN{1,2,3}X2$ input(INP) output(Z)Z=BUF1(INP) ;
INVX{0,1,2,4,8,16,32}$ input(INP) output(ZN) ZN=INV1(INP) ;
AOINVX{1,2,4}$ input(INP) output(ZN) ZN=INV1(INP) ;

13
src/kyupy/verilog.py

@ -10,7 +10,7 @@ from lark import Lark, Transformer, Tree @@ -10,7 +10,7 @@ from lark import Lark, Transformer, Tree
from . import log, readtext
from .circuit import Circuit, Node, Line
from .techlib import KYUPY
from .techlib import NANGATE
Instantiation = namedtuple('Instantiation', ['type', 'name', 'pins'])
@ -35,7 +35,7 @@ class SignalDeclaration: @@ -35,7 +35,7 @@ class SignalDeclaration:
class VerilogTransformer(Transformer):
def __init__(self, branchforks=False, tlib=KYUPY):
def __init__(self, branchforks=False, tlib=NANGATE):
super().__init__()
self.branchforks = branchforks
self.tlib = tlib
@ -96,7 +96,8 @@ class VerilogTransformer(Transformer): @@ -96,7 +96,8 @@ class VerilogTransformer(Transformer):
sel = args[0]
ctrue = args[1]
cfalse = args[2]
log.warn(f"FIXME: not implemented: ternary if {args[0]} {args[1]}")
print(f"got ternary if {args[0]} {args[1]}")
return args[1]
def declaration(self, kind, args):
@ -258,7 +259,7 @@ GRAMMAR = r""" @@ -258,7 +259,7 @@ GRAMMAR = r"""
"""
def parse(text, tlib=KYUPY, branchforks=False) -> Circuit:
def parse(text, tlib=NANGATE, branchforks=False):
"""Parses the given ``text`` as Verilog code.
:param text: A string with Verilog code.
@ -269,10 +270,10 @@ def parse(text, tlib=KYUPY, branchforks=False) -> Circuit: @@ -269,10 +270,10 @@ def parse(text, tlib=KYUPY, branchforks=False) -> Circuit:
(see :py:func:`~kyupy.sdf.DelayFile.interconnects()`).
:return: A :py:class:`~kyupy.circuit.Circuit` object.
"""
return Lark(GRAMMAR, parser="lalr", transformer=VerilogTransformer(branchforks, tlib)).parse(text) # type: ignore
return Lark(GRAMMAR, parser="lalr", transformer=VerilogTransformer(branchforks, tlib)).parse(text)
def load(file, tlib=KYUPY, branchforks=False):
def load(file, tlib=NANGATE, branchforks=False):
"""Parses the contents of ``file`` as Verilog code.
:param file: A file name or a file handle. Files with `.gz`-suffix are decompressed on-the-fly.

2
tests/Makefile

@ -1,2 +0,0 @@ @@ -1,2 +0,0 @@
all:
abc -F map_to_minimal.abc

91
tests/all_kyupy_simprims.minimal.v

@ -1,91 +0,0 @@ @@ -1,91 +0,0 @@
// Benchmark "all_kyupy_primitives" written by ABC on Thu Nov 6 13:13:21 2025
module all_kyupy_primitives (
i0, i1, i2, i3,
\o[0] , \o[1] , \o[2] , \o[3] , \o[4] , \o[5] , \o[6] , \o[7] , \o[8] ,
\o[9] , \o[10] , \o[11] , \o[12] , \o[13] , \o[14] , \o[15] , \o[16] ,
\o[17] , \o[18] , \o[19] , \o[20] , \o[21] , \o[22] , \o[23] , \o[24] ,
\o[25] , \o[26] , \o[27] , \o[28] , \o[29] , \o[30] , \o[31] , \o[32] );
input i0, i1, i2, i3;
output \o[0] , \o[1] , \o[2] , \o[3] , \o[4] , \o[5] , \o[6] , \o[7] ,
\o[8] , \o[9] , \o[10] , \o[11] , \o[12] , \o[13] , \o[14] , \o[15] ,
\o[16] , \o[17] , \o[18] , \o[19] , \o[20] , \o[21] , \o[22] , \o[23] ,
\o[24] , \o[25] , \o[26] , \o[27] , \o[28] , \o[29] , \o[30] , \o[31] ,
\o[32] ;
wire new_n45, new_n48, new_n51, new_n54, new_n55, new_n56, new_n57,
new_n60, new_n61, new_n62, new_n63, new_n64, new_n65, new_n66, new_n67,
new_n68, new_n69, new_n72, new_n73, new_n74, new_n75, new_n76, new_n77,
new_n78, new_n79, new_n80, new_n81, new_n86, new_n87, new_n91, new_n92,
new_n96, new_n99, new_n102, new_n103, new_n104;
INV1 g00(.i0(i1), .o(\o[1] ));
AND2 g01(.i0(i1), .i1(i0), .o(\o[2] ));
AND2 g02(.i0(\o[2] ), .i1(i2), .o(\o[3] ));
AND2 g03(.i0(\o[3] ), .i1(i3), .o(\o[4] ));
INV1 g04(.i0(\o[2] ), .o(\o[5] ));
INV1 g05(.i0(\o[3] ), .o(\o[6] ));
INV1 g06(.i0(\o[4] ), .o(\o[7] ));
INV1 g07(.i0(i0), .o(new_n45));
AND2 g08(.i0(\o[1] ), .i1(new_n45), .o(\o[11] ));
INV1 g09(.i0(\o[11] ), .o(\o[8] ));
INV1 g10(.i0(i2), .o(new_n48));
AND2 g11(.i0(\o[11] ), .i1(new_n48), .o(\o[12] ));
INV1 g12(.i0(\o[12] ), .o(\o[9] ));
INV1 g13(.i0(i3), .o(new_n51));
AND2 g14(.i0(\o[12] ), .i1(new_n51), .o(\o[13] ));
INV1 g15(.i0(\o[13] ), .o(\o[10] ));
AND2 g16(.i0(\o[1] ), .i1(i0), .o(new_n54));
INV1 g17(.i0(new_n54), .o(new_n55));
AND2 g18(.i0(i1), .i1(new_n45), .o(new_n56));
INV1 g19(.i0(new_n56), .o(new_n57));
AND2 g20(.i0(new_n57), .i1(new_n55), .o(\o[17] ));
INV1 g21(.i0(\o[17] ), .o(\o[14] ));
AND2 g22(.i0(i2), .i1(i1), .o(new_n60));
INV1 g23(.i0(new_n60), .o(new_n61));
AND2 g24(.i0(new_n48), .i1(\o[1] ), .o(new_n62));
INV1 g25(.i0(new_n62), .o(new_n63));
AND2 g26(.i0(new_n63), .i1(new_n61), .o(new_n64));
INV1 g27(.i0(new_n64), .o(new_n65));
AND2 g28(.i0(new_n65), .i1(i0), .o(new_n66));
INV1 g29(.i0(new_n66), .o(new_n67));
AND2 g30(.i0(new_n64), .i1(new_n45), .o(new_n68));
INV1 g31(.i0(new_n68), .o(new_n69));
AND2 g32(.i0(new_n69), .i1(new_n67), .o(\o[18] ));
INV1 g33(.i0(\o[18] ), .o(\o[15] ));
AND2 g34(.i0(i3), .i1(new_n48), .o(new_n72));
INV1 g35(.i0(new_n72), .o(new_n73));
AND2 g36(.i0(new_n51), .i1(i2), .o(new_n74));
INV1 g37(.i0(new_n74), .o(new_n75));
AND2 g38(.i0(new_n75), .i1(new_n73), .o(new_n76));
INV1 g39(.i0(new_n76), .o(new_n77));
AND2 g40(.i0(new_n77), .i1(\o[17] ), .o(new_n78));
INV1 g41(.i0(new_n78), .o(new_n79));
AND2 g42(.i0(new_n76), .i1(\o[14] ), .o(new_n80));
INV1 g43(.i0(new_n80), .o(new_n81));
AND2 g44(.i0(new_n81), .i1(new_n79), .o(\o[19] ));
INV1 g45(.i0(\o[19] ), .o(\o[16] ));
AND2 g46(.i0(\o[5] ), .i1(new_n48), .o(\o[24] ));
INV1 g47(.i0(\o[24] ), .o(\o[20] ));
AND2 g48(.i0(i3), .i1(i2), .o(new_n86));
INV1 g49(.i0(new_n86), .o(new_n87));
AND2 g50(.i0(new_n87), .i1(\o[5] ), .o(\o[25] ));
INV1 g51(.i0(\o[25] ), .o(\o[21] ));
AND2 g52(.i0(\o[8] ), .i1(i2), .o(\o[22] ));
AND2 g53(.i0(new_n51), .i1(new_n48), .o(new_n91));
INV1 g54(.i0(new_n91), .o(new_n92));
AND2 g55(.i0(new_n92), .i1(\o[8] ), .o(\o[23] ));
INV1 g56(.i0(\o[22] ), .o(\o[26] ));
INV1 g57(.i0(\o[23] ), .o(\o[27] ));
AND2 g58(.i0(\o[5] ), .i1(new_n51), .o(new_n96));
AND2 g59(.i0(new_n96), .i1(new_n48), .o(\o[30] ));
INV1 g60(.i0(\o[30] ), .o(\o[28] ));
AND2 g61(.i0(\o[8] ), .i1(i3), .o(new_n99));
AND2 g62(.i0(new_n99), .i1(i2), .o(\o[29] ));
INV1 g63(.i0(\o[29] ), .o(\o[31] ));
AND2 g64(.i0(new_n48), .i1(i0), .o(new_n102));
INV1 g65(.i0(new_n102), .o(new_n103));
AND2 g66(.i0(new_n103), .i1(new_n61), .o(new_n104));
INV1 g67(.i0(new_n104), .o(\o[32] ));
BUF1 g68(.i0(i0), .o(\o[0] ));
endmodule

53
tests/all_kyupy_simprims.v

@ -1,53 +0,0 @@ @@ -1,53 +0,0 @@
module all_kyupy_primitives (i0, i1, i2, i3, o);
input i0;
input i1;
input i2;
input i3;
output [32:0] o;
BUF1 buf1_0 (.i0(i0), .o(o[0]));
INV1 inv1_0 (.i0(i1), .o(o[1]));
AND2 and2_0 (.i0(i0), .i1(i1), .o(o[2]));
AND3 and3_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[3]));
AND4 and4_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[4]));
NAND2 nand2_0 (.i0(i0), .i1(i1), .o(o[5]));
NAND3 nand3_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[6]));
NAND4 nand4_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[7]));
OR2 or2_0 (.i0(i0), .i1(i1), .o(o[8]));
OR3 or3_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[9]));
OR4 or4_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[10]));
NOR2 nor2_0 (.i0(i0), .i1(i1), .o(o[11]));
NOR3 nor3_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[12]));
NOR4 nor4_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[13]));
XOR2 xor2_0 (.i0(i0), .i1(i1), .o(o[14]));
XOR3 xor3_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[15]));
XOR4 xor4_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[16]));
XNOR2 xnor2_0 (.i0(i0), .i1(i1), .o(o[17]));
XNOR3 xnor3_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[18]));
XNOR4 xnor4_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[19]));
AO21 ao21_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[20]));
AO22 ao22_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[21]));
OA21 oa21_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[22]));
OA22 oa22_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[23]));
AOI21 aoi21_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[24]));
AOI22 aoi22_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[25]));
OAI21 oai21_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[26]));
OAI22 oai22_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[27]));
AO211 ao211_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[28]));
OA211 oa211_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[29]));
AOI211 aoi211_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[30]));
OAI211 oai211_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[31]));
MUX21 mux21_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[32]));
endmodule

34
tests/kyupy_simprims.genlib

@ -1,34 +0,0 @@ @@ -1,34 +0,0 @@
# library of all KyuPy simulation primitives defined in kyupy.sim
GATE BUF1 1 o=i0; PIN * NONINV 1 999 1 0 1 0
GATE INV1 1 o=!i0; PIN * INV 1 999 1 0 1 0
GATE AND2 1 o=i0*i1; PIN * NONINV 1 999 1 0 1 0
GATE AND3 1 o=i0*i1*i2; PIN * NONINV 1 999 1 0 1 0
GATE AND4 1 o=i0*i1*i2*i3; PIN * NONINV 1 999 1 0 1 0
GATE NAND2 1 o=!(i0*i1); PIN * INV 1 999 1 0 1 0
GATE NAND3 1 o=!(i0*i1*i2); PIN * INV 1 999 1 0 1 0
GATE NAND4 1 o=!(i0*i1*i2*i3); PIN * INV 1 999 1 0 1 0
GATE OR2 1 o=i0+i1; PIN * NONINV 1 999 1 0 1 0
GATE OR3 1 o=i0+i1+i2; PIN * NONINV 1 999 1 0 1 0
GATE OR4 1 o=i0+i1+i2+i3; PIN * NONINV 1 999 1 0 1 0
GATE NOR2 1 o=!(i0+i1); PIN * INV 1 999 1 0 1 0
GATE NOR3 1 o=!(i0+i1+i2); PIN * INV 1 999 1 0 1 0
GATE NOR4 1 o=!(i0+i1+i2+i3); PIN * INV 1 999 1 0 1 0
GATE XOR2 1 o=i0^i1; PIN * UNKNOWN 1 999 1 0 1 0
GATE XOR3 1 o=i0^i1^i2; PIN * UNKNOWN 1 999 1 0 1 0
GATE XOR4 1 o=i0^i1^i2^i3; PIN * UNKNOWN 1 999 1 0 1 0
GATE XNOR2 1 o=!(i0^i1); PIN * UNKNOWN 1 999 1 0 1 0
GATE XNOR3 1 o=!(i0^i1^i2); PIN * UNKNOWN 1 999 1 0 1 0
GATE XNOR4 1 o=!(i0^i1^i2^i3); PIN * UNKNOWN 1 999 1 0 1 0
GATE AO21 1 o=(i0*i1)+i2; PIN * NONINV 1 999 1 0 1 0
GATE AO22 1 o=(i0*i1)+(i2*i3); PIN * NONINV 1 999 1 0 1 0
GATE OA21 1 o=(i0+i1)*i2; PIN * NONINV 1 999 1 0 1 0
GATE OA22 1 o=(i0+i1)*(i2+i3); PIN * NONINV 1 999 1 0 1 0
GATE AOI21 1 o=!( (i0*i1)+i2 ); PIN * INV 1 999 1 0 1 0
GATE AOI22 1 o=!( (i0*i1)+(i2*i3) ); PIN * INV 1 999 1 0 1 0
GATE OAI21 1 o=!( (i0+i1)*i2 ); PIN * INV 1 999 1 0 1 0
GATE OAI22 1 o=!( (i0+i1)*(i2+i3) ); PIN * INV 1 999 1 0 1 0
GATE AO211 1 o=(i0*i1)+i2+i3; PIN * NONINV 1 999 1 0 1 0
GATE OA211 1 o=(i0+i1)*i2*i3; PIN * NONINV 1 999 1 0 1 0
GATE AOI211 1 o=!( (i0*i1)+i2+i3 ); PIN * INV 1 999 1 0 1 0
GATE OAI211 1 o=!( (i0+i1)*i2*i3 ); PIN * INV 1 999 1 0 1 0
GATE MUX21 1 o=(i0*!i2)+(i1*i2); PIN * UNKNOWN 1 999 1 0 1 0

6
tests/map_to_minimal.abc

@ -1,6 +0,0 @@ @@ -1,6 +0,0 @@
read kyupy_simprims.genlib
read -m all_kyupy_simprims.v
fraig
read minimal.genlib
map
write all_kyupy_simprims.minimal.v

5
tests/minimal.genlib

@ -1,5 +0,0 @@ @@ -1,5 +0,0 @@
# A minimal library for generating logically equivalent circuits with
# minimum number of gate types.
GATE BUF1 1 o=i0; PIN * NONINV 1 999 1 0 1 0
GATE INV1 1 o=!i0; PIN * INV 1 999 1 0 1 0
GATE AND2 1 o=i0*i1; PIN * NONINV 1 999 1 0 1 0

9
tests/test_circuit.py

@ -131,14 +131,7 @@ def test_pickle(mydir): @@ -131,14 +131,7 @@ def test_pickle(mydir):
cs = pickle.dumps(c)
assert cs is not None
c2 = pickle.loads(cs)
assert len(c.nodes) == len(c2.nodes)
def test_equivalency():
c1 = bench.parse('input(a) output(z) z=not(a)')
c2 = bench.parse('input(a) output(z) z=not(a)')
assert c1 != c2
assert c1.lines[0] != c2.lines[0]
assert c1.nodes[0] != c2.nodes[0]
assert c == c2
def test_substitute():

147
tests/test_logic_sim.py

@ -1,117 +1,14 @@ @@ -1,117 +1,14 @@
import numpy as np
from kyupy.logic_sim import LogicSim, LogicSim2V, LogicSim4V, LogicSim6V
from kyupy import bench, logic, sim, verilog
from kyupy.logic_sim import LogicSim, LogicSim6V
from kyupy import bench, logic, sim
from kyupy.logic import mvarray, bparray, bp_to_mv, mv_to_bp
from kyupy.techlib import SAED90
def test_dangling():
c = verilog.parse('''
module test(i, o);
input i; output o; wire dangling;
INVX0 dff1 (.INP(i),.ZN(o));
assign dangling=o;
endmodule
''', tlib=SAED90)
c.resolve_tlib_cells(SAED90)
sim = LogicSim2V(c)
p = sim.allocate()
p[...] = 0
sim.simulate(p)
assert p[1,0] == logic.ONE
def assert_equal_shape_and_contents(actual, desired):
desired = np.array(desired, dtype=np.uint8)
assert actual.shape == desired.shape
np.testing.assert_allclose(actual, desired)
def test_LogicSim2V_minimal():
c = bench.parse('input(i0, i1) output(o0, o1, o2) o0=AND2(i0,i1) o1=INV1(i0) o2=BUF1(i0)')
sim = LogicSim2V(c)
expect = mvarray('00010',
'01010',
'10001',
'11101')
actual = sim.simulate(expect.copy())
np.testing.assert_array_equal(
expect[c.io_locs('o')],
actual[c.io_locs('o')])
def test_LogicSim4V_minimal():
c = bench.parse('input(i0, i1) output(o0, o1, o2) o0=AND2(i0,i1) o1=INV1(i0) o2=BUF1(i0)')
sim = LogicSim4V(c, 16)
expect = mvarray('00010', '01010', '0-010', '0X010',
'10001', '11101', '1-X01', '1XX01',
'-00XX', '-1XXX', '--XXX', '-XXXX',
'X00XX', 'X1XXX', 'X-XXX', 'XXXXX',)
actual = sim.simulate(expect.copy())
np.testing.assert_array_equal(
expect[c.io_locs('o')],
actual[c.io_locs('o')])
def test_LogicSim6V_minimal():
c = bench.parse('input(i0, i1) output(o0, o1, o2) o0=AND2(i0,i1) o1=INV1(i0) o2=BUF1(i0)')
sim = LogicSim6V(c, 36)
expect = mvarray('00010', '01010', '0R010', '0F010', '0P010', '0N010',
'10001', '11101', '1RR01', '1FF01', '1PP01', '1NN01',
'R00FR', 'R1RFR', 'RRRFR', 'RFPFR', 'RPPFR', 'RNRFR',
'F00RF', 'F1FRF', 'FRPRF', 'FFFRF', 'FPPRF', 'FNFRF',
'P00NP', 'P1PNP', 'PRPNP', 'PFPNP', 'PPPNP', 'PNPNP',
'N00PN', 'N1NPN', 'NRRPN', 'NFFPN', 'NPPPN', 'NNNPN')
actual = sim.simulate(expect.copy())
np.testing.assert_array_equal(
expect[c.io_locs('o')],
actual[c.io_locs('o')])
def test_LogicSim2V_simprims(mydir):
c1 = verilog.load(mydir / 'all_kyupy_simprims.v')
c2 = verilog.load(mydir / 'all_kyupy_simprims.minimal.v')
pi_count = sum([len(n.ins)==0 for n in c1.io_nodes])
sim1 = LogicSim2V(c1, sims=2**pi_count)
sim2 = LogicSim2V(c2, sims=2**pi_count)
tests1 = sim1.allocate()
tests1[sim1.pippi_s_locs] = np.fromfunction(lambda x, y: logic.ONE * ((2**x & y) != 0), (pi_count, sim1.sims), dtype=int) # exhaustive test
tests2 = tests1.copy()
sim1.simulate(tests1)
sim2.simulate(tests2)
np.testing.assert_array_equal(
tests1[c1.io_locs('o')],
tests2[c2.io_locs('o')])
def test_LogicSim4V_simprims(mydir):
c1 = verilog.load(mydir / 'all_kyupy_simprims.v')
c2 = verilog.load(mydir / 'all_kyupy_simprims.minimal.v')
pi_count = sum([len(n.ins)==0 for n in c1.io_nodes])
sim1 = LogicSim4V(c1, sims=4**pi_count)
sim2 = LogicSim4V(c2, sims=4**pi_count)
tests1 = sim1.allocate()
tests1[sim1.pippi_s_locs] = np.fromfunction(lambda x, y: (y//(4**x))%4, (pi_count, sim1.sims), dtype=int) # exhaustive test
tests2 = tests1.copy()
sim1.simulate(tests1)
sim2.simulate(tests2)
np.testing.assert_array_equal(
tests1[c1.io_locs('o')],
tests2[c2.io_locs('o')])
def test_LogicSim6V_simprims(mydir):
c1 = verilog.load(mydir / 'all_kyupy_simprims.v')
c2 = verilog.load(mydir / 'all_kyupy_simprims.minimal.v')
pi_count = sum([len(n.ins)==0 for n in c1.io_nodes])
sim1 = LogicSim6V(c1, sims=6**pi_count)
sim2 = LogicSim6V(c2, sims=6**pi_count)
tests1 = sim1.allocate()
tests1[sim1.pippi_s_locs] = logic.mvarray('01RFPN')[np.fromfunction(lambda x, y: (y//(6**x))%6, (pi_count, sim1.sims), dtype=int)] # exhaustive test
tests2 = tests1.copy()
sim1.simulate(tests1)
sim2.simulate(tests2)
for loc1, loc2 in zip(c1.io_locs('o'), c2.io_locs('o')):
n = c1.s_nodes[loc1]
if (tests1[loc1] != tests2[loc2]).any():
print(f'Mismatch at output {n}')
np.testing.assert_array_equal(
tests1[c1.io_locs('o')],
tests2[c2.io_locs('o')])
def test_2v():
c = bench.parse(f'''
input(i3, i2, i1, i0)
@ -178,7 +75,7 @@ def test_2v(): @@ -178,7 +75,7 @@ def test_2v():
def test_4v():
c = bench.parse('input(x, y) output(a, o, n) a=and(x,y) o=or(x,y) n=not(x)')
s = LogicSim(c, 16, m=4)
s = LogicSim(c, 16, m=8) # FIXME: m=4
assert s.s_len == 5
bpa = bparray(
'00---', '01---', '0----', '0X---',
@ -196,44 +93,6 @@ def test_4v(): @@ -196,44 +93,6 @@ def test_4v():
'--0XX', '--X1X', '--XXX', '--XXX',
'--0XX', '--X1X', '--XXX', '--XXX'))
def test_4v_fault():
c = bench.parse('input(x, y) output(a) a=and(x,y)')
s = LogicSim(c, 16, m=4)
assert s.s_len == 3
bpa = bparray(
'00-', '01-', '0--', '0X-',
'10-', '11-', '1--', '1X-',
'-0-', '-1-', '---', '-X-',
'X0-', 'X1-', 'X--', 'XX-')
s.s[0] = bpa
s.s_to_c()
s.c_prop()
s.c_to_s()
mva = bp_to_mv(s.s[1])
assert_equal_shape_and_contents(mva, mvarray(
'--0', '--0', '--0', '--0',
'--0', '--1', '--X', '--X',
'--0', '--X', '--X', '--X',
'--0', '--X', '--X', '--X'))
fault_line = s.circuit.cells['a'].ins[0]
s.s_to_c()
s.c_prop(fault_line=fault_line, fault_model=1)
s.c_to_s()
mva = bp_to_mv(s.s[1])
assert_equal_shape_and_contents(mva, mvarray(
'--0', '--1', '--X', '--X',
'--0', '--1', '--X', '--X',
'--0', '--1', '--X', '--X',
'--0', '--1', '--X', '--X'))
s.s_to_c()
s.c_prop(fault_line=fault_line, fault_model=0)
s.c_to_s()
mva = bp_to_mv(s.s[1])
assert_equal_shape_and_contents(mva, mvarray(
'--0', '--0', '--0', '--0',
'--0', '--0', '--0', '--0',
'--0', '--0', '--0', '--0',
'--0', '--0', '--0', '--0'))
def test_6v():
c = bench.parse('input(x, y) output(a, o, n, xo, no) a=AND2(x,y) o=OR2(x,y) n=INV1(x) xo=XOR2(x,y) no=NOR2(x,y)')

6
tests/test_wave_sim.py

@ -25,11 +25,10 @@ def test_xnor2_delays(): @@ -25,11 +25,10 @@ def test_xnor2_delays():
delays[0, 1, 1, 1] = 0.036 # B fall -> Z fall
simctl_int = np.asarray([0], dtype=np.int32)
simctl_float = np.asarray([1], dtype=np.float32)
def wave_assert(inputs, output):
for i, a in zip(inputs, c.reshape(-1,16)): a[:len(i)] = i
wave_eval_cpu(op, c, c_locs, c_caps, ebuf, 0, delays, simctl_int, simctl_float, 0, 0)
wave_eval_cpu(op, c, c_locs, c_caps, ebuf, 0, delays, simctl_int, 0, 0)
for i, v in enumerate(output): np.testing.assert_allclose(c.reshape(-1,16)[2,i], v)
wave_assert([[TMIN,TMAX],[TMIN,TMAX]], [TMIN,TMAX]) # XNOR(1,1) => 1
@ -64,11 +63,10 @@ def test_nand_delays(): @@ -64,11 +63,10 @@ def test_nand_delays():
delays[0, 3, :, 1] = 0.8
simctl_int = np.asarray([0], dtype=np.int32)
simctl_float = np.asarray([1], dtype=np.float32)
def wave_assert(inputs, output):
for i, a in zip(inputs, c.reshape(-1,16)): a[:len(i)] = i
wave_eval_cpu(op, c, c_locs, c_caps, ebuf, 0, delays, simctl_int, simctl_float, 0, 0)
wave_eval_cpu(op, c, c_locs, c_caps, ebuf, 0, delays, simctl_int, 0, 0)
for i, v in enumerate(output): np.testing.assert_allclose(c.reshape(-1,16)[4,i], v)
wave_assert([[TMAX,TMAX],[TMAX,TMAX],[TMIN,TMAX],[TMIN,TMAX]], [TMIN,TMAX]) # NAND(0,0,1,1) => 1

Loading…
Cancel
Save