Home Technical Articles Microcontrollers
Microcontrollers

PIC Assembly: Optimizing loops with Dattalo's techniques

Applying Scott Dattalo's techniques to create efficient loops in PIC Assembly — cycle reduction, precise delays, nested loops, and intelligent use of program memory.

PIC Assembly DECFSZ Loops Optimization Dattalo
SD
This article applies and expands on techniques documented by Scott Dattalo — an electronic engineer and one of the leading references in algorithms for PIC microcontrollers. His original articles are compiled at camposlh.com/dattalo.

Introduction: every cycle counts

Programming in Assembly for PIC microcontrollers is, in essence, an exercise in economy. With a 4 MHz clock and a 1 µs instruction cycle, every wasted instruction has a real and measurable cost. In real-time systems — motor control, signal generation, software serial communication — the difference between a 5-cycle loop and an 8-cycle loop can mean the difference between a system that works and one that fails.

Scott Dattalo, in his technical articles on PIC Assembly, has repeatedly demonstrated that the most efficient way to solve a problem is rarely the most obvious. His techniques for delays, PWM, and signal generation fundamentally depend on well-constructed loops. This article extracts and deepens these patterns, presenting them systematically with practical examples and cycle analysis.

📐
PIC instruction cycle reference (baseline/midrange)
Most PIC instructions execute in 1 instruction cycle = 4 clock cycles. Branch instructions (GOTO, CALL, RETURN) and skip instructions when the skip is taken execute in 2 instruction cycles. When the skip is not taken, it executes in 1 cycle. This is fundamental for calculating precise delays.

1. PIC loop instructions

O PIC midrange (PIC16Fxxx) não possui uma instrução de loop dedicada como o DJNZ do 8051. Em vez disso, os loops são construídos com duas instruções fundamentais que, combinadas, formam o núcleo de quase todo loop em Assembly PIC.

DECFSZ — Decrement and Skip if Zero

A instrução DECFSZ f, d decrementa o registrador f e pula a próxima instrução se o resultado for zero. É a instrução de loop mais importante do PIC. Sua semântica exata é:

PIC Assembly — DECFSZ 1 cycle (no skip) / 2 cycles (with skip)
__PROTECTED_3__

Um detalhe crítico: DECFSZ decrementa antes de testar. Isso significa que um registrador inicializado com 0 executará o loop 256 vezes (de 0 → 255 → 254 → ... → 1 → 0), não zero vezes. Inicializar com N executa o loop exatamente N vezes.

INCFSZ — Increment and Skip if Zero

A instrução complementar INCFSZ f, d incrementa e pula se o resultado for zero (overflow de 8 bits). É menos comum para loops de contagem simples, mas útil em padrões específicos de otimização que veremos adiante.

InstructionOperationSkip whenCycles (no skip)Cycles (with skip)
DECFSZ f, df = f − 1result = 012
INCFSZ f, df = f + 1result = 0 (overflow)12
BTFSC f, btests bit b of fbit = 012
BTFSS f, btests bit b of fbit = 112
GOTO labelPC ← label2

2. The basic loop and its real cost

The simplest loop in PIC Assembly — counting from N down to 0 — seems trivial, but already reveals the first optimization opportunity. Compare the two versions below:

✗ Naive version — 4 cycles/iteration
__PROTECTED_11__
✓ With DECFSZ — 3 cycles/iteration
__PROTECTED_12__

A versão com DECFSZ elimina uma instrução por iteração. Para um loop de 256 iterações, isso representa 256 ciclos economizados — com clock de 4 MHz, são 64 µs a menos de overhead puro. Em loops internos de algoritmos críticos, essa diferença se multiplica.

💡
Dattalo's technique: eliminating the GOTO with NOP
Em alguns casos, é possível eliminar completamente o GOTO usando a estrutura inversa do loop: em vez de pular o GOTO quando zero, pular o NOP (ou a próxima instrução útil) quando não zero. Isso funciona quando o corpo do loop cabe em um bloco de código linear. Veremos essa técnica na seção de delays.

