OpenPLC-Ladder-Editor/ldmicro-rel2.2/ldmicro/intcode.cpp

1180 lines
42 KiB
C++

//-----------------------------------------------------------------------------
// 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;
}