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#[derive(Debug, Clone, Copy, Deserialize, clap::ValueEnum)]
12#[clap(rename_all = "verbatim")]
13pub enum Orient {
14 R0,
15 R90,
16 R180,
17 R270,
18 MX,
19 MY,
20 MXR90,
21 MYR90,
22}
23
24impl Orient {
25 pub fn as_str(&self) -> &'static str {
26 match self {
27 Self::R0 => "R0",
28 Self::R90 => "R90",
29 Self::R180 => "R180",
30 Self::R270 => "R270",
31 Self::MX => "MX",
32 Self::MY => "MY",
33 Self::MXR90 => "MXR90",
34 Self::MYR90 => "MYR90",
35 }
36 }
37}
38
39pub fn open(lib: &str, cell: &str, view: &str) -> Result<Value> {
42 let client = VirtuosoClient::from_env()?;
43 let skill = client.schematic.open_cellview(lib, cell, view);
44 let r = client.execute_skill(&skill, None)?;
45 Ok(json!({
46 "status": if r.skill_ok() { "success" } else { "error" },
47 "lib": lib, "cell": cell, "view": view,
48 "output": r.output,
49 }))
50}
51
52pub fn place(
53 master: &str,
54 name: &str,
55 x: i64,
56 y: i64,
57 orient: Orient,
58 params: &[(String, String)],
59) -> Result<Value> {
60 let (lib, cell) = master
61 .split_once('/')
62 .ok_or_else(|| VirtuosoError::Config("--master must be lib/cell format".into()))?;
63 let client = VirtuosoClient::from_env()?;
64 let mut ed = SchematicEditor::new(&client);
65 ed.add_instance(lib, cell, "symbol", name, (x, y), orient.as_str());
66 for (k, v) in params {
67 ed.set_param(name, k, v);
68 }
69 let r = ed.execute()?;
70 Ok(json!({
71 "status": if r.skill_ok() { "success" } else { "error" },
72 "instance": name, "master": master,
73 "output": r.output,
74 }))
75}
76
77pub fn wire_from_strings(net: &str, points: &[String]) -> Result<Value> {
78 let pts: Vec<(i64, i64)> = points
79 .iter()
80 .map(|s| {
81 let (x, y) = s
82 .split_once(',')
83 .ok_or_else(|| VirtuosoError::Config(format!("Point '{s}' must be x,y")))?;
84 Ok((
85 x.parse()
86 .map_err(|_| VirtuosoError::Config(format!("Bad x: {x}")))?,
87 y.parse()
88 .map_err(|_| VirtuosoError::Config(format!("Bad y: {y}")))?,
89 ))
90 })
91 .collect::<Result<Vec<_>>>()?;
92 wire(net, &pts)
93}
94
95pub fn wire(net: &str, points: &[(i64, i64)]) -> Result<Value> {
96 let client = VirtuosoClient::from_env()?;
97 let skill = client.schematic.create_wire(points, "wire", net);
98 let r = client.execute_skill(&skill, None)?;
99 Ok(json!({
100 "status": if r.skill_ok() { "success" } else { "error" },
101 "net": net, "output": r.output,
102 }))
103}
104
105pub fn conn(net: &str, from: &str, to: &str) -> Result<Value> {
106 let (inst1, term1) = from
107 .split_once(':')
108 .ok_or_else(|| VirtuosoError::Config("--from must be inst:term format".into()))?;
109 let (inst2, term2) = to
110 .split_once(':')
111 .ok_or_else(|| VirtuosoError::Config("--to must be inst:term format".into()))?;
112 let client = VirtuosoClient::from_env()?;
113 let mut ed = SchematicEditor::new(&client);
114 ed.assign_net(inst1, term1, net);
115 ed.assign_net(inst2, term2, net);
116 let r = ed.execute()?;
117 Ok(json!({
118 "status": if r.skill_ok() { "success" } else { "error" },
119 "net": net, "from": from, "to": to,
120 "output": r.output,
121 }))
122}
123
124pub fn label(net: &str, x: i64, y: i64) -> Result<Value> {
125 let client = VirtuosoClient::from_env()?;
126 let skill = client.schematic.create_wire_label(net, (x, y));
127 let r = client.execute_skill(&skill, None)?;
128 Ok(json!({
129 "status": if r.skill_ok() { "success" } else { "error" },
130 "net": net, "output": r.output,
131 }))
132}
133
134pub fn pin(net: &str, pin_type: &str, x: i64, y: i64) -> Result<Value> {
135 let client = VirtuosoClient::from_env()?;
136 let skill = client.schematic.create_pin(net, pin_type, (x, y));
137 let r = client.execute_skill(&skill, None)?;
138 Ok(json!({
139 "status": if r.skill_ok() { "success" } else { "error" },
140 "net": net, "type": pin_type, "output": r.output,
141 }))
142}
143
144pub fn check() -> Result<Value> {
145 let client = VirtuosoClient::from_env()?;
146 let skill = client.schematic.check();
147 let r = client.execute_skill(&skill, None)?;
148 Ok(json!({
149 "status": if r.skill_ok() { "success" } else { "error" },
150 "output": r.output,
151 }))
152}
153
154pub fn save() -> Result<Value> {
155 let client = VirtuosoClient::from_env()?;
156 let skill = client.schematic.save();
157 let r = client.execute_skill(&skill, None)?;
158 Ok(json!({
159 "status": if r.skill_ok() { "success" } else { "error" },
160 "output": r.output,
161 }))
162}
163
164#[derive(Deserialize)]
167pub struct SchematicSpec {
168 pub target: SpecTarget,
169 #[serde(default)]
170 pub instances: Vec<SpecInstance>,
171 #[serde(default)]
172 pub connections: Vec<SpecConnection>,
173 #[serde(default)]
174 pub globals: Vec<SpecGlobal>,
175 #[serde(default)]
176 pub pins: Vec<SpecPin>,
177}
178
179#[derive(Deserialize)]
180pub struct SpecTarget {
181 pub lib: String,
182 pub cell: String,
183 #[serde(default = "default_view")]
184 pub view: String,
185}
186
187fn default_view() -> String {
188 "schematic".into()
189}
190
191#[derive(Deserialize)]
192pub struct SpecInstance {
193 pub name: String,
194 pub master: String, #[serde(default)]
196 pub x: i64,
197 #[serde(default)]
198 pub y: i64,
199 #[serde(default = "default_orient")]
200 pub orient: Orient,
201 #[serde(default)]
202 pub params: HashMap<String, String>,
203}
204
205fn default_orient() -> Orient {
206 Orient::R0
207}
208
209#[derive(Deserialize)]
210pub struct SpecConnection {
211 pub net: String,
212 pub from: String, pub to: String,
214}
215
216#[derive(Deserialize)]
217pub struct SpecGlobal {
218 pub net: String,
219 pub insts: Vec<String>, }
221
222#[derive(Deserialize)]
223pub struct SpecPin {
224 pub net: String,
225 #[serde(rename = "type")]
226 pub pin_type: String,
227 #[serde(default)]
228 pub connect: Option<String>, #[serde(default)]
230 pub x: i64,
231 #[serde(default)]
232 pub y: i64,
233}
234
235pub fn build(spec_path: &str) -> Result<Value> {
236 let spec_str = fs::read_to_string(spec_path)
237 .map_err(|e| VirtuosoError::Config(format!("Cannot read spec file {spec_path}: {e}")))?;
238 let spec: SchematicSpec = serde_json::from_str(&spec_str)
239 .map_err(|e| VirtuosoError::Config(format!("Invalid spec JSON: {e}")))?;
240
241 let client = VirtuosoClient::from_env()?;
242
243 let open_skill =
245 client
246 .schematic
247 .open_cellview(&spec.target.lib, &spec.target.cell, &spec.target.view);
248 let r = client.execute_skill(&open_skill, None)?;
249 if !r.skill_ok() {
250 return Err(VirtuosoError::Execution(format!(
251 "Failed to open cellview: {}",
252 r.output
253 )));
254 }
255
256 let mut ed = SchematicEditor::new(&client);
258 for inst in &spec.instances {
259 let (lib, cell) = inst.master.split_once('/').ok_or_else(|| {
260 VirtuosoError::Config(format!(
261 "Instance {} master '{}' must be lib/cell",
262 inst.name, inst.master
263 ))
264 })?;
265 ed.add_instance(
266 lib,
267 cell,
268 "symbol",
269 &inst.name,
270 (inst.x, inst.y),
271 inst.orient.as_str(),
272 );
273 for (k, v) in &inst.params {
274 ed.set_param(&inst.name, k, v);
275 }
276 }
277 let r = ed.execute()?;
278 if !r.skill_ok() {
279 return Err(VirtuosoError::Execution(format!(
280 "Failed to place instances: {}",
281 r.output
282 )));
283 }
284
285 {
288 let mut assignments: Vec<(String, String, String)> = Vec::new();
289 for c in &spec.connections {
290 let (i1, t1) = c.from.split_once(':').ok_or_else(|| {
291 VirtuosoError::Config(format!("Bad from '{}' in connection", c.from))
292 })?;
293 let (i2, t2) = c
294 .to
295 .split_once(':')
296 .ok_or_else(|| VirtuosoError::Config(format!("Bad to '{}' in connection", c.to)))?;
297 assignments.push((i1.into(), t1.into(), c.net.clone()));
298 assignments.push((i2.into(), t2.into(), c.net.clone()));
299 }
300 for g in &spec.globals {
301 for inst_term in &g.insts {
302 let (inst, term) = inst_term.split_once(':').ok_or_else(|| {
303 VirtuosoError::Config(format!("Bad global '{}' in {}", inst_term, g.net))
304 })?;
305 assignments.push((inst.into(), term.into(), g.net.clone()));
306 }
307 }
308
309 let helper_path = "/tmp/rb_schematic_helper.il";
311 fs::write(
312 helper_path,
313 include_str!("../../resources/rb_connect_terminal.il"),
314 )
315 .map_err(|e| VirtuosoError::Config(format!("Cannot write helper: {e}")))?;
316 let r = client.execute_skill(&format!(r#"load("{helper_path}")"#), None)?;
317 if !r.skill_ok() {
318 return Err(VirtuosoError::Execution(format!(
319 "Failed to load connection helper: {}",
320 r.output
321 )));
322 }
323
324 let mut lines = vec!["let((cv)".to_string(), "cv = RB_SCH_CV".to_string()];
326 for (inst, term, net) in &assignments {
327 lines.push(format!(
328 r#"RB_connectTerminal(cv "{inst}" "{term}" "{net}")"#
329 ));
330 }
331 lines.push("t)".to_string());
332
333 let script_path = "/tmp/rb_schematic_conn.il";
334 fs::write(script_path, lines.join("\n"))
335 .map_err(|e| VirtuosoError::Config(format!("Cannot write script: {e}")))?;
336 let r = client.execute_skill(&format!(r#"load("{script_path}")"#), None)?;
337 if !r.skill_ok() {
338 return Err(VirtuosoError::Execution(format!(
339 "Failed to create connections: {}",
340 r.output
341 )));
342 }
343 }
344
345 if !spec.pins.is_empty() {
347 let mut ed = SchematicEditor::new(&client);
348 for p in &spec.pins {
349 ed.add_pin(&p.net, &p.pin_type, (p.x, p.y));
350 }
351 let r = ed.execute()?;
352 if !r.skill_ok() {
353 return Err(VirtuosoError::Execution(format!(
354 "Failed to create pins: {}",
355 r.output
356 )));
357 }
358 }
359
360 let save_skill = client.schematic.save();
362 client.execute_skill(&save_skill, None)?;
363 let check_skill = client.schematic.check();
364 let r = client.execute_skill(&check_skill, None)?;
365
366 Ok(json!({
367 "status": "success",
368 "target": format!("{}/{}/{}", spec.target.lib, spec.target.cell, spec.target.view),
369 "instances": spec.instances.len(),
370 "connections": spec.connections.len() + spec.globals.len(),
371 "pins": spec.pins.len(),
372 "check": r.output,
373 }))
374}
375
376pub fn parse_skill_json(output: &str) -> Result<Value> {
381 let s = output.trim_matches('"');
384 if let Ok(v) = serde_json::from_str(s) {
386 return Ok(v);
387 }
388 let unescaped = s.replace("\\\"", "\"").replace("\\\\", "\\");
390 serde_json::from_str(&unescaped).map_err(|e| {
391 VirtuosoError::Execution(format!(
392 "Failed to parse SKILL JSON output: {e}. Raw: {output}"
393 ))
394 })
395}
396
397pub fn list_instances() -> Result<Value> {
398 let client = VirtuosoClient::from_env()?;
399 let skill = client.schematic.list_instances();
400 let r = client.execute_skill(&skill, None)?;
401 if !r.skill_ok() {
402 return Err(VirtuosoError::Execution(format!(
403 "Failed to list instances: {}",
404 r.output
405 )));
406 }
407 parse_skill_json(&r.output)
408}
409
410pub fn list_nets() -> Result<Value> {
411 let client = VirtuosoClient::from_env()?;
412 let skill = client.schematic.list_nets();
413 let r = client.execute_skill(&skill, None)?;
414 if !r.skill_ok() {
415 return Err(VirtuosoError::Execution(format!(
416 "Failed to list nets: {}",
417 r.output
418 )));
419 }
420 parse_skill_json(&r.output)
421}
422
423pub fn list_pins() -> Result<Value> {
424 let client = VirtuosoClient::from_env()?;
425 let skill = client.schematic.list_pins();
426 let r = client.execute_skill(&skill, None)?;
427 if !r.skill_ok() {
428 return Err(VirtuosoError::Execution(format!(
429 "Failed to list pins: {}",
430 r.output
431 )));
432 }
433 parse_skill_json(&r.output)
434}
435
436pub fn get_params(inst: &str) -> Result<Value> {
437 let client = VirtuosoClient::from_env()?;
438 let skill = client.schematic.get_instance_params(inst);
439 let r = client.execute_skill(&skill, None)?;
440 if !r.skill_ok() {
441 return Err(VirtuosoError::Execution(format!(
442 "Failed to get params for '{}': {}",
443 inst, r.output
444 )));
445 }
446 Ok(json!({"instance": inst, "params": parse_skill_json(&r.output)?}))
447}