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