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
10pub 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
32 .split_once('/')
33 .ok_or_else(|| VirtuosoError::Config("--master must be lib/cell format".into()))?;
34 let _ = orient; 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.skill_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
52 .iter()
53 .map(|s| {
54 let (x, y) = s
55 .split_once(',')
56 .ok_or_else(|| VirtuosoError::Config(format!("Point '{s}' must be x,y")))?;
57 Ok((
58 x.parse()
59 .map_err(|_| VirtuosoError::Config(format!("Bad x: {x}")))?,
60 y.parse()
61 .map_err(|_| VirtuosoError::Config(format!("Bad y: {y}")))?,
62 ))
63 })
64 .collect::<Result<Vec<_>>>()?;
65 wire(net, &pts)
66}
67
68pub fn wire(net: &str, points: &[(i64, i64)]) -> Result<Value> {
69 let client = VirtuosoClient::from_env()?;
70 let skill = client.schematic.create_wire(points, "wire", net);
71 let r = client.execute_skill(&skill, None)?;
72 Ok(json!({
73 "status": if r.skill_ok() { "success" } else { "error" },
74 "net": net, "output": r.output,
75 }))
76}
77
78pub fn conn(net: &str, from: &str, to: &str) -> Result<Value> {
79 let (inst1, term1) = from
80 .split_once(':')
81 .ok_or_else(|| VirtuosoError::Config("--from must be inst:term format".into()))?;
82 let (inst2, term2) = to
83 .split_once(':')
84 .ok_or_else(|| VirtuosoError::Config("--to must be inst:term format".into()))?;
85 let client = VirtuosoClient::from_env()?;
86 let mut ed = SchematicEditor::new(&client);
87 ed.assign_net(inst1, term1, net);
88 ed.assign_net(inst2, term2, net);
89 let r = ed.execute()?;
90 Ok(json!({
91 "status": if r.skill_ok() { "success" } else { "error" },
92 "net": net, "from": from, "to": to,
93 "output": r.output,
94 }))
95}
96
97pub fn label(net: &str, x: i64, y: i64) -> Result<Value> {
98 let client = VirtuosoClient::from_env()?;
99 let skill = client.schematic.create_wire_label(net, (x, y));
100 let r = client.execute_skill(&skill, None)?;
101 Ok(json!({
102 "status": if r.skill_ok() { "success" } else { "error" },
103 "net": net, "output": r.output,
104 }))
105}
106
107pub fn pin(net: &str, pin_type: &str, x: i64, y: i64) -> Result<Value> {
108 let client = VirtuosoClient::from_env()?;
109 let skill = client.schematic.create_pin(net, pin_type, (x, y));
110 let r = client.execute_skill(&skill, None)?;
111 Ok(json!({
112 "status": if r.skill_ok() { "success" } else { "error" },
113 "net": net, "type": pin_type, "output": r.output,
114 }))
115}
116
117pub fn check() -> Result<Value> {
118 let client = VirtuosoClient::from_env()?;
119 let skill = client.schematic.check();
120 let r = client.execute_skill(&skill, None)?;
121 Ok(json!({
122 "status": if r.skill_ok() { "success" } else { "error" },
123 "output": r.output,
124 }))
125}
126
127pub fn save() -> Result<Value> {
128 let client = VirtuosoClient::from_env()?;
129 let skill = client.schematic.save();
130 let r = client.execute_skill(&skill, None)?;
131 Ok(json!({
132 "status": if r.skill_ok() { "success" } else { "error" },
133 "output": r.output,
134 }))
135}
136
137#[derive(Deserialize)]
140pub struct SchematicSpec {
141 pub target: SpecTarget,
142 #[serde(default)]
143 pub instances: Vec<SpecInstance>,
144 #[serde(default)]
145 pub connections: Vec<SpecConnection>,
146 #[serde(default)]
147 pub globals: Vec<SpecGlobal>,
148 #[serde(default)]
149 pub pins: Vec<SpecPin>,
150}
151
152#[derive(Deserialize)]
153pub struct SpecTarget {
154 pub lib: String,
155 pub cell: String,
156 #[serde(default = "default_view")]
157 pub view: String,
158}
159
160fn default_view() -> String {
161 "schematic".into()
162}
163
164#[derive(Deserialize)]
165pub struct SpecInstance {
166 pub name: String,
167 pub master: String, #[serde(default)]
169 pub x: i64,
170 #[serde(default)]
171 pub y: i64,
172 #[serde(default)]
173 pub params: HashMap<String, String>,
174}
175
176#[derive(Deserialize)]
177pub struct SpecConnection {
178 pub net: String,
179 pub from: String, pub to: String,
181}
182
183#[derive(Deserialize)]
184pub struct SpecGlobal {
185 pub net: String,
186 pub insts: Vec<String>, }
188
189#[derive(Deserialize)]
190pub struct SpecPin {
191 pub net: String,
192 #[serde(rename = "type")]
193 pub pin_type: String,
194 #[serde(default)]
195 pub connect: Option<String>, #[serde(default)]
197 pub x: i64,
198 #[serde(default)]
199 pub y: i64,
200}
201
202pub fn build(spec_path: &str) -> Result<Value> {
203 let spec_str = fs::read_to_string(spec_path)
204 .map_err(|e| VirtuosoError::Config(format!("Cannot read spec file {spec_path}: {e}")))?;
205 let spec: SchematicSpec = serde_json::from_str(&spec_str)
206 .map_err(|e| VirtuosoError::Config(format!("Invalid spec JSON: {e}")))?;
207
208 let client = VirtuosoClient::from_env()?;
209
210 let open_skill =
212 client
213 .schematic
214 .open_cellview(&spec.target.lib, &spec.target.cell, &spec.target.view);
215 let r = client.execute_skill(&open_skill, None)?;
216 if !r.skill_ok() {
217 return Err(VirtuosoError::Execution(format!(
218 "Failed to open cellview: {}",
219 r.output
220 )));
221 }
222
223 let mut ed = SchematicEditor::new(&client);
225 for inst in &spec.instances {
226 let (lib, cell) = inst.master.split_once('/').ok_or_else(|| {
227 VirtuosoError::Config(format!(
228 "Instance {} master '{}' must be lib/cell",
229 inst.name, inst.master
230 ))
231 })?;
232 ed.add_instance(lib, cell, "symbol", &inst.name, (inst.x, inst.y));
233 for (k, v) in &inst.params {
234 ed.set_param(&inst.name, k, v);
235 }
236 }
237 let r = ed.execute()?;
238 if !r.skill_ok() {
239 return Err(VirtuosoError::Execution(format!(
240 "Failed to place instances: {}",
241 r.output
242 )));
243 }
244
245 {
248 let mut assignments: Vec<(String, String, String)> = Vec::new();
249 for c in &spec.connections {
250 let (i1, t1) = c.from.split_once(':').ok_or_else(|| {
251 VirtuosoError::Config(format!("Bad from '{}' in connection", c.from))
252 })?;
253 let (i2, t2) = c
254 .to
255 .split_once(':')
256 .ok_or_else(|| VirtuosoError::Config(format!("Bad to '{}' in connection", c.to)))?;
257 assignments.push((i1.into(), t1.into(), c.net.clone()));
258 assignments.push((i2.into(), t2.into(), c.net.clone()));
259 }
260 for g in &spec.globals {
261 for inst_term in &g.insts {
262 let (inst, term) = inst_term.split_once(':').ok_or_else(|| {
263 VirtuosoError::Config(format!("Bad global '{}' in {}", inst_term, g.net))
264 })?;
265 assignments.push((inst.into(), term.into(), g.net.clone()));
266 }
267 }
268
269 let helper_path = "/tmp/rb_schematic_helper.il";
271 fs::write(
272 helper_path,
273 include_str!("../../resources/rb_connect_terminal.il"),
274 )
275 .map_err(|e| VirtuosoError::Config(format!("Cannot write helper: {e}")))?;
276 let r = client.execute_skill(&format!(r#"load("{helper_path}")"#), None)?;
277 if !r.skill_ok() {
278 return Err(VirtuosoError::Execution(format!(
279 "Failed to load connection helper: {}",
280 r.output
281 )));
282 }
283
284 let mut lines = vec!["let((cv)".to_string(), "cv = RB_SCH_CV".to_string()];
286 for (inst, term, net) in &assignments {
287 lines.push(format!(
288 r#"RB_connectTerminal(cv "{inst}" "{term}" "{net}")"#
289 ));
290 }
291 lines.push("t)".to_string());
292
293 let script_path = "/tmp/rb_schematic_conn.il";
294 fs::write(script_path, lines.join("\n"))
295 .map_err(|e| VirtuosoError::Config(format!("Cannot write script: {e}")))?;
296 let r = client.execute_skill(&format!(r#"load("{script_path}")"#), None)?;
297 if !r.skill_ok() {
298 return Err(VirtuosoError::Execution(format!(
299 "Failed to create connections: {}",
300 r.output
301 )));
302 }
303 }
304
305 if !spec.pins.is_empty() {
307 let mut ed = SchematicEditor::new(&client);
308 for p in &spec.pins {
309 ed.add_pin(&p.net, &p.pin_type, (p.x, p.y));
310 }
311 let r = ed.execute()?;
312 if !r.skill_ok() {
313 return Err(VirtuosoError::Execution(format!(
314 "Failed to create pins: {}",
315 r.output
316 )));
317 }
318 }
319
320 let save_skill = client.schematic.save();
322 client.execute_skill(&save_skill, None)?;
323 let check_skill = client.schematic.check();
324 let r = client.execute_skill(&check_skill, None)?;
325
326 Ok(json!({
327 "status": "success",
328 "target": format!("{}/{}/{}", spec.target.lib, spec.target.cell, spec.target.view),
329 "instances": spec.instances.len(),
330 "connections": spec.connections.len() + spec.globals.len(),
331 "pins": spec.pins.len(),
332 "check": r.output,
333 }))
334}
335
336pub fn parse_skill_json(output: &str) -> Value {
340 let s = output.trim_matches('"');
343 if let Ok(v) = serde_json::from_str(s) {
345 return v;
346 }
347 let unescaped = s.replace("\\\"", "\"").replace("\\\\", "\\");
349 serde_json::from_str(&unescaped).unwrap_or_else(|_| json!({"raw": output}))
350}
351
352pub fn list_instances() -> Result<Value> {
353 let client = VirtuosoClient::from_env()?;
354 let skill = client.schematic.list_instances();
355 let r = client.execute_skill(&skill, None)?;
356 if !r.skill_ok() {
357 return Err(VirtuosoError::Execution(format!(
358 "Failed to list instances: {}",
359 r.output
360 )));
361 }
362 Ok(parse_skill_json(&r.output))
363}
364
365pub fn list_nets() -> Result<Value> {
366 let client = VirtuosoClient::from_env()?;
367 let skill = client.schematic.list_nets();
368 let r = client.execute_skill(&skill, None)?;
369 if !r.skill_ok() {
370 return Err(VirtuosoError::Execution(format!(
371 "Failed to list nets: {}",
372 r.output
373 )));
374 }
375 Ok(parse_skill_json(&r.output))
376}
377
378pub fn list_pins() -> Result<Value> {
379 let client = VirtuosoClient::from_env()?;
380 let skill = client.schematic.list_pins();
381 let r = client.execute_skill(&skill, None)?;
382 if !r.skill_ok() {
383 return Err(VirtuosoError::Execution(format!(
384 "Failed to list pins: {}",
385 r.output
386 )));
387 }
388 Ok(parse_skill_json(&r.output))
389}
390
391pub fn get_params(inst: &str) -> Result<Value> {
392 let client = VirtuosoClient::from_env()?;
393 let skill = client.schematic.get_instance_params(inst);
394 let r = client.execute_skill(&skill, None)?;
395 if !r.skill_ok() {
396 return Err(VirtuosoError::Execution(format!(
397 "Failed to get params for '{}': {}",
398 inst, r.output
399 )));
400 }
401 Ok(json!({"instance": inst, "params": parse_skill_json(&r.output)}))
402}