Lezione 37 di 43

Lezione 36

🎓 PIC16 — From Zero to Hero · Lezione 36 · Modulo 6

Ottimizzazione e Profiling del Codice ⚡

Contare i cicli macchina con TMR0. ISR a latenza minima con il trick SWAPF.
Bank switching raggruppato. Inline vs subroutine. Loop unrolling.

🎯 Cosa impari in questa lezione

  • Costo in cicli di ogni categoria di istruzione PIC16: 1 ciclo per ALU/MOV/bit, 2 per GOTO/CALL/RETURN e per ogni skip preso
  • Profiling con TMR0 come cronometro: leggi TMR0 prima e dopo il codice da misurare, sottrai → cicli esatti
  • ISR a latenza minima: il trick SWAPF salva STATUS senza modificare Z/C, risparmiando 1 ciclo rispetto al metodo naive con MOVF
  • Bank switching raggruppato: tutti gli accessi a Bank1 in un solo blocco BSF/BCF → elimina cambi di banco ripetuti (risparmio fino al 25%)
  • Inline vs subroutine: CALL+RETURN = 4 cicli overhead fisso — inline vince con corpo <3 istruzioni o <4 chiamate, subroutine vince per Flash
  • Loop unrolling ×4: riduce overhead DECFSZ+GOTO da 3 cicli ogni iterazione a 0.75 cicli → 21% più veloce, 4× più Flash
  • Principio di Amdahl applicato al PIC: ottimizzare il 10% del codice che gira il 90% del tempo

📋 Scheda lezione

Lezione: 36 / 42
Modulo: 6 — Tecniche avanzate
Device: PIC16F877A
Prerequisiti: Lez. 16 (TMR0), Lez. 21 (ISR)
Difficoltà: ★★★★☆
Tecnica: profiling, ottimizzazione cicli
Applicazioni: real-time, bit-banging, DSP


1 · Costo in cicli: la tabella di riferimento

Con Fosc=4MHz ogni ciclo macchina dura 1µs (4 periodi di clock ÷ 4 = 1 ciclo = 1µs). La pipeline a 2 stadi del PIC16 fa sì che quasi tutte le istruzioni completino in 1 ciclo — ma ogni trasferimento di controllo (GOTO, CALL, RETURN) e ogni skip preso richiedono 2 cicli perché invalidano la fase di fetch già avviata. Conoscere questi costi a memoria è il primo passo per scrivere codice real-time affidabile.

Costi cicli per istruzione PIC16 @ 4 MHz (1 ciclo = 1µs) Istruzione Cicli (normale) Cicli (skip/branch) Note MOVLW, MOVWF, MOVF, CLRF 1 — tutte le MOV ADDWF, SUBWF, ANDWF, XORWF 1 — ALU reg-reg BSF, BCF, BTFSS, BTFSC 1 2 (se salta) bit ops DECFSZ, INCFSZ 1 2 (se salta) dec/inc+skip GOTO 2 — sempre 2 CALL 2 — push PC stack RETURN, RETLW, RETFIE 2 — pop PC stack NOP 1 — delay preciso Fosc=4MHz → ciclo macchina = 4 periodi clock = 1µs · Pipeline 2-stage: fetch+execute in parallelo

2 · Profiling con TMR0 — misura cicli esatti

TMR0 è un contatore a 8 bit che avanza a ogni ciclo macchina (con prescaler al WDT e clock interno). Configurando OPTION_REG con PSA=1 (prescaler assegnato al WDT, non a TMR0), TMR0 conta 1 tick = 1µs = 1 ciclo. La tecnica: salva TMR0 prima del codice da misurare, riesegui il codice, leggi TMR0 di nuovo, sottrai. Il risultato è il numero esatto di cicli macchina — inclusa la latenza di lettura stessa (2 cicli per le due MOVF TMR0).

⏱ Limite TMR0 come cronometro: TMR0 è un registro da 8 bit — overflow a 256µs. Per misurare codice che dura più di 256µs, usa TMR1 (16 bit, overflow a 65535µs = 65ms). Per codice <256 cicli TMR0 è perfetto. Attenzione: leggere TMR0 due volte costa ~2 cicli che vanno sottratti manualmente dal risultato per avere la misura pulita del codice sotto test.

