use std::collections::HashMap;
use std::fs;
use crate::client::bridge::VirtuosoClient;
use crate::client::editor::SchematicEditor;
use crate::error::{Result, VirtuosoError};
use serde::Deserialize;
use serde_json::{json, Value};
pub fn open(lib: &str, cell: &str, view: &str) -> Result<Value> {
let client = VirtuosoClient::from_env()?;
let skill = client.schematic.open_cellview(lib, cell, view);
let r = client.execute_skill(&skill, None)?;
Ok(json!({
"status": if r.skill_ok() { "success" } else { "error" },
"lib": lib, "cell": cell, "view": view,
"output": r.output,
}))
}
pub fn place(
master: &str,
name: &str,
x: i64,
y: i64,
orient: &str,
params: &[(String, String)],
) -> Result<Value> {
let (lib, cell) = master
.split_once('/')
.ok_or_else(|| VirtuosoError::Config("--master must be lib/cell format".into()))?;
let _ = orient;
let client = VirtuosoClient::from_env()?;
let mut ed = SchematicEditor::new(&client);
ed.add_instance(lib, cell, "symbol", name, (x, y));
for (k, v) in params {
ed.set_param(name, k, v);
}
let r = ed.execute()?;
Ok(json!({
"status": if r.skill_ok() { "success" } else { "error" },
"instance": name, "master": master,
"output": r.output,
}))
}
pub fn wire_from_strings(net: &str, points: &[String]) -> Result<Value> {
let pts: Vec<(i64, i64)> = points
.iter()
.map(|s| {
let (x, y) = s
.split_once(',')
.ok_or_else(|| VirtuosoError::Config(format!("Point '{s}' must be x,y")))?;
Ok((
x.parse()
.map_err(|_| VirtuosoError::Config(format!("Bad x: {x}")))?,
y.parse()
.map_err(|_| VirtuosoError::Config(format!("Bad y: {y}")))?,
))
})
.collect::<Result<Vec<_>>>()?;
wire(net, &pts)
}
pub fn wire(net: &str, points: &[(i64, i64)]) -> Result<Value> {
let client = VirtuosoClient::from_env()?;
let skill = client.schematic.create_wire(points, "wire", net);
let r = client.execute_skill(&skill, None)?;
Ok(json!({
"status": if r.skill_ok() { "success" } else { "error" },
"net": net, "output": r.output,
}))
}
pub fn conn(net: &str, from: &str, to: &str) -> Result<Value> {
let (inst1, term1) = from
.split_once(':')
.ok_or_else(|| VirtuosoError::Config("--from must be inst:term format".into()))?;
let (inst2, term2) = to
.split_once(':')
.ok_or_else(|| VirtuosoError::Config("--to must be inst:term format".into()))?;
let client = VirtuosoClient::from_env()?;
let mut ed = SchematicEditor::new(&client);
ed.assign_net(inst1, term1, net);
ed.assign_net(inst2, term2, net);
let r = ed.execute()?;
Ok(json!({
"status": if r.skill_ok() { "success" } else { "error" },
"net": net, "from": from, "to": to,
"output": r.output,
}))
}
pub fn label(net: &str, x: i64, y: i64) -> Result<Value> {
let client = VirtuosoClient::from_env()?;
let skill = client.schematic.create_wire_label(net, (x, y));
let r = client.execute_skill(&skill, None)?;
Ok(json!({
"status": if r.skill_ok() { "success" } else { "error" },
"net": net, "output": r.output,
}))
}
pub fn pin(net: &str, pin_type: &str, x: i64, y: i64) -> Result<Value> {
let client = VirtuosoClient::from_env()?;
let skill = client.schematic.create_pin(net, pin_type, (x, y));
let r = client.execute_skill(&skill, None)?;
Ok(json!({
"status": if r.skill_ok() { "success" } else { "error" },
"net": net, "type": pin_type, "output": r.output,
}))
}
pub fn check() -> Result<Value> {
let client = VirtuosoClient::from_env()?;
let skill = client.schematic.check();
let r = client.execute_skill(&skill, None)?;
Ok(json!({
"status": if r.skill_ok() { "success" } else { "error" },
"output": r.output,
}))
}
pub fn save() -> Result<Value> {
let client = VirtuosoClient::from_env()?;
let skill = client.schematic.save();
let r = client.execute_skill(&skill, None)?;
Ok(json!({
"status": if r.skill_ok() { "success" } else { "error" },
"output": r.output,
}))
}
#[derive(Deserialize)]
pub struct SchematicSpec {
pub target: SpecTarget,
#[serde(default)]
pub instances: Vec<SpecInstance>,
#[serde(default)]
pub connections: Vec<SpecConnection>,
#[serde(default)]
pub globals: Vec<SpecGlobal>,
#[serde(default)]
pub pins: Vec<SpecPin>,
}
#[derive(Deserialize)]
pub struct SpecTarget {
pub lib: String,
pub cell: String,
#[serde(default = "default_view")]
pub view: String,
}
fn default_view() -> String {
"schematic".into()
}
#[derive(Deserialize)]
pub struct SpecInstance {
pub name: String,
pub master: String, #[serde(default)]
pub x: i64,
#[serde(default)]
pub y: i64,
#[serde(default)]
pub params: HashMap<String, String>,
}
#[derive(Deserialize)]
pub struct SpecConnection {
pub net: String,
pub from: String, pub to: String,
}
#[derive(Deserialize)]
pub struct SpecGlobal {
pub net: String,
pub insts: Vec<String>, }
#[derive(Deserialize)]
pub struct SpecPin {
pub net: String,
#[serde(rename = "type")]
pub pin_type: String,
#[serde(default)]
pub connect: Option<String>, #[serde(default)]
pub x: i64,
#[serde(default)]
pub y: i64,
}
pub fn build(spec_path: &str) -> Result<Value> {
let spec_str = fs::read_to_string(spec_path)
.map_err(|e| VirtuosoError::Config(format!("Cannot read spec file {spec_path}: {e}")))?;
let spec: SchematicSpec = serde_json::from_str(&spec_str)
.map_err(|e| VirtuosoError::Config(format!("Invalid spec JSON: {e}")))?;
let client = VirtuosoClient::from_env()?;
let open_skill =
client
.schematic
.open_cellview(&spec.target.lib, &spec.target.cell, &spec.target.view);
let r = client.execute_skill(&open_skill, None)?;
if !r.skill_ok() {
return Err(VirtuosoError::Execution(format!(
"Failed to open cellview: {}",
r.output
)));
}
let mut ed = SchematicEditor::new(&client);
for inst in &spec.instances {
let (lib, cell) = inst.master.split_once('/').ok_or_else(|| {
VirtuosoError::Config(format!(
"Instance {} master '{}' must be lib/cell",
inst.name, inst.master
))
})?;
ed.add_instance(lib, cell, "symbol", &inst.name, (inst.x, inst.y));
for (k, v) in &inst.params {
ed.set_param(&inst.name, k, v);
}
}
let r = ed.execute()?;
if !r.skill_ok() {
return Err(VirtuosoError::Execution(format!(
"Failed to place instances: {}",
r.output
)));
}
{
let mut assignments: Vec<(String, String, String)> = Vec::new();
for c in &spec.connections {
let (i1, t1) = c.from.split_once(':').ok_or_else(|| {
VirtuosoError::Config(format!("Bad from '{}' in connection", c.from))
})?;
let (i2, t2) = c
.to
.split_once(':')
.ok_or_else(|| VirtuosoError::Config(format!("Bad to '{}' in connection", c.to)))?;
assignments.push((i1.into(), t1.into(), c.net.clone()));
assignments.push((i2.into(), t2.into(), c.net.clone()));
}
for g in &spec.globals {
for inst_term in &g.insts {
let (inst, term) = inst_term.split_once(':').ok_or_else(|| {
VirtuosoError::Config(format!("Bad global '{}' in {}", inst_term, g.net))
})?;
assignments.push((inst.into(), term.into(), g.net.clone()));
}
}
let helper_path = "/tmp/rb_schematic_helper.il";
fs::write(
helper_path,
include_str!("../../resources/rb_connect_terminal.il"),
)
.map_err(|e| VirtuosoError::Config(format!("Cannot write helper: {e}")))?;
let r = client.execute_skill(&format!(r#"load("{helper_path}")"#), None)?;
if !r.skill_ok() {
return Err(VirtuosoError::Execution(format!(
"Failed to load connection helper: {}",
r.output
)));
}
let mut lines = vec!["let((cv)".to_string(), "cv = RB_SCH_CV".to_string()];
for (inst, term, net) in &assignments {
lines.push(format!(
r#"RB_connectTerminal(cv "{inst}" "{term}" "{net}")"#
));
}
lines.push("t)".to_string());
let script_path = "/tmp/rb_schematic_conn.il";
fs::write(script_path, lines.join("\n"))
.map_err(|e| VirtuosoError::Config(format!("Cannot write script: {e}")))?;
let r = client.execute_skill(&format!(r#"load("{script_path}")"#), None)?;
if !r.skill_ok() {
return Err(VirtuosoError::Execution(format!(
"Failed to create connections: {}",
r.output
)));
}
}
if !spec.pins.is_empty() {
let mut ed = SchematicEditor::new(&client);
for p in &spec.pins {
ed.add_pin(&p.net, &p.pin_type, (p.x, p.y));
}
let r = ed.execute()?;
if !r.skill_ok() {
return Err(VirtuosoError::Execution(format!(
"Failed to create pins: {}",
r.output
)));
}
}
let save_skill = client.schematic.save();
client.execute_skill(&save_skill, None)?;
let check_skill = client.schematic.check();
let r = client.execute_skill(&check_skill, None)?;
Ok(json!({
"status": "success",
"target": format!("{}/{}/{}", spec.target.lib, spec.target.cell, spec.target.view),
"instances": spec.instances.len(),
"connections": spec.connections.len() + spec.globals.len(),
"pins": spec.pins.len(),
"check": r.output,
}))
}
pub fn parse_skill_json(output: &str) -> Value {
let s = output.trim_matches('"');
if let Ok(v) = serde_json::from_str(s) {
return v;
}
let unescaped = s.replace("\\\"", "\"").replace("\\\\", "\\");
serde_json::from_str(&unescaped).unwrap_or_else(|_| json!({"raw": output}))
}
pub fn list_instances() -> Result<Value> {
let client = VirtuosoClient::from_env()?;
let skill = client.schematic.list_instances();
let r = client.execute_skill(&skill, None)?;
if !r.skill_ok() {
return Err(VirtuosoError::Execution(format!(
"Failed to list instances: {}",
r.output
)));
}
Ok(parse_skill_json(&r.output))
}
pub fn list_nets() -> Result<Value> {
let client = VirtuosoClient::from_env()?;
let skill = client.schematic.list_nets();
let r = client.execute_skill(&skill, None)?;
if !r.skill_ok() {
return Err(VirtuosoError::Execution(format!(
"Failed to list nets: {}",
r.output
)));
}
Ok(parse_skill_json(&r.output))
}
pub fn list_pins() -> Result<Value> {
let client = VirtuosoClient::from_env()?;
let skill = client.schematic.list_pins();
let r = client.execute_skill(&skill, None)?;
if !r.skill_ok() {
return Err(VirtuosoError::Execution(format!(
"Failed to list pins: {}",
r.output
)));
}
Ok(parse_skill_json(&r.output))
}
pub fn get_params(inst: &str) -> Result<Value> {
let client = VirtuosoClient::from_env()?;
let skill = client.schematic.get_instance_params(inst);
let r = client.execute_skill(&skill, None)?;
if !r.skill_ok() {
return Err(VirtuosoError::Execution(format!(
"Failed to get params for '{}': {}",
inst, r.output
)));
}
Ok(json!({"instance": inst, "params": parse_skill_json(&r.output)}))
}