//-----------------------------------------------------------------------------
// 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/>.
//------
//
// An AVR assembler, for our own internal use, plus routines to generate
// code from the ladder logic structure, plus routines to generate the
// runtime needed to schedule the cycles.
// Jonathan Westhues, Oct 2004
//-----------------------------------------------------------------------------
#include <linuxUI.h>
#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h>
#include <limits.h>
#include <math.h>

#include "ldmicro.h"
#include "intcode.h"

// not complete; just what I need
typedef enum AvrOpTag {
    OP_VACANT,
    OP_ADC,
    OP_ADD,
    OP_ASR,
    OP_BRCC,
    OP_BRCS,
    OP_BREQ,
    OP_BRGE,
    OP_BRLO,
    OP_BRLT,
    OP_BRNE,
    OP_CBR,
    OP_CLC,
    OP_CLR,
    OP_COM,
    OP_CP,
    OP_CPC,
    OP_DEC,
    OP_EOR,
    OP_ICALL,
    OP_IJMP,
    OP_INC,
    OP_LDI,
    OP_LD_X,
    OP_MOV,
    OP_OUT,
    OP_RCALL,
    OP_RET,
    OP_RETI,
    OP_RJMP,
    OP_ROR,
    OP_SEC,
    OP_SBC,
    OP_SBCI,
    OP_SBR,
    OP_SBRC,
    OP_SBRS,
    OP_ST_X,
    OP_SUB,
    OP_SUBI,
    OP_TST,
    OP_WDR,
} AvrOp;

typedef struct AvrInstructionTag {
    AvrOp       op;
    DWORD       arg1;
    DWORD       arg2;
} AvrInstruction;

#define MAX_PROGRAM_LEN 128*1024
static AvrInstruction AvrProg[MAX_PROGRAM_LEN];
static DWORD AvrProgWriteP;

// For yet unresolved references in jumps
static DWORD FwdAddrCount;

// Fancier: can specify a forward reference to the high or low octet of a
// 16-bit address, which is useful for indirect jumps.
#define FWD_LO(x) ((x) | 0x20000000)
#define FWD_HI(x) ((x) | 0x40000000)

// Address to jump to when we finish one PLC cycle
static DWORD BeginningOfCycleAddr;

// Address of the multiply subroutine, and whether we will have to include it
static DWORD MultiplyAddress;
static BOOL MultiplyUsed;
// and also divide
static DWORD DivideAddress;
static BOOL DivideUsed;

// For EEPROM: we queue up characters to send in 16-bit words (corresponding
// to the integer variables), but we can actually just program 8 bits at a
// time, so we need to store the high byte somewhere while we wait.
static DWORD EepromHighByte;
static DWORD EepromHighByteWaitingAddr;
static int EepromHighByteWaitingBit;

// Some useful registers, unfortunately many of which are in different places
// on different AVRs! I consider this a terrible design choice by Atmel.
static DWORD REG_TIMSK;
static DWORD REG_TIFR;
#define REG_OCR1AH  0x4b
#define REG_OCR1AL  0x4a
#define REG_TCCR1A  0x4f
#define REG_TCCR1B  0x4e
#define REG_SPH     0x5e
#define REG_SPL     0x5d
#define REG_ADMUX   0x27
#define REG_ADCSRA  0x26
#define REG_ADCL    0x24
#define REG_ADCH    0x25

static DWORD REG_UBRRH;
static DWORD REG_UBRRL;
static DWORD REG_UCSRB;
static DWORD REG_UCSRA;
static DWORD REG_UDR;

#define REG_OCR2    0x43
#define REG_TCCR2   0x45

#define REG_EEARH   0x3f
#define REG_EEARL   0x3e
#define REG_EEDR    0x3d
#define REG_EECR    0x3c


static int IntPc;

static void CompileFromIntermediate(void);

//-----------------------------------------------------------------------------
// Wipe the program and set the write pointer back to the beginning. Also
// flush all the state of the register allocators etc.
//-----------------------------------------------------------------------------
static void WipeMemory(void)
{
    memset(AvrProg, 0, sizeof(AvrProg));
    AvrProgWriteP = 0;
}

//-----------------------------------------------------------------------------
// Store an instruction at the next spot in program memory.  Error condition
// if this spot is already filled. We don't actually assemble to binary yet;
// there may be references to resolve.
//-----------------------------------------------------------------------------
static void Instruction(AvrOp op, DWORD arg1, DWORD arg2)
{
    if(AvrProg[AvrProgWriteP].op != OP_VACANT) oops();

    AvrProg[AvrProgWriteP].op = op;
    AvrProg[AvrProgWriteP].arg1 = arg1;
    AvrProg[AvrProgWriteP].arg2 = arg2;
    AvrProgWriteP++;
}

//-----------------------------------------------------------------------------
// Allocate a unique descriptor for a forward reference. Later that forward
// reference gets assigned to an absolute address, and we can go back and
// fix up the reference.
//-----------------------------------------------------------------------------
static DWORD AllocFwdAddr(void)
{
    FwdAddrCount++;
    return 0x80000000 | FwdAddrCount;
}

//-----------------------------------------------------------------------------
// Go back and fix up the program given that the provided forward address
// corresponds to the next instruction to be assembled.
//-----------------------------------------------------------------------------
static void FwdAddrIsNow(DWORD addr)
{
    if(!(addr & 0x80000000)) oops();

    DWORD i;
    for(i = 0; i < AvrProgWriteP; i++) {
        if(AvrProg[i].arg1 == addr) {
            AvrProg[i].arg1 = AvrProgWriteP;
        } else if(AvrProg[i].arg2 == FWD_LO(addr)) {
            AvrProg[i].arg2 = (AvrProgWriteP & 0xff);
        } else if(AvrProg[i].arg2 == FWD_HI(addr)) {
            AvrProg[i].arg2 = AvrProgWriteP >> 8;
        }
    }
}