; ============================================================
; Lezione 36 — Contare i cicli macchina: profiling manuale
; PIC16F877A @ 4 MHz
; Ogni istruzione = 1 ciclo = 1µs tranne GOTO/CALL/RETURN/
; RETLW/RETFIE/DECFSZ-skip/INCFSZ-skip = 2 cicli
; Tecnica: usa TMR0 come cronometro, leggi prima e dopo
; TMR0 conta a Fosc/4 (=1MHz), prescaler OFF → 1 tick = 1µs
; ============================================================
    LIST    P=16F877A
    #INCLUDE <p16f877a.inc>
    __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF

    CBLOCK  0x20
T_START         ; valore TMR0 all&#x27;inizio
T_END           ; valore TMR0 alla fine
T_ELAPSED       ; cicli trascorsi
LOOP_CNT
ACC
FAST_RES        ; risultato versione veloce
SLOW_RES        ; risultato versione lenta
    ENDC

    ORG     0x0000
    GOTO    INIT

    ORG     0x0030
INIT
    BSF     STATUS,RP0
    CLRF    TRISB
    ; TMR0: prescaler off (assegnato a WDT), clock interno
    MOVLW   b'11001000'        ; T0CS=0 (interno), PSA=1 (prescaler→WDT)
    MOVWF   OPTION_REG
    BCF     STATUS,RP0

; ============================================================
; PROFILING — misura cicli di due versioni dello stesso calcolo
; CALCOLO: somma di 10 + 20 + 30 + 40 + 50 = 150
; Versione A: addizioni inline (veloce, usa più Flash)
; Versione B: loop con tabella (lento, usa meno Flash)
; ============================================================

; --- Versione A: inline (no loop) ---
    MOVF    TMR0,W
    MOVWF   T_START            ; t0

    CLRF    ACC
    MOVLW   D'10'
    ADDWF   ACC,F              ; +10
    MOVLW   D'20'
    ADDWF   ACC,F              ; +20
    MOVLW   D'30'
    ADDWF   ACC,F              ; +30
    MOVLW   D'40'
    ADDWF   ACC,F              ; +40
    MOVLW   D'50'
    ADDWF   ACC,F              ; +50
    ; 11 istruzioni × 1 ciclo = 11µs

    MOVF    TMR0,W
    MOVWF   T_END              ; t1
    MOVF    T_START,W
    SUBWF   T_END,W
    MOVWF   T_ELAPSED          ; dovrebbe essere ~13 (11 + 2 per le MOVFs)
    MOVF    ACC,W
    MOVWF   FAST_RES           ; salva risultato (150)

; --- Versione B: loop (5 iterazioni) ---
    MOVF    TMR0,W
    MOVWF   T_START

    CLRF    ACC
    MOVLW   D'5'
    MOVWF   LOOP_CNT
    MOVLW   D'10'              ; valore iniziale
SLOW_LOOP
    ADDWF   ACC,F              ; accumula
    ADDLW   D'10'              ; prossimo valore (+10)
    DECFSZ  LOOP_CNT,F        ; 1 ciclo (normale) / 2 (skip)
    GOTO    SLOW_LOOP          ; 2 cicli
    ; Per iterazione: 1+1+1+2 = 5 cicli (senza skip) + 2 (skip finale)
    ; 4 iter × 5 + 1 × 6 = 26 cicli + setup ≈ 33µs totali

    MOVF    TMR0,W
    MOVWF   T_END
    MOVF    T_START,W
    SUBWF   T_END,W
    MOVWF   T_ELAPSED          ; ~35 cicli vs ~13 della versione inline
    MOVF    ACC,W
    MOVWF   SLOW_RES

    ; Versione A vince: 13µs vs ~35µs — 2.7× più veloce
    ; Ma usa 11 istruzioni vs 8 → tradeoff Flash/velocità

