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!(
15 r#"progn(unless(simulator()=='{simulator} simulator('{simulator})) design("{lib}" "{cell}" "{view}") resultsDir())"#
16 )
17}
18
19pub fn analysis_skill(config: &AnalysisConfig) -> String {
20 let typ = &config.analysis_type;
21 let mut skill = format!("analysis('{typ}");
22 for (k, v) in &config.params {
23 let val = match v {
24 serde_json::Value::String(s) => format!(" ?{k} \"{s}\""),
25 serde_json::Value::Number(n) => format!(" ?{k} {n}"),
26 other => format!(" ?{k} \"{other}\""),
27 };
28 skill.push_str(&val);
29 }
30 skill.push(')');
31 skill
32}
33
34pub fn analysis_skill_simple(typ: &str, params: &HashMap<String, String>) -> String {
35 let mut skill = format!("analysis('{typ}");
36 for (k, v) in params {
37 if v == "t" || v == "nil" || v.parse::<f64>().is_ok() {
39 skill.push_str(&format!(" ?{k} {v}"));
40 } else {
41 skill.push_str(&format!(" ?{k} \"{v}\""));
42 }
43 }
44 skill.push(')');
45 skill
46}
47
48pub fn run_skill() -> String {
49 "run()".into()
50}
51
52pub fn measure_skill(analysis_type: &str, exprs: &[String]) -> String {
53 if exprs.len() == 1 {
54 format!("selectResult('{analysis_type})\n{}", exprs[0])
55 } else {
56 let body = exprs
57 .iter()
58 .map(|e| format!(" {e}"))
59 .collect::<Vec<_>>()
60 .join("\n");
61 format!("selectResult('{analysis_type})\nlist(\n{body}\n)")
62 }
63}
64
65pub fn sweep_skill(
66 var: &str,
67 values: &[f64],
68 analysis_type: &str,
69 measure_exprs: &[String],
70) -> String {
71 let var = escape_skill_string(var);
72 let values_str = values
73 .iter()
74 .map(|v| format!("{v:e}"))
75 .collect::<Vec<_>>()
76 .join(" ");
77
78 let measures = measure_exprs
79 .iter()
80 .map(|e| format!(" {e}"))
81 .collect::<Vec<_>>()
82 .join("\n");
83
84 format!(
85 r#"let((results)
86 results = nil
87 foreach(val '({values_str})
88 desVar("{var}" val)
89 run()
90 selectResult('{analysis_type})
91 results = cons(list(val
92{measures}
93 ) results)
94 )
95 reverse(results)
96)"#
97 )
98}
99
100pub fn corner_skill(config: &CornerConfig) -> String {
101 let model_file = escape_skill_string(&config.model_file);
102 let analysis = analysis_skill(&config.analysis);
103
104 let _corner_entries: Vec<String> = config
106 .corners
107 .iter()
108 .map(|c| {
109 let _name = escape_skill_string(&c.name);
110 let section = escape_skill_string(&c.section);
111 let vars: Vec<String> = c
113 .vars
114 .iter()
115 .map(|(k, v)| {
116 let val = match v {
117 serde_json::Value::Number(n) => n.to_string(),
118 serde_json::Value::String(s) => format!("\"{s}\""),
119 other => other.to_string(),
120 };
121 format!(" desVar(\"{k}\" {val})")
122 })
123 .collect();
124 let vars_code = vars.join("\n");
125 format!(
126 r#" ;; Corner: {name}
127 modelFile('("{model_file}" "") "{section}")
128 temp({temp})
129{vars_code}"#,
130 name = c.name,
131 temp = c.temp,
132 )
133 })
134 .collect();
135
136 let measures = config
137 .measures
138 .iter()
139 .map(|m| format!(" {}", m.expr))
140 .collect::<Vec<_>>()
141 .join("\n");
142
143 let _corner_names: Vec<String> = config
145 .corners
146 .iter()
147 .map(|c| format!("\"{}\"", escape_skill_string(&c.name)))
148 .collect();
149
150 let mut skill = format!(
151 "simulator('{sim})\ndesign(\"{lib}\" \"{cell}\" \"{view}\")\n{analysis}\n",
152 sim = config.simulator.as_deref().unwrap_or("spectre"),
153 lib = escape_skill_string(&config.design.lib),
154 cell = escape_skill_string(&config.design.cell),
155 view = escape_skill_string(&config.design.view),
156 );
157
158 skill.push_str("let((results)\n results = nil\n");
159
160 for corner in config.corners.iter() {
161 let name = escape_skill_string(&corner.name);
162 let section = escape_skill_string(&corner.section);
163 let vars_code: String = corner
164 .vars
165 .iter()
166 .map(|(k, v)| {
167 let val = match v {
168 serde_json::Value::Number(n) => n.to_string(),
169 serde_json::Value::String(s) => format!("\"{s}\""),
170 other => other.to_string(),
171 };
172 format!(" desVar(\"{k}\" {val})\n")
173 })
174 .collect();
175
176 skill.push_str(&format!(
177 r#" ;; {name}
178 modelFile('("{model_file}" "") "{section}")
179 temp({temp})
180{vars_code} run()
181 selectResult('{analysis_type})
182 results = cons(list("{name}" {temp}
183{measures}
184 ) results)
185"#,
186 temp = corner.temp,
187 analysis_type = config.analysis.analysis_type,
188 ));
189 }
190
191 skill.push_str(" reverse(results)\n)");
192 skill
193}
194
195pub fn parse_skill_list(output: &str) -> Vec<Vec<String>> {
197 let output = output.trim();
198 if output.is_empty() || output == "nil" {
199 return Vec::new();
200 }
201
202 let mut results = Vec::new();
203 let mut depth = 0i32;
204 let mut current_row = Vec::new();
205 let mut current_token = String::new();
206
207 for ch in output.chars() {
208 match ch {
209 '(' => {
210 depth += 1;
211 if depth == 1 {
212 continue;
214 }
215 if depth == 2 {
216 current_row.clear();
218 continue;
219 }
220 current_token.push(ch);
221 }
222 ')' => {
223 depth -= 1;
224 if depth == 1 {
225 if !current_token.is_empty() {
227 current_row.push(current_token.trim().trim_matches('"').to_string());
228 current_token.clear();
229 }
230 if !current_row.is_empty() {
231 results.push(current_row.clone());
232 }
233 continue;
234 }
235 if depth == 0 {
236 if !current_token.is_empty() {
238 current_row.push(current_token.trim().trim_matches('"').to_string());
239 current_token.clear();
240 }
241 if !current_row.is_empty() && results.is_empty() {
242 results.push(current_row.clone());
243 }
244 continue;
245 }
246 current_token.push(ch);
247 }
248 ' ' | '\t' | '\n' => {
249 if !current_token.is_empty() {
250 current_row.push(current_token.trim().trim_matches('"').to_string());
251 current_token.clear();
252 }
253 }
254 _ => {
255 current_token.push(ch);
256 }
257 }
258 }
259
260 if results.is_empty() && !output.starts_with('(') {
262 results.push(vec![output.trim_matches('"').to_string()]);
263 }
264
265 results
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271 use std::collections::HashMap;
272
273 #[test]
274 fn setup_skill_format() {
275 let s = setup_skill("myLib", "myCell", "schematic", "spectre");
276 assert!(s.starts_with("progn("), "{s}");
278 assert!(
279 s.contains("design(\"myLib\" \"myCell\" \"schematic\")"),
280 "{s}"
281 );
282 assert!(s.contains("spectre"), "{s}");
283 assert!(s.contains("resultsDir()"), "{s}");
284 }
285
286 #[test]
287 fn setup_skill_escapes_lib() {
288 let s = setup_skill(r#"my"Lib"#, "cell", "schematic", "spectre");
289 assert!(s.contains(r#"my\"Lib"#), "{s}");
290 }
291
292 #[test]
293 fn analysis_skill_simple_boolean_unquoted() {
294 let mut params = HashMap::new();
295 params.insert("start".into(), "1e-9".into());
296 params.insert("stop".into(), "1e-6".into());
297 params.insert("conservative".into(), "t".into());
298 let s = analysis_skill_simple("tran", ¶ms);
299 assert!(s.starts_with("analysis('tran"), "{s}");
300 assert!(s.contains("?conservative t"), "{s}");
302 }
303
304 #[test]
305 fn analysis_skill_simple_string_quoted() {
306 let mut params = HashMap::new();
307 params.insert("errpreset".into(), "moderate".into());
308 let s = analysis_skill_simple("tran", ¶ms);
309 assert!(s.contains("?errpreset \"moderate\""), "{s}");
310 }
311
312 #[test]
313 fn sweep_skill_uses_desvar() {
314 let values = vec![1.0, 1.2, 1.8];
315 let exprs = vec!["VT(\"M1\" \"VGS\")".to_string()];
316 let s = sweep_skill("Vdd", &values, "dc", &exprs);
317 assert!(s.contains("desVar(\"Vdd\" val)"), "{s}");
318 assert!(s.contains("run()"), "{s}");
319 assert!(s.contains("reverse(results)"), "{s}");
320 }
321
322 #[test]
323 fn parse_skill_list_nested() {
324 let rows = parse_skill_list("((1.0 2.0) (3.0 4.0))");
325 assert_eq!(rows.len(), 2);
326 assert_eq!(rows[0], vec!["1.0", "2.0"]);
327 assert_eq!(rows[1], vec!["3.0", "4.0"]);
328 }
329
330 #[test]
331 fn parse_skill_list_single_value() {
332 let rows = parse_skill_list("42");
333 assert_eq!(rows, vec![vec!["42"]]);
334 }
335}