blob: 9e43542798f726b2f5e13cb29f29559c343f97f8 [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright 2020 The SkyWater PDK Authors.
#
# Use of this source code is governed by the Apache 2.0
# license that can be found in the LICENSE file or at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
import csv
import itertools
import json
import os
import pprint
import re
import sys
import textwrap
from collections import defaultdict
copyright_header = """\
/**
* Copyright 2020 The SkyWater PDK Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
"""
from skywater_pdk.utils import OrderedEnum
class PortGroup(OrderedEnum):
DATA_IN_CHAR = 10 # A, A1, D, etc
DATA_IN_WORD = 11 # IN
DATA_OUT_CHAR = 20 # X, Y, Q
DATA_OUT_WORD = 21 # OUT
DATA_IO_CHAR = 30 # Inout?
DATA_IO_WORD = 31 # Inout?
DATA_CONTROL = 40 # SET / RESET / etc
SCAN_CHAIN = 44 # SCD, SCE, etc
CLOCK = 50 # Clock
CLOCK_CONTROL = 51 # Clock enable
POWER_CONTROL = 78 # SLEEP
POWER_OTHER = 79 # KAPWR
POWER_POSITIVE = 80 # VPWR
POWER_NEGATIVE = 81 # VGND
@property
def type(self):
pg = self
if pg in (self.DATA_IN_CHAR, self.DATA_IN_WORD,
self.DATA_OUT_CHAR, self.DATA_OUT_WORD,
self.DATA_IO_CHAR, self.DATA_IO_WORD,):
return 'data'
if pg in (self.DATA_CONTROL,):
return 'control'
if pg in (self.CLOCK, self.CLOCK_CONTROL,):
return 'clocks'
if pg in (self.SCAN_CHAIN,):
return 'scanchain'
if pg in (self.POWER_CONTROL, self.POWER_OTHER,
self.POWER_POSITIVE, self.POWER_NEGATIVE,):
return 'power'
assert False, self
@property
def desc(self):
pg = self
if pg in (self.DATA_IN_CHAR, self.DATA_IN_WORD,
self.DATA_OUT_CHAR, self.DATA_OUT_WORD,
self.DATA_IO_CHAR, self.DATA_IO_WORD,):
return 'Data Signals'
if pg in (self.DATA_CONTROL,):
return 'Control Signals'
if pg in (self.SCAN_CHAIN,):
return 'Scan Chain'
if pg in (self.CLOCK, self.CLOCK_CONTROL,):
return 'Clocking'
if pg in (self.POWER_CONTROL, self.POWER_OTHER,
self.POWER_POSITIVE, self.POWER_NEGATIVE,):
return 'Power'
assert False, self
@classmethod
def classify(cls, name, modname=None):
"""
Data Input Signals
++++++++++++++++++
>>> PortGroup.classify('A')
<PortGroup.DATA_IN_CHAR: 10>
>>> PortGroup.classify('A1')
<PortGroup.DATA_IN_CHAR: 10>
>>> PortGroup.classify('A1_N')
<PortGroup.DATA_IN_CHAR: 10>
>>> PortGroup.classify('A_N')
<PortGroup.DATA_IN_CHAR: 10>
>>> PortGroup.classify('B')
<PortGroup.DATA_IN_CHAR: 10>
>>> PortGroup.classify('B1')
<PortGroup.DATA_IN_CHAR: 10>
>>> PortGroup.classify('B1_N')
<PortGroup.DATA_IN_CHAR: 10>
>>> PortGroup.classify('B_N')
<PortGroup.DATA_IN_CHAR: 10>
>>> PortGroup.classify('C')
<PortGroup.DATA_IN_CHAR: 10>
>>> PortGroup.classify('C1')
<PortGroup.DATA_IN_CHAR: 10>
>>> PortGroup.classify('C1_N')
<PortGroup.DATA_IN_CHAR: 10>
>>> PortGroup.classify('C_N')
<PortGroup.DATA_IN_CHAR: 10>
>>> PortGroup.classify('J')
<PortGroup.DATA_IN_CHAR: 10>
>>> PortGroup.classify('K')
<PortGroup.DATA_IN_CHAR: 10>
>>> PortGroup.classify('IN')
<PortGroup.DATA_IN_WORD: 11>
>>> PortGroup.classify('D')
<PortGroup.DATA_IN_CHAR: 10>
>>> PortGroup.classify('D_N')
<PortGroup.DATA_IN_CHAR: 10>
>>> PortGroup.classify('P0')
<PortGroup.DATA_IN_CHAR: 10>
>>> PortGroup.classify('N1')
<PortGroup.DATA_IN_CHAR: 10>
>>> PortGroup.classify('CI')
<PortGroup.DATA_IN_WORD: 11>
>>> PortGroup.classify('CIN')
<PortGroup.DATA_IN_WORD: 11>
SCD
Scan Chain Data
>>> PortGroup.classify('SCD')
<PortGroup.SCAN_CHAIN: 44>
SCE
Scan Chain Enable
>>> PortGroup.classify('SCE')
<PortGroup.SCAN_CHAIN: 44>
Data Output Signals
+++++++++++++++++++
>>> PortGroup.classify('X')
<PortGroup.DATA_OUT_CHAR: 20>
>>> PortGroup.classify('X_N')
<PortGroup.DATA_OUT_CHAR: 20>
>>> PortGroup.classify('Y')
<PortGroup.DATA_OUT_CHAR: 20>
>>> PortGroup.classify('Y_N')
<PortGroup.DATA_OUT_CHAR: 20>
>>> PortGroup.classify('Z')
<PortGroup.DATA_OUT_CHAR: 20>
>>> PortGroup.classify('Z_N')
<PortGroup.DATA_OUT_CHAR: 20>
>>> PortGroup.classify('Q')
<PortGroup.DATA_OUT_CHAR: 20>
>>> PortGroup.classify('OUT')
<PortGroup.DATA_OUT_WORD: 21>
>>> PortGroup.classify('HI')
<PortGroup.DATA_OUT_WORD: 21>
>>> PortGroup.classify('LO')
<PortGroup.DATA_OUT_WORD: 21>
>>> PortGroup.classify('COUT')
<PortGroup.DATA_OUT_WORD: 21>
>>> PortGroup.classify('SUM')
<PortGroup.DATA_OUT_WORD: 21>
Data Control Signals
++++++++++++++++++++
>>> PortGroup.classify('SET')
<PortGroup.DATA_CONTROL: 40>
>>> PortGroup.classify('SET_N')
<PortGroup.DATA_CONTROL: 40>
>>> PortGroup.classify('SET_B')
<PortGroup.DATA_CONTROL: 40>
>>> PortGroup.classify('S')
<PortGroup.DATA_CONTROL: 40>
>>> PortGroup.classify('S_N')
<PortGroup.DATA_CONTROL: 40>
>>> PortGroup.classify('S_B')
<PortGroup.DATA_CONTROL: 40>
>>> PortGroup.classify('R')
<PortGroup.DATA_CONTROL: 40>
>>> PortGroup.classify('R_N')
<PortGroup.DATA_CONTROL: 40>
>>> PortGroup.classify('R_B')
<PortGroup.DATA_CONTROL: 40>
>>> PortGroup.classify('RESET')
<PortGroup.DATA_CONTROL: 40>
>>> PortGroup.classify('RESET_N')
<PortGroup.DATA_CONTROL: 40>
>>> PortGroup.classify('RESET_B')
<PortGroup.DATA_CONTROL: 40>
>>> PortGroup.classify('RESET')
<PortGroup.DATA_CONTROL: 40>
>>> PortGroup.classify('RESET_N')
<PortGroup.DATA_CONTROL: 40>
>>> PortGroup.classify('RESET_B')
<PortGroup.DATA_CONTROL: 40>
Data Enable
>>> PortGroup.classify('DE')
<PortGroup.DATA_CONTROL: 40>
Tristate Enable
>>> PortGroup.classify('TE_B')
<PortGroup.DATA_CONTROL: 40>
Select lines on muxes
>>> PortGroup.classify('S0')
<PortGroup.DATA_CONTROL: 40>
>>> PortGroup.classify('S4')
<PortGroup.DATA_CONTROL: 40>
Clock Signals
+++++++++++++
#>>> PortGroup.classify('C')
#<PortGroup.CLOCK: 50>
#>>> PortGroup.classify('CN')
#<PortGroup.CLOCK: 50>
#>>> PortGroup.classify('C_N')
#<PortGroup.CLOCK: 50>
>>> PortGroup.classify('CLK')
<PortGroup.CLOCK: 50>
>>> PortGroup.classify('GCLK')
<PortGroup.CLOCK: 50>
>>> PortGroup.classify('CLK_N')
<PortGroup.CLOCK: 50>
>>> PortGroup.classify('G')
<PortGroup.CLOCK: 50>
>>> PortGroup.classify('GN')
<PortGroup.CLOCK: 50>
>>> PortGroup.classify('G_N')
<PortGroup.CLOCK: 50>
>>> PortGroup.classify('GATE')
<PortGroup.CLOCK: 50>
>>> PortGroup.classify('GATE_N')
<PortGroup.CLOCK: 50>
Clock Control Signals
+++++++++++++++++++++
>>> PortGroup.classify('CLK_EN')
<PortGroup.CLOCK_CONTROL: 51>
>>> PortGroup.classify('CE')
<PortGroup.CLOCK_CONTROL: 51>
>>> PortGroup.classify('CEN')
<PortGroup.CLOCK_CONTROL: 51>
>>> PortGroup.classify('GATE_EN')
<PortGroup.CLOCK_CONTROL: 51>
>>> PortGroup.classify('GEN')
<PortGroup.CLOCK_CONTROL: 51>
>>> PortGroup.classify('GE')
<PortGroup.CLOCK_CONTROL: 51>
Positive Power Supplies
+++++++++++++++++++++++
>>> PortGroup.classify('VPWR')
<PortGroup.POWER_POSITIVE: 80>
>>> PortGroup.classify('VPB')
<PortGroup.POWER_POSITIVE: 80>
Negative Power Supplies
+++++++++++++++++++++++
>>> PortGroup.classify('VGND')
<PortGroup.POWER_NEGATIVE: 81>
>>> PortGroup.classify('VNB')
<PortGroup.POWER_NEGATIVE: 81>
>>> PortGroup.classify('DEST')
<PortGroup.POWER_OTHER: 79>
Power Control Signals
+++++++++++++++++++++
>>> PortGroup.classify('SLEEP')
<PortGroup.POWER_CONTROL: 78>
>>> PortGroup.classify('SLEEP_B')
<PortGroup.POWER_CONTROL: 78>
>>> PortGroup.classify('SLEEP_N')
<PortGroup.POWER_CONTROL: 78>
Other Power Suppliers
+++++++++++++++++++++
>>> PortGroup.classify('KAPWR')
<PortGroup.POWER_OTHER: 79>
>>> PortGroup.classify('KAGND')
<PortGroup.POWER_OTHER: 79>
>>> PortGroup.classify('DIODE')
<PortGroup.POWER_OTHER: 79>
>>> PortGroup.classify('LOWLVPWRA')
<PortGroup.POWER_OTHER: 79>
"""
# Override for csw module
if modname and 'csw' in modname:
if name == 'VPB':
return cls.POWER_POSITIVE
elif name == 'VNB':
return cls.POWER_NEGATIVE
elif name in ('S', 'D', 'GN', 'GP'):
return cls.POWER_OTHER
name = name.upper()
if re.search('^[A-FJKPN][0-9]?(_[NB])?$', name):
return cls.DATA_IN_CHAR
if re.search('^[XYZQ][0-9]?(_[NB])?$', name):
return cls.DATA_OUT_CHAR
if re.search('^IN[0-9]?_?', name):
return cls.DATA_IN_WORD
if re.search('^CI[0-9]?_?', name):
return cls.DATA_IN_WORD
if re.search('^CIN[0-9]?_?', name):
return cls.DATA_IN_WORD
if re.search('^OUT[0-9]?_?', name):
return cls.DATA_OUT_WORD
if re.search('^COUT[0-9]?_?', name):
return cls.DATA_OUT_WORD
if re.search('^SUM[0-9]?_?', name):
return cls.DATA_OUT_WORD
if name in ('HI', 'LO'):
return cls.DATA_OUT_WORD
if name in ('UDP_IN',):
return cls.DATA_IN_WORD
if name in ('UDP_OUT',):
return cls.DATA_OUT_WORD
if re.search('^[SR][0-9]+$', name):
return cls.DATA_CONTROL
if re.search('^((SET)|(RESET)|[SR])(_[NB])?$', name):
return cls.DATA_CONTROL
if re.search('^((T)|(TE))(_[NB])?$', name):
return cls.DATA_CONTROL
if re.search('^(DE)(_[NB])?$', name):
return cls.DATA_CONTROL
if re.search('^(DATA_EN)$', name):
return cls.DATA_CONTROL
if re.search('^(SET_ASYNC)$', name):
return cls.DATA_CONTROL
if re.search('^((CLK)|(GCLK)|(GATE))_EN$', name):
return cls.CLOCK_CONTROL
if re.search('^[CG]EN$', name):
return cls.CLOCK_CONTROL
if re.search('^[CG]E$', name):
return cls.CLOCK_CONTROL
if re.search('^((CLK)|(GCLK)|(GATE))$', name):
return cls.CLOCK
if re.search('^((CLK)|(GCLK)|(GATE))_N$', name):
return cls.CLOCK
if re.search('^[G]_?N?$', name):
return cls.CLOCK
if re.search('^((CK)|(CP))$', name):
return cls.CLOCK
if re.search('^SLEEP(_[NB])?$', name):
return cls.POWER_CONTROL
if re.search('^VPWR', name):
return cls.POWER_POSITIVE
if re.search('^VPB$', name):
return cls.POWER_POSITIVE
if re.search('^VGND', name):
return cls.POWER_NEGATIVE
if re.search('^VNB$', name):
return cls.POWER_NEGATIVE
if name.startswith('DEST'):
return cls.POWER_OTHER
if 'NOT' in name:
return cls.POWER_OTHER
if 'DIODE' in name:
return cls.POWER_OTHER
if 'PWR' in name:
return cls.POWER_OTHER
if 'GND' in name:
return cls.POWER_OTHER
if 'SHORT' in name:
return cls.POWER_OTHER
if 'WELL' in name:
return cls.POWER_OTHER
if 'MET' in name:
return cls.POWER_OTHER
if re.search('^[M][0-1]$', name):
return cls.POWER_OTHER
if 'SRC' == name:
return cls.DATA_IN_WORD
if 'DST' == name:
return cls.DATA_OUT_WORD
if 'NETA' == name:
return cls.DATA_IN_WORD
if 'NETB' == name:
return cls.DATA_OUT_WORD
if name.startswith('SC'):
return cls.SCAN_CHAIN
if name.startswith('ASYNC'):
return cls.SCAN_CHAIN
def seek_backwards(f):
start_pos = f.tell()
current_pos = f.tell()-1
while True:
f.seek(current_pos)
d = f.read(1)
if d not in '\n ,':
break
current_pos -= 1
f.seek(current_pos+1)
def drive_strengths(basename, cellpath):
drives = []
for f in os.listdir(cellpath):
if not f.endswith('.gds'):
continue
f = f.split('.', 1)[0]
libname, modname = f.split('__', 1)
drive = modname.replace(basename, '')
if not drive:
continue
assert drive.startswith('_'), drive
drives.append(drive[1:])
return drives
def wrap(s, i=''):
return "\n".join(textwrap.wrap(s, initial_indent=' * '+i, subsequent_indent=' * '+i))
warning = """\
WARNING: This file is autogenerated, do not modify directly!
"""
def file_guard(fname):
fname = re.sub('[^A-Za-z_0-9]', '_', fname)
return fname.upper()
def write_verilog_header(f, desc, define_data):
assert f.name, f
guard = file_guard(os.path.basename(f.name))
f.write(copyright_header)
f.write('\n')
f.write(f'`ifndef {guard}\n')
f.write(f'`define {guard}\n')
f.write('\n')
f.write(f"/**\n")
if '\n' in define_data['description']:
f.write(f" * {define_data['name']}:\n")
for l in define_data['description'].splitlines():
f.write(wrap(l.rstrip(), i=' '))
f.write('\n')
else:
f.write(wrap(f"{define_data['name']}: {define_data['description']}"))
f.write('\n')
f.write(f" *\n")
f.write(wrap(desc))
f.write('\n')
f.write(f" *\n")
f.write(wrap(warning))
f.write('\n')
f.write(f" */\n")
f.write('\n')
f.write('`timescale 1ns / 1ps\n')
f.write('\n')
def write_verilog_footer(f):
assert f.name, f
guard = file_guard(os.path.basename(f.name))
f.write('\n')
f.write(f'`endif // {guard}\n')
f.close()
def write_module_header(f, define_data):
f.write('(* blackbox *)\n')
f.write(f"module {define_data['verilog_name']} (")
def write_verilog_parameters(f, define_data):
maxlen = {}
maxlen['pname'] = max([0]+[len(p[0]) for p in define_data['parameters']])
maxlen['ptype'] = max([0]+[len(p[1]) for p in define_data['parameters']])
if maxlen['pname']:
f.write('\n // Parameters\n')
for pname, ptype in define_data['parameters']:
pname = pname.ljust(maxlen['pname'])
if maxlen['ptype']:
ptype = ptype.ljust(maxlen['ptype'])+ ' '
else:
ptype = ''
f.write(f' parameter {ptype}{pname};\n')
def write_verilog_supplies(f, define_data):
maxlen = {}
maxlen['pname'] = max([0]+[len(p[1]) for p in define_data['ports'] if p[0] == 'power'])
maxlen['ptype'] = max([0]+[len(p[3]) for p in define_data['ports'] if p[0] == 'power'])
if maxlen['pname']:
f.write('\n // Voltage supply signals\n')
for pclass, pname, pdir, ptype in define_data['ports']:
if pclass != 'power':
continue
assert ptype, (pclass, pname, pdir, ptype)
pname = pname.ljust(maxlen['pname'])
ptype = ptype.ljust(maxlen['ptype'])
f.write(f' {ptype} {pname};\n')
f.write('\n')
def write_verilog_ports(f, ports):
maxlen = {}
maxlen['pname'] = max([0]+[len(p[1]) for p in ports])
maxlen['pdir'] = max([0]+[len(p[2]) for p in ports])
maxlen['ptype'] = max([0]+[len(p[3]) for p in ports if p[0] != 'power'])
for pclass, pname, pdir, ptype in ports:
pname = pname.ljust(maxlen['pname'])
f.write(f"\n {pname},")
seek_backwards(f)
if ports:
f.write("\n")
f.write(");\n")
for pclass, pname, pdir, ptype in ports:
pname = pname.ljust(maxlen['pname'])
if maxlen['pdir']:
pdir = pdir.ljust(maxlen['pdir'])+' '
else:
pdir = ''
if pclass == 'power':
ptype = ''
if maxlen['ptype']:
ptype = ptype.ljust(maxlen['ptype'])+ ' '
else:
ptype = ''
f.write(f"\n {pdir}{ptype}{pname};")
seek_backwards(f)
if ports:
f.write("\n")
def write_verilog_symbol_ports(f, pports):
maxlen = {}
maxlen['pname'] = max([0]+[len(p[2]) for p in pports if p])
maxlen['pdir'] = max([0]+[len(p[3]) for p in pports if p])
maxlen['ptype'] = max([0]+[len(p[4]) for p in pports if p and p[1] != 'power'])
for i, p in enumerate(pports):
if not p:
np = pports[i+1]
if i > 0:
f.write('\n')
f.write('\n //# {{'+np[0].type+'|'+np[0].desc+'}}')
continue
pg, pclass, pname, pdir, ptype = p
if pclass == 'power':
ptype = ''
pname = pname.ljust(maxlen['pname'])
if maxlen['pdir']:
pdir = pdir.ljust(maxlen['pdir'])+' '
else:
pdir = ''
if maxlen['ptype']:
ptype = ptype.ljust(maxlen['ptype'])+ ' '
else:
ptype = ''
f.write(f"\n {pdir}{ptype}{pname},")
seek_backwards(f)
if pports:
f.write("\n")
f.write(");\n")
def nonpp_ports(define_data):
ports = []
for pclass, pname, pdir, ptype in define_data['ports']:
if pclass != 'signal':
continue
ports.append((pclass, pname, pdir, ptype))
return ports
def pp_ports(define_data):
ports = []
for pclass, pname, pdir, ptype in define_data['ports']:
if pclass == 'power':
ptype = ''
ports.append((pclass, pname, pdir, ptype))
return ports
def outfile(cellpath, define_data, ftype='', extra='', exists=False):
fname = define_data['name'].lower().replace('$', '_')
if ftype:
ftype = '.'+ftype
outpath = os.path.join(cellpath, f'{define_data["file_prefix"]}{extra}{ftype}.v')
if exists is None:
pass
elif not exists:
#assert not os.path.exists(outpath), "Refusing to overwrite existing file:"+outpath
print("Creating", outpath)
elif exists:
assert os.path.exists(outpath), "Missing required:"+outpath
return outpath
def write_blackbox(cellpath, define_data):
outpath = outfile(cellpath, define_data, 'blackbox')
with open(outpath, "w+") as f:
write_verilog_header(
f,
"Verilog stub definition (black box without power pins).",
define_data)
write_module_header(f, define_data)
write_verilog_ports(f, nonpp_ports(define_data))
write_verilog_parameters(f, define_data)
write_verilog_supplies(f, define_data)
f.write('endmodule\n')
write_verilog_footer(f)
def write_blackbox_pp(cellpath, define_data):
if define_data['type'] == 'cell':
ofile = 'pp.blackbox'
else:
ofile = 'blackbox'
outpath = outfile(cellpath, define_data, ofile)
with open(outpath, "w+") as f:
write_verilog_header(
f,
"Verilog stub definition (black box with power pins).",
define_data)
write_module_header(f, define_data)
write_verilog_ports(f, pp_ports(define_data))
write_verilog_parameters(f, define_data)
f.write('endmodule\n')
write_verilog_footer(f)
def group_ports_for_symbol(define_data, only):
ports = []
for pclass, pname, pdir, ptype in define_data['ports']:
if pclass not in only:
continue
pg = PortGroup.classify(pname, define_data['name'])
ports.append((pg, pclass, pname, pdir, ptype))
ports.sort()
pports = [None,]
while len(ports) > 0:
assert ports[0][0], ports[0]
if pports[-1] and pports[-1][0].type != ports[0][0].type:
pports.append(None)
pports.append(ports.pop(0))
if len(pports) == 1:
return []
return pports
def write_primitive(cellpath, define_data):
assert define_data['type'] == 'primitive', define_data
outpath = outfile(cellpath, define_data)
table_datafile = outpath.replace('.v', '.table.tsv')
assert os.path.exists(table_datafile), (table_data, define_data)
table_data = list(csv.reader(open(table_datafile, 'r', newline=''), delimiter='\t'))
with open(outpath, "w+") as f:
write_verilog_header(
f,
"Verilog primitive definition.",
define_data)
f.write(f"primitive {define_data['verilog_name']} (")
write_verilog_ports(f, define_data['ports'])
if table_data[0].count(':') == 2:
_, pname, _, _ = define_data['ports'][0]
assert pname == 'Q', (define_data['ports'], table_data[0])
f.write('\n reg Q;\n')
maxlen = [max(len(r[i]) for r in table_data if len(r) > i) for i in range(0, len(table_data[0]))]
for i in range(0, len(maxlen)):
if table_data[0][i] == ':':
continue
if maxlen[i] == 1:
maxlen[i] += 2
if maxlen[i] == 2:
maxlen[i] += 1
f.write('\n')
f.write(' table\n')
prefix_first = ' // '
prefix_rest = ' '
for i, r in enumerate(table_data):
if i == 0:
f.write(prefix_first)
else:
f.write(prefix_rest)
if len(r) != len(maxlen):
f.write('// ')
f.write(repr(r))
continue
for j, c in enumerate(r[:-1]):
f.write(c.center(maxlen[j]))
f.write(' ')
if i == 0:
assert r[-1] == 'Comments', (i, r)
seek_backwards(f)
f.write('\n')
continue
f.write(' ;')
if r[-1]:
f.write(' // ')
f.write(r[-1])
f.write('\n')
f.write(' endtable\n')
f.write('endprimitive\n')
write_verilog_footer(f)
def write_testbench(cellpath, define_data):
ports_by_class = defaultdict(lambda: list())
ports_by_dir = defaultdict(lambda: list())
for pclass, pname, pdir, ptype in define_data['ports']:
pg = PortGroup.classify(pname, define_data['name'])
ports_by_class[pg].append((pclass, pname, pdir, ptype))
ports_by_dir[pdir].append((pclass, pname, pdir, ptype))
pprint.pprint(ports_by_class)
pprint.pprint(ports_by_dir)
vfile = outfile(cellpath, define_data, exists=True)
outpath = outfile(cellpath, define_data, 'tb')
vfile = os.path.relpath(vfile, os.path.dirname(outpath))
with open(outpath, "w+") as f:
write_verilog_header(
f,
"Autogenerated test bench.",
define_data)
f.write('`include "{}"\n'.format(vfile))
f.write('\n')
f.write('module top();\n')
f.write('\n')
port_args = []
input_port_names = []
for pclass, pname, pdir, ptype in ports_by_dir['input']:
if PortGroup.classify(pname, define_data['name']) == PortGroup.CLOCK:
continue
input_port_names.append(pname)
maxlen = max(len(i) for i in input_port_names)
input_port_names = [n.ljust(maxlen) for n in input_port_names]
f.write(' // Inputs are registered\n')
for pclass, pname, pdir, ptype in ports_by_dir['input']:
if PortGroup.classify(pname, define_data['name']) == PortGroup.CLOCK:
continue
assert pdir == 'input'
f.write(" reg {};\n".format(pname))
port_args.append('.{0}({0})'.format(pname))
f.write('\n');
f.write(' // Outputs are wires\n')
for pclass, pname, pdir, ptype in ports_by_dir['output']:
assert pdir == 'output'
f.write(" wire {};\n".format(pname))
port_args.append('.{0}({0})'.format(pname))
f.write('\n');
f.write("""\
initial
begin
// Initial state is x for all inputs.
""")
indent = " "
for n in sorted(input_port_names):
f.write(indent+"{0} = 1'bX;\n".format(n))
f.write("\n")
DELTA = 20
i = 0
# Set all the inputs to 0, one at a time
# x -> 0
for n in sorted(input_port_names):
i += DELTA
f.write(indent+"#{0:<4d} {1} = 1'b0;\n".format(i, n))
# Set all the inputs to 1, one at a time
# 0 -> 1
for n in sorted(input_port_names):
i += DELTA
f.write(indent+"#{0:<4d} {1} = 1'b1;\n".format(i, n))
# Set all the inputs to zero, one at a time
# 1 -> 0
for n in sorted(input_port_names):
i += DELTA
f.write(indent+"#{0:<4d} {1} = 1'b0;\n".format(i, n))
# Set all the inputs to input, one at a time
# 0 -> 1
for n in reversed(sorted(input_port_names)):
i += DELTA
f.write(indent+"#{0:<4d} {1} = 1'b1;\n".format(i, n))
# Set all the inputs to x, one at a time
# 1 -> 0
for n in reversed(sorted(input_port_names)):
i += DELTA
f.write(indent+"#{0:<4d} {1} = 1'bx;\n".format(i, n))
f.write("""\
end
""")
if PortGroup.CLOCK in ports_by_class:
assert len(ports_by_class[PortGroup.CLOCK]) == 1, ports
clk_port = ports_by_class[PortGroup.CLOCK][0]
clk_class, clk_name, clk_dir, clk_type = clk_port
assert clk_class == 'signal', clk_port
assert clk_dir == 'input', clk_port
assert clk_type == '', clk_port
port_args.append('.{0}({0})'.format(clk_name))
f.write("""\
// Create a clock
reg {0};
initial
begin
{0} = 1'b0;
end
always
begin
#{1} {0} = ~{0};
end
""".format(clk_name, DELTA//4))
f.write("""\
{} dut ({args});
""".format(define_data['verilog_name'], args=", ".join(port_args)))
f.write('endmodule\n')
write_verilog_footer(f)
pass
def write_symbol(cellpath, define_data):
outpath = outfile(cellpath, define_data, 'symbol')
# Group the ports to make a nice symbol
pports = group_ports_for_symbol(define_data, ['signal'])
with open(outpath, "w+") as f:
write_verilog_header(
f,
"Verilog stub (without power pins) for graphical symbol definition generation.",
define_data)
write_module_header(f, define_data)
write_verilog_symbol_ports(f, pports)
write_verilog_parameters(f, define_data)
write_verilog_supplies(f, define_data)
f.write('endmodule\n')
write_verilog_footer(f)
def write_symbol_pp(cellpath, define_data):
if define_data['type'] == 'cell':
ofile = 'pp.symbol'
else:
ofile = 'symbol'
outpath = outfile(cellpath, define_data, ofile)
# Group the ports to make a nice symbol
pports = group_ports_for_symbol(define_data, ['signal', 'power'])
with open(outpath, "w+") as f:
write_verilog_header(
f,
"Verilog stub (with power pins) for graphical symbol definition generation.",
define_data)
write_module_header(f, define_data)
write_verilog_symbol_ports(f, pports)
write_verilog_parameters(f, define_data)
f.write('endmodule\n')
write_verilog_footer(f)
def write_verilog_wrapper(f, extra, supplies, ports, define_data):
f.write('\n')
f.write('`celldefine\n')
f.write(f"module {define_data['verilog_name']}{extra} (")
write_verilog_ports(f, pp_ports(define_data))
write_verilog_parameters(f, define_data)
if supplies:
write_verilog_supplies(f, define_data)
param_str = ''
if define_data['parameters']:
param_str += '#('
param_str += " ".join('.{0}({0})'.format(p[0]) for p in define_data['parameters'])
param_str += ') '
ports_str = ''
if ports:
ports_str += ",\n ".join('.{0}({0})'.format(p[1]) for p in ports);
f.write(f" {define_data['verilog_name']} cell ")
f.write(param_str)
f.write('(\n ')
f.write(ports_str)
seek_backwards(f)
if ports:
f.write("\n );\n")
else:
f.write(");\n")
f.write('\n')
f.write('endmodule\n')
f.write('`endcelldefine\n')
f.write('\n')
def write_drive_wrapper(drive, cellpath, define_data):
outpath = os.path.join(cellpath, f'{define_data["file_prefix"]}_{drive}.v')
#assert not os.path.exists(outpath), outpath
print("Creating", outpath)
with open(outpath, "w+") as f:
write_verilog_header(
f,
f"Verilog wrapper for {define_data['name']} drive {drive}.",
define_data)
f.write(f'`include "{define_data["file_prefix"]}.v"\n')
f.write('\n')
f.write('`ifdef USE_POWER_PINS\n')
f.write('/*********************************************************/\n')
write_verilog_wrapper(f, '_'+drive, False, pp_ports(define_data), define_data)
f.write('/*********************************************************/\n')
f.write('`else // If not USE_POWER_PINS\n')
f.write('/*********************************************************/\n')
write_verilog_wrapper(f, '_'+drive, True, nonpp_ports(define_data), define_data)
f.write('/*********************************************************/\n')
f.write('`endif // USE_POWER_PINS\n')
write_verilog_footer(f)
def process(cellpath):
print()
print(cellpath)
define_json = os.path.join(cellpath, 'definition.json')
if not os.path.exists(define_json):
print("No definition.json in", cellpath)
return
assert os.path.exists(define_json), define_json
define_data = json.load(open(define_json))
if define_data['type'] == 'cell':
write_blackbox(cellpath, define_data)
write_blackbox_pp(cellpath, define_data)
if define_data['type'] == 'cell':
write_symbol(cellpath, define_data)
write_symbol_pp(cellpath, define_data)
if define_data['type'] == 'cell':
for d in drive_strengths(define_data['name'], cellpath):
write_drive_wrapper(d, cellpath, define_data)
if define_data['type'] == 'primitive':
write_primitive(cellpath, define_data)
write_testbench(cellpath, define_data)
return
def main(args):
for a in args:
process(a)
if __name__ == "__main__":
import doctest
fails, _ = doctest.testmod()
if fails>0:
sys.exit()
sys.exit(main(sys.argv[1:]))