; ============================================================
; OTTIMIZZAZIONE loop: DECFSZ vs SUBWF+BTFSS
; Misura overhead chiamata subroutine vs inline
; ============================================================
    MOVF    TMR0,W
    MOVWF   T_START

    ; 100 NOP inline — benchmark base: 100 cicli
    ; (dimostrato con 10 NOP × CALL×10 = overhead call)
    MOVLW   D'10'
    MOVWF   LOOP_CNT
NOP_LOOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP                        ; 10 NOP = 10 cicli per iter
    DECFSZ  LOOP_CNT,F        ; 1 ciclo
    GOTO    NOP_LOOP           ; 2 cicli → 13 cicli/iter × 10 = 130
                               ; + skip finale = 2 → totale ~132 cicli

    MOVF    TMR0,W
    MOVWF   T_END
    MOVF    T_START,W
    SUBWF   T_END,W
    MOVWF   T_ELAPSED          ; ~134 cicli (2 per letture TMR0)

DONE
    GOTO    DONE

    END

3 · ISR a bassa latenza: il trick SWAPF

Il problema classico del salvataggio STATUS nell’ISR: MOVF STATUS,W legge STATUS ma modifica il flag Z — corrompendo esattamente il registro che si vuole salvare. La soluzione è SWAPF STATUS,W: l’istruzione scambia i nibble alto e basso di STATUS e mette il risultato in W, senza toccare nessun flag. Al ripristino, un secondo SWAPF ri-scambia i nibble riportando STATUS all’originale. Per W il trucco del doppio SWAPF-in-place è ugualmente elegante: SWAPF W_SHADOW,F poi SWAPF W_SHADOW,W restituisce W originale senza passare per STATUS.

ISR: latenza entrata/uscita — confronto naive vs SWAPF ottimizzato ❌ Naive — 4 cicli salvataggio MOVWF W_SAVE ; 1c – salva W MOVF STATUS,W ; 1c – legge STATUS ; ^ MODIFICA Z! BUG MOVWF ST_SAVE ; 1c – salva STATUS ; STATUS ora corrotto! MOVF ST_SAVE,W ; 1c – ripristina MOVWF STATUS ; 1c MOVF W_SAVE,W ; 1c – ripristina W RETFIE ; 2c ✅ SWAPF trick — 3 cicli salvataggio MOVWF W_SHADOW ; 1c – salva W SWAPF STATUS,W ; 1c – nibble swap ; NO modifica Z/C! MOVWF ST_SHADOW ; 1c – salva STATUS ; totale: 3 cicli (1 in meno) SWAPF ST_SHADOW,W ; 1c – ri-swap MOVWF STATUS ; 1c – ripristina SWAPF W_SHADOW,F ; 1c – swap in place SWAPF W_SHADOW,W ; 1c – W originale
; ============================================================
; Lezione 36 — ISR a bassa latenza: tecniche di ottimizzazione
; PIC16F877A @ 4 MHz
; Latenza ISR standard: 3+1+2+2 = 8 cicli (vettore→primo utile)
; Con salvataggio W/STATUS: +4 cicli → 12 cicli prima del codice
; Obiettivo: ridurre al minimo con shadow registers e SWAPF trick
; ============================================================
    LIST    P=16F877A
    #INCLUDE <p16f877a.inc>
    __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF

; Area shared: accessibile da qualsiasi banco senza cambiare RP0
#DEFINE W_SHADOW    0x70       ; salvataggio W
#DEFINE ST_SHADOW   0x71       ; salvataggio STATUS

    CBLOCK  0x20
ISR_COUNT       ; contatore interrupt
EDGE_TIME_H     ; timestamp interrupt alto
EDGE_TIME_L     ; timestamp interrupt basso
PERIOD_H        ; periodo misurato (alto)
PERIOD_L        ; periodo basso
PREV_H
PREV_L
PULSE_COUNT
FLAGS           ; bit0=nuovo_dato, bit1=overflow
    ENDC

    ORG     0x0000
    GOTO    INIT

; ============================================================
; ISR OTTIMIZZATA — latenza minima
; Trick SWAPF: non modifica STATUS (a differenza di MOVF)
; Ordine: salva W con MOVWF (1c), poi STATUS con SWAPF (1c)
; Nessuna PUSH/POP: tutto in locazioni fisse shared
; ============================================================
    ORG     0x0004