//-----------------------------------------------------------------------------
// Given an opcode and its operands, assemble the 16-bit instruction for the
// AVR. Check that the operands do not have more bits set than is meaningful;
// it is an internal error if they do not. Needs to know what address it is
// being assembled to so that it generate relative jumps; internal error if
// a relative jump goes out of range.
//-----------------------------------------------------------------------------
static DWORD Assemble(DWORD addrAt, AvrOp op, DWORD arg1, DWORD arg2)
{
#define CHECK(v, bits) if((v) != ((v) & ((1 << (bits))-1))) oops()
    switch(op) {
        case OP_ASR:
            CHECK(arg1, 5); CHECK(arg2, 0);
            return (9 << 12) | (2 << 9) | (arg1 << 4) | 5;

        case OP_ROR:
            CHECK(arg1, 5); CHECK(arg2, 0);
            return (9 << 12) | (2 << 9) | (arg1 << 4) | 7;

        case OP_ADD:
            CHECK(arg1, 5); CHECK(arg2, 5);
            return (3 << 10) | ((arg2 & 0x10) << 5) | (arg1 << 4) |
                (arg2 & 0x0f);

        case OP_ADC:
            CHECK(arg1, 5); CHECK(arg2, 5);
            return (7 << 10) | ((arg2 & 0x10) << 5) | (arg1 << 4) |
                (arg2 & 0x0f);

        case OP_EOR:
            CHECK(arg1, 5); CHECK(arg2, 5);
            return (9 << 10) | ((arg2 & 0x10) << 5) | (arg1 << 4) |
                (arg2 & 0x0f);

        case OP_SUB:
            CHECK(arg1, 5); CHECK(arg2, 5);
            return (6 << 10) | ((arg2 & 0x10) << 5) | (arg1 << 4) |
                (arg2 & 0x0f);

        case OP_SBC:
            CHECK(arg1, 5); CHECK(arg2, 5);
            return (2 << 10) | ((arg2 & 0x10) << 5) | (arg1 << 4) |
                (arg2 & 0x0f);

        case OP_CP:
            CHECK(arg1, 5); CHECK(arg2, 5);
            return (5 << 10) | ((arg2 & 0x10) << 5) | (arg1 << 4) |
                (arg2 & 0x0f);

        case OP_CPC:
            CHECK(arg1, 5); CHECK(arg2, 5);
            return (1 << 10) | ((arg2 & 0x10) << 5) | (arg1 << 4) |
                (arg2 & 0x0f);

        case OP_COM:
            CHECK(arg1, 5); CHECK(arg2, 0);
            return (9 << 12) | (2 << 9) | (arg1 << 4);

        case OP_SBR:
            CHECK(arg1, 5); CHECK(arg2, 8);
            if(!(arg1 & 0x10)) oops();
            arg1 &= ~0x10;
            return (6 << 12) | ((arg2 & 0xf0) << 4) | (arg1 << 4) |
                (arg2 & 0x0f);

        case OP_CBR:
            CHECK(arg1, 5); CHECK(arg2, 8);
            if(!(arg1 & 0x10)) oops();
            arg1 &= ~0x10;
            arg2 = ~arg2;
            return (7 << 12) | ((arg2 & 0xf0) << 4) | (arg1 << 4) |
                (arg2 & 0x0f);

        case OP_INC:
            CHECK(arg1, 5); CHECK(arg2, 0);
            return (0x4a << 9) | (arg1 << 4) | 3;

        case OP_DEC:
            CHECK(arg1, 5); CHECK(arg2, 0);
            return (0x4a << 9) | (arg1 << 4) | 10;

        case OP_SUBI:
            CHECK(arg1, 5); CHECK(arg2, 8);
            if(!(arg1 & 0x10)) oops();
            arg1 &= ~0x10;
            return (5 << 12) | ((arg2 & 0xf0) << 4) | (arg1 << 4) |
                (arg2 & 0x0f);

        case OP_SBCI:
            CHECK(arg1, 5); CHECK(arg2, 8);
            if(!(arg1 & 0x10)) oops();
            arg1 &= ~0x10;
            return (4 << 12) | ((arg2 & 0xf0) << 4) | (arg1 << 4) |
                (arg2 & 0x0f);

        case OP_TST:
            CHECK(arg1, 5); CHECK(arg2, 0);
            return (8 << 10) | ((arg1 & 0x10) << 4) | ((arg1 & 0x10) << 5) |
                ((arg1 & 0xf) << 4) | (arg1 & 0xf);

        case OP_SEC:
            CHECK(arg1, 0); CHECK(arg2, 0);
            return 0x9408;

        case OP_CLC:
            CHECK(arg1, 0); CHECK(arg2, 0);
            return 0x9488;

        case OP_IJMP:
            CHECK(arg1, 0); CHECK(arg2, 0);
            return 0x9409;

        case OP_ICALL:
            CHECK(arg1, 0); CHECK(arg2, 0);
            return 0x9509;

        case OP_RJMP:
            CHECK(arg2, 0);
            arg1 = arg1 - addrAt - 1;
            if(((int)arg1) > 2047 || ((int)arg1) < -2048) oops();
            arg1 &= (4096-1);
            return (12 << 12) | arg1;

        case OP_RCALL:
            CHECK(arg2, 0);
            arg1 = arg1 - addrAt - 1;
            if(((int)arg1) > 2047 || ((int)arg1) < -2048) oops();
            arg1 &= (4096-1);
            return (13 << 12) | arg1;

        case OP_RETI:
            return 0x9518;

        case OP_RET:
            return 0x9508;

        case OP_SBRC:
            CHECK(arg1, 5); CHECK(arg2, 3);
            return (0x7e << 9) | (arg1 << 4) | arg2;

        case OP_SBRS:
            CHECK(arg1, 5); CHECK(arg2, 3);
            return (0x7f << 9) | (arg1 << 4) | arg2;

        case OP_BREQ:
            CHECK(arg2, 0);
            arg1 = arg1 - addrAt - 1;
            if(((int)arg1) > 63 || ((int)arg1) < -64) oops();
            arg1 &= (128-1);
            return (0xf << 12) | (arg1 << 3) | 1;

        case OP_BRNE:
            CHECK(arg2, 0);
            arg1 = arg1 - addrAt - 1;
            if(((int)arg1) > 63 || ((int)arg1) < -64) oops();
            arg1 &= (128-1);
            return (0xf << 12) | (1 << 10) | (arg1 << 3) | 1;

        case OP_BRLO:
            CHECK(arg2, 0);
            arg1 = arg1 - addrAt - 1;
            if(((int)arg1) > 63 || ((int)arg1) < -64) oops();
            arg1 &= (128-1);
            return (0xf << 12) | (arg1 << 3);

        case OP_BRGE:
            CHECK(arg2, 0);
            arg1 = arg1 - addrAt - 1;
            if(((int)arg1) > 63 || ((int)arg1) < -64) oops();
            arg1 &= (128-1);
            return (0xf << 12) | (1 << 10) | (arg1 << 3) | 4;

        case OP_BRLT:
            CHECK(arg2, 0);
            arg1 = arg1 - addrAt - 1;
            if(((int)arg1) > 63 || ((int)arg1) < -64) oops();
            arg1 &= (128-1);
            return (0xf << 12) | (arg1 << 3) | 4;

        case OP_BRCC:
            CHECK(arg2, 0);
            arg1 = arg1 - addrAt - 1;
            if(((int)arg1) > 63 || ((int)arg1) < -64) oops();
            arg1 &= (128-1);
            return (0xf << 12) | (1 << 10) | (arg1 << 3);

        case OP_BRCS:
            CHECK(arg2, 0);
            arg1 = arg1 - addrAt - 1;
            if(((int)arg1) > 63 || ((int)arg1) < -64) oops();
            arg1 &= (128-1);
            return (0xf << 12) | (arg1 << 3);

        case OP_MOV:
            CHECK(arg1, 5); CHECK(arg2, 5);
            return (0xb << 10) | ((arg2 & 0x10) << 5) | (arg1 << 4) |
                (arg2 & 0x0f);

        case OP_LDI:
            CHECK(arg1, 5); CHECK(arg2, 8);
            if(!(arg1 & 0x10)) oops();
            arg1 &= ~0x10;
            return (0xe << 12) | ((arg2 & 0xf0) << 4) | (arg1 << 4) |
                (arg2 & 0x0f);

        case OP_LD_X:
            CHECK(arg1, 5); CHECK(arg2, 0);
            return (9 << 12) | (arg1 << 4) | 12;

        case OP_ST_X:
            CHECK(arg1, 5); CHECK(arg2, 0);
            return (0x49 << 9) | (arg1 << 4) | 12;

        case OP_WDR:
            CHECK(arg1, 0); CHECK(arg2, 0);
            return 0x95a8;

        default:
            oops();
            break;
    }
}

