mirror of
https://github.com/enjoy-digital/litex.git
synced 2025-01-04 09:52:26 -05:00
9adf3f02f2
it's generally better to have identical code between simulations and synthesis, but here tricks inserted for simulation are clearly expected to be simplified by synthesis tools, so it's better not inserting them.
296 lines
8.9 KiB
Python
296 lines
8.9 KiB
Python
import os, sys
|
|
|
|
from migen.fhdl.std import *
|
|
from migen.fhdl.structure import _Fragment
|
|
from migen.genlib.record import Record
|
|
from migen.genlib.io import CRG
|
|
from migen.fhdl import verilog, edif
|
|
from migen.util.misc import autotype
|
|
|
|
from mibuild import tools
|
|
|
|
class ConstraintError(Exception):
|
|
pass
|
|
|
|
class Pins:
|
|
def __init__(self, *identifiers):
|
|
self.identifiers = []
|
|
for i in identifiers:
|
|
self.identifiers += i.split()
|
|
|
|
class IOStandard:
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
class Drive:
|
|
def __init__(self, strength):
|
|
self.strength = strength
|
|
|
|
class Misc:
|
|
def __init__(self, misc):
|
|
self.misc = misc
|
|
|
|
class Subsignal:
|
|
def __init__(self, name, *constraints):
|
|
self.name = name
|
|
self.constraints = list(constraints)
|
|
|
|
class PlatformInfo:
|
|
def __init__(self, info):
|
|
self.info = info
|
|
|
|
def _lookup(description, name, number):
|
|
for resource in description:
|
|
if resource[0] == name and (number is None or resource[1] == number):
|
|
return resource
|
|
raise ConstraintError("Resource not found: " + name + ":" + str(number))
|
|
|
|
def _resource_type(resource):
|
|
t = None
|
|
for element in resource[2:]:
|
|
if isinstance(element, Pins):
|
|
assert(t is None)
|
|
t = len(element.identifiers)
|
|
elif isinstance(element, Subsignal):
|
|
if t is None:
|
|
t = []
|
|
assert(isinstance(t, list))
|
|
n_bits = None
|
|
for c in element.constraints:
|
|
if isinstance(c, Pins):
|
|
assert(n_bits is None)
|
|
n_bits = len(c.identifiers)
|
|
t.append((element.name, n_bits))
|
|
return t
|
|
|
|
class ConnectorManager:
|
|
def __init__(self, connectors):
|
|
self.connector_table = dict()
|
|
for connector in connectors:
|
|
cit = iter(connector)
|
|
conn_name = next(cit)
|
|
if isinstance(connector[1], str):
|
|
pin_list = []
|
|
for pins in cit:
|
|
pin_list += pins.split()
|
|
pin_list = [None if pin == "None" else pin for pin in pin_list]
|
|
elif isinstance(connector[1], dict):
|
|
pin_list = connector[1]
|
|
else:
|
|
raise ValueError("Unsupported pin list type {} for connector"
|
|
" {}".format(type(connector[1]), conn_name))
|
|
if conn_name in self.connector_table:
|
|
raise ValueError("Connector specified more than once: "+conn_name)
|
|
self.connector_table[conn_name] = pin_list
|
|
|
|
def resolve_identifiers(self, identifiers):
|
|
r = []
|
|
for identifier in identifiers:
|
|
if ":" in identifier:
|
|
conn, pn = identifier.split(":")
|
|
if pn.isdigit():
|
|
pn = int(pn)
|
|
r.append(self.connector_table[conn][pn])
|
|
else:
|
|
r.append(identifier)
|
|
return r
|
|
|
|
def _separate_pins(constraints):
|
|
pins = None
|
|
others = []
|
|
for c in constraints:
|
|
if isinstance(c, Pins):
|
|
assert(pins is None)
|
|
pins = c.identifiers
|
|
else:
|
|
others.append(c)
|
|
return pins, others
|
|
|
|
class ConstraintManager:
|
|
def __init__(self, io, connectors):
|
|
self.available = list(io)
|
|
self.matched = []
|
|
self.platform_commands = []
|
|
self.connector_manager = ConnectorManager(connectors)
|
|
|
|
def add_extension(self, io):
|
|
self.available.extend(io)
|
|
|
|
def request(self, name, number=None):
|
|
resource = _lookup(self.available, name, number)
|
|
rt = _resource_type(resource)
|
|
if isinstance(rt, int):
|
|
obj = Signal(rt, name_override=resource[0])
|
|
else:
|
|
obj = Record(rt, name=resource[0])
|
|
for element in resource[2:]:
|
|
if isinstance(element, PlatformInfo):
|
|
obj.platform_info = element.info
|
|
break
|
|
self.available.remove(resource)
|
|
self.matched.append((resource, obj))
|
|
return obj
|
|
|
|
def lookup_request(self, name, number=None):
|
|
for resource, obj in self.matched:
|
|
if resource[0] == name and (number is None or resource[1] == number):
|
|
return obj
|
|
raise ConstraintError("Resource not found: " + name + ":" + str(number))
|
|
|
|
def add_platform_command(self, command, **signals):
|
|
self.platform_commands.append((command, signals))
|
|
|
|
def get_io_signals(self):
|
|
r = set()
|
|
for resource, obj in self.matched:
|
|
if isinstance(obj, Signal):
|
|
r.add(obj)
|
|
else:
|
|
r.update(obj.flatten())
|
|
return r
|
|
|
|
def get_sig_constraints(self):
|
|
r = []
|
|
for resource, obj in self.matched:
|
|
name = resource[0]
|
|
number = resource[1]
|
|
has_subsignals = False
|
|
top_constraints = []
|
|
for element in resource[2:]:
|
|
if isinstance(element, Subsignal):
|
|
has_subsignals = True
|
|
else:
|
|
top_constraints.append(element)
|
|
if has_subsignals:
|
|
for element in resource[2:]:
|
|
if isinstance(element, Subsignal):
|
|
sig = getattr(obj, element.name)
|
|
pins, others = _separate_pins(top_constraints + element.constraints)
|
|
pins = self.connector_manager.resolve_identifiers(pins)
|
|
r.append((sig, pins, others, (name, number, element.name)))
|
|
else:
|
|
pins, others = _separate_pins(top_constraints)
|
|
pins = self.connector_manager.resolve_identifiers(pins)
|
|
r.append((obj, pins, others, (name, number, None)))
|
|
return r
|
|
|
|
def get_platform_commands(self):
|
|
return self.platform_commands
|
|
|
|
class GenericPlatform:
|
|
def __init__(self, device, io, connectors=[], name=None):
|
|
self.device = device
|
|
self.constraint_manager = ConstraintManager(io, connectors)
|
|
if name is None:
|
|
name = self.__module__.split(".")[-1]
|
|
self.name = name
|
|
self.sources = []
|
|
self.verilog_include_paths = []
|
|
self.finalized = False
|
|
|
|
def request(self, *args, **kwargs):
|
|
return self.constraint_manager.request(*args, **kwargs)
|
|
|
|
def lookup_request(self, *args, **kwargs):
|
|
return self.constraint_manager.lookup_request(*args, **kwargs)
|
|
|
|
def add_period_constraint(self, clk, period):
|
|
raise NotImplementedError
|
|
|
|
def add_platform_command(self, *args, **kwargs):
|
|
return self.constraint_manager.add_platform_command(*args, **kwargs)
|
|
|
|
def add_extension(self, *args, **kwargs):
|
|
return self.constraint_manager.add_extension(*args, **kwargs)
|
|
|
|
def finalize(self, fragment, *args, **kwargs):
|
|
if self.finalized:
|
|
raise ConstraintError("Already finalized")
|
|
# if none exists, create a default clock domain and drive it
|
|
if not fragment.clock_domains:
|
|
if not hasattr(self, "default_clk_name"):
|
|
raise NotImplementedError("No default clock and no clock domain defined")
|
|
crg = CRG(self.request(self.default_clk_name))
|
|
fragment += crg.get_fragment()
|
|
self.do_finalize(fragment, *args, **kwargs)
|
|
self.finalized = True
|
|
|
|
def do_finalize(self, fragment, *args, **kwargs):
|
|
"""overload this and e.g. add_platform_command()'s after the modules had their say"""
|
|
if hasattr(self, "default_clk_period"):
|
|
try:
|
|
self.add_period_constraint(self.lookup_request(self.default_clk_name), self.default_clk_period)
|
|
except ConstraintError:
|
|
pass
|
|
|
|
def add_source(self, filename, language=None):
|
|
if language is None:
|
|
language = tools.language_by_filename(filename)
|
|
if language is None:
|
|
language = "verilog" # default to Verilog
|
|
filename = os.path.abspath(filename)
|
|
self.sources.append((filename, language))
|
|
|
|
def add_sources(self, path, *filenames, language=None):
|
|
for f in filenames:
|
|
self.add_source(os.path.join(path, f), language)
|
|
|
|
def add_source_dir(self, path, recursive=True):
|
|
dir_files = []
|
|
if recursive:
|
|
for root, dirs, files in os.walk(path):
|
|
for filename in files:
|
|
dir_files.append(os.path.join(root, filename))
|
|
else:
|
|
for item in os.listdir(path):
|
|
if os.path.isfile(os.path.join(path, item)):
|
|
dir_files.append(os.path.join(path, item))
|
|
for filename in dir_files:
|
|
language = tools.language_by_filename(filename)
|
|
if language is not None:
|
|
self.add_source(filename, language)
|
|
|
|
def add_verilog_include_path(self, path):
|
|
self.verilog_include_paths.append(os.path.abspath(path))
|
|
|
|
def resolve_signals(self, vns):
|
|
# resolve signal names in constraints
|
|
sc = self.constraint_manager.get_sig_constraints()
|
|
named_sc = [(vns.get_name(sig), pins, others, resource) for sig, pins, others, resource in sc]
|
|
# resolve signal names in platform commands
|
|
pc = self.constraint_manager.get_platform_commands()
|
|
named_pc = []
|
|
for template, args in pc:
|
|
name_dict = dict((k, vns.get_name(sig)) for k, sig in args.items())
|
|
named_pc.append(template.format(**name_dict))
|
|
return named_sc, named_pc
|
|
|
|
def _get_source(self, fragment, gen_fn):
|
|
if not isinstance(fragment, _Fragment):
|
|
fragment = fragment.get_fragment()
|
|
# generate source
|
|
src, vns = gen_fn(fragment)
|
|
return src, vns
|
|
|
|
def get_verilog(self, fragment, **kwargs):
|
|
return self._get_source(fragment, lambda f: verilog.convert(f, self.constraint_manager.get_io_signals(),
|
|
return_ns=True, create_clock_domains=False, simulation=False, **kwargs))
|
|
|
|
def get_edif(self, fragment, cell_library, vendor, device, **kwargs):
|
|
return self._get_source(fragment, lambda f: edif.convert(f, self.constraint_manager.get_io_signals(),
|
|
cell_library, vendor, device, return_ns=True, simulation=False, **kwargs))
|
|
|
|
def build(self, fragment):
|
|
raise NotImplementedError("GenericPlatform.build must be overloaded")
|
|
|
|
def build_cmdline(self, *args, **kwargs):
|
|
arg = sys.argv[1:]
|
|
if len(arg) % 2:
|
|
print("Missing value for option: "+sys.argv[-1])
|
|
sys.exit(1)
|
|
argdict = dict((k, autotype(v)) for k, v in zip(*[iter(arg)]*2))
|
|
kwargs.update(argdict)
|
|
self.build(*args, **kwargs)
|
|
|
|
def create_programmer(self):
|
|
raise NotImplementedError
|