diff options
author | akshay-c | 2019-01-30 12:23:44 +0530 |
---|---|---|
committer | akshay-c | 2019-01-30 12:23:44 +0530 |
commit | 4196481f74afb84e5cc59cdf00c06c1ca1becab7 (patch) | |
tree | b531deb0466897691f08f9076b7012592f026664 /ldmicro/ldinterpret.c | |
download | LDmicroQt-4196481f74afb84e5cc59cdf00c06c1ca1becab7.tar.gz LDmicroQt-4196481f74afb84e5cc59cdf00c06c1ca1becab7.tar.bz2 LDmicroQt-4196481f74afb84e5cc59cdf00c06c1ca1becab7.zip |
First commit
Diffstat (limited to 'ldmicro/ldinterpret.c')
-rw-r--r-- | ldmicro/ldinterpret.c | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/ldmicro/ldinterpret.c b/ldmicro/ldinterpret.c new file mode 100644 index 0000000..a37ff09 --- /dev/null +++ b/ldmicro/ldinterpret.c @@ -0,0 +1,380 @@ +//----------------------------------------------------------------------------- +// A sample interpreter for the .int files generate by LDmicro. These files +// represent a ladder logic program for a simple 'virtual machine.' The +// interpreter must simulate the virtual machine and for proper timing the +// program must be run over and over, with the period specified when it was +// compiled (in Settings -> MCU Parameters). +// +// This method of running the ladder logic code would be useful if you wanted +// to embed a ladder logic interpreter inside another program. LDmicro has +// converted all variables into addresses, for speed of execution. However, +// the .int file includes the mapping between variable names (same names +// that the user specifies, that are visible on the ladder diagram) and +// addresses. You can use this to establish specially-named variables that +// define the interface between your ladder code and the rest of your program. +// +// In this example, I use this mechanism to print the value of the integer +// variable 'a' after every cycle, and to generate a square wave with period +// 2*Tcycle on the input 'Xosc'. That is only for demonstration purposes, of +// course. +// +// In a real application you would need some way to get the information in the +// .int file into your device; this would be very application-dependent. Then +// you would need something like the InterpretOneCycle() routine to actually +// run the code. You can redefine the program and data memory sizes to +// whatever you think is practical; there are no particular constraints. +// +// The disassembler is just for debugging, of course. Note the unintuitive +// names for the condition ops; the INT_IFs are backwards, and the INT_ELSE +// is actually an unconditional jump! This is because I reused the names +// from the intermediate code that LDmicro uses, in which the if/then/else +// constructs have not yet been resolved into (possibly conditional) +// absolute jumps. It makes a lot of sense to me, but probably not so much +// to you; oh well. +// +// Jonathan Westhues, Aug 2005 +//----------------------------------------------------------------------------- +#include <stdio.h> +#include <ctype.h> +#include <unistd.h> + +#define INTCODE_H_CONSTANTS_ONLY +#include "intcode.h" + +typedef unsigned char BYTE; // 8-bit unsigned +typedef unsigned short WORD; // 16-bit unsigned +typedef signed short SWORD; // 16-bit signed + +// Some arbitrary limits on the program and data size +#define MAX_OPS 1024 +#define MAX_VARIABLES 128 +#define MAX_INTERNAL_RELAYS 128 + +// This data structure represents a single instruction for the 'virtual +// machine.' The .op field gives the opcode, and the other fields give +// arguments. I have defined all of these as 16-bit fields for generality, +// but if you want then you can crunch them down to 8-bit fields (and +// limit yourself to 256 of each type of variable, of course). If you +// crunch down .op then nothing bad happens at all. If you crunch down +// .literal then you only have 8-bit literals now (so you can't move +// 300 into 'var'). If you crunch down .name3 then that limits your code size, +// because that is the field used to encode the jump addresses. +// +// A more compact encoding is very possible if space is a problem for +// you. You will probably need some kind of translator regardless, though, +// to put it in whatever format you're going to pack in flash or whatever, +// and also to pick out the name <-> address mappings for those variables +// that you're going to use for your interface out. I will therefore leave +// that up to you. +typedef struct { + WORD op; + WORD name1; + WORD name2; + WORD name3; + SWORD literal; +} BinOp; + +BinOp Program[MAX_OPS]; +SWORD Integers[MAX_VARIABLES]; +BYTE Bits[MAX_INTERNAL_RELAYS]; + +// This are addresses (indices into Integers[] or Bits[]) used so that your +// C code can get at some of the ladder variables, by remembering the +// mapping between some ladder names and their addresses. +int SpecialAddrForA; +int SpecialAddrForXosc; + +//----------------------------------------------------------------------------- +// What follows are just routines to load the program, which I represent as +// hex bytes, one instruction per line, into memory. You don't need to +// remember the length of the program because the last instruction is a +// special marker (INT_END_OF_PROGRAM). +// +void BadFormat(void) +{ + fprintf(stderr, "Bad program format.\n"); + exit(-1); +} +int HexDigit(int c) +{ + c = tolower(c); + if(isdigit(c)) { + return c - '0'; + } else if(c >= 'a' && c <= 'f') { + return (c - 'a') + 10; + } else { + BadFormat(); + } + return 0; +} +void LoadProgram(char *fileName) +{ + int pc; + FILE *f = fopen(fileName, "r"); + char line[80]; + + // This is not suitable for untrusted input. + + if(!f) { + fprintf(stderr, "couldn't open '%s'\n", f); + exit(-1); + } + + if(!fgets(line, sizeof(line), f)) BadFormat(); + if(strcmp(line, "$$LDcode\n")!=0) BadFormat(); + + for(pc = 0; ; pc++) { + char *t, i; + BYTE *b; + + if(!fgets(line, sizeof(line), f)) BadFormat(); + if(strcmp(line, "$$bits\n")==0) break; + if(strlen(line) != sizeof(BinOp)*2 + 1) BadFormat(); + + t = line; + b = (BYTE *)&Program[pc]; + + for(i = 0; i < sizeof(BinOp); i++) { + b[i] = HexDigit(t[1]) | (HexDigit(t[0]) << 4); + t += 2; + } + } + + SpecialAddrForA = -1; + SpecialAddrForXosc = -1; + while(fgets(line, sizeof(line), f)) { + if(memcmp(line, "a,", 2)==0) { + SpecialAddrForA = atoi(line+2); + } + if(memcmp(line, "Xosc,", 5)==0) { + SpecialAddrForXosc = atoi(line+5); + } + if(memcmp(line, "$$cycle", 7)==0) { + if(atoi(line + 7) != 10*1000) { + fprintf(stderr, "cycle time was not 10 ms when compiled; " + "please fix that.\n"); + exit(-1); + } + } + } + + if(SpecialAddrForA < 0 || SpecialAddrForXosc < 0) { + fprintf(stderr, "special interface variables 'a' or 'Xosc' not " + "used in prog.\n"); + exit(-1); + } + + fclose(f); +} +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Disassemble the program and pretty-print it. This is just for debugging, +// and it is also the only documentation for what each op does. The bit +// variables (internal relays or whatever) live in a separate space from the +// integer variables; I refer to those as bits[addr] and int16s[addr] +// respectively. +//----------------------------------------------------------------------------- +void Disassemble(void) +{ + int pc; + for(pc = 0; ; pc++) { + BinOp *p = &Program[pc]; + printf("%03x: ", pc); + + switch(Program[pc].op) { + case INT_SET_BIT: + printf("bits[%03x] := 1", p->name1); + break; + + case INT_CLEAR_BIT: + printf("bits[%03x] := 0", p->name1); + break; + + case INT_COPY_BIT_TO_BIT: + printf("bits[%03x] := bits[%03x]", p->name1, p->name2); + break; + + case INT_SET_VARIABLE_TO_LITERAL: + printf("int16s[%03x] := %d (0x%04x)", p->name1, p->literal, + p->literal); + break; + + case INT_SET_VARIABLE_TO_VARIABLE: + printf("int16s[%03x] := int16s[%03x]", p->name1, p->name2); + break; + + case INT_INCREMENT_VARIABLE: + printf("(int16s[%03x])++", p->name1); + break; + + { + char c; + case INT_SET_VARIABLE_ADD: c = '+'; goto arith; + case INT_SET_VARIABLE_SUBTRACT: c = '-'; goto arith; + case INT_SET_VARIABLE_MULTIPLY: c = '*'; goto arith; + case INT_SET_VARIABLE_DIVIDE: c = '/'; goto arith; +arith: + printf("int16s[%03x] := int16s[%03x] %c int16s[%03x]", + p->name1, p->name2, c, p->name3); + break; + } + + case INT_IF_BIT_SET: + printf("unless (bits[%03x] set)", p->name1); + goto cond; + case INT_IF_BIT_CLEAR: + printf("unless (bits[%03x] clear)", p->name1); + goto cond; + case INT_IF_VARIABLE_LES_LITERAL: + printf("unless (int16s[%03x] < %d)", p->name1, p->literal); + goto cond; + case INT_IF_VARIABLE_EQUALS_VARIABLE: + printf("unless (int16s[%03x] == int16s[%03x])", p->name1, + p->name2); + goto cond; + case INT_IF_VARIABLE_GRT_VARIABLE: + printf("unless (int16s[%03x] > int16s[%03x])", p->name1, + p->name2); + goto cond; +cond: + printf(" jump %03x+1", p->name3); + break; + + case INT_ELSE: + printf("jump %03x+1", p->name3); + break; + + case INT_END_OF_PROGRAM: + printf("<end of program>\n"); + return; + + default: + BadFormat(); + break; + } + printf("\n"); + } +} + +//----------------------------------------------------------------------------- +// This is the actual interpreter. It runs the program, and needs no state +// other than that kept in Bits[] and Integers[]. If you specified a cycle +// time of 10 ms when you compiled the program, then you would have to +// call this function 100 times per second for the timing to be correct. +// +// The execution time of this function depends mostly on the length of the +// program. It will be a little bit data-dependent but not very. +//----------------------------------------------------------------------------- +void InterpretOneCycle(void) +{ + int pc; + for(pc = 0; ; pc++) { + BinOp *p = &Program[pc]; + + switch(Program[pc].op) { + case INT_SET_BIT: + Bits[p->name1] = 1; + break; + + case INT_CLEAR_BIT: + Bits[p->name1] = 0; + break; + + case INT_COPY_BIT_TO_BIT: + Bits[p->name1] = Bits[p->name2]; + break; + + case INT_SET_VARIABLE_TO_LITERAL: + Integers[p->name1] = p->literal; + break; + + case INT_SET_VARIABLE_TO_VARIABLE: + Integers[p->name1] = Integers[p->name2]; + break; + + case INT_INCREMENT_VARIABLE: + (Integers[p->name1])++; + break; + + case INT_SET_VARIABLE_ADD: + Integers[p->name1] = Integers[p->name2] + Integers[p->name3]; + break; + + case INT_SET_VARIABLE_SUBTRACT: + Integers[p->name1] = Integers[p->name2] - Integers[p->name3]; + break; + + case INT_SET_VARIABLE_MULTIPLY: + Integers[p->name1] = Integers[p->name2] * Integers[p->name3]; + break; + + case INT_SET_VARIABLE_DIVIDE: + if(Integers[p->name3] != 0) { + Integers[p->name1] = Integers[p->name2] / + Integers[p->name3]; + } + break; + + case INT_IF_BIT_SET: + if(!Bits[p->name1]) pc = p->name3; + break; + + case INT_IF_BIT_CLEAR: + if(Bits[p->name1]) pc = p->name3; + break; + + case INT_IF_VARIABLE_LES_LITERAL: + if(!(Integers[p->name1] < p->literal)) pc = p->name3; + break; + + case INT_IF_VARIABLE_EQUALS_VARIABLE: + if(!(Integers[p->name1] == Integers[p->name2])) pc = p->name3; + break; + + case INT_IF_VARIABLE_GRT_VARIABLE: + if(!(Integers[p->name1] > Integers[p->name2])) pc = p->name3; + break; + + case INT_ELSE: + pc = p->name3; + break; + + case INT_END_OF_PROGRAM: + return; + } + } +} + + +int main(int argc, char **argv) +{ + int i; + + if(argc != 2) { + fprintf(stderr, "usage: %s xxx.int\n", argv[0]); + return -1; + } + + LoadProgram(argv[1]); + memset(Integers, 0, sizeof(Integers)); + memset(Bits, 0, sizeof(Bits)); + + // 1000 cycles times 10 ms gives 10 seconds execution + for(i = 0; i < 1000; i++) { + InterpretOneCycle(); + + // Example for reaching in and reading a variable: just print it. + printf("a = %d \r", Integers[SpecialAddrForA]); + + // Example for reaching in and writing a variable. + Bits[SpecialAddrForXosc] = !Bits[SpecialAddrForXosc]; + + // XXX, nonportable; replace with whatever timing functions are + // available on your target. + usleep(10000); + } + + return 0; +} |