/**************************************************************************** Copyright (C) 1987-2005 by Jeffery P. Hansen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "gsim.h" #define DEBUG_QUEUE 0 #define DEBUG_EVENT 0 extern int do_commandEcho; static SEvent *event_freelist = 0; void logMsg(char *s,...) { FILE *f; va_list ap; f = fopen("/dev/ttyp4","w"); va_start(ap,s); vfprintf(f,s,ap); va_end(ap); fprintf(f,"\n"); fclose(f); } SEvent *new_SEvent() { SEvent *E; static int fl_count = 0; static int ma_count = 0; if (event_freelist) { E = event_freelist; event_freelist = E->evbase.next; SState_zero(&E->evbase.state); fl_count++; assert(E->evbase.status == -1 || E->evbase.status == -2); E->evbase.status = 1; } else { E = (SEvent*) malloc(sizeof(SEvent)); SState_init(&E->evbase.state,1); ma_count++; E->evbase.status = 2; } E->evclass = EV_UNKNOWN; E->evbase.time = 0; E->evbase.next = 0; #if DEBUG_EVENT if (((fl_count+ma_count) % 10000) == 0) { sendMsg("echo event: fl:%d ma:%d\n",fl_count,ma_count); } #endif return E; } void delete_SEvent(SEvent *E,int S) { E->evbase.next = event_freelist; event_freelist = E; assert(E->evbase.status == 1 || E->evbase.status == 2); E->evbase.status = S; } SBreakpoint *new_SBreakpoint(int id,char *bp,SNet *N,SState *S,int op) { SBreakpoint *B = (SBreakpoint *) malloc(sizeof(SBreakpoint)); SState *R = alloc_SState(); SState_reinit(R,S->nbits); SState_copy(R,S); B->bp_id = id; B->bp_text = strdup(bp); B->bp_net = N; B->bp_value = R; B->bp_op = op; return B; } void delete_SBreakpoint(SBreakpoint *B) { free(B->bp_text); free_SState(B->bp_value); free(B); } void EvQueue_init(EvQueue *Q,SModule *M) { int i; Q->mod = M; Q->curStep = 0; Q->numPending = 0; Q->flags = 0; for (i = 0;i < THYMEWHEEL_SIZE;i++) Q->wheel_head[i] = Q->wheel_tail[i] = 0; Q->wheel_overflow = 0; NHash_init(&Q->bptable); } /* * Insert an event E into the sorted list Q. This insert is only used for "overflow" * with lead times exceeding the size of the time wheel. */ static SEvent *EventList_insert(SEvent *Q,SEvent *E) { if (!Q) { E->evbase.next = 0; return E; } if (E->evbase.time < Q->evbase.time) { E->evbase.next = Q; return E; } Q->evbase.next = EventList_insert(Q->evbase.next,E); return Q; } /* * Insert an event into the event queue. */ void EvQueue_enqueue(EvQueue *Q,SEvent *E) { int s = E->evbase.time & THYMEWHEEL_MASK; if (E->evbase.time-Q->curStep >= THYMEWHEEL_SIZE) { Q->wheel_overflow = EventList_insert(Q->wheel_overflow,E); Q->numPending++; return; } E->evbase.next = 0; if (Q->wheel_tail[s]) { Q->wheel_tail[s]->evbase.next = E; Q->wheel_tail[s] = E; } else { Q->wheel_tail[s] = Q->wheel_head[s] = E; } assert(E->evbase.next == 0); Q->numPending++; #if DEBUG_QUEUE sendMsg("comment enqueue [%p] @ %d p=%d",E,E->evbase.time,EvQueue_pending(Q)); #endif } SEvent *EvQueue_dequeue(EvQueue *Q) { int s; SEvent *E; /* * If there are any overflow events, do special processing to see if they need to be moved * to the main part of the queue. */ if (Q->wheel_overflow) { for (;;) { /* * Check to see if we can move overflow events to the main time wheel. */ if (Q->wheel_overflow->evbase.time - Q->curStep < THYMEWHEEL_SIZE) { SEvent *overflow_event = Q->wheel_overflow; Q->numPending--; Q->wheel_overflow = Q->wheel_overflow->evbase.next; EvQueue_enqueue(Q,overflow_event); } s = Q->curStep & THYMEWHEEL_MASK; if (Q->wheel_head[s]) break; Q->curStep++; } } else { s = Q->curStep & THYMEWHEEL_MASK; while (!Q->wheel_head[s]) { s = (s+1) & THYMEWHEEL_MASK; Q->curStep++; } } Q->numPending--; E = Q->wheel_head[s]; Q->wheel_head[s] = E->evbase.next; if (!Q->wheel_head[s]) Q->wheel_tail[s] = 0; E->evbase.next = 0; if (E->evclass == EV_PORT) { if (E->evport.port->p_ledge == Q->curStep) E->evport.port->p_ledge = NO_TIME; if (E->evport.port->p_nedge == Q->curStep) E->evport.port->p_nedge = E->evport.port->p_ledge; } #if DEBUG_QUEUE sendMsg("comment dequeue [%p] @ %d p=%d",E,E->evbase.time,EvQueue_pending(Q)); #endif return E; } void EvQueue_remove(EvQueue *Q,SEvent *E) { int s = E->evbase.time % THYMEWHEEL_SIZE; SEvent *P,*D; P = 0; for (D = Q->wheel_head[s];D;D = D->evbase.next) { if (D == E) break; P = D; } if (P) { P->evbase.next = E->evbase.next; if (!P->evbase.next) Q->wheel_tail[s] = P; } else { Q->wheel_head[s] = E->evbase.next; if (!Q->wheel_head[s]) Q->wheel_tail[s] = 0; } } void EvQueue_processControl(EvQueue *Q,SEvent *E) { switch (E->evctl.type) { case EVC_STOP : Q->flags &= ~(EVF_RUN|EVF_NOCMD); break; } } void EvQueue_process(EvQueue *Q,SEvent *E) { switch (E->evclass) { case EV_UNKNOWN : sendMsg("simerror %d: Event[%p] unknown",E->evbase.time,E); break; case EV_NET : #if DEBUG_EVENT sendMsg("comment %d: Event[%p] on net %s",E->evbase.time,E,E->evnet.net->n_name); #endif SNet_process(E->evnet.net,Q,E); break; case EV_PORT : #if DEBUG_EVENT sendMsg("comment %d: Event[%p] on port at net %s",E->evbase.time,E,E->evport.port->p_net->n_name); #endif SPort_process(E->evport.port,Q,E); break; case EV_GATE : #if DEBUG_EVENT sendMsg("comment %d: Event[%p] on gate %s",E->evbase.time,E,E->evgate.gate->g_name); #endif (*E->evgate.gate->g_type->gi_processEvent)(E->evgate.gate,Q,E); break; case EV_CONTROL : #if DEBUG_EVENT sendMsg("comment %d: Event[%p] control",E->evbase.time,E); #endif EvQueue_processControl(Q,E); break; default : sendMsg("simerror %d: Event[%p] bogonic %d",E->evbase.time,E,E->evclass); break; } } void EvQueue_doEvent(EvQueue *Q) { SEvent *E; E = EvQueue_dequeue(Q); EvQueue_process(Q,E); delete_SEvent(E,-2); } /* Execute all events in current time slot, and advance one step. */ void EvQueue_step(EvQueue *Q) { while (EvQueue_curPending(Q)) EvQueue_doEvent(Q); Q->curStep++; while (EvQueue_curPending(Q)) EvQueue_doEvent(Q); } /* Create and queue an event for port P changing to S at time t. */ void EvQueue_queuePortEvent(EvQueue *Q,SPort *P,SState *S,simTime t) { SEvent *E; E = new_SEvent(); E->evclass = EV_PORT; E->evport.time = t; SState_reinit(&E->evnet.state,S->nbits); SState_copy(&E->evnet.state,S); E->evport.port = P; EvQueue_enqueue(Q,E); } /* Find an event for port P at time t. */ SEvent *EvQueue_findPortEvent(EvQueue *Q,SPort *P,simTime t) { SEvent *E; t = t % THYMEWHEEL_SIZE; for (E = Q->wheel_head[t];E;E = E->evbase.next) if (E->evclass == EV_PORT && E->evport.port == P) break; return E; } void EvQueue_setNet(EvQueue *Q,SNet *N,SState *S,simTime delay) { SEvent *E = new_SEvent(); E->evclass = EV_NET; E->evnet.time = Q->curStep + delay; SState_reinit(&E->evnet.state,S->nbits); SState_copy(&E->evnet.state,S); E->evnet.net = N; EvQueue_enqueue(Q,E); } void EvQueue_qGateEv(EvQueue *Q,SGate *g,int c,void *d,simTime delay) { SEvent *E = new_SEvent(); E->evclass = EV_GATE; E->evgate.time = Q->curStep + delay; E->evgate.gate = g; E->evgate.type = c; E->evgate.gdata = d; EvQueue_enqueue(Q,E); } void EvQueue_control(EvQueue *Q,int type,void *d,int delay) { SEvent *E = new_SEvent(); E->evclass = EV_CONTROL; E->evctl.time = Q->curStep + delay; E->evctl.type = type; E->evctl.data = d; EvQueue_enqueue(Q,E); } void EvQueue_addBreak(EvQueue *Q,int n,char *bp) { SNet *N; SBreakpoint *B; char net[STRMAX]; char sop[STRMAX]; char val[STRMAX]; int bad = 0; int op = BPO_EQUAL; SState *S; if (sscanf(bp,"%[^!= \t] %[=!] %[^ \t\n]",net,sop,val) == 3) { if (strcmp(sop,"==") == 0) op = BPO_EQUAL; else if (strcmp(sop,"!=") == 0) op = BPO_NEQ; else bad = 1; } else if (sscanf(bp,"! %s",net) == 1) { strcpy(val,"0"); op = BPO_EQUAL; } else if (sscanf(bp,"%s",net) == 1) { strcpy(val,"0"); op = BPO_NEQ; } else bad = 1; if (bad) { sendMsg("comment break (bad syntax)"); return; } if (!(N = SModule_findNet(Q->mod,net))) { sendMsg("comment break (net '%s' not found)",net); return; } S = alloc_SState(); SState_convert(S,val); if (S->nbits > N->n_state.nbits) S->nbits = N->n_state.nbits; else SState_expandExtend(S,N->n_state.nbits); B = new_SBreakpoint(n,bp,N,S,op); NHash_insert(&Q->bptable,n,B); if (!N->n_breaks) N->n_breaks = new_NHash(); NHash_insert(N->n_breaks,n,B); sendMsg("comment break (added)"); free_SState(S); } void EvQueue_delBreak(EvQueue *Q,int n) { SBreakpoint *B = (SBreakpoint *) NHash_find(&Q->bptable,n); if (B) { NHash_remove(&Q->bptable,n); NHash_remove(B->bp_net->n_breaks,n); if (Hash_numElems(B->bp_net->n_breaks) == 0) { delete_NHash(B->bp_net->n_breaks); B->bp_net->n_breaks = 0; } delete_SBreakpoint(B); } } void EvQueue_check(EvQueue *Q) { int i; for (i = 0;i < THYMEWHEEL_SIZE;i++) { SEvent *E; for (E = Q->wheel_head[i];E;E = E->evbase.next) assert(E->evbase.status == 1 || E->evbase.status == 2); } } /* Change the value of a port 'delay' time units in the future. The effect of the change depends on previously queued events for this port. It is assumed that if inputs changed too quickly, that the output will be in the unknown state. This is managed by allowing no more than two events for a port in the queue at once, and no more than one event for a port in the same time slot. Furthermore, when there are two events in the queue, the earlier event is changed into an 'unknown' state transition. The last call to setPort is always assumed to be the most realiable, so if setPort is called for the same time slot more than once, the last call holds. Event time codes: D = delay of new event N = next event in queue L = last event in queue n = next event in queue (modified in place) l = last event in queue (modified in place) . = deleted last event , = deleted next event Old New Meaning ------------------------------------------------ D N New event with nothing in queue L N n Event replaced with new event L l D N D n D Next event modified L N L D N . L New event between next and last N L N l New event replaces last D N D L N l Event between next and last ignored N L N l Event at next time but not last ignored D D N L N , L Next event moved back to earlier time */ void EvQueue_setPort(EvQueue *Q,SPort *P,SState *xS,simTime delay) { SEvent *E; simTime evtime; SState *S = alloc_SState(); SState_reinit(S,xS->nbits); SState_copy(S,xS); if (P->p_comp) SState_not(S,S); #if 0 { char Sbuf[STRMAX],Pbuf[STRMAX]; SState_getstr(S,Sbuf); SState_getstr(&P->p_qstate,Pbuf); sendMsg("echo SState_eq(%s, %s) = %d",Sbuf,Pbuf,SState_eq(S,&P->p_qstate)); } #endif /* No need to set port */ if (SState_eq(S,&P->p_qstate)) { free_SState(S); return; } SState_copy(&P->p_qstate,S); /* Copy last queued state to port */ evtime = Q->curStep + delay; /* * If there are no events pending, queue a new event and set * the event pointers for the port to the time of the event. */ if (P->p_ledge == NO_TIME) { EvQueue_queuePortEvent(Q,P,S,evtime); P->p_nedge = P->p_ledge = evtime; free_SState(S); return; } if (evtime <= P->p_ledge) { /* * If the time of the new event is before or at the same time as * that latest event for this port, overwrite the state of the * event but keep it at the latest event time. */ E = EvQueue_findPortEvent(Q,P,P->p_ledge); SState_reinit(&E->evnet.state,S->nbits); SState_copy(&E->evport.state,S); if (evtime < P->p_nedge && P->p_nedge != P->p_ledge) { /* * If the new change is before the current next edge, and * the next edge is not the last edge, move the next edge * back to the new change time. */ E = EvQueue_findPortEvent(Q,P,P->p_nedge); EvQueue_remove(Q,E); E->evport.time = evtime; EvQueue_enqueue(Q,E); P->p_nedge = evtime; } } else { EvQueue_queuePortEvent(Q,P,S,evtime); if (P->p_ledge == P->p_nedge) { E = EvQueue_findPortEvent(Q,P,P->p_nedge); if (E) SState_unknown(&E->evport.state); } else { E = EvQueue_findPortEvent(Q,P,P->p_ledge); if (E) { EvQueue_remove(Q,E); delete_SEvent(E,-1); } } P->p_ledge = evtime; } free_SState(S); } static void EvQueue_execShow(EvQueue *Q,char *net) { SNet *N = SModule_findNet(Q->mod,net); SNet *cN; char buf[STRMAX],*p; if (!N) { error("Can't find net '%s'.",net); return; } cN = SNet_canonical(N); p = buf; p += sprintf(p,"valueof %s ",N->n_name); p += SState_getstr(&cN->n_state,p); sendMsg("%s",buf); } static void EvQueue_execSet(EvQueue *Q,char *net,char *val) { SNet *N = SModule_findNet(Q->mod,net); SState S; if (!N) { error("Can't find net '%s'.",net); return; } SState_init(&S,N->n_nbits); SState_convert(&S,val); EvQueue_setNet(Q,N,&S,0); SState_uninit(&S); /* * Restart simulation if in persistent run mode and the set triggered an event. */ if ((Q->flags & EVF_PERSRUN) && EvQueue_pending(Q) > 0) Q->flags |= EVF_RUN; } static void EvQueue_execWait(EvQueue *Q) { while (EvQueue_pending(Q) > 0) EvQueue_doEvent(Q); } static void EvQueue_execStep(EvQueue *Q,int steps) { int i; for (i = 0;i < steps;i++) EvQueue_step(Q); } static void EvQueue_ledMonitor(EvQueue *Q,char *gname,int d) { SGate *g; g = SModule_findGate(Q->mod,gname); if (!g) return; /* Should never happen */ if (d) { /* Enabling led monitoring */ Led_enableMonitor(g,Q); } else { /* Disabling led monitoring */ Led_disableMonitor(g,Q); } } static void EvQueue_execWatch(EvQueue *Q,char *net,int d) { SNet *N = SModule_findNet(Q->mod,net); char buf[STRMAX],*p; if (!N) { error("Can't find net '%s'.",net); return; } N = SNet_canonical(N); if (d) { if (!N->n_watchNames) N->n_watchNames = new_SHash(); SHash_insert(N->n_watchNames,net,N); } else { SHash_remove(N->n_watchNames,net); } p = buf; p += sprintf(p,"net %s ",net); p += SState_getstr(&N->n_state,p); p += sprintf(p," @ %d",Q->curStep); sendMsg("%s",buf); } static void EvQueue_execMemLoad(EvQueue *Q,char *fileName,char *gname) { SGate *g = 0; FILE *f; char buf[STRMAX]; Memory *M = 0; if (!(f = openInPath(fileName))) { sendMsg("simerror Could not open file '%s'.",fileName); return; } if (gname) { g = SModule_findGate(Q->mod,gname); if (!g) { sendMsg("simerror Could not find gate %s.",gname); return; } if (g->g_type->gi_code != GT_RAM && g->g_type->gi_code != GT_ROM) { sendMsg("simerror %s is not a RAM or ROM.",gname); return; } } M = (g && g->g_type->gi_getMem) ? (g->g_type->gi_getMem)(g) : 0; while (fgets(buf,STRMAX,f)) { char name[STRMAX]; if (sscanf(buf," memory %s",name) == 1) { g = SModule_findGate(Q->mod,name); if (!g) { sendMsg("simerror Could not find gate %s.",name); return; } if (g->g_type->gi_code != GT_RAM && g->g_type->gi_code != GT_ROM) { sendMsg("simerror %s is not a RAM or ROM.",name); return; } M = (g && g->g_type->gi_getMem) ? (g->g_type->gi_getMem)(g) : 0; EvQueue_qGateEv(Q,g,0,0,0); } else { if (M) Memory_putLine(M,buf); } } fclose(f); } /* Return the n longest paths */ void EvQueue_criticalPath(EvQueue *Q,int n) { findCriticalPaths(Q,n); } void gateCommand(EvQueue *Q,char *gat,char *cmd) { SGate *g = SModule_findGate(Q->mod,gat); if (!g) sendMsg("simerror Could not find gate %s.",gat); else if (g->g_type->gi_command) (*g->g_type->gi_command)(Q,g,cmd); } void EvQueue_execute(EvQueue *Q,char *command) { char buf[STRMAX],buf2[STRMAX],*p,c; int d,n; p = strrchr(command,'\n'); if (p) *p = 0; if (do_commandEcho) printf(">>> %s\n",command); if (testLogic(command)) return; if (sscanf(command," show %s",buf) == 1) { EvQueue_execShow(Q,buf); } else if (sscanf(command," command %s %[^\n]",buf,buf2) == 2) { gateCommand(Q,buf,buf2); } else if (sscanf(command," cpath %d",&d) == 1) { EvQueue_criticalPath(Q,d); } else if (sscanf(command," set %s %s",buf,buf2) == 2) { EvQueue_execSet(Q,buf,buf2); } else if (sscanf(command," break %d %[^\n]",&d,buf) == 2) { EvQueue_addBreak(Q,d,buf); } else if (sscanf(command," delete_break %d",&d) == 1) { EvQueue_delBreak(Q,d); } else if (sscanf(command," ledmon %s",buf) == 1) { EvQueue_ledMonitor(Q,buf,1); } else if (sscanf(command," ledhide %s",buf) == 1) { EvQueue_ledMonitor(Q,buf,0); } else if (sscanf(command," watch %s %d",buf,&d) == 2) { EvQueue_execWatch(Q,buf,d); } else if (sscanf(command," step %d",&d) == 1) { EvQueue_execStep(Q,d); } else if (sscanf(command," memload %s %s",buf,buf2) == 2) { EvQueue_execMemLoad(Q,buf,buf2); } else if (sscanf(command," memload %s",buf) == 1) { EvQueue_execMemLoad(Q,buf,0); } else if (sscanf(command," wai%c",buf) == 1 && *buf == 't') { EvQueue_execWait(Q); } else if (sscanf(command," tim%c",buf) == 1 && *buf == 'e') { sendMsg("stop @ %d (time)",Q->curStep); } else if (sscanf(command," clock %c %d %d %s",&c,&n,&d,buf2) == 4) { if ((Q->flags & EVF_HASCLOCK)) { Q->triggerClock = SModule_findGate(Q->mod,buf2); if (!Q->triggerClock) { sendMsg("simerror Could not find clock %s.",buf2); return; } else if (strcmp(Q->triggerClock->g_type->gi_name,"clock") != 0) { Q->triggerClock = 0; sendMsg("simerror Gate %s is not a clock.",buf2); return; } Q->clockHold = d; Q->clockCount = n; Q->flags |= EVF_RUN|EVF_NOCMD|(c == '+' ? EVF_POSCLOCK : EVF_NEGCLOCK); } } else if (sscanf(command," clock %c %d %d",&c,&n,&d) == 3) { if ((Q->flags & EVF_HASCLOCK)) { Q->clockHold = d; Q->clockCount = n; Q->triggerClock = 0; Q->flags |= EVF_RUN|EVF_NOCMD|(c == '+' ? EVF_POSCLOCK : EVF_NEGCLOCK); } } else if (sscanf(command," go %d",&d) == 1) { if (d) Q->flags |= EVF_RUN|EVF_PERSRUN; else Q->flags &= ~(EVF_RUN|EVF_PERSRUN); } else { error("illegal command."); } } static char cmdin_buf[STRMAX]; /* Buffer for unread characters */ static char *cmdin_q = cmdin_buf; /* End of unread characters */ /* * Reads data from standard input and stores it in the buffer. * return 0 on eof */ int get_data() { int c,en; if ((cmdin_q-cmdin_buf) >= STRMAX) return 0; do { errno = 0; c = read(0,cmdin_q,STRMAX-(cmdin_q-cmdin_buf)); en = errno; } while (c == 0 && en == EINTR); if (c > 0) cmdin_q += c; return c != 0; } int input_ready(int doWait) { fd_set rd,wr,ex; struct timeval tv; int r; if (cmdin_q != cmdin_buf) return 1; FD_ZERO(&rd); FD_ZERO(&wr); FD_ZERO(&ex); FD_SET(0,&rd); tv.tv_sec = 0; tv.tv_usec = 0; for (;;) { if (doWait) r = select(1,&rd,&wr,&ex,0); else r = select(1,&rd,&wr,&ex,&tv); if (r >= 0 || errno != EINTR) break; } return (r == 1); } int get_line(char *s,int n) { char *p; int got_nl = 0; for (;;) { for (p = cmdin_buf;n > 0 && p != cmdin_q;p++) { *s++ = *p; n--; if (n <= 0 || *p == '\n') { got_nl = 1; p++; break; } } if (p != cmdin_q) memmove(cmdin_buf,p,cmdin_q-p); cmdin_q = cmdin_buf+(cmdin_q-p); if (got_nl) { *s = 0; if (s[-1] == '\n') s[-1] = 0; return 1; } if (!get_data()) return 0; } return 0; } int do_input_check = 0; void timer_event(int p) { do_input_check = 1; #if TKGATE_RESIGNAL signal(SIGALRM,timer_event); /* Reset timer event handler */ #endif } void input_setup() { struct itimerval itv; int ms = POLL_RATE*1000; int s = POLL_RATE/1000; #if 0 fcntl(0,F_SETFL,O_NONBLOCK|fcntl(0,F_GETFL)); /* Set non-blocking I/O on stdin (does not seem necessary) */ #endif itv.it_interval.tv_sec = itv.it_value.tv_sec = s; /* Set up timer interval */ itv.it_interval.tv_usec = itv.it_value.tv_usec = ms; signal(SIGALRM,timer_event); /* Set timer event handler */ setitimer(ITIMER_REAL,&itv,0); /* Enable timer */ } /* Main event loop */ void EvQueue_mainEventLoop(EvQueue *Q) { char buf[STRMAX]; input_setup(); for (;;) { if (EvQueue_pending(Q) == 0) { Q->flags &= ~EVF_RUN; } if (!(Q->flags & EVF_RUN)) { sendMsg("stop @ %d (empty)",Q->curStep); input_ready(1); if (!get_line(buf,STRMAX)) return; EvQueue_execute(Q,buf); } else { if (do_input_check && !(Q->flags & EVF_NOCMD)) { if (input_ready(0)) { if (!get_line(buf,STRMAX)) return; EvQueue_execute(Q,buf); } else do_input_check = 0; } if (EvQueue_pending(Q)) EvQueue_step(Q); } } }