Skip to content

Commit 27a1b2a

Browse files
committed
yosys synthesis SRAM evaluation
1 parent fe73d24 commit 27a1b2a

File tree

3 files changed

+209
-6
lines changed

3 files changed

+209
-6
lines changed

hw/syn/yosys/Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ synthesis: $(BUILD_DIR)/sources.txt
100100
cd $(BUILD_DIR); TOP=$(TOP_LEVEL_ENTITY) \
101101
SRC_FILE=sources.txt \
102102
SDC_FILE=$(SRC_DIR)/project.sdc \
103+
BB_MODULES="VX_dp_ram_asic,VX_sp_ram_asic" \
103104
$(SYN_LIB_ENV) \
104105
TOOL_DIR=$(SCRIPT_DIR) \
105106
RUN_SYNTH=1 \
@@ -109,6 +110,7 @@ techmap: $(BUILD_DIR)/sources.txt
109110
cd $(BUILD_DIR); TOP=$(TOP_LEVEL_ENTITY) \
110111
SRC_FILE=sources.txt \
111112
SDC_FILE=$(SRC_DIR)/project.sdc \
113+
BB_MODULES="VX_dp_ram_asic,VX_sp_ram_asic" \
112114
$(SYN_LIB_ENV) \
113115
TOOL_DIR=$(SCRIPT_DIR) \
114116
RUN_MAP=1 \
@@ -118,6 +120,7 @@ timing: $(BUILD_DIR)/sources.txt
118120
cd $(BUILD_DIR); TOP=$(TOP_LEVEL_ENTITY) \
119121
SRC_FILE=sources.txt \
120122
SDC_FILE=$(SRC_DIR)/project.sdc \
123+
BB_MODULES="VX_dp_ram_asic,VX_sp_ram_asic" \
121124
$(SYN_LIB_ENV) \
122125
TOOL_DIR=$(SCRIPT_DIR) \
123126
RUN_STA=1 \

hw/syn/yosys/run_synth.sh

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,21 @@
22
# run_synth.sh — minimal Yosys wrapper: generates synth.ys and runs it
33
#
44
# Usage (env):
5-
# TOP=<top> SRC_FILE=<filelist.f> \
6-
# [LIB_TGT=<tech.lib>] [LIB_ROOT=<dir_with_libs>] \
7-
# [SDC_FILE=<constraints.sdc>] \
8-
# [OUT_DIR=out] [RPT_DIR=reports] \
9-
# [RUN_SYNTH=1] [RUN_MAP=1] [RUN_STA=0] \
10-
# [ABC_PERIOD=<ns>] [BB_MODULES="modA,modB"] \
5+
# TOP=<top> SRC_FILE=<filelist.f>
6+
# [LIB_TGT=<tech.lib>]
7+
# [LIB_ROOT=<dir_with_libs>]
8+
# [SDC_FILE=<constraints.sdc>]
9+
# [OUT_DIR=out]
10+
# [RPT_DIR=reports]
11+
# [RUN_SYNTH=1]
12+
# [RUN_MAP=1]
13+
# [RUN_STA=0]
14+
# [ABC_PERIOD=<ns>]
15+
# [BB_MODULES="modA,modB"]
16+
# [SRAM_BIT_AREA=0.1]
17+
# [SRAM_OVERHEAD=100.0]
18+
# [SRAM_W_PORTS="wdata,rdata"]
19+
# [SRAM_A_PORTS="addr,waddr,raddr"]
1120
# ./run_synth.sh
1221
#
1322
set -euo pipefail
@@ -36,6 +45,12 @@ RUN_STA="${RUN_STA:-0}"
3645
ABC_PERIOD="${ABC_PERIOD:-}"
3746
BB_MODULES="${BB_MODULES:-}"
3847

48+
# Area Estimation Defaults
49+
SRAM_BIT_AREA="${SRAM_BIT_AREA:-0.1}"
50+
SRAM_OVERHEAD="${SRAM_OVERHEAD:-100.0}"
51+
SRAM_W_PORTS="wdata,rdata"
52+
SRAM_A_PORTS="addr,waddr,raddr"
53+
3954
mkdir -p "$OUT_DIR" "$RPT_DIR"
4055

