1+ #!/usr/bin/env python3
2+
3+ import json
4+ import argparse
5+ import os
6+ import sys
7+ import matplotlib .pyplot as plt
8+ import seaborn as sns
9+ import numpy as np
10+
11+ # Set the plotting style
12+ plt .style .use ('seaborn-v0_8' )
13+ sns .set_palette ("husl" )
14+
15+ # Color scheme
16+ LILAC_COLOR = '#C8A2C8'
17+ RV_COLOR = '#4A90E2' # Nice blue that contrasts with lilac
18+
19+ def load_json_data (json_file ):
20+ """Load data from JSON file generated by generate-json-table.py."""
21+ with open (json_file , 'r' ) as f :
22+ return json .load (f )
23+
24+ def extract_resource_data (data , resource ):
25+ """Extract data for a specific resource from the JSON structure."""
26+ lilac_data = {}
27+ rv_data = {}
28+
29+ # Extract Lilac data
30+ if 'Lilac' in data :
31+ for ii , values in data ['Lilac' ].items ():
32+ if resource in values and values [resource ] is not None :
33+ lilac_data [int (ii )] = values [resource ]
34+
35+ # Extract RV data
36+ if 'RV' in data :
37+ for ii , values in data ['RV' ].items ():
38+ if resource in values and values [resource ] is not None :
39+ rv_data [int (ii )] = values [resource ]
40+
41+ return lilac_data , rv_data
42+
43+ def create_side_by_side_plot (lilac_data , rv_data , resource , output_dir ):
44+ """Create side-by-side bar plot comparing Lilac and RV."""
45+ # Get common II values
46+ ii_values = sorted (set (lilac_data .keys ()) | set (rv_data .keys ()))
47+
48+ # Prepare data for plotting
49+ lilac_values = [lilac_data .get (ii , 0 ) for ii in ii_values ]
50+ rv_values = [rv_data .get (ii , 0 ) for ii in ii_values ]
51+
52+ # Create the plot
53+ fig , ax = plt .subplots (figsize = (10 , 6 ))
54+
55+ x = np .arange (len (ii_values ))
56+ width = 0.35
57+
58+ bars1 = ax .bar (x - width / 2 , lilac_values , width , label = 'Lilac' , color = LILAC_COLOR , alpha = 0.8 )
59+ bars2 = ax .bar (x + width / 2 , rv_values , width , label = 'RV' , color = RV_COLOR , alpha = 0.8 )
60+
61+ # Customize the plot
62+ ax .set_xlabel ('II Value' , fontsize = 12 )
63+ ax .set_ylabel (get_y_label (resource ), fontsize = 12 )
64+ ax .set_title (f'{ get_resource_title (resource )} Comparison' , fontsize = 14 , fontweight = 'bold' )
65+ ax .set_xticks (x )
66+ ax .set_xticklabels (ii_values )
67+ ax .legend ()
68+
69+ # Set y-axis to start at 0
70+ ax .set_ylim (bottom = 0 )
71+
72+ # Add horizontal grid lines only
73+ ax .grid (True , axis = 'y' , alpha = 0.3 )
74+ ax .set_axisbelow (True )
75+
76+ # Tight layout
77+ plt .tight_layout ()
78+
79+ # Save the plot
80+ output_path = os .path .join (output_dir , f'{ resource } .pdf' )
81+ plt .savefig (output_path , format = 'pdf' , bbox_inches = 'tight' , dpi = 300 )
82+ plt .close ()
83+
84+ return output_path
85+
86+ def create_normalized_plot (lilac_data , rv_data , resource , output_dir ):
87+ """Create normalized bar plot showing RV/Lilac ratios."""
88+ # Get common II values
89+ ii_values = sorted (set (lilac_data .keys ()) & set (rv_data .keys ()))
90+
91+ if not ii_values :
92+ print (f"Warning: No common II values for { resource } , skipping normalized plot" )
93+ return None
94+
95+ # Calculate normalized values (RV/Lilac)
96+ normalized_values = []
97+ for ii in ii_values :
98+ if lilac_data [ii ] != 0 :
99+ normalized_values .append (rv_data [ii ] / lilac_data [ii ])
100+ else :
101+ normalized_values .append (0 ) # Handle division by zero
102+
103+ # Create the plot
104+ fig , ax = plt .subplots (figsize = (10 , 6 ))
105+
106+ x = np .arange (len (ii_values ))
107+
108+ # Use different colors based on whether RV is better (< 1.0) or worse (> 1.0)
109+ colors = [RV_COLOR if val <= 1.0 else '#E74C3C' for val in normalized_values ]
110+
111+ bars = ax .bar (x , normalized_values , color = colors , alpha = 0.8 )
112+
113+ # Add horizontal line at y=1.0 for reference
114+ ax .axhline (y = 1.0 , color = 'black' , linestyle = '--' , alpha = 0.7 , linewidth = 1 )
115+
116+ # Customize the plot
117+ ax .set_xlabel ('II Value' , fontsize = 12 )
118+ ax .set_ylabel (f'RV / Lilac Ratio' , fontsize = 12 )
119+ ax .set_title (f'{ get_resource_title (resource )} - Normalized (RV/Lilac)' , fontsize = 14 , fontweight = 'bold' )
120+ ax .set_xticks (x )
121+ ax .set_xticklabels (ii_values )
122+
123+ # Set y-axis to start at 0
124+ ax .set_ylim (bottom = 0 )
125+
126+ # Add horizontal grid lines only
127+ ax .grid (True , axis = 'y' , alpha = 0.3 )
128+ ax .set_axisbelow (True )
129+
130+ # Add text annotation for the reference line
131+ ax .text (0.02 , 1.05 , 'Equal performance' , transform = ax .transAxes ,
132+ fontsize = 9 , alpha = 0.7 , verticalalignment = 'bottom' )
133+
134+ # Tight layout
135+ plt .tight_layout ()
136+
137+ # Save the plot
138+ output_path = os .path .join (output_dir , f'{ resource } _normalized.pdf' )
139+ plt .savefig (output_path , format = 'pdf' , bbox_inches = 'tight' , dpi = 300 )
140+ plt .close ()
141+
142+ return output_path
143+
144+ def get_resource_title (resource ):
145+ """Get formatted title for resource."""
146+ titles = {
147+ 'luts' : 'LUT Utilization' ,
148+ 'registers' : 'Register Utilization' ,
149+ 'latency' : 'Latency (Cycles)' ,
150+ 'freq_mhz' : 'Frequency (MHz)' ,
151+ 'frames_per_sec' : 'Frames per Second'
152+ }
153+ return titles .get (resource , resource .replace ('_' , ' ' ).title ())
154+
155+ def get_y_label (resource ):
156+ """Get y-axis label for resource."""
157+ labels = {
158+ 'luts' : 'LUTs' ,
159+ 'registers' : 'Registers' ,
160+ 'latency' : 'Cycles' ,
161+ 'freq_mhz' : 'MHz' ,
162+ 'frames_per_sec' : 'Frames/sec'
163+ }
164+ return labels .get (resource , resource .replace ('_' , ' ' ).title ())
165+
166+ def main ():
167+ parser = argparse .ArgumentParser (description = 'Generate bar plots from JSON synthesis data' )
168+ parser .add_argument ('--json-file' , required = True ,
169+ help = 'Path to JSON file from generate-json-table.py' )
170+ parser .add_argument ('--output-dir' , default = '.' ,
171+ help = 'Directory for output PDFs (default: current directory)' )
172+ parser .add_argument ('--normalized' , action = 'store_true' ,
173+ help = 'Generate normalized plots (RV/Lilac ratios)' )
174+ parser .add_argument ('--resources' , nargs = '+' ,
175+ default = ['luts' , 'registers' , 'latency' , 'freq_mhz' , 'frames_per_sec' ],
176+ help = 'Resources to plot (default: all available)' )
177+
178+ args = parser .parse_args ()
179+
180+ # Load JSON data
181+ try :
182+ data = load_json_data (args .json_file )
183+ except FileNotFoundError :
184+ print (f"Error: JSON file '{ args .json_file } ' not found" , file = sys .stderr )
185+ sys .exit (1 )
186+ except json .JSONDecodeError as e :
187+ print (f"Error parsing JSON file: { e } " , file = sys .stderr )
188+ sys .exit (1 )
189+
190+ # Create output directory if it doesn't exist
191+ os .makedirs (args .output_dir , exist_ok = True )
192+
193+ # Generate plots for each resource
194+ generated_files = []
195+
196+ for resource in args .resources :
197+ print (f"Generating plots for { resource } ..." )
198+
199+ # Extract data for this resource
200+ lilac_data , rv_data = extract_resource_data (data , resource )
201+
202+ if not lilac_data and not rv_data :
203+ print (f"Warning: No data found for resource '{ resource } ', skipping" )
204+ continue
205+
206+ try :
207+ if args .normalized :
208+ output_path = create_normalized_plot (lilac_data , rv_data , resource , args .output_dir )
209+ if output_path :
210+ generated_files .append (output_path )
211+ else :
212+ output_path = create_side_by_side_plot (lilac_data , rv_data , resource , args .output_dir )
213+ generated_files .append (output_path )
214+ except Exception as e :
215+ print (f"Error generating plot for { resource } : { e } " , file = sys .stderr )
216+ continue
217+
218+ print (f"\n Generated { len (generated_files )} plots:" )
219+ for file_path in generated_files :
220+ print (f" { file_path } " )
221+
222+ if __name__ == '__main__' :
223+ main ()
0 commit comments