Table of Contents for Programming Languages: a survey


Atmel AVR

"The AVR processors were designed with the efficient execution of compiled C code in mind and have several built-in pointers for the task.... The mostly regular instruction set makes programming it using C (or even Ada) compilers fairly straightforward. GCC has included AVR support for quite some time, and that support is widely used. In fact, Atmel solicited input from major developers of compilers for small microcontrollers, to determine the instruction set features that were most useful in a compiler for high-level languages." --

Most AVRs are modified Harvard architecture designs. Harvard architecture means that program code and data are stored in separate memory banks. Modified Harvard architecture means that program code can still be accessed. Most, but not all AVRs can access program code in a read-only fashion; some AVRs can also write to program code memory. The smallest AVRs have 512 bytes of program memory and 32 bytes of data memory.

Here we look at the AVR Minimal Core ISA, found in: ATtiny11, ATtiny12, ATtiny15, ATtiny28. Note that i think the "Reduced Core" may be more current, found in: ATtiny10, ATtiny9, ATtiny5, ATtiny4. I'll omit explanations for instruction that can be guessed from the mnemonic when the mnemonic follows the same pattern as previously listed instructions.

The AVRs have 16 or more general purpose registers. The registers are mapped to RAM. This is a load-store architecture; only the load and store operations access RAM, everything else works with registers. The AVR has a status register composed of 8 flags: Carry, Zero, Negative, Overflow (V), Sign, Half carry (for Binary Coded Decimal arithmetic), Bit copy, Interrupts enabled. Most recent AVRs have an on-chip oscillator. Most AVRs have a 2-stage pipelined architecture ("the next instruction is fetched as the current one is executing") and most instructions are single cycle, allowing almost 1 MIPS per MHZ (e.g. an 8 MHz processor can achieve 8 MIPS). AVRs have a 'watchdog timer', which can be used to generate an interrupt or to reboot (reset) the MCU after some amount of time; if enabled, the watchdog timer is continually counting up and the software must periodically reset it with the WDR instruction to prevent it from activating (e.g. it can be used as a timeout failsafe so you don't have to manually reboot hung devices in the field). Most AVRs support JTAG, a debugging and program-code loading mechanism. The stack is allocated out of ordinary RAM and can grow to the entire RAM size. Four addressing modes are supported (at least in Reduced Core): direct, indirect, indirect with pre-decrement, and indirect with post-increment.

Arithmetic instructions: ADD, ADC (add with carry), SUB (subtract), SUBI (subtract immediate), SBC (subtract with carry), SBCI, NEG, INC (increment), DEC, TST (test for zero or minus), CLR (clear register), SER (set register). The AVR Minimal Core ISA does not contain a MUL (multiplication) instruction but the AVR Enhanced Core ISA does.

Branches: RJMP (relative jump), RCALL (relative subroutine call), RET (subroutine return), RETI (interrupt return), CPSE (compare, skip if equal), CP (compare), CPC (compare with carry), CPI (compare immediate), SBRC (skip if bit in register cleared), SBRS, SBIC (skip if bit in I/O register cleared), SBIS, BRBS (branch if status flag set), BRBC (branch if status flag cleared), BREQ (branch if equal), BRNE (branch if not equal), BRCS (branch if carry set), BRCC, BRSH (branch if same or higher), BRLO (branch if lower), BRMI (branch if minus), BRPL (branch if plus), BRGE (branch if greater-than-or-equal, signed), BRLT, BRHS (branch if half-carry set), BRHC, BRTS (branch if T set), BRTC, BRVS (branch if overflow set), BRVC

Transfers: LD (load from memory), ST (store to memory), MOV (move), LDI (load immediate), IN (load from I/O memory), OUT, LPM (load from program memory). The AVR Reduced Core uses LD for program and data memory. The AVR Reduced Core has PUSH and POP instructions. The AVR Minimal Core ISA does not contain a SPM (store to program memory) instruction but the AVR Enhanced Core ISA does.

Bitwise: SBI (set bit in I/O register), CBI, LSL (logical shift left), LSR (logical shift right), ROL (rotate left thru carry), ROR, ASR (arithmetic shift right), SWAP (swap nibbles), BSET (flag set), BCLR, BST, BLD (bit load from T to register), SEC (set carry), CLC (clear carry), SEN, CLN, SEZ, CLZ, SES, CLS, SEV, CLV, SET, CLT, SEH, CLH, AND, ANDI (AND immediate), OR, ORI, EOR (xor), COM (bitwise negation (one's complement)), SBR (set register bit), CBR (clear register bit),

Control: SEI (set interrupt), CLI, BRIE (branch if interrupt enabled), BRID (branch if interrupt disabled), NOP, SLEEP (sleep until interrupt), WDR (reset watchdog timer)

Summary description: We have: load/store, mov, relative jump, relative call/return, <=/</=/>,>= comparison and branching, arithmetic (addition, subtraction, negation, comparisons and set/clears for carry/negative/overflow/zero flags and various registers; many arithmetic operations have multiple forms for carry/no-carry) bitwise arithmetic (negation, and, or, xor, set/clear/skip-if bit, logical shifts, arithmetic shifts, rotate, swap nibbles), and interrupts, NOP, sleep (until interrupt) and watchdog timer reset.


" > The main problem I had with the AVR's (for "bigger" applications in > C), is the contortions you have to go through to access constant data > in flash. It has a "harvard" architecture, meaning you need a > different pointer type to access data stored in the "program" > space. This makes it hard to write general purpose functions which are > equally happy working on RAM and flash data. While in principle I > think the compilers could hide this, as far as I know they all have > similar clumsy work-arounds which end up infecting many of your > function and variable definitions. (This is for the case when you have > quite a lot of constant data, too much to make a ram copy. For example > CRC tables, menu structures & screen layouts, fonts.) >

Yes, that's definitely a problem, and one of the weak points of the AVR core (the other main weaknesses are poor pointer register support, and no SP+offset addressing). " --


All small microcontrollers have their idiosyncrasies. I think the AVR devices are the best overall choice at the moment for a wide range of applications. By the time you are getting over the 128k code level, however, it is probably best to jump to a 32-bit device. "

The AVR Microcontroller and C Compiler Co-Design

When designing the AVR ISA, the ISA developers worked with C compiler engineers -- afterwards, they wrote a paper describing the changes they made to the ISA based on the C compiler engineers' advice. In this section we present notes on that paper: The AVR Microcontroller and C Compiler Co-Design

initial comments on good things about AVR:

things that they changed in response to the compiler engineers' comments:

other things that they already had that they noted were important:

AVR links


The PIC design is a Harvard architecture. It has:


There is no distinction between memory space and register space because the RAM serves the job of both memory and registers, and the RAM is usually just referred to as the register file or simply as the registers....The addressability of memory varies depending on device series, and all PIC devices have some banking mechanism to extend addressing to additional memory...To implement indirect addressing, a "file select register" (FSR) and "indirect register" (INDF) are used. A register number is written to the FSR, after which reads from or writes to INDF will actually be to or from the register pointed to by FSR....PICs have a hardware call stack, which is used to save return addresses....Some operations, such as bit setting and testing, can be performed on any numbered register, but bi-operand arithmetic operations always involve W (the accumulator), writing the result back to either W or the other operand register. To load a constant, it is necessary to load it into W before it can be moved into another register. On the older cores, all register moves needed to pass through W, but this changed on the "high end" cores....PIC cores have skip instructions which are used for conditional execution and branching. The skip instructions are 'skip if bit set' and 'skip if bit not set'. Because cores before PIC18 had only unconditional branch instructions, conditional jumps are implemented by a conditional skip (with the opposite condition) followed by an unconditional branch.

In general, PIC instructions fall into 5 classes:

... The architectural decisions are directed at the maximization of speed-to-cost ratio. The PIC architecture was among the first scalar CPU designs,[citation needed] and is still among the simplest and cheapest. The Harvard architecture—in which instructions and data come from separate sources—simplifies timing and microcircuit design greatly, and this benefits clock speed, price, and power consumption.

The PIC instruction set is suited to implementation of fast lookup tables in the program space. Such lookups take one instruction and two instruction cycles. Many functions can be modeled in this way. Optimization is facilitated by the relatively large program space of the PIC (e.g. 4096 × 14-bit words on the 16F690) and by the design of the instruction set, which allows for embedded constants. For example, a branch instruction's target may be indexed by W, and execute a "RETLW" which does as it is named - return with literal in W. ...


    One accumulator
    Register-bank switching is required to access the entire RAM of many devices
    Operations and registers are not orthogonal; some instructions can address RAM and/or immediate constants, while others can only use the accumulator

The following stack limitations have been addressed in the PIC18 series, but still apply to earlier cores:

    The hardware call stack is not addressable, so preemptive task switching cannot be implemented
    Software-implemented stacks are not efficient, so it is difficult to generate reentrant code and support local variables

With paged program memory, there are two page sizes to worry about


The easy to learn RISC instruction set of the PIC assembly language code can make the overall flow difficult to comprehend. Judicious use of simple macros can increase the readability of PIC assembly language.


Baseline core devices (12 bit)

These devices feature a 12-bit wide code memory, a 32-byte register file, and a tiny two level deep call stack. They are represented by the PIC10 series, as well as by some PIC12 and PIC16 devices.


Generally the first 7 to 9 bytes of the register file are special-purpose registers, and the remaining bytes are general purpose RAM. Pointers are implemented using a register pair: after writing an address to the FSR (file select register), the INDF (indirect f) register becomes an alias for the addressed register. If banked RAM is implemented, the bank number is selected by the high 3 bits of the FSR. This affects register numbers 16–31; registers 0–15 are global and not affected by the bank select bits.

Because of the very limited register space (5 bits), 4 rarely read registers were not assigned addresses, but written by special instructions (OPTION and TRIS).

The ROM address space is 512 words (12 bits each), which may be extended to 2048 words by banking. CALL and GOTO instructions specify the low 9 bits of the new code location; additional high-order bits are taken from the status register. Note that a CALL instruction only includes 8 bits of address, and may only specify addresses in the first half of each 512-word page.

Lookup tables are implemented using a computed GOTO (assignment to PCL register) into a table of RETLW instructions. ...

" --

Instructions (remember that W is the accumulator; f is a register number):

misc: NOP, OPTION (copy W to option register), SLEEP, CLRWDT (reset watchdog timer), TRIS k (k = 1,2, or 3) (copy W to one of the tristate registers; tristate registers control port I/O direction; "in 12bit cores, the TRISn registers are not mapped in the file registers space, so the TRIS instruction is the only way of setting port direction for those processors." -- ),

moves: MOVF f r (r = f), MOVWF r (f = W)


bitwise arithmetic:



control flow:

immediate addressing mode operations (e.g. operations that take a constant parameter):

Summary: PIC's ISA is a non-regular design in that the accumulator register has a special role (every binary operation takes the accumulator as one operand; many operations cannot take constant operands; and constants can only be directly placed into W (and then can be moved into another register with a MOVWF instruction). There are moves, clears, bit sets and clears, bitwise arithmetic (and, or, xor, not, swap nibbles, rotate-right/left-thru-carry; only and, or, xor can have a constant operand), arithmetic (add, subtract, increment, decrement), skips (inc/dec and skip if zero; skip if bit is set/clear), control flow (call/return, goto). As noted above, there is an idiomatic lookup-table-in-program-memory implementation using RETLW k instructions to encode each table entry.