ISR_FAST
    ; --- SALVATAGGIO CONTESTO: 3 istruzioni ---
    MOVWF   W_SHADOW           ; salva W (1c, non tocca STATUS)
    SWAPF   STATUS,W           ; leggi STATUS nibble-swappato (1c, non tocca Z/C!)
    MOVWF   ST_SHADOW          ; salva STATUS (1c)
    ; Totale salvataggio: 3 cicli (vs 4 del metodo naive)

    BCF     STATUS,RP0         ; assicura Bank 0

    ; --- IDENTIFICA SORGENTE ---
    BTFSS   INTCON,INTF        ; INT0 (RB0)?
    GOTO    CHECK_TMR0

    ; --- GESTIONE INT0: misura periodo impulso ---
    BCF     INTCON,INTF        ; azzera flag
    ; Leggi TMR1 come timestamp (16 bit)
    MOVF    TMR1L,W
    MOVWF   EDGE_TIME_L
    MOVF    TMR1H,W
    MOVWF   EDGE_TIME_H
    ; Calcola periodo = TIME_CURR - TIME_PREV
    MOVF    PREV_L,W
    SUBWF   EDGE_TIME_L,W
    MOVWF   PERIOD_L
    MOVF    PREV_H,W
    BTFSS   STATUS,C
    INCFSZ  PREV_H,W           ; borrow dalla parte bassa
    SUBWF   EDGE_TIME_H,W
    MOVWF   PERIOD_H
    ; Aggiorna PREV
    MOVF    EDGE_TIME_L,W
    MOVWF   PREV_L
    MOVF    EDGE_TIME_H,W
    MOVWF   PREV_H
    ; Segnala nuovo dato
    BSF     FLAGS,0
    INCF    PULSE_COUNT,F
    GOTO    ISR_EXIT

CHECK_TMR0
    BTFSS   INTCON,T0IF
    GOTO    ISR_EXIT
    BCF     INTCON,T0IF
    INCF    ISR_COUNT,F
    GOTO    ISR_EXIT

ISR_EXIT
    ; --- RIPRISTINO CONTESTO: 3 istruzioni ---
    SWAPF   ST_SHADOW,W        ; ri-nibble-swap → STATUS originale
    MOVWF   STATUS             ; ripristina STATUS (1c)
    SWAPF   W_SHADOW,F         ; prepara W: swap in place (1c)
    SWAPF   W_SHADOW,W         ; ri-swap → W originale (1c, non tocca STATUS)
    RETFIE                     ; 2 cicli
    ; Totale ripristino: 5 cicli (stessa latenza di ritorno)

    ORG     0x0060
INIT
    ; Configura TMR1 come contatore libero (timestamp)
    CLRF    T1CON              ; TMR1 off
    CLRF    TMR1H
    CLRF    TMR1L
    MOVLW   b'00000001'        ; TMR1ON
    MOVWF   T1CON

    ; INT0 su RB0: fronte di salita
    BSF     STATUS,RP0
    MOVLW   b'11111111'
    MOVWF   TRISB              ; RB0 input
    BCF     STATUS,RP0

    CLRF    FLAGS
    CLRF    PULSE_COUNT
    CLRF    ISR_COUNT
    CLRF    PREV_H
    CLRF    PREV_L

    ; Abilita INT0 e TMR0
    MOVLW   b'10110000'        ; GIE=1, T0IE=1(bit5), INTE=1(bit4)
    MOVWF   INTCON

; ============================================================
; MAIN — polling FLAGS e output
; ============================================================
MAIN
    BTFSS   FLAGS,0            ; nuovo dato?
    GOTO    MAIN
    BCF     FLAGS,0
    ; Usa PERIOD_H:PERIOD_L — frequenza = 1.000.000 / PERIOD
    ; Esempio: PERIOD=1000 → 1kHz
    MOVF    PERIOD_L,W
    MOVWF   PORTB              ; mostra byte basso periodo (proxy freq)
    GOTO    MAIN

    END

