summaryrefslogtreecommitdiff
path: root/ldmicro/intcode.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ldmicro/intcode.cpp')
-rw-r--r--ldmicro/intcode.cpp1179
1 files changed, 1179 insertions, 0 deletions
diff --git a/ldmicro/intcode.cpp b/ldmicro/intcode.cpp
new file mode 100644
index 0000000..2ef460e
--- /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 <windows.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;
+}