virtuoso_cli/ocean/
mod.rs1pub mod corner;
2
3use crate::client::bridge::escape_skill_string;
4use corner::{AnalysisConfig, CornerConfig};
5use std::collections::HashMap;
6
7pub fn setup_skill(lib: &str, cell: &str, view: &str, simulator: &str) -> String {
8 let lib = escape_skill_string(lib);
9 let cell = escape_skill_string(cell);
10 let view = escape_skill_string(view);
11 format!(
13 "unless(simulator() == '{simulator} simulator('{simulator}))\ndesign(\"{lib}\" \"{cell}\" \"{view}\")\nresultsDir()"
14 )
15}
16
17pub fn analysis_skill(config: &AnalysisConfig) -> String {
18 let typ = &config.analysis_type;
19 let mut skill = format!("analysis('{typ}");
20 for (k, v) in &config.params {
21 let val = match v {
22 serde_json::Value::String(s) => format!(" ?{k} \"{s}\""),
23 serde_json::Value::Number(n) => format!(" ?{k} {n}"),
24 other => format!(" ?{k} \"{other}\""),
25 };
26 skill.push_str(&val);
27 }
28 skill.push(')');
29 skill
30}
31
32pub fn analysis_skill_simple(typ: &str, params: &HashMap<String, String>) -> String {
33 let mut skill = format!("analysis('{typ}");
34 for (k, v) in params {
35 if v == "t" || v == "nil" || v.parse::<f64>().is_ok() {
37 skill.push_str(&format!(" ?{k} {v}"));
38 } else {
39 skill.push_str(&format!(" ?{k} \"{v}\""));
40 }
41 }
42 skill.push(')');
43 skill
44}
45
46pub fn run_skill() -> String {
47 "run()".into()
48}
49
50pub fn measure_skill(analysis_type: &str, exprs: &[String]) -> String {
51 if exprs.len() == 1 {
52 format!("selectResult('{analysis_type})\n{}", exprs[0])
53 } else {
54 let body = exprs
55 .iter()
56 .map(|e| format!(" {e}"))
57 .collect::<Vec<_>>()
58 .join("\n");
59 format!("selectResult('{analysis_type})\nlist(\n{body}\n)")
60 }
61}
62
63pub fn sweep_skill(
64 var: &str,
65 values: &[f64],
66 analysis_type: &str,
67 measure_exprs: &[String],
68) -> String {
69 let var = escape_skill_string(var);
70 let values_str = values
71 .iter()
72 .map(|v| format!("{v:e}"))
73 .collect::<Vec<_>>()
74 .join(" ");
75
76 let measures = measure_exprs
77 .iter()
78 .map(|e| format!(" {e}"))
79 .collect::<Vec<_>>()
80 .join("\n");
81
82 format!(
83 r#"let((results)
84 results = nil
85 foreach(val '({values_str})
86 desVar("{var}" val)
87 run()
88 selectResult('{analysis_type})
89 results = cons(list(val
90{measures}
91 ) results)
92 )
93 reverse(results)
94)"#
95 )
96}
97
98pub fn corner_skill(config: &CornerConfig) -> String {
99 let model_file = escape_skill_string(&config.model_file);
100 let analysis = analysis_skill(&config.analysis);
101
102 let _corner_entries: Vec<String> = config
104 .corners
105 .iter()
106 .map(|c| {
107 let _name = escape_skill_string(&c.name);
108 let section = escape_skill_string(&c.section);
109 let vars: Vec<String> = c
111 .vars
112 .iter()
113 .map(|(k, v)| {
114 let val = match v {
115 serde_json::Value::Number(n) => n.to_string(),
116 serde_json::Value::String(s) => format!("\"{s}\""),
117 other => other.to_string(),
118 };
119 format!(" desVar(\"{k}\" {val})")
120 })
121 .collect();
122 let vars_code = vars.join("\n");
123 format!(
124 r#" ;; Corner: {name}
125 modelFile('("{model_file}" "") "{section}")
126 temp({temp})
127{vars_code}"#,
128 name = c.name,
129 temp = c.temp,
130 )
131 })
132 .collect();
133
134 let measures = config
135 .measures
136 .iter()
137 .map(|m| format!(" {}", m.expr))
138 .collect::<Vec<_>>()
139 .join("\n");
140
141 let _corner_names: Vec<String> = config
143 .corners
144 .iter()
145 .map(|c| format!("\"{}\"", escape_skill_string(&c.name)))
146 .collect();
147
148 let mut skill = format!(
149 "simulator('{sim})\ndesign(\"{lib}\" \"{cell}\" \"{view}\")\n{analysis}\n",
150 sim = config.simulator.as_deref().unwrap_or("spectre"),
151 lib = escape_skill_string(&config.design.lib),
152 cell = escape_skill_string(&config.design.cell),
153 view = escape_skill_string(&config.design.view),
154 );
155
156 skill.push_str("let((results)\n results = nil\n");
157
158 for corner in config.corners.iter() {
159 let name = escape_skill_string(&corner.name);
160 let section = escape_skill_string(&corner.section);
161 let vars_code: String = corner
162 .vars
163 .iter()
164 .map(|(k, v)| {
165 let val = match v {
166 serde_json::Value::Number(n) => n.to_string(),
167 serde_json::Value::String(s) => format!("\"{s}\""),
168 other => other.to_string(),
169 };
170 format!(" desVar(\"{k}\" {val})\n")
171 })
172 .collect();
173
174 skill.push_str(&format!(
175 r#" ;; {name}
176 modelFile('("{model_file}" "") "{section}")
177 temp({temp})
178{vars_code} run()
179 selectResult('{analysis_type})
180 results = cons(list("{name}" {temp}
181{measures}
182 ) results)
183"#,
184 temp = corner.temp,
185 analysis_type = config.analysis.analysis_type,
186 ));
187 }
188
189 skill.push_str(" reverse(results)\n)");
190 skill
191}
192
193pub fn parse_skill_list(output: &str) -> Vec<Vec<String>> {
195 let output = output.trim();
196 if output.is_empty() || output == "nil" {
197 return Vec::new();
198 }
199
200 let mut results = Vec::new();
201 let mut depth = 0i32;
202 let mut current_row = Vec::new();
203 let mut current_token = String::new();
204
205 for ch in output.chars() {
206 match ch {
207 '(' => {
208 depth += 1;
209 if depth == 1 {
210 continue;
212 }
213 if depth == 2 {
214 current_row.clear();
216 continue;
217 }
218 current_token.push(ch);
219 }
220 ')' => {
221 depth -= 1;
222 if depth == 1 {
223 if !current_token.is_empty() {
225 current_row.push(current_token.trim().trim_matches('"').to_string());
226 current_token.clear();
227 }
228 if !current_row.is_empty() {
229 results.push(current_row.clone());
230 }
231 continue;
232 }
233 if depth == 0 {
234 if !current_token.is_empty() {
236 current_row.push(current_token.trim().trim_matches('"').to_string());
237 current_token.clear();
238 }
239 if !current_row.is_empty() && results.is_empty() {
240 results.push(current_row.clone());
241 }
242 continue;
243 }
244 current_token.push(ch);
245 }
246 ' ' | '\t' | '\n' => {
247 if !current_token.is_empty() {
248 current_row.push(current_token.trim().trim_matches('"').to_string());
249 current_token.clear();
250 }
251 }
252 _ => {
253 current_token.push(ch);
254 }
255 }
256 }
257
258 if results.is_empty() && !output.starts_with('(') {
260 results.push(vec![output.trim_matches('"').to_string()]);
261 }
262
263 results
264}