diff options
Diffstat (limited to 'ldmicro/intcode.cpp')
-rw-r--r-- | ldmicro/intcode.cpp | 1179 |
1 files changed, 1179 insertions, 0 deletions
diff --git a/ldmicro/intcode.cpp b/ldmicro/intcode.cpp new file mode 100644 index 0000000..a577759 --- /dev/null +++ b/ldmicro/intcode.cpp @@ -0,0 +1,1179 @@ +//----------------------------------------------------------------------------- +// Copyright 2007 Jonathan Westhues +// +// This file is part of LDmicro. +// +// LDmicro 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 3 of the License, or +// (at your option) any later version. +// +// LDmicro 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 LDmicro. If not, see <http://www.gnu.org/licenses/>. +//------ +// +// Generate intermediate code for the ladder logic. Basically generate code +// for a `virtual machine' with operations chosen to be easy to compile to +// AVR or PIC16 code. +// Jonathan Westhues, Nov 2004 +//----------------------------------------------------------------------------- +#include "linuxUI.h" +#include <stdio.h> +#include <setjmp.h> +#include <stdlib.h> + +#include "ldmicro.h" +#include "intcode.h" + +IntOp IntCode[MAX_INT_OPS]; +int IntCodeLen; + +static DWORD GenSymCountParThis; +static DWORD GenSymCountParOut; +static DWORD GenSymCountOneShot; +static DWORD GenSymCountFormattedString; + +static WORD EepromAddrFree; + +//----------------------------------------------------------------------------- +// Pretty-print the intermediate code to a file, for debugging purposes. +//----------------------------------------------------------------------------- +void IntDumpListing(char *outFile) +{ + FILE *f = fopen(outFile, "w"); + if(!f) { + Error("Couldn't dump intermediate code to '%s'.", outFile); + } + + int i; + int indent = 0; + for(i = 0; i < IntCodeLen; i++) { + + if(IntCode[i].op == INT_END_IF) indent--; + if(IntCode[i].op == INT_ELSE) indent--; + + fprintf(f, "%3d:", i); + int j; + for(j = 0; j < indent; j++) fprintf(f, " "); + + switch(IntCode[i].op) { + case INT_SET_BIT: + fprintf(f, "set bit '%s'", IntCode[i].name1); + break; + + case INT_CLEAR_BIT: + fprintf(f, "clear bit '%s'", IntCode[i].name1); + break; + + case INT_COPY_BIT_TO_BIT: + fprintf(f, "let bit '%s' := '%s'", IntCode[i].name1, + IntCode[i].name2); + break; + + case INT_SET_VARIABLE_TO_LITERAL: + fprintf(f, "let var '%s' := %d", IntCode[i].name1, + IntCode[i].literal); + break; + + case INT_SET_VARIABLE_TO_VARIABLE: + fprintf(f, "let var '%s' := '%s'", IntCode[i].name1, + IntCode[i].name2); + break; + + case INT_SET_VARIABLE_ADD: + fprintf(f, "let var '%s' := '%s' + '%s'", IntCode[i].name1, + IntCode[i].name2, IntCode[i].name3); + break; + + case INT_SET_VARIABLE_SUBTRACT: + fprintf(f, "let var '%s' := '%s' - '%s'", IntCode[i].name1, + IntCode[i].name2, IntCode[i].name3); + break; + + case INT_SET_VARIABLE_MULTIPLY: + fprintf(f, "let var '%s' := '%s' * '%s'", IntCode[i].name1, + IntCode[i].name2, IntCode[i].name3); + break; + + case INT_SET_VARIABLE_DIVIDE: + fprintf(f, "let var '%s' := '%s' / '%s'", IntCode[i].name1, + IntCode[i].name2, IntCode[i].name3); + break; + + case INT_INCREMENT_VARIABLE: + fprintf(f, "increment '%s'", IntCode[i].name1); + break; + + case INT_READ_ADC: + fprintf(f, "read adc '%s'", IntCode[i].name1); + break; + + case INT_SET_PWM: + fprintf(f, "set pwm '%s' %s Hz", IntCode[i].name1, + IntCode[i].name2); + break; + + case INT_EEPROM_BUSY_CHECK: + fprintf(f, "set bit '%s' if EEPROM busy", IntCode[i].name1); + break; + + case INT_EEPROM_READ: + fprintf(f, "read EEPROM[%d,%d+1] into '%s'", + IntCode[i].literal, IntCode[i].literal, IntCode[i].name1); + break; + + case INT_EEPROM_WRITE: + fprintf(f, "write '%s' into EEPROM[%d,%d+1]", + IntCode[i].name1, IntCode[i].literal, IntCode[i].literal); + break; + + case INT_UART_SEND: + fprintf(f, "uart send from '%s', done? into '%s'", + IntCode[i].name1, IntCode[i].name2); + break; + + case INT_UART_RECV: + fprintf(f, "uart recv int '%s', have? into '%s'", + IntCode[i].name1, IntCode[i].name2); + break; + + case INT_IF_BIT_SET: + fprintf(f, "if '%s' {", IntCode[i].name1); indent++; + break; + + case INT_IF_BIT_CLEAR: + fprintf(f, "if not '%s' {", IntCode[i].name1); indent++; + break; + + case INT_IF_VARIABLE_LES_LITERAL: + fprintf(f, "if '%s' < %d {", IntCode[i].name1, + IntCode[i].literal); indent++; + break; + + case INT_IF_VARIABLE_EQUALS_VARIABLE: + fprintf(f, "if '%s' == '%s' {", IntCode[i].name1, + IntCode[i].name2); indent++; + break; + + case INT_IF_VARIABLE_GRT_VARIABLE: + fprintf(f, "if '%s' > '%s' {", IntCode[i].name1, + IntCode[i].name2); indent++; + break; + + case INT_END_IF: + fprintf(f, "}"); + break; + + case INT_ELSE: + fprintf(f, "} else {"); indent++; + break; + + case INT_SIMULATE_NODE_STATE: + // simulation-only; the real code generators don't care + break; + + case INT_COMMENT: + fprintf(f, "# %s", IntCode[i].name1); + break; + + default: + oops(); + } + fprintf(f, "\n"); + fflush(f); + } + fclose(f); +} + +//----------------------------------------------------------------------------- +// Convert a hex digit (0-9a-fA-F) to its hex value, or return -1 if the +// character is not a hex digit. +//----------------------------------------------------------------------------- +int HexDigit(int c) +{ + if((c >= '0') && (c <= '9')) { + return c - '0'; + } else if((c >= 'a') && (c <= 'f')) { + return 10 + (c - 'a'); + } else if((c >= 'A') && (c <= 'F')) { + return 10 + (c - 'A'); + } + return -1; +} + +//----------------------------------------------------------------------------- +// Generate a unique symbol (unique with each call) having the given prefix +// guaranteed not to conflict with any user symbols. +//----------------------------------------------------------------------------- +static void GenSymParThis(char *dest) +{ + sprintf(dest, "$parThis_%04x", GenSymCountParThis); + GenSymCountParThis++; +} +static void GenSymParOut(char *dest) +{ + sprintf(dest, "$parOut_%04x", GenSymCountParOut); + GenSymCountParOut++; +} +static void GenSymOneShot(char *dest) +{ + sprintf(dest, "$oneShot_%04x", GenSymCountOneShot); + GenSymCountOneShot++; +} +static void GenSymFormattedString(char *dest) +{ + sprintf(dest, "$formattedString_%04x", GenSymCountFormattedString); + GenSymCountFormattedString++; +} + +//----------------------------------------------------------------------------- +// Compile an instruction to the program. +//----------------------------------------------------------------------------- +static void Op(int op, char *name1, char *name2, char *name3, SWORD lit) +{ + IntCode[IntCodeLen].op = op; + if(name1) strcpy(IntCode[IntCodeLen].name1, name1); + if(name2) strcpy(IntCode[IntCodeLen].name2, name2); + if(name3) strcpy(IntCode[IntCodeLen].name3, name3); + IntCode[IntCodeLen].literal = lit; + IntCodeLen++; +} +static void Op(int op, char *name1, char *name2, SWORD lit) +{ + Op(op, name1, name2, NULL, lit); +} +static void Op(int op, char *name1, SWORD lit) +{ + Op(op, name1, NULL, NULL, lit); +} +static void Op(int op, char *name1, char *name2) +{ + Op(op, name1, name2, NULL, 0); +} +static void Op(int op, char *name1) +{ + Op(op, name1, NULL, NULL, 0); +} +static void Op(int op) +{ + Op(op, NULL, NULL, NULL, 0); +} + +//----------------------------------------------------------------------------- +// Compile the instruction that the simulator uses to keep track of which +// nodes are energized (so that it can display which branches of the circuit +// are energized onscreen). The MCU code generators ignore this, of course. +//----------------------------------------------------------------------------- +static void SimState(BOOL *b, char *name) +{ + IntCode[IntCodeLen].op = INT_SIMULATE_NODE_STATE; + strcpy(IntCode[IntCodeLen].name1, name); + IntCode[IntCodeLen].poweredAfter = b; + IntCodeLen++; +} + +//----------------------------------------------------------------------------- +// printf-like comment function +//----------------------------------------------------------------------------- +void Comment(char *str, ...) +{ + va_list f; + char buf[MAX_NAME_LEN]; + va_start(f, str); + vsprintf(buf, str, f); + Op(INT_COMMENT, buf); +} + +//----------------------------------------------------------------------------- +// Calculate the period in scan units from the period in microseconds, and +// raise an error if the given period is unachievable. +//----------------------------------------------------------------------------- +static int TimerPeriod(ElemLeaf *l) +{ + int period = (l->d.timer.delay / Prog.cycleTime) - 1; + + if(period < 1) { + Error(_("Timer period too short (needs faster cycle time).")); + CompileError(); + } + if(period >= (1 << 15)) { + Error(_("Timer period too long (max 32767 times cycle time); use a " + "slower cycle time.")); + CompileError(); + } + + return period; +} + +//----------------------------------------------------------------------------- +// Is an expression that could be either a variable name or a number a number? +//----------------------------------------------------------------------------- +static BOOL IsNumber(char *str) +{ + if(*str == '-' || isdigit(*str)) { + return TRUE; + } else if(*str == '\'') { + // special case--literal single character + return TRUE; + } else { + return FALSE; + } +} + +//----------------------------------------------------------------------------- +// Report an error if a constant doesn't fit in 16 bits. +//----------------------------------------------------------------------------- +void CheckConstantInRange(int v) +{ + if(v < -32768 || v > 32767) { + Error(_("Constant %d out of range: -32768 to 32767 inclusive."), v); + CompileError(); + } +} + +//----------------------------------------------------------------------------- +// Try to turn a string into a 16-bit constant, and raise an error if +// something bad happens when we do so (e.g. out of range). +//----------------------------------------------------------------------------- +SWORD CheckMakeNumber(char *str) +{ + int val; + + if(*str == '\'') { + val = str[1]; + } else { + val = atoi(str); + } + + CheckConstantInRange(val); + + return (SWORD)val; +} + +//----------------------------------------------------------------------------- +// Return an integer power of ten. +//----------------------------------------------------------------------------- +static int TenToThe(int x) +{ + int i; + int r = 1; + for(i = 0; i < x; i++) { + r *= 10; + } + return r; +} + +//----------------------------------------------------------------------------- +// Compile code to evaluate the given bit of ladder logic. The rung input +// state is in stateInOut before calling and will be in stateInOut after +// calling. +//----------------------------------------------------------------------------- +static char *VarFromExpr(char *expr, char *tempName) +{ + if(IsNumber(expr)) { + Op(INT_SET_VARIABLE_TO_LITERAL, tempName, CheckMakeNumber(expr)); + return tempName; + } else { + return expr; + } +} +static void IntCodeFromCircuit(int which, void *any, char *stateInOut) +{ + ElemLeaf *l = (ElemLeaf *)any; + + switch(which) { + case ELEM_SERIES_SUBCKT: { + int i; + ElemSubcktSeries *s = (ElemSubcktSeries *)any; + + Comment("start series ["); + for(i = 0; i < s->count; i++) { + IntCodeFromCircuit(s->contents[i].which, s->contents[i].d.any, + stateInOut); + } + Comment("] finish series"); + break; + } + case ELEM_PARALLEL_SUBCKT: { + char parThis[MAX_NAME_LEN]; + GenSymParThis(parThis); + + char parOut[MAX_NAME_LEN]; + GenSymParOut(parOut); + + Comment("start parallel ["); + + Op(INT_CLEAR_BIT, parOut); + + ElemSubcktParallel *p = (ElemSubcktParallel *)any; + int i; + for(i = 0; i < p->count; i++) { + Op(INT_COPY_BIT_TO_BIT, parThis, stateInOut); + + IntCodeFromCircuit(p->contents[i].which, p->contents[i].d.any, + parThis); + + Op(INT_IF_BIT_SET, parThis); + Op(INT_SET_BIT, parOut); + Op(INT_END_IF); + } + Op(INT_COPY_BIT_TO_BIT, stateInOut, parOut); + Comment("] finish parallel"); + + break; + } + case ELEM_CONTACTS: { + if(l->d.contacts.negated) { + Op(INT_IF_BIT_SET, l->d.contacts.name); + } else { + Op(INT_IF_BIT_CLEAR, l->d.contacts.name); + } + Op(INT_CLEAR_BIT, stateInOut); + Op(INT_END_IF); + break; + } + case ELEM_COIL: { + if(l->d.coil.negated) { + Op(INT_IF_BIT_SET, stateInOut); + Op(INT_CLEAR_BIT, l->d.contacts.name); + Op(INT_ELSE); + Op(INT_SET_BIT, l->d.contacts.name); + Op(INT_END_IF); + } else if(l->d.coil.setOnly) { + Op(INT_IF_BIT_SET, stateInOut); + Op(INT_SET_BIT, l->d.contacts.name); + Op(INT_END_IF); + } else if(l->d.coil.resetOnly) { + Op(INT_IF_BIT_SET, stateInOut); + Op(INT_CLEAR_BIT, l->d.contacts.name); + Op(INT_END_IF); + } else { + Op(INT_COPY_BIT_TO_BIT, l->d.contacts.name, stateInOut); + } + break; + } + case ELEM_RTO: { + int period = TimerPeriod(l); + + Op(INT_IF_VARIABLE_LES_LITERAL, l->d.timer.name, period); + + Op(INT_IF_BIT_SET, stateInOut); + Op(INT_INCREMENT_VARIABLE, l->d.timer.name); + Op(INT_END_IF); + Op(INT_CLEAR_BIT, stateInOut); + + Op(INT_ELSE); + + Op(INT_SET_BIT, stateInOut); + + Op(INT_END_IF); + + break; + } + case ELEM_RES: + Op(INT_IF_BIT_SET, stateInOut); + Op(INT_SET_VARIABLE_TO_LITERAL, l->d.reset.name); + Op(INT_END_IF); + break; + + case ELEM_TON: { + int period = TimerPeriod(l); + + Op(INT_IF_BIT_SET, stateInOut); + + Op(INT_IF_VARIABLE_LES_LITERAL, l->d.timer.name, period); + + Op(INT_INCREMENT_VARIABLE, l->d.timer.name); + Op(INT_CLEAR_BIT, stateInOut); + Op(INT_END_IF); + + Op(INT_ELSE); + + Op(INT_SET_VARIABLE_TO_LITERAL, l->d.timer.name); + + Op(INT_END_IF); + + break; + } + case ELEM_TOF: { + int period = TimerPeriod(l); + + // All variables start at zero by default, so by default the + // TOF timer would start out with its output forced HIGH, until + // it finishes counting up. This does not seem to be what + // people expect, so add a special case to fix that up. + char antiGlitchName[MAX_NAME_LEN]; + sprintf(antiGlitchName, "$%s_antiglitch", l->d.timer.name); + Op(INT_IF_BIT_CLEAR, antiGlitchName); + Op(INT_SET_VARIABLE_TO_LITERAL, l->d.timer.name, period); + Op(INT_END_IF); + Op(INT_SET_BIT, antiGlitchName); + + Op(INT_IF_BIT_CLEAR, stateInOut); + + Op(INT_IF_VARIABLE_LES_LITERAL, l->d.timer.name, period); + + Op(INT_INCREMENT_VARIABLE, l->d.timer.name); + Op(INT_SET_BIT, stateInOut); + Op(INT_END_IF); + + Op(INT_ELSE); + + Op(INT_SET_VARIABLE_TO_LITERAL, l->d.timer.name); + + Op(INT_END_IF); + break; + } + case ELEM_CTU: { + CheckConstantInRange(l->d.counter.max); + char storeName[MAX_NAME_LEN]; + GenSymOneShot(storeName); + + Op(INT_IF_BIT_SET, stateInOut); + Op(INT_IF_BIT_CLEAR, storeName); + Op(INT_INCREMENT_VARIABLE, l->d.counter.name); + Op(INT_END_IF); + Op(INT_END_IF); + Op(INT_COPY_BIT_TO_BIT, storeName, stateInOut); + + Op(INT_IF_VARIABLE_LES_LITERAL, l->d.counter.name, + l->d.counter.max); + Op(INT_CLEAR_BIT, stateInOut); + Op(INT_ELSE); + Op(INT_SET_BIT, stateInOut); + Op(INT_END_IF); + break; + } + case ELEM_CTD: { + CheckConstantInRange(l->d.counter.max); + char storeName[MAX_NAME_LEN]; + GenSymOneShot(storeName); + + Op(INT_IF_BIT_SET, stateInOut); + Op(INT_IF_BIT_CLEAR, storeName); + Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch", 1); + Op(INT_SET_VARIABLE_SUBTRACT, l->d.counter.name, + l->d.counter.name, "$scratch", 0); + Op(INT_END_IF); + Op(INT_END_IF); + Op(INT_COPY_BIT_TO_BIT, storeName, stateInOut); + + Op(INT_IF_VARIABLE_LES_LITERAL, l->d.counter.name, + l->d.counter.max); + Op(INT_CLEAR_BIT, stateInOut); + Op(INT_ELSE); + Op(INT_SET_BIT, stateInOut); + Op(INT_END_IF); + break; + } + case ELEM_CTC: { + char storeName[MAX_NAME_LEN]; + GenSymOneShot(storeName); + + Op(INT_IF_BIT_SET, stateInOut); + Op(INT_IF_BIT_CLEAR, storeName); + Op(INT_INCREMENT_VARIABLE, l->d.counter.name); + Op(INT_IF_VARIABLE_LES_LITERAL, l->d.counter.name, + l->d.counter.max+1); + Op(INT_ELSE); + Op(INT_SET_VARIABLE_TO_LITERAL, l->d.counter.name, + (SWORD)0); + Op(INT_END_IF); + Op(INT_END_IF); + Op(INT_END_IF); + Op(INT_COPY_BIT_TO_BIT, storeName, stateInOut); + break; + } + case ELEM_GRT: + case ELEM_GEQ: + case ELEM_LES: + case ELEM_LEQ: + case ELEM_NEQ: + case ELEM_EQU: { + char *op1 = VarFromExpr(l->d.cmp.op1, "$scratch"); + char *op2 = VarFromExpr(l->d.cmp.op2, "$scratch2"); + + if(which == ELEM_GRT) { + Op(INT_IF_VARIABLE_GRT_VARIABLE, op1, op2); + Op(INT_ELSE); + } else if(which == ELEM_GEQ) { + Op(INT_IF_VARIABLE_GRT_VARIABLE, op2, op1); + } else if(which == ELEM_LES) { + Op(INT_IF_VARIABLE_GRT_VARIABLE, op2, op1); + Op(INT_ELSE); + } else if(which == ELEM_LEQ) { + Op(INT_IF_VARIABLE_GRT_VARIABLE, op1, op2); + } else if(which == ELEM_EQU) { + Op(INT_IF_VARIABLE_EQUALS_VARIABLE, op1, op2); + Op(INT_ELSE); + } else if(which == ELEM_NEQ) { + Op(INT_IF_VARIABLE_EQUALS_VARIABLE, op1, op2); + } else oops(); + + Op(INT_CLEAR_BIT, stateInOut); + Op(INT_END_IF); + break; + } + case ELEM_ONE_SHOT_RISING: { + char storeName[MAX_NAME_LEN]; + GenSymOneShot(storeName); + + Op(INT_COPY_BIT_TO_BIT, "$scratch", stateInOut); + Op(INT_IF_BIT_SET, storeName); + Op(INT_CLEAR_BIT, stateInOut); + Op(INT_END_IF); + Op(INT_COPY_BIT_TO_BIT, storeName, "$scratch"); + break; + } + case ELEM_ONE_SHOT_FALLING: { + char storeName[MAX_NAME_LEN]; + GenSymOneShot(storeName); + + Op(INT_COPY_BIT_TO_BIT, "$scratch", stateInOut); + + Op(INT_IF_BIT_CLEAR, stateInOut); + Op(INT_IF_BIT_SET, storeName); + Op(INT_SET_BIT, stateInOut); + Op(INT_END_IF); + Op(INT_ELSE); + Op(INT_CLEAR_BIT, stateInOut); + Op(INT_END_IF); + + Op(INT_COPY_BIT_TO_BIT, storeName, "$scratch"); + break; + } + case ELEM_MOVE: { + if(IsNumber(l->d.move.dest)) { + Error(_("Move instruction: '%s' not a valid destination."), + l->d.move.dest); + CompileError(); + } + Op(INT_IF_BIT_SET, stateInOut); + if(IsNumber(l->d.move.src)) { + Op(INT_SET_VARIABLE_TO_LITERAL, l->d.move.dest, + CheckMakeNumber(l->d.move.src)); + } else { + Op(INT_SET_VARIABLE_TO_VARIABLE, l->d.move.dest, l->d.move.src, + 0); + } + Op(INT_END_IF); + break; + } + + // These four are highly processor-dependent; the int code op does + // most of the highly specific work + { + case ELEM_READ_ADC: + Op(INT_IF_BIT_SET, stateInOut); + Op(INT_READ_ADC, l->d.readAdc.name); + Op(INT_END_IF); + break; + + case ELEM_SET_PWM: { + Op(INT_IF_BIT_SET, stateInOut); + char line[80]; + // ugh; need a >16 bit literal though, could be >64 kHz + sprintf(line, "%d", l->d.setPwm.targetFreq); + Op(INT_SET_PWM, l->d.readAdc.name, line); + Op(INT_END_IF); + break; + } + case ELEM_PERSIST: { + Op(INT_IF_BIT_SET, stateInOut); + + // At startup, get the persistent variable from flash. + char isInit[MAX_NAME_LEN]; + GenSymOneShot(isInit); + Op(INT_IF_BIT_CLEAR, isInit); + Op(INT_CLEAR_BIT, "$scratch"); + Op(INT_EEPROM_BUSY_CHECK, "$scratch"); + Op(INT_IF_BIT_CLEAR, "$scratch"); + Op(INT_SET_BIT, isInit); + Op(INT_EEPROM_READ, l->d.persist.var, EepromAddrFree); + Op(INT_END_IF); + Op(INT_END_IF); + + // While running, continuously compare the EEPROM copy of + // the variable against the RAM one; if they are different, + // write the RAM one to EEPROM. + Op(INT_CLEAR_BIT, "$scratch"); + Op(INT_EEPROM_BUSY_CHECK, "$scratch"); + Op(INT_IF_BIT_CLEAR, "$scratch"); + Op(INT_EEPROM_READ, "$scratch", EepromAddrFree); + Op(INT_IF_VARIABLE_EQUALS_VARIABLE, "$scratch", + l->d.persist.var); + Op(INT_ELSE); + Op(INT_EEPROM_WRITE, l->d.persist.var, EepromAddrFree); + Op(INT_END_IF); + Op(INT_END_IF); + + Op(INT_END_IF); + + EepromAddrFree += 2; + break; + } + case ELEM_UART_SEND: + Op(INT_UART_SEND, l->d.uart.name, stateInOut); + break; + + case ELEM_UART_RECV: + Op(INT_IF_BIT_SET, stateInOut); + Op(INT_UART_RECV, l->d.uart.name, stateInOut); + Op(INT_END_IF); + break; + } + + case ELEM_ADD: + case ELEM_SUB: + case ELEM_MUL: + case ELEM_DIV: { + if(IsNumber(l->d.math.dest)) { + Error(_("Math instruction: '%s' not a valid destination."), + l->d.math.dest); + CompileError(); + } + Op(INT_IF_BIT_SET, stateInOut); + + char *op1 = VarFromExpr(l->d.math.op1, "$scratch"); + char *op2 = VarFromExpr(l->d.math.op2, "$scratch2"); + + int intOp; + if(which == ELEM_ADD) { + intOp = INT_SET_VARIABLE_ADD; + } else if(which == ELEM_SUB) { + intOp = INT_SET_VARIABLE_SUBTRACT; + } else if(which == ELEM_MUL) { + intOp = INT_SET_VARIABLE_MULTIPLY; + } else if(which == ELEM_DIV) { + intOp = INT_SET_VARIABLE_DIVIDE; + } else oops(); + + Op(intOp, l->d.math.dest, op1, op2, 0); + + Op(INT_END_IF); + break; + } + case ELEM_MASTER_RELAY: + // Tricky: must set the master control relay if we reach this + // instruction while the master control relay is cleared, because + // otherwise there is no good way for it to ever become set + // again. + Op(INT_IF_BIT_CLEAR, "$mcr"); + Op(INT_SET_BIT, "$mcr"); + Op(INT_ELSE); + Op(INT_COPY_BIT_TO_BIT, "$mcr", stateInOut); + Op(INT_END_IF); + break; + + case ELEM_SHIFT_REGISTER: { + char storeName[MAX_NAME_LEN]; + GenSymOneShot(storeName); + Op(INT_IF_BIT_SET, stateInOut); + Op(INT_IF_BIT_CLEAR, storeName); + int i; + for(i = (l->d.shiftRegister.stages-2); i >= 0; i--) { + char dest[MAX_NAME_LEN+10], src[MAX_NAME_LEN+10]; + sprintf(src, "%s%d", l->d.shiftRegister.name, i); + sprintf(dest, "%s%d", l->d.shiftRegister.name, i+1); + Op(INT_SET_VARIABLE_TO_VARIABLE, dest, src); + } + Op(INT_END_IF); + Op(INT_END_IF); + Op(INT_COPY_BIT_TO_BIT, storeName, stateInOut); + break; + } + case ELEM_LOOK_UP_TABLE: { + // God this is stupid; but it will have to do, at least until I + // add new int code instructions for this. + int i; + Op(INT_IF_BIT_SET, stateInOut); + ElemLookUpTable *t = &(l->d.lookUpTable); + for(i = 0; i < t->count; i++) { + Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch", i); + Op(INT_IF_VARIABLE_EQUALS_VARIABLE, t->index, "$scratch"); + Op(INT_SET_VARIABLE_TO_LITERAL, t->dest, t->vals[i]); + Op(INT_END_IF); + } + Op(INT_END_IF); + break; + } + case ELEM_PIECEWISE_LINEAR: { + // This one is not so obvious; we have to decide how best to + // perform the linear interpolation, using our 16-bit fixed + // point math. + ElemPiecewiseLinear *t = &(l->d.piecewiseLinear); + if(t->count == 0) { + Error(_("Piecewise linear lookup table with zero elements!")); + CompileError(); + } + int i; + int xThis = t->vals[0]; + for(i = 1; i < t->count; i++) { + if(t->vals[i*2] <= xThis) { + Error(_("x values in piecewise linear table must be " + "strictly increasing.")); + CompileError(); + } + xThis = t->vals[i*2]; + } + Op(INT_IF_BIT_SET, stateInOut); + for(i = t->count - 1; i >= 1; i--) { + int thisDx = t->vals[i*2] - t->vals[(i-1)*2]; + int thisDy = t->vals[i*2 + 1] - t->vals[(i-1)*2 + 1]; + // The output point is given by + // yout = y[i-1] + (xin - x[i-1])*dy/dx + // and this is the best form in which to keep it, numerically + // speaking, because you can always fix numerical problems + // by moving the PWL points closer together. + + // Check for numerical problems, and fail if we have them. + if((thisDx*thisDy) >= 32767 || (thisDx*thisDy) <= -32768) { + Error(_("Numerical problem with piecewise linear lookup " + "table. Either make the table entries smaller, " + "or space the points together more closely.\r\n\r\n" + "See the help file for details.")); + CompileError(); + } + + // Hack to avoid AVR brge issue again, since long jumps break + Op(INT_CLEAR_BIT, "$scratch"); + Op(INT_IF_VARIABLE_LES_LITERAL, t->index, t->vals[i*2]+1); + Op(INT_SET_BIT, "$scratch"); + Op(INT_END_IF); + + Op(INT_IF_BIT_SET, "$scratch"); + Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch", t->vals[(i-1)*2]); + Op(INT_SET_VARIABLE_SUBTRACT, "$scratch", t->index, + "$scratch", 0); + Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch2", thisDx); + Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch3", thisDy); + Op(INT_SET_VARIABLE_MULTIPLY, t->dest, "$scratch", "$scratch3", + 0); + Op(INT_SET_VARIABLE_DIVIDE, t->dest, t->dest, "$scratch2", 0); + + Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch", + t->vals[(i-1)*2 + 1]); + Op(INT_SET_VARIABLE_ADD, t->dest, t->dest, "$scratch", 0); + Op(INT_END_IF); + } + Op(INT_END_IF); + break; + } + case ELEM_FORMATTED_STRING: { + // Okay, this one is terrible and ineffcient, and it's a huge pain + // to implement, but people want it a lot. The hard part is that + // we have to let the PLC keep cycling, of course, and also that + // we must do the integer to ASCII conversion sensisbly, with + // only one divide per PLC cycle. + + // This variable is basically our sequencer: it is a counter that + // increments every time we send a character. + char seq[MAX_NAME_LEN]; + GenSymFormattedString(seq); + + // The variable whose value we might interpolate. + char *var = l->d.fmtdStr.var; + + // This is the state variable for our integer-to-string conversion. + // It contains the absolute value of var, possibly with some + // of the higher powers of ten missing. + char convertState[MAX_NAME_LEN]; + GenSymFormattedString(convertState); + + // We might need to suppress some leading zeros. + char isLeadingZero[MAX_NAME_LEN]; + GenSymFormattedString(isLeadingZero); + + // This is a table of characters to transmit, as a function of the + // sequencer position (though we might have a hole in the middle + // for the variable output) + char outputChars[MAX_LOOK_UP_TABLE_LEN]; + + BOOL mustDoMinus = FALSE; + + // The total number of characters that we transmit, including + // those from the interpolated variable. + int steps; + + // The total number of digits to convert. + int digits = -1; + + // So count that now, and build up our table of fixed things to + // send. + steps = 0; + char *p = l->d.fmtdStr.string; + while(*p) { + if(*p == '\\' && (isdigit(p[1]) || p[1] == '-')) { + if(digits >= 0) { + Error(_("Multiple escapes (\\0-9) present in format " + "string, not allowed.")); + CompileError(); + } + p++; + if(*p == '-') { + mustDoMinus = TRUE; + outputChars[steps++] = 1; + p++; + } + if(!isdigit(*p) || (*p - '0') > 5 || *p == '0') { + Error(_("Bad escape sequence following \\; for a " + "literal backslash, use \\\\")); + CompileError(); + } + digits = (*p - '0'); + int i; + for(i = 0; i < digits; i++) { + outputChars[steps++] = 0; + } + } else if(*p == '\\') { + p++; + switch(*p) { + case 'r': outputChars[steps++] = '\r'; break; + case 'n': outputChars[steps++] = '\n'; break; + case 'b': outputChars[steps++] = '\b'; break; + case 'f': outputChars[steps++] = '\f'; break; + case '\\': outputChars[steps++] = '\\'; break; + case 'x': { + int h, l; + p++; + h = HexDigit(*p); + if(h >= 0) { + p++; + l = HexDigit(*p); + if(l >= 0) { + outputChars[steps++] = (h << 4) | l; + break; + } + } + Error(_("Bad escape: correct form is \\xAB.")); + CompileError(); + break; + } + default: + Error(_("Bad escape '\\%c'"), *p); + CompileError(); + break; + } + } else { + outputChars[steps++] = *p; + } + if(*p) p++; + } + + if(digits >= 0 && (strlen(var) == 0)) { + Error(_("Variable is interpolated into formatted string, but " + "none is specified.")); + CompileError(); + } else if(digits < 0 && (strlen(var) > 0)) { + Error(_("No variable is interpolated into formatted string, " + "but a variable name is specified. Include a string like " + "'\\-3', or leave variable name blank.")); + CompileError(); + } + + // We want to respond to rising edges, so yes we need a one shot. + char oneShot[MAX_NAME_LEN]; + GenSymOneShot(oneShot); + + Op(INT_IF_BIT_SET, stateInOut); + Op(INT_IF_BIT_CLEAR, oneShot); + Op(INT_SET_VARIABLE_TO_LITERAL, seq, (SWORD)0); + Op(INT_END_IF); + Op(INT_END_IF); + Op(INT_COPY_BIT_TO_BIT, oneShot, stateInOut); + + // Everything that involves seqScratch is a terrible hack to + // avoid an if statement with a big body, which is the risk + // factor for blowing up on PIC16 page boundaries. + + char *seqScratch = "$scratch3"; + + Op(INT_SET_VARIABLE_TO_VARIABLE, seqScratch, seq); + + // No point doing any math unless we'll get to transmit this + // cycle, so check that first. + + Op(INT_IF_VARIABLE_LES_LITERAL, seq, steps); + Op(INT_ELSE); + Op(INT_SET_VARIABLE_TO_LITERAL, seqScratch, -1); + Op(INT_END_IF); + + Op(INT_CLEAR_BIT, "$scratch"); + Op(INT_UART_SEND, "$scratch", "$scratch"); + Op(INT_IF_BIT_SET, "$scratch"); + Op(INT_SET_VARIABLE_TO_LITERAL, seqScratch, -1); + Op(INT_END_IF); + + // So we transmit this cycle, so check out which character. + int i; + int digit = 0; + for(i = 0; i < steps; i++) { + if(outputChars[i] == 0) { + // Note gross hack to work around limit of range for + // AVR brne op, which is +/- 64 instructions. + Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch", i); + Op(INT_CLEAR_BIT, "$scratch"); + Op(INT_IF_VARIABLE_EQUALS_VARIABLE, "$scratch", seqScratch); + Op(INT_SET_BIT, "$scratch"); + Op(INT_END_IF); + + Op(INT_IF_BIT_SET, "$scratch"); + + // Start the integer-to-string + + // If there's no minus, then we have to load up + // convertState ourselves the first time. + if(digit == 0 && !mustDoMinus) { + Op(INT_SET_VARIABLE_TO_VARIABLE, convertState, var); + } + if(digit == 0) { + Op(INT_SET_BIT, isLeadingZero); + } + + Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch", + TenToThe((digits-digit)-1)); + Op(INT_SET_VARIABLE_DIVIDE, "$scratch2", convertState, + "$scratch", 0); + Op(INT_SET_VARIABLE_MULTIPLY, "$scratch", "$scratch", + "$scratch2", 0); + Op(INT_SET_VARIABLE_SUBTRACT, convertState, + convertState, "$scratch", 0); + Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch", '0'); + Op(INT_SET_VARIABLE_ADD, "$scratch2", "$scratch2", + "$scratch", 0); + + // Suppress all but the last leading zero. + if(digit != (digits - 1)) { + Op(INT_IF_VARIABLE_EQUALS_VARIABLE, "$scratch", + "$scratch2"); + Op(INT_IF_BIT_SET, isLeadingZero); + Op(INT_SET_VARIABLE_TO_LITERAL, + "$scratch2", ' '); + Op(INT_END_IF); + Op(INT_ELSE); + Op(INT_CLEAR_BIT, isLeadingZero); + Op(INT_END_IF); + } + + Op(INT_END_IF); + + digit++; + } else if(outputChars[i] == 1) { + // do the minus; ugliness to get around the BRNE jump + // size limit, though + Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch", i); + Op(INT_CLEAR_BIT, "$scratch"); + Op(INT_IF_VARIABLE_EQUALS_VARIABLE, "$scratch", seqScratch); + Op(INT_SET_BIT, "$scratch"); + Op(INT_END_IF); + Op(INT_IF_BIT_SET, "$scratch"); + + // Also do the `absolute value' calculation while + // we're at it. + Op(INT_SET_VARIABLE_TO_VARIABLE, convertState, var); + Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch2", ' '); + Op(INT_IF_VARIABLE_LES_LITERAL, var, (SWORD)0); + Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch2", '-'); + Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch", + (SWORD)0); + Op(INT_SET_VARIABLE_SUBTRACT, convertState, + "$scratch", var, 0); + Op(INT_END_IF); + + Op(INT_END_IF); + } else { + // just another character + Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch", i); + Op(INT_IF_VARIABLE_EQUALS_VARIABLE, "$scratch", seqScratch); + Op(INT_SET_VARIABLE_TO_LITERAL, "$scratch2", + outputChars[i]); + Op(INT_END_IF); + } + } + + Op(INT_IF_VARIABLE_LES_LITERAL, seqScratch, (SWORD)0); + Op(INT_ELSE); + Op(INT_SET_BIT, "$scratch"); + Op(INT_UART_SEND, "$scratch2", "$scratch"); + Op(INT_INCREMENT_VARIABLE, seq); + Op(INT_END_IF); + + // Rung-out state: true if we're still running, else false + Op(INT_CLEAR_BIT, stateInOut); + Op(INT_IF_VARIABLE_LES_LITERAL, seq, steps); + Op(INT_SET_BIT, stateInOut); + Op(INT_END_IF); + break; + } + case ELEM_OPEN: + Op(INT_CLEAR_BIT, stateInOut); + break; + + case ELEM_SHORT: + // goes straight through + break; + + case ELEM_PLACEHOLDER: + Error( + _("Empty row; delete it or add instructions before compiling.")); + CompileError(); + break; + + default: + oops(); + break; + } + + if(which != ELEM_SERIES_SUBCKT && which != ELEM_PARALLEL_SUBCKT) { + // then it is a leaf; let the simulator know which leaf it + // should be updating for display purposes + SimState(&(l->poweredAfter), stateInOut); + } +} + +//----------------------------------------------------------------------------- +// Generate intermediate code for the entire program. Return TRUE if it worked, +// else FALSE. +//----------------------------------------------------------------------------- +BOOL GenerateIntermediateCode(void) +{ + GenSymCountParThis = 0; + GenSymCountParOut = 0; + GenSymCountOneShot = 0; + GenSymCountFormattedString = 0; + + // The EEPROM addresses for the `Make Persistent' op are assigned at + // int code generation time. + EepromAddrFree = 0; + + IntCodeLen = 0; + memset(IntCode, 0, sizeof(IntCode)); + + if(setjmp(CompileErrorBuf) != 0) { + return FALSE; + } + + Op(INT_SET_BIT, "$mcr"); + + int i; + for(i = 0; i < Prog.numRungs; i++) { + if(Prog.rungs[i]->count == 1 && + Prog.rungs[i]->contents[0].which == ELEM_COMMENT) + { + // nothing to do for this one + continue; + } + Comment(""); + Comment("start rung %d", i+1); + Op(INT_COPY_BIT_TO_BIT, "$rung_top", "$mcr"); + SimState(&(Prog.rungPowered[i]), "$rung_top"); + IntCodeFromCircuit(ELEM_SERIES_SUBCKT, Prog.rungs[i], "$rung_top"); + } + + return TRUE; +} |