from copy import copy import os, argparse from migen.fhdl.structure import * from migen.genlib.record import Record from migen.fhdl import verilog from mibuild import tools class ConstraintError(Exception): pass class Pins: def __init__(self, *identifiers): self.identifiers = identifiers 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) 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, name_map): 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((name_map(element.name), n_bits)) return t def _match(description, requests): available = list(description) matched = [] # 1. Match requests for a specific number for request in requests: if request[1] is not None: resource = _lookup(available, request[0], request[1]) available.remove(resource) matched.append((resource, request[2], request[3])) # 2. Match requests for no specific number for request in requests: if request[1] is None: resource = _lookup(available, request[0], request[1]) available.remove(resource) matched.append((resource, request[2], request[3])) return matched 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, description): self.description = description self.requests = [] self.platform_commands = [] self.io_signals = set() def request(self, name, number=None, obj=None, name_map=lambda s: s): r = _lookup(self.description, name, number) t = _resource_type(r, name_map) # If obj is None, then create it. # If it already exists, do some sanity checking. # Update io_signals at the same time. if obj is None: if isinstance(t, int): obj = Signal(t, name_override=name_map(r[0])) self.io_signals.add(obj) else: obj = Record(t, name=r[0]) for sig in obj.flatten(): self.io_signals.add(sig) else: if isinstance(t, int): assert(isinstance(obj, Signal) and obj.nbits == t) self.io_signals.add(obj) else: for attr, nbits in t: sig = getattr(obj, attr) assert(isinstance(sig, Signal) and sig.nbits == nbits) self.io_signals.add(sig) # Register the request self.requests.append((name, number, obj, name_map)) return obj def add_platform_command(self, command, **signals): self.platform_commands.append((command, signals)) def get_io_signals(self): return self.io_signals def get_sig_constraints(self): r = [] matched = _match(self.description, self.requests) for resource, obj, name_map in 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, name_map(element.name)) pins, others = _separate_pins(top_constraints + element.constraints) r.append((sig, pins, others, (name, number, element.name))) else: pins, others = _separate_pins(top_constraints) r.append((obj, pins, others, (name, number, None))) return r def get_platform_commands(self): return self.platform_commands def save(self): return copy(self.requests), copy(self.platform_commands), copy(self.io_signals) def restore(self, backup): self.request, self.platform_commands, self.io_signals = backup class GenericPlatform: def __init__(self, device, io, default_crg_factory=None, name=None): self.device = device self.constraint_manager = ConstraintManager(io) self.default_crg_factory = default_crg_factory if name is None: name = self.__module__.split(".")[-1] self.name = name self.sources = [] def request(self, *args, **kwargs): return self.constraint_manager.request(*args, **kwargs) def add_platform_command(self, *args, **kwargs): return self.constraint_manager.add_platform_command(*args, **kwargs) 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): for root, dirs, files in os.walk(path): for filename in files: language = tools.language_by_filename(filename) if language is not None: self.add_source(os.path.join(root, filename), language) def get_verilog(self, fragment, clock_domains=None, **kwargs): if not isinstance(fragment, Fragment): fragment = fragment.get_fragment() # We may create a temporary clock/reset generator that would request pins. # Save the constraint manager state so that such pin requests disappear # at the end of this function. backup = self.constraint_manager.save() try: # if none exists, create a default clock domain and drive it if clock_domains is None: if self.default_crg_factory is None: raise NotImplementedError("No clock/reset generator defined by either platform or user") crg = self.default_crg_factory(self) frag = fragment + crg.get_fragment() clock_domains = crg.get_clock_domains() else: frag = fragment # generate Verilog src, vns = verilog.convert(frag, self.constraint_manager.get_io_signals(), clock_domains=clock_domains, return_ns=True, **kwargs) # 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)) finally: self.constraint_manager.restore(backup) return src, named_sc, named_pc def build(self, fragment, clock_domains=None): raise NotImplementedError("GenericPlatform.build must be overloaded") def add_arguments(self, parser): pass # default: no arguments def build_arg_ns(self, ns, *args, **kwargs): self.build(*args, **kwargs) def build_cmdline(self, *args, **kwargs): parser = argparse.ArgumentParser(description="FPGA bitstream build system") self.add_arguments(parser) ns = parser.parse_args() self.build_arg_ns(ns, *args, **kwargs)