Skip to content

Commit c83a9c0

Browse files
committed
Added ability to generate single port RAM from spreadsheet
Signed-off-by: Jeff Ng <jeffng@precisioninno.com>
1 parent 1a61aaa commit c83a9c0

29 files changed

+7023
-125
lines changed

spreadsheet_ram.py

Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
#!/usr/bin/env python3
2+
3+
import re
4+
import sys
5+
import csv
6+
import argparse
7+
import importlib.util
8+
9+
from utils.timing_data import TimingData
10+
from utils.memory_config import MemoryConfig
11+
from utils.class_process import Process
12+
from utils.run_utils import RunUtils
13+
from utils.rw_port_group import RWPortGroup
14+
from utils.ss_port_creator import SSPortCreator
15+
from utils.single_port_ssram import SinglePortSSRAM
16+
17+
# TODO
18+
# support dual port
19+
# support reg file
20+
21+
#
22+
# Class to generate a single port RAM from customer-specific spreadsheet input
23+
#
24+
# Usage: spreadsheet_ram.py --config <fakeram_config> --physical <physical_csv>
25+
# --mem_config <metrics_csv>
26+
# --mapping <custom_mapping> --output_dir <output_dir>
27+
#
28+
# where
29+
# fakeram_config - standard FakeRAM2.0 JSON config
30+
# physical_csv - CSV file containing physical data such as size, pins (layer
31+
# and rect) and obstructions
32+
# metrics_csv - CSV file containing power and timing characteristics
33+
# custom_mapping - Python3 file containing two mapping routines that are
34+
# custom-specific (see below)
35+
# output_dir - output directory name
36+
#
37+
38+
39+
class SSRAMGenerator:
40+
"""Container class for generating a spreadsheet-based memory"""
41+
42+
def __init__(self, config_file, util_file):
43+
"""Initializer"""
44+
self._import_custom_mappings(util_file)
45+
# Process is required for the voltage
46+
self._process = Process(RunUtils.get_config(config_file))
47+
48+
def _import_custom_mappings(self, file_name):
49+
"""
50+
Import custom maps into the self._util_module
51+
52+
file must contain the following methods:
53+
54+
get_pin_type_map - returns a dictionary that maps a pin name or bus name
55+
string to a string that indicates the type of the pin
56+
or bus. Recognized values include: address_bus,
57+
data_bus, output_bus, write_enable, clock, power,
58+
ground
59+
60+
Example:
61+
pin_type_map = {
62+
"addr_in": "address_bus",
63+
"data_in": "data_bus",
64+
"rd_out": "output_bus",
65+
"we_in": "write_enable",
66+
"clk": "clock",
67+
"VSS": "ground",
68+
"VDD": "power",
69+
}
70+
71+
get_key_map - returns a dictionary that maps the CSV column to a
72+
dictionary that helps the SSRAMGenerator properly handle
73+
the data in its own structures. The dictionary recognizes
74+
the following keys:
75+
76+
key: name of the field in the functional or timing
77+
data object
78+
type: object type to convert into
79+
conversion: optional field for unit conversion (csv
80+
value is multiplied by the specified value
81+
82+
Example:
83+
key_map = {
84+
"Num Words": { "key": "depth", "type": int },
85+
"Num Bits": { "key": "width", "type": int },
86+
"Num Banks": { "key": "banks", "type": int },
87+
"Memory Name": { "key": "name", "type": str },
88+
"cin (pf)":{ "key": "cap_input_pf", "type": float },
89+
"corner.Ts (ns)": { "key": "t_setup_ns", "type": float },
90+
"corner.Th (ns)": { "key": "t_hold_ns", "type": float },
91+
"corner.Ta (ns)": { "key": "access_time_ns", "type": float },
92+
"corner.Tc (ns)": { "key": "cycle_time_ns", "type": float },
93+
"Static Power (uW)": { "key": "standby_leakage_per_bank_mW", "type": float, "conversion": 1e-3 },
94+
"Dynamic Power (uW/MHz)": { "key": "pin_dynamic_power_mW", "type": float , "conversion": 1e-3},
95+
}
96+
"""
97+
98+
module_name = "spreadsheet_utils"
99+
spec = importlib.util.spec_from_file_location(module_name, file_name)
100+
self._util_module = importlib.util.module_from_spec(spec)
101+
sys.modules[module_name] = self._util_module
102+
spec.loader.exec_module(self._util_module)
103+
self._pin_type_map = self._util_module.get_pin_type_map()
104+
self._key_map = self._util_module.get_key_map()
105+
106+
def classify_pin(self, pin_name):
107+
"""
108+
Returns the pin classification to help identify whether the pin or bus
109+
is the address, data in, data out, write enable, clock or power pin or
110+
bus
111+
"""
112+
if pin_name in self._pin_type_map:
113+
return self._pin_type_map[pin_name]
114+
return None
115+
116+
def create_memory(self, mem_config, physical):
117+
"""Extracts the data from the CSV files and returns the memory object"""
118+
119+
# Get the physical data and organize it
120+
phys_data = self.read_physical_file(physical)
121+
pins = self.organize_pins(phys_data)
122+
num_pins = len(phys_data["pin_data"])
123+
124+
# Get the metrics data and organize it
125+
macro_metrics = self.read_metrics_file(mem_config, phys_data["name"])
126+
timing_data = TimingData(macro_metrics)
127+
mem_config = MemoryConfig.from_json(macro_metrics)
128+
129+
mem = SinglePortSSRAM(mem_config, self._process, timing_data, num_pins)
130+
self.set_logical_pins(mem, pins)
131+
port_creator = SSPortCreator(mem, self._pin_type_map)
132+
port_creator.create_ports(phys_data["pin_data"])
133+
if "obs" in phys_data:
134+
port_creator.create_obs(phys_data["obs"])
135+
mem.get_physical_data().set_extents(
136+
float(phys_data["width"]), float(phys_data["height"])
137+
)
138+
# snap to grid to sync up the physical data fields
139+
mem.get_physical_data().snap_to_grid(1, 1)
140+
return mem
141+
142+
def read_physical_file(self, file_name):
143+
"""
144+
Reads the physical data CSV file and returns a dictionary that includes
145+
the pin and obstruction data
146+
"""
147+
148+
macro_data = {"pin_data": {}, "obs": []}
149+
with open(file_name, "r", encoding="utf-8-sig") as csv_fh:
150+
reader = csv.DictReader(csv_fh)
151+
is_first = True
152+
for row in reader:
153+
# MACRO,SIZE_WIDTH,SIZE_HEIGHT,SOURCE,PIN,USE,LAYER,x1,y1,x2,y2
154+
if is_first:
155+
macro_data["name"] = row["MACRO"]
156+
macro_data["width"] = row["SIZE_WIDTH"]
157+
macro_data["height"] = row["SIZE_HEIGHT"]
158+
is_first = False
159+
source = row["SOURCE"]
160+
if source == "PIN":
161+
pin_data = {
162+
"name": row["PIN"],
163+
"use": row["USE"],
164+
"layer": row["LAYER"],
165+
"rect": [
166+
float(row["x1"]),
167+
float(row["y1"]),
168+
float(row["x2"]),
169+
float(row["y2"]),
170+
],
171+
}
172+
if pin_data["name"] not in macro_data["pin_data"]:
173+
macro_data["pin_data"][pin_data["name"]] = pin_data
174+
else: # pragma: no cover
175+
raise Exception(
176+
"{} had multiple pin shapes".format(pin_data["name"])
177+
)
178+
elif source == "OBS":
179+
obs_data = {
180+
"layer": row["LAYER"],
181+
"rect": [
182+
float(row["x1"]),
183+
float(row["y1"]),
184+
float(row["x2"]),
185+
float(row["y2"]),
186+
],
187+
}
188+
macro_data["obs"].append(obs_data)
189+
else:
190+
print(
191+
"Skipping {} since source is {}".format(
192+
row["PIN"], row["SOURCE"]
193+
)
194+
)
195+
return macro_data
196+
197+
def organize_pins(self, macro_data):
198+
"""
199+
Iterates through the macro_data and creates a pin dictionary that
200+
maps the pin or bus name to a dictionary that includes the pin name,
201+
msb, lsb, and type
202+
"""
203+
204+
pins = {}
205+
bus_name_re = re.compile("^(\S+)\[(\d+)\]")
206+
for pin_name, pin_data in macro_data["pin_data"].items():
207+
result = bus_name_re.match(pin_name)
208+
if result:
209+
bus_name = result.group(1)
210+
bit_num = int(result.group(2))
211+
if bus_name in pins:
212+
pins[bus_name]["lsb"] = min(bit_num, pins[bus_name]["lsb"])
213+
pins[bus_name]["msb"] = max(bit_num, pins[bus_name]["msb"])
214+
else:
215+
pins[bus_name] = {
216+
"name": bus_name,
217+
"msb": bit_num,
218+
"lsb": bit_num,
219+
"type": self.classify_pin(bus_name),
220+
}
221+
else:
222+
if pin_name in pins: # pragma: no cover
223+
raise Exception(f"pin {pin_name} appears twice")
224+
pins[pin_name] = {"name": pin_name, "type": self.classify_pin(pin_name)}
225+
226+
return pins
227+
228+
def get_size_keys(self):
229+
"""Returns the keys that map to depth and width"""
230+
231+
depth_key = width_key = None
232+
for key, val in self._key_map.items():
233+
if val.get("key") == "depth":
234+
depth_key = key
235+
elif val.get("key") == "width":
236+
width_key = key
237+
return (depth_key, width_key)
238+
239+
def read_metrics_file(self, file_name, macro_name):
240+
"""
241+
Reads the metrics CSV file to extract the power and timing data.
242+
243+
Returns a dictionary that has been normalized to our expected metrics
244+
and Process/TimingData names
245+
246+
The depth and width are present on the first row and apply to all
247+
subsequent rows until they are set again on a subsequent row.
248+
Effectively, the cells should have been merged, but weren't.
249+
"""
250+
macro_metrics = {}
251+
depth = width = None
252+
(depth_key, width_key) = self.get_size_keys()
253+
with open(file_name, "r", encoding="utf-8-sig") as csv_fh:
254+
reader = csv.DictReader(csv_fh)
255+
for row in reader:
256+
if row[depth_key]:
257+
depth = row[depth_key]
258+
if row[width_key]:
259+
width = row[width_key]
260+
if row["memory_name"] == macro_name:
261+
row[depth_key] = depth
262+
row[width_key] = width
263+
for csv_key, metric_key_data in self._key_map.items():
264+
if csv_key in row and row[csv_key] != "N/A":
265+
metric_key = metric_key_data["key"]
266+
metric_type_fn = metric_key_data["type"]
267+
metric_conv_factor = metric_key_data.get("conversion", 1)
268+
macro_metrics[metric_key] = (
269+
metric_type_fn(row[csv_key]) * metric_conv_factor
270+
)
271+
return macro_metrics
272+
273+
def set_logical_pins(self, mem, pins):
274+
"""Sets the pins to be used for Verilog and Liberty output"""
275+
rw_port_group = RWPortGroup()
276+
mem.add_rw_port_group(rw_port_group)
277+
for pin_name, pin_data in pins.items():
278+
pin_type = pin_data["type"]
279+
if pin_type == "clock":
280+
rw_port_group.set_clock_name(pin_name)
281+
elif pin_type in ["power", "ground"]:
282+
# skip
283+
pass
284+
elif pin_type == "address_bus":
285+
rw_port_group.set_address_bus_name(pin_name)
286+
elif pin_type == "data_bus":
287+
rw_port_group.set_data_input_bus_name(pin_name)
288+
elif pin_type == "output_bus":
289+
rw_port_group.set_data_output_bus_name(pin_name)
290+
elif pin_type == "write_enable":
291+
rw_port_group.set_write_enable_name(pin_name)
292+
elif "msb" in pin_data:
293+
bus = {"name": pin_name, "msb": pin_data["msb"], "lsb": pin_data["lsb"]}
294+
mem.add_misc_bus(bus)
295+
else:
296+
mem.add_misc_port(pin_name)
297+
298+
@staticmethod
299+
def main():
300+
"""Main driver"""
301+
parser = argparse.ArgumentParser(
302+
description="Create a set of memory colllateral from a spreadsheet input"
303+
)
304+
parser.add_argument(
305+
"--config",
306+
help="Input configuration file containing technology parameters",
307+
required=True,
308+
)
309+
parser.add_argument(
310+
"--mem_config",
311+
help="CSV file containing technical parameters such as size, timing, power",
312+
required=True,
313+
)
314+
parser.add_argument(
315+
"--physical",
316+
help="CSV file containing physical data such as pin locations and layers",
317+
required=True,
318+
)
319+
parser.add_argument(
320+
"--mapping", help="Custom Python mapping file", required=True
321+
)
322+
parser.add_argument(
323+
"--output_dir",
324+
action="store",
325+
help="Output directory ",
326+
required=False,
327+
default="results",
328+
)
329+
330+
args = parser.parse_args()
331+
rep = SSRAMGenerator(args.config, args.mapping)
332+
mem = rep.create_memory(args.mem_config, args.physical)
333+
RunUtils.write_memory(mem, args.output_dir)
334+
335+
336+
if __name__ == "__main__":
337+
SSRAMGenerator.main()

