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 #[allow(dead_code)]
229 pub connect: Option<String>, #[serde(default)]
231 pub x: i64,
232 #[serde(default)]
233 pub y: i64,
234}
235
236pub fn build(spec_path: &str) -> Result<Value> {
237 let spec_str = fs::read_to_string(spec_path)
238 .map_err(|e| VirtuosoError::Config(format!("Cannot read spec file {spec_path}: {e}")))?;
239 let spec: SchematicSpec = serde_json::from_str(&spec_str)
240 .map_err(|e| VirtuosoError::Config(format!("Invalid spec JSON: {e}")))?;
241
242 let client = VirtuosoClient::from_env()?;
243
244 let open_skill =
246 client
247 .schematic
248 .open_cellview(&spec.target.lib, &spec.target.cell, &spec.target.view);
249 let r = client.execute_skill(&open_skill, None)?;
250 if !r.skill_ok() {
251 return Err(VirtuosoError::Execution(format!(
252 "Failed to open cellview: {}",
253 r.output
254 )));
255 }
256
257 let mut ed = SchematicEditor::new(&client);
259 for inst in &spec.instances {
260 let (lib, cell) = inst.master.split_once('/').ok_or_else(|| {
261 VirtuosoError::Config(format!(
262 "Instance {} master '{}' must be lib/cell",
263 inst.name, inst.master
264 ))
265 })?;
266 ed.add_instance(
267 lib,
268 cell,
269 "symbol",
270 &inst.name,
271 (inst.x, inst.y),
272 inst.orient.as_str(),
273 );
274 for (k, v) in &inst.params {
275 ed.set_param(&inst.name, k, v);
276 }
277 }
278 let r = ed.execute()?;
279 if !r.skill_ok() {
280 return Err(VirtuosoError::Execution(format!(
281 "Failed to place instances: {}",
282 r.output
283 )));
284 }
285
286 {
289 let mut assignments: Vec<(String, String, String)> = Vec::new();
290 for c in &spec.connections {
291 let (i1, t1) = c.from.split_once(':').ok_or_else(|| {
292 VirtuosoError::Config(format!("Bad from '{}' in connection", c.from))
293 })?;
294 let (i2, t2) = c
295 .to
296 .split_once(':')
297 .ok_or_else(|| VirtuosoError::Config(format!("Bad to '{}' in connection", c.to)))?;
298 assignments.push((i1.into(), t1.into(), c.net.clone()));
299 assignments.push((i2.into(), t2.into(), c.net.clone()));
300 }
301 for g in &spec.globals {
302 for inst_term in &g.insts {
303 let (inst, term) = inst_term.split_once(':').ok_or_else(|| {
304 VirtuosoError::Config(format!("Bad global '{}' in {}", inst_term, g.net))
305 })?;
306 assignments.push((inst.into(), term.into(), g.net.clone()));
307 }
308 }
309
310 let helper_path = "/tmp/rb_schematic_helper.il";
312 fs::write(
313 helper_path,
314 include_str!("../../resources/rb_connect_terminal.il"),
315 )
316 .map_err(|e| VirtuosoError::Config(format!("Cannot write helper: {e}")))?;
317 let r = client.execute_skill(&format!(r#"load("{helper_path}")"#), None)?;
318 if !r.skill_ok() {
319 return Err(VirtuosoError::Execution(format!(
320 "Failed to load connection helper: {}",
321 r.output
322 )));
323 }
324
325 let mut lines = vec!["let((cv)".to_string(), "cv = RB_SCH_CV".to_string()];
327 for (inst, term, net) in &assignments {
328 lines.push(format!(
329 r#"RB_connectTerminal(cv "{inst}" "{term}" "{net}")"#
330 ));
331 }
332 lines.push("t)".to_string());
333
334 let script_path = "/tmp/rb_schematic_conn.il";
335 fs::write(script_path, lines.join("\n"))
336 .map_err(|e| VirtuosoError::Config(format!("Cannot write script: {e}")))?;
337 let r = client.execute_skill(&format!(r#"load("{script_path}")"#), None)?;
338 if !r.skill_ok() {
339 return Err(VirtuosoError::Execution(format!(
340 "Failed to create connections: {}",
341 r.output
342 )));
343 }
344 }
345
346 if !spec.pins.is_empty() {
348 let mut ed = SchematicEditor::new(&client);
349 for p in &spec.pins {
350 ed.add_pin(&p.net, &p.pin_type, (p.x, p.y));
351 }
352 let r = ed.execute()?;
353 if !r.skill_ok() {
354 return Err(VirtuosoError::Execution(format!(
355 "Failed to create pins: {}",
356 r.output
357 )));
358 }
359 }
360
361 let save_skill = client.schematic.save();
363 client.execute_skill(&save_skill, None)?;
364 let check_skill = client.schematic.check();
365 let r = client.execute_skill(&check_skill, None)?;
366
367 Ok(json!({
368 "status": "success",
369 "target": format!("{}/{}/{}", spec.target.lib, spec.target.cell, spec.target.view),
370 "instances": spec.instances.len(),
371 "connections": spec.connections.len() + spec.globals.len(),
372 "pins": spec.pins.len(),
373 "check": r.output,
374 }))
375}
376
377pub fn parse_skill_json(output: &str) -> Result<Value> {
382 let s = output.trim_matches('"');
385 if let Ok(v) = serde_json::from_str(s) {
387 return Ok(v);
388 }
389 let unescaped = s.replace("\\\"", "\"").replace("\\\\", "\\");
391 serde_json::from_str(&unescaped).map_err(|e| {
392 VirtuosoError::Execution(format!(
393 "Failed to parse SKILL JSON output: {e}. Raw: {output}"
394 ))
395 })
396}
397
398pub fn list_instances() -> Result<Value> {
399 let client = VirtuosoClient::from_env()?;
400 let skill = client.schematic.list_instances();
401 let r = client.execute_skill(&skill, None)?;
402 if !r.skill_ok() {
403 return Err(VirtuosoError::Execution(format!(
404 "Failed to list instances: {}",
405 r.output
406 )));
407 }
408 parse_skill_json(&r.output)
409}
410
411pub fn list_nets() -> Result<Value> {
412 let client = VirtuosoClient::from_env()?;
413 let skill = client.schematic.list_nets();
414 let r = client.execute_skill(&skill, None)?;
415 if !r.skill_ok() {
416 return Err(VirtuosoError::Execution(format!(
417 "Failed to list nets: {}",
418 r.output
419 )));
420 }
421 parse_skill_json(&r.output)
422}
423
424pub fn list_pins() -> Result<Value> {
425 let client = VirtuosoClient::from_env()?;
426 let skill = client.schematic.list_pins();
427 let r = client.execute_skill(&skill, None)?;
428 if !r.skill_ok() {
429 return Err(VirtuosoError::Execution(format!(
430 "Failed to list pins: {}",
431 r.output
432 )));
433 }
434 parse_skill_json(&r.output)
435}
436
437pub fn get_params(inst: &str) -> Result<Value> {
438 let client = VirtuosoClient::from_env()?;
439 let skill = client.schematic.get_instance_params(inst);
440 let r = client.execute_skill(&skill, None)?;
441 if !r.skill_ok() {
442 return Err(VirtuosoError::Execution(format!(
443 "Failed to get params for '{}': {}",
444 inst, r.output
445 )));
446 }
447 Ok(json!({"instance": inst, "params": parse_skill_json(&r.output)?}))
448}