Skip to main content

virtuoso_cli/commands/
schematic.rs

1use std::collections::HashMap;
2use std::fs;
3
4use crate::client::bridge::VirtuosoClient;
5use crate::client::editor::SchematicEditor;
6use crate::error::{Result, VirtuosoError};
7use serde::Deserialize;
8use serde_json::{json, Value};
9
10// ── Atomic commands ─────────────────────────────────────────────────
11
12pub fn open(lib: &str, cell: &str, view: &str) -> Result<Value> {
13    let client = VirtuosoClient::from_env()?;
14    let skill = client.schematic.open_cellview(lib, cell, view);
15    let r = client.execute_skill(&skill, None)?;
16    Ok(json!({
17        "status": if r.skill_ok() { "success" } else { "error" },
18        "lib": lib, "cell": cell, "view": view,
19        "output": r.output,
20    }))
21}
22
23pub fn place(
24    master: &str,
25    name: &str,
26    x: i64,
27    y: i64,
28    orient: &str,
29    params: &[(String, String)],
30) -> Result<Value> {
31    let (lib, cell) = master.split_once('/').ok_or_else(|| {
32        VirtuosoError::Config("--master must be lib/cell format".into())
33    })?;
34    let _ = orient; // TODO: pass orient to create_instance
35
36    let client = VirtuosoClient::from_env()?;
37    let mut ed = SchematicEditor::new(&client);
38    ed.add_instance(lib, cell, "symbol", name, (x, y));
39    for (k, v) in params {
40        ed.set_param(name, k, v);
41    }
42    let r = ed.execute()?;
43    Ok(json!({
44        "status": if r.ok() { "success" } else { "error" },
45        "instance": name, "master": master,
46        "output": r.output,
47    }))
48}
49
50pub fn wire_from_strings(net: &str, points: &[String]) -> Result<Value> {
51    let pts: Vec<(i64, i64)> = points.iter().map(|s| {
52        let (x, y) = s.split_once(',')
53            .ok_or_else(|| VirtuosoError::Config(format!("Point '{s}' must be x,y")))?;
54        Ok((
55            x.parse().map_err(|_| VirtuosoError::Config(format!("Bad x: {x}")))?,
56            y.parse().map_err(|_| VirtuosoError::Config(format!("Bad y: {y}")))?,
57        ))
58    }).collect::<Result<Vec<_>>>()?;
59    wire(net, &pts)
60}
61
62pub fn wire(net: &str, points: &[(i64, i64)]) -> Result<Value> {
63    let client = VirtuosoClient::from_env()?;
64    let skill = client.schematic.create_wire(points, "wire", net);
65    let r = client.execute_skill(&skill, None)?;
66    Ok(json!({
67        "status": if r.skill_ok() { "success" } else { "error" },
68        "net": net, "output": r.output,
69    }))
70}
71
72pub fn conn(net: &str, from: &str, to: &str) -> Result<Value> {
73    let (inst1, term1) = from.split_once(':').ok_or_else(|| {
74        VirtuosoError::Config("--from must be inst:term format".into())
75    })?;
76    let (inst2, term2) = to.split_once(':').ok_or_else(|| {
77        VirtuosoError::Config("--to must be inst:term format".into())
78    })?;
79    let client = VirtuosoClient::from_env()?;
80    let mut ed = SchematicEditor::new(&client);
81    ed.assign_net(inst1, term1, net);
82    ed.assign_net(inst2, term2, net);
83    let r = ed.execute()?;
84    Ok(json!({
85        "status": if r.ok() { "success" } else { "error" },
86        "net": net, "from": from, "to": to,
87        "output": r.output,
88    }))
89}
90
91pub fn label(net: &str, x: i64, y: i64) -> Result<Value> {
92    let client = VirtuosoClient::from_env()?;
93    let skill = client.schematic.create_wire_label(net, (x, y));
94    let r = client.execute_skill(&skill, None)?;
95    Ok(json!({
96        "status": if r.skill_ok() { "success" } else { "error" },
97        "net": net, "output": r.output,
98    }))
99}
100
101pub fn pin(net: &str, pin_type: &str, x: i64, y: i64) -> Result<Value> {
102    let client = VirtuosoClient::from_env()?;
103    let skill = client.schematic.create_pin(net, pin_type, (x, y));
104    let r = client.execute_skill(&skill, None)?;
105    Ok(json!({
106        "status": if r.skill_ok() { "success" } else { "error" },
107        "net": net, "type": pin_type, "output": r.output,
108    }))
109}
110
111pub fn check() -> Result<Value> {
112    let client = VirtuosoClient::from_env()?;
113    let skill = client.schematic.check();
114    let r = client.execute_skill(&skill, None)?;
115    Ok(json!({
116        "status": if r.skill_ok() { "success" } else { "error" },
117        "output": r.output,
118    }))
119}
120
121pub fn save() -> Result<Value> {
122    let client = VirtuosoClient::from_env()?;
123    let skill = client.schematic.save();
124    let r = client.execute_skill(&skill, None)?;
125    Ok(json!({
126        "status": if r.skill_ok() { "success" } else { "error" },
127        "output": r.output,
128    }))
129}
130
131// ── Build (batch from JSON spec) ────────────────────────────────────
132
133#[derive(Deserialize)]
134pub struct SchematicSpec {
135    pub target: SpecTarget,
136    #[serde(default)]
137    pub instances: Vec<SpecInstance>,
138    #[serde(default)]
139    pub connections: Vec<SpecConnection>,
140    #[serde(default)]
141    pub globals: Vec<SpecGlobal>,
142    #[serde(default)]
143    pub pins: Vec<SpecPin>,
144}
145
146#[derive(Deserialize)]
147pub struct SpecTarget {
148    pub lib: String,
149    pub cell: String,
150    #[serde(default = "default_view")]
151    pub view: String,
152}
153
154fn default_view() -> String { "schematic".into() }
155
156#[derive(Deserialize)]
157pub struct SpecInstance {
158    pub name: String,
159    pub master: String, // "lib/cell"
160    #[serde(default)]
161    pub x: i64,
162    #[serde(default)]
163    pub y: i64,
164    #[serde(default)]
165    pub params: HashMap<String, String>,
166}
167
168#[derive(Deserialize)]
169pub struct SpecConnection {
170    pub net: String,
171    pub from: String, // "inst:term"
172    pub to: String,
173}
174
175#[derive(Deserialize)]
176pub struct SpecGlobal {
177    pub net: String,
178    pub insts: Vec<String>, // ["M5:S", "M5:B"]
179}
180
181#[derive(Deserialize)]
182pub struct SpecPin {
183    pub net: String,
184    #[serde(rename = "type")]
185    pub pin_type: String,
186    #[serde(default)]
187    pub connect: Option<String>, // "M2:G"
188    #[serde(default)]
189    pub x: i64,
190    #[serde(default)]
191    pub y: i64,
192}
193
194pub fn build(spec_path: &str) -> Result<Value> {
195    let spec_str = fs::read_to_string(spec_path).map_err(|e| {
196        VirtuosoError::Config(format!("Cannot read spec file {spec_path}: {e}"))
197    })?;
198    let spec: SchematicSpec = serde_json::from_str(&spec_str).map_err(|e| {
199        VirtuosoError::Config(format!("Invalid spec JSON: {e}"))
200    })?;
201
202    let client = VirtuosoClient::from_env()?;
203
204    // 1. Open/create cellview
205    let open_skill = client.schematic.open_cellview(
206        &spec.target.lib, &spec.target.cell, &spec.target.view,
207    );
208    let r = client.execute_skill(&open_skill, None)?;
209    if !r.skill_ok() {
210        return Err(VirtuosoError::Execution(format!(
211            "Failed to open cellview: {}", r.output
212        )));
213    }
214
215    // 2. Place instances + set params
216    let mut ed = SchematicEditor::new(&client);
217    for inst in &spec.instances {
218        let (lib, cell) = inst.master.split_once('/').ok_or_else(|| {
219            VirtuosoError::Config(format!(
220                "Instance {} master '{}' must be lib/cell", inst.name, inst.master
221            ))
222        })?;
223        ed.add_instance(lib, cell, "symbol", &inst.name, (inst.x, inst.y));
224        for (k, v) in &inst.params {
225            ed.set_param(&inst.name, k, v);
226        }
227    }
228    let r = ed.execute()?;
229    if !r.ok() {
230        return Err(VirtuosoError::Execution(format!(
231            "Failed to place instances: {}", r.output
232        )));
233    }
234
235    // 3. Connections — generate .il script with RB_connectTerminal calls
236    //    Bridge has limitations with complex SKILL, so we write to file and load().
237    {
238        let mut assignments: Vec<(String, String, String)> = Vec::new();
239        for c in &spec.connections {
240            let (i1, t1) = c.from.split_once(':').ok_or_else(|| {
241                VirtuosoError::Config(format!("Bad from '{}' in connection", c.from))
242            })?;
243            let (i2, t2) = c.to.split_once(':').ok_or_else(|| {
244                VirtuosoError::Config(format!("Bad to '{}' in connection", c.to))
245            })?;
246            assignments.push((i1.into(), t1.into(), c.net.clone()));
247            assignments.push((i2.into(), t2.into(), c.net.clone()));
248        }
249        for g in &spec.globals {
250            for inst_term in &g.insts {
251                let (inst, term) = inst_term.split_once(':').ok_or_else(|| {
252                    VirtuosoError::Config(format!("Bad global '{}' in {}", inst_term, g.net))
253                })?;
254                assignments.push((inst.into(), term.into(), g.net.clone()));
255            }
256        }
257
258        // Load the RB_connectTerminal helper procedure
259        let helper_path = "/tmp/rb_schematic_helper.il";
260        fs::write(helper_path, include_str!("../../resources/rb_connect_terminal.il"))
261            .map_err(|e| VirtuosoError::Config(format!("Cannot write helper: {e}")))?;
262        let r = client.execute_skill(&format!(r#"load("{helper_path}")"#), None)?;
263        if !r.skill_ok() {
264            return Err(VirtuosoError::Execution(format!(
265                "Failed to load connection helper: {}", r.output
266            )));
267        }
268
269        // Generate connection script
270        let mut lines = vec!["let((cv)".to_string(), "cv = RB_SCH_CV".to_string()];
271        for (inst, term, net) in &assignments {
272            lines.push(format!(r#"RB_connectTerminal(cv "{inst}" "{term}" "{net}")"#));
273        }
274        lines.push("t)".to_string());
275
276        let script_path = "/tmp/rb_schematic_conn.il";
277        fs::write(script_path, lines.join("\n"))
278            .map_err(|e| VirtuosoError::Config(format!("Cannot write script: {e}")))?;
279        let r = client.execute_skill(&format!(r#"load("{script_path}")"#), None)?;
280        if !r.skill_ok() {
281            return Err(VirtuosoError::Execution(format!(
282                "Failed to create connections: {}", r.output
283            )));
284        }
285    }
286
287    // 4. Pins
288    if !spec.pins.is_empty() {
289        let mut ed = SchematicEditor::new(&client);
290        for p in &spec.pins {
291            ed.add_pin(&p.net, &p.pin_type, (p.x, p.y));
292        }
293        let r = ed.execute()?;
294        if !r.ok() {
295            return Err(VirtuosoError::Execution(format!(
296                "Failed to create pins: {}", r.output
297            )));
298        }
299    }
300
301    // 5. Save + check
302    let save_skill = client.schematic.save();
303    client.execute_skill(&save_skill, None)?;
304    let check_skill = client.schematic.check();
305    let r = client.execute_skill(&check_skill, None)?;
306
307    Ok(json!({
308        "status": "success",
309        "target": format!("{}/{}/{}", spec.target.lib, spec.target.cell, spec.target.view),
310        "instances": spec.instances.len(),
311        "connections": spec.connections.len() + spec.globals.len(),
312        "pins": spec.pins.len(),
313        "check": r.output,
314    }))
315}