Compare commits
9
Commits
c5bdeb4a68
...
d3d06722c1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3d06722c1 | ||
|
|
1f984c9bd9 | ||
|
|
0c8a2b7746 | ||
|
|
f66953f2d3 | ||
|
|
7203145a1d | ||
|
|
32e0766d41 | ||
|
|
9570161744 | ||
|
|
a594e2fb9a | ||
|
|
1bc9dc0e9d |
No files matched your search
@@ -5,6 +5,10 @@ Historically it was first used in the
|
||||
`ISCAS89 benchmark set <https://people.engr.ncsu.edu/brglez/CBL/benchmarks/ISCAS89/>`_.
|
||||
Besides loading these benchmarks, this module is also useful for easily constructing simple circuits:
|
||||
``c = bench.parse('input(x, y) output(a, o, n) a=and(x,y) o=or(x,y) n=not(x)')``.
|
||||
|
||||
This parser automatically translates gate to simulation primitives supported by kyupy.
|
||||
In most cases this means just adding the number of inputs to the name: e.g. NAND -> NAND2, OR -> OR4, INV -> INV1.
|
||||
Since simulators only support at most four-input-gates, gates with more than four inputs are implemented as tree.
|
||||
"""
|
||||
|
||||
from lark import Lark, Transformer
|
||||
|
||||
+15
-11
@@ -614,20 +614,24 @@ class Circuit:
|
||||
if marks[n]:
|
||||
yield n
|
||||
|
||||
def fanout(self, origin_nodes):
|
||||
def fanout(self, origin_nodes: list[Node], node_filter = lambda n: 'dff' not in n.name.lower()):
|
||||
"""Generator function to iterate over the fan-out cone of a given list of origin nodes.
|
||||
|
||||
Nodes are yielded in topological order.
|
||||
origin_nodes are yielded first, followed by nodes driven by them in a breadth-first manner.
|
||||
The search stops at nodes for which node_filter returns False.
|
||||
Only origin_nodes and nodes for which node_filter returned True are yielded.
|
||||
By default, search stops at flip-flops.
|
||||
"""
|
||||
marks = [False] * len(self.nodes)
|
||||
for n in origin_nodes:
|
||||
marks[n] = True
|
||||
for n in self.topological_order():
|
||||
if not marks[n]:
|
||||
for line in n.ins.without_nones():
|
||||
marks[n] |= marks[line.driver]
|
||||
if marks[n]:
|
||||
yield n
|
||||
queue = deque(origin_nodes)
|
||||
yielded = set()
|
||||
while len(queue) > 0:
|
||||
n = queue.popleft()
|
||||
for line in n.outs.without_nones():
|
||||
succ = line.reader
|
||||
if succ not in yielded and node_filter(succ):
|
||||
yielded.add(succ)
|
||||
queue.append(succ)
|
||||
yield n
|
||||
|
||||
def fanout_free_regions(self):
|
||||
for stem in self.reversed_topological_order():
|
||||
|
||||
+42
-12
@@ -81,6 +81,29 @@ on a signal. ``'N'``, ``'n'``, and ``'v'`` are interpreted as ``NPULSE``.
|
||||
"""
|
||||
|
||||
|
||||
# 0 X - 1 P R F N
|
||||
_mv_xor = np.array([[0,1,1,3,4,5,6,7], # 0
|
||||
[1,1,1,1,1,1,1,1], # X
|
||||
[1,1,1,1,1,1,1,1], # -
|
||||
[3,1,1,0,7,6,5,4], # 1
|
||||
[4,1,1,7,4,5,6,7], # P
|
||||
[5,1,1,6,5,4,7,6], # R
|
||||
[6,1,1,5,6,7,4,5], # F
|
||||
[7,1,1,4,7,6,5,4], # N
|
||||
], dtype=np.uint8)
|
||||
|
||||
# 0 X - 1 P R F N
|
||||
_mv_merge = np.array([[0,0,0,1,1,1,1,1], # 0
|
||||
[0,1,1,3,4,5,6,7], # X
|
||||
[0,1,2,3,4,5,6,7], # -
|
||||
[1,3,3,3,1,1,1,1], # 1
|
||||
[1,4,4,1,4,1,1,1], # P
|
||||
[1,5,5,1,1,5,1,1], # R
|
||||
[1,6,6,1,1,1,6,1], # F
|
||||
[1,7,7,1,1,1,1,7], # N
|
||||
], dtype=np.uint8)
|
||||
|
||||
|
||||
def interpret(value):
|
||||
"""Converts characters, strings, and lists of them to lists of logic constants defined above.
|
||||
|
||||
@@ -191,17 +214,6 @@ def mv_and(x1, x2, out=None):
|
||||
return out
|
||||
|
||||
|
||||
def _mv_xor(out, *ins):
|
||||
any_unknown = (ins[0] == UNKNOWN) | (ins[0] == UNASSIGNED)
|
||||
for inp in ins[1:]: any_unknown |= (inp == UNKNOWN) | (inp == UNASSIGNED)
|
||||
|
||||
out[...] = ZERO
|
||||
for inp in ins:
|
||||
np.bitwise_xor(out, inp & 0b011, out=out)
|
||||
np.bitwise_or(out, inp & 0b100, out=out)
|
||||
np.putmask(out, any_unknown, UNKNOWN)
|
||||
|
||||
|
||||
def mv_xor(x1, x2, out=None):
|
||||
"""A multi-valued XOR operator.
|
||||
|
||||
@@ -211,7 +223,25 @@ def mv_xor(x1, x2, out=None):
|
||||
:return: A multi-valued array with the result.
|
||||
"""
|
||||
out = out or np.empty(np.broadcast(x1, x2).shape, dtype=np.uint8)
|
||||
_mv_xor(out, x1, x2)
|
||||
out[...] = _mv_xor[x1, x2]
|
||||
return out
|
||||
|
||||
|
||||
def mv_merge(x1, x2, out=None):
|
||||
"""A multi-valued merge operator.
|
||||
|
||||
Merging identical values ``a`` and ``a`` yields ``a``.
|
||||
Merging ``X`` and ``-`` yields ``X``.
|
||||
Merging any value ``a`` with ``X`` or ``-`` yields ``a``.
|
||||
Merging any other values ``a != b`` yields ``X``.
|
||||
|
||||
:param x1: A multi-valued array.
|
||||
:param x2: A multi-valued array.
|
||||
:param out: An optional storage destination. If None, a new multi-valued array is returned.
|
||||
:return: A multi-valued array with the result.
|
||||
"""
|
||||
out = out or np.empty(np.broadcast(x1, x2).shape, dtype=np.uint8)
|
||||
out[...] = _mv_merge[x1, x2]
|
||||
return out
|
||||
|
||||
|
||||
|
||||
@@ -492,7 +492,7 @@ class LogicSim4V(sim.SimOps):
|
||||
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)}}}'
|
||||
return f'{{name: "{self.circuit.name}", sims: {self.sims}, s_len: {self.s_len}, 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.
|
||||
|
||||
+6
-1
@@ -191,7 +191,7 @@ class SimOps:
|
||||
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
|
||||
|
||||
level_lines = [n.ins[0] for n in root_nodes]
|
||||
level_lines = [n.ins[0] for n in root_nodes if len(n.ins) > 0 ]
|
||||
# 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
|
||||
@@ -330,11 +330,16 @@ class SimOps:
|
||||
self.prim_counts = dict(d)
|
||||
|
||||
self.pi_s_locs = np.flatnonzero(self.c_locs[self.ppi_offset+np.arange(len(self.circuit.io_nodes))] >= 0)
|
||||
"""Locations in pattern array (axis 0) of all primary inputs (excluding flip-flops)."""
|
||||
self.po_s_locs = np.flatnonzero(self.c_locs[self.ppo_offset+np.arange(len(self.circuit.io_nodes))] >= 0)
|
||||
"""Locations in pattern array (axis 0) of all primary outputs (excluding flip-flops)."""
|
||||
self.ppio_s_locs = np.arange(len(self.circuit.io_nodes), self.s_len)
|
||||
"""Locations in pattern array (axis 0) of all flip-flops. Pseudo-primary inputs (PPI) and pseudo-primary outputs (PPO) share the same location."""
|
||||
|
||||
self.pippi_s_locs = np.concatenate([self.pi_s_locs, self.ppio_s_locs])
|
||||
"""Locations in pattern array (axis 0) of all primary and pseudo-primary inputs (input ports and flip-flops)."""
|
||||
self.poppo_s_locs = np.concatenate([self.po_s_locs, self.ppio_s_locs])
|
||||
"""Locations in pattern array (axis 0) of all primary and pseudo-primary outputs (output ports and flip-flops)."""
|
||||
|
||||
self.pi_c_locs = self.c_locs[self.ppi_offset+self.pi_s_locs]
|
||||
self.po_c_locs = self.c_locs[self.ppo_offset+self.po_s_locs]
|
||||
|
||||
@@ -128,7 +128,7 @@ OAI21X1 input(A0,A1,B0) output(Y) Y=OAI21(A0,A1,B0) ;
|
||||
OAI22X1 input(A0,A1,B0,B1) output(Y) Y=OAI22(A0,A1,B0,B1) ;
|
||||
OAI33X1 input(A0,A1,A2,B0,B1,B2) output(Y) AA=OR2(A0,A1) BB=OR2(B0,B1) Y=OAI22(AA,A2,BB,B2) ;
|
||||
ADDFX1 input(A,B,CI) output(CO,S) AB=XOR2(A,B) CO=XOR2(AB,CI) S=AO22(AB,CI,A,B) ;
|
||||
ADDHX1 input(A,B) output(CO,S) CO=XOR2(A,B) S=AND2(A,B) ;
|
||||
ADDHX1 input(A,B) output(CO,S) CO=AND2(A,B) S=XOR2(A,B) ;
|
||||
|
||||
DFFX1 input(CK,D) output(Q,QN) Q=DFF(D,CK) QN=INV1(Q) ;
|
||||
DFFSRX1 input(CK,D,RN,SN) output(Q,QN) DR=AND2(D,RN) SET=INV1(SN) DRS=OR2(DR,SET) Q=DFF(DRS,CK) QN=INV1(Q) ;
|
||||
@@ -172,7 +172,7 @@ XNOR2_X1 input(A1,A2) output(ZN) ZN=XNOR2(A1,A2) ;
|
||||
|
||||
MUX2_X1 input(I0,I1,S) output(Z) Z=MUX21(I0,I1,S) ;
|
||||
|
||||
HA_X1 input(A,B) output(CO,S) CO=XOR2(A,B) S=AND2(A,B) ;
|
||||
HA_X1 input(A,B) output(CO,S) CO=AND2(A,B) S=XOR2(A,B) ;
|
||||
FA_X1 input(A,B,CI) output(CO,S) AB=XOR2(A,B) CO=XOR2(AB,CI) S=AO22(CI,A,B) ;
|
||||
|
||||
AOI21_X{1,2} input(A1,A2,B) output(ZN) ZN=AOI21(A1,A2,B) ;
|
||||
@@ -225,7 +225,7 @@ XNOR2_X{1,2} input(A,B) output(ZN) ZN=XNOR2(A,B) ;
|
||||
|
||||
MUX2_X{1,2} input(A,B,S) output(Z) Z=MUX21(A,B,S) ;
|
||||
|
||||
HA_X1 input(A,B) output(CO,S) CO=XOR2(A,B) S=AND2(A,B) ;
|
||||
HA_X1 input(A,B) output(CO,S) CO=AND2(A,B) S=XOR2(A,B) ;
|
||||
FA_X1 input(A,B,CI) output(CO,S) AB=XOR2(A,B) CO=XOR2(AB,CI) S=AO22(CI,A,B) ;
|
||||
|
||||
AOI21_X{1,2,4} input(A,B1,B2) output(ZN) ZN=AOI21(B1,B2,A) ;
|
||||
@@ -340,7 +340,7 @@ MUX41X{1,2}$ input(A1,A2,A3,A4,S0,S1) output(Y) A=MUX21(A1,A2,S0) B=MUX21(A3,A4,
|
||||
|
||||
DEC24X{1,2}$ input(A0,A1) output(Y0,Y1,Y2,Y3) A0B=INV1(A0) A1B=INV1(A1) Y0=NOR2(A0,A1) Y1=AND(A0,A1B) Y2=AND(A0B,A1) Y3=AND(A0,A1) ;
|
||||
FADDX{1,2}$ input(A,B,CI) output(S,CO) AB=XOR2(A,B) CO=XOR2(AB,CI) S=AO22(AB,CI,A,B) ;
|
||||
HADDX{1,2}$ input(A0,B0) output(SO,C1) C1=XOR2(A0,B0) SO=AND2(A0,B0) ;
|
||||
HADDX{1,2}$ input(A0,B0) output(SO,C1) C1=AND2(A0,B0) SO=XOR2(A0,B0) ;
|
||||
|
||||
{,AO}DFFARX{1,2}$ input(D,CLK,RSTB) output(Q,QN) DR=AND2(D,RSTB) Q=DFF(DR,CLK) QN=INV1(Q) ;
|
||||
DFFASRX{1,2}$ input(D,CLK,RSTB,SETB) output(Q,QN) DR=AND2(D,RSTB) SET=INV1(SETB) DRS=OR2(DR,SET) Q=DFF(DRS,CLK) QN=INV1(Q) ;
|
||||
@@ -430,7 +430,7 @@ MUX41X{1,2}$ input(IN1,IN2,IN3,IN4,S0,S1) output(Q) A=MUX21(IN1,IN2,S0) B=MUX21(
|
||||
|
||||
DEC24X{1,2}$ input(IN1,IN2) output(Q0,Q1,Q2,Q3) IN1B=INV1(IN1) IN2B=INV1(IN2) Q0=NOR2(IN1,IN2) Q1=AND(IN1,IN2B) Q2=AND(IN1B,IN2) Q3=AND(IN1,IN2) ;
|
||||
FADDX{1,2}$ input(A,B,CI) output(S,CO) AB=XOR2(A,B) CO=XOR2(AB,CI) S=AO22(AB,CI,A,B) ;
|
||||
HADDX{1,2}$ input(A0,B0) output(SO,C1) C1=XOR2(A0,B0) SO=AND2(A0,B0) ;
|
||||
HADDX{1,2}$ input(A0,B0) output(SO,C1) C1=AND2(A0,B0) SO=XOR2(A0,B0) ;
|
||||
|
||||
{,AO}DFFARX{1,2}$ input(D,CLK,RSTB) output(Q,QN) DR=AND2(D,RSTB) Q=DFF(DR,CLK) QN=INV1(Q) ;
|
||||
DFFASRX{1,2}$ input(D,CLK,RSTB,SETB) output(Q,QN) DR=AND2(D,RSTB) SET=INV1(SETB) DRS=OR2(DR,SET) Q=DFF(DRS,CLK) QN=INV1(Q) ;
|
||||
|
||||
+13
-6
@@ -41,6 +41,9 @@ class Scope:
|
||||
s = s.parent_scope
|
||||
return '/'.join(reversed(parts))
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.path
|
||||
|
||||
|
||||
class VcdHeader:
|
||||
def __init__(self) -> None:
|
||||
@@ -99,10 +102,12 @@ class VcdVarMap:
|
||||
|
||||
|
||||
class VcdData:
|
||||
def __init__(self, var_map, steps, data):
|
||||
def __init__(self, var_map: VcdVarMap, steps, data):
|
||||
self.var_map = var_map
|
||||
self.steps = steps
|
||||
self.data = data
|
||||
def __repr__(self) -> str:
|
||||
return f'{{variables: {len(self.var_map.var_list)}, bits: {self.data.shape[0]}, steps: {self.data.shape[1]}}}'
|
||||
|
||||
|
||||
class VcdHeaderTransformer(Transformer):
|
||||
@@ -124,9 +129,9 @@ class VcdHeaderTransformer(Transformer):
|
||||
|
||||
def var(self, args):
|
||||
type, width, idcode, reference = args
|
||||
reference = reference.strip()
|
||||
reference = reference.value.strip()
|
||||
width = int(width)
|
||||
self.header.add_var(type, width, idcode, reference)
|
||||
self.header.add_var(type.value, width, idcode.value, reference)
|
||||
|
||||
def start(self, args):
|
||||
return self.header
|
||||
@@ -149,12 +154,13 @@ GRAMMAR = r"""
|
||||
"""
|
||||
|
||||
|
||||
def load(file, var_locs = lambda _: [], step_filter = lambda *_: True):
|
||||
def load(file, var_locs = lambda _: [], step_filter = lambda *_: True, s_len = None) -> VcdData:
|
||||
"""Parses the contents of ``file`` as Verilog Change Dump (VCD).
|
||||
|
||||
:param file: A file name or a file handle. Files with `.gz`-suffix are decompressed on-the-fly.
|
||||
:param var_locs: A callback ``(var) -> list[int]`` mapping each variable to ndarray column indices. Empty list drops the variable.
|
||||
:param step_filter: A callback ``(time, values, var_map) -> bool`` to select which timesteps to include.
|
||||
:param s_len: length of the first axis of returned pattern data.
|
||||
:return: A VcdData object with metadata and an ndarray with all values.
|
||||
"""
|
||||
vcd = readtext(file)
|
||||
@@ -164,10 +170,11 @@ def load(file, var_locs = lambda _: [], step_filter = lambda *_: True):
|
||||
vcd_header : VcdHeader = Lark(GRAMMAR, parser="lalr", lexer='contextual', transformer=VcdHeaderTransformer()).parse(vcd_header_str) # type: ignore
|
||||
vcd_data = vcd[header_size:].splitlines()
|
||||
var_map = VcdVarMap(vcd_header, var_locs)
|
||||
width = s_len if s_len is not None else var_map.total_width
|
||||
|
||||
chunk_size = 10240
|
||||
chunks = []
|
||||
chunk = np.full((chunk_size, var_map.total_width), logic.UNASSIGNED, dtype=np.uint8)
|
||||
chunk = np.full((chunk_size, width), logic.UNASSIGNED, dtype=np.uint8)
|
||||
|
||||
_val_map = {'0': logic.ZERO, '1': logic.ONE,
|
||||
'x': logic.UNKNOWN, 'X': logic.UNKNOWN,
|
||||
@@ -186,7 +193,7 @@ def load(file, var_locs = lambda _: [], step_filter = lambda *_: True):
|
||||
steps.append(current_time)
|
||||
if step_idx >= chunk_size:
|
||||
chunks.append(chunk)
|
||||
chunk = np.empty((chunk_size, var_map.total_width), dtype=np.uint8)
|
||||
chunk = np.empty((chunk_size, width), dtype=np.uint8)
|
||||
chunk[0] = chunks[-1][-1]
|
||||
step_idx = 0
|
||||
else:
|
||||
|
||||
Reference in new issue
Block a user