3. Precise delays: Dattalo's technique

Generating precise delays without using hardware timers is one of the most classic problems in PIC Assembly. The naive approach — a simple decrement loop — works, but wastes cycles and makes exact time calculation difficult. Dattalo documented a family of techniques that allow arbitrarily precise delays with minimal code.

The fundamental delay formula

Um loop de delay com DECFSZ e GOTO tem o seguinte custo exato:

Tdelay = 3 × N − 1 ciclos de instrução

Onde N é o valor inicial do contador (1 a 256). O "−1" vem do fato de que na última iteração o DECFSZ executa em 2 ciclos (skip tomado) mas o GOTO não é executado, economizando 1 ciclo. Para N = 0 (256 iterações): T = 3 × 256 − 1 = 767 ciclos.

PIC Assembly — Basic calibrated delay 3N − 1 cycles
__PROTECTED_18__

Fine-tuning: delays not multiples of 3

O problema com o loop de 3 ciclos é que só gera delays múltiplos de 3 (menos 1). Para delays arbitrários, Dattalo usa a técnica de preenchimento com NOPs antes ou depois do loop:

PIC Assembly — Exactly 100 cycles delay 100 exact cycles
__PROTECTED_19__
💬
Dattalo's insight: GOTO $+1 as a 2-cycle NOP
O truque GOTO $+1 (salto para o endereço seguinte) é um NOP de 2 ciclos — útil quando você precisa adicionar exatamente 2 ciclos de ajuste sem usar dois NOPs separados. Dattalo usa esse padrão extensivamente nos artigos de PWM e delays para alinhar o timing sem desperdiçar memória de programa.

4. Nested loops: multiplying the reach

A single 8-bit loop generates at most 767 delay cycles (N=256). For longer delays — milliseconds or seconds — nested loops are necessary. Dattalo's technique for nested loops is elegant: each outer level multiplies the reach of the inner loop.

Millisecond delay

With a 4 MHz clock (1 instruction cycle = 1 µs), a 1 ms delay requires 1000 cycles. A simple 8-bit loop reaches at most 767 cycles, so we need two levels:

PIC Assembly — 1 ms delay @ 4 MHz ≈ 1000 cycles
__PROTECTED_21__

Parametric delay: passing N via W

A very useful variation documented by Dattalo is the parametric delay — where the number of milliseconds is passed in the W register before the call. This allows a single reusable delay sub-routine:

PIC Assembly — Parametric delay (N × 1ms)
__PROTECTED_22__

5. The loop without GOTO: Dattalo's technique for ultra-efficient loops

A técnica mais elegante de Dattalo para loops é a eliminação completa do GOTO. Em vez de usar DECFSZ + GOTO (3 ciclos/iteração), ele usa a estrutura de skip invertido para criar um loop de apenas 2 ciclos por iteração — uma redução de 33%.

A ideia é: em vez de pular o GOTO quando o contador chega a zero, pular o DECFSZ (ou equivalente) quando o contador ainda não chegou a zero. Isso é possível quando o corpo do loop tem exatamente 1 instrução:

PIC Assembly — 2-cycle loop (without GOTO) 2N cycles
__PROTECTED_26__

Unrolled loop (loop unrolling)

A técnica mais poderosa de Dattalo para loops de alto desempenho é o loop desenrolado (loop unrolling): em vez de executar N iterações de um loop, duplicar o corpo do loop N vezes no código. Isso elimina completamente o overhead de controle (DECFSZ + GOTO = 3 ciclos) ao custo de mais memória de programa.

PIC Assembly — Comparison: loop vs. unrolled
__PROTECTED_27__
⚠️
Loop unrolling: when is it worth it?
Loop unrolling is advantageous when N is small (≤ 8) and the loop body is simple. For large N, the program memory cost outweighs the speed gain. The PIC16F has 2K or 8K program words — use loop unrolling judiciously in time-critical sections, not as a general rule.

