| #!/usr/bin/env python3 | |
| import argparse | |
| import os | |
| import re | |
| import json | |
| import lxml.etree as ET | |
| import sdf_timing.sdfparse | |
| from sdf_timing.utils import get_scale_seconds | |
| from termcolor import colored | |
| DEBUG = 0 | |
| def log(ltype, message, outdesc=None): | |
| """Prints log messages. | |
| Parameters | |
| ---------- | |
| ltype: str | |
| Log type, can be INFO, WARNING, ERROR | |
| message: str | |
| Log message | |
| """ | |
| LOGLEVELS = ["INFO", "WARNING", "ERROR", "ALL"] | |
| SUPPRESSBELOW = "ERROR" | |
| if ltype not in LOGLEVELS[:-1]: | |
| return | |
| dat = { | |
| "INFO": (0, "green"), | |
| "WARNING": (1, "yellow"), | |
| "ERROR": (2, "red"), | |
| "ALL": (3, "black") | |
| } | |
| if dat[ltype][0] >= dat[SUPPRESSBELOW][0]: | |
| print(colored("{}: {}".format(ltype, message), dat[ltype][1])) | |
| if outdesc: | |
| print( | |
| colored("{}: {}".format(ltype, message), dat[ltype][1]), | |
| file=outdesc | |
| ) | |
| # ============================================================================= | |
| # RAM 2x1 ports, their widths and associated clocks. | |
| # FIXME: Read that from the techfile or phy_db.pickle | |
| RAM_2X1_PORTS = { | |
| "input": | |
| [ | |
| # RAM part 1, port 1 | |
| ["WIDTH_SELECT1_0", 2, None], | |
| ["CLK1EN_0", 1, None], # "CLK1_0"], | |
| ["CS1_0", 1, None], # "CLK1_0"], | |
| ["A1_0", 11, "CLK1_0"], | |
| ["WD_0", 18, "CLK1_0"], | |
| ["WEN1_0", 2, "CLK1_0"], | |
| ["P1_0", 1, "CLK1_0"], | |
| # RAM part 1, port 2 | |
| ["WIDTH_SELECT2_0", 2, None], | |
| ["CLK2EN_0", 1, None], # "CLK2_0"], | |
| ["CS2_0", 1, None], # "CLK2_0"], | |
| ["A2_0", 11, "CLK2_0"], | |
| ["P2_0", 1, "CLK2_0"], | |
| # RAM part 1, common | |
| ["CONCAT_EN_0", 1, None], | |
| ["PIPELINE_RD_0", 1, None], | |
| ["FIFO_EN_0", 1, None], | |
| ["DIR_0", 1, None], | |
| ["SYNC_FIFO_0", 1, None], | |
| ["ASYNC_FLUSH_0", 1, None], | |
| # RAM part 2, port 1 | |
| ["WIDTH_SELECT1_1", 2, None], | |
| ["CLK1EN_1", 1, None], # "CLK1_1"], | |
| ["CS1_1", 1, None], # "CLK1_1"], | |
| ["A1_1", 11, "CLK1_1"], | |
| ["WD_1", 18, "CLK1_1"], | |
| ["WEN1_1", 2, "CLK1_1"], | |
| ["P1_1", 1, "CLK1_1"], | |
| # RAM part 2, port 2 | |
| ["WIDTH_SELECT2_1", 2, None], | |
| ["CLK2EN_1", 1, None], # "CLK2_1"], | |
| ["CS2_1", 1, None], # "CLK2_1"], | |
| ["A2_1", 11, "CLK2_1"], | |
| ["P2_1", 1, "CLK2_1"], | |
| # RAM part 2, common | |
| ["CONCAT_EN_1", 1, None], | |
| ["PIPELINE_RD_1", 1, None], | |
| ["FIFO_EN_1", 1, None], | |
| ["DIR_1", 1, None], | |
| ["SYNC_FIFO_1", 1, None], | |
| ["ASYNC_FLUSH_1", 1, None], | |
| # Common, unknown | |
| ["DS", 1, None], | |
| ["DS_RB1", 1, None], | |
| ["LS", 1, None], | |
| ["LS_RB1", 1, None], | |
| ["SD", 1, None], | |
| ["SD_RB1", 1, None], | |
| ["RMA", 4, None], | |
| ["RMB", 4, None], | |
| ["RMEA", 1, None], | |
| ["RMEB", 1, None], | |
| ["TEST1A", 1, None], | |
| ["TEST1B", 1, None], | |
| ], | |
| "clock": | |
| [ | |
| # RAM part 1 | |
| ["CLK1_0", 1, None], | |
| ["CLK2_0", 1, None], | |
| # RAM part 2 | |
| ["CLK1_1", 1, None], | |
| ["CLK2_1", 1, None], | |
| ], | |
| "output": | |
| [ | |
| # RAM part 1, port 1 | |
| ["Almost_Full_0", 1, "CLK1_0"], | |
| ["PUSH_FLAG_0", 4, "CLK1_0"], | |
| # RAM part 1, port 2 | |
| ["Almost_Empty_0", 1, "CLK2_0"], | |
| ["POP_FLAG_0", 4, "CLK2_0"], | |
| ["RD_0", 18, "CLK2_0"], | |
| # RAM part 2, port 1 | |
| ["Almost_Full_1", 1, "CLK1_1"], | |
| ["PUSH_FLAG_1", 4, "CLK1_1"], | |
| # RAM part 2, port 2 | |
| ["Almost_Empty_1", 1, "CLK2_1"], | |
| ["POP_FLAG_1", 4, "CLK2_1"], | |
| ["RD_1", 18, "CLK2_1"], | |
| ], | |
| } | |
| # A list of non-routable ports | |
| RAM_2X1_NON_ROUTABLE_PORTS = { | |
| "input": | |
| [ | |
| ["CLK1S_0", 1, None], | |
| ["CLK2S_0", 1, None], | |
| ["CLK1S_1", 1, None], | |
| ["CLK2S_1", 1, None], | |
| ["ASYNC_FLUSH_S0", 1, None], | |
| ["ASYNC_FLUSH_S1", 1, None], | |
| ] | |
| } | |
| # A list of test ports | |
| RAM_TEST_PORTS = [ | |
| "DS", | |
| "DS_RB1", | |
| "LS", | |
| "LS_RB1", | |
| "SD", | |
| "SD_RB1", | |
| "RMA", | |
| "RMB", | |
| "RMEA", | |
| "RMEB", | |
| "TEST1A", | |
| "TEST1B", | |
| ] | |
| # A list of test ports specific to a device | |
| RAM_2X1_TEST_PORTS = { | |
| "ql-eos-s3": RAM_TEST_PORTS, | |
| "ql-pp3e": [], | |
| } | |
| # A list of ports relevant only in RAM mode | |
| RAM_PORTS = [ | |
| "WEN1", | |
| "WEN2", | |
| "A1", | |
| "A2", | |
| ] | |
| # A list of ports relevant only in FIFO mode | |
| FIFO_PORTS = [ | |
| "DIR", | |
| "SYNC_FIFO", | |
| "ASYNC_FLUSH", | |
| "P1", | |
| "P2", | |
| "Almost_Full", | |
| "Almost_Empty", | |
| "PUSH_FLAG", | |
| "POP_FLAG", | |
| ] | |
| # A clock map for FIFO operating in DIR=1 mode | |
| FIFO_CLOCK_MAP = { | |
| "CLK1_0": "CLK2_0", | |
| "CLK2_0": "CLK1_0", | |
| "CLK1_1": "CLK2_1", | |
| "CLK2_1": "CLK1_1", | |
| "CLK1": "CLK2", | |
| "CLK2": "CLK1", | |
| } | |
| # A list of non-splitable ports | |
| NON_SPLITABLE_PORTS = [ | |
| "WIDTH_SELECT1_0", | |
| "WIDTH_SELECT2_0", | |
| "WIDTH_SELECT1_1", | |
| "WIDTH_SELECT2_1", | |
| "WIDTH_SELECT1", | |
| "WIDTH_SELECT2", | |
| ] | |
| # ============================================================================= | |
| def blacklist_ports(ports, blacklist): | |
| """ | |
| Removes port present on the blacklist | |
| """ | |
| new_ports = dict() | |
| for key, ports in ports.items(): | |
| new_ports[key] = [] | |
| for name, width, assoc_clock in ports: | |
| if name not in blacklist: | |
| new_ports[key].append([name, width, assoc_clock]) | |
| return new_ports | |
| # ============================================================================= | |
| def yield_ram_modes(ram_tree): | |
| """ | |
| Yields all possible combinations of ram modes. Generated strings contain | |
| comma separated control signal conditions. | |
| """ | |
| def inner(d, cond_prefix=None): | |
| for k, v in d.items(): | |
| if not cond_prefix: | |
| cond = k | |
| else: | |
| cond = cond_prefix + "," + k | |
| if v is None: | |
| yield cond | |
| else: | |
| yield from inner(v, cond) | |
| yield from inner(ram_tree) | |
| def make_mode_name(cond): | |
| """ | |
| Formats a mode name given a condition set | |
| """ | |
| name = [] | |
| for c in cond.split(","): | |
| sig, val = [s.strip() for s in c.strip().split("=")] | |
| # If the signal name ends with a digit, replace it with a letter | |
| # FIXME: Assuming start from 1 | |
| if sig[-1].isdigit(): | |
| n = int(sig[-1]) | |
| sig = sig[:-1] + "_" + chr(ord('A') + n - 1) | |
| # Abbreviate the signal | |
| fields = sig.split("_") | |
| sig = "".join([f[0] for f in fields]) | |
| # Append the value | |
| sig += val | |
| name.append(sig) | |
| return "_".join(name) | |
| # ============================================================================= | |
| def filter_cells( | |
| timings, cond, part=None, normalized_names=False, debug=False | |
| ): | |
| """ | |
| Filters SDF timings basing on conditions present in cell type names. | |
| If a negated condition from the condition list is found in a cell type | |
| then that cell type is rejected. | |
| """ | |
| # Make false condition strings | |
| cond_strs = [] | |
| for c in cond.split(","): | |
| sig, val = [s.strip() for s in c.strip().split("=")] | |
| # There appears to be a single CONCAT_EN_0 condition for both cells. | |
| # Handle it here separately | |
| if sig == "CONCAT_EN": | |
| nval = str(1 - int(val)) | |
| cond_strs.append(sig + "_0" + "_EQ_" + nval) | |
| continue | |
| # For a single part | |
| if part is not None: | |
| assert part in [0, 1], part | |
| part_str = "_{}".format(int(part)) | |
| # Single bit | |
| # "<param>_EQ_<!val>" | |
| nval = str(1 - int(val)) | |
| cond_strs.append(sig + part_str + "_EQ_" + nval) | |
| # Single bit of a multi-bit parameter | |
| # "<param>_<bit_idx>_EQ_<!bit_val>" | |
| # FIXME: Assuming 2-bit max !! | |
| for b in range(4): | |
| nval = str(int((int(val) & (1 << b)) == 0)) | |
| if normalized_names: | |
| cond_strs.append(sig + part_str + str(b) + "_EQ_" + nval) | |
| else: | |
| cond_strs.append( | |
| sig + part_str + "[{}]".format(b) + "_EQ_" + nval | |
| ) | |
| # Reject the other part completely | |
| other_part_str = "_{}".format(int(1 - part)) | |
| cond_strs.append(sig + other_part_str + "_EQ_0") | |
| cond_strs.append(sig + other_part_str + "_EQ_1") | |
| # FIXME: Assuming 2-bit max !! | |
| for b in range(4): | |
| if normalized_names: | |
| cond_strs.append(sig + other_part_str + str(b) + "_EQ_0") | |
| cond_strs.append(sig + other_part_str + str(b) + "_EQ_1") | |
| else: | |
| cond_strs.append( | |
| sig + other_part_str + "[{}]".format(b) + "_EQ_0" | |
| ) | |
| cond_strs.append( | |
| sig + other_part_str + "[{}]".format(b) + "_EQ_1" | |
| ) | |
| # For both parts | |
| else: | |
| # Assume that control parameters must be equal for both parts | |
| nval = str(1 - int(val)) | |
| cond_strs.append(sig + "_0" + "_EQ_" + nval) | |
| cond_strs.append(sig + "_1" + "_EQ_" + nval) | |
| for b in range(4): | |
| nval = str(int((int(val) & (1 << b)) == 0)) | |
| if normalized_names: | |
| cond_strs.append(sig + "_0" + str(b) + "_EQ_" + nval) | |
| cond_strs.append(sig + "_1" + str(b) + "_EQ_" + nval) | |
| else: | |
| cond_strs.append( | |
| sig + "_0" + "[{}]".format(b) + "_EQ_" + nval | |
| ) | |
| cond_strs.append( | |
| sig + "_1" + "[{}]".format(b) + "_EQ_" + nval | |
| ) | |
| # DEBUG | |
| if debug: | |
| print(cond) | |
| for s in cond_strs: | |
| print("", "!" + s) | |
| # Filter | |
| cells = {} | |
| for cell_type, cell_data in timings.items(): | |
| if debug: | |
| print("", "check", cell_type) | |
| # If any of the false conditions is found in the cell_type then the | |
| # cell is rejected | |
| reject = False | |
| for s in cond_strs: | |
| if s in cell_type: | |
| if debug: | |
| print(" ", "reject", s) | |
| reject = True | |
| # The cell is ok | |
| if not reject: | |
| if debug: | |
| print(" ", "OK") | |
| assert cell_type not in cells | |
| cells[cell_type] = cell_data | |
| return cells | |
| def filter_instances(timings, name): | |
| """ | |
| Filters a single cell instance from the given timing data | |
| """ | |
| cells = {} | |
| for cell_type, cell_data in timings.items(): | |
| # Don't have that instance | |
| if name not in cell_data: | |
| continue | |
| # Leave only the one that we are looking for | |
| cells[cell_type] = {name: cell_data[name]} | |
| return cells | |
| def find_timings(timings, src_pin, dst_pin): | |
| path_timings = {} | |
| for cell_type, cell_data in timings.items(): | |
| # FIXME: Skip falling edges | |
| if "FALLING" in cell_type: | |
| continue | |
| for inst_name, inst_data in cell_data.items(): | |
| for tname, tdata in inst_data.items(): | |
| # Got matching pins | |
| if src_pin in tdata["from_pin"]: | |
| if dst_pin in tdata["to_pin"]: | |
| if tname in path_timings: | |
| print("new", cell_type) | |
| print("old", path_timings[tname][0]) | |
| # FIXME | |
| continue | |
| path_timings[tname] = ( | |
| cell_type, | |
| inst_name, | |
| tdata, | |
| ) | |
| return path_timings | |
| # ============================================================================= | |
| def make_single_ram(ports): | |
| """ | |
| Makes port definition for a single RAM cell (not 2x1) | |
| """ | |
| new_ports = { | |
| "input": set(), | |
| "clock": set(), | |
| "output": set(), | |
| } | |
| for key in ["input", "clock", "output"]: | |
| all_port_names = set([p[0] for p in ports[key]]) | |
| for name, width, assoc_clock in ports[key]: | |
| # Make a generic associated clock name | |
| if assoc_clock is not None: | |
| if assoc_clock.endswith("_0"): | |
| base_assoc_clock = assoc_clock[:-2] | |
| if assoc_clock.endswith("_1"): | |
| base_assoc_clock = assoc_clock[:-2] | |
| else: | |
| base_assoc_clock = None | |
| # Make a generic port name | |
| if name.endswith("_0"): | |
| base = name[:-2] | |
| if (base + "_1") in all_port_names: | |
| new_ports[key].add((base, width, base_assoc_clock)) | |
| elif name.endswith("_1"): | |
| base = name[:-2] | |
| if (base + "_0") in all_port_names: | |
| new_ports[key].add((base, width, base_assoc_clock)) | |
| # This is a common port for both RAMs, add it unchanged | |
| else: | |
| assert key == "input", ( | |
| key, | |
| name, | |
| width, | |
| assoc_clock, | |
| ) | |
| new_ports[key].add(( | |
| name, | |
| width, | |
| assoc_clock, | |
| )) | |
| return new_ports | |
| def remap_clocks(ports, clock_map): | |
| """ | |
| Remaps clock dependencies of all ports and returns a new port map. | |
| Does not remap clocks for "P1" and "P2" ports. | |
| """ | |
| new_ports = { | |
| "input": set(), | |
| "clock": set(), | |
| "output": set(), | |
| } | |
| for key in ["input", "clock", "output"]: | |
| for name, width, assoc_clock in ports[key]: | |
| # P1 and P2 are not subject to the remap | |
| if "P1" not in name and "P2" not in name: | |
| # Remap the associated clock | |
| if assoc_clock in clock_map: | |
| assoc_clock = clock_map[assoc_clock] | |
| new_ports[key].add(( | |
| name, | |
| width, | |
| assoc_clock, | |
| )) | |
| return new_ports | |
| def filter_ports(ports, ports_to_filter): | |
| """ | |
| Removes timing relation from ports of a model | |
| """ | |
| # Make a copy | |
| new_ports = dict() | |
| for key in ["input", "clock", "output"]: | |
| new_ports[key] = [] | |
| for name, width, assoc_clock in ports[key]: | |
| if key in ["input", "output"]: | |
| if name.endswith("_0") or name.endswith("_1"): | |
| generic_name = name[:-2] | |
| if generic_name in ports_to_filter: | |
| assoc_clock = None | |
| if name in ports_to_filter: | |
| assoc_clock = None | |
| new_ports[key].append(( | |
| name, | |
| width, | |
| assoc_clock, | |
| )) | |
| return new_ports | |
| # ============================================================================= | |
| def make_pin_name(port, index): | |
| """ | |
| Formats a pin name of a multi-bit port | |
| """ | |
| return "{}_b{}".format(port, index) | |
| def split_port_bit_index(name): | |
| """ | |
| Extracts bit index from the port | |
| """ | |
| m = re.match(r"(?P<name>.*)(_b(?P<bit>[0-9]+))$", name) | |
| if m is not None: | |
| return m.group("name"), int(m.group("bit")), | |
| return name, None | |
| def parse_port_name(name): | |
| """ | |
| Parses a port name. Returns the base name, cell index and bit index. | |
| >>> parse_port_name("A_PORT") | |
| ('A_PORT', None, None) | |
| >>> parse_port_name("A_PORT_0") | |
| ('A_PORT', 0, None) | |
| >>> parse_port_name("A_PORT_b31") | |
| ('A_PORT', None, 31) | |
| >>> parse_port_name("A_PORT_0_b15") | |
| ('A_PORT', 0, 15) | |
| """ | |
| # A multi-bit port | |
| m = re.match(r"(?P<name>.*)(_b(?P<bit>[0-9]+))$", name) | |
| if m is not None: | |
| port = m.group("name") | |
| bit = int(m.group("bit")) | |
| else: | |
| port = name | |
| bit = None | |
| # A port of a sub-cell | |
| m = re.match(r"(?P<name>.*)(_(?P<cell>[0-9]+))$", port) | |
| if m is not None: | |
| return m.group("name"), int(m.group("cell")), bit | |
| return port, None, bit | |
| def split_ports(ports): | |
| """ | |
| Splits multi-bit ports into single-bit | |
| """ | |
| split_ports = dict() | |
| for key in ports.keys(): | |
| split_ports[key] = list() | |
| for name, width, assoc_clock in ports[key]: | |
| if width == 1 or name in NON_SPLITABLE_PORTS: | |
| split_ports[key].append(( | |
| name, | |
| width, | |
| assoc_clock, | |
| )) | |
| else: | |
| for i in range(width): | |
| split_ports[key].append( | |
| (make_pin_name(name, i), 1, assoc_clock) | |
| ) | |
| return split_ports | |
| def make_model(model_name, ports): | |
| """ | |
| Makex a model XML given the port definition | |
| """ | |
| # The model root | |
| xml_model = ET.Element("model", { | |
| "name": model_name, | |
| }) | |
| # Port lists | |
| xml_ports = { | |
| "input": ET.SubElement(xml_model, "input_ports"), | |
| "output": ET.SubElement(xml_model, "output_ports"), | |
| } | |
| xml_ports["clock"] = xml_ports["input"] | |
| # Emits a XML tag for a port | |
| def add_port(xml_parent, name, width, assoc_clock): | |
| # A clock | |
| if key == "clock": | |
| assert assoc_clock is None, (name, width, assoc_clock) | |
| attrs = {"is_clock": "1"} | |
| # An input / output | |
| else: | |
| if assoc_clock is not None: | |
| attrs = {"clock": assoc_clock} | |
| else: | |
| attrs = dict() | |
| ET.SubElement(xml_parent, "port", {"name": name, **attrs}) | |
| # Add ports | |
| for key in ["clock", "input", "output"]: | |
| for name, width, assoc_clock in ports[key]: | |
| add_port(xml_ports[key], name, width, assoc_clock) | |
| return xml_model | |
| def make_pb_type( | |
| pb_name, | |
| ports, | |
| model_name, | |
| timings=None, | |
| timescale=1.0, | |
| normalized_names=False | |
| ): | |
| stats = { | |
| "total_timings": 0, | |
| "missing_timings": 0, | |
| } | |
| # The pb_type tag | |
| if model_name: | |
| attrs = {"blif_model": ".subckt {}".format(model_name)} | |
| else: | |
| attrs = dict() | |
| xml_pb = ET.Element("pb_type", {"name": pb_name, "num_pb": "1", **attrs}) | |
| # Ports | |
| for key in ["clock", "input", "output"]: | |
| for name, width, *data in ports[key]: | |
| ET.SubElement( | |
| xml_pb, key, { | |
| "name": name, | |
| "num_pins": str(width), | |
| } | |
| ) | |
| # fasm | |
| if model_name: | |
| xml_metadata = ET.SubElement(xml_pb, "metadata") | |
| xml_fasmprefix = ET.SubElement( | |
| xml_metadata, "meta", {"name": "fasm_prefix"} | |
| ) | |
| xml_fasmprefix.text = "RAM.RAM" | |
| xml_fasm = ET.SubElement(xml_metadata, "meta", {"name": "fasm_params"}) | |
| if "_0_" in pb_name: | |
| xml_fasm.text = "\n\ | |
| INIT[9215:0] = INIT\n" | |
| elif "_1_" in pb_name: | |
| xml_fasm.text = "\n\ | |
| INIT[18431:9216] = INIT\n" | |
| else: | |
| xml_fasm.text = "\n\ | |
| INIT[18431:0] = INIT\n" | |
| # Timings | |
| if timings is not None: | |
| # Setup and hold | |
| for name, width, assoc_clock in ports["input"]: | |
| if assoc_clock is None: | |
| continue | |
| # Split the port and its bit index (if any) | |
| alias, bit = split_port_bit_index(name) | |
| # Find all timing paths for that port | |
| path_timings = find_timings(timings, assoc_clock, alias.upper()) | |
| # Bit index suffix | |
| if bit is not None: | |
| if normalized_names: | |
| suffix = "{}".format(bit) | |
| else: | |
| suffix = "[{}]".format(bit) | |
| else: | |
| suffix = "" | |
| # Setup | |
| for key in path_timings: | |
| if key.startswith("setup") and key.endswith(suffix): | |
| tim = path_timings[key][2] | |
| delay = tim["delay_paths"]["nominal"]["avg"] * timescale | |
| break | |
| if key.startswith("setuphold") and key.endswith(suffix): | |
| tim = path_timings[key][2] | |
| print("SETUPHOLD", tim) | |
| exit(-1) | |
| delay = tim["delay_paths"]["nominal"]["avg"] * timescale | |
| break | |
| else: | |
| delay = 1e-10 | |
| stats["missing_timings"] += 1 | |
| log( | |
| "WARNING", | |
| "No setup timing for '{}'->'{}' for pb_type '{}'".format( | |
| alias + suffix, assoc_clock, pb_name | |
| ) | |
| ) | |
| stats["total_timings"] += 1 | |
| ET.SubElement( | |
| xml_pb, "T_setup", { | |
| "port": name, | |
| "clock": assoc_clock, | |
| "value": "{:+e}".format(delay) | |
| } | |
| ) | |
| # Hold | |
| for key in path_timings: | |
| if key.startswith("hold") and key.endswith(suffix): | |
| tim = path_timings[key][2] | |
| delay = tim["delay_paths"]["nominal"]["avg"] * timescale | |
| break | |
| if key.startswith("setuphold") and key.endswith(suffix): | |
| tim = path_timings[key][2] | |
| print("SETUPHOLD", tim) | |
| exit(-1) | |
| delay = tim["delay_paths"]["nominal"]["avg"] * timescale | |
| break | |
| else: | |
| delay = 1e-10 | |
| stats["missing_timings"] += 1 | |
| log( | |
| "WARNING", | |
| "No hold timing for '{}'->'{}' for pb_type '{}'".format( | |
| alias + suffix, assoc_clock, pb_name | |
| ) | |
| ) | |
| stats["total_timings"] += 1 | |
| ET.SubElement( | |
| xml_pb, "T_hold", { | |
| "port": name, | |
| "clock": assoc_clock, | |
| "value": "{:+e}".format(delay) | |
| } | |
| ) | |
| # Clock to Q | |
| for name, width, assoc_clock in ports["output"]: | |
| if assoc_clock is None: | |
| continue | |
| # Split the port and its bit index (if any) | |
| alias, bit = split_port_bit_index(name) | |
| # Find all timing paths for that port | |
| path_timings = find_timings(timings, assoc_clock, alias.upper()) | |
| # Index suffixes | |
| # Bit index suffix | |
| if bit is not None: | |
| if normalized_names: | |
| suffix = "{}".format(bit) | |
| else: | |
| suffix = "[{}]".format(bit) | |
| else: | |
| suffix = "" | |
| # "Clock to Q" | |
| for key in path_timings: | |
| if key.startswith("iopath") and key.endswith(suffix): | |
| tim = path_timings[key][2] | |
| delay_min = tim["delay_paths"]["slow"]["min"] * timescale | |
| delay_max = tim["delay_paths"]["slow"]["max"] * timescale | |
| break | |
| else: | |
| delay_min = 1e-10 | |
| delay_max = 1e-10 | |
| stats["missing_timings"] += 1 | |
| log( | |
| "WARNING", | |
| "No \"clock to Q\" timing for '{}'->'{}' for pb_type '{}'". | |
| format(assoc_clock, alias + suffix, pb_name) | |
| ) | |
| stats["total_timings"] += 1 | |
| ET.SubElement( | |
| xml_pb, "T_clock_to_Q", { | |
| "port": name, | |
| "clock": assoc_clock, | |
| "min": "{:+e}".format(delay_min), | |
| "max": "{:+e}".format(delay_max) | |
| } | |
| ) | |
| return xml_pb, stats | |
| # ============================================================================= | |
| def auto_interconnect(pb_type): | |
| """ | |
| Automatically generates an interconnect that connects matching ports of | |
| a child pb_type to its parent. Moreover, when a child pb_type is suffixed | |
| with sth. like "_0" then parent ports with the same suffix are matched. | |
| """ | |
| def get_ports(pb, type): | |
| """ | |
| Yields pb_type ports of the given type | |
| """ | |
| for port in pb.findall(type): | |
| yield port.attrib["name"], int(port.attrib["num_pins"]), | |
| # Get parent for the interconnect (can be either "mode" or "pb_type") | |
| assert pb_type.tag == "mode" | |
| pb_parent = pb_type.getparent() | |
| assert pb_parent.tag == "pb_type" | |
| pb_children = list(pb_type.findall("pb_type")) | |
| # Upstream ports | |
| parent_name = pb_parent.attrib["name"] | |
| parent_ports = set([(*p, "I") for p in get_ports(pb_parent, "input")]) | |
| parent_ports |= set([(*p, "I") for p in get_ports(pb_parent, "clock")]) | |
| parent_ports |= set([(*p, "O") for p in get_ports(pb_parent, "output")]) | |
| # Downstream ports | |
| children = {} | |
| for child in pb_children: | |
| name = child.attrib["name"] | |
| ports = set([(*p, "I") for p in get_ports(child, "input")]) | |
| ports |= set([(*p, "I") for p in get_ports(child, "clock")]) | |
| ports |= set([(*p, "O") for p in get_ports(child, "output")]) | |
| children[name] = ports | |
| # Adds a connection | |
| def add_connection(xml, iname, oname, reverse=False): | |
| if reverse: | |
| ET.SubElement( | |
| xml, "direct", { | |
| "name": "{}_to_{}".format(oname, iname), | |
| "input": oname, | |
| "output": iname | |
| } | |
| ) | |
| else: | |
| ET.SubElement( | |
| xml, "direct", { | |
| "name": "{}_to_{}".format(iname, oname), | |
| "input": iname, | |
| "output": oname | |
| } | |
| ) | |
| # Create the interconnect | |
| ic = ET.SubElement(pb_type, "interconnect") | |
| for child in sorted(children.keys()): | |
| child_ports = sorted(list(children[child]), key=lambda x: x[0]) | |
| # Get the child cell index | |
| m = re.match(r"(.*)(_(?P<idx>[0-9]+))$", child) | |
| if m is not None: | |
| child_index = int(m.group("idx")) | |
| else: | |
| child_index = None | |
| # Loop over all child ports | |
| for dn_port, dn_width, dn_type in child_ports: | |
| # Got a 1-to-1 match | |
| if (dn_port, dn_width, dn_type) in parent_ports: | |
| iname = "{}.{}".format(parent_name, dn_port) | |
| oname = "{}.{}".format(child, dn_port) | |
| add_connection(ic, iname, oname, dn_type == "O") | |
| continue | |
| # Parse the child port name | |
| dn_alias, dn_index, dn_bit = parse_port_name(dn_port) | |
| # Got a match with the child cell index | |
| up_port = "{}_{}".format(dn_alias, child_index) | |
| if (up_port, dn_width, dn_type) in parent_ports: | |
| iname = "{}.{}".format(parent_name, up_port) | |
| oname = "{}.{}".format(child, dn_port) | |
| add_connection(ic, iname, oname, dn_type == "O") | |
| continue | |
| # The downstream port is split into bits | |
| if dn_bit is not None: | |
| # Try without the cell index | |
| ports = [(*p, ) for p in parent_ports if p[0] == dn_alias] | |
| assert len(ports) < 2, ports | |
| if ports: | |
| up_port, up_width, up_type = ports[0] | |
| iname = "{}.{}[{}]".format(parent_name, up_port, dn_bit) | |
| oname = "{}.{}".format(child, dn_port) | |
| add_connection(ic, iname, oname, dn_type == "O") | |
| continue | |
| up_alias = "{}_{}".format(dn_alias, dn_index) | |
| ports = [(*p, ) for p in parent_ports if p[0] == up_alias] | |
| assert len(ports) < 2, ports | |
| if ports: | |
| up_port, up_width, up_type = ports[0] | |
| iname = "{}.{}[{}]".format(parent_name, up_port, dn_bit) | |
| oname = "{}.{}".format(child, dn_port) | |
| add_connection(ic, iname, oname, dn_type == "O") | |
| continue | |
| # Couldn't find a matching port | |
| print( | |
| "ERROR: No matching parent port in '{}' for '{}.{}'".format( | |
| parent_name, child, dn_port | |
| ) | |
| ) | |
| return ic | |
| # ============================================================================= | |
| def make_ports(ports, separator=",\n"): | |
| verilog = "" | |
| for key in ["clock", "input", "output"]: | |
| if key == "clock": | |
| type = "input" | |
| else: | |
| type = key | |
| verilog += "\n" | |
| for name, width, assoc_clock in ports[key]: | |
| if width > 1: | |
| verilog += " {:<6} [{:2d}:0] {}{}".format( | |
| type, width - 1, name, separator | |
| ) | |
| else: | |
| verilog += " {:<6} {}{}".format(type, name, separator) | |
| return verilog | |
| def make_ram2x1_instance(device, ports, separator=",\n"): | |
| verilog = "" | |
| verilog += "\n ram8k_2x1_cell # (.INIT(INIT)) I1 ( \n" | |
| for key in ["clock", "input", "output"]: | |
| for name, width, assoc_clock in ports[key]: | |
| if name not in RAM_2X1_TEST_PORTS[device]: | |
| if name[:-3] not in RAM_2X1_TEST_PORTS[device]: | |
| if name.find("_0") >= 0 or name.find("_1") >= 0: | |
| verilog += " .{}({}){}".format( | |
| name, name, separator | |
| ) | |
| elif name.find("_b") >= 0: | |
| verilog += " .{}({}){}".format( | |
| name.replace("_b", "_0_b"), name, separator | |
| ) | |
| else: | |
| verilog += " .{}_0({}){}".format( | |
| name, name, separator | |
| ) | |
| verilog = verilog[:-2] + ");\n\n" | |
| return verilog | |
| def make_specify(ports, separator=";\n"): | |
| verilog = "" | |
| verilog += "\n specify\n" | |
| for key in ["clock", "input", "output"]: | |
| for name, width, assoc_clock in ports[key]: | |
| if key == "input": | |
| if assoc_clock is not None: | |
| verilog += " $setup({}, posedge {}, \"\"){}".format( | |
| name, assoc_clock, separator | |
| ) | |
| verilog += " $hold(posedge {}, {}, \"\"){}".format( | |
| assoc_clock, name, separator | |
| ) | |
| elif key == "output": | |
| if assoc_clock is not None: | |
| verilog += " ({}*>{})=\"\"{}".format( | |
| assoc_clock, name, separator | |
| ) | |
| verilog += " endspecify\n\n" | |
| return verilog | |
| def make_blackbox(device, name, ports, specify_ports): | |
| # Header | |
| verilog = """ | |
| `timescale 1ns/10ps | |
| (* blackbox *) | |
| module {} ( | |
| """.format(name) | |
| # Ports | |
| verilog += make_ports(ports) | |
| verilog = verilog[:-2] + "\n" | |
| verilog += ");\n" | |
| verilog += " parameter [18431:0] INIT = 18432'bx;\n" | |
| # Specify | |
| verilog += make_specify(specify_ports) | |
| # RAM2x1 cell instance | |
| verilog += make_ram2x1_instance(device, ports) | |
| # Footer | |
| verilog += "endmodule\n" | |
| return verilog | |
| def make_techmap(port_blacklist, conditions): | |
| # The original cell name | |
| cell_name = "ram8k_2x1_cell_macro" | |
| # Header | |
| verilog = "module {} (\n".format(cell_name) | |
| # Ports | |
| map_ports = {} | |
| for key, ports in RAM_2X1_PORTS.items(): | |
| map_ports[key] = list(ports) | |
| if key in RAM_2X1_NON_ROUTABLE_PORTS: | |
| map_ports[key] += RAM_2X1_NON_ROUTABLE_PORTS[key] | |
| verilog += make_ports(map_ports) | |
| verilog = verilog[:-2] + "\n" | |
| verilog += ");\n" | |
| verilog += "\n" | |
| # Gather significant control parameters | |
| control_signals = set() | |
| for condition in conditions: | |
| for cond_str in condition.split(","): | |
| sig, val = cond_str.split("=") | |
| for part in [0, 1]: | |
| part_sig = "{}_{}".format(sig, part) | |
| control_signals.add(part_sig) | |
| # Techmap special parameters | |
| for sig in sorted(control_signals): | |
| verilog += " parameter _TECHMAP_CONSTMSK_{}_ = 1'bx;\n".format(sig) | |
| verilog += " parameter _TECHMAP_CONSTVAL_{}_ = 1'bx;\n".format(sig) | |
| verilog += "\n" | |
| verilog += " parameter [18431:0] INIT = 18432'bx;\n" | |
| # Split condition strings for single and dual ram modes | |
| sing_conditions = [c for c in conditions if "CONCAT_EN=0" in c] | |
| dual_conditions = [c for c in conditions if "CONCAT_EN=1" in c] | |
| # Split RAM mode | |
| verilog_cond = [] | |
| verilog_cond.append("(_TECHMAP_CONSTVAL_CONCAT_EN_0_ == 1'b0)") | |
| verilog_cond.append("(_TECHMAP_CONSTVAL_CONCAT_EN_1_ == 1'b0)") | |
| verilog_cond = " && ".join(verilog_cond) | |
| verilog += " // Split RAM\n" | |
| verilog += " generate if({}) begin\n".format(verilog_cond) | |
| # Each part is independent | |
| model_ports = split_ports(blacklist_ports(RAM_2X1_PORTS, port_blacklist)) | |
| for part in [0, 1]: | |
| verilog += " // RAM {}\n".format(part) | |
| for i, condition in enumerate(sing_conditions): | |
| # Case condition | |
| verilog_cond = [] | |
| for condition_str in condition.split(","): | |
| sig, val = condition_str.split("=") | |
| if sig == "CONCAT_EN": | |
| continue | |
| cond_part = "(_TECHMAP_CONSTVAL_{}_{}_ == {})".format( | |
| sig, part, val | |
| ) | |
| verilog_cond.append(cond_part) | |
| verilog_cond = " && ".join(verilog_cond) | |
| if i == 0: | |
| verilog += " if ({}) begin\n".format(verilog_cond) | |
| else: | |
| verilog += " end else if ({}) begin\n".format(verilog_cond) | |
| # Instance | |
| mode_name = make_mode_name(condition) | |
| model_name = "RAM_" + mode_name + "_VPR" | |
| verilog += " {} # (\n".format(model_name) | |
| if part == 0: | |
| verilog += " .INIT(INIT[9215:0]),\n" | |
| else: | |
| verilog += " .INIT(INIT[18431:9216]),\n" | |
| verilog += " )" | |
| verilog += " RAM_{} (\n".format(part) | |
| # Ports mapped to the part | |
| for key in ["clock", "input", "output"]: | |
| for name, width, assoc_clock in model_ports[key]: | |
| # Parse the port name | |
| alias, index, bit = parse_port_name(name) | |
| # Not for that part | |
| if index is not None and part != index: | |
| continue | |
| if index is None: | |
| pname = alias | |
| else: | |
| pname = "{}_{}".format(alias, index) | |
| if bit is not None: | |
| portspec = "{}_b{}".format(alias, bit) | |
| sigspec = "{}[{}]".format(pname, bit) | |
| else: | |
| portspec = alias | |
| sigspec = pname | |
| verilog += " .{}({}),\n".format(portspec, sigspec) | |
| verilog = verilog[:-2] + "\n" | |
| verilog += " );\n" | |
| # Error catcher | |
| verilog += """ | |
| end else begin | |
| wire _TECHMAP_FAIL_; | |
| end | |
| """ | |
| # Non-split (concatenated) RAM mode | |
| verilog_cond = [] | |
| # It appears that only CONCAT_EN_0 needs to be set | |
| verilog_cond.append("(_TECHMAP_CONSTVAL_CONCAT_EN_0_ == 1'b1)") | |
| verilog_cond = " && ".join(verilog_cond) | |
| verilog += " // Concatenated RAM\n" | |
| verilog += " end else if({}) begin\n".format(verilog_cond) | |
| for i, condition in enumerate(dual_conditions): | |
| # Case condition | |
| verilog_cond = [] | |
| for condition_str in condition.split(","): | |
| sig, val = condition_str.split("=") | |
| if sig == "CONCAT_EN": | |
| continue | |
| cond_part = "(_TECHMAP_CONSTVAL_{}_0_ == {})".format(sig, val) | |
| verilog_cond.append(cond_part) | |
| verilog_cond = " && ".join(verilog_cond) | |
| if i == 0: | |
| verilog += " if ({}) begin\n".format(verilog_cond) | |
| else: | |
| verilog += " end else if ({}) begin\n".format(verilog_cond) | |
| # Instance | |
| mode_name = make_mode_name(condition) | |
| model_name = "RAM_" + mode_name + "_VPR" | |
| verilog += " {} # (\n".format(model_name) | |
| verilog += " .INIT(INIT[18431:0]),\n" | |
| verilog += " )" | |
| verilog += " RAM (\n" | |
| # Ports always mapped 1-to-1 | |
| for key in ["clock", "input", "output"]: | |
| for name, width, assoc_clock in model_ports[key]: | |
| # Parse the port name | |
| alias, index, bit = parse_port_name(name) | |
| if index is None: | |
| pname = alias | |
| else: | |
| pname = "{}_{}".format(alias, index) | |
| if bit is not None: | |
| portspec = "{}_b{}".format(pname, bit) | |
| sigspec = "{}[{}]".format(pname, bit) | |
| else: | |
| portspec = pname | |
| sigspec = pname | |
| verilog += " .{}({}),\n".format(portspec, sigspec) | |
| verilog = verilog[:-2] + "\n" | |
| verilog += " );\n" | |
| # Error catcher | |
| verilog += """ | |
| end else begin | |
| wire _TECHMAP_FAIL_; | |
| end | |
| """ | |
| # Error handler for unexpected configuration | |
| verilog += """ | |
| end else begin | |
| wire _TECHMAP_FAIL_; | |
| end endgenerate | |
| """ | |
| # Footer | |
| verilog += "endmodule\n" | |
| return verilog | |
| # ============================================================================= | |
| def main(): | |
| # Parse arguments | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument( | |
| "--device", type=str, choices=["ql-eos-s3", "ql-pp3e"], required=True | |
| ) | |
| parser.add_argument( | |
| "--mode-defs", | |
| type=str, | |
| default="ram_modes.json", | |
| help="A JSON file defining RAM modes" | |
| ) | |
| parser.add_argument( | |
| "--sdf", type=str, required=True, help="An SDF file with timing data" | |
| ) | |
| parser.add_argument( | |
| "--xml-path", type=str, default="./", help="Output path for XML files" | |
| ) | |
| parser.add_argument( | |
| "--vlog-path", | |
| type=str, | |
| default="./", | |
| help="Output path for Verilog files" | |
| ) | |
| args = parser.parse_args() | |
| # Prepare RAM port blacklist (containing test ports) | |
| port_blacklist = set() | |
| if not RAM_2X1_TEST_PORTS[args.device]: | |
| port_blacklist = set(RAM_TEST_PORTS) | |
| # Load the RAM mode tree definition | |
| with open(args.mode_defs, "r") as fp: | |
| ram_tree = json.load(fp) | |
| # Load RAM timings | |
| with open(args.sdf, "r") as fp: | |
| ram_timings = sdf_timing.sdfparse.parse(fp.read()) | |
| timescale = get_scale_seconds(ram_timings["header"]["timescale"]) | |
| # Make port definition for a single ram | |
| ram_ports_all = blacklist_ports(RAM_2X1_PORTS, port_blacklist) | |
| ram_ports_sing = make_single_ram(ram_ports_all) | |
| # Gather all RAM instances | |
| instances = set() | |
| for v in ram_timings["cells"].values(): | |
| instances |= set(v.keys()) | |
| # Use not normalized names in SDFs (i.e. with brackets) | |
| # TODO: Make this an argument | |
| normalized_names = False | |
| xml_models = {} | |
| # Generate a pb_type tree for each instance | |
| for instance in instances: | |
| print(instance) | |
| # Initialize the top-level pb_type XML | |
| xml_pb_root = make_pb_type("RAM", ram_ports_all, None)[0] | |
| # Wrapper pb_type for split RAM (CONCAT_EN=0) | |
| xml_mode = ET.SubElement(xml_pb_root, "mode", {"name": "SING"}) | |
| xml_sing = [ | |
| make_pb_type("RAM_0", ram_ports_sing, None)[0], | |
| make_pb_type("RAM_1", ram_ports_sing, None)[0], | |
| ] | |
| for x in xml_sing: | |
| xml_mode.append(x) | |
| ic = auto_interconnect(xml_mode) | |
| xml_mode.append(ic) | |
| # Wrapper pb_type for non-split RAM (CONCAT_EN=1) | |
| xml_mode = ET.SubElement(xml_pb_root, "mode", {"name": "DUAL"}) | |
| xml_dual = make_pb_type("RAM_DUAL", ram_ports_all, None)[0] | |
| xml_mode.append(xml_dual) | |
| ic = auto_interconnect(xml_mode) | |
| xml_mode.append(ic) | |
| # Get timings for this instance | |
| all_timings = filter_instances(ram_timings["cells"], instance) | |
| total_timings = 0 | |
| missing_timings = 0 | |
| # Generate RAM modes | |
| for cond in yield_ram_modes(ram_tree): | |
| print("", cond) | |
| mode_name = make_mode_name(cond) | |
| model_name = "RAM_" + mode_name + "_VPR" | |
| # CONCAT_EN=0 - the 2x1 RAM is split into two | |
| if "CONCAT_EN=0" in cond: | |
| if "FIFO_EN=0" in cond: | |
| model_ports = filter_ports(ram_ports_sing, FIFO_PORTS) | |
| else: | |
| model_ports = filter_ports(ram_ports_sing, RAM_PORTS) | |
| if "DIR=1" in cond: | |
| model_ports = remap_clocks(model_ports, FIFO_CLOCK_MAP) | |
| # For each part | |
| for part in [0, 1]: | |
| # Filter timings basing on the generated set of conditions | |
| # and RAM part | |
| timings = filter_cells( | |
| all_timings, | |
| cond, | |
| part, | |
| normalized_names=normalized_names | |
| ) | |
| if DEBUG: | |
| print(" RAM_" + str(part)) | |
| for cname, cdata in timings.items(): | |
| print(" ", cname) | |
| for iname, idata in cdata.items(): | |
| for tname, tdata in idata.items(): | |
| print( | |
| " ", tname, "src={}, dst={}".format( | |
| tdata["from_pin"], tdata["to_pin"] | |
| ) | |
| ) | |
| # Make the mode XML | |
| xml_mode = ET.SubElement( | |
| xml_sing[part], "mode", {"name": mode_name} | |
| ) | |
| # Make the pb_type XML | |
| pb_name = "RAM_{}_{}".format(part, mode_name) | |
| xml_pb, stats = make_pb_type( | |
| pb_name, | |
| split_ports(model_ports), | |
| model_name, | |
| timings, | |
| timescale, | |
| normalized_names=normalized_names, | |
| ) | |
| xml_mode.append(xml_pb) | |
| total_timings += stats["total_timings"] | |
| missing_timings += stats["missing_timings"] | |
| # Make the interconnect | |
| ic = auto_interconnect(xml_mode) | |
| xml_mode.append(ic) | |
| # Make the model XML | |
| xml_model = make_model(model_name, split_ports(model_ports)) | |
| xml_models[model_name] = xml_model | |
| # CONCAT_EN=1 - keep the 2x1 RAM as one | |
| elif "CONCAT_EN=1" in cond: | |
| if "FIFO_EN=0" in cond: | |
| model_ports = filter_ports(ram_ports_all, FIFO_PORTS) | |
| else: | |
| model_ports = filter_ports(ram_ports_all, RAM_PORTS) | |
| if "DIR=1" in cond: | |
| model_ports = remap_clocks(model_ports, FIFO_CLOCK_MAP) | |
| # Filter timings | |
| timings = filter_cells( | |
| all_timings, cond, None, normalized_names=normalized_names | |
| ) | |
| if DEBUG: | |
| print(" Dual RAM") | |
| for cname, cdata in timings.items(): | |
| print(" ", cname) | |
| for iname, idata in cdata.items(): | |
| for tname, tdata in idata.items(): | |
| print( | |
| " ", tname, "src={}, dst={}".format( | |
| tdata["from_pin"], tdata["to_pin"] | |
| ) | |
| ) | |
| # Make the mode XML | |
| xml_mode = ET.SubElement(xml_dual, "mode", {"name": mode_name}) | |
| # Make the pb_type XML | |
| pb_name = "RAM_" + mode_name | |
| xml_pb, stats = make_pb_type( | |
| pb_name, | |
| split_ports(model_ports), | |
| model_name, | |
| timings, | |
| timescale, | |
| normalized_names=normalized_names, | |
| ) | |
| xml_mode.append(xml_pb) | |
| total_timings += stats["total_timings"] | |
| missing_timings += stats["missing_timings"] | |
| # Make the interconnect | |
| ic = auto_interconnect(xml_mode) | |
| xml_mode.append(ic) | |
| # Make the model XML | |
| xml_model = make_model(model_name, split_ports(model_ports)) | |
| xml_models[model_name] = xml_model | |
| # Should not happen | |
| else: | |
| assert False, cond | |
| # Serialize the pb_type XML | |
| fname = os.path.join(args.xml_path, instance.lower() + ".pb_type.xml") | |
| ET.ElementTree(xml_pb_root).write(fname, pretty_print=True) | |
| print("total timings : {}".format(total_timings)) | |
| print( | |
| "missing timings: {} {:.2f}%".format( | |
| missing_timings, 100.0 * missing_timings / total_timings | |
| ) | |
| ) | |
| # Write models XML | |
| xml_model_root = ET.Element("models") | |
| for key in sorted(list(xml_models.keys())): | |
| xml_model_root.append(xml_models[key]) | |
| fname = os.path.join(args.xml_path, "ram.model.xml") | |
| ET.ElementTree(xml_model_root).write(fname, pretty_print=True) | |
| # Make blackboxes | |
| blackboxes = {} | |
| for cond in yield_ram_modes(ram_tree): | |
| mode_name = make_mode_name(cond) | |
| model_name = "RAM_" + mode_name + "_VPR" | |
| # CONCAT_EN=0 - the 2x1 RAM is split into two | |
| if "CONCAT_EN=0" in cond: | |
| if "DIR=1" in cond: | |
| ports = remap_clocks(ram_ports_sing, FIFO_CLOCK_MAP) | |
| else: | |
| ports = ram_ports_sing | |
| if "FIFO_EN=0" in cond: | |
| model_ports = filter_ports(ram_ports_sing, FIFO_PORTS) | |
| else: | |
| model_ports = filter_ports(ram_ports_sing, RAM_PORTS) | |
| if "DIR=1" in cond: | |
| model_ports = remap_clocks(model_ports, FIFO_CLOCK_MAP) | |
| verilog = make_blackbox( | |
| args.device, model_name, split_ports(ports), | |
| split_ports(model_ports) | |
| ) | |
| blackboxes[model_name] = verilog | |
| # CONCAT_EN=1 - keep the 2x1 RAM as one | |
| elif "CONCAT_EN=1" in cond: | |
| if "DIR=1" in cond: | |
| ports = remap_clocks(ram_ports_all, FIFO_CLOCK_MAP) | |
| else: | |
| ports = ram_ports_all | |
| if "FIFO_EN=0" in cond: | |
| model_ports = filter_ports(ram_ports_all, FIFO_PORTS) | |
| else: | |
| model_ports = filter_ports(ram_ports_all, RAM_PORTS) | |
| if "DIR=1" in cond: | |
| model_ports = remap_clocks(model_ports, FIFO_CLOCK_MAP) | |
| verilog = make_blackbox( | |
| args.device, model_name, split_ports(ports), | |
| split_ports(model_ports) | |
| ) | |
| blackboxes[model_name] = verilog | |
| # Write blackbox definitions | |
| fname = os.path.join(args.vlog_path, "ram_sim.v") | |
| with open(fname, "w") as fp: | |
| for k, v in blackboxes.items(): | |
| fp.write(v) | |
| # Make techmap | |
| techmap = make_techmap(port_blacklist, list(yield_ram_modes(ram_tree))) | |
| fname = os.path.join(args.vlog_path, "ram_map.v") | |
| with open(fname, "w") as fp: | |
| fp.write(techmap) | |
| if __name__ == "__main__": | |
| main() |