4156
YS="$OUT_DIR/synth.ys"
@@ -136,6 +151,27 @@ log "yosys -q -s $YS -l $YLOG"
136151
yosys -q -s "$YS" -l "$YLOG"
137152
stamp "yosys"
138153

154+
# -------- run sram area estimation --------
155+
if [[ -n "$BB_MODULES" ]]; then
156+
if [[ -f "$JOUT" && -f "$SCRIPT_DIR/sram_cost.py" ]]; then
157+
log "Running SRAM Area Estimator..."
158+
BB_ARGS=$(echo "$BB_MODULES" | tr ',' ' ')
159+
160+
python3 "$SCRIPT_DIR/sram_cost.py" "$JOUT" \
161+
--top "$TOP" \
162+
--modules $BB_ARGS \
163+
--width-ports $SRAM_W_PORTS \
164+
--addr-ports $SRAM_A_PORTS \
165+
--bit-area "$SRAM_BIT_AREA" \
166+
--overhead "$SRAM_OVERHEAD" \
167+
| tee "$RPT_DIR/sram_area.rpt"
168+
else
169+
log "Warning: Skipping SRAM estimation. (Missing JSON or script)"
170+
fi
171+
else
172+
log "Skipping SRAM Area Estimation (BB_MODULES is empty)"
173+
fi
174+
139175
# -------- optional OpenSTA (run_sta.tcl colocated) --------
140176
if [[ "$RUN_STA" == "1" ]]; then
141177
STA_SCRIPT="$SCRIPT_DIR/run_sta.tcl"

