Low Cost FPGA Konfiguration

Wechseln zu: Navigation, Suche

Einleitung

Wenn man FPGAs auf selbst entworfenen Platinen einsetzen möchte, stellt sich oft das Problem des Konfigurationsspeichers. Die meisten FPGAs sind SRAM-basiert und haben keinen nichtflüchtigen Speicher auf dem Chip bzw. im gleichen Gehäuse (Ausnahmen sind Spartan-3N von Xilinx, XP und XP2 von Lattice und die FPGAs von Actel in Antifuse-Technologie). Also muss irgendwie ein Speicher her. Dazu gibt es mehrere Möglichkeiten.

Konfigurationsmethode Vorteile Nachteile
PC mit Programmierkabel einfache und schnelle Programmierung Platine kann nur mit PC genutzt werden
Spezial-PROM
z. B. Xilinx XCFxxS
einfachste Anwendung hoher Preis
schlechte Verfügbarkeit für Hobbybastler
Standard-EEPROM mit SPI einfache Anwendung
billig
nur bei einigen FPGA-Familien möglich (z. B. Spartan3E)
Mikrocontroller mit
internem oder externem Flash
sehr gute Verfügbarkeit für Hobbybastler
billig
zusätzlicher Mikrocontroller nötig
wenn das FPGA allein auf der Platine betrieben wird
Zusatzaufwand für die Programmierung des EEPROMs und Mikrocontrollers

Dieser Artikel soll die letzte Methode näher beleuchten. Dabei wird ein FPGA vom Typ Spartan 2 von Xilinx sowie ein Mikrocontroller der AVR-Familie vom Typ Tiny12 von Atmel eingesetzt. Da die FPGAs anderer Hersteller aber sehr ähnlich konfiguriert werden, kann man das Ganze leicht anpassen.

FPGA Konfiguration mit Mikrocontroller

Für kleine bis mittlere FPGAs wird nur verhältnismässig wenig Konfigurationsspeicher benötigt. Auf der anderen Seite gibt es kleine EEPROMs im SO-8 Gehäuse mit I2C Schnittstelle. Damit kann sehr kostengünstig und platzsparend die Konfiguration gespeichert werden. Nun braucht man nur noch einen kleinen Mikrocontroller, welcher das EEPROM ausliest und die Daten zum FPGA schickt. Glücklicherweise gibt es auch die im sehr kleinen SO-8 Gehäuse. Damit ist die Lösung sehr platzsparend.

FPGA-Familie Typ Konfiguration
[Bit]
EEPROM-Anzahl Konfigurationszeit [s]
I2C mit 400kBit/s
24C128 24C256 24C512
Spartan 2 XC2S15 197.696 2 1 1 0,5
XC2S30 336.768 3 2 1 0,8
XC2S50 559.200 5 3 2 1,4
XC2S100 781.216 6 3 2 2
XC2S150 1.040.096 8 4 2 2,6
XC2S200 1.335.840 * 6 3 3,3
Spartan 3 XC3S50 439.264 4 2 1 1,1
XC3S200 1.047.616 8 4 2 2,6
XC3S400 1.699.136 * 7 4 4,2
XC3S1000 3.223.488 * * 7 8,1

Schaltung

Die Schaltung ist recht einfach. Über den I2C-Bus wird ein oder mehrere EEPROMs ausgelesen. Zur Konfiguration sind nur die vier Signale CCLK, Din, Done und PROGRAM benötigt. Der FPGA wird im Slave Serial Mode betrieben. Da der 8-polige Mikrocontroller aber nur 5 wirklich freie IO-Pins hat, müssen Reset und PROGRAM verbunden werden. Das ist aber kein Problem, im Gegenteil, hier werden FPGA und Mikrocontroller parallel in den Resetzustand versetzt, wenn die Versorgungsspannung angelegt wird oder manuell PROGAMM auf LOW gezogen wird. Es ist jeder beliebige AVR verwendbar, da das Programm sehr klein ist und keinerlei spezielle Resourcen benötigt, nicht einmal SRAM. Es können EEPROMs mit 128, 256 oder 512kbit eingesetzt werden. Allerdings müssen alle die gleiche Grösse haben. Eine LED am Signal SDA wird zur Fehleranzeige verwendet. Es sollte ein Low Current Typ mit niedriger Flusspannung sein (also keinen blauen oder weissen LEDs). R5 dient der Entkopplung von DONE zur ISP-Schnittstelle. Damit kann der AVR in der Schaltung programmiert werden.