4 · Bank switching, inline vs subroutine, loop unrolling

Tradeoff: inline vs subroutine — Flash (word) vs velocità (cicli) Numero di chiamate alla funzione Cicli totali inline sub 1 2 3 4 5 6 breakeven Regola pratica Inline: corpo < 3 istruzioni O chiamate < 4 → usa inline (meno cicli) Sub: corpo ≥ 3 istruzioni E chiamate ≥ 4 → usa subroutine (meno Flash)
; ============================================================
; Lezione 36 — Bank switching efficiente e inline vs subroutine
; PIC16F877A @ 4 MHz
; Regola d'oro: raggruppa accessi allo stesso banco per minimizzare
; il numero di BSF/BCF STATUS,RP0 (ogni cambio = 1 ciclo)
; Inline vs subroutine: CALL+RETURN = 4 cicli overhead fisso
; ============================================================
    LIST    P=16F877A
    #INCLUDE <p16f877a.inc>
    __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF

    CBLOCK  0x20
CONFIG_REG1     ; valore da scrivere in Bank1
CONFIG_REG2
RESULT1
RESULT2
RESULT3
TMP
    ENDC

    ORG     0x0000
    GOTO    INIT

    ORG     0x0040
INIT

; ============================================================
; ESEMPIO A — bank switching INEFFICIENTE (da evitare)
; Ogni accesso a Bank1 causa un doppio cambio banco
; ============================================================
; Cicli: 1+1+1+1+1+1+1+1+1+1 = 10 cambi banco = 10 cicli sprecati
    MOVLW   0x07               ; configura ADCON1 (Bank1)
    BSF     STATUS,RP0         ; → Bank1  [1c]
    MOVWF   ADCON1             ; scrivi    [1c]
    BCF     STATUS,RP0         ; → Bank0  [1c]

    MOVLW   0x00               ; configura TRISD (Bank1)
    BSF     STATUS,RP0         ; → Bank1  [1c] ← inutile!
    MOVWF   TRISD              ; scrivi    [1c]
    BCF     STATUS,RP0         ; → Bank0  [1c]

    MOVLW   0xFF               ; configura TRISB (Bank1)
    BSF     STATUS,RP0         ; → Bank1  [1c] ← inutile!
    MOVWF   TRISB              ; scrivi    [1c]
    BCF     STATUS,RP0         ; → Bank0  [1c]
    ; Totale: 12 istruzioni, 12 cicli, 6 cambi banco

; ============================================================
; ESEMPIO B — bank switching OTTIMIZZATO (raggruppato)
; Un solo cambio banco per tutti gli accessi a Bank1
; ============================================================
    BSF     STATUS,RP0         ; → Bank1  [1c] ← UN SOLO cambio
    MOVLW   0x07
    MOVWF   ADCON1             ; [1c]
    MOVLW   0x00
    MOVWF   TRISD              ; [1c]
    MOVLW   0xFF
    MOVWF   TRISB              ; [1c]
    BCF     STATUS,RP0         ; → Bank0  [1c] ← UN SOLO ritorno
    ; Totale: 9 istruzioni, 9 cicli, 2 cambi banco
    ; Risparmio: 3 istruzioni e 3 cicli (il 25% in meno!)

; ============================================================
; INLINE vs SUBROUTINE — tradeoff velocità/spazio
; Funzione: calcola val XOR 0xAA e scrivi su PORTB
; ============================================================

; --- Inline (chiamata 3 volte) ---
; Costo: 3 × 3 istruzioni = 9 word Flash, 9 cicli
    MOVLW   0x55
    XORLW   0xAA               ; 0xFF
    MOVWF   PORTB              ; [3 istr, 3c]

    MOVLW   0x0F
    XORLW   0xAA               ; 0xA5
    MOVWF   PORTB              ; [3 istr, 3c]

    MOVLW   0x3C
    XORLW   0xAA               ; 0x96
    MOVWF   PORTB              ; [3 istr, 3c]
    ; Flash usata: 9 word, Tempo: 9 cicli