//-----------------------------------------------------------------------------
// Write an intel IHEX format description of the program assembled so far.
// This is where we actually do the assembly to binary format.
//-----------------------------------------------------------------------------
static void WriteHexFile(FILE *f)
{
    BYTE soFar[16];
    int soFarCount = 0;
    DWORD soFarStart = 0;

    DWORD i;
    for(i = 0; i < AvrProgWriteP; i++) {
        DWORD w = Assemble(i, AvrProg[i].op, AvrProg[i].arg1, AvrProg[i].arg2);

        if(soFarCount == 0) soFarStart = i;
        soFar[soFarCount++] = (BYTE)(w & 0xff);
        soFar[soFarCount++] = (BYTE)(w >> 8);

        if(soFarCount >= 0x10 || i == (AvrProgWriteP-1)) {
            StartIhex(f);
            WriteIhex(f, soFarCount);
            WriteIhex(f, (BYTE)((soFarStart*2) >> 8));
            WriteIhex(f, (BYTE)((soFarStart*2) & 0xff));
            WriteIhex(f, 0x00);
            int j;
            for(j = 0; j < soFarCount; j++) {
                WriteIhex(f, soFar[j]);
            }
            FinishIhex(f);
            soFarCount = 0;
        }
    }

    // end of file record
    fprintf(f, ":00000001FF\n");
}

//-----------------------------------------------------------------------------
// Make sure that the given address is loaded in the X register; might not
// have to update all of it.
//-----------------------------------------------------------------------------
static void LoadXAddr(DWORD addr)
{
    Instruction(OP_LDI, 27, (addr >> 8));
    Instruction(OP_LDI, 26, (addr & 0xff));
}

//-----------------------------------------------------------------------------
// Generate code to write an 8-bit value to a particular register.
//-----------------------------------------------------------------------------
static void WriteMemory(DWORD addr, BYTE val)
{
    LoadXAddr(addr);
    // load r16 with the data
    Instruction(OP_LDI, 16, val);
    // do the store
    Instruction(OP_ST_X, 16, 0);
}

//-----------------------------------------------------------------------------
// Copy just one bit from one place to another.
//-----------------------------------------------------------------------------
static void CopyBit(DWORD addrDest, int bitDest, DWORD addrSrc, int bitSrc)
{
    LoadXAddr(addrSrc); Instruction(OP_LD_X, 16, 0);
    LoadXAddr(addrDest); Instruction(OP_LD_X, 17, 0);
    Instruction(OP_SBRS, 16, bitSrc);
    Instruction(OP_CBR, 17, (1 << bitDest));
    Instruction(OP_SBRC, 16, bitSrc);
    Instruction(OP_SBR, 17, (1 << bitDest));

    Instruction(OP_ST_X, 17, 0);
}

//-----------------------------------------------------------------------------
// Execute the next instruction only if the specified bit of the specified
// memory location is clear (i.e. skip if set).
//-----------------------------------------------------------------------------
static void IfBitClear(DWORD addr, int bit)
{
    LoadXAddr(addr);
    Instruction(OP_LD_X, 16, 0);
    Instruction(OP_SBRS, 16, bit);
}

//-----------------------------------------------------------------------------
// Execute the next instruction only if the specified bit of the specified
// memory location is set (i.e. skip if clear).
//-----------------------------------------------------------------------------
static void IfBitSet(DWORD addr, int bit)
{
    LoadXAddr(addr);
    Instruction(OP_LD_X, 16, 0);
    Instruction(OP_SBRC, 16, bit);
}

//-----------------------------------------------------------------------------
// Set a given bit in an arbitrary (not necessarily I/O memory) location in
// memory.
//-----------------------------------------------------------------------------
static void SetBit(DWORD addr, int bit)
{
    LoadXAddr(addr);
    Instruction(OP_LD_X, 16, 0);
    Instruction(OP_SBR, 16, (1 << bit));
    Instruction(OP_ST_X, 16, 0);
}

//-----------------------------------------------------------------------------
// Clear a given bit in an arbitrary (not necessarily I/O memory) location in
// memory.
//-----------------------------------------------------------------------------
static void ClearBit(DWORD addr, int bit)
{
    LoadXAddr(addr);
    Instruction(OP_LD_X, 16, 0);
    Instruction(OP_CBR, 16, (1 << bit));
    Instruction(OP_ST_X, 16, 0);
}