Schaltplan des FPGA Konfigurators


Programmierung des Mikrocontrollers

Die Programmierung des Konfigurationsprogramms erfolgt in Assembler. Zum einen um eine minimale Programmgröße und maximale Geschwindigkeit zu erzielen. Zum anderen, weil kleine AVRs ohne SRAM vom AVR-GCC nicht unterstützt werden. Das ist aber unkritisch, das Programm ist klein und damit überschaubar.

Nach dem Reset wird der Oszillator auf maximalen Takt gestellt, der ist beim Tiny12 sowieso nicht so hoch, max. 2,5 MHz. Dann werden die IOs und Variablen initialisiert. Am I2C-Bus können maximal acht EEPROMs angeschlossen werden, weil diese ICs nur maximal drei konfigurierbare Adressbits besitzen, einige Typen sogar nur zwei. Wenn die Größe der Konfigurationsdatei die Größe eines einzelnen EEPROMS übersteigt, muss schrittweise aus den einzelnen EEPROMs gelesen werden. Dazu muss der jeweilige EEPROM zum Beginn adressiert werden. Danach können lückenlos die Daten aus dem EEPROM gelesen werden. Nachdem alle Bytes geladen wurden wird noch ein zusätzliches Byte geschickt, um die Startsequenz des FPGAs auszulösen. Ist die Konfiguration erfolgreich verlaufen ist danach der FPGA in Funktion. Das wird durch das Signal Done angezeigt.

 
.include "tn12def.inc"

;*******************************************************************************
;*
;* A small but fine FPGA configurator
;* using a Tiny 12 and serial EEPROMs 
;*
;* Up to eigth EEPROMs can be cascaded on the I2C bus
;* All devices must be of same size
;*
;* theoritical limit is 8x64kB = 512 kB
;*
;* Examles for FPGA config size
;* Spartan-II : XC2S200(biggest family member): 166,980 Bytes
;* Spartan-3  : XC3S1000                      : 402,936 Bytes
;*
;* Configuration speed is ~8.5kBytes/s @ 2MHz
;* Top speed is ~ 43kB/s @ 10 MHz, 400kHz limit of I2C
;*
;* example uses a Xilinx XC2S30 with 42.096 bytes config size
;* and a ST24C512 with 64 kB

.equ fpga_size   = 42096;           ; FPGA config file size
.equ EEPROM_size = 65536;           ; EEPROM size per device
.equ F_CPU_MHz   = 2;               ; CPU clock in MHz, 1..10

.equ bitreverse  = 0               

;*  = 0 , bits will be copied bit by bit from EEPROM to FPGA, faster configuration
;*        binary files must have same bitorder as required by FPGA (MSB first)
;*        For Xilinx, the bit files must be processed by bitdreher.exe!!!

;*  = 1 , bits will be copied byte by bytes from EEPROM and written bit reversed to FPGA, slow configuration
;*        binary files must have reverse bitorder as required by FPGA (LSB first)
;*        For Xilinx, the bit files are generated this way by default

;*******************************************************************************
 
.equ cfg_port = portb
.equ cfg_ddr  = ddrb
.equ cfg_pin  = pinb  

.equ DIN   = PB0        ; FPGA serial configuration
.equ DONE  = PB1
.equ CCLK  = PB2
.equ SDA   = PB3        ; Connection to I2C EEPROM
.equ SCL   = PB4

; Reset is connected to FPGA AND AVR, so we have a automatic Power-on reset

; some marcos to support readability

.macro SDA_LOW
    sbi     cfg_ddr, sda    ; open drain, low
.endmacro 

.macro SDA_HIGH
    cbi     cfg_ddr, sda    ; open drain, high
.endmacro

.macro SCL_LOW
    sbi     cfg_ddr, scl    ; open drain, low
.endmacro 

.macro SCL_HIGH
    cbi     cfg_ddr, scl    ; open drain, high
.endmacro