; --- Subroutine (chiamata 3 volte) ---
; Costo: 3 × (MOVLW + CALL) + corpo 2 istr = 3×2+2 = 8 word Flash
; Tempo: 3 × (1+2+2+2) = 21 cicli (MOVLW + CALL + corpo + RETURN)
    MOVLW   0x55
    CALL    XOR_OUT            ; [1+2+2+2 = 7c]
    MOVLW   0x0F
    CALL    XOR_OUT            ; [7c]
    MOVLW   0x3C
    CALL    XOR_OUT            ; [7c]
    ; Flash usata: 8 word (meno!), Tempo: 21 cicli (peggio!)
    ; CALL vale la pena solo con corpo ≥ 3 istruzioni o ≥ 4 chiamate

; ============================================================
; OTTIMIZZAZIONE LOOP CRITICO — riduzione istruzioni per ciclo
; Goal: output massima velocità possibile su PORTD
; Versione 1: loop normale (overhead alto)
; Versione 2: loop unrolled parzialmente
; ============================================================

    ; Versione 1: loop normale, 256 iterazioni
    CLRF    TMP
BCF     STATUS,RP0
    BSF     STATUS,RP0
    CLRF    TRISD
    BCF     STATUS,RP0
LOOP_V1
    MOVF    TMP,W
    MOVWF   PORTD              ; output
    INCF    TMP,F
    DECFSZ  TMP,W              ; trucco: decrementa copia in W, non TMP
    GOTO    LOOP_V1            ; 1+1+1+1+2 = 6 cicli/iter × 256 = 1536c
    ; Nota: DECFSZ su W=0 è inutile, meglio usare INCF con overflow

    ; Versione corretta: usa il fatto che INCF su 255 torna a 0 (overflow)
    CLRF    TMP
LOOP_V2
    MOVF    TMP,W
    MOVWF   PORTD
    INCF    TMP,F              ; 255+1=0, Z=1
    BTFSS   STATUS,Z           ; se non zero, continua
    GOTO    LOOP_V2            ; 1+1+1+1+2 = 6 cicli/iter − nessun miglioramento

    ; Versione 3: unroll × 4 (meno overhead, più Flash)
    CLRF    TMP
LOOP_V3
    MOVF    TMP,W
    MOVWF   PORTD              ; iter 0
    INCF    TMP,F
    MOVF    TMP,W
    MOVWF   PORTD              ; iter 1
    INCF    TMP,F
    MOVF    TMP,W
    MOVWF   PORTD              ; iter 2
    INCF    TMP,F
    MOVF    TMP,W
    MOVWF   PORTD              ; iter 3
    INCF    TMP,F
    BTFSS   STATUS,Z           ; ogni 4 iter controlla overflow (TMP==0)
    GOTO    LOOP_V3
    ; 4+4+4+4+1+2 = 4×(1+1) + check = 4 iter × 4c + 3c overhead/4 = ~4.75c/iter
    ; vs 6c/iter della V1 → 21% più veloce, usa 4× Flash per il corpo

MAIN
    GOTO    MAIN

XOR_OUT
    XORLW   0xAA
    MOVWF   PORTB
    RETURN

    END

🧪 Prova tu

  1. Carica il profiling TMR0. Esegui la versione A (inline). T_ELAPSED dovrebbe valere circa 13–14 (11 istruzioni + 2 overhead letture TMR0 + 1 GOTO init). Poi esegui la versione B (loop). T_ELAPSED dovrebbe essere circa 33–36. Calcola il rapporto: quante volte è più lenta? Spiega perché con il conteggio dei cicli per iterazione.
  2. Modifica la ISR ottimizzata: aggiungi un secondo sorgente interrupt (TMR0). Usa la stessa struttura SWAPF per salvataggio/ripristino. Misura la latenza aggiuntiva (cicli sprecati nel check BTFSS INTCON,INTF prima di arrivare a CHECK_TMR0). Come ridurla se TMR0 è il sorgente più frequente? (Suggerimento: metti il controllo più frequente per primo.)
  3. Implementa un ritardo preciso di esattamente 500µs senza timer hardware, solo con NOP e loop. Calcola: quanti loop da X cicli ciascuno? Verifica con TMR0 che T_ELAPSED sia 500±2. (A 4MHz, 500µs = 500 cicli.)
  4. Sfida: ottimizza questo loop critico per la massima velocità su PORTD. Il loop deve scrivere i valori 0x00, 0x55, 0xAA, 0xFF in sequenza continua. Versione naive: 4 MOVLW + 4 MOVWF + GOTO = ~12 cicli/ciclo-output. Versione ottimizzata con tabella RETLW e contatore di fase: quanti cicli? Versione con scrittura diretta senza loop: quanti cicli per 4 output?

