diff --git a/tools/FakeRAM2.0/orfs_asap7/generate.py b/tools/FakeRAM2.0/orfs_asap7/generate.py index ebec9c7ec9..1e39732d99 100644 --- a/tools/FakeRAM2.0/orfs_asap7/generate.py +++ b/tools/FakeRAM2.0/orfs_asap7/generate.py @@ -1,4 +1,20 @@ +"""ORFS asap7 backend: emit .lib/.lef views for the memories AUTO_MEMORIES converts. + +The input is memories.json, ORFS's inventory. Every converted memory +carries the pin list of the module the flow blackboxes, and the macro has +to expose exactly those pins or the blackboxed module never links to its +liberty view. So the macro is built from the declared pins: a single +read-write port -- clock, address, enable, write mode, data in, data out +and an optional write mask with one lane per mask bit -- becomes a +single-port RAM whose ports carry the module's names. + +A memory whose pins were synthesized by the detector rather than read off +the module (port_convention "") has no such names and gets FakeRAM's +historical single-port pins, which is what earlier ORFS releases emitted. +""" + import json +import math import sys from pathlib import Path @@ -32,6 +48,57 @@ "leakage": 0.001, } +# memories.json pin function -> the role SinglePortRAM names its port by. +_ROLE_OF_FUNCTION = { + "clk": "clk", + "addr": "addr", + "en": "ce", + "wmode": "we", + "data_in": "din", + "data_out": "dout", + "mask": "mask", +} + + +def single_port_pins(mem: dict): + """The (pin_names, mask_lanes) of a memory declared as one RW port. + + Returns None when the pins are not a single read-write port carrying + the functions a single-port RAM has: the caller then falls back to + FakeRAM's historical pin names. + """ + pins = mem.get("pins") or [] + groups = {p["port_id"] for p in pins} + if len(groups) != 1 or not next(iter(groups)).startswith("RW"): + return None + names = {} + lanes = 0 + for p in pins: + role = _ROLE_OF_FUNCTION.get(p["function"]) + if role is None or role in names: + return None + names[role] = p["name"] + if role == "mask": + lanes = int(p["width"]) + if not {"clk", "addr", "ce", "we", "din", "dout"} <= set(names): + return None + return names, lanes + + +def additional_height_for_pins(process: Process, bits: int, rows: int, lanes: int): + """Height to add so every pin gets a track on the macro's left edge. + + FakeRAM sizes the macro from its bit cells and then fails when the + pins do not fit; a memory folded square by the column mux with a + per-bit mask is exactly that case. The extra height is computed here + rather than left to the caller to discover one macro at a time. + """ + addr_w = max(1, math.ceil(math.log2(rows))) if rows > 1 else 1 + num_pins = 2 * bits + addr_w + 3 + lanes + _, height = process.get_macro_dimensions(bits, rows, 1, 0) + needed = (num_pins + 1) * process.pin_pitch_um + 2 * process.y_offset + return max(0.0, needed - height) + def run_orfs_asap7(platform: str, out_dir: Path, json_path: Path): if platform != "asap7": @@ -56,14 +123,29 @@ def run_orfs_asap7(platform: str, out_dir: Path, json_path: Path): blackboxes = [] for m in converted: name = m["name"] - bits = m.get("bits", 32) - rows = m.get("rows", 128) + bits = int(m.get("bits", 32)) + rows = int(m.get("rows", 128)) + + pin_names = {} + lanes = 0 + declared = single_port_pins(m) + if declared is not None: + pin_names, lanes = declared + elif m.get("port_convention"): + sys.stderr.write( + f"FakeRAM2.0 orfs_asap7: {name} declares pins that are not a " + "single read-write port; nothing this backend emits would link\n" + ) + return 1 sram_dict = { "name": name, "width": bits, "depth": rows, "banks": 1, + "additional_height": additional_height_for_pins(process, bits, rows, lanes), + "pin_names": pin_names, + "mask_lanes": lanes, } mem_config = MemoryConfig.from_json(sram_dict) ram = MemoryFactory.create(mem_config, "RAM", "SP", process, timing_data) diff --git a/tools/FakeRAM2.0/test/spsram_mask_test.py b/tools/FakeRAM2.0/test/spsram_mask_test.py new file mode 100644 index 0000000000..1aa7ea1fe6 --- /dev/null +++ b/tools/FakeRAM2.0/test/spsram_mask_test.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 +"""A single-port RAM with declared pin names and write-mask lanes. + +The ORFS asap7 backend builds the macro from the pins of the module it +replaces, so the views must carry those names and the mask bus, and a +memory without either must come out exactly as before. +""" + +import io +import json +import os +import sys +import tempfile +import unittest +from pathlib import Path + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "utils"))) +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) +from class_process import Process +from memory_config import MemoryConfig +from memory_factory import MemoryFactory +from timing_data import TimingData +from ram_liberty_exporter import RAMLibertyExporter +from lef_exporter import LefExporter +from test_utils import TestUtils +from orfs_asap7.generate import ( + ASAP7_TIMING_CONFIG, + additional_height_for_pins, + run_orfs_asap7, + single_port_pins, +) + +RW0_NAMES = { + "clk": "RW0_clk", + "ce": "RW0_en", + "we": "RW0_wmode", + "addr": "RW0_addr", + "din": "RW0_wdata", + "dout": "RW0_rdata", + "mask": "RW0_wmask", +} + + +def rw0_pins(bits, addr_w, lanes): + """memories.json pins for a firtool array_x module.""" + pin = lambda name, direction, width, function: { + "name": name, + "direction": direction, + "width": width, + "port_id": "RW0", + "function": function, + } + pins = [ + pin("RW0_addr", "input", addr_w, "addr"), + pin("RW0_en", "input", 1, "en"), + pin("RW0_clk", "input", 1, "clk"), + pin("RW0_wmode", "input", 1, "wmode"), + pin("RW0_wdata", "input", bits, "data_in"), + pin("RW0_rdata", "output", bits, "data_out"), + ] + if lanes: + pins.append(pin("RW0_wmask", "input", lanes, "mask")) + return pins + + +class MaskedSinglePortRAMTest(unittest.TestCase): + def setUp(self): + self.process = Process(TestUtils.get_base_process_data()) + self.timing = TimingData(ASAP7_TIMING_CONFIG) + + def make(self, name, bits, rows, pin_names=None, lanes=0): + extra = additional_height_for_pins(self.process, bits, rows, lanes) + cfg = MemoryConfig(name, bits, rows, 1, extra, pin_names, lanes) + return MemoryFactory.create(cfg, "RAM", "SP", self.process, self.timing) + + def test_declared_names_and_mask_reach_liberty_and_lef(self): + ram = self.make("array_64x114", 114, 64, RW0_NAMES, 2) + lib = io.StringIO() + RAMLibertyExporter(ram).export(lib) + lib = lib.getvalue() + for pin in ("RW0_clk", "RW0_en", "RW0_wmode"): + self.assertIn(f"pin({pin})", lib) + for bus in ("RW0_addr", "RW0_wdata", "RW0_rdata", "RW0_wmask"): + self.assertIn(f"bus({bus})", lib) + self.assertIn("type (array_64x114_RW0_wmask)", lib) + self.assertIn("bit_width : 2;", lib) + self.assertNotIn("we_in", lib) + self.assertNotIn("ce_in", lib) + lef = io.StringIO() + LefExporter(ram).export(lef) + lef = lef.getvalue() + self.assertIn("PIN RW0_wmask[0]", lef) + self.assertIn("PIN RW0_wmask[1]", lef) + self.assertNotIn("PIN RW0_wmask[2]", lef) + self.assertIn("PIN RW0_rdata[113]", lef) + + def test_per_bit_mask_gets_a_track_per_pin(self): + # 76 lanes on a 76-bit word: the pins outnumber the tracks the bit + # cells alone would give the macro, so the height has to grow. + ram = self.make("array_128x76", 76, 128, RW0_NAMES, 76) + lef = io.StringIO() + LefExporter(ram).export(lef) + self.assertIn("PIN RW0_wmask[75]", lef.getvalue()) + + def test_unmasked_default_names_are_unchanged(self): + ram = self.make("spsram_256x32", 32, 256) + lib = io.StringIO() + RAMLibertyExporter(ram).export(lib) + lib = lib.getvalue() + for pin in ("we_in", "ce_in", "clk"): + self.assertIn(f"pin({pin})", lib) + for bus in ("addr_in", "wd_in", "rd_out"): + self.assertIn(f"bus({bus})", lib) + self.assertNotIn("w_mask_in", lib) + + def test_verilog_writes_lanes_under_the_mask(self): + ram = self.make("array_64x114", 114, 64, RW0_NAMES, 2) + with tempfile.TemporaryDirectory() as d: + path = os.path.join(d, "m.v") + ram.write_verilog_file(path) + v = Path(path).read_text() + bb = os.path.join(d, "m.sv") + ram.write_verilog_file(bb, True) + sv = Path(bb).read_text() + self.assertIn("parameter MASK_LANES = 2;", v) + self.assertIn("parameter LANE_WIDTH = 57;", v) + self.assertIn("if (RW0_wmask[j])", v) + self.assertIn("input [MASK_LANES-1:0] RW0_wmask;", v) + self.assertIn("input [1:0] RW0_wmask", sv) + + def test_mask_must_divide_the_word(self): + with self.assertRaises(Exception): + MemoryConfig("m", 114, 64, 1, 0, RW0_NAMES, 5) + + +class SinglePortPinsTest(unittest.TestCase): + def test_reads_names_and_lanes(self): + names, lanes = single_port_pins({"pins": rw0_pins(114, 6, 2)}) + self.assertEqual(names, RW0_NAMES) + self.assertEqual(lanes, 2) + + def test_unmasked_port(self): + names, lanes = single_port_pins({"pins": rw0_pins(66, 8, 0)}) + self.assertNotIn("mask", names) + self.assertEqual(lanes, 0) + + def test_separate_ports_are_not_single_port(self): + pins = rw0_pins(32, 5, 0) + pins[0]["port_id"] = "R0" + self.assertIsNone(single_port_pins({"pins": pins})) + + def test_synthesized_pins_are_not_single_port(self): + self.assertIsNone(single_port_pins({"pins": []})) + + +class BackendTest(unittest.TestCase): + def test_emits_views_named_by_the_module(self): + inventory = { + "version": 1, + "platform": "asap7", + "memories": [ + { + "name": "array_64x114", + "rows": 64, + "bits": 114, + "addr_w": 6, + "read_ports": 0, + "write_ports": 0, + "rw_ports": 1, + "mask_lanes": 2, + "port_convention": "firtool", + "pins": rw0_pins(114, 6, 2), + "idiomatic": True, + "reason": "meets ASAP7 macro floors", + }, + { + "name": "diff_rat_state_bank_704x776", + "rows": 704, + "bits": 776, + "idiomatic": False, + "reason": "1 combinational read port(s)", + }, + ], + } + with tempfile.TemporaryDirectory() as d: + inv = Path(d) / "memories.json" + inv.write_text(json.dumps(inventory)) + out = Path(d) / "memories" + self.assertEqual(run_orfs_asap7("asap7", out, inv), 0) + self.assertEqual( + sorted(p.name for p in out.iterdir()), + [ + "array_64x114.lef", + "array_64x114.lib", + "array_64x114_pre_layout.lib", + "blackboxes.txt", + ], + ) + lib = (out / "array_64x114.lib").read_text() + self.assertIn("cell(array_64x114)", lib) + self.assertIn("bus(RW0_wmask)", lib) + self.assertIn("pin(RW0_en)", lib) + # ` `: synthesis costs the blackbox by its area + [line] = (out / "blackboxes.txt").read_text().splitlines() + module, area = line.split() + self.assertEqual(module, "array_64x114") + self.assertGreater(float(area), 0) + + def test_declared_separate_ports_are_refused(self): + pins = rw0_pins(32, 5, 0) + for p in pins: + p["port_id"] = "R0" if p["function"] in ("data_out",) else "W0" + inventory = { + "version": 1, + "memories": [ + { + "name": "ram_32x32", + "rows": 32, + "bits": 32, + "port_convention": "firtool", + "pins": pins, + "idiomatic": True, + } + ], + } + with tempfile.TemporaryDirectory() as d: + inv = Path(d) / "memories.json" + inv.write_text(json.dumps(inventory)) + self.assertEqual(run_orfs_asap7("asap7", Path(d) / "out", inv), 1) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/FakeRAM2.0/utils/basic_port_creator.py b/tools/FakeRAM2.0/utils/basic_port_creator.py index 9bd263e6b8..16b1cab404 100644 --- a/tools/FakeRAM2.0/utils/basic_port_creator.py +++ b/tools/FakeRAM2.0/utils/basic_port_creator.py @@ -96,6 +96,16 @@ def create_signals(self, rw_port_group, y_step, pin_pitch, group_pitch): pin_pitch, ) y_step += group_pitch + if rw_port_group.get_write_mask_bus_name(): + y_step = self.write_signal_bus( + rw_port_group.get_write_mask_bus_name(), + 0, + rw_port_group.get_write_mask_lanes(), + Port.Direction.INPUT, + y_step, + pin_pitch, + ) + y_step += group_pitch y_step = self.write_signal_bus( rw_port_group.get_address_bus_name(), 0, diff --git a/tools/FakeRAM2.0/utils/class_memory.py b/tools/FakeRAM2.0/utils/class_memory.py index b6631a7aba..1cb9046dbc 100644 --- a/tools/FakeRAM2.0/utils/class_memory.py +++ b/tools/FakeRAM2.0/utils/class_memory.py @@ -36,6 +36,7 @@ def __init__(self, mem_config, process, timing_data): self.width_in_bytes = math.ceil(self.width_in_bits / 8.0) self.total_size = self.width_in_bytes * self.depth self.additional_height = mem_config.get_additional_height() + self.mask_lanes = mem_config.get_mask_lanes() self.timing_data = timing_data self.physical = PhysicalData() if self.process.calc_dimensions(): diff --git a/tools/FakeRAM2.0/utils/lef_exporter.py b/tools/FakeRAM2.0/utils/lef_exporter.py index ea65feee22..3fc0c65755 100644 --- a/tools/FakeRAM2.0/utils/lef_exporter.py +++ b/tools/FakeRAM2.0/utils/lef_exporter.py @@ -140,6 +140,13 @@ def write_signals(self, fid, rw_port_group): ) if rw_port_group.get_data_input_bus_name(): self.write_signal_bus(fid, rw_port_group.get_data_input_bus_name(), 0, bits) + if rw_port_group.get_write_mask_bus_name(): + self.write_signal_bus( + fid, + rw_port_group.get_write_mask_bus_name(), + 0, + rw_port_group.get_write_mask_lanes(), + ) if rw_port_group.get_address_bus_name(): self.write_signal_bus( fid, diff --git a/tools/FakeRAM2.0/utils/liberty_exporter.py b/tools/FakeRAM2.0/utils/liberty_exporter.py index ef3f87e9bd..c1396b3b21 100644 --- a/tools/FakeRAM2.0/utils/liberty_exporter.py +++ b/tools/FakeRAM2.0/utils/liberty_exporter.py @@ -155,6 +155,15 @@ def write_bus_defs(self, out_fh): bus_data["msb"] - bus_data["lsb"] + 1, bus_data["msb"], ) + for rw_port_group in mem.get_rw_port_groups(): + if rw_port_group.get_write_mask_bus_name(): + lanes = rw_port_group.get_write_mask_lanes() + self.write_bus_def( + out_fh, + mem.get_name() + "_" + rw_port_group.get_write_mask_bus_name(), + lanes, + lanes - 1, + ) for bus_name, bus_data in mem.get_misc_busses().items(): self.write_bus_def( out_fh, @@ -442,6 +451,10 @@ def write_rw_pin_set(self, out_fh, name, rw_port_group, is_ram): is_ram, addr_bus_name, ) + if rw_port_group.get_write_mask_bus_name(): + self.write_generic_bus( + out_fh, name, rw_port_group.get_write_mask_bus_name(), clk_pin_name + ) if rw_port_group.get_data_output_bus_name(): self.write_output_bus( out_fh, diff --git a/tools/FakeRAM2.0/utils/memory_config.py b/tools/FakeRAM2.0/utils/memory_config.py index 843bb85c8a..db202c76d7 100644 --- a/tools/FakeRAM2.0/utils/memory_config.py +++ b/tools/FakeRAM2.0/utils/memory_config.py @@ -4,7 +4,16 @@ class MemoryConfig: """Container for functional memory configuration""" - def __init__(self, name, width_in_bits, depth, num_banks, additional_height): + def __init__( + self, + name, + width_in_bits, + depth, + num_banks, + additional_height, + pin_names=None, + mask_lanes=0, + ): """Initializer""" self._name = name @@ -12,6 +21,17 @@ def __init__(self, name, width_in_bits, depth, num_banks, additional_height): self._depth = depth self._num_banks = num_banks self._additional_height = additional_height + # Optional: the port names the macro must carry, keyed by role + # (clk, ce, we, addr, din, dout, mask), and the number of write + # mask lanes. Absent, the memory keeps its historical pin names + # and writes whole words. + self._pin_names = dict(pin_names) if pin_names else {} + self._mask_lanes = int(mask_lanes or 0) + if self._mask_lanes and width_in_bits % self._mask_lanes: + raise Exception( + "Width %d is not a whole number of %d mask lanes (%s)" + % (width_in_bits, self._mask_lanes, name) + ) def set_name(self, name): """Sets the name""" @@ -37,6 +57,14 @@ def get_additional_height(self): """Returns the additional_height""" return self._additional_height + def get_pin_names(self): + """Returns the role -> port name dictionary (may be empty)""" + return self._pin_names + + def get_mask_lanes(self): + """Returns the number of write mask lanes, 0 when unmasked""" + return self._mask_lanes + @staticmethod def from_json(json_data): return MemoryConfig( @@ -45,4 +73,6 @@ def from_json(json_data): int(json_data["depth"]), int(json_data["banks"]), json_data.get("additional_height", 0), + json_data.get("pin_names"), + json_data.get("mask_lanes", 0), ) diff --git a/tools/FakeRAM2.0/utils/rw_port_group.py b/tools/FakeRAM2.0/utils/rw_port_group.py index 3f3c4549e7..8b71ff4aaa 100644 --- a/tools/FakeRAM2.0/utils/rw_port_group.py +++ b/tools/FakeRAM2.0/utils/rw_port_group.py @@ -37,6 +37,9 @@ def __init__(self, suffix=None): self._clk_name = None self._related_pin_list = [] self._related_busses = {} + # Optional write mask: one enable per lane of the data word. + self._write_mask_bus_name = None + self._write_mask_lanes = 0 def set_suffix(self, suffix): """ @@ -86,6 +89,19 @@ def get_data_output_bus_name(self): """Gets the data output bus name""" return self._data_output_bus_name + def set_write_mask_bus(self, name, lanes): + """Sets the write mask bus name and its lane count""" + self._write_mask_bus_name = name + self._write_mask_lanes = lanes + + def get_write_mask_bus_name(self): + """Gets the write mask bus name, or None when unmasked""" + return self._write_mask_bus_name + + def get_write_mask_lanes(self): + """Gets the number of write mask lanes, 0 when unmasked""" + return self._write_mask_lanes + def add_related_pin(self, name): """Adds related pin""" self._related_pin_list.append(name) diff --git a/tools/FakeRAM2.0/utils/single_port_ram.py b/tools/FakeRAM2.0/utils/single_port_ram.py index 1f3f2d39a2..f1a7409c7a 100755 --- a/tools/FakeRAM2.0/utils/single_port_ram.py +++ b/tools/FakeRAM2.0/utils/single_port_ram.py @@ -36,19 +36,24 @@ def __init__(self, mem_config, process_data, timing_data): """ RAM.__init__(self, mem_config, process_data, timing_data) rw_port_group = RWPortGroup() - rw_port_group.set_write_enable_name("we_in") - rw_port_group.set_address_bus_name("addr_in") - rw_port_group.set_data_input_bus_name("wd_in") - rw_port_group.set_data_output_bus_name("rd_out") - rw_port_group.set_clock_name("clk") + names = mem_config.get_pin_names() + rw_port_group.set_write_enable_name(names.get("we", "we_in")) + rw_port_group.set_address_bus_name(names.get("addr", "addr_in")) + rw_port_group.set_data_input_bus_name(names.get("din", "wd_in")) + rw_port_group.set_data_output_bus_name(names.get("dout", "rd_out")) + rw_port_group.set_clock_name(names.get("clk", "clk")) + if self.mask_lanes: + rw_port_group.set_write_mask_bus( + names.get("mask", "w_mask_in"), self.mask_lanes + ) self.add_rw_port_group(rw_port_group) - self.add_misc_port("ce_in") + self.add_misc_port(names.get("ce", "ce_in")) self.create_ports() def get_num_pins(self): """Returns the total number of logical pins""" # rd_out (#bits) + wd_in (#bits) + addr_in (#addr_width) + we_in/ce_in/clk - return (2 * self.get_width()) + self.get_addr_width() + 3 + return (2 * self.get_width()) + self.get_addr_width() + 3 + self.mask_lanes def write_verilog_file(self, out_file_name, is_blackbox=False): """ diff --git a/tools/FakeRAM2.0/utils/single_port_ram_liberty_exporter.py b/tools/FakeRAM2.0/utils/single_port_ram_liberty_exporter.py index 8ec41e0288..5f556242e8 100644 --- a/tools/FakeRAM2.0/utils/single_port_ram_liberty_exporter.py +++ b/tools/FakeRAM2.0/utils/single_port_ram_liberty_exporter.py @@ -46,3 +46,7 @@ def write_cell(self, out_fh): True, addr_bus_name, ) + if rw_port_group.get_write_mask_bus_name(): + self.write_generic_bus( + out_fh, name, rw_port_group.get_write_mask_bus_name(), clk_pin_name + ) diff --git a/tools/FakeRAM2.0/utils/single_port_ram_verilog_exporter.py b/tools/FakeRAM2.0/utils/single_port_ram_verilog_exporter.py index 6955b8be3f..e5c1a84157 100644 --- a/tools/FakeRAM2.0/utils/single_port_ram_verilog_exporter.py +++ b/tools/FakeRAM2.0/utils/single_port_ram_verilog_exporter.py @@ -24,6 +24,14 @@ def _get_names(self): ce_pin = next(iter(mem.get_misc_ports())) return (addr_bus, din_bus, dout_bus, we_pin, clk_pin, ce_pin) + def _get_mask(self): + """Returns (mask bus name, lanes); (None, 0) when unmasked""" + rw_port_group = self.get_memory().get_rw_port_groups()[0] + return ( + rw_port_group.get_write_mask_bus_name(), + rw_port_group.get_write_mask_lanes(), + ) + def export_module(self, out_fh): """Exports the verilog module to the output stream""" @@ -59,7 +67,15 @@ def export_module(self, out_fh): out_fh.write(" end\n") out_fh.write(f" else if ({we_pin})\n") out_fh.write(" begin\n") - out_fh.write(f" mem[{addr_bus}] <= {din_bus};\n") + mask_bus, lanes = self._get_mask() + if mask_bus: + out_fh.write(" for (j = 0; j < MASK_LANES; j = j + 1)\n") + out_fh.write(f" if ({mask_bus}[j])\n") + out_fh.write( + f" mem[{addr_bus}][j*LANE_WIDTH +: LANE_WIDTH] <= {din_bus}[j*LANE_WIDTH +: LANE_WIDTH];\n" + ) + else: + out_fh.write(f" mem[{addr_bus}] <= {din_bus};\n") out_fh.write(" end\n") out_fh.write(" // read\n") out_fh.write(f" {dout_bus} <= mem[{addr_bus}];\n") @@ -85,12 +101,20 @@ def write_module_ports(self, out_fh): out_fh.write(f" {we_pin},\n") out_fh.write(f" {din_bus},\n") out_fh.write(f" {clk_pin},\n") - out_fh.write(f" {ce_pin}\n") + mask_bus, lanes = self._get_mask() + if mask_bus: + out_fh.write(f" {ce_pin},\n") + out_fh.write(f" {mask_bus}\n") + else: + out_fh.write(f" {ce_pin}\n") out_fh.write(");\n") out_fh.write(f" parameter BITS = {mem.get_width()};\n") out_fh.write(f" parameter WORD_DEPTH = {mem.get_depth()};\n") out_fh.write(f" parameter ADDR_WIDTH = {mem.get_addr_width()};\n") out_fh.write(f" parameter corrupt_mem_on_X_p = 1;\n") + if mask_bus: + out_fh.write(f" parameter MASK_LANES = {lanes};\n") + out_fh.write(f" parameter LANE_WIDTH = {mem.get_width() // lanes};\n") out_fh.write("\n") out_fh.write(f" output reg [BITS-1:0] {dout_bus};\n") out_fh.write(f" input [ADDR_WIDTH-1:0] {addr_bus};\n") @@ -98,6 +122,8 @@ def write_module_ports(self, out_fh): out_fh.write(f" input [BITS-1:0] {din_bus};\n") out_fh.write(f" input {clk_pin};\n") out_fh.write(f" input {ce_pin};\n") + if mask_bus: + out_fh.write(f" input [MASK_LANES-1:0] {mask_bus};\n") out_fh.write("\n") def write_timing_check(self, out_fh): @@ -130,6 +156,11 @@ def write_timing_check(self, out_fh): ) out_fh.write( f" $setuphold (posedge {clk_pin}, {din_bus}, 0, 0, notifier);\n" + + ( + f" $setuphold (posedge {clk_pin}, {self._get_mask()[0]}, 0, 0, notifier);\n" + if self._get_mask()[0] + else "" + ) ) out_fh.write(" endspecify\n") out_fh.write("\n") @@ -147,5 +178,10 @@ def export_blackbox(self, out_fh): out_fh.write(f" input {we_pin},\n") out_fh.write(f" input [{data_bus_msb}:0] {din_bus},\n") out_fh.write(f" input {clk_pin},\n") - out_fh.write(f" input {ce_pin}\n") + mask_bus, lanes = self._get_mask() + if mask_bus: + out_fh.write(f" input {ce_pin},\n") + out_fh.write(f" input [{lanes - 1}:0] {mask_bus}\n") + else: + out_fh.write(f" input {ce_pin}\n") self.export_bb_footer(out_fh)