//-----------------------------------------------------------------------------
// Configure AVR 16-bit Timer1 to do the timing for us.
//-----------------------------------------------------------------------------
static void ConfigureTimer1(int cycleTimeMicroseconds)
{
    int divisor = 1;
    int countsPerCycle;
    while(divisor <= 1024) {
        int timerRate = (Prog.mcuClock / divisor); // hertz
        double timerPeriod = 1e6 / timerRate; // timer period, us
        countsPerCycle = ((int)(cycleTimeMicroseconds / timerPeriod)) - 1;

        if(countsPerCycle < 1000) {
            Error(_("Cycle time too fast; increase cycle time, or use faster "
                "crystal."));
            CompileError();
        } else if(countsPerCycle > 0xffff) {
            if(divisor >= 1024) {
                Error(
                    _("Cycle time too slow; decrease cycle time, or use slower "
                    "crystal."));
                CompileError();
            }
        } else {
            break;
        }

        if(divisor == 1) divisor = 8;
        else if(divisor == 8) divisor = 64;
        else if(divisor == 64) divisor = 256;
        else if(divisor == 256) divisor = 1024;
    }
    WriteMemory(REG_TCCR1A, 0x00); // WGM11=0, WGM10=0

    int csn;
    switch(divisor) {
        case    1: csn = 1; break;
        case    8: csn = 2; break;
        case   64: csn = 3; break;
        case  256: csn = 4; break;
        case 1024: csn = 5; break;
        default: oops();
    }
    
    WriteMemory(REG_TCCR1B, (1<<3) | csn); // WGM13=0, WGM12=1

    // `the high byte must be written before the low byte'
    WriteMemory(REG_OCR1AH, (countsPerCycle - 1) >> 8);
    WriteMemory(REG_OCR1AL, (countsPerCycle - 1) & 0xff);
    
    // Okay, so many AVRs have a register called TIFR, but the meaning of
    // the bits in that register varies from device to device...
    if(strcmp(Prog.mcu->mcuName, "Atmel AVR ATmega162 40-PDIP")==0) {
        WriteMemory(REG_TIMSK, (1 << 6));
    } else {
        WriteMemory(REG_TIMSK, (1 << 4));
    }
}

//-----------------------------------------------------------------------------
// Write the basic runtime. We set up our reset vector, configure all the
// I/O pins, then set up the timer that does the cycling. Next instruction
// written after calling WriteRuntime should be first instruction of the
// timer loop (i.e. the PLC logic cycle).
//-----------------------------------------------------------------------------
static void WriteRuntime(void)
{
    DWORD resetVector = AllocFwdAddr();

    int i;
    Instruction(OP_RJMP, resetVector, 0);       // $0000, RESET
    for(i = 0; i < 34; i++)
        Instruction(OP_RETI, 0, 0);

    FwdAddrIsNow(resetVector);

    // set up the stack, which we use only when we jump to multiply/divide
    // routine
    WORD topOfMemory = (WORD)Prog.mcu->ram[0].start + Prog.mcu->ram[0].len - 1;
    WriteMemory(REG_SPH, topOfMemory >> 8);
    WriteMemory(REG_SPL, topOfMemory & 0xff);

    // zero out the memory used for timers, internal relays, etc.
    LoadXAddr(Prog.mcu->ram[0].start + Prog.mcu->ram[0].len);
    Instruction(OP_LDI, 16, 0);
    Instruction(OP_LDI, 18, (Prog.mcu->ram[0].len) & 0xff);
    Instruction(OP_LDI, 19, (Prog.mcu->ram[0].len) >> 8);

    DWORD loopZero = AvrProgWriteP;
    Instruction(OP_SUBI, 26, 1);
    Instruction(OP_SBCI, 27, 0);
    Instruction(OP_ST_X, 16, 0);
    Instruction(OP_SUBI, 18, 1);
    Instruction(OP_SBCI, 19, 0);
    Instruction(OP_TST, 18, 0);
    Instruction(OP_BRNE, loopZero, 0);
    Instruction(OP_TST, 19, 0);
    Instruction(OP_BRNE, loopZero, 0);
    

    // set up I/O pins
    BYTE isInput[MAX_IO_PORTS], isOutput[MAX_IO_PORTS];
    BuildDirectionRegisters(isInput, isOutput);

    if(UartFunctionUsed()) {
        if(Prog.baudRate == 0) {
            Error(_("Zero baud rate not possible."));
            return;
        }

        // bps = Fosc/(16*(X+1))
        // bps*16*(X + 1) = Fosc
        // X = Fosc/(bps*16)-1
        // and round, don't truncate
        int divisor = (Prog.mcuClock + Prog.baudRate*8)/(Prog.baudRate*16) - 1;

        double actual = Prog.mcuClock/(16.0*(divisor+1));
        double percentErr = 100*(actual - Prog.baudRate)/Prog.baudRate;

        if(fabs(percentErr) > 2) {
            ComplainAboutBaudRateError(divisor, actual, percentErr);
        }
        if(divisor > 4095) ComplainAboutBaudRateOverflow();
        
        WriteMemory(REG_UBRRH, divisor >> 8);
        WriteMemory(REG_UBRRL, divisor & 0xff);
        WriteMemory(REG_UCSRB, (1 << 4) | (1 << 3)); // RXEN, TXEN

        for(i = 0; i < Prog.mcu->pinCount; i++) {
            if(Prog.mcu->pinInfo[i].pin == Prog.mcu->uartNeeds.txPin) {
                McuIoPinInfo *iop = &(Prog.mcu->pinInfo[i]);
                isOutput[iop->port - 'A'] |= (1 << iop->bit);
                break;
            }
        }
        if(i == Prog.mcu->pinCount) oops();
    }

    if(PwmFunctionUsed()) {
        for(i = 0; i < Prog.mcu->pinCount; i++) {
            if(Prog.mcu->pinInfo[i].pin == Prog.mcu->pwmNeedsPin) {
                McuIoPinInfo *iop = &(Prog.mcu->pinInfo[i]);
                isOutput[iop->port - 'A'] |= (1 << iop->bit);
                break;
            }
        }
        if(i == Prog.mcu->pinCount) oops();
    }

    for(i = 0; Prog.mcu->dirRegs[i] != 0; i++) {
        if(Prog.mcu->dirRegs[i] == 0xff && Prog.mcu->outputRegs[i] == 0xff) {
            // skip this one, dummy entry for MCUs with I/O ports not
            // starting from A
        } else {
            WriteMemory(Prog.mcu->dirRegs[i], isOutput[i]);
            // turn on the pull-ups, and drive the outputs low to start
            WriteMemory(Prog.mcu->outputRegs[i], isInput[i]);
        }
    }


    ConfigureTimer1(Prog.cycleTime);

    // and now the generated PLC code will follow
    BeginningOfCycleAddr = AvrProgWriteP;

    // Okay, so many AVRs have a register called TIFR, but the meaning of
    // the bits in that register varies from device to device...
    int tifrBitForOCF1A;
    if(strcmp(Prog.mcu->mcuName, "Atmel AVR ATmega162 40-PDIP")==0) {
        tifrBitForOCF1A = 6;
    } else {
        tifrBitForOCF1A = 4;
    }

    DWORD now = AvrProgWriteP;
    IfBitClear(REG_TIFR, tifrBitForOCF1A);
    Instruction(OP_RJMP, now, 0);

    SetBit(REG_TIFR, tifrBitForOCF1A);

    Instruction(OP_WDR, 0, 0);
}