.macro DIN_LOW
    cbi     cfg_port, din   ; push pull
.endmacro 

.macro DIN_HIGH
    sbi     cfg_port, din   ; push pull
.endmacro

.macro CCLK_LOW
    cbi     cfg_port, cclk  ; push pull
.endmacro 

.macro CCLK_HIGH
    sbi     cfg_port, cclk  ; push pull
.endmacro

.macro i2c_error_check
    brts    PC+2
    rjmp    error_i2c
.endmacro

; add a delay to reach given delay time
; usage
; delay_1 time [Unit 0.1us], clocks_already_done
.macro delay

; calculate number of remaining clocks
; +1 is safety margin to account for integer truncation

.set clks = @0*F_CPU_MHz/10-@1+1

; use optimized delay blocks with "nonsense" commands to have maximum
; delay with minimum code size

.if clks==1
    nop
.endif

.if clks==2
    ld  null, z
.endif

.if clks==3
    lpm
.endif

.if clks==4
    lpm
    nop
.endif

.if clks==5
    lpm
    ld  null, z
.endif

.if clks==6
    lpm
    lpm
.endif

.if clks==7
    lpm
    lpm
    nop
.endif

.if clks==8
    lpm
    lpm
    ld  null,z
.endif

.if clks==9
    lpm
    lpm
    lpm
.endif

.if clks==10
    lpm
    lpm
    lpm
    nop
.endif

.if clks==11
    lpm
    lpm
    lpm
    ld  null, z
.endif

.if clks==12
    lpm
    lpm
    lpm
    lpm
.endif

.if clks==13
    lpm
    lpm
    lpm
    lpm
    nop
.endif

.if clks==14
    lpm
    lpm
    lpm
    lpm
    ld  null, z
.endif

.endmacro

.def zero  = r1         ; always zero
.def one   = r2         ; always one
.def null  = r3         ; write only ;-)
.def tmp1  = r16        ; general purpose
.def tmp2  = r17
.def tmp3  = r18
.def cnt0  = r19        ; loop counter, 24 Bit
.def cnt1  = r20
.def cnt2  = r21            
.def i2c   = r22        ; I2C address, used for multiple devices
.def abs0  = r23        ; data counter, 24 Bit
.def abs1  = r24        ; 
.def abs2  = r25

;*
;*******************************************************************************

.cseg
; lets go

; initialize stack

; hardware stack for Tiny12, no init necessary

; full speed, ~2 MHz with ATtiny12

    ldi     tmp1,0xFF
    out     osccal, tmp1

; config IOs

    ldi     tmp1, (1<<cclk) | (1<<din)
    out     cfg_ddr,tmp1

    ldi     tmp1, (1<<done)
    out     cfg_port,tmp1           ; internal pull up for DONE

; generate constant registers

    sub     zero,zero
    ldi     tmp1,1
    mov     one,tmp1
    ldi     i2c,0xA0                ; I2C base address

; set byte counter

    ldi     abs0,byte1(fpga_size)
    ldi     abs1,byte2(fpga_size)
    ldi     abs2,byte3(fpga_size)
        
; wait 10ms for FPGA to finish power up and clear config space

    ldi     tmp1,10
    rcall   wait

; when using multiple EEPROMs, we must address them individual

device_loop:

; number of remaining bytes >EEPROM_size ?

    ldi     tmp1,byte1(EEPROM_size)
    ldi     tmp2,byte2(EEPROM_size)
    ldi     tmp3,byte3(EEPROM_size)

    cp      abs0,tmp1
    cpc     abs1,tmp2
    cpc     abs2,tmp3
    brge    device_loop_big     ; >= EEPROM_size

    ; less than EEPROM_size, copy remaining bytes
    mov     cnt0,abs0       ; remaining bytes
    mov     cnt1,abs1
    mov     cnt2,abs2
    mov     abs0,zero       ; set zero
    mov     abs1,zero
    mov     abs2,zero
    rjmp    device_loop_init

device_loop_big:
    mov     cnt0,tmp1       ; EEPROM_size loop
    mov     cnt1,tmp2
    mov     cnt2,tmp3
    sub     abs0,tmp1       ; - EEPROM_size
    sbc     abs1,tmp2
    sbc     abs2,tmp3

