Skip to main content

virtuoso_cli/client/
maestro_ops.rs

1use crate::client::bridge::escape_skill_string;
2
3pub struct MaestroOps;
4
5impl MaestroOps {
6    /// Open maestro session in background (non-GUI) mode.
7    /// Returns session string like "fnxSession4".
8    pub fn open_session(&self, lib: &str, cell: &str, view: &str) -> String {
9        let lib = escape_skill_string(lib);
10        let cell = escape_skill_string(cell);
11        let view = escape_skill_string(view);
12        format!(r#"maeOpenSetup("{lib}" "{cell}" "{view}")"#)
13    }
14
15    /// Close a maestro session, force-cancelling any in-flight simulation.
16    pub fn close_session(&self, session: &str) -> String {
17        let session = escape_skill_string(session);
18        format!(r#"maeCloseSession("{session}" ?forceClose t)"#)
19    }
20
21    /// List all active maestro sessions.
22    pub fn list_sessions(&self) -> String {
23        r#"let((sessions out sep) sessions = maeGetSessions() out = "[" sep = "" foreach(s sessions out = strcat(out sep sprintf(nil "\"%s\"" s)) sep = ",") strcat(out "]"))"#.into()
24    }
25
26    /// Set a design variable value.
27    /// maeSetVar(name value ?typeName "test"|"corner" ?typeValue ?session)
28    pub fn set_var(&self, _session: &str, name: &str, value: &str) -> String {
29        let name = escape_skill_string(name);
30        let value = escape_skill_string(value);
31        format!(r#"maeSetVar("{name}" "{value}")"#)
32    }
33
34    /// Get a design variable value.
35    pub fn get_var(&self, name: &str) -> String {
36        let name = escape_skill_string(name);
37        format!(r#"maeGetVar("{name}")"#)
38    }
39
40    /// List all design variables. Returns JSON via sprintf.
41    pub fn list_vars(&self, _session: &str) -> String {
42        r#"let((vars out sep) vars = asiGetDesignVarList(asiGetCurrentSession()) out = "[" sep = "" foreach(v vars out = strcat(out sep sprintf(nil "{\"name\":\"%s\",\"value\":\"%s\"}" car(v) cadr(v))) sep = ",") strcat(out "]"))"#.to_string()
43    }
44
45    /// Get enabled analyses for a session (resolves setup name internally).
46    /// maeGetEnabledAnalysis(t_setupName) — takes setup name, not session name.
47    pub fn get_analyses(&self, session: &str) -> String {
48        let session = escape_skill_string(session);
49        format!(
50            r#"let((setup) setup = car(maeGetSetup(?session "{session}")) maeGetEnabledAnalysis(setup))"#
51        )
52    }
53
54    /// Enable an analysis type on a session (resolves setup name internally).
55    /// maeSetAnalysis(t_setupName t_analysisType) — returns t on success.
56    /// analysisType: "ac" | "dc" | "tran" | "noise" | etc.
57    pub fn set_analysis(&self, session: &str, analysis_type: &str) -> String {
58        let session = escape_skill_string(session);
59        let analysis_type = escape_skill_string(analysis_type);
60        format!(
61            r#"let((setup) setup = car(maeGetSetup(?session "{session}")) maeSetAnalysis(setup "{analysis_type}"))"#
62        )
63    }
64
65    /// Run simulation asynchronously. Returns immediately.
66    pub fn run_simulation(&self, session: &str) -> String {
67        let session = escape_skill_string(session);
68        format!(r#"maeRunSimulation(?session "{session}")"#)
69    }
70
71    /// Get test outputs (measurement expressions).
72    /// maeGetTestOutputs(t_testName [?session t_session])
73    pub fn get_outputs(&self, test_name: &str) -> String {
74        let test_name = escape_skill_string(test_name);
75        format!(
76            r#"let((outs out sep) outs = maeGetTestOutputs("{test_name}") out = "[" sep = "" foreach(o outs out = strcat(out sep sprintf(nil "{{\"name\":\"%s\",\"type\":\"%s\"}}" car(o) cadr(o))) sep = ",") strcat(out "]"))"#
77        )
78    }
79
80    /// Add an output expression to the session (resolves setup name internally).
81    /// maeAddOutput(t_outputName t_testName ?expr e)
82    pub fn add_output(&self, session: &str, output_name: &str, expr: &str) -> String {
83        let session = escape_skill_string(session);
84        let output_name = escape_skill_string(output_name);
85        let expr = escape_skill_string(expr);
86        format!(
87            r#"let((setup) setup = car(maeGetSetup(?session "{session}")) maeAddOutput("{output_name}" setup ?expr "{expr}"))"#
88        )
89    }
90
91    /// Set the design target for a test.
92    pub fn set_design(&self, session: &str, lib: &str, cell: &str, view: &str) -> String {
93        let session = escape_skill_string(session);
94        let lib = escape_skill_string(lib);
95        let cell = escape_skill_string(cell);
96        let view = escape_skill_string(view);
97        format!(
98            r#"maeSetDesign(?session "{session}" ?libName "{lib}" ?cellName "{cell}" ?viewName "{view}")"#
99        )
100    }
101
102    /// Save maestro setup to disk.
103    pub fn save_setup(&self, session: &str) -> String {
104        let session = escape_skill_string(session);
105        format!(r#"maeSaveSetup(?session "{session}")"#)
106    }
107
108    /// Get simulation messages (errors/warnings from last run).
109    pub fn get_sim_messages(&self, session: &str) -> String {
110        let session = escape_skill_string(session);
111        format!(r#"maeGetSimulationMessages(?session "{session}")"#)
112    }
113
114    /// Get focused ADE window name, all window names, and active sessions.
115    /// Returns a SKILL list: (focused_window_name (all_names...) (sessions...))
116    pub fn focused_window_skill(&self) -> String {
117        r#"let((cw) cw=hiGetCurrentWindow() list(if(cw hiGetWindowName(cw) nil) mapcar(lambda((w) hiGetWindowName(w)) hiGetWindowList()) maeGetSessions()))"#.into()
118    }
119
120    /// Get simulation run directory for a maestro session via asiGetAnalogRunDir.
121    pub fn run_dir_skill(&self, session: &str) -> String {
122        let session = escape_skill_string(session);
123        format!(
124            r#"let((sess) sess=asiGetSession("{session}") if(sess asiGetAnalogRunDir(sess) nil))"#
125        )
126    }
127
128    /// Export results to CSV.
129    pub fn export_results(&self, session: &str, file_path: &str) -> String {
130        let session = escape_skill_string(session);
131        let file_path = escape_skill_string(file_path);
132        format!(
133            r#"maeExportOutputView(?session "{session}" ?fileName "{file_path}" ?view "Detail")"#
134        )
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    fn ops() -> MaestroOps {
143        MaestroOps
144    }
145
146    #[test]
147    fn open_session_quoting() {
148        let s = ops().open_session("myLib", "myCell", "adexl");
149        assert_eq!(s, r#"maeOpenSetup("myLib" "myCell" "adexl")"#);
150    }
151
152    #[test]
153    fn open_session_escapes_quotes() {
154        let s = ops().open_session(r#"lib"x"#, "cell", "adexl");
155        assert!(s.contains(r#"lib\"x"#), "{s}");
156    }
157
158    #[test]
159    fn set_var_format() {
160        let s = ops().set_var("sess1", "Vdd", "1.8");
161        assert_eq!(s, r#"maeSetVar("Vdd" "1.8")"#);
162    }
163
164    #[test]
165    fn run_simulation_includes_session() {
166        let s = ops().run_simulation("sess1");
167        assert!(s.contains("maeRunSimulation"), "{s}");
168        assert!(s.contains("\"sess1\""), "{s}");
169    }
170
171    #[test]
172    fn set_analysis_resolves_setup() {
173        let s = ops().set_analysis("sess1", "ac");
174        assert!(s.contains("maeGetSetup"), "must resolve setup: {s}");
175        assert!(s.contains("maeSetAnalysis"), "{s}");
176        assert!(s.contains("\"ac\""), "{s}");
177    }
178
179    #[test]
180    fn add_output_includes_expr() {
181        let s = ops().add_output("sess1", "gain", "getData(\"vout\")");
182        assert!(s.contains("maeAddOutput"), "{s}");
183        assert!(s.contains("\"gain\""), "{s}");
184    }
185}