1 /*----------------------------------------------------------------------*/
2 /* ngspice.c --- Handling of background ngspice process. */
3 /* Copyright (c) 2004 Tim Edwards, MultiGiG, Inc. */
4 /* These routines work only if ngspice is on the system. */
5 /*----------------------------------------------------------------------*/
7 /* #undef SPICE_DEBUG */
18 #include <sys/wait.h> /* for waitpid() */
22 #include <sys/types.h>
26 /* This code should be able to run without requiring Tcl-based XCircuit */
27 /* but this has not been worked out, so instead it is just disabled. */
31 /*----------------------------------------------------------------------*/
33 /*----------------------------------------------------------------------*/
38 #include <X11/Intrinsic.h>
39 #include <X11/StringDefs.h>
42 #include "colordefs.h"
45 /*----------------------------------------------------------------------*/
46 /* Function prototype declarations */
47 /*----------------------------------------------------------------------*/
48 #include "prototypes.h"
50 /*------------------------------------------------------------------------*/
51 /* External Variable definitions */
52 /*------------------------------------------------------------------------*/
54 extern char _STR2
[250], _STR
[150];
55 extern Globaldata xobjs
;
56 extern XCWindowData
*areawin
;
58 extern int number_colors
;
59 extern colorindex
*colorlist
;
60 extern Cursor appcursors
[NUM_CURSORS
];
66 int spice_state
; /* Track state of the simulator */
68 #define SPICE_INIT 0 /* Initial state; ngspice interpreter is idle. */
69 #define SPICE_BUSY 1 /* Simulation in progress; ngspice is busy. */
70 #define SPICE_READY 2 /* Simulation at break; ngspice is waiting for */
73 #define EXPECT_ANY 0 /* Read routine accepts all input until input */
74 /* buffer is empty, then returns. */
75 #define EXPECT_PROMPT 1 /* Read routine accepts all input up to the */
76 /* next ngspice prompt, removes the prompt, */
77 /* sets the Tcl return value to the history */
78 /* number, and returns. */
79 #define EXPECT_REF 2 /* Read routine accepts all input until the */
80 /* first "Reference value" line, truncates the */
81 /* output, and sets the Tcl return value to the */
82 /* reference value seen. */
88 /*------------------------------------------------------*/
89 /* Global variable definitions */
90 /*------------------------------------------------------*/
93 pid_t spiceproc
= -1; /* ngspice process */
95 HANDLE spiceproc
= INVALID_HANDLE_VALUE
;
98 int pipeRead
, pipeWrite
; /* I/O pipe pair for ngspice */
100 /*------------------------------------------------------*/
101 /* Start a ngspice process and arrange the I/O pipes */
102 /* (Commented lines cause ngspice to relay its stderr */
103 /* to xcircuit's stderr) */
107 /* -1 failed---ngspice exited with error condition */
108 /* -2 exec failed---e.g., unable to find ngspice */
109 /* 1 ngspice is already running */
110 /*------------------------------------------------------*/
117 PROCESS_INFORMATION pr_info
;
118 SECURITY_ATTRIBUTES attr
;
119 HANDLE child_stdin
, child_stdout
, tmp
;
122 attr
.nLength
= sizeof(SECURITY_ATTRIBUTES
);
123 attr
.lpSecurityDescriptor
= NULL
;
124 attr
.bInheritHandle
= FALSE
;
126 if (!CreatePipe(&tmp
, &child_stdout
, &attr
, 0))
129 SetHandleInformation(tmp
, HANDLE_FLAG_INHERIT
, 0);
130 pipeRead
= _open_osfhandle(tmp
, _O_RDONLY
);
131 if (!CreatePipe(&child_stdin
, &tmp
, &attr
, 0))
133 SetHandleInformation(tmp
, HANDLE_FLAG_INHERIT
, 0);
134 pipeWrite
= _open_osfhandle(tmp
, _O_WRONLY
);
136 ZeroMemory(&pr_info
, sizeof(PROCESS_INFORMATION
));
137 ZeroMemory(&st_info
, sizeof(STARTUPINFO
));
138 st_info
.cb
= sizeof(STARTUPINFO
);
139 st_info
.dwFlags
= STARTF_USESTDHANDLES
;
140 st_info
.hStdError
= st_info
.hStdOutput
= child_stdout
;
141 st_info
.hStdInput
= child_stdin
;
143 snprintf(cmd
, 4095, "%s -p", SPICE_EXEC
);
144 if (CreateProcess(NULL
, cmd
, NULL
, NULL
, TRUE
, 0, NULL
, NULL
,
145 &st_info
, &pr_info
) == 0)
148 CloseHandle(pr_info
.hThread
);
149 spiceproc
= pr_info
.hProcess
;
153 Fprintf(stderr
, "Exec of ngspice failed\n");
157 #else /* ! MSC_VER */
161 int std_in
[2], std_out
[2], ret
;
166 /* Input goes from xcircuit to ngspice to provide commands to the */
167 /* ngspice interpreter front-end. Output goes from ngspice back to */
168 /* xcircuit to provide data for displaying. */
170 if (spiceproc
< 0) { /* ngspice is not running yet */
172 if (spiceproc
== 0) { /* child process (ngspice) */
174 fprintf(stdout
, "Calling %s\n", SPICE_EXEC
);
179 dup2(std_in
[1], fileno(stdout
));
180 dup2(std_in
[1], fileno(stderr
));
181 dup2(std_out
[0], fileno(stdin
));
185 /* Force interactive mode with "-p". Otherwise, ngspice will */
186 /* detect that the pipe is not a terminal and switch to batch- */
187 /* processing mode. */
189 execlp(SPICE_EXEC
, "ngspice", "-p", (char *)NULL
);
191 Fprintf(stderr
, "Exec of ngspice failed\n");
194 else if (spiceproc
< 0) {
195 Wprintf("Error: ngspice not running");
200 return -1; /* error condition */
202 else { /* parent process */
205 pipeRead
= std_in
[0];
206 pipeWrite
= std_out
[1];
207 return 0; /* success */
210 return 1; /* spice is already running */
213 #endif /* ! MSC_VER */
215 /*------------------------------------------------------*/
216 /* Send text to the ngspice simulator */
217 /*------------------------------------------------------*/
219 void send_to_spice(char *text
)
221 int tlen
= strlen(text
);
222 write(pipeWrite
, text
, tlen
);
223 if (*(text
+ tlen
- 1) != '\n')
224 write(pipeWrite
, "\n", 1);
226 if (!strncmp(text
, "run", 3) || !strncmp(text
, "resume", 6))
227 spice_state
= SPICE_BUSY
;
228 else if (!strncmp(text
, "quit", 4) || !strncmp(text
, "exit", 4))
229 spice_state
= SPICE_INIT
;
232 /* fprintf(stdout, "writing: %s\n", text); */
236 /*------------------------------------------------------*/
237 /* Get text from the ngspice simulator */
238 /*------------------------------------------------------*/
240 #define RECV_BUFSIZE 1024
242 char *recv_from_spice(Tcl_Interp
*interp
, int expect
)
244 int n
, numc
, totc
, nfd
;
245 fd_set readfds
, writefds
, exceptfds
;
246 static char *buffer
= NULL
;
247 struct timeval timeout
;
252 buffer
= (char *)malloc(RECV_BUFSIZE
);
254 /* 2-second timeout on reads */
255 timeout
.tv_sec
= (expect
== EXPECT_ANY
) ? 0 : 2;
263 numc
= RECV_BUFSIZE
- 1;
264 while (numc
== RECV_BUFSIZE
- 1) {
268 FD_SET(pipeRead
, &readfds
);
269 FD_SET(pipeRead
, &exceptfds
);
272 n
= select(nfd
, &readfds
, &writefds
, &exceptfds
, &timeout
);
274 if (expect
!= EXPECT_ANY
)
275 Fprintf(stderr
, "Timeout during select()\n");
276 return buffer
; /* Timeout, or no data */
279 /* Interrupt here? */
280 Fprintf(stderr
, "Exception received by select()\n");
283 numc
= read(pipeRead
, bufptr
, RECV_BUFSIZE
- 1);
284 *(bufptr
+ numc
) = '\0'; /* Terminate the line just read */
289 for (pptr
= bufptr
+ numc
- 1; pptr
>= buffer
; pptr
--)
292 if (!strncmp(pptr
+ 1, "ngspice", 7)) {
294 if (sscanf(pptr
+ 8, "%d", &numc
) == 1) {
295 sprintf(_STR2
, "%d", numc
);
296 Tcl_SetResult(interp
, _STR2
, TCL_STATIC
);
300 /* Force the process to continue reading until the */
301 /* prompt is received. */
302 numc
= RECV_BUFSIZE
- 1;
306 for (pptr
= bufptr
+ numc
- 1; pptr
> buffer
; pptr
--) {
308 while ((--pptr
>= buffer
) && !isspace(*pptr
));
309 if (sscanf(pptr
+ 1, "%g", &refval
)) {
310 sprintf(_STR2
, "%g", refval
);
311 Tcl_SetResult(interp
, _STR2
, TCL_STATIC
);
316 /* Force the process to continue reading until a */
317 /* reference value is received. */
318 numc
= RECV_BUFSIZE
- 1;
320 /* Drop through to below to remove the control chars */
323 /* Remove control characters from the input */
325 while (*pptr
!= '\0') {
326 if (*pptr
== '\r') *pptr
= '\n';
327 else if (!isprint(*pptr
)) *pptr
= ' ';
333 if (numc
== RECV_BUFSIZE
- 1) {
334 buffer
= (char *)realloc(buffer
, totc
+ RECV_BUFSIZE
);
335 bufptr
= buffer
+ totc
;
340 /* fprintf(stdout, "read %d characters: %s", totc, buffer); */
345 /*------------------------------------------------------*/
346 /* Send break (ctrl-c) to the ngspice simulator */
347 /* Return 0 on success, -1 if ngspice didn't return a */
348 /* prompt within the timeout period. */
349 /*------------------------------------------------------*/
351 int break_spice(Tcl_Interp
*interp
)
356 if (spiceproc
== INVALID_HANDLE_VALUE
) return 0; /* No process to interrupt */
358 if (spiceproc
== -1) return 0; /* No process to interrupt */
361 /* Sending SIGINT in any state other than "busy" will kill */
362 /* the ngspice process, not interrupt it! */
364 if (spice_state
== SPICE_BUSY
) {
366 kill(spiceproc
, SIGINT
);
368 TerminateProcess(spiceproc
, -1);
370 msg
= recv_from_spice(interp
, EXPECT_PROMPT
); /* Flush the output */
375 spice_state
= SPICE_READY
;
377 /* fprintf(stdout, "interrupt ngspice\n"); */
382 /*------------------------------------------------------*/
383 /* Resume the ngspice simulator */
384 /*------------------------------------------------------*/
388 spice_state
= SPICE_BUSY
;
389 write(pipeWrite
, "resume\n", 7);
391 /* fprintf(stdout, "resume ngspice\n"); */
395 /*------------------------------------------------------*/
396 /* Exit ngspice. . . not so gently */
397 /*------------------------------------------------------*/
402 if (spiceproc
== INVALID_HANDLE_VALUE
) return -1; /* ngspice not running */
404 if (spiceproc
< 0) return -1; /* ngspice not running */
408 fprintf(stdout
, "Waiting for ngspice to exit\n");
411 kill(spiceproc
, SIGKILL
);
412 waitpid(spiceproc
, NULL
, 0);
414 TerminateProcess(spiceproc
, -1);
417 fprintf(stdout
, "ngspice has exited\n");
421 spiceproc
= INVALID_HANDLE_VALUE
;
425 spice_state
= SPICE_INIT
;
430 /*------------------------------------------------------*/
433 /* Run the ngspice simulator from the tcl command line. */
434 /* Currently, the Tcl command-line interface is the */
435 /* *only* way to access the ngspice simulator through */
436 /* XCircuit. ngspice runs as a separate process via a */
437 /* fork call, and sends and receives through the */
438 /* "spice" command in Tcl-XCircuit. */
439 /*------------------------------------------------------*/
441 int xctcl_spice(ClientData clientData
, Tcl_Interp
*interp
,
442 int objc
, Tcl_Obj
*CONST objv
[])
445 char *msg
, *msgin
, *eptr
;
447 static char *subCmds
[] = {
448 "start", "send", "get", "break", "resume", "status",
449 "flush", "exit", "run", "print", NULL
452 StartIdx
, SendIdx
, GetIdx
, BreakIdx
, ResumeIdx
, StatusIdx
,
453 FlushIdx
, ExitIdx
, RunIdx
, PrintIdx
456 if (objc
== 1 || objc
> 3) {
457 Tcl_WrongNumArgs(interp
, 1, objv
, "option ?arg ...?");
460 else if ((result
= Tcl_GetIndexFromObj(interp
, objv
[1],
461 (CONST84
char **)subCmds
,
462 "option", 0, &idx
)) != TCL_OK
) {
468 if (spice_state
!= SPICE_INIT
) {
469 Tcl_SetResult(interp
, "ngspice process already running", NULL
);
472 result
= start_spice();
474 Tcl_SetResult(interp
, "unable to run ngspice", NULL
);
477 msg
= recv_from_spice(interp
, EXPECT_PROMPT
);
480 Fprintf(stdout
, "%s", msg
);
483 /* Force the output not to use "more" */
484 send_to_spice("set nomoremode true");
485 msg
= recv_from_spice(interp
, EXPECT_PROMPT
);
487 /* This needs to be considerably more clever. Should */
488 /* also generate the spice file if it does not exist. */
489 /* Finally, needs to add models and some statement */
490 /* like "trans" to the end to start the simulation. */
492 sprintf(_STR2
, "source %s.spc", topobject
->name
);
493 send_to_spice(_STR2
);
494 msg
= recv_from_spice(interp
, EXPECT_PROMPT
);
498 Fprintf(stdout
, "%s", msg
);
500 spice_state
= SPICE_READY
;
505 if (spice_state
!= SPICE_READY
) {
506 Tcl_SetResult(interp
, "Spice process busy or nonexistant", NULL
);
510 send_to_spice("run");
511 msg
= recv_from_spice(interp
, EXPECT_REF
);
515 spice_state
= SPICE_BUSY
;
516 Fprintf(stdout
, "%s", msg
);
522 /* Allow commands in INIT mode, because they may come from */
523 /* parameterized labels. No output will be generated. */
524 if (spice_state
== SPICE_INIT
)
526 else if (spice_state
== SPICE_BUSY
)
527 if (break_spice(interp
) < 0)
530 if (objc
== 2) break;
532 /* Prevent execution of "run" and "resume" using this */
533 /* method, so we keep control over the output and the */
534 /* state of the simulator. */
536 msgin
= Tcl_GetString(objv
[2]);
537 if (!strncmp(msgin
, "run", 3) || !strncmp(msgin
, "resume", 6)) {
538 Tcl_SetResult(interp
, "Do not use \"send\" with "
539 "\"run\" or \"resume\"\n", NULL
);
543 send_to_spice(msgin
);
545 msg
= recv_from_spice(interp
, EXPECT_PROMPT
);
550 /* ngspice echos the terminal, so walk past input */
551 while (*mp
++ == *msgin
++);
552 Tcl_SetResult(interp
, mp
, TCL_STATIC
);
557 if (spice_state
== SPICE_INIT
)
559 msg
= recv_from_spice(interp
, EXPECT_ANY
); /* Grab input and discard */
563 if (spice_state
== SPICE_INIT
)
565 msg
= recv_from_spice(interp
, EXPECT_ANY
); /* Grab input and return */
567 Tcl_SetResult(interp
, msg
, TCL_STATIC
);
571 if (spice_state
== SPICE_INIT
)
573 else if (spice_state
== SPICE_BUSY
)
574 if (break_spice(interp
) < 0)
577 /* Return the value of the stepsize from the function. */
578 send_to_spice("print length(TIME)");
582 /* Allow command in INIT mode, because it may come from */
583 /* parameterized labels. No output will be generated. */
584 if (spice_state
== SPICE_INIT
)
587 /* Do *not* allow this command to be sent while spice */
589 else if (spice_state
== SPICE_BUSY
)
590 if (break_spice(interp
) < 0)
593 if (objc
== 2) break;
595 /* Similar to "spice send {print ...}", but processes */
596 /* the output like "spice break" does to return just */
597 /* the result. However, if no index is provided for */
598 /* the variable, we determine the timestep and set the */
599 /* index accordingly. */
601 msg
= Tcl_GetString(objv
[2]);
602 if (strchr(msg
, '[') != NULL
)
603 sprintf(_STR2
, "print %s", msg
);
608 send_to_spice("print length(TIME)");
609 stepstr
= recv_from_spice(interp
, EXPECT_PROMPT
);
610 if (stepstr
&& ((eptr
= strrchr(stepstr
, '=')) != NULL
)) {
612 while (isspace(*eptr
)) eptr
++;
613 if (sscanf(eptr
, "%g", &refval
) == 1)
614 sprintf(_STR2
, "print %s[%d]", msg
, (int)(refval
- 1));
616 sprintf(_STR2
, "print %s", msg
);
619 sprintf(_STR2
, "print %s", msg
);
621 send_to_spice(_STR2
);
624 msg
= recv_from_spice(interp
, EXPECT_PROMPT
); /* Grab the output */
625 if (msg
&& ((eptr
= strrchr(msg
, '=')) != NULL
)) {
627 while (isspace(*eptr
)) eptr
++;
628 Tcl_SetResult(interp
, eptr
, TCL_STATIC
);
630 else if (*msg
== '\0')
635 if (spice_state
!= SPICE_READY
) {
636 Tcl_SetResult(interp
, "Spice process busy or nonexistant", NULL
);
643 switch(spice_state
) {
645 Tcl_SetResult(interp
, "init", NULL
);
648 Tcl_SetResult(interp
, "busy", NULL
);
651 Tcl_SetResult(interp
, "ready", NULL
);
660 return XcTagCallback(interp
, objc
, objv
);
663 #endif /* TCL_WRAPPER */
664 /*----------------------------------------------------------------------*/