Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions flow/scripts/memories/gen_memories_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import json
import re
import sys
import tempfile
import unittest
Expand Down Expand Up @@ -134,6 +135,36 @@ def test_additional_memories_forces_conversion(self):
self.assertTrue(tags["idiomatic"])
self.assertIn("forced", tags["reason"])

def test_deep_memory_is_folded_square(self):
# 512x17 with one bit line per column is 2.3 x 166.6 um, 73:1, a
# sliver macro placement cannot fit; the column mux folds it. The
# fold squares the bit-cell counts, and a bit cell is not square,
# so the bound leaves room for that.
netlist = json.loads(json.dumps(SAMPLE_YOSYS_NETLIST))
mem = netlist["modules"].pop("\\mem_128x32")
mem["cells"]["\\$mem_0"]["parameters"].update(SIZE=512, WIDTH=17)
netlist["modules"] = {"\\mem_512x17": mem}
d = Path(self.tmp.name)
(d / "netlist.json").write_text(json.dumps(netlist))
code = gen_memories.main(
[
"--platform",
"asap7",
"--out-dir",
str(d / "memories"),
"--json",
str(d / "memories.json"),
"--yosys-json",
str(d / "netlist.json"),
]
)
self.assertEqual(code, 0)
lef = (d / "memories" / "mem_512x17.lef").read_text()
size = re.search(r"SIZE\s+(\S+)\s+BY\s+(\S+)", lef)
self.assertIsNotNone(size, "no SIZE in the generated LEF")
width, height = map(float, size.groups())
self.assertLessEqual(max(width, height) / min(width, height), 8.0)

def test_unsupported_platform_fails_clearly(self):
code, _d = self.run_generator(platform="nangate45")
self.assertEqual(code, 1)
Expand Down
1 change: 0 additions & 1 deletion tools/FakeRAM2.0/orfs_asap7/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"manufacturing_grid_nm": 1,
"contacted_poly_pitch_nm": 54,
"fin_pitch_nm": 27,
"column_mux_factor": 1,
"snap_width_nm": 190,
"snap_height_nm": 1400,
}
Expand Down
33 changes: 33 additions & 0 deletions tools/FakeRAM2.0/test/macro_dim_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,39 @@ def test_macro_dim_bitcell_override(self):
self.assertAlmostEqual(width, exp_width, delta=self._delta)
self.assertAlmostEqual(height, exp_height, delta=self._delta)

def _dimensions(self, column_mux_factor, width, depth, banks):
"""Macro dimensions, column_mux_factor None leaves the key out"""

process_data = TestUtils.get_base_process_data().copy()
del process_data["column_mux_factor"]
if column_mux_factor is not None:
process_data["column_mux_factor"] = column_mux_factor
return Process(process_data).get_macro_dimensions(width, depth, banks, 0)

def test_macro_dim_column_mux_picked(self):
"""An absent column_mux_factor folds a deep memory square"""

# sqrt(512 / 17) = 5.5, the nearest power of two is 4
picked = self._dimensions(None, 17, 512, 1)
self.assertEqual(picked, self._dimensions(4, 17, 512, 1))
width, height = picked
self.assertLessEqual(max(width, height) / min(width, height), 8.0)

def test_macro_dim_column_mux_explicit(self):
"""An explicit column_mux_factor is kept, even when it is a sliver"""

width, height = self._dimensions(1, 17, 512, 1)
self.assertGreater(height / width, 50.0)

def test_macro_dim_column_mux_after_banks(self):
"""The factor is picked for the array banking leaves"""

# 4 banks make 2048x39 a 512x156 array: sqrt(512 / 156) = 1.8,
# so 2, where sqrt(2048 / 39) = 7.2 would have given 8
self.assertEqual(
self._dimensions(None, 39, 2048, 4), self._dimensions(2, 39, 2048, 4)
)


if __name__ == "__main__":
unittest.main()
8 changes: 8 additions & 0 deletions tools/FakeRAM2.0/test/process_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,14 @@ def test_process_optional_snap(self):
+ (process.get_pin_width_um() / 2.0),
)

def test_process_optional_column_mux(self):
"""Tests that column_mux_factor is left to be picked per memory"""

process_data = self._base_data.copy()
del process_data["column_mux_factor"]
process = Process(process_data)
self.assertIsNone(process.get_column_mux_factor())

def test_process_misaligned_pin_mfg_grid_pitch(self):
"""
Tests detection that pin pitch is not a multiple of the metal track
Expand Down
41 changes: 38 additions & 3 deletions tools/FakeRAM2.0/utils/class_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,32 @@
################################################################################


import math


def pick_column_mux_factor(rows, bits):
"""Fold a tall array into a square one.

The array is rows/factor cells tall and bits*factor cells wide, so
the factor that squares it is sqrt(rows/bits). Rounded to a power of
two because a column mux selects one of 2^n bit lines, and clamped
at 1 so a wide, shallow memory is left alone.

Area does not change: this moves cells between the two dimensions
rather than adding or removing any.
"""
if rows <= 0 or bits <= 0:
return 1
ideal = math.sqrt(float(rows) / float(bits))
if ideal <= 1.0:
return 1
lo = 2 ** math.floor(math.log2(ideal))
hi = lo * 2
# Nearest in log space, which is what "closest to square" means when
# the candidates are powers of two.
return lo if (ideal / lo) <= (hi / ideal) else hi


class Process:
def __init__(self, json_data):
"""Initialize from json_data imported from config file"""
Expand All @@ -23,9 +49,14 @@ def __init__(self, json_data):
self.contacted_poly_pitch_nm = json_data.get("contacted_poly_pitch_nm", None)
self.fin_pitch_nm = json_data.get("fin_pitch_nm", None)
self.manufacturing_grid_nm = int(json_data["manufacturing_grid_nm"])
self.column_mux_factor = int(json_data["column_mux_factor"])

# Optional keys
# Absent means get_macro_dimensions picks one per memory
self.column_mux_factor = (
int(json_data["column_mux_factor"])
if "column_mux_factor" in json_data
else None
)
self.snap_width_nm = (
int(json_data["snap_width_nm"]) if "snap_width_nm" in json_data else 1
)
Expand Down Expand Up @@ -99,7 +130,6 @@ def get_macro_dimensions(self, width_in_bits, depth, num_banks, additional_heigh
and process parameters
"""

column_mux_factor = self.column_mux_factor
bitcell_width, bitcell_height = self.get_bitcell_dimensions()

all_bitcell_height = bitcell_height * depth
Expand All @@ -111,6 +141,11 @@ def get_macro_dimensions(self, width_in_bits, depth, num_banks, additional_heigh
elif num_banks != 1:
raise Exception("Unsupported number of banks: {}".format(num_banks))

column_mux_factor = self.column_mux_factor
if column_mux_factor is None:
column_mux_factor = pick_column_mux_factor(
depth / num_banks, width_in_bits * num_banks
)
all_bitcell_height = all_bitcell_height / column_mux_factor
all_bitcell_width = all_bitcell_width * column_mux_factor

Expand Down Expand Up @@ -202,7 +237,7 @@ def get_y_step(self):
return self.y_step

def get_column_mux_factor(self):
"""Returns the column mux factor"""
"""Returns the column mux factor, None if picked per memory"""
return self.column_mux_factor

def set_calc_dimensions(self, val):
Expand Down