Compare commits
10
Commits
449bf9a031
...
devel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea2ef1c3ec | ||
|
|
aabef17eea | ||
|
|
f0c10633bf | ||
|
|
ed9b5c6cd6 | ||
|
|
e19463d249 | ||
|
|
7a69ce9016 | ||
|
|
f5af7ec3d9 | ||
|
|
53309f9e59 | ||
|
|
c90bc89522 | ||
|
|
bda89dc1b0 |
No files matched your search
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
version: 2
|
version: 2
|
||||||
|
|
||||||
build:
|
build:
|
||||||
os: "ubuntu-20.04"
|
os: "ubuntu-24.04"
|
||||||
tools:
|
tools:
|
||||||
python: "3.12"
|
python: "3.12"
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import gzip
|
|||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
type NestedNumericList = list['int|NestedNumericList']
|
||||||
|
type NestedStrIntDict = dict[str,'int|NestedStrIntDict']
|
||||||
|
|
||||||
_pop_count_lut = np.asarray([bin(x).count('1') for x in range(256)])
|
_pop_count_lut = np.asarray([bin(x).count('1') for x in range(256)])
|
||||||
|
|
||||||
@@ -111,9 +113,9 @@ class Timer:
|
|||||||
|
|
||||||
|
|
||||||
class Timers:
|
class Timers:
|
||||||
def __init__(self, t={}): self.timers = defaultdict(Timer) | t
|
def __init__(self, t={}): self.timers: dict[str, Timer] = defaultdict(Timer) | t
|
||||||
def __getitem__(self, name): return self.timers[name]
|
def __getitem__(self, name): return self.timers[name]
|
||||||
def __repr__(self): return '{' + ', '.join([f'{k}: {v}' for k, v in self.timers.items()]) + '}'
|
def __repr__(self): return '{' + ', '.join([f'{k}: {v:.2f}' for k, v in self.dict().items()]) + '}'
|
||||||
def __add__(self, t):
|
def __add__(self, t):
|
||||||
tmr = Timers(self.timers)
|
tmr = Timers(self.timers)
|
||||||
for k, v in t.timers.items(): tmr.timers[k] += v
|
for k, v in t.timers.items(): tmr.timers[k] += v
|
||||||
@@ -121,7 +123,7 @@ class Timers:
|
|||||||
def sum(self):
|
def sum(self):
|
||||||
return sum([v.s for v in self.timers.values()])
|
return sum([v.s for v in self.timers.values()])
|
||||||
def dict(self):
|
def dict(self):
|
||||||
return dict([(k, v.s) for k, v in self.timers.items()])
|
return dict(sorted([(k, v.s) for k, v in self.timers.items()], key=lambda item: -item[1]))
|
||||||
|
|
||||||
|
|
||||||
class Log:
|
class Log:
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
"""A parser and dumper for Atalanta's plain-text test pattern format (*.test).
|
||||||
|
|
||||||
|
A *.test file contains one test pattern per line in the form ``<index>: <bits>``,
|
||||||
|
where ``<bits>`` is a string of ``'0'`` and ``'1'`` characters giving the values of
|
||||||
|
the primary inputs (in circuit order). Lines starting with ``'*'`` are comments.
|
||||||
|
|
||||||
|
This parser also accepts files without ``<index>:`` and patterns elements in 8-valued
|
||||||
|
logic such as ``'R'``, ``'F'``, ``'X'``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from . import readtext, logic
|
||||||
|
from .circuit import Circuit
|
||||||
|
|
||||||
|
_pattern_re = re.compile(r'^\s*(?:\d+:\s*)?([01HLRFX-]+)\s*$')
|
||||||
|
|
||||||
|
|
||||||
|
class TestFile:
|
||||||
|
"""Intermediate representation of a *.test pattern file.
|
||||||
|
|
||||||
|
:param text: The contents of a *.test file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, text: str):
|
||||||
|
patterns = []
|
||||||
|
for line in text.splitlines():
|
||||||
|
if (m := _pattern_re.match(line)) is not None:
|
||||||
|
patterns.append(logic.mvarray(m.group(1)))
|
||||||
|
|
||||||
|
self.patterns = np.stack(patterns, axis=-1) if patterns \
|
||||||
|
else np.zeros((0, 0), dtype=np.uint8)
|
||||||
|
"""The parsed patterns as a mvarray (see :py:mod:`~kyupy.logic`).
|
||||||
|
|
||||||
|
The second-to-last axis goes along primary inputs, the last axis goes along patterns.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def tests(self, circuit: Circuit):
|
||||||
|
"""Assembles and returns the test pattern set for the given circuit.
|
||||||
|
|
||||||
|
:param circuit: The circuit to assemble the patterns for. The patterns will follow the
|
||||||
|
:py:attr:`~kyupy.circuit.Circuit.io_nodes` ordering of the circuit.
|
||||||
|
:return: A logic array (see :py:mod:`~kyupy.logic`). The values for primary inputs are
|
||||||
|
filled, all other values are left unassigned.
|
||||||
|
"""
|
||||||
|
pi_locs = [i for i, n in enumerate(circuit.io_nodes) if len(n.ins) == 0]
|
||||||
|
tests = np.full((len(circuit.io_nodes), self.patterns.shape[-1]), logic.UNASSIGNED, dtype=np.uint8)
|
||||||
|
tests[pi_locs] = self.patterns
|
||||||
|
return tests
|
||||||
|
|
||||||
|
|
||||||
|
def parse(text) -> TestFile:
|
||||||
|
"""Parses the given ``text`` and returns a :class:`TestFile` object."""
|
||||||
|
return TestFile(text)
|
||||||
|
|
||||||
|
|
||||||
|
def load(file) -> TestFile:
|
||||||
|
"""Parses the contents of ``file`` and returns a :class:`TestFile` object.
|
||||||
|
|
||||||
|
Files with `.gz`-suffix are decompressed on-the-fly.
|
||||||
|
"""
|
||||||
|
return parse(readtext(file))
|
||||||
+24
-4
@@ -14,7 +14,7 @@ Since simulators only support at most four-input-gates, gates with more than fou
|
|||||||
from lark import Lark, Transformer
|
from lark import Lark, Transformer
|
||||||
|
|
||||||
from .circuit import Circuit, Node, Line
|
from .circuit import Circuit, Node, Line
|
||||||
from . import readtext, batchrange
|
from . import readtext, batchrange, log
|
||||||
|
|
||||||
def treeify(l, max_degree=4):
|
def treeify(l, max_degree=4):
|
||||||
if len(l) <= max_degree: return l
|
if len(l) <= max_degree: return l
|
||||||
@@ -26,12 +26,32 @@ class BenchTransformer(Transformer):
|
|||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.c = Circuit(name)
|
self.c = Circuit(name)
|
||||||
|
self.inputs = set()
|
||||||
|
self.outputs = set()
|
||||||
|
|
||||||
def start(self, _): return self.c
|
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 if name is not None]
|
||||||
|
|
||||||
def interface(self, args): self.c.io_nodes.extend(args[0])
|
def input(self, args):
|
||||||
|
for input in args[0]:
|
||||||
|
if input in self.outputs:
|
||||||
|
new_input = self.c.get_or_add_fork(input.name + '~i')
|
||||||
|
Line(self.c, new_input, input)
|
||||||
|
log.warn(f'input-output passthrough, renaming input: {input.name} -> {new_input.name}')
|
||||||
|
input = new_input
|
||||||
|
self.inputs.add(input)
|
||||||
|
self.c.io_nodes.append(input)
|
||||||
|
|
||||||
|
def output(self, args):
|
||||||
|
for output in args[0]:
|
||||||
|
if output in self.inputs:
|
||||||
|
new_output = self.c.get_or_add_fork(output.name + '~o')
|
||||||
|
Line(self.c, output, new_output)
|
||||||
|
log.warn(f'input-output passthrough, renaming output: {output.name} -> {new_output.name}')
|
||||||
|
output = new_output
|
||||||
|
self.outputs.add(output)
|
||||||
|
self.c.io_nodes.append(output)
|
||||||
|
|
||||||
def _cell_tree_inner(self, name, kind, inner_kind, drivers):
|
def _cell_tree_inner(self, name, kind, inner_kind, drivers):
|
||||||
cell = Node(self.c, name, f'{kind}{len(drivers)}')
|
cell = Node(self.c, name, f'{kind}{len(drivers)}')
|
||||||
@@ -69,8 +89,8 @@ class BenchTransformer(Transformer):
|
|||||||
GRAMMAR = r"""
|
GRAMMAR = r"""
|
||||||
start: (statement)*
|
start: (statement)*
|
||||||
statement: input | output | assignment
|
statement: input | output | assignment
|
||||||
input: ("INPUT" | "input") parameters -> interface
|
input: ("INPUT" | "input") parameters
|
||||||
output: ("OUTPUT" | "output") parameters -> interface
|
output: ("OUTPUT" | "output") parameters
|
||||||
assignment: NAME "=" NAME parameters
|
assignment: NAME "=" NAME parameters
|
||||||
parameters: "(" [ NAME ( "," NAME )* ] ")"
|
parameters: "(" [ NAME ( "," NAME )* ] ")"
|
||||||
NAME: /[-_a-z0-9]+/i
|
NAME: /[-_a-z0-9]+/i
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ import re
|
|||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
type NestedNumericList = list['int|NestedNumericList']
|
from . import NestedNumericList, NestedStrIntDict
|
||||||
type NestedStrIntDict = dict[str,'int|NestedStrIntDict']
|
|
||||||
|
|
||||||
class GrowingList[T](list[T]):
|
class GrowingList[T](list[T]):
|
||||||
def __setitem__(self, index, value):
|
def __setitem__(self, index, value):
|
||||||
|
|||||||
+19
-10
@@ -6,7 +6,7 @@ as an intermediate representation (:class:`DefFile` object).
|
|||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from lark import Lark, Transformer, Tree
|
from lark import Lark, Transformer
|
||||||
|
|
||||||
from kyupy import readtext
|
from kyupy import readtext
|
||||||
|
|
||||||
@@ -85,6 +85,8 @@ class DefFile:
|
|||||||
self.pins = {}
|
self.pins = {}
|
||||||
self.specialnets = {}
|
self.specialnets = {}
|
||||||
self.nets = {}
|
self.nets = {}
|
||||||
|
self.design = ""
|
||||||
|
self.diearea = 0
|
||||||
|
|
||||||
|
|
||||||
class DefTransformer(Transformer):
|
class DefTransformer(Transformer):
|
||||||
@@ -140,9 +142,11 @@ class DefTransformer(Transformer):
|
|||||||
def comp_stmt(self, args):
|
def comp_stmt(self, args):
|
||||||
name = args[0].value
|
name = args[0].value
|
||||||
kind = args[1].value
|
kind = args[1].value
|
||||||
point = args[2]
|
for aidx in range(2, len(args)):
|
||||||
orientation = args[3].value
|
if args[aidx] in ('PLACED', 'FIXED'):
|
||||||
self.def_file.components[name] = (kind, point, orientation)
|
point = args[aidx+1]
|
||||||
|
orientation = args[aidx+2].value
|
||||||
|
self.def_file.components[name] = (kind, point, orientation)
|
||||||
|
|
||||||
def pins_stmt(self, args):
|
def pins_stmt(self, args):
|
||||||
pin = DefPin(args[0].value)
|
pin = DefPin(args[0].value)
|
||||||
@@ -207,6 +211,7 @@ GRAMMAR = r"""
|
|||||||
| /DIEAREA/ point+ ";"
|
| /DIEAREA/ point+ ";"
|
||||||
| /ROW/ ID ID NUMBER NUMBER ID do_step ";"
|
| /ROW/ ID ID NUMBER NUMBER ID do_step ";"
|
||||||
| /TRACKS/ /[XY]/ NUMBER "DO" NUMBER "STEP" NUMBER "LAYER" ID ";"
|
| /TRACKS/ /[XY]/ NUMBER "DO" NUMBER "STEP" NUMBER "LAYER" ID ";"
|
||||||
|
| /GCELLGRID/ /[XY]/ NUMBER "DO" NUMBER "STEP" NUMBER ";"
|
||||||
| propdef | vias | nondef | comp | pins | pinprop | spnets | nets
|
| propdef | vias | nondef | comp | pins | pinprop | spnets | nets
|
||||||
|
|
||||||
propdef: "PROPERTYDEFINITIONS" propdef_stmt* "END" "PROPERTYDEFINITIONS"
|
propdef: "PROPERTYDEFINITIONS" propdef_stmt* "END" "PROPERTYDEFINITIONS"
|
||||||
@@ -228,7 +233,9 @@ GRAMMAR = r"""
|
|||||||
| "+" /VIA/ ID )* ";"
|
| "+" /VIA/ ID )* ";"
|
||||||
|
|
||||||
comp: "COMPONENTS" NUMBER ";" comp_stmt* "END" "COMPONENTS"
|
comp: "COMPONENTS" NUMBER ";" comp_stmt* "END" "COMPONENTS"
|
||||||
comp_stmt: "-" ID ID "+" "PLACED" point ID ";"
|
comp_stmt: "-" ID ID ( "+" /PLACED/ point ID
|
||||||
|
| "+" /FIXED/ point ID
|
||||||
|
| "+" /SOURCE/ ID )* ";"
|
||||||
|
|
||||||
pins: "PINS" NUMBER ";" pins_stmt* "END" "PINS"
|
pins: "PINS" NUMBER ";" pins_stmt* "END" "PINS"
|
||||||
pins_stmt: "-" ID pins_opt* ";"
|
pins_stmt: "-" ID pins_opt* ";"
|
||||||
@@ -239,6 +246,8 @@ GRAMMAR = r"""
|
|||||||
| "+" /PORT/
|
| "+" /PORT/
|
||||||
| "+" /LAYER/ ID point point
|
| "+" /LAYER/ ID point point
|
||||||
| "+" /PLACED/ point ID
|
| "+" /PLACED/ point ID
|
||||||
|
| "+" /FIXED/ point ID
|
||||||
|
|
||||||
|
|
||||||
pinprop: "PINPROPERTIES" NUMBER ";" pinprop_stmt* "END" "PINPROPERTIES"
|
pinprop: "PINPROPERTIES" NUMBER ";" pinprop_stmt* "END" "PINPROPERTIES"
|
||||||
pinprop_stmt: "-" "PIN" ID "+" "PROPERTY" ID STRING ";"
|
pinprop_stmt: "-" "PIN" ID "+" "PROPERTY" ID STRING ";"
|
||||||
@@ -266,10 +275,10 @@ GRAMMAR = r"""
|
|||||||
wire: ID wire_opt points
|
wire: ID wire_opt points
|
||||||
wire_opt: ( "TAPER" | "TAPERRULE" ID )? ("STYLE" ID)?
|
wire_opt: ( "TAPER" | "TAPERRULE" ID )? ("STYLE" ID)?
|
||||||
|
|
||||||
points: point ( point | points_via )+
|
points: point ( point | points_via_or_rect )+
|
||||||
points_via: ID ORIENTATION?
|
points_via_or_rect: ID ( ORIENTATION? | "(" (NUMBER|SIGNED_NUMBER|/\*/) (NUMBER|SIGNED_NUMBER|/\*/) (NUMBER|SIGNED_NUMBER|/\*/) (NUMBER|SIGNED_NUMBER|/\*/) ")" )
|
||||||
|
|
||||||
point: "(" (NUMBER|/\*/) (NUMBER|/\*/) NUMBER? ")"
|
point: "(" (NUMBER|SIGNED_NUMBER|/\*/) (NUMBER|SIGNED_NUMBER|/\*/) NUMBER? ")"
|
||||||
|
|
||||||
do_step: "DO" NUMBER "BY" NUMBER "STEP" (NUMBER|SIGNED_NUMBER) (NUMBER|SIGNED_NUMBER)
|
do_step: "DO" NUMBER "BY" NUMBER "STEP" (NUMBER|SIGNED_NUMBER) (NUMBER|SIGNED_NUMBER)
|
||||||
|
|
||||||
@@ -284,9 +293,9 @@ GRAMMAR = r"""
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def parse(text):
|
def parse(text) -> DefFile:
|
||||||
"""Parses the given ``text`` and returns a :class:`DefFile` object."""
|
"""Parses the given ``text`` and returns a :class:`DefFile` object."""
|
||||||
return Lark(GRAMMAR, parser="lalr", transformer=DefTransformer()).parse(text)
|
return Lark(GRAMMAR, parser="lalr", transformer=DefTransformer()).parse(text) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def load(file):
|
def load(file):
|
||||||
|
|||||||
+6
-5
@@ -49,7 +49,7 @@ from collections.abc import Iterable
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from numpy.typing import DTypeLike
|
from numpy.typing import DTypeLike
|
||||||
|
|
||||||
from . import numba, hr_bytes
|
from . import numba, hr_bytes, NestedNumericList
|
||||||
|
|
||||||
|
|
||||||
ZERO = 0b000
|
ZERO = 0b000
|
||||||
@@ -104,7 +104,7 @@ _mv_merge = np.array([[0,0,0,1,1,1,1,1], # 0
|
|||||||
], dtype=np.uint8)
|
], dtype=np.uint8)
|
||||||
|
|
||||||
|
|
||||||
def interpret(value):
|
def interpret(value) -> int|NestedNumericList:
|
||||||
"""Converts characters, strings, and lists of them to lists of logic constants defined above.
|
"""Converts characters, strings, and lists of them to lists of logic constants defined above.
|
||||||
|
|
||||||
:param value: A character (string of length 1), Boolean, Integer, None, or Iterable.
|
:param value: A character (string of length 1), Boolean, Integer, None, or Iterable.
|
||||||
@@ -123,11 +123,12 @@ def interpret(value):
|
|||||||
return UNKNOWN
|
return UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
def mvarray(*a):
|
def mvarray(*a) -> np.ndarray[tuple[int]|tuple[int,int],np.dtype[np.uint8]]:
|
||||||
"""Converts (lists of) Boolean values or strings into a multi-valued array.
|
"""Converts (lists of) Boolean values or strings into a multi-valued array.
|
||||||
|
|
||||||
The given values are interpreted and the axes are arranged as per KyuPy's convention.
|
Iterable over characters, Booleans, Integers (0,1), None are interpreted as a single pattern.
|
||||||
Use this function to convert strings into multi-valued arrays.
|
Multi-character strings are Iterables over characters and are interpreted as a single pattern.
|
||||||
|
Lists of said patterns form a pattern set as ndarray with the axes are arranged as per KyuPy's convention.
|
||||||
"""
|
"""
|
||||||
mva = np.array(interpret(a), dtype=np.uint8)
|
mva = np.array(interpret(a), dtype=np.uint8)
|
||||||
if mva.ndim < 2: return mva
|
if mva.ndim < 2: return mva
|
||||||
|
|||||||
+21
-4
@@ -51,6 +51,7 @@ class LogicSim(sim.SimOps):
|
|||||||
nbytes = cdiv(sims, 8)
|
nbytes = cdiv(sims, 8)
|
||||||
|
|
||||||
self.c = np.zeros((self.c_len, self.mdim, nbytes), dtype=np.uint8)
|
self.c = np.zeros((self.c_len, self.mdim, nbytes), dtype=np.uint8)
|
||||||
|
self.c_dirty = np.full(self.c_len, 1, dtype=np.uint8)
|
||||||
self.s = np.zeros((2, self.s_len, 3, nbytes), dtype=np.uint8)
|
self.s = np.zeros((2, self.s_len, 3, nbytes), dtype=np.uint8)
|
||||||
"""Logic values of the sequential elements (flip-flops) and ports.
|
"""Logic values of the sequential elements (flip-flops) and ports.
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ class LogicSim(sim.SimOps):
|
|||||||
t1 = self.c_locs[self.tmp2_idx]
|
t1 = self.c_locs[self.tmp2_idx]
|
||||||
if self.m == 2:
|
if self.m == 2:
|
||||||
if inject_cb is None:
|
if inject_cb is None:
|
||||||
c_prop_2v_cpu(self.ops, self.c_locs, self.c, int(fault_line), fault_mask, int(fault_model))
|
c_prop_2v_cpu(self.ops, self.c_locs, self.c, self.c_dirty, int(fault_line), fault_mask, int(fault_model))
|
||||||
else:
|
else:
|
||||||
for op, o0l, i0l, i1l, i2l, i3l in self.ops[:,:6]:
|
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)]
|
o0, i0, i1, i2, i3 = [self.c_locs[x] for x in (o0l, i0l, i1l, i2l, i3l)]
|
||||||
@@ -356,6 +357,19 @@ class LogicSim2V(sim.SimOps):
|
|||||||
Storage locations are indirectly addressed.
|
Storage locations are indirectly addressed.
|
||||||
Data for line `l` is in `self.c[self.c_locs[l]]`.
|
Data for line `l` is in `self.c[self.c_locs[l]]`.
|
||||||
"""
|
"""
|
||||||
|
self.c_dirty = np.full(self.c_len, 1, dtype=np.uint8)
|
||||||
|
"""Marker for logic values that have changed recently for use in incremental simulation.
|
||||||
|
|
||||||
|
A node n will be evaluated by `c_prop()` if and only if at least one of its inputs is marked dirty (`c_dirty[c_locs[n.ins[i]]] == 1`).
|
||||||
|
If a node is evaluated, its output is marked dirty.
|
||||||
|
By default, this array is all-1, therefore every node is evaluated every time by `c_prop()`.
|
||||||
|
Enable incremental simulation by setting this array to all-0, change signal values in `c` and set `c_dirty` to 1 for all changed signals.
|
||||||
|
Next call to `c_prop()` will only evaluate nodes that read from the changed signals.
|
||||||
|
To restore the original simulation state, revert the signal value changes in `c`, and call `c_prop()` again.
|
||||||
|
Caveats:
|
||||||
|
- Injected changes that are located downstream of other changes will have no effect.
|
||||||
|
- Only works with `c_reuse=False` (default).
|
||||||
|
"""
|
||||||
self.s_assign = np.zeros((self.s_len, self.sims), dtype=np.uint8)
|
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.
|
"""Logic values assigned to the ports and flip-flops of the circuit.
|
||||||
|
|
||||||
@@ -390,7 +404,7 @@ class LogicSim2V(sim.SimOps):
|
|||||||
fault_mask2 = np.full(self.c.shape[-1], 0, dtype=np.uint8)
|
fault_mask2 = np.full(self.c.shape[-1], 0, dtype=np.uint8)
|
||||||
fault_mask2[:len(fault_mask)] = fault_mask
|
fault_mask2[:len(fault_mask)] = fault_mask
|
||||||
fault_mask = fault_mask2
|
fault_mask = fault_mask2
|
||||||
c_prop_2v_cpu(self.ops, self.c_locs, self.c, int(fault_line), fault_mask, int(fault_model))
|
c_prop_2v_cpu(self.ops, self.c_locs, self.c, self.c_dirty, int(fault_line), fault_mask, int(fault_model))
|
||||||
|
|
||||||
def c_to_s(self):
|
def c_to_s(self):
|
||||||
"""Captures the results of the combinational portion into ``self.s_result``.
|
"""Captures the results of the combinational portion into ``self.s_result``.
|
||||||
@@ -424,9 +438,11 @@ class LogicSim2V(sim.SimOps):
|
|||||||
|
|
||||||
|
|
||||||
@numba.njit
|
@numba.njit
|
||||||
def c_prop_2v_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model):
|
def c_prop_2v_cpu(ops, c_locs, c, c_dirty, fault_line, fault_mask, fault_model):
|
||||||
for op, o0l, i0l, i1l, i2l, i3l in ops[:,:6]:
|
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)]
|
o0, i0, i1, i2, i3 = [c_locs[x] for x in (o0l, i0l, i1l, i2l, i3l)]
|
||||||
|
if fault_line < 0 or o0l != fault_line: # fault injection forces node evaluation
|
||||||
|
if not (c_dirty[i0] | c_dirty[i1] | c_dirty[i2] | c_dirty[i3]): continue
|
||||||
if op == sim.BUF1: c[o0]=c[i0]
|
if op == sim.BUF1: c[o0]=c[i0]
|
||||||
elif op == sim.INV1: c[o0] = ~c[i0]
|
elif op == sim.INV1: c[o0] = ~c[i0]
|
||||||
elif op == sim.AND2: c[o0] = c[i0] & c[i1]
|
elif op == sim.AND2: c[o0] = c[i0] & c[i1]
|
||||||
@@ -466,8 +482,9 @@ def c_prop_2v_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model):
|
|||||||
c[o0] = c[o0] & ~fault_mask
|
c[o0] = c[o0] & ~fault_mask
|
||||||
elif fault_model == 1:
|
elif fault_model == 1:
|
||||||
c[o0] = c[o0] | fault_mask
|
c[o0] = c[o0] | fault_mask
|
||||||
else:
|
elif fault_model == 2:
|
||||||
c[o0] = c[o0] ^ fault_mask
|
c[o0] = c[o0] ^ fault_mask
|
||||||
|
c_dirty[o0] = 1
|
||||||
|
|
||||||
|
|
||||||
class LogicSim4V(sim.SimOps):
|
class LogicSim4V(sim.SimOps):
|
||||||
|
|||||||
+23
-4
@@ -189,10 +189,10 @@ class SimOps:
|
|||||||
levels = []
|
levels = []
|
||||||
|
|
||||||
ppio2idx = dict((n, i) for i, n in enumerate(circuit.s_nodes(KYUPY)))
|
ppio2idx = dict((n, i) for i, n in enumerate(circuit.s_nodes(KYUPY)))
|
||||||
root_nodes = set([n for n in circuit.s_nodes(KYUPY) if len(n.ins) > 0] + [n for n in circuit.nodes if len(n.outs) == 0]) # start from POs, PPOs, and any dangling nodes
|
root_nodes = set([n for n in circuit.nodes if len(n.outs) == 0 or KYUPY.is_dff(n.kind)]) # start from POs (that are unused by circuit itself), 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
|
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
|
||||||
|
|
||||||
level_lines = [n.ins[0] for n in root_nodes if len(n.ins) > 0 ]
|
level_lines = [n.ins[0] for n in sorted(root_nodes, key=lambda n: n.index) if len(n.ins) > 0 ]
|
||||||
# FIXME: Should probably instanciate buffers for PPOs and attach DFF clocks
|
# 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
|
while len(level_lines) > 0: # traverse the circuit level-wise back towards (P)PIs
|
||||||
@@ -201,10 +201,15 @@ class SimOps:
|
|||||||
|
|
||||||
for l in level_lines:
|
for l in level_lines:
|
||||||
n = l.driver
|
n = l.driver
|
||||||
assert len(n.ins) <= 4, f'Too many input pins for node {n}. Map circuit to KYUPY techlib first using resolce_tlib_cells().'
|
assert len(n.ins) <= 4, f'Too many input pins for node {n}. Map circuit to KYUPY techlib first using resolve_tlib_cells().'
|
||||||
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]]
|
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:
|
if n in ppio2idx:
|
||||||
in_idxs[0] = self.ppi_offset + ppio2idx[n]
|
in_idxs[1] = self.zero_idx
|
||||||
|
if 'dff' in n.kind.lower() or in_idxs[0] == self.zero_idx:
|
||||||
|
in_idxs[0] = self.ppi_offset + ppio2idx[n]
|
||||||
|
else: # this is a PO that is also used within the circuit. Treat as normal fork with ref counting. TODO: check strip_forks=True case for this.
|
||||||
|
readers[n.ins[0]] -= 1
|
||||||
|
if readers[n.ins[0]] == 0: prev_level_lines.append(n.ins[0])
|
||||||
if l.driver_pin == 1 and 'dff' in n.kind.lower(): # second output of DFF is inverted
|
if l.driver_pin == 1 and 'dff' in n.kind.lower(): # second output of DFF is inverted
|
||||||
level_ops.append((INV1, l.index, *in_idxs, *a_ctrl[l]))
|
level_ops.append((INV1, l.index, *in_idxs, *a_ctrl[l]))
|
||||||
else:
|
else:
|
||||||
@@ -312,6 +317,11 @@ class SimOps:
|
|||||||
if len(n.ins) > 0:
|
if len(n.ins) > 0:
|
||||||
self.c_locs[self.ppo_offset + i], self.c_caps[self.ppo_offset + i] = self.c_locs[n.ins[0]], self.c_caps[n.ins[0]]
|
self.c_locs[self.ppo_offset + i], self.c_caps[self.ppo_offset + i] = self.c_locs[n.ins[0]], self.c_caps[n.ins[0]]
|
||||||
|
|
||||||
|
# sanity check
|
||||||
|
for oidx, io in zip(*np.nonzero(self.c_locs[self.ops[:, 1:6]] < 0)):
|
||||||
|
log.error(f'unallocated {["o0","i0","i1","i2","i3"][io]} in op {self.ops[oidx]}')
|
||||||
|
assert np.min(self.c_locs[self.ops[:, 1:6]]) >= 0, "Unallocated signal lines."
|
||||||
|
|
||||||
# line use information
|
# line use information
|
||||||
self.line_use_start = np.full(self.c_locs_len, -1, dtype=np.int32)
|
self.line_use_start = np.full(self.c_locs_len, -1, dtype=np.int32)
|
||||||
self.line_use_stop = np.full(self.c_locs_len, len(self.levels), dtype=np.int32)
|
self.line_use_stop = np.full(self.c_locs_len, len(self.levels), dtype=np.int32)
|
||||||
@@ -346,3 +356,12 @@ class SimOps:
|
|||||||
|
|
||||||
self.pippi_c_locs = np.concatenate([self.pi_c_locs, self.ppi_c_locs])
|
self.pippi_c_locs = np.concatenate([self.pi_c_locs, self.ppi_c_locs])
|
||||||
self.poppo_c_locs = np.concatenate([self.po_c_locs, self.ppo_c_locs])
|
self.poppo_c_locs = np.concatenate([self.po_c_locs, self.ppo_c_locs])
|
||||||
|
|
||||||
|
def cone_ops(self, origin_lines: set[int]):
|
||||||
|
active_lines = {l for l in origin_lines}
|
||||||
|
ops = []
|
||||||
|
for kind, out0, in0, in1, in2, in3, *params in self.ops:
|
||||||
|
if in0 in active_lines or in1 in active_lines or in2 in active_lines or in3 in active_lines or out0 in origin_lines:
|
||||||
|
ops.append((kind, out0, in0, in1, in2, in3, *params))
|
||||||
|
active_lines.add(out0)
|
||||||
|
return np.asarray(ops, dtype=np.int32)
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from kyupy import atalanta, logic
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse():
|
||||||
|
text = '\n'.join([
|
||||||
|
'* Test pattern file',
|
||||||
|
'1: 0011',
|
||||||
|
'2: 1100',
|
||||||
|
'1010',
|
||||||
|
])
|
||||||
|
tf = atalanta.parse(text)
|
||||||
|
|
||||||
|
# second-to-last axis = inputs, last axis = patterns
|
||||||
|
assert tf.patterns.shape == (4, 3)
|
||||||
|
assert tf.patterns.dtype == np.uint8
|
||||||
|
assert set(np.unique(tf.patterns)) <= {logic.ZERO, logic.ONE}
|
||||||
|
|
||||||
|
# patterns are stored along the last axis
|
||||||
|
assert list(tf.patterns[:, 0]) == [logic.ZERO, logic.ZERO, logic.ONE, logic.ONE]
|
||||||
|
assert list(tf.patterns[:, 1]) == [logic.ONE, logic.ONE, logic.ZERO, logic.ZERO]
|
||||||
|
assert list(tf.patterns[:, 2]) == [logic.ONE, logic.ZERO, logic.ONE, logic.ZERO]
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty():
|
||||||
|
tf = atalanta.parse('* only comments\n* nothing else\n')
|
||||||
|
assert tf.patterns.shape == (0, 0)
|
||||||
|
assert tf.patterns.dtype == np.uint8
|
||||||
@@ -20,6 +20,18 @@ def test_dangling():
|
|||||||
sim.simulate(p)
|
sim.simulate(p)
|
||||||
assert p[1,0] == logic.ONE
|
assert p[1,0] == logic.ONE
|
||||||
|
|
||||||
|
def test_used_output():
|
||||||
|
c = bench.parse('input(i0) output(o0) output(o1) o0=BUF1(i0) o1=BUF(o0)')
|
||||||
|
sim = LogicSim2V(c)
|
||||||
|
expect = mvarray('000', '111')
|
||||||
|
actual = sim.simulate(expect.copy())
|
||||||
|
np.testing.assert_array_equal(
|
||||||
|
expect[c.io_locs('o0')],
|
||||||
|
actual[c.io_locs('o0')])
|
||||||
|
np.testing.assert_array_equal(
|
||||||
|
expect[c.io_locs('o1')],
|
||||||
|
actual[c.io_locs('o1')])
|
||||||
|
|
||||||
def assert_equal_shape_and_contents(actual, desired):
|
def assert_equal_shape_and_contents(actual, desired):
|
||||||
desired = np.array(desired, dtype=np.uint8)
|
desired = np.array(desired, dtype=np.uint8)
|
||||||
assert actual.shape == desired.shape
|
assert actual.shape == desired.shape
|
||||||
|
|||||||
Reference in new issue
Block a user