diff --git a/src/frontend/Makefile.am b/src/frontend/Makefile.am index 8f75443ca..e822f615a 100644 --- a/src/frontend/Makefile.am +++ b/src/frontend/Makefile.am @@ -53,6 +53,8 @@ libfte_la_SOURCES = \ com_let.h \ com_optimize.c \ com_optimize.h \ + com_checkpoint.c \ + com_checkpoint.h \ com_option.c \ com_option.h \ com_plot.c \ diff --git a/src/frontend/com_commands.h b/src/frontend/com_commands.h index 063e0a173..06ff152d2 100644 --- a/src/frontend/com_commands.h +++ b/src/frontend/com_commands.h @@ -7,6 +7,8 @@ void com_alter(wordlist *wl); void com_altermod(wordlist *wl); void com_alterparam(wordlist *wl); void com_optimize(wordlist *wl); /* Enhancement-130 */ +void com_savestate(wordlist *wl); /* Enhancement-131 */ +void com_loadstate(wordlist *wl); /* Enhancement-131 */ void com_meas(wordlist *wl); void com_sysinfo(wordlist *wl); void com_check_ifparm(wordlist *wl); diff --git a/src/frontend/commands.c b/src/frontend/commands.c index 69735057e..c800aefb9 100644 --- a/src/frontend/commands.c +++ b/src/frontend/commands.c @@ -428,6 +428,14 @@ struct comm spcp_coms[] = { { 040, 040, 040, 040 }, E_DEFHMASK, 1, LOTS, NULL, "-param name init lo hi ... -analysis -minimize [-maxiter N] [-tol T] [-verbose] : Nelder-Mead parameter optimizer." }, + { "savestate", com_savestate, FALSE, TRUE, /* Enhancement-131 */ + { 1, 040000, 040000, 040000 }, E_DEFHMASK, 1, 1, + NULL, + "file : Save the current transient state to a checkpoint file." }, + { "loadstate", com_loadstate, FALSE, TRUE, /* Enhancement-131 */ + { 1, 040000, 040000, 040000 }, E_DEFHMASK, 1, 1, + NULL, + "file : Restore a transient checkpoint and continue the .tran run." }, { "resume", com_resume, TRUE, FALSE, { 0, 0, 0, 0 }, E_DEFHMASK, 0, 0, NULL, diff --git a/src/include/ngspice/cktdefs.h b/src/include/ngspice/cktdefs.h index 1eac359b8..687852e25 100644 --- a/src/include/ngspice/cktdefs.h +++ b/src/include/ngspice/cktdefs.h @@ -270,6 +270,8 @@ struct CKTcircuit { unsigned int CKTkeepOpInfo:1; /* flag for small signal analyses */ unsigned int CKTcopyNodesets:1; /* NodesetFIX */ unsigned int CKTnodeDamping:1; /* flag for node damping fix */ + unsigned int CKTcheckpoint:1; /* Enhancement-131: DCtran should continue from a restored + checkpoint (keep loaded state, build a fresh output plot) */ double CKTabsDv; /* abs limit for iter-iter voltage change */ double CKTrelDv; /* rel limit for iter-iter voltage change */ int CKTtroubleNode; /* Non-convergent node number */ diff --git a/src/spicelib/analysis/dctran.c b/src/spicelib/analysis/dctran.c index cc6e933db..0c643a7ac 100644 --- a/src/spicelib/analysis/dctran.c +++ b/src/spicelib/analysis/dctran.c @@ -113,6 +113,78 @@ DCtran(CKTcircuit *ckt, #if defined SHARED_MODULE int redostep; #endif + if(ckt->CKTcheckpoint) { + /* Enhancement-131: continue a transient run from a checkpoint that was + restored from disk (see com_loadstate() in com_checkpoint.c). The + restored circuit already carries the saved CKTtime, CKTdelta, + CKTstates[], CKTrhsOld and integration order, so -- unlike a fresh + run -- we must NOT reset them or redo the DC operating point. Unlike + the in-memory `resume` path below we also cannot 666-relink an + existing output plot (there is none across a reload), so we open a + FRESH plot exactly as the restart branch does, then jump straight + into the time-stepping loop. */ + ckt->CKTcheckpoint = 0; /* one-shot */ + ckt->CKTmode = (ckt->CKTmode & MODEUIC) | MODETRAN | MODEINITPRED; + INIT_STATS(); + firsttime = 0; + if(ckt->CKTminBreak == 0) + ckt->CKTminBreak = ckt->CKTmaxStep * 5e-5; + +#ifdef XSPICE + /* The in-memory `resume` inherits these from the original run; a + cross-session restore starts a fresh process, so initialise the + XSPICE temporary-breakpoint markers (otherwise the stepping loop + forces CKTdelta to breakpoint.current - CKTtime = -CKTtime). */ + g_mif_info.circuit.anal_type = MIF_DC; + g_mif_info.breakpoint.current = 1.0e30; + g_mif_info.breakpoint.last = 1.0e30; +#endif + + /* Build a fresh output plot for the continuation. */ + error = CKTnames(ckt, &numNames, &nameList); + if(error) return(error); + SPfrontEnd->IFnewUid (ckt, &timeUid, NULL, "time", UID_OTHER, NULL); + error = SPfrontEnd->OUTpBeginPlot (ckt, ckt->CKTcurJob, + ckt->CKTcurJob->JOBname, + timeUid, IF_REAL, + numNames, nameList, IF_REAL, + &(job->TRANplot)); + tfree(nameList); + if(error) return(error); + + if (ckt->CKTsoaCheck) + error = CKTsoaInit(); + + /* Fix up the breakpoint list for the continuation. Drop any restored + breakpoints at or before the resume time (they are in the past and + would yield a negative time step); keep the genuine future ones (e.g. + pending source edges). Then guarantee the (possibly extended) final + time is present and that at least two entries exist -- the stepping + loop reads CKTbreaks[1]. Sources re-schedule their own future edges + as the run proceeds. */ + if(ckt->CKTbreaks != NULL && ckt->CKTbreakSize > 0) { + int nb, w = 0; + for(nb = 0; nb < ckt->CKTbreakSize; nb++) + if(ckt->CKTbreaks[nb] > ckt->CKTtime + ckt->CKTdelmin) + ckt->CKTbreaks[w++] = ckt->CKTbreaks[nb]; + ckt->CKTbreakSize = w; + } + if(ckt->CKTbreaks == NULL || ckt->CKTbreakSize < 1) { + if(ckt->CKTbreaks) FREE(ckt->CKTbreaks); + ckt->CKTbreaks = TMALLOC(double, 2); + if(ckt->CKTbreaks == NULL) return(E_NOMEM); + ckt->CKTbreaks[0] = ckt->CKTfinalTime; + ckt->CKTbreaks[1] = ckt->CKTfinalTime + ckt->CKTmaxStep; + ckt->CKTbreakSize = 2; + } else { + if(ckt->CKTfinalTime > ckt->CKTtime) + CKTsetBreak(ckt, ckt->CKTfinalTime); + if(ckt->CKTbreakSize < 2) + CKTsetBreak(ckt, ckt->CKTfinalTime + ckt->CKTmaxStep); + } + + goto resume; + } if(restart || ckt->CKTtime == 0) { /* dctran() is entered here upon starting transient simulation with time 0 and restart 1. diff --git a/visualc/vngspice.vcxproj b/visualc/vngspice.vcxproj index 95f500676..7f0782510 100644 --- a/visualc/vngspice.vcxproj +++ b/visualc/vngspice.vcxproj @@ -874,6 +874,7 @@ + @@ -1490,6 +1491,7 @@ +