//-----------------------------------------------------------------------------
// Handle an IF statement. Flow continues to the first instruction generated
// by this function if the condition is true, else it jumps to the given
// address (which is an FwdAddress, so not yet assigned). Called with IntPc
// on the IF statement, returns with IntPc on the END IF.
//-----------------------------------------------------------------------------
static void CompileIfBody(DWORD condFalse)
{
    IntPc++;
    CompileFromIntermediate();
    if(IntCode[IntPc].op == INT_ELSE) {
        IntPc++;
        DWORD endBlock = AllocFwdAddr();
        Instruction(OP_RJMP, endBlock, 0);

        FwdAddrIsNow(condFalse);
        CompileFromIntermediate();
        FwdAddrIsNow(endBlock);
    } else {
        FwdAddrIsNow(condFalse);
    }

    if(IntCode[IntPc].op != INT_END_IF) oops();
}

//-----------------------------------------------------------------------------
// Call a subroutine, using either an rcall or an icall depending on what
// the processor supports or requires.
//-----------------------------------------------------------------------------
static void CallSubroutine(DWORD addr)
{
    if(Prog.mcu->avrUseIjmp) {
        Instruction(OP_LDI, 30, FWD_LO(addr));
        Instruction(OP_LDI, 31, FWD_HI(addr));
        Instruction(OP_ICALL, 0, 0);
    } else {
        Instruction(OP_RCALL, addr, 0);
    }
}