device_loop_init:
    subi    cnt0,1          ; -1, since last byte must be read with no ACK
    sbci    cnt1,0          ; outside the loop
    sbci    cnt2,0

; write first adress

    rcall   i2c_start
    mov     tmp1, i2c       ; load i2c address
    rcall   i2c_write
    i2c_error_check
    ldi     tmp1, 0         ; write adress 0
    rcall   i2c_write
    i2c_error_check
    ldi     tmp1, 0
    rcall   i2c_write
    i2c_error_check
    rcall   i2c_stop

    rcall   i2c_start
    mov     tmp1, i2c       ; load i2c address
    subi    tmp1,-1         ; +1, read access
    rcall   i2c_write
    i2c_error_check
    set                     ; set T-Flag, continous reading

main_loop:

.if bitreverse==1           
;   configuration uses online bit reverse, slow

    rcall   i2c_read
    rcall   fpga_write

.else
;   configuration uses offline bit reverse, fast (bitdreher.exe)

; read a byte from I2C and parallel send it to the FPGA
; fast version
    ldi     tmp2, 8         ; loop counter

main_bit_loop:
    scl_high                ; SCL rising edge
    sbic    cfg_pin,sda     ; copy SDA to Din
    din_high
    sbis    cfg_pin,sda
    din_low
    delay 7, 6              ; minimum LOW time 0,6µs + 0.1µs safty margin
    scl_low                 ; falling edge generates new bit
    cclk_high               ; LOW time min. 9 CLKs
    cclk_low
    delay 14, 9             ; minimum HIGH time 1,3µs + 0.1µs safty margin
    dec     tmp2
    brne    main_bit_loop

; write ACK to EEPROM

    sda_low                 ; write ACK, more to read
    delay  1, 0
    scl_high
    delay  7, 2
    scl_low
    sda_high
    delay  14, 10

.endif

    subi    cnt0,1          ; decrement data counter
    sbci    cnt1,0
    sbci    cnt2,0
    brne    main_loop

    clt                     ; no ACK for last byte of block
    rcall   i2c_read
    rcall   fpga_write

    rcall   i2c_stop
    subi    i2c,-2          ; +2, increment I2C address bits

; number of remaining data bytes = 0 ?

    cp      abs0,zero
    cpc     abs1,zero
    cpc     abs2,zero
    breq    main_end        ; nothing left to copy, finish

    rjmp    device_loop     ; process next data block

main_end:
    ldi     tmp1,0
    rcall   fpga_write      ; one more dummy byte for FPGA startup

    ldi     tmp1,1
    rcall   wait            ; wait for DONE to go HIGH (open drain)
    sbis    cfg_pin, done   ; check if DONE is HIGH (= FPGA config OK)
    rjmp    error_fpga      ; something went wrong during configuration
    ldi     tmp1, (1<<SE) | (1<<SM)     
    out     mcucr,tmp1      ; power down sleep mode
    sleep                   ; good night
    
;  no wake up here, only through reset

; i2c has no ACK, fast blinking

error_i2c:
    sda_low
    ldi     tmp1,100
    rcall   wait
    sda_high
    ldi     tmp1,100
    rcall   wait
    rjmp    error_i2c

; FPGA configuration error, DONE stays low, slow blinking

error_fpga:
    sda_low
    ldi     tmp1,250
    rcall   wait
    ldi     tmp1,250
    rcall   wait
    sda_high
    ldi     tmp1,250
    rcall   wait
    ldi     tmp1,250
    rcall   wait
    rjmp    error_fpga

; *****************************************************************************

; functions

; *****************************************************************************

; just wait N*1ms, N is given in tmp1

wait:
    ldi     tmp2,20*F_CPU_MHz
wait_1:
    ldi     tmp3,16
wait_2:
    dec     tmp3
    brne    wait_2
    dec     tmp2
    brne    wait_1
    dec     tmp1
    brne    wait
    ret

; *****************************************************************************

; write a byte to I2C
; input  : tmp1   : data
; output : T-flag : ACK (0= NACK (SDA high), 1 = ACK (SDA low))
 
i2c_write:
    ldi     tmp2, 8     ; loop conter
