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