hw/syn/yosys/sram_cost.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
#!/usr/bin/env python3
2+
import json
3+
import sys
4+
import os
5+
import argparse
6+
7+
# -----------------------------------------------------------------------------
8+
# Robustness Helper
9+
# -----------------------------------------------------------------------------
10+
def get_connection_width(cell, port_candidates):
11+
"""
12+
Determines the width of a port by looking at what is physically connected.
13+
Tries a list of potential port names (e.g., ['wdata', 'rdata']).
14+
"""
15+
connections = cell.get("connections", {})
16+
17+
for port in port_candidates:
18+
# Yosys JSON keys might be escaped (e.g., "\wdata" or "wdata")
19+
candidates = [port, f"\\{port}"]
20+
21+
for c in candidates:
22+
if c in connections:
23+
# The connection value is a list of bits. Length = Width.
24+
conn_bits = connections[c]
25+
if len(conn_bits) > 0:
26+
return len(conn_bits)
27+
return 0
28+
29+
def is_target_module(cell_type, target_modules):
30+
"""
31+
Checks if the cell type matches our Blackbox RAMs.
32+
Handles Yosys prefixing (e.g., '$paramod$...\VX_sp_ram_asic').
33+
"""
34+
# 1. Exact match
35+
if cell_type in target_modules:
36+
return True
37+
38+
# 2. Suffix match
39+
normalized_type = cell_type.replace('\\', '/')
40+
41+
for t in target_modules:
42+
if normalized_type.endswith(f"/{t}"):
43+
return True
44+
45+
return False
46+
47+
def get_arg_list(arg_list):
48+
"""
49+
Helper to flatten mixed space/comma separated args.
50+
Input: ['modA,modB', 'modC'] -> Output: ['modA', 'modB', 'modC']
51+
"""
52+
if not arg_list: return []
53+
result = []
54+
for item in arg_list:
55+
# Split by comma and strip whitespace
56+
for sub_item in item.split(','):
57+
if sub_item.strip():
58+
result.append(sub_item.strip())
59+
return result
60+
61+
# -----------------------------------------------------------------------------
62+
# Hierarchical Calculation
63+
# -----------------------------------------------------------------------------
64+
def get_module_area(mod_name, modules_dict, target_modules, w_ports, a_ports, args, memo, verbose=False):
65+
# Memoization to handle multiple instantiations of the same core/cluster
66+
if mod_name in memo:
67+
return memo[mod_name]
68+
69+
if mod_name not in modules_dict:
70+
return 0.0
71+
72+
total_area = 0.0
73+
cells = modules_dict[mod_name].get("cells", {})
74+
75+
for cell_name, cell_data in cells.items():
76+
cell_type = cell_data.get("type")
77+
78+
if is_target_module(cell_type, target_modules):
79+
# --- HIT: Found a Blackbox RAM Instance ---
80+
81+
# 1. Infer Width (DATAW) from data ports
82+
w = get_connection_width(cell_data, w_ports)
83+
84+
# 2. Infer Depth (SIZE) from address ports (Size = 2 ^ Addr_Bits)
85+
addr_bits = get_connection_width(cell_data, a_ports)
86+
d = 1 << addr_bits if addr_bits > 0 else 0
87+
88+
# 3. Calculate
89+
if w > 0 and d > 0:
90+
inst_area = (w * d * args.bit_area) + args.overhead
91+
total_area += inst_area
92+
if verbose:
93+
print(f" [RAM MATCH] {cell_name} ({cell_type})\n -> inferred: {w}x{d} = {inst_area:.2f}")
94+
else:
95+
if verbose:
96+
print(f" [RAM FAIL] {cell_name} ({cell_type})\n -> Could not infer dims (w={w}, addr_bits={addr_bits})")
97+
98+
else:
99+
# --- MISS: It's a sub-module, recurse down ---
100+
total_area += get_module_area(cell_type, modules_dict, target_modules, w_ports, a_ports, args, memo, verbose)
101+
102+
memo[mod_name] = total_area
103+
return total_area
104+
105+
# -----------------------------------------------------------------------------
106+
# Main
107+
# -----------------------------------------------------------------------------
108+
def main():
109+
parser = argparse.ArgumentParser(description="Robust SRAM Area Estimator via Port Inference")
110+
parser.add_argument("json_file")
111+
parser.add_argument("--top", required=True)
112+
parser.add_argument("-m", "--modules", nargs='+', required=True,
113+
help="Blackbox Module names (comma or space separated)")
114+
parser.add_argument("--width-ports", nargs='+', default=["wdata", "rdata", "din", "dout"],
115+
help="Data ports to infer width (comma or space separated)")
116+
parser.add_argument("--addr-ports", nargs='+', default=["addr", "waddr", "raddr", "address"],
117+
help="Address ports to infer depth (comma or space separated)")
118+
parser.add_argument("--bit-area", type=float, default=0.1)
119+
parser.add_argument("--overhead", type=float, default=100.0)
120+
parser.add_argument("-v", "--verbose", action="store_true")
121+
122+
args = parser.parse_args()
123+
124+
if not os.path.exists(args.json_file):
125+
print(f"Error: File {args.json_file} not found.")
126+
sys.exit(1)
127+
128+
with open(args.json_file, 'r') as f:
129+
data = json.load(f)
130+
131+
modules_dict = data.get("modules", {})
132+
133+
# Clean up input lists (handle commas)
134+
target_modules = set(get_arg_list(args.modules))
135+
w_ports = get_arg_list(args.width_ports)
136+
a_ports = get_arg_list(args.addr_ports)
137+
138+
# Robust Top Module Lookup
139+
real_top = args.top
140+
if real_top not in modules_dict:
141+
for m in modules_dict.keys():
142+
if m == args.top or m.endswith(f"\\{args.top}"):
143+
real_top = m
144+
break
145+
146+
if real_top not in modules_dict:
147+
print(f"Error: Top module '{args.top}' not found in JSON.")
148+
sys.exit(1)
149+
150+
print("\n" + "="*80)
151+
print(f"SRAM AREA ESTIMATION (Connectivity Inference)")
152+
print(f" Top Module : {real_top}")
153+
print(f" Target RAMs : {list(target_modules)}")
154+
print(f" Scanning : Data Ports={w_ports} | Addr Ports={a_ports}")
155+
print("-" * 80)
156+
157+
total_area = get_module_area(real_top, modules_dict, target_modules, w_ports, a_ports, args, {}, args.verbose)
158+
159+
print("-" * 80)
160+
print(f"TOTAL SRAM ESTIMATED AREA: {total_area:.4f} um^2")
161+
print("="*80 + "\n")
162+
163+
if __name__ == "__main__":
164+
main()

0 commit comments

Comments
 (0)