i2c_write_loop:

    sbrs    tmp1,7      ; copy MSB to SDA
    sda_low
    sbrc    tmp1,7
    sda_high

    delay 14, 8 
    scl_high
    delay 7, 2
    scl_low
    lsl     tmp1        ; get next bit
    dec     tmp2
    brne    i2c_write_loop

; check ACK from EEPROM
    sda_high
    delay 14, 5
    scl_high
    clt                     ; clear T-Flag, = no ACK!
    sbis    cfg_pin,sda     ; check SDA is ACK=0 (positive ACK)
    set                     ; ACK=0, OK
    delay 7, 5
    scl_low
    delay 14, 7     
    ret

; *****************************************************************************

; read a byte from I2C
; input  : T-flag : ACK, (0= NACK (SDA high), 1 = ACK (SDA low))
; output : tmp1   : data
i2c_read:
    ldi     tmp2, 8         ; loop conter
i2c_read_loop:
    scl_high
    clc                     ; copy SDA to carry
    sbic    cfg_pin, sda    ; read SDA
    sec                     ; set carry
    rol     tmp1            ; shift in next bit
    delay 7, 6
    scl_low                 ; minimum pulse width LOW = 1.3µs = 13 clks @10 MHz
    delay 14, 5
    dec     tmp2
    brne    i2c_read_loop

; write ACK to EEPROM

    brtc    i2c_read_no_ack
    sda_low                 ; write ACK, more to read
    rjmp    i2c_read_send_ack
i2c_read_no_ack:
    sda_high                ; no ACK, stop reading
i2c_read_send_ack:
    delay 14, 6
    scl_high
    delay 7, 2
    scl_low
    sda_high                ; release SDA
    ret

; *****************************************************************************

; Create a I2C START Condition
  
i2c_start:
    delay 5, 2
    sda_low
    delay 5, 2
    scl_low
    delay 30, 7
    ret

; *****************************************************************************

; Create a I2C STOP Condition

i2c_stop:
    delay 5, 2
    sda_low
    delay 5,2 
    scl_high
    delay 5,2
    sda_high
    delay 30, 7
    ret

; *****************************************************************************

; write a byte to FPGA, with bit reversal
; input  : tmp1   : data

.if bitreverse==1       
.message "Bit reverse ON, slow configuration."  
; configuration uses online bit reverse, slow
; bit reverse, LSB first
fpga_write:
    ldi     tmp2, 8     ; loop conter
fpga_write_loop:

    sbrs    tmp1,0      ; copy LSB to DIN
    din_low
    sbrc    tmp1,0
    din_high

    cclk_high           ; generate cclk pulse
    cclk_low
    lsr     tmp1        ; get next bit
    dec     tmp2
    brne    fpga_write_loop
    ret

; *****************************************************************************
.else
.message "Bit reverse OFF, fast configuration." 
; configuration uses offline bit reverse, fast (bitdreher.exe)
; no bit reverse, MSB first
fpga_write:
    ldi     tmp2, 8     ; loop conter
fpga_write_loop:

    sbrs    tmp1,7      ; copy MSB to DIN
    din_low
    sbrc    tmp1,7
    din_high

    cclk_high           ; generate cclk pulse
    cclk_low
    lsl     tmp1        ; get next bit
    dec     tmp2
    brne    fpga_write_loop
    ret

; *****************************************************************************
.endif

Fehlererkennung

Seitens des FPGAs ist Done ein Open Drain Ausgang. Im AVR wird der interne Pull-Up eingeschaltet, um das Signal auf HIGH ziehen zu können. Ist jedoch ein Fehler aufgetreten (CRC-Fehler im Datenstrom), bleibt Done auf LOW. Der AVR prüft Done am Ende der Konfiguration. Ist Done gleich HIGH, geht er in den Power Down Sleep Mode um Strom zu sparen. Ist aber ein Fehler aufgetreten und Done somit LOW, wird das durch langsames Blinken der LED an SDA angezeigt (~1 Hz). Tritt während der Adressierung des EEPROMs ein Fehler auf (kein ACK vom EEPROM), dann wird dies durch schnelles Blinken der LED angezeigt (~5 Hz). Im Normalfall treten diese Ereignisse nur sehr selten auf, z. B. bei einem EEPROM-Defekt durch Alterung/Datenverlust oder einer Störung der Datenübertragung durch starke Störimpulse. Aber zur Programmierung und zum Testen sind sie sehr nützlich.

