From fa45a4453e975a049ac457e3c7eac3d78934e48c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 3 Oct 2026 08:42:52 +0200 Subject: [PATCH 1/2] FakeRAM2.0: drop the unexplained 3.5 um minimum macro height MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Process.get_macro_dimensions() floored every macro's height at 3.5 um. The floor came in with the ORFS asap7 backend patch (a632406e8) as a bare literal: no comment, nothing in the commit message, and no PDK value it corresponds to. It sits in the shared Process class, so it applied to every FakeRAM2.0 config, not only the asap7 backend. Removed so that whatever depended on it fails loudly. A forced 4x25 memory, as in gen_memories_test.py and tinyRocket's tag_array, now fails with "not enough tracks (num pins: 55, available tracks: 27)". The next commit fixes that from the pin count. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Øyvind Harboe --- tools/FakeRAM2.0/utils/class_process.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tools/FakeRAM2.0/utils/class_process.py b/tools/FakeRAM2.0/utils/class_process.py index c11dae3d6a..487a853e6a 100644 --- a/tools/FakeRAM2.0/utils/class_process.py +++ b/tools/FakeRAM2.0/utils/class_process.py @@ -119,11 +119,6 @@ def get_macro_dimensions(self, width_in_bits, depth, num_banks, additional_heigh total_height += additional_height - min_height = max( - 3.5, self.snap_height_nm / 1000.0 if self.snap_height_nm else 0 - ) - total_height = max(total_height, min_height) - return (total_width, total_height) def get_tech_nm(self): From 208f63bdebcc10a63e2b0de5c7cc8ce1d36d8817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 3 Oct 2026 08:42:52 +0200 Subject: [PATCH 2/2] FakeRAM2.0: make a macro tall enough for its signal pins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signal pins are stacked one per track up the macro's left edge, inset by y_offset at both ends. A shallow memory with many pins came out shorter than that and set_pin_pitches() raised "not enough tracks". The macro is now at least 2 * y_offset + num_pins * pin_pitch tall, computed from values the process config already has. This is what the dropped 3.5 um floor was papering over for one shape. A forced 4x25 asap7 RAM (55 pins) is now 2.8 um tall, not 4.2. Shapes the floor did not cover also generate now: 16x64 and 16x128 asap7 RAMs passed the idiomatic gate but failed with "not enough tracks" even with the floor. Memories whose bitcells already fit their pins are unchanged. set_pin_pitches() rounds before flooring the track count, so a height that fits its pins exactly does not lose a track to float error (46.99999 -> 46). With a 1 nm snap this failed 185 of 14448 swept configurations, and none after. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Øyvind Harboe --- tools/FakeRAM2.0/test/memory_test.py | 47 +++++++++++++++++++++++++ tools/FakeRAM2.0/utils/class_memory.py | 9 ++++- tools/FakeRAM2.0/utils/physical_data.py | 6 +++- 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/tools/FakeRAM2.0/test/memory_test.py b/tools/FakeRAM2.0/test/memory_test.py index bea42cb229..ee586299d4 100755 --- a/tools/FakeRAM2.0/test/memory_test.py +++ b/tools/FakeRAM2.0/test/memory_test.py @@ -77,6 +77,53 @@ def test_memory(self): self.assertEqual(timing_data.cycle_time_ns, 0.1566) self.assertEqual(timing_data.fo4_ps, 9.0632) + def _create_sp_ram(self, process, width, depth): + mem_config = MemoryConfig.from_json( + {"name": "sample", "width": width, "depth": depth, "banks": 1} + ) + return MemoryFactory.create(mem_config, "RAM", "SP", process, TimingData()) + + def _pin_height(self, process, memory): + return 2 * process.y_offset + memory.get_num_pins() * process.pin_pitch_um + + def test_shallow_memory_is_tall_enough_for_its_pins(self): + """ + A 4x25 RAM's bitcells are 1.296 um tall, too short for its 55 pins; + it is made as tall as its pins need, snapped up to 2.8 um + """ + + memory = self._create_sp_ram(self._process, 25, 4) + physical = memory.get_physical_data() + self.assertEqual(memory.get_num_pins(), 55) + self.assertGreaterEqual( + physical.get_height(), self._pin_height(self._process, memory) + ) + self.assertAlmostEqual(physical.get_height(), 2.8, delta=self._delta) + + def test_deep_memory_keeps_bitcell_height(self): + """A memory whose bitcells already fit its pins is not made taller""" + + memory = self._create_sp_ram(self._process, 32, 64) + _, height = self._process.get_macro_dimensions(32, 64, 1, 0) + self.assertGreater(height, self._pin_height(self._process, memory)) + self.assertAlmostEqual( + memory.get_physical_data().get_height(), 21.0, delta=self._delta + ) + + def test_pins_fit_exactly_without_snapping(self): + """ + With a 1 nm snap the height is exactly what the pins need, and + every pin still gets a track + """ + + process_data = TestUtils.get_base_process_data().copy() + process_data["snap_height_nm"] = 1 + process = Process(process_data) + for width in range(1, 300): + # constructing the memory raises if its pins do not fit + memory = self._create_sp_ram(process, width, 4) + self.assertGreaterEqual(memory.get_physical_data().get_pin_pitch(), 0) + if __name__ == "__main__": unittest.main() diff --git a/tools/FakeRAM2.0/utils/class_memory.py b/tools/FakeRAM2.0/utils/class_memory.py index 33d81f9d94..b6631a7aba 100644 --- a/tools/FakeRAM2.0/utils/class_memory.py +++ b/tools/FakeRAM2.0/utils/class_memory.py @@ -42,11 +42,18 @@ def __init__(self, mem_config, process, timing_data): width_um, height_um = self.process.get_macro_dimensions( self.width_in_bits, self.depth, self.num_banks, self.additional_height ) + # Signal pins are stacked one per track up the left edge, inset + # by y_offset at both ends, so the macro must be at least that + # tall; a shallow memory with many pins is otherwise too short + num_pins = self.get_num_pins() + height_um = max( + height_um, + 2 * self.process.y_offset + num_pins * self.process.pin_pitch_um, + ) self.physical.set_extents(width_um, height_um) self.physical.snap_to_grid( self.process.snap_width_nm, self.process.snap_height_nm ) - num_pins = self.get_num_pins() self.physical.set_pin_pitches( self.get_name(), num_pins, diff --git a/tools/FakeRAM2.0/utils/physical_data.py b/tools/FakeRAM2.0/utils/physical_data.py index 5c2aaefdfd..06ab390baa 100644 --- a/tools/FakeRAM2.0/utils/physical_data.py +++ b/tools/FakeRAM2.0/utils/physical_data.py @@ -92,7 +92,11 @@ def set_pin_pitches(self, name, num_pins, min_pin_pitch, y_offset): raise Exception( f"Error: attempting to set pin pitches before height ({name})" ) - number_of_tracks_available = math.floor((h - 2 * y_offset) / min_pin_pitch) + # Rounded before flooring: a height that fits its pins exactly would + # otherwise lose a track to float error (e.g. 46.99999 -> 46) + number_of_tracks_available = math.floor( + round((h - 2 * y_offset) / min_pin_pitch, 6) + ) number_of_spare_tracks = number_of_tracks_available - num_pins if number_of_spare_tracks < 0: