1use crate::client::bridge::escape_skill_string;
2
3const SCH_CV_VAR: &str = "RB_SCH_CV";
11
12fn cv_guard() -> String {
15 format!(
16 r#"unless(boundp('{SCH_CV_VAR}) && {SCH_CV_VAR} && dbIsValidObject({SCH_CV_VAR}) error("RB_SCH_CV is not set or invalid — run 'vcli schematic open lib/cell/view' first"))"#
17 )
18}
19
20#[derive(Default)]
21pub struct SchematicOps;
22
23impl SchematicOps {
24 pub fn new() -> Self {
25 Self
26 }
27
28 pub fn create_instance(
29 &self,
30 lib: &str,
31 cell: &str,
32 view: &str,
33 name: &str,
34 origin: (i64, i64),
35 orient: &str,
36 ) -> String {
37 let lib = escape_skill_string(lib);
38 let cell = escape_skill_string(cell);
39 let view = escape_skill_string(view);
40 let name = escape_skill_string(name);
41 let orient = escape_skill_string(orient);
42 let (x, y) = origin;
43 let guard = cv_guard();
44 format!(
45 r#"let((cv master inst) {guard} cv = RB_SCH_CV master = dbOpenCellViewByType("{lib}" "{cell}" "{view}" nil "r") inst = dbCreateInst(cv master "{name}" list({x} {y}) "{orient}" 1) inst)"#
46 )
47 }
48
49 pub fn create_wire(&self, points: &[(i64, i64)], layer: &str, net_name: &str) -> String {
50 let layer = escape_skill_string(layer);
51 let net_name = escape_skill_string(net_name);
52 let pts: String = points
53 .iter()
54 .map(|(x, y)| format!("list({x} {y})"))
55 .collect::<Vec<_>>()
56 .join(" ");
57 let guard = cv_guard();
58 format!(
59 r#"let((cv) {guard} cv = RB_SCH_CV dbCreateWire(cv dbMakeNet(cv "{net_name}") dbFindLayerByName(cv "{layer}") list({pts})))"#
60 )
61 }
62
63 pub fn create_wire_between_terms(
64 &self,
65 inst1: &str,
66 _term1: &str,
67 inst2: &str,
68 _term2: &str,
69 net_name: &str,
70 ) -> String {
71 let inst1 = escape_skill_string(inst1);
72 let inst2 = escape_skill_string(inst2);
73 let net_name = escape_skill_string(net_name);
74 let guard = cv_guard();
75 format!(
76 r#"let((cv net) {guard} cv = RB_SCH_CV net = dbMakeNet(cv "{net_name}") dbCreateWire(net dbFindTermByName(cv "{inst1}") dbFindTermByName(cv "{inst2}")))"#
77 )
78 }
79
80 pub fn create_wire_label(&self, net_name: &str, origin: (i64, i64)) -> String {
81 let net_name = escape_skill_string(net_name);
82 let (x, y) = origin;
83 let guard = cv_guard();
84 format!(
85 r#"let((cv net) {guard} cv = RB_SCH_CV net = dbFindNetByName(cv "{net_name}") when(net dbCreateLabel(cv net "{net_name}" list({x} {y}) "centerCenter" "R0" "stick" 0.0625)))"#
86 )
87 }
88
89 pub fn create_pin(&self, net_name: &str, _pin_type: &str, origin: (i64, i64)) -> String {
90 let net_name = escape_skill_string(net_name);
91 let (x, y) = origin;
92 let guard = cv_guard();
93 format!(
94 r#"let((cv net pinInst) {guard} cv = RB_SCH_CV net = dbMakeNet(cv "{net_name}") pinInst = dbCreateInst(cv dbOpenCellViewByType("basic" "ipin" "symbol" nil "r") "PIN_{net_name}" list({x} {y}) "R0" 1) dbCreatePin(net pinInst))"#
95 )
96 }
97
98 pub fn check(&self) -> String {
99 let guard = cv_guard();
100 format!(r#"let((cv) {guard} cv = RB_SCH_CV schCheck(cv))"#)
101 }
102
103 pub fn open_cellview(&self, lib: &str, cell: &str, view: &str) -> String {
104 let lib = escape_skill_string(lib);
105 let cell = escape_skill_string(cell);
106 let view = escape_skill_string(view);
107 format!(r#"RB_SCH_CV = dbOpenCellViewByType("{lib}" "{cell}" "{view}" "schematic" "a")"#)
111 }
112
113 pub fn save(&self) -> String {
114 let guard = cv_guard();
115 format!(r#"let((cv) {guard} cv = RB_SCH_CV dbSave(cv))"#)
116 }
117
118 pub fn set_instance_param(&self, inst_name: &str, param: &str, value: &str) -> String {
119 let inst_name = escape_skill_string(inst_name);
120 let param = escape_skill_string(param);
121 let value = escape_skill_string(value);
122 let guard = cv_guard();
123 format!(
124 r#"let((cv inst) {guard} cv = RB_SCH_CV inst = car(setof(i cv~>instances i~>name == "{inst_name}")) when(inst dbReplaceProp(inst "{param}" "string" "{value}")))"#
125 )
126 }
127
128 pub fn list_instances(&self) -> String {
132 let guard = cv_guard();
133 format!(
134 r#"let((cv out sep lib cell) {guard} cv = RB_SCH_CV out = "[" sep = "" foreach(inst cv~>instances lib = if(inst~>master inst~>master~>libName "?") cell = if(inst~>master inst~>master~>cellName "?") out = strcat(out sep sprintf(nil "{{\"name\":\"%s\",\"master\":\"%s/%s\",\"x\":%g,\"y\":%g}}" inst~>name lib cell car(inst~>xy) cadr(inst~>xy))) sep = ",") strcat(out "]"))"#
135 )
136 }
137
138 pub fn list_nets(&self) -> String {
140 let guard = cv_guard();
141 format!(
142 r#"let((cv out sep) {guard} cv = RB_SCH_CV out = "[" sep = "" foreach(net cv~>nets out = strcat(out sep sprintf(nil "\"%s\"" net~>name)) sep = ",") strcat(out "]"))"#
143 )
144 }
145
146 pub fn list_pins(&self) -> String {
148 let guard = cv_guard();
149 format!(
150 r#"let((cv out sep) {guard} cv = RB_SCH_CV out = "[" sep = "" foreach(term cv~>terminals out = strcat(out sep sprintf(nil "{{\"name\":\"%s\",\"direction\":\"%s\"}}" term~>name term~>direction)) sep = ",") strcat(out "]"))"#
151 )
152 }
153
154 pub fn get_instance_params(&self, inst_name: &str) -> String {
156 let inst_name = escape_skill_string(inst_name);
157 let guard = cv_guard();
158 format!(
159 r#"let((cv inst out sep v) {guard} cv = RB_SCH_CV inst = car(setof(i cv~>instances strcmp(i~>name "{inst_name}")==0)) if(inst then out = "{{" sep = "" foreach(prop inst~>prop when(prop~>name != nil v = prop~>value when(v out = strcat(out sep sprintf(nil "\"%s\":\"%s\"" prop~>name if(stringp(v) v sprintf(nil "%L" v)))) sep = ","))) strcat(out "}}") else "null"))"#
160 )
161 }
162
163 pub fn assign_net(&self, inst_name: &str, term_name: &str, net_name: &str) -> String {
167 let inst_name = escape_skill_string(inst_name);
168 let term_name = escape_skill_string(term_name);
169 let net_name = escape_skill_string(net_name);
170 format!(
171 r#"let((cv inst iterm net) cv = RB_SCH_CV inst = car(setof(i cv~>instances strcmp(i~>name "{inst_name}")==0)) iterm = car(setof(x inst~>instTerms strcmp(x~>name "{term_name}")==0)) net = dbMakeNet(cv "{net_name}") when(iterm dbConnectToNet(iterm net)))"#
172 )
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 fn ops() -> SchematicOps {
181 SchematicOps::new()
182 }
183
184 #[test]
185 fn create_instance_uses_orient() {
186 let s = ops().create_instance("analogLib", "nmos4", "symbol", "M1", (100, 200), "MY");
187 assert!(s.contains("\"MY\""), "orient must be in SKILL: {s}");
188 assert!(
189 s.contains("100") && s.contains("200"),
190 "origin must be in SKILL: {s}"
191 );
192 assert!(s.contains("\"M1\""), "instance name must be quoted: {s}");
193 }
194
195 #[test]
196 fn create_instance_default_orient() {
197 let s = ops().create_instance("lib", "cell", "symbol", "X0", (0, 0), "R0");
198 assert!(s.contains("\"R0\""), "{s}");
199 }
200
201 #[test]
202 fn assign_net_uses_dbconnect() {
203 let s = ops().assign_net("M1", "G", "VIN");
204 assert!(s.contains("dbConnectToNet"), "must use dbConnectToNet: {s}");
205 assert!(
206 !s.contains("schCreateWire"),
207 "must not use schCreateWire: {s}"
208 );
209 assert!(
210 !s.contains("0 0"),
211 "hardcoded coordinates must be gone: {s}"
212 );
213 }
214
215 #[test]
216 fn assign_net_escapes_names() {
217 let s = ops().assign_net(r#"M"1"#, "D", "VDD");
218 assert!(s.contains(r#"M\"1"#), "inst name must be escaped: {s}");
219 }
220
221 #[test]
222 fn open_cellview_sets_global() {
223 let s = ops().open_cellview("myLib", "myCell", "schematic");
224 assert!(s.starts_with("RB_SCH_CV ="), "{s}");
225 assert!(s.contains("\"myLib\"") && s.contains("\"myCell\""), "{s}");
226 }
227
228 #[test]
229 fn cv_guard_is_injected_in_write_ops() {
230 let s = ops().create_wire(&[(0, 0), (10, 10)], "wire", "VDD");
231 assert!(s.contains("dbIsValidObject"), "guard must be present: {s}");
232 assert!(s.contains("dbCreateWire"), "{s}");
233 }
234
235 #[test]
236 fn create_wire_label_contains_guard() {
237 let s = ops().create_wire_label("GND", (50, 50));
238 assert!(s.contains("dbIsValidObject"), "{s}");
239 }
240
241 #[test]
242 fn save_contains_guard() {
243 let s = ops().save();
244 assert!(s.contains("dbIsValidObject"), "{s}");
245 assert!(s.contains("dbSave"), "{s}");
246 }
247}