Nutzung

Die Nutzung der Schaltung ist recht einfach. Im Quellcode müssen nur drei Variablen an das jeweilige Projekt angepasst werden. Soll ein anderer AVR-Typ verwendet werden können auch die Pins einfach zugewiesen werden. Danach wird das Programm assembliert und per ISP in den AVR geschrieben. Abschliessend sollten die Fuses gesetzt werden. Der Takt des interen RC-Oszillators sollte möglichst hoch gewählt werden, bis ca. 10 MHz ist das Progamm ohne Änderungen nutzbar. Wenn verfügbar sollte der Brown Out Detector eingeschaltet werden und mit ca. 2,7V Schwellspannung betrieben werden. Für den hier verwendeten Tiny12 muss das Fuse-Byte mit 0x12 beschrieben werden. HALT! Wenn jetzt der Reset gelöst wird läuft der AVR sofort los und versucht die Daten aus den EEPROMs zu lesen. Da diese aber noch nicht programmiert sind werden keine sinnvollen Daten ins FPGA geladen. Das schadet dem FPGA zwar nicht, aber die Konfiguration scheitert! In der Folge wird SDA vom AVR kontinuierlich zwischen LOW und HIGH umgeschaltet, um den Fehler durch Blinken der LED zu signalisieren. Damit ist aber keine Programmierung der EEPROMs mehr möglich!

Mögliche Auswege:

  • RESET auf LOW halten
  • die EEPROMs vor dem AVR programmieren

Im Normalfall wird man den zweiten Weg wählen. Denn während der Entwicklung wird man meist das FPGA über JTAG konfigurieren. Erst wenn alles komplett funktioniert wird man die Konfiguration einmalig in den EEPROM schreiben.

Hinweis! Wird während der Entwicklungsphase das FPGA über JTAG programmiert, muss in den Optionen für "Generate Programming File" unter Startup Options -> FPGA Start-up Clock "JTAG" gewählt werden.

Die Programmierung der EEPROMs erfolgt mit Ponyprog. Mittels einfachem Adapter kann man direkt auf den I2C-Bus gehen, es werden nur SDA, SCL und GND benötigt, die Pull-Ups sind schon auf dem Board. Das Board muss dazu natürlich mit Spannung versorgt werden.

I2C Adapter für Ponyprog


Der Ablauf der EEPROM-Programmierung sieht wie folgt aus:

  • In den Optionen für "Generate Programming File" muss unter Startup Options -> FPGA Start-up Clock "CCLK" gewählt werden.
  • Im Xilinx ISE den kompletten Synthesezyklus inclusive "Generate Programming File" durchlaufen.
  • IMPACT starten und eine PROM-Datei im BIN Format erzeugen.
  • Jetzt hat man die Wahl zwischen zwei Möglichkeiten.
    • Die Daten direkt in den EEPROM schreiben. Dann müssen sie beim Lesen und anschliessender Ausgabe zum FPGA in der Reihenfolge der Bits vertauscht werden, das kostet natürlich Zeit
    • Die Daten mit einem kleinen Tool Bitdreher umwandeln. Dabei wird die Bitverdrehung vorher durchgeführt. Die Daten können damit ohne zusätzliche Verdrehung schnell ins FPGA geschrieben werden.
  • Ponyprog starten, EEPROM Familie "IC Bus 16bit eeprom" auswählen, den jeweiligen Typ auch auswählen (25C128, 24C256, 24C512).
  • Die Bin-Datei laden und "Write Device" ausführen.
  • Das erfolgreiche Programmieren und Verifizieren wird von Ponyprog angezeigt.

Wenn die FPGA-Konfiguration grösser als ein EEPROM ist, muss die Datei in mehrere Stücke aufgeteilt werden. Das kann man mit IMPACT machen oder promgen direkt von der Kommandozeile aufrufen. Die Option -s <size> erlaubt das Aufteilen der Konfigurationsdaten in kleinere Dateien. Beachtet werden muss dabei, dass die einzelnen Blöcke exakt so gross sein müssen wie ein einzelner EEPROM. Ausser natürlich der letzte Block, dessen Länge ergibt sich aus (Dateigrösse MODULO EEPROM-Grösse). Die einzelnen Dateien können dann einfach per Ponyprog in die EEPROMs geschrieben werden.