//-----------------------------------------------------------------------------
// Compile the intermediate code to AVR native code.
//-----------------------------------------------------------------------------
static void CompileFromIntermediate(void)
{   
    DWORD addr, addr2;
    int bit, bit2;
    DWORD addrl, addrh;
    DWORD addrl2, addrh2;

    for(; IntPc < IntCodeLen; IntPc++) {
        IntOp *a = &IntCode[IntPc];
        switch(a->op) {
            case INT_SET_BIT:   
                MemForSingleBit(a->name1, FALSE, &addr, &bit);
                SetBit(addr, bit);
                break;

            case INT_CLEAR_BIT:
                MemForSingleBit(a->name1, FALSE, &addr, &bit);
                ClearBit(addr, bit);
                break;

            case INT_COPY_BIT_TO_BIT:
                MemForSingleBit(a->name1, FALSE, &addr, &bit);
                MemForSingleBit(a->name2, FALSE, &addr2, &bit2);
                CopyBit(addr, bit, addr2, bit2);
                break;

            case INT_SET_VARIABLE_TO_LITERAL:
                MemForVariable(a->name1, &addrl, &addrh);
                WriteMemory(addrl, a->literal & 0xff);
                WriteMemory(addrh, a->literal >> 8);
                break;

            case INT_INCREMENT_VARIABLE: {
                MemForVariable(a->name1, &addrl, &addrh);
                LoadXAddr(addrl);
                Instruction(OP_LD_X, 16, 0);
                LoadXAddr(addrh);
                Instruction(OP_LD_X, 17, 0);
                // increment
                Instruction(OP_INC, 16, 0);
                DWORD noCarry = AllocFwdAddr();
                Instruction(OP_BRNE, noCarry, 0);
                Instruction(OP_INC, 17, 0);
                FwdAddrIsNow(noCarry);
                // X is still addrh
                Instruction(OP_ST_X, 17, 0);
                LoadXAddr(addrl);
                Instruction(OP_ST_X, 16, 0);
                break;
            }
            case INT_IF_BIT_SET: {
                DWORD condFalse = AllocFwdAddr();
                MemForSingleBit(a->name1, TRUE, &addr, &bit);
                IfBitClear(addr, bit);
                Instruction(OP_RJMP, condFalse, 0);
                CompileIfBody(condFalse);
                break;
            }
            case INT_IF_BIT_CLEAR: {
                DWORD condFalse = AllocFwdAddr();
                MemForSingleBit(a->name1, TRUE, &addr, &bit);
                IfBitSet(addr, bit);
                Instruction(OP_RJMP, condFalse, 0);
                CompileIfBody(condFalse);
                break;
            }
            case INT_IF_VARIABLE_LES_LITERAL: {
                DWORD notTrue = AllocFwdAddr();

                MemForVariable(a->name1, &addrl, &addrh);
                LoadXAddr(addrl);
                Instruction(OP_LD_X, 16, 0);
                LoadXAddr(addrh);
                Instruction(OP_LD_X, 17, 0);

                Instruction(OP_LDI, 18, (a->literal & 0xff));
                Instruction(OP_LDI, 19, (a->literal >> 8));

                Instruction(OP_CP, 16, 18);
                Instruction(OP_CPC, 17, 19);
                Instruction(OP_BRGE, notTrue, 0);

                CompileIfBody(notTrue);
                break;
            }
            case INT_IF_VARIABLE_GRT_VARIABLE:
            case INT_IF_VARIABLE_EQUALS_VARIABLE: {
                DWORD notTrue = AllocFwdAddr();

                MemForVariable(a->name1, &addrl, &addrh);
                LoadXAddr(addrl);
                Instruction(OP_LD_X, 16, 0);
                LoadXAddr(addrh);
                Instruction(OP_LD_X, 17, 0);
                MemForVariable(a->name2, &addrl, &addrh);
                LoadXAddr(addrl);
                Instruction(OP_LD_X, 18, 0);
                LoadXAddr(addrh);
                Instruction(OP_LD_X, 19, 0);

                if(a->op == INT_IF_VARIABLE_EQUALS_VARIABLE) {
                    Instruction(OP_CP, 16, 18);
                    Instruction(OP_CPC, 17, 19);
                    Instruction(OP_BRNE, notTrue, 0);
                } else if(a->op == INT_IF_VARIABLE_GRT_VARIABLE) {
                    DWORD isTrue = AllocFwdAddr();

                    // true if op1 > op2
                    // false if op1 >= op2
                    Instruction(OP_CP, 18, 16);
                    Instruction(OP_CPC, 19, 17);
                    Instruction(OP_BRGE, notTrue, 0);
                } else oops();
                CompileIfBody(notTrue);
                break;
            }
            case INT_SET_VARIABLE_TO_VARIABLE:
                MemForVariable(a->name1, &addrl, &addrh);
                MemForVariable(a->name2, &addrl2, &addrh2);

                LoadXAddr(addrl2);
                Instruction(OP_LD_X, 16, 0);
                LoadXAddr(addrl);
                Instruction(OP_ST_X, 16, 0);

                LoadXAddr(addrh2);
                Instruction(OP_LD_X, 16, 0);
                LoadXAddr(addrh);
                Instruction(OP_ST_X, 16, 0);
                break;

            case INT_SET_VARIABLE_DIVIDE:
                // Do this one separately since the divide routine uses
                // slightly different in/out registers and I don't feel like
                // modifying it.
                MemForVariable(a->name2, &addrl, &addrh);
                MemForVariable(a->name3, &addrl2, &addrh2);

                LoadXAddr(addrl2);
                Instruction(OP_LD_X, 18, 0);
                LoadXAddr(addrh2);
                Instruction(OP_LD_X, 19, 0);

                LoadXAddr(addrl);
                Instruction(OP_LD_X, 16, 0);
                LoadXAddr(addrh);
                Instruction(OP_LD_X, 17, 0);

                CallSubroutine(DivideAddress);
                DivideUsed = TRUE;
                
                MemForVariable(a->name1, &addrl, &addrh);

                LoadXAddr(addrl);
                Instruction(OP_ST_X, 16, 0);
                LoadXAddr(addrh);
                Instruction(OP_ST_X, 17, 0);
                break;

            case INT_SET_VARIABLE_ADD:
            case INT_SET_VARIABLE_SUBTRACT:
            case INT_SET_VARIABLE_MULTIPLY:
                MemForVariable(a->name2, &addrl, &addrh);
                MemForVariable(a->name3, &addrl2, &addrh2);

                LoadXAddr(addrl);
                Instruction(OP_LD_X, 18, 0);
                LoadXAddr(addrh);
                Instruction(OP_LD_X, 19, 0);

                LoadXAddr(addrl2);
                Instruction(OP_LD_X, 16, 0);
                LoadXAddr(addrh2);
                Instruction(OP_LD_X, 17, 0);

                if(a->op == INT_SET_VARIABLE_ADD) {
                    Instruction(OP_ADD, 18, 16);
                    Instruction(OP_ADC, 19, 17);
                } else if(a->op == INT_SET_VARIABLE_SUBTRACT) {
                    Instruction(OP_SUB, 18, 16);
                    Instruction(OP_SBC, 19, 17);
                } else if(a->op == INT_SET_VARIABLE_MULTIPLY) {
                    CallSubroutine(MultiplyAddress);
                    MultiplyUsed = TRUE;
                } else oops();

                MemForVariable(a->name1, &addrl, &addrh);

                LoadXAddr(addrl);
                Instruction(OP_ST_X, 18, 0);
                LoadXAddr(addrh);
                Instruction(OP_ST_X, 19, 0);
                break;

            case INT_SET_PWM: {
                int target = atoi(a->name2);

                // PWM frequency is 
                //   target = xtal/(256*prescale)
                // so not a lot of room for accurate frequency here

                int prescale;
                int bestPrescale;
                int bestError = INT_MAX;
                int bestFreq;
                for(prescale = 1;;) {
                    int freq = (Prog.mcuClock + prescale*128)/(prescale*256);

                    int err = abs(freq - target);
                    if(err < bestError) {
                        bestError = err;
                        bestPrescale = prescale;
                        bestFreq = freq;
                    }
                    
                    if(prescale == 1) {
                        prescale = 8;
                    } else if(prescale == 8) {
                        prescale = 64;
                    } else if(prescale == 64) {
                        prescale = 256;
                    } else if(prescale == 256) {
                        prescale = 1024;
                    } else {
                        break;
                    }
                }

                if(((double)bestError)/target > 0.05) {
                    Error(_("Target frequency %d Hz, closest achievable is "
                        "%d Hz (warning, >5%% error)."), target, bestFreq);
                }

                DivideUsed = TRUE; MultiplyUsed = TRUE;
                MemForVariable(a->name1, &addrl, &addrh);
                LoadXAddr(addrl);
                Instruction(OP_LD_X, 16, 0);
                Instruction(OP_LDI, 17, 0);
                Instruction(OP_LDI, 19, 0);
                Instruction(OP_LDI, 18, 255);
                CallSubroutine(MultiplyAddress);
                Instruction(OP_MOV, 17, 19);
                Instruction(OP_MOV, 16, 18);
                Instruction(OP_LDI, 19, 0);
                Instruction(OP_LDI, 18, 100);
                CallSubroutine(DivideAddress);
                LoadXAddr(REG_OCR2);
                Instruction(OP_ST_X, 16, 0);

                // Setup only happens once
                MemForSingleBit("$pwm_init", FALSE, &addr, &bit);
                DWORD skip = AllocFwdAddr();
                IfBitSet(addr, bit);
                Instruction(OP_RJMP, skip, 0);
                SetBit(addr, bit);

                BYTE cs;
                switch(bestPrescale) {
                    case    1: cs = 1; break;
                    case    8: cs = 2; break;
                    case   64: cs = 3; break;
                    case  256: cs = 4; break;
                    case 1024: cs = 5; break;
                    default: oops(); break;
                }
    
                // fast PWM mode, non-inverted operation, given prescale
                WriteMemory(REG_TCCR2, (1 << 6) | (1 << 3) | (1 << 5) | cs);

                FwdAddrIsNow(skip);

                break;
            }
            case INT_EEPROM_BUSY_CHECK: {
                MemForSingleBit(a->name1, FALSE, &addr, &bit);

                DWORD isBusy = AllocFwdAddr();
                DWORD done = AllocFwdAddr();
                IfBitSet(REG_EECR, 1);
                Instruction(OP_RJMP, isBusy, 0);

                IfBitClear(EepromHighByteWaitingAddr, EepromHighByteWaitingBit);
                Instruction(OP_RJMP, done, 0);

                // Just increment EEARH:EEARL, to point to the high byte of
                // whatever we just wrote the low byte for.
                LoadXAddr(REG_EEARL);
                Instruction(OP_LD_X, 16, 0);
                LoadXAddr(REG_EEARH);
                Instruction(OP_LD_X, 17, 0);
                Instruction(OP_INC, 16, 0);
                DWORD noCarry = AllocFwdAddr();
                Instruction(OP_BRNE, noCarry, 0);
                Instruction(OP_INC, 17, 0);
                FwdAddrIsNow(noCarry);
                // X is still REG_EEARH
                Instruction(OP_ST_X, 17, 0);
                LoadXAddr(REG_EEARL);
                Instruction(OP_ST_X, 16, 0);

                LoadXAddr(EepromHighByte);
                Instruction(OP_LD_X, 16, 0);
                LoadXAddr(REG_EEDR);
                Instruction(OP_ST_X, 16, 0);
                LoadXAddr(REG_EECR);
                Instruction(OP_LDI, 16, 0x04);
                Instruction(OP_ST_X, 16, 0);
                Instruction(OP_LDI, 16, 0x06);
                Instruction(OP_ST_X, 16, 0);

                ClearBit(EepromHighByteWaitingAddr, EepromHighByteWaitingBit);

                FwdAddrIsNow(isBusy);
                SetBit(addr, bit);
                FwdAddrIsNow(done);
                break;
            }
            case INT_EEPROM_READ: {
                MemForVariable(a->name1, &addrl, &addrh);
                int i;
                for(i = 0; i < 2; i++) {
                    WriteMemory(REG_EEARH, ((a->literal+i) >> 8));
                    WriteMemory(REG_EEARL, ((a->literal+i) & 0xff));
                    WriteMemory(REG_EECR, 0x01);
                    LoadXAddr(REG_EEDR);
                    Instruction(OP_LD_X, 16, 0);
                    if(i == 0) {
                        LoadXAddr(addrl);
                    } else {
                        LoadXAddr(addrh);
                    }
                    Instruction(OP_ST_X, 16, 0);
                }
                break;
            }
            case INT_EEPROM_WRITE:
                MemForVariable(a->name1, &addrl, &addrh);
                SetBit(EepromHighByteWaitingAddr, EepromHighByteWaitingBit);
                LoadXAddr(addrh);
                Instruction(OP_LD_X, 16, 0);
                LoadXAddr(EepromHighByte);
                Instruction(OP_ST_X, 16, 0);

                WriteMemory(REG_EEARH, (a->literal >> 8));
                WriteMemory(REG_EEARL, (a->literal & 0xff));
                LoadXAddr(addrl);
                Instruction(OP_LD_X, 16, 0);
                LoadXAddr(REG_EEDR);
                Instruction(OP_ST_X, 16, 0);
                LoadXAddr(REG_EECR);
                Instruction(OP_LDI, 16, 0x04);
                Instruction(OP_ST_X, 16, 0);
                Instruction(OP_LDI, 16, 0x06);
                Instruction(OP_ST_X, 16, 0);
                break;
            
            case INT_READ_ADC: {
                MemForVariable(a->name1, &addrl, &addrh);

                WriteMemory(REG_ADMUX, 
                    (0 << 6) |              // AREF, internal Vref odd
                    (0 << 5) |              // right-adjusted
                    MuxForAdcVariable(a->name1));

                // target something around 200 kHz for the ADC clock, for
                // 25/(200k) or 125 us conversion time, reasonable
                int divisor = (Prog.mcuClock / 200000);
                int j = 0;
                for(j = 1; j <= 7; j++) {
                    if((1 << j) > divisor) break;
                }

                BYTE adcsra = 
                    (1 << 7) |              // ADC enabled
                    (0 << 5) |              // not free running
                    (0 << 3) |              // no interrupt enabled
                    j;                      // prescaler setup
            
                WriteMemory(REG_ADCSRA, adcsra);
                WriteMemory(REG_ADCSRA, (BYTE)(adcsra | (1 << 6)));

                DWORD waitForFinsh = AvrProgWriteP;
                IfBitSet(REG_ADCSRA, 6);
                Instruction(OP_RJMP, waitForFinsh, 0);

                LoadXAddr(REG_ADCL);
                Instruction(OP_LD_X, 16, 0);
                LoadXAddr(addrl);
                Instruction(OP_ST_X, 16, 0);

                LoadXAddr(REG_ADCH);
                Instruction(OP_LD_X, 16, 0);
                LoadXAddr(addrh);
                Instruction(OP_ST_X, 16, 0);

                break;
            }
            case INT_UART_SEND: {
                MemForVariable(a->name1, &addrl, &addrh);
                MemForSingleBit(a->name2, TRUE, &addr, &bit);

                DWORD noSend = AllocFwdAddr();
                IfBitClear(addr, bit);
                Instruction(OP_RJMP, noSend, 0);

                LoadXAddr(addrl);
                Instruction(OP_LD_X, 16, 0);
                LoadXAddr(REG_UDR);
                Instruction(OP_ST_X, 16, 0);

                FwdAddrIsNow(noSend);

                ClearBit(addr, bit);
                DWORD dontSet = AllocFwdAddr();
                IfBitSet(REG_UCSRA, 5); // UDRE, is 1 when tx buffer is empty
                Instruction(OP_RJMP, dontSet, 0);
                SetBit(addr, bit);
                FwdAddrIsNow(dontSet);

                break;
            }
            case INT_UART_RECV: {
                MemForVariable(a->name1, &addrl, &addrh);
                MemForSingleBit(a->name2, TRUE, &addr, &bit);
        
                ClearBit(addr, bit);

                DWORD noChar = AllocFwdAddr();
                IfBitClear(REG_UCSRA, 7);
                Instruction(OP_RJMP, noChar, 0);

                SetBit(addr, bit);
                LoadXAddr(REG_UDR);
                Instruction(OP_LD_X, 16, 0);
                LoadXAddr(addrl);
                Instruction(OP_ST_X, 16, 0);
                
                LoadXAddr(addrh);
                Instruction(OP_LDI, 16, 0);
                Instruction(OP_ST_X, 16, 0);

                FwdAddrIsNow(noChar);
                break;
            }
            case INT_END_IF:
            case INT_ELSE:
                return;

            case INT_SIMULATE_NODE_STATE:
            case INT_COMMENT:
                break;

            default:
                oops();
                break;
        }
    }
}