6. RETLW tables: the loop that doesn't look like a loop

Uma das técnicas mais elegantes do PIC Assembly, amplamente usada por Dattalo em seus artigos de geração de sinais, é a tabela de lookup implementada com RETLW. Ela é, na essência, um "loop" de acesso indexado à memória de programa — mas sem nenhum overhead de controle.

PIC Assembly — RETLW table (Dattalo's technique) 3 cycles per access (CALL + RETLW)
__PROTECTED_29__

O truque ADDWF PCL, F soma o índice ao contador de programa, saltando diretamente para a entrada correta da tabela. Cada RETLW carrega o valor em W e retorna em 2 ciclos. O custo total é 3 ciclos por acesso (1 para ADDWF + 2 para RETLW) — independente do tamanho da tabela.

⚠️
Watch out for the page boundary
A instrução ADDWF PCL, F modifica apenas os 8 bits baixos do PC. Se a tabela cruzar um limite de 256 palavras (página de 256 endereços), o acesso será incorreto. Dattalo recomenda sempre alinhar tabelas ao início de uma página usando a diretiva ORG ou garantir que a tabela inteira caiba dentro de 256 endereços.

7. Loops in PWM generation: applying Dattalo

O artigo de PWM de Dattalo (disponível em camposlh.com/dattalo/pwm.html) demonstra como loops bem construídos são a base da geração de PWM por software. A técnica de phase-shifted counters usa dois contadores que incrementam no mesmo loop, com o output sendo controlado pelos overflows.

PIC Assembly — Software PWM (Dattalo's technique) 8-cycle loop per iteration
__PROTECTED_34__

8. Technique comparison: cycles and memory

TechniqueCycles/iterationOverhead instructionsMemoryBest use
DECF + BTFSS + GOTO 4 3 Low Legacy code, compatibility
DECFSZ + GOTO 3 2 Low General loop, most common pattern
INCFSZ + GOTO $+2 3–4 2–3 Low PWM, overflow counters
Loop desenrolado 0 0 High (N × body) Small N, critical timing
Tabela RETLW 3 (acesso) 1 (ADDWF PCL) N + 1 words Value lookup, signal generation
Loop aninhado 2 níveis ~3 (inner) 4 (2 per level) Low Delays longos, N > 256

Conclusion

As técnicas de Dattalo para loops em PIC Assembly não são apenas truques de otimização — são a expressão de um entendimento profundo da arquitetura Harvard do PIC e do custo real de cada instrução. A filosofia subjacente é sempre a mesma: entender o que o hardware faz em nível de ciclo e explorar isso ao máximo.

Os padrões apresentados aqui — DECFSZ + GOTO como estrutura base, ajuste fino com NOPs e GOTO $+1, loops aninhados para delays longos, loop unrolling para seções críticas e tabelas RETLW para lookup — formam um vocabulário completo para escrever Assembly PIC eficiente. Combinados com as técnicas de PWM e geração de sinais documentadas por Dattalo, eles permitem implementar algoritmos sofisticados em microcontroladores com apenas 2K palavras de programa e 68 bytes de RAM.

📚
Related Dattalo articles on the site
PWM Techniques — Techniques de geração de PWM por software usando phase-shifted counters e phase accumulators.
Generating Sine Waves in Software — 10 métodos para geração de senoides, incluindo tabelas RETLW e o algoritmo de Goertzel.
Square Root Theory and Algorithms — Square root algorithms for 8-bit microcontrollers.
LH
Luis H. Campos
Electrical Engineer · camposlh.com
Electrical Engineer with over 10 years of experience in electronic projects, PCB design, and embedded systems. Specialized in PIC, ESP32, and STM32 microcontrollers, firmware and hardware development for industrial and IoT applications.