Achtung!

  • Dabei muss man aufpassen, dass die Reihenfolge nicht verwechselt wird! Der erste Block muss in den EEPROM mit Adresse 0, der zweite in den mit Adresse 1 usw.
  • Wenn man mit Ponyprog mehrere EEPROMs an einem Bus programmieren will, muss die Adresse der EEPROMs manuell in der Datei PONYPROG2000.INI in der Zeile
        I2CBaseAddress=0xA0
eingetragen werden (0xA0, 0xA2, 0xA4, 0xA6, 0xA8, 0xAA, 0xAC, 0xAE). Nach jeder Änderung muss Ponyprog neu gestartet werden.

Leistung

Auf einem Tiny12 mit voll aufgedrehtem RC-Oszillator (ca. 2 MHz) und einer Online Bitverdrehung (bitreversal=1) erreicht man eine Geschwindigkeit von ca. 8,5kB/s, d.h. der im Test verwendete FPGA vom Typ XC2S30 mit 42096 Bytes Konfigurationsspeicher wird in ca. 5s konfiguriert. Nicht gerade berauschend, aber ausreichend. Der Grund dafür liegt nicht nur in der begrenzten Geschwindigkeit des I2C Busses (400kHz), sondern in einer Merkwürdigkeit der Konfigurationssoftware von Xilinx. Diese verdreht nämlich die Bits innerhalb der Bytes aus fadenscheinigen Gründen (Bit 0<->7, Bit 1<->6, etc.). Damit muss aber immer zunächst ein Byte vollständig gelesen werden (MSB zuerst) und dananch in umgekehrter Reihenfolge ausgegeben werden (LSB zuerst). Das kostet einiges an Zeit. Damit werden bei 2 MHz nur etwa 15% der maximalen Geschwindigkeit erreicht, dementsprechend sind die Konfigurationszeiten etwa sechs mal so lang wie in der Tabelle oben angegeben. Mit dem Parameter bitreversal=0 und der Bearbeitung der Konfigurationsdaten mit dem Tool Bitdreher erreicht man eine Konfigurationszeit von ca 3s, 40% schneller! Die erreichbaren Konfigurationszeiten sind in nachfolgender Tabelle exemplarisch angegeben, mit dem in Klammern angegebenen Faktor kann man direkt in die zweite Tabelle gehen und die reale Konfigurationszeit ausrechnen. Bei höherem Prozessortakt werden längere Pausen eingefügt, sodass die minimalen Pulszeiten für 400kHz auf dem I2C-Bus eingehalten werden. Dies wird mit dem schönen, leistungsfähigen Makro delay erreicht.

Takt [MHz] Konfigurationszeit [s] (Faktor)
bitreversal=0 bitreversal=1
2 3,0 (3,8) 4,8 (6)
5 1,2 (1,5) 2,1 (2,6)
10 0,9 (1,1) 1,4 (1,8)

Zukunftsaussichten

Für grössere FPGAs werden die seriellen EEPROMs irgendwann zu klein und zu langsam. Hier müssen andere Datenträger her. SD-Karten sind klein, billig und weit verbreitet. Auch wenn der Aufwand zu deren Ansteuerung plus Minimalfunktionalität für FAT12/FAT16 um einiges höher liegt, ist dies ein interessanter Ansatzpunkt, der in Zukunft einmal umgesetzt werden kann.

Als Zwischenlösung bieten sich Flashbausteine mit SPI im SO-8 Gehäuse an, die gibt es bis zu unglaublichen 32 Mbit zum Spottpreis, z.b. bei CSD. Allerdings muss dann ein AVR mit zwei zusätzlichen Pins her, hier muss man dann auf ein 20 poliges Gehäuse wechseln. Enthusiasten nehmen das platzsparende MLF20 ;-).

Alternativ könnte geprüft werden, ob der Dataflash eines AVR_Butterfly ausreicht. Siehe: FPGA_Konfiguration_mit_AVR_Butterfly

Diskussion im Forum

Links