//-----------------------------------------------------------------------------
// 16x16 signed multiply, code from Atmel app note AVR200. op1 in r17:16,
// op2 in r19:18, result low word goes into r19:18.
//-----------------------------------------------------------------------------
static void MultiplyRoutine(void)
{
    FwdAddrIsNow(MultiplyAddress);

    DWORD m16s_1;
    DWORD m16s_2 = AllocFwdAddr();

    Instruction(OP_SUB, 21, 21);
    Instruction(OP_SUB, 20, 20);
    Instruction(OP_LDI, 22, 16);
    m16s_1 = AvrProgWriteP; Instruction(OP_BRCC, m16s_2, 0);
    Instruction(OP_ADD, 20, 16);
    Instruction(OP_ADC, 21, 17);
    FwdAddrIsNow(m16s_2); Instruction(OP_SBRC, 18, 0);
    Instruction(OP_SUB, 20, 16);
    Instruction(OP_SBRC, 18, 0);
    Instruction(OP_SBC, 21, 17);
    Instruction(OP_ASR, 21, 0);
    Instruction(OP_ROR, 20, 0);
    Instruction(OP_ROR, 19, 0);
    Instruction(OP_ROR, 18, 0);
    Instruction(OP_DEC, 22, 0);
    Instruction(OP_BRNE, m16s_1, 0);
    Instruction(OP_RET, 0, 0);
}