test/au/dprf_256x256.au

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9834,7 +9834,7 @@ cell(dprf_256x256) {
98349834
"0.050, 0.050" \
98359835
)
98369836
}
9837-
}
9837+
}
98389838
timing() {
98399839
related_pin : clk_a;
98409840
timing_type : hold_rising ;
@@ -9889,7 +9889,7 @@ cell(dprf_256x256) {
98899889
"0.050, 0.050" \
98909890
)
98919891
}
9892-
}
9892+
}
98939893
timing() {
98949894
related_pin : clk_a;
98959895
timing_type : hold_rising ;
@@ -9944,7 +9944,7 @@ cell(dprf_256x256) {
99449944
"0.050, 0.050" \
99459945
)
99469946
}
9947-
}
9947+
}
99489948
timing() {
99499949
related_pin : clk_a;
99509950
timing_type : hold_rising ;
@@ -10061,7 +10061,7 @@ cell(dprf_256x256) {
1006110061
"0.050, 0.050" \
1006210062
)
1006310063
}
10064-
}
10064+
}
1006510065
timing() {
1006610066
related_pin : clk_b;
1006710067
timing_type : hold_rising ;
@@ -10116,7 +10116,7 @@ cell(dprf_256x256) {
1011610116
"0.050, 0.050" \
1011710117
)
1011810118
}
10119-
}
10119+
}
1012010120
timing() {
1012110121
related_pin : clk_b;
1012210122
timing_type : hold_rising ;
@@ -10171,7 +10171,7 @@ cell(dprf_256x256) {
1017110171
"0.050, 0.050" \
1017210172
)
1017310173
}
10174-
}
10174+
}
1017510175
timing() {
1017610176
related_pin : clk_b;
1017710177
timing_type : hold_rising ;

0 commit comments

Comments
 (0)