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.split_once('/').ok_or_else(|| {
32 VirtuosoError::Config("--master must be lib/cell format".into())
33 })?;
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.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#[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, #[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, pub to: String,
173}
174
175#[derive(Deserialize)]
176pub struct SpecGlobal {
177 pub net: String,
178 pub insts: Vec<String>, }
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>, #[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 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 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 {
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 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 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 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 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}