❓ Quiz rapido

1. Perché MOVF STATUS,W non va usato per salvare STATUS nell’ISR, e come fa SWAPF STATUS,W a risolvere il problema?

Mostra risposta

MOVF imposta il flag Z in STATUS in base al valore letto (Z=1 se STATUS=0x00, Z=0 altrimenti). Questo significa che il bit Z di STATUS viene modificato dall’atto stesso di leggerlo — una contraddizione: vuoi salvare STATUS, ma il salvataggio lo corrompe. SWAPF invece esegue uno scambio dei nibble (bit7:4 ↔ bit3:0) e non modifica nessun flag: C, Z, DC rimangono invariati. L’unico effetto è che il valore in W ha i nibble scambiati — ma questo non importa perché al ripristino un secondo SWAPF riscambia, restituendo il valore originale. Il doppio SWAPF è in sostanza un’operazione nulla sulla sequenza completa.


2. Un loop ha corpo da 2 istruzioni e viene chiamato 10 volte. Conviene inline o subroutine? E se il corpo fosse 8 istruzioni?

Mostra risposta

Con corpo da 2 istruzioni: inline costa 2×10=20 cicli e 20 word Flash. Subroutine costa (1+2)×10 + 2+2 = 34 cicli (MOVLW+CALL per ogni chiamata + corpo+RETURN) e 2+4=6 word Flash. Inline è più veloce (20 vs 34) ma usa più Flash (20 vs 6). Con corpo da 8 istruzioni: inline = 8×10=80 cicli, 80 word Flash. Subroutine = (1+2+8+2)×10=130 cicli, ma solo 8+2=10 word Flash. Inline è ancora più veloce (80 vs 130 cicli), ma subroutine usa 8× meno Flash. La regola “subroutine è più veloce con più chiamate” è spesso un malinteso: la subroutine risparmia sempre Flash con N≥2 chiamate, ma è sempre più lenta in cicli a causa dei 4 cicli fissi di CALL+RETURN. Se la velocità è prioritaria, inline quasi sempre vince.


3. Il loop con DECFSZ e GOTO ha overhead di 3 cicli ogni iterazione (1 per DECFSZ + 2 per GOTO). Con unroll ×4 l’overhead scende a ~0.75 cicli/iterazione. Qual è il limite dell’unrolling e quando smette di essere conveniente?

Mostra risposta

L’unrolling è conveniente finché il risparmio in cicli supera il costo in Flash. Con corpo da 2 istruzioni: unroll ×4 usa 4×2+3=11 word per 4 iter invece di 2+3=5 word per 1 iter (×4=20 word) — quindi risparmia Flash E cicli, ottimo. Ma con unroll ×256 (= nessun loop) per 256 iterazioni, si usa 256×2=512 word di Flash solo per il corpo: su un PIC16F84A con 1024 word totali è il 50% della Flash solo per un loop. Il limite pratico è che la Flash del programma deve rimanere utilizzabile, e che un loop unrolled ×N richiede che N divida esattamente il numero di iterazioni. Un ulteriore limite: con unroll molto aggressivo si perde la località di istruzioni nella cache istruzioni (pipeline), anche se su PIC16 senza cache questo conta meno.

Prossima lezione →

Lezione 37 — Bit-banging: protocolli software
Implementare 1-Wire, SPI software e I²C software senza periferiche hardware. Timing preciso con NOP. Dallas DS18B20 temperatura via 1-Wire. Tutto con le istruzioni che conosci già.

© Prof. D. Bertolino · PIC16 From Zero to Hero · WebPicSimulator · Lezione 36