/*
* picasm.c
*
* Copyright 1995-2005 Timo Rossi, <trossi@iki.fi>
* See the file LICENSE for license terms.
*
* http://www.iki.fi/trossi/pic/
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
#include <time.h>
#include <errno.h>
#include "picasm.h"
#include "util.h"
#include "token.h"
#include "symtab.h"
int warnlevel;
static int total_line_count;
static int errors, warnings; /* error & warning counts */
unsigned short list_flags;
static FILE *list_fp;
int listing_on;
static int list_loc;
static pic_instr_t *list_ptr;
long list_val;
static long list_len;
int default_radix;
int cond_nest_count;
int unique_id_count;
/* the current source file/macro */
struct inc_file *current_file;
/* Line buffer & pointer to it */
char *line_buf_ptr;
char line_buffer[256];
static struct patch *global_patch_list;
struct patch **local_patch_list_ptr;
char pic_name[256];
instrset_t pic_instr_set; /* set the PIC_NONE before PIC type is defined */
int prog_mem_size = PROGMEM_MAX;
static int data_eeprom_size = EEPROM_MAX;
static int reg_file_limit = 0x100;
short code_generated;
static pic_instr_t prog_mem[PROGMEM_MAX];
static pic_instr_t data_eeprom[EEPROM_MAX];
static pic_instr_t pic_id[4];
pic_instr_t config_fuses;
org_mode_t O_Mode;
int prog_location; /* current address for program code */
int reg_location; /* current address for register file */
int edata_location; /* current address for data EEPROM */
int org_val;
int local_level;
static void
err_out(char *str)
{
fputs(str, stderr);
fputc('\n', stderr);
}
/* Error handling */
/*
* Show line number/line with error message
*/
static void
err_line_ref(void)
{
struct inc_file *inc;
char outbuf[128];
int len;
if(current_file != NULL)
{
inc = current_file;
if(inc->type != INC_FILE)
{
p_snprintf(outbuf, sizeof outbuf, "(Macro %-.99s line %d)",
inc->v.m.sym->name, inc->linenum);
err_out(outbuf);
while(inc != NULL && inc->type != INC_FILE)
inc = inc->next;
}
p_snprintf(outbuf, sizeof outbuf, "%-.99s:%d:",
inc->v.f.fname, inc->linenum);
err_out(outbuf);
len = strlen(line_buffer);
if(len > 100)
len = 100;
strncpy(outbuf, line_buffer, len);
outbuf[len] = '\0';
err_out(outbuf);
}
}
/*
* Warning message
*/
void
warning(char *fmt, ...)
{
char outbuf[128];
va_list args;
static const char warning_msg[] = "Warning: ";
err_line_ref();
strcpy(outbuf, warning_msg);
va_start(args, fmt);
p_vsnprintf(outbuf + (sizeof warning_msg - 1),
sizeof outbuf - (sizeof warning_msg - 1), fmt, args);
va_end(args);
err_out(outbuf);
if(list_fp != NULL)
fputs(outbuf, list_fp);
warnings++;
}
/*
* skip the end of line, in case of an error
*/
static void
error_lineskip(void)
{
write_listing_line(0);
skip_eol();
get_token();
}
/*
* Error message
* call error_lineskip() if lskip is non-zero
*
*/
void
error(int lskip, char *fmt, ...)
{
va_list args;
char outbuf[128];
static const char error_msg[] = "Error: ";
err_line_ref();
strcpy(outbuf, error_msg);
va_start(args, fmt);
p_vsnprintf(outbuf + (sizeof error_msg - 1),
sizeof outbuf - (sizeof error_msg - 1),
fmt, args);
va_end(args);
err_out(outbuf);
if(list_fp != NULL)
{
fputs(outbuf, list_fp);
fputc('\n', list_fp);
}
if(++errors >= MAX_ERRORS)
fatal_error("too many errors, aborting");
if(lskip)
error_lineskip();
}
/*
* Fatal error message
*/
void
fatal_error(char *fmt, ...)
{
va_list args;
char outbuf[128];
static char const fatal_err_msg[] = "Fatal error: ";
err_line_ref();
strcpy(outbuf, fatal_err_msg);
va_start(args, fmt);
p_vsnprintf(outbuf + (sizeof fatal_err_msg - 1),
sizeof outbuf - (sizeof fatal_err_msg - 1),
fmt, args);
va_end(args);
err_out(outbuf);
exit(EXIT_FAILURE);
}
/* memory allocation */
void *
mem_alloc(int size)
{
void *p;
if((p = malloc(size)) == NULL)
fatal_error("Out of memory");
return p;
}
/*
* initialize the assembler
*/
static void
init_assembler(void)
{
pic_instr_t *cp;
int n;
init_symtab(); /* initialize symbol table */
init_includes(); /* initialize include paths */
/* initialize program memory to invalid values */
for(n = PROGMEM_MAX, cp = prog_mem; n-- > 0; *cp++ = INVALID_INSTR)
;
/* initialize data memory to invalid values */
for(n = EEPROM_MAX, cp = data_eeprom; n-- > 0; *cp++ = INVALID_DATA)
;
/* initialize jump patch list */
global_patch_list = NULL;
prog_location = -1;
reg_location = -1;
edata_location = -1;
org_val = -1;
current_file = NULL;
config_fuses = INVALID_CONFIG;
pic_id[0] = INVALID_ID;
errors = warnings = 0;
list_flags = 0;
list_len = 0;
listing_on = 1;
cond_nest_count = 0;
unique_id_count = 1;
total_line_count = 0;
code_generated = 0;
local_level = 0;
ifskip_mode = 0;
default_radix = 10;
}
/*
* generate program code
*/
void gen_code(int val)
{
if(pic_instr_set == PIC_NONE)
fatal_error("PIC device type not set");
if(O_Mode == O_NONE)
{
O_Mode = O_PROGRAM;
if(org_val < 0)
{
error(0, "ORG value not set");
prog_location = 0;
return;
}
prog_location = org_val;
}
else if(O_Mode != O_PROGRAM)
{
error(0, "ORG mode conflict");
O_Mode = O_PROGRAM;
return;
}
if(prog_location >= prog_mem_size)
fatal_error("Code address out of range");
if(prog_mem[prog_location] != INVALID_INSTR)
warning("Overlapping code at 0x%x", prog_location);
if((list_flags & LIST_PROG) == 0)
{
list_loc = prog_location;
list_flags = LIST_LOC | LIST_PROG;
}
list_len++;
prog_mem[prog_location++] = val;
code_generated = 1;
}
/*
* Generate data for data EEPROM
*/
void
gen_edata(int val)
{
if(O_Mode == O_NONE)
{
O_Mode = O_EDATA;
if(org_val < 0)
{
error(0, "ORG value not set");
edata_location = 0;
return;
}
edata_location = org_val;
}
else if(O_Mode != O_EDATA)
{
error(0, "ORG mode conflict");
O_Mode = O_EDATA;
return;
}
if(edata_location >= data_eeprom_size)
fatal_error("Data EEPROM address out of range");
if(data_eeprom[edata_location] < 0x100)
warning("Overlapping EEPROM data at 0x%x", edata_location);
if((list_flags & LIST_LOC) == 0)
{
list_loc = edata_location;
list_flags = LIST_LOC | LIST_EDATA;
}
list_len++;
data_eeprom[edata_location++] = val;
code_generated = 1;
}
/*
* Write one line of Intel-hex file
*/
static void
write_hex_record(FILE *fp,
int reclen, /* length (in words) */
int loc, /* address */
pic_instr_t *data, /* pointer to word data */
int format) /* IHX8M or IHX16 */
{
unsigned int check = 0;
switch(format)
{
case IHX8M:
fprintf(fp, ":%02X%04X00", 2*reclen, 2*loc);
check += ((2*loc) & 0xff) + (((2*loc) >> 8) & 0xff) + 2*reclen;
break;
case IHX16:
fprintf(fp, ":%02X%04X00", reclen, loc);
check += (loc & 0xff) + ((loc >> 8) & 0xff) + reclen;
break;
}
while(reclen--)
{
switch(format)
{
case IHX8M:
fprintf(fp, "%02X%02X",
(int)(*data & 0xff), (int)((*data >> 8) & 0xff));
break;
case IHX16:
fprintf(fp, "%02X%02X",
(int)((*data >> 8) & 0xff), (int)(*data & 0xff));
break;
}
check += (*data & 0xff) + ((*data >> 8) & 0xff);
data++;
}
/* write checksum */
fprintf(fp, "%02X" HEX_EOL, (0x100 - (check & 0xff)) & 0xff);
}
/*
* Write output file in ihx8m or ihx16-format
*
*/
static void
write_output(char *fname, int format)
{
int loc, reclen;
FILE *fp;
if((fp = fopen(fname, "w")) == NULL)
fatal_error("Can't create file '%s': %s", fname, strerror(errno));
/* program */
for(loc = 0;;)
{
while(loc < prog_mem_size && prog_mem[loc] == INVALID_INSTR)
loc++;
if(loc >= prog_mem_size)
break;
reclen = 0;
while(reclen < 8 && loc < prog_mem_size
&& prog_mem[loc] != INVALID_INSTR)
{
loc++;
reclen++;
}
write_hex_record(fp, reclen, loc-reclen, &prog_mem[loc-reclen], format);
}
/* PIC ID */
if(pic_id[0] != INVALID_ID)
{
switch(pic_instr_set)
{
case PIC12BIT:
write_hex_record(fp, 4, prog_mem_size, pic_id, format);
break;
case PIC14BIT:
write_hex_record(fp, 4, 0x2000, pic_id, format);
break;
default:
break;
}
}
/* config fuses */
if(config_fuses != INVALID_CONFIG)
{
switch(pic_instr_set)
{
case PIC12BIT:
write_hex_record(fp, 1, 0xfff, &config_fuses, format);
break;
case PIC14BIT:
write_hex_record(fp, 1, 0x2007, &config_fuses, format);
break;
case PIC16BIT_NOMUL:
case PIC16BIT:
/* XXX Is this correct? */
write_hex_record(fp, 1, 0xfe00, &config_fuses, format);
break;
case PIC_NONE:
fatal_error("internal error");
break;
}
}
if(data_eeprom_size > 0)
{ /* data EEPROM */
for(loc = 0;;)
{
while(loc < data_eeprom_size && data_eeprom[loc] >= 0x100)
loc++;
if(loc >= data_eeprom_size)
break;
reclen = 0;
while(reclen < 8 && loc < data_eeprom_size
&& data_eeprom[loc] < 0x100)
{
loc++;
reclen++;
}
write_hex_record(fp, reclen, 0x2100+loc-reclen,
&data_eeprom[loc-reclen], format);
}
}
fputs(":00000001FF" HEX_EOL , fp); /* end record */
fclose(fp);
}
/*
* Write one line to listing file (if listing is enabled)
*/
void
write_listing_line(int cond_flag)
{
int i;
if(list_fp != NULL && listing_on)
{
fprintf(list_fp, "%04d%c%c",
++total_line_count,
(current_file != NULL && current_file->type == INC_MACRO ?
'+' : ' '),
(cond_flag ? '!' : ' '));
if(line_buffer[0] != '\0')
{
if(list_flags & LIST_VAL)
{
fprintf(list_fp, "%08lX ", list_val);
}
else
{
if(list_flags & LIST_LOC)
fprintf(list_fp, "%04X", list_loc);
else
fputs(" ", list_fp);
if((list_flags & (LIST_PROG|LIST_EDATA|LIST_PTR)) != 0)
{
fputc(((list_flags & LIST_FORWARD) ? '?' : ' '), list_fp);
if(list_flags & LIST_PROG)
{
fprintf(list_fp, "%04X ",
(unsigned short)prog_mem[list_loc]);
}
else if(list_flags & LIST_EDATA)
{
fprintf(list_fp, "%04X ",
(unsigned short)data_eeprom[list_loc]);
}
else if(list_flags & LIST_PTR)
{
fprintf(list_fp, "%04X ",
(unsigned short)(*list_ptr++));
}
}
else
{
fputs(" ", list_fp);
}
}
fputs(line_buffer, list_fp);
fputc('\n', list_fp);
list_len--;
for(i = 0; i < list_len; i++)
{
list_loc++;
fprintf(list_fp, "%04d%c ",
total_line_count,
(current_file != NULL
&& current_file->type == INC_MACRO ?
'+' : ' '));
if(list_flags & LIST_LOC)
fprintf(list_fp, "%04X", list_loc);
else
fputs(" ", list_fp);
if(list_flags & LIST_PROG)
{
fprintf(list_fp, " %04X\n",
(unsigned short)prog_mem[list_loc]);
}
else if(list_flags & LIST_EDATA)
{
fprintf(list_fp, " %04X\n",
(unsigned short)
data_eeprom[list_loc]);
}
else if(list_flags & LIST_PTR)
{
fprintf(list_fp, " %04X\n",
(unsigned short)(*list_ptr++));
}
}
}
else
{ /* if(line_buffer[0] != '\0' ... */
fputc('\n', list_fp);
}
if(listing_on < 0)
listing_on = 0;
}
list_flags = 0;
list_len = 0;
}
/*
* parse and handle OPT-directive
* (this is special as it must be done at macro definition time
* if inside a macro)
*/
static int
handle_opt(void)
{
if(token_type != TOK_IDENTIFIER)
{
error(1, "OPT syntax error");
return FAIL;
}
/*
* Note: when listing is turned off, 'listing_on' is set to -1
* here and the listing routine sets it to zero after listing
* the line containing the 'opt nol'.
*/
if(strcasecmp(token_string, "nol") == 0
|| strcasecmp(token_string, "nolist") == 0)
{
listing_on = -1;
}
else if(strcasecmp(token_string, "l") == 0
|| strcasecmp(token_string, "list") == 0)
{
listing_on = 1;
}
else
{
error(1, "OPT syntax error");
return FAIL;
}
get_token();
return OK;
}
/*
* Define a macro
*
* the check for multiple definitions of the macro name
* has been done before calling this function.
*/
static void
define_macro(char *name)
{
struct symbol *sym;
struct macro_line *ml;
int t;
if(token_type != TOK_NEWLINE && token_type != TOK_EOF)
error(0, "Extraneous characters after a valid source line");
skip_eol();
write_listing_line(0);
sym = add_symbol(name, SYMTAB_GLOBAL);
sym->type = SYM_MACRO;
sym->v.text = NULL;
ml = NULL;
for(;;)
{
get_token(); /* read first token on next line */
t = 0;
if(token_type == TOK_IDENTIFIER)
{
t = 1;
get_token();
if(token_type == TOK_COLON)
get_token();
}
if(token_type == TOK_EOF || token_type == KW_END)
fatal_error("Macro definition not terminated");
if(token_type == KW_MACRO)
fatal_error("Nested macro definitions not allowed");
if(token_type == KW_ENDM) /* end macro definition */
break;
/* OPT must be handled inside macros at definition time */
if(token_type == KW_OPT)
{
get_token();
handle_opt();
}
else
{
if(ml == NULL)
{
ml = mem_alloc(sizeof(struct macro_line)
+strlen(line_buffer));
sym->v.text = ml;
}
else
{
ml->next = mem_alloc(sizeof(struct macro_line)
+strlen(line_buffer));
ml = ml->next;
}
strcpy(ml->text, line_buffer);
ml->next = NULL;
}
write_listing_line(0);
line_buf_ptr = NULL;
tok_char = ' ';
}
if(t)
error(0, "Label not allowed with ENDM");
get_token();
}
/*
* Skip subroutine used by the conditional assembly directives
* return: -1=premature EOF, 0=ok, 1=label (not allowed)
*/
static int
if_else_skip(void)
{
int t, ccount;
ccount = 0;
ifskip_mode++;
do
{
skip_eol();
get_token();
write_listing_line(1);
t = 0;
if(token_type == TOK_IDENTIFIER)
{
t = 1;
get_token();
if(token_type == TOK_COLON)
get_token();
}
if(token_type == KW_IF)
{
ccount++;
}
else if(token_type == KW_ENDIF)
{
if(ccount <= 0)
break;
ccount--;
}
else if(token_type == KW_ELSE && ccount <= 0)
{
break;
}
} while(token_type != TOK_EOF && token_type != KW_END);
ifskip_mode--;
return (token_type == TOK_EOF || token_type == KW_END) ? -1 : t;
}
/*
* Add a patch pointing to the current location
*/
void
add_patch(int tab, struct symbol *sym, patchtype_t type)
{
struct patch **patch_list_ptr, *ptch;
patch_list_ptr =
(tab == SYMTAB_GLOBAL ? &global_patch_list : local_patch_list_ptr);
if(O_Mode == O_NONE)
{
O_Mode = O_PROGRAM;
if(org_val < 0)
{
error(0, "ORG value not set");
prog_location = 0;
return;
}
prog_location = org_val;
}
ptch = mem_alloc(sizeof(struct patch));
ptch->label = sym;
ptch->type = type;
ptch->location = prog_location;
/* add a new patch to patch_list */
ptch->next = *patch_list_ptr;
*patch_list_ptr = ptch;
}
/*
* Apply the store patches and also free the patch list
*/
static void
apply_patches(struct patch *patch_list)
{
struct patch *ptch, *p2;
/*
* fix forward jumps/calls
*/
for(ptch = patch_list; ptch != NULL; ptch = p2)
{
p2 = ptch->next;
if(ptch->label->type == SYM_FORWARD)
{
error(0, "Undefined label '%s%s'",
(patch_list == global_patch_list ? "" : "="),
ptch->label->name);
}
else
{
switch(ptch->type)
{
case PATCH8:
if(pic_instr_set == PIC12BIT
&& (ptch->label->v.value & 0x100) != 0
&& (prog_mem[ptch->location] & 0xff00) == 0x900)
error(0, "CALL address in upper half of a page (label '%s%s')",
(patch_list == global_patch_list ? "" : "="),
ptch->label->name);
prog_mem[ptch->location] =
(prog_mem[ptch->location] & 0xff00)
| (ptch->label->v.value & 0xff);
break;
case PATCH9:
prog_mem[ptch->location] =
(prog_mem[ptch->location] & 0xfe00)
| (ptch->label->v.value & 0x1ff);
break;
case PATCH11:
prog_mem[ptch->location] =
(prog_mem[ptch->location] & 0xf800)
| (ptch->label->v.value & 0x7ff);
break;
case PATCH_UPPER_BYTE:
prog_mem[ptch->location] =
(prog_mem[ptch->location] & 0xf800)
| ((ptch->label->v.value >> 8) & 0xff);
break;
}
}
mem_free(ptch);
}
}
/*
* Generate code for an instruction with 8-bit literal data
* allows forward references
*/
int
gen_byte_c(int instr_code)
{
int symtype;
long val;
struct symbol *sym;
if(token_type == KW_HIBYTE)
{
get_token();
if(token_type != TOK_LEFTPAR)
{
error(1, "'(' expected");
return FAIL;
}
get_token();
if(token_type != TOK_IDENTIFIER && token_type != TOK_LOCAL_ID)
{
error(1, "Identifier expected");
return FAIL;
}
symtype =
(token_type == TOK_IDENTIFIER ? SYMTAB_GLOBAL : SYMTAB_LOCAL);
if(symtype == SYMTAB_LOCAL && local_level == 0)
{
error(1, "Local symbol outside a LOCAL block");
return FAIL;
}
sym = lookup_symbol(token_string, symtype);
if(sym != NULL && sym->type != SYM_FORWARD)
{
if(sym->type != SYM_SET || sym->type != SYM_DEFINED)
{
error(1, "Invalid symbol type");
return FAIL;
}
gen_code(instr_code | ((sym->v.value >> 8) & 0xff));
}
else
{
if(sym == NULL)
{
sym = add_symbol(token_string, symtype);
sym->type = SYM_FORWARD;
}
add_patch(symtype, sym, PATCH_UPPER_BYTE);
gen_code(instr_code);
list_flags |= LIST_FORWARD;
}
get_token();
if(token_type != TOK_RIGHTPAR)
{
error(1, "')' expected");
return FAIL;
}
get_token();
return OK;
}
if(token_type == TOK_IDENTIFIER || token_type == TOK_LOCAL_ID)
{
symtype =
(token_type == TOK_IDENTIFIER ? SYMTAB_GLOBAL : SYMTAB_LOCAL);
if(symtype == SYMTAB_LOCAL && local_level == 0)
{
error(1, "Local symbol outside a LOCAL block");
return FAIL;
}
sym = lookup_symbol(token_string, symtype);
if(sym == NULL || sym->type == SYM_FORWARD)
{
if(sym == NULL)
{
sym = add_symbol(token_string, symtype);
sym->type = SYM_FORWARD;
}
add_patch(symtype, sym, PATCH8);
get_token();
gen_code(instr_code);
list_flags |= LIST_FORWARD;
return OK;
}
}
if(get_expression(&val) != OK)
return FAIL;
if(val < -0x80 || val > 0xff)
{
error(0, "8-bit literal out of range");
return FAIL;
}
gen_code(instr_code | (val & 0xff));
return OK;
}
/*
* check if the current token is a valid ORG mode specifier
* and return the mode (or O_NONE if not valid mode specifier)
*/
static int
org_mode(void)
{
if(token_type == KW_EDATA)
return O_EDATA;
if(token_type == TOK_IDENTIFIER)
{
if(strcasecmp(token_string, "code") == 0)
return O_PROGRAM;
if(strcasecmp(token_string, "reg") == 0)
return O_REGFILE;
}
return O_NONE;
}
/*
* Device type selection: automatically include a device-specific
* header file (which uses the set_pic_type directive and defines
* the __do_config directive).
*/
static void
do_device_include(char *devname)
{
char incname[256];
char *cp;
int c;
if(strncasecmp(devname, "PIC", 3) == 0)
devname += 3;
strcpy(incname, "_pic");
cp = &incname[4];
while(cp < &incname[sizeof(incname)-3])
{
c = (unsigned char)(*devname);
if(c == '\0')
break;
if(isupper(c))
c = tolower(c);
*cp++ = c;
devname++;
}
strcpy(cp, ".i");
get_token();
if(pic_instr_set != PIC_NONE)
{
/*
* This error message would be misleading when the device type
* has been set on the picasm command line, but because it can
* only be done once from the command line, we can not get
* this error condition in that case.
*/
error(1, "Duplicate DEVICE setting");
return;
}
begin_include(incname, 1, 1);
}
/*
* The assembler itself
*/
static void
assembler(char *fname, char *cmdline_pic_type)
{
static char symname[256];
struct symbol *sym;
int op, t, symtype;
long val;
char *cp;
sym = add_symbol("_PICASM", SYMTAB_GLOBAL);
sym->type = SYM_DEFINED;
sym->type2 = SYMT_CONSTANT;
sym->v.value = I_VERSION;
begin_include(fname, 0, 1);
if(cmdline_pic_type != NULL)
do_device_include(cmdline_pic_type);
get_token();
while(token_type != TOK_EOF)
{
sym = NULL;
if(token_type == TOK_IDENTIFIER || token_type == TOK_LOCAL_ID)
{
symtype =
(token_type == TOK_IDENTIFIER ? SYMTAB_GLOBAL : SYMTAB_LOCAL);
if(symtype == SYMTAB_LOCAL && local_level == 0)
{
error(1, "Local symbol outside a LOCAL block");
continue;
}
t = (line_buf_off == 0);
p_strcpy(symname, token_string, sizeof symname);
sym = lookup_symbol(symname, symtype);
if(sym != NULL && sym->type == SYM_MACRO)
{
/* skip whitespace */
while(isspace(tok_char))
read_src_char();
if(line_buf_ptr != NULL &&
strncasecmp(line_buf_ptr-1, "macro", 5) == 0 &&
line_buf_ptr[4] != '.' && line_buf_ptr[4] != '_' &&
!isalnum((unsigned char)line_buf_ptr[4]))
{
error(1, "Multiple definition of macro '%s'", symname);
continue;
}
expand_macro(sym);
continue;
}
get_token();
switch(token_type)
{
case KW_MACRO:
get_token();
if(sym != NULL)
{
error(1, "Multiply defined symbol '%s%s'",
(symtype == SYMTAB_LOCAL ? "=" : ""),
sym->name);
continue;
}
define_macro(symname);
goto line_end;
case TOK_EQUAL: /* '=' can be used instead of EQU */
case KW_EQU:
if(sym != NULL)
{
if(sym->type != SYM_FORWARD)
error(0, "Multiply defined symbol '%s%s'",
(symtype == SYMTAB_LOCAL ? "=" : ""),
sym->name);
}
else
{
sym = add_symbol(symname, symtype);
}
get_token();
sym->type = SYM_DEFINED;
sym->type2 = SYMT_CONSTANT;
if(get_expression(&sym->v.value) != OK)
continue; /* error_lineskip() done in expr.c */
list_val = sym->v.value;
list_flags = LIST_VAL;
goto line_end;
case KW_SET:
if(sym != NULL && sym->type != SYM_SET)
{
error(0, "Multiply defined symbol '%s%s'",
(symtype == SYMTAB_LOCAL ? "=" : ""),
sym->name);
}
else if(sym == NULL)
{
sym = add_symbol(symname, symtype);
}
get_token();
sym->type = SYM_SET;
sym->type2 = SYMT_CONSTANT;
if(get_expression(&sym->v.value) != OK)
continue;
list_val = sym->v.value;
list_flags = LIST_VAL;
goto line_end;
case TOK_COLON:
get_token();
goto do_label;
default:
if(t == 0)
warning("Label not in the beginning of a line");
do_label:
switch(O_Mode)
{
case O_PROGRAM:
t = prog_location;
break;
case O_REGFILE:
t = reg_location;
break;
case O_EDATA:
t = edata_location;
break;
case O_NONE:
t = org_val;
break;
}
if(t < 0)
{
error(0, "ORG value not set");
}
else
{
if(sym != NULL && sym->type != SYM_FORWARD)
{
error(0, "Multiply defined symbol '%s%s'",
(symtype == SYMTAB_LOCAL ? "=" : ""),
sym->name);
}
if(sym == NULL)
{
sym = add_symbol(symname, symtype);
}
sym->type = SYM_DEFINED;
switch(O_Mode)
{
case O_PROGRAM:
sym->type2 = SYMT_LABEL;
break;
case O_REGFILE:
sym->type2 = SYMT_REGFILE;
break;
default:
break;
}
sym->v.value = t;
list_loc = t;
list_flags = LIST_LOC;
}
break;
}
} /* if(token_type == TOK_IDENTIFIER... */
/* if this line has a label, 'sym' points to it */
if(token_type == TOK_NEWLINE)
{
write_listing_line(0);
get_token();
continue;
}
if(token_type == TOK_IDENTIFIER &&
(sym = lookup_symbol(token_string, SYMTAB_GLOBAL))
!= NULL && sym->type == SYM_MACRO)
{
expand_macro(sym);
continue;
}
if(token_type == KW_END)
break;
if(token_type == KW_ERROR)
{
while(isspace((unsigned char)(*line_buf_ptr)))
line_buf_ptr++;
error(1, "%s", line_buf_ptr);
continue;
}
op = token_type;
get_token();
switch(op)
{
case KW_INCLUDE:
if(token_type != TOK_STRCONST)
{
error(1, "Missing file name after INCLUDE");
continue;
}
p_strcpy(symname, token_string, sizeof symname);
get_token();
if(token_type != TOK_NEWLINE && token_type != TOK_EOF)
error(0, "Extraneous characters after a valid source line");
begin_include(symname, 1, 0);
write_listing_line(0);
get_token();
continue;
case TOK_EQUAL:
case KW_SET:
case KW_EQU:
if(sym == NULL)
error(1, "SET/EQU without a label");
else
error(1, "SET/EQU syntax error");
continue;
case KW_MACRO:
error(1, "MACRO without a macro name");
continue;
case KW_ENDM:
error(1, "ENDM not allowed outside a macro");
continue;
case KW_EXITM:
if(current_file == NULL || current_file->type != INC_MACRO)
{
error(1, "EXITM not allowed outside a macro");
continue;
}
cond_nest_count = current_file->cond_nest_count;
end_include();
break;
case KW_OPT:
if(handle_opt() != OK)
{
error_lineskip();
continue;
}
break;
case KW_LOCAL:
add_local_symtab();
break;
case KW_ENDLOCAL:
if(local_level == 0)
{
error(1, "ENDLOCAL without LOCAL");
continue;
}
apply_patches(*local_patch_list_ptr);
remove_local_symtab();
break;
case KW_IF:
if(sym != NULL)
error(0, "Label not allowed with IF");
get_expression(&val); /*note: error status ignored*/
if(token_type != TOK_NEWLINE && token_type != TOK_EOF)
error(0, "Extraneous characters after a valid source line");
if(val == 0)
{
write_listing_line(0);
t=if_else_skip();
if(t == -1)
fatal_error("Conditional not terminated");
else if(t == 1)
error(0, "Label not allowed with %s",
(token_type == KW_ELSE ? "ELSE" : "ENDIF"));
if(token_type == KW_ELSE)
cond_nest_count++;
get_token();
goto line_end2;
}
else
{
cond_nest_count++;
}
break;
case KW_ELSE:
if(sym != NULL)
error(0, "Label not allowed with %s", "ELSE");
if(current_file == NULL
|| cond_nest_count <= current_file->cond_nest_count)
error(0, "ELSE without IF");
write_listing_line(0);
t = if_else_skip();
if(t == -1)
fatal_error("Conditional not terminated");
else if(t == 1)
error(0, "Label not allowed with %s",
(token_type == KW_ELSE ? "ELSE" : "ENDIF"));
if(token_type == KW_ELSE)
error(0, "Multiple ELSE statements with one IF");
cond_nest_count--;
get_token();
goto line_end2;
case KW_ENDIF:
if(sym != NULL)
error(0, "Label not allowed with %s", "ENDIF");
if(current_file == NULL
|| cond_nest_count <= current_file->cond_nest_count)
error(0, "ENDIF without IF");
cond_nest_count--;
break;
case KW_ORG:
if(pic_instr_set == PIC_NONE)
fatal_error("PIC device type not set");
org_val = -1;
if((t = org_mode()) != O_NONE)
{
get_token();
O_Mode = (org_mode_t)t;
switch(O_Mode)
{
case O_PROGRAM:
case O_NONE: /* shut up GCC warning, cannot really happen here */
org_val = prog_location;
break;
case O_REGFILE:
org_val = reg_location;
break;
case O_EDATA:
org_val = edata_location;
break;
}
if(org_val < 0)
{
error(0, "ORG value not set");
O_Mode = O_NONE;
}
break;
}
if(get_expression(&val) != OK)
continue;
if(val < 0 || val >= prog_mem_size)
{
error(1, "ORG value out of range");
continue;
}
org_val = val;
O_Mode = O_NONE;
if(token_type == TOK_COMMA)
{
get_token();
if((t = org_mode()) == O_NONE)
{
error(0, "Invalid ORG mode");
}
else
{
O_Mode = (org_mode_t)t;
switch(O_Mode)
{
case O_PROGRAM:
case O_NONE: /* shut up GCC warning, cannot really happen here */
prog_location = org_val;
break;
case O_REGFILE:
reg_location = org_val;
break;
case O_EDATA:
edata_location = org_val;
break;
}
}
get_token();
}
list_loc = org_val;
list_flags = LIST_LOC;
break;
case KW_DS:
if(get_expression(&val) != OK)
continue;
if(O_Mode == O_NONE)
{
O_Mode = O_REGFILE;
if(org_val < 0)
{
error(0, "ORG value not set");
reg_location = 0;
}
else
{
reg_location = org_val;
}
}
if(O_Mode != O_REGFILE)
{
error(0, "ORG mode conflict");
}
else
{
if(reg_location >= reg_file_limit)
fatal_error("Register file address out of range");
list_loc = reg_location;
list_flags = LIST_LOC;
reg_location += val;
}
if(sym != NULL && sym->type2 == SYMT_UNKNOWN)
sym->type2 = SYMT_REGFILE;
break;
case KW_CBLOCK:
if(sym != NULL)
error(0, "Label not allowed with CBLOCK");
if(token_type != TOK_NEWLINE && token_type != TOK_EOF)
{
if(get_expression(&val) != OK)
{
error_lineskip();
}
else
{
reg_location = val;
if(token_type != TOK_NEWLINE && token_type != TOK_EOF)
error(0, "Extraneous characters after a valid source line");
skip_eol();
}
}
if(reg_location < 0)
{
error(0, "ORG/CBLOCK register address not set");
reg_location = 0;
}
/* not really implemented .... */
while(token_type != KW_ENDC)
{
list_loc = -1;
while(token_type != TOK_NEWLINE)
{
if(token_type == TOK_EOF)
{
error(0, "Unexpected EOF inside a CBLOCK");
break;
}
if(token_type != TOK_IDENTIFIER)
{
error(1, "identifier expected");
goto out;
}
if(reg_location >= reg_file_limit)
fatal_error("Register file address out of range");
if(list_loc < 0)
list_loc = reg_location;
sym = lookup_symbol(token_string, SYMTAB_GLOBAL);
if(sym != NULL)
{
if(sym->type != SYM_FORWARD)
{
error(0, "Symbol type conflict (%s)", token_string);
sym = NULL;
}
}
else
{
sym = add_symbol(token_string, SYMTAB_GLOBAL);
}
if(sym != NULL)
{
sym->type = SYM_DEFINED;
sym->type2 = SYMT_REGFILE;
sym->v.value = reg_location;
reg_location++;
}
get_token();
if(token_type == TOK_COMMA)
get_token();
}
list_flags = (list_loc >= 0 ? LIST_LOC : 0);
write_listing_line(0);
get_token(); /* skip the newline */
}
get_token(); /* skip KW_ENDC token */
out:
break;
case KW_EDATA:
if(pic_instr_set == PIC_NONE)
fatal_error("PIC device type not set");
if(data_eeprom_size == 0)
{
error(1, "PIC%s does not have data EEPROM", pic_name);
continue;
}
for(;;)
{
if(token_type == TOK_STRCONST)
{
for(cp = token_string; *cp != '\0'; cp++)
gen_edata((int)((unsigned char)(*cp)));
get_token();
}
else
{
if(get_expression(&val) != OK)
break;
if(val < 0 || val > 0xff)
{
error(0, "Data EEPROM byte out of range");
}
else
{
gen_edata(val);
}
}
if(token_type != TOK_COMMA)
break;
get_token();
}
break;
case KW_CONFIG:
if(pic_instr_set == PIC_NONE)
fatal_error("PIC device type not set");
parse_config();
list_flags = LIST_PTR;
list_ptr = &config_fuses;
list_len = 1;
break;
/* Device type */
case KW_DEVICE:
if(token_type != TOK_IDENTIFIER && token_type != TOK_STRCONST)
{
error(1, "DEVICE requires a device type");
continue;
}
do_device_include(token_string);
break;
case KW_SET_PIC_TYPE:
if(pic_instr_set != PIC_NONE)
fatal_error("PIC type already set");
if(token_type != TOK_STRCONST)
fatal_error("Invalid set_pic_type parameters");
strncpy(pic_name, token_string, sizeof(pic_name));
pic_name[sizeof(pic_name)-1] = '\0';
get_token();
if(token_type != TOK_COMMA)
fatal_error("Invalid set_pic_type parameters");
get_token();
if(token_type != TOK_STRCONST)
fatal_error("Invalid set_pic_type parameters");
if(strcasecmp(token_string, "12-bit") == 0 ||
strcasecmp(token_string, "12bit") == 0)
{
pic_instr_set = PIC12BIT;
}
else if(strcasecmp(token_string, "14-bit") == 0 ||
strcasecmp(token_string, "14bit") == 0)
{
pic_instr_set = PIC14BIT;
}
else if(strcasecmp(token_string, "16-bit") == 0 ||
strcasecmp(token_string, "16bit") == 0)
{
pic_instr_set = PIC16BIT;
}
else if(strcasecmp(token_string, "16-bit-no-mul") == 0 ||
strcasecmp(token_string, "16bit_nomul") == 0)
{
pic_instr_set = PIC16BIT_NOMUL;
}
else
{
fatal_error("Invalid set_pic_type parameters");
}
get_token();
cp = pic_name;
if(strncasecmp(cp, "PIC", 3) == 0)
cp += 3;
p_snprintf(symname, sizeof symname, "__%s", cp);
sym = add_symbol(symname, SYMTAB_GLOBAL);
sym->type = SYM_DEFINED;
sym->type2 = SYMT_CONSTANT;
sym->v.value = 1;
sym = lookup_symbol("__progmem_size", SYMTAB_GLOBAL);
if(sym != NULL && (sym->type == SYM_SET || sym->type == SYM_DEFINED))
{
if(sym->v.value < 0)
fatal_error("Invalid __progmem_size");
prog_mem_size = sym->v.value;
}
sym = lookup_symbol("__data_eeprom_size", SYMTAB_GLOBAL);
if(sym != NULL && (sym->type == SYM_SET || sym->type == SYM_DEFINED))
{
if(sym->v.value < 0)
fatal_error("Invalid __data_eeprom_size");
data_eeprom_size = sym->v.value;
}
break;
/* PIC ID */
case KW_PICID:
if(pic_instr_set == PIC_NONE)
fatal_error("PIC device type not set");
if(pic_id[0] != INVALID_ID)
{
error(1, "Multiple ID definitions");
continue;
}
/*
* should check if ID is really allowed for all PIC types.
* Microchip 1994 databook specfically says that ID is not
* implemented on PIC16C6x/74... but 1995/96 databook
* has different information about that...
*
* But seems that the 16-bit PICs don't have an id
*
*/
if(pic_instr_set == PIC16BIT)
{
error(1, "ID locations not supported on PIC%s", pic_name);
continue;
}
for(t = 0;;)
{
if(get_expression(&val) != OK)
continue;
if(val < 0 || val > 0x3fff)
{
error(0, "PIC ID value out of range");
}
else
{
if(t >= 4)
{
error(1, "PIC ID too long (max 4 bytes)");
continue;
}
pic_id[t] = (pic_instr_t)val;
}
t++;
if(token_type != TOK_COMMA)
break;
get_token();
}
if(t > 0)
{
list_flags = LIST_PTR;
list_len = t;
list_ptr = pic_id;
while(t < 4)
pic_id[t++] = 0x3fff;
}
break;
/* mnemonics */
default:
if(pic_instr_set == PIC_NONE)
fatal_error("PIC device type not set");
switch(pic_instr_set)
{
case PIC12BIT:
t = assemble_12bit_mnemonic(op);
break;
case PIC14BIT:
default:
t = assemble_14bit_mnemonic(op);
break;
case PIC16BIT_NOMUL:
case PIC16BIT:
t = assemble_16bit_mnemonic(op);
break;
}
if(t != OK)
continue;
break;
}
line_end:
write_listing_line(0);
line_end2:
if(token_type == TOK_EOF)
continue;
if(token_type != TOK_NEWLINE)
{
error(0, "Extraneous characters after a valid source line");
{
#if 0
fprintf(stderr, "token_type==%d, token_string='%s'\n",
token_type, token_string);/*XXX*/
#endif
}
skip_eol();
}
get_token();
} /* while(token_type != TOK_EOF) */
/*
* Close all open source files
* (only really necessary if END has been used)
*/
while(current_file != NULL)
end_include();
if(local_level > 0)
error(0, "LOCAL not terminated with ENDLOCAL");
apply_patches(global_patch_list);
}
static void
usage(void)
{
fputs("Usage: picasm "
"[-o<objname>] [-l<listfile>] [-s] [-ihx8m/ihx16] [-i<incdir>]\n"
" "
"[-pic<device>] [-d<sym>[=<value>]] [-w[n]] <filename>\n", stderr);
exit(EXIT_FAILURE);
}
/*
* main program
*/
int
main(int argc, char *argv[])
{
static char in_filename[256], out_filename[256],
list_filename[256], sym_filename[256];
static int out_format = IHX8M;
int listing = 0, symdump = 0;
char *p;
time_t ti;
struct tm *tm;
char *cmdline_pic_type = NULL;
pic_instr_set = PIC_NONE;
out_filename[0] = '\0';
list_filename[0] = '\0';
sym_filename[0] = '\0';
warnlevel = 0;
init_assembler();
while(argc > 1 && argv[1][0] == '-')
{
switch(argv[1][1])
{
case 'o': /* output file name */
if(argv[1][2] != '\0')
{
p_strcpy(out_filename, &argv[1][2], sizeof out_filename);
}
else
{
if(argc < 3)
{
fputs("-o option requires a file name\n", stderr);
exit(EXIT_FAILURE);
}
p_strcpy(out_filename, argv[2], sizeof out_filename);
argc--;
argv++;
}
break;
case 'i': case 'I': /* output hex format (ihx8m/ihx16) */
if(strcasecmp(&argv[1][1], "ihx8m") == 0)
{
out_format = IHX8M;
}
else if(strcasecmp(&argv[1][1], "ihx16") == 0)
{
out_format = IHX16;
}
else
{
if(argv[1][2] != '\0')
{
add_include_path(&argv[1][2]);
}
else
{
if(argc < 3)
usage();
add_include_path(argv[2]);
argc--;
argv++;
}
}
break;
case 'd': /* define symbols */
{
char symname[256];
long val = 1;
struct symbol *sym;
p = &argv[1][2];
if(!isalpha((unsigned char)(*p)) && *p != '\0')
{
badsym:
fputs("Bad symbol name (-d option)\n", stderr);
exit(EXIT_FAILURE);
}
while(*p != '\0' && *p != '=')
{
if(!isalnum((unsigned char)(*p)) && *p != '_')
goto badsym;
if(p >= &argv[1][2] + 255)
{
fputs("Symbol too long (-d option)\n", stderr);
exit(EXIT_FAILURE);
}
p++;
}
if(p == &argv[1][2])
goto badsym;
memcpy(symname, &argv[1][2], p - &argv[1][2]);
symname[p - &argv[1][2]] = '\0';
if(*p == '=')
{
char *ep;
val = strtol(p+1, &ep, 0);
if(*ep != '\0')
{
fputs("Invalid numeric value (-d option)\n", stderr);
exit(EXIT_FAILURE);
}
}
if(lookup_symbol(symname, SYMTAB_GLOBAL) != NULL)
{
fprintf(stderr, "Multiply defined symbol '%s'\n", symname);
exit(EXIT_FAILURE);
}
sym = add_symbol(symname, SYMTAB_GLOBAL);
sym->type = SYM_DEFINED;
sym->type2 = SYMT_CONSTANT;
sym->v.value = val;
}
break;
case 'p': case 'P': /* PIC type */
if(!((argv[1][2] == 'i' || argv[1][2] == 'I') &&
(argv[1][3] == 'c' || argv[1][3] == 'C')))
usage();
cmdline_pic_type = &argv[1][1];
break;
case 'l': /* listing/list filename */
listing = 1;
if(argv[1][2] != '\0')
p_strcpy(list_filename, &argv[1][2], sizeof list_filename);
break;
case 's':
symdump++;
break;
case 'w': /* warning mode (gives some more warnings) */
if(argv[1][2] != '\0')
{
warnlevel = atoi(&argv[1][2]);
}
else
{
warnlevel = 1;
}
break;
case 'v': /* version info */
fprintf(stderr,
"12/14/16-bit PIC assembler " VERSION
" -- Copyright 1995-2004 Timo Rossi\n");
break;
case '-': /* end of option list */
case '\0':
argc--;
argv++;
goto opt_done;
default:
usage();
}
argc--;
argv++;
}
opt_done:
if(argc != 2)
usage();
#ifdef BUILTIN_INCLUDE1
add_include_path(BUILTIN_INCLUDE1);
#endif
#ifdef BUILTIN_INCLUDE2
add_include_path(BUILTIN_INCLUDE2);
#endif
strncpy(in_filename, argv[1], sizeof(in_filename)-1);
if(strchr(in_filename, '.') == NULL)
p_strcat(in_filename, ".asm", sizeof in_filename);
if(out_filename[0] == '\0')
{
p_strcpy(out_filename, in_filename, sizeof out_filename);
if((p = strrchr(out_filename, '.')) != NULL)
*p = '\0';
}
if(strchr(out_filename, '.') == NULL)
p_strcat(out_filename, ".hex", sizeof out_filename);
list_fp = NULL;
if(listing)
{
if(list_filename[0] == '\0')
{
p_strcpy(list_filename, in_filename, sizeof list_filename);
if((p = strrchr(list_filename, '.')) != NULL)
*p = '\0';
p_strcat(list_filename, ".lst", sizeof list_filename);
}
if((list_fp = fopen(list_filename, "w")) == NULL)
fatal_error("Can't create listing file '%s': %s",
list_filename, strerror(errno));
ti = time(NULL);
tm = localtime(&ti);
fprintf(list_fp, "** 12/14-bit PIC assembler " VERSION "\n");
fprintf(list_fp, "** %s assembled %s\n",
in_filename, asctime(tm));
}
assembler(in_filename, cmdline_pic_type);
if(errors == 0)
{
if(pic_instr_set != PIC_NONE && code_generated)
write_output(out_filename, out_format);
else
fputs("No code generated\n", stderr);
}
else
{
fprintf(stderr, "%d %s found\n",
errors, errors == 1 ? "error" : "errors");
}
if(warnings != 0)
{
fprintf(stderr, "%d %s\n",
warnings, warnings == 1 ? "warning" : "warnings");
}
if(list_fp)
{
if(symdump)
dump_symtab(list_fp, symdump);
fclose(list_fp);
}
return errors ? EXIT_FAILURE : EXIT_SUCCESS;
}
syntax highlighted by Code2HTML, v. 0.9.1