| #!/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() |