//-----------------------------------------------------------------------------
// 16/16 signed divide, code from the same app note. Dividend in r17:16,
// divisor in r19:18, result goes in r17:16 (and remainder in r15:14).
//-----------------------------------------------------------------------------
static void DivideRoutine(void)
{
    FwdAddrIsNow(DivideAddress);

    DWORD d16s_1 = AllocFwdAddr();
    DWORD d16s_2 = AllocFwdAddr();
    DWORD d16s_3;
    DWORD d16s_4 = AllocFwdAddr();
    DWORD d16s_5 = AllocFwdAddr();
    DWORD d16s_6 = AllocFwdAddr();

    Instruction(OP_MOV, 13, 17);
    Instruction(OP_EOR, 13, 19);
    Instruction(OP_SBRS, 17, 7);
    Instruction(OP_RJMP, d16s_1, 0);
    Instruction(OP_COM, 17, 0);
    Instruction(OP_COM, 16, 0);
    Instruction(OP_SUBI, 16, 0xff);
    Instruction(OP_SBCI, 17, 0xff);
    FwdAddrIsNow(d16s_1); Instruction(OP_SBRS, 19, 7);
    Instruction(OP_RJMP, d16s_2, 0);
    Instruction(OP_COM, 19, 0);
    Instruction(OP_COM, 18, 0);
    Instruction(OP_SUBI, 18, 0xff);
    Instruction(OP_SBCI, 19, 0xff);
    FwdAddrIsNow(d16s_2); Instruction(OP_EOR, 14, 14);
    Instruction(OP_SUB, 15, 15);
    Instruction(OP_LDI, 20, 17);

    d16s_3 = AvrProgWriteP; Instruction(OP_ADC, 16, 16);
    Instruction(OP_ADC, 17, 17);
    Instruction(OP_DEC, 20, 0);
    Instruction(OP_BRNE, d16s_5, 0);
    Instruction(OP_SBRS, 13, 7);
    Instruction(OP_RJMP, d16s_4, 0);
    Instruction(OP_COM, 17, 0);
    Instruction(OP_COM, 16, 0);
    Instruction(OP_SUBI, 16, 0xff);
    Instruction(OP_SBCI, 17, 0xff);
    FwdAddrIsNow(d16s_4); Instruction(OP_RET, 0, 0);
    FwdAddrIsNow(d16s_5); Instruction(OP_ADC, 14, 14);
    Instruction(OP_ADC, 15, 15);
    Instruction(OP_SUB, 14, 18);
    Instruction(OP_SBC, 15, 19);
    Instruction(OP_BRCC, d16s_6, 0);
    Instruction(OP_ADD, 14, 18);
    Instruction(OP_ADC, 15, 19);
    Instruction(OP_CLC, 0, 0);
    Instruction(OP_RJMP, d16s_3, 0);
    FwdAddrIsNow(d16s_6); Instruction(OP_SEC, 0, 0);
    Instruction(OP_RJMP, d16s_3, 0);
}

//-----------------------------------------------------------------------------
// Compile the program to REG code for the currently selected processor
// and write it to the given file. Produce an error message if we cannot
// write to the file, or if there is something inconsistent about the
// program.
//-----------------------------------------------------------------------------
void CompileAvr(char *outFile)
{
    FILE *f = fopen(outFile, "w");
    if(!f) {
        Error(_("Couldn't open file '%s'"), outFile);
        return;
    }

    if(setjmp(CompileErrorBuf) != 0) {
        fclose(f);
        return;
    }

    // Here we must set up the addresses of some registers that for some
    // stupid reason move around from AVR to AVR.
    if(strcmp(Prog.mcu->mcuName, "Atmel AVR ATmega16 40-PDIP")==0  ||
       strcmp(Prog.mcu->mcuName, "Atmel AVR ATmega32 40-PDIP")==0 ||
       strcmp(Prog.mcu->mcuName, "Atmel AVR ATmega162 40-PDIP")==0 ||
       strcmp(Prog.mcu->mcuName, "Atmel AVR ATmega8 28-PDIP")==0)
    {
        REG_TIMSK = 0x59;
        REG_TIFR = 0x58;
        REG_UBRRH = 0x40;
        REG_UBRRL = 0x29;
        REG_UCSRB = 0x2a;
        REG_UCSRA = 0x2b;
        REG_UDR = 0x2c;
    } else {
        REG_TIMSK = 0x57;
        REG_TIFR = 0x56;
        REG_UBRRH = 0x98;
        REG_UBRRL = 0x99;
        REG_UCSRB = 0x9a;
        REG_UCSRA = 0x9b;
        REG_UDR = 0x9c;
    }

    WipeMemory();
    MultiplyUsed = FALSE;
    MultiplyAddress = AllocFwdAddr();
    DivideUsed = FALSE;
    DivideAddress = AllocFwdAddr();
    AllocStart();

    // Where we hold the high byte to program in EEPROM while the low byte
    // programs.
    EepromHighByte = AllocOctetRam();
    AllocBitRam(&EepromHighByteWaitingAddr, &EepromHighByteWaitingBit);

    WriteRuntime();
    IntPc = 0;
    CompileFromIntermediate();

    if(Prog.mcu->avrUseIjmp) {
        Instruction(OP_LDI, 30, (BeginningOfCycleAddr & 0xff));
        Instruction(OP_LDI, 31, (BeginningOfCycleAddr >> 8));
        Instruction(OP_IJMP, 0, 0);
    } else {
        Instruction(OP_RJMP, BeginningOfCycleAddr, 0);
    }

    MemCheckForErrorsPostCompile();

    if(MultiplyUsed) MultiplyRoutine();
    if(DivideUsed) DivideRoutine();

    WriteHexFile(f);
    fclose(f);

    char str[MAX_PATH+500];
    sprintf(str, _("Compile successful; wrote IHEX for AVR to '%s'.\r\n\r\n"
        "Remember to set the processor configuration (fuses) correctly. "
        "This does not happen automatically."), outFile);
    CompileSuccessfulMessage(str);
}