Projekte.PCI-Tutorial-BTEntwurf (Struktur)


Projekt "Board-Test" - ein einfaches PCI-Target

Übersicht

Die für dieses Projekt modifizerten Dateien des LogiCore PCI Interface und die darüber hinaus benötigten Dateien befinden sind: [xc2s200fg456_32_33.ucf] [pcim_top.vhd] [cfg.vhd] [displayrom.vhd] [userapp.vhd] .

In diesem Kapitel wird der Entwurf eines möglichst einfachen PCI-Targets ohne Burstunterstützung beschrieben. Das Target implementiert drei 32 Bit Register:

  • LED-Register (read/write), Offset 0x00, steuert die User-LED des Entwicklungsboards an.
    Bit 31 - Bit 1Bit 0
    nicht benutztUSER
    ("USER_LED")
     
  • Display-Register (read/write), Offset 0x04, steuert die 7-Segment-Anzeigen an.
    Bit 31 - Bit 8Bit 7 - Bit 4Bit 3 - Bit 0
    nicht benutztDD2
    ("TEN_DIGIT")
    DD1
    ("ONE_DIGIT")
     
  • Switches-Register (read only), Offset 0x08, sind die DIP-Schalter, die direkt ausgelesen werden.
    Bit 31 - Bit 8Bit 7 - Bit 0
    nicht benutztDIP-Schalter
    ("DIP_SWITCHES")
Der Entwurf erfolgt in der Komponente userapp (userapp.vhd).

Anpassung des LogiCore PCI Interfaces

Das LogiCore PCI Interface wird in der Datei cfg.vhd mit symbolischen Konstanten wie zuvor beschrieben konfiguriert.

Für das PCI-Target wird ein 3 x 32 Bit = 12 Byte umfassender Adressraum benötigt. Wegen der "Mindestens 4 KByte"-Empfehlung wird ein 4 KByte großer Block im Basisadressregister angefordert.

    --------------------------------------------------------------
    -- Configure Base Address Registers
    --------------------------------------------------------------

      -- BAR0 : 4 KByte prefetchable memory space
      cfg_int(0)              <= ENABLE ;
      cfg_int(32 downto 1)    <= SIZE4K ;
      cfg_int(33)             <= NOFETCH ;
      cfg_int(35 downto 34)   <= TYPE00 ;
      cfg_int(36)             <= MEMORY ;

      -- BAR1 : disabled
      cfg_int(37)             <= DISABLE ;
      cfg_int(69 downto 38)   <= SIZE2G ;
      cfg_int(70)             <= NOFETCH ;
      cfg_int(72 downto 71)   <= TYPE00 ;
      cfg_int(73)             <= MEMORY ;

      -- BAR2 : disabled
      cfg_int(74)             <= DISABLE ;
      cfg_int(106 downto 75)  <= SIZE2G ;
      cfg_int(107)            <= NOFETCH ;
      cfg_int(109 downto 108) <= TYPE00 ;
      cfg_int(110)            <= MEMORY ;
Alle anderen Optionen behalten ihre Voreinstellungen bei bzw. bleiben deaktiviert.
      -- Device ID and Vendor ID
      cfg_int(151 downto 120) <= X"030010ee";

      -- Class Code and Revision ID
      cfg_int(183 downto 152) <= X"0b400000";

      -- Subsystem ID and SubVendor ID
      cfg_int(215 downto 184) <= X"00000000" ;

      -- External Subsystem ID and Subvendor ID
      cfg_int(114) <= DISABLE ;

      -- MAX_LAT and MIN_GNT
      cfg_int(231 downto 224) <= X"00" ; 
      cfg_int(223 downto 216) <= X"00" ; 

      -- Latency Timer Enable
      cfg_int(112) <= DISABLE ;

      -- Interrupt Enable
      cfg_int(113) <= DISABLE ;

      -- Capability List Enable
      cfg_int(116) <= DISABLE ;

      -- Capability List Pointer
      cfg_int(239 downto 232) <= X"00" ;

      -- User Config Space Enable
      cfg_int(118) <= DISABLE ;

      -- Interrupt Acknowledge
      cfg_int(240) <= DISABLE ;

Verwendete Signale des LogiCore PCI Interfaces

Die Signale werden nur so weit erklärt, wie es für Single Transfer Targets notwendig ist. Die exakten vollständigen Definitionen sind dem [LogiCore PCI Design Guide] zu entnehmen.

Allgemeine Signale

ADDR (31 downto 0)
(PCI → Userapp.)
ADDR enthält die in der Adressphase zu Beginn einer PCI-Transaktion übermittelte Adresse.

Die Adresse bleibt für die gesamte Transaktion gültig. In einfachen Designs kann man also direkt mit dem Signal ADDR arbeiten und davon ausgehen, dass es gültig ist, wenn man den Beginn einer Transaktion festgestellt hat.

BASE_HIT (7 downto 0)
(PCI → Userapp.)
Dieses Signal ist der erste Indikator dafür, dass die aktuelle PCI-Transaktion in einen der Adressräme des Targets zielt. Da das LogiCore PCI Interface nur max. drei Adressräume implementiert, sind auch nur die unteren drei Bits BASE_HIT(2) bis BASE_HIT(0) relevant (One-Hot-Codierung).

Gültigkeit: genau ein Takt.

ADIO (31 downto 0)
(PCI ↔ Userapp.)
Über diesen bidirektionalen Bus werden Daten- und Adressentransfer durchgeführt.

Signale des Target- ("Slave"-) Automaten des LogiCore PCI Interfaces

S_WRDN
(PCI → Userapp.)
S_WRDN gibt die Richtung der aktuellen Target-Transaktion an.
  • '1' : schreiben (PCI → Userapp.)
  • '0' : lesen (Userapp. → PCI)
Gültigkeit: S_WRDN wird in dem Taktzyklus gültig, in dem auch BASE_HIT(x) gesetzt wird, und für die Dauer der Transaktion gehalten.
S_DATA_VLD
(PCI → Userapp.)
S_DATA_VLD hat zwei Bedeutungen, die von der Richtung des Datentransfers abhängen:
  • Bei einem Schreibzyklus (PCI → Userapp.) sind die Daten auf dem ADIO-Bus gültig, wenn S_DATA_VLD gesetzt ist.
  • Innerhalb eines Lesezyklus (Userapp. → PCI) signalisiert S_DATA_VLD, dass eine Datenphase auf dem PCI-Bus abgeschlossen wurde.
S_DATA
(PCI → Userapp.)
S_DATA signalisiert, dass sich der Target-Automat des LogiCore PCI Interfaces im Datentranfer-Zustand befindet: Die Adresse wurde zuvor dekodiert und erfolgreich mit einem der Basisadressregister abgeglichen. Das Target hat die Anfrage akzeptiert und wird nun reagieren.

Signale zur Flusssteuerung und Transferabbruch

S_READY
(Userapp. → PCI)
Mit S_READY wird dem LogiCore PCI Interface die Bereitschaft zum Datentransfer angezeigt. Dieses Signal kann dazu benutzt werden, Wartezyklen in den Transfer einzufügen.
S_TERM
(Userapp. → PCI)
Der Datentransfer soll beendet werden.
S_ABORT
(Userapp. → PCI)
S_ABORT dient der Signalisierung schwerer Fehler (Target Abort auf dem PCI-Bus).

Die Kombination von S_READY und S_TERM realisiert die verschiedenen Möglichkeiten eines Targets, den Transfer "geordnet" zu beenden oder zu steuern. Sie spielen insbesondere bei Burst-Transfers (also nicht in diesem Beispiel) eine Rolle.

Bedingung S_TERM S_READY Erläuterung
Wait 0 0 Einfügen von Wartezyklen zu Beginn einer PCI-Transaktion (Verzögerung der ersten Datenphase)
Normal 0 1 Die Datenphase(n) werden normal, d.h. ohne zusätzliche Wartezyklen und Transferabbruch durch das Target ausgeführt.
Disconnect Without Data (Retry) 1 0 Beendigung der aktuellen PCI-Bus Transaktion ohne einen Datentransfer in der letzten Datenphase. Der Initiator des Transfers ("Master") muß den Datentransfer später wiederholen.
Disconnect With Data 1 1 Der Datentransfer in der letzten Datenphase wird zuendegeführt, bevor die Transaktion terminiert.

S_ABORT wird in diesem Beispiel nicht aktiv verwendet.

Das Hilfsmodul displayrom.vhd

Die Umsetzung der 4-Bit-Binärzahlen des Display-Registers in 7-Segment-Ziffern übernimmt das Modul displayrom (Datei displayrom.vhd). Die Datei wird dem Projekt hinzugefügt. In der Komponente userapp wird das Modul später eingebunden und für beide Ziffern zweimal instanziiert.
    library ieee;
    use ieee.std_logic_1164.all;

    entity displayrom is
      port (
        ADDR : in std_logic_vector(3 downto 0);
        DATA : out std_logic_vector(6 downto 0)
      );
    end displayrom;

    architecture rtl of displayrom is

      begin
        with ADDR select
          -- Segment-Folge: gfedcba
          DATA <= "0111111" when x"0",
                  "0000110" when x"1",
                  "1011011" when x"2",
                  "1001111" when x"3",
                  "1100110" when x"4",
                  "1101101" when x"5",
                  "1111101" when x"6",
                  "0000111" when x"7",
                  "1111111" when x"8",
                  "1101111" when x"9",
                  "1110111" when x"A",
                  "1111100" when x"B",
                  "1011000" when x"C",
                  "1011110" when x"D",
                  "1111001" when x"E",
                  "1110001" when x"F",
                  "0000001" when others;
      end rtl;

Der Hardware-Entwurf des PCI-Targets

Vorarbeit

Das PCI-Target soll die User-LED, die 7-Segment-Anzeigen und die DIP-Schalter ansteuern. Dazu müssen die Anschlüsse bzw. die sie repräsentierenden Signale von dem Top-Level-Modul pcim_top zur Komponente userapp, in der der eigentliche Beispielentwurf geschieht, "durchgereicht" werden. Daraus resultieren folgende Ergänzungen in der Datei pcim_top.vhd:
    [...]

    entity pcim_top is
      port (

        [...]

        USER_LED        : out std_logic;
        ONE_DIGIT       : out std_logic_vector(6 downto 0);
        TEN_DIGIT       : out std_logic_vector(6 downto 0);
        DIP_SWITCHES    : in std_logic_vector(7 downto 0)
      );
    end pcim_top;

    architecture rtl of pcim_top is

    [...]

     component userapp
      port (
    
        [...]

        USER_LED        : out std_logic;
        ONE_DIGIT       : out std_logic_vector(6 downto 0);
        TEN_DIGIT       : out std_logic_vector(6 downto 0);
        DIP_SWITCHES    : in std_logic_vector(7 downto 0)
      );
      end component;

    [...]

      USER_APP : userapp port map (

        [...]

        USER_LED        => USER_LED,
        ONE_DIGIT       => ONE_DIGIT,
        TEN_DIGIT       => TEN_DIGIT,
        DIP_SWITCHES    => DIP_SWITCHES
      );

    end rtl;
In der Datei userapp.vhd muß die Portbeschreibung entsprechend erweitert werden:
    [...]

    entity userapp is
      port (

        [...]

        USER_LED        : out std_logic;
        ONE_DIGIT       : out std_logic_vector(6 downto 0);
        TEN_DIGIT       : out std_logic_vector(6 downto 0);
        DIP_SWITCHES    : in std_logic_vector(7 downto 0)
      );
    end userapp;

    [...]
    
Nun fehlen noch die zugehörigen Pin-Vereinbarungen am Ende der User Constraint Datei xc2s200fg456_32_33.ucf:
    [...]

    NET "USER_LED"    	 LOC = "A10";

    NET "TEN_DIGIT<0>"   LOC = "D6" ;   //DISPLAY.2A
    NET "TEN_DIGIT<1>"   LOC = "C5" ;   //DISPLAY.2B
    NET "TEN_DIGIT<2>"   LOC = "D5" ;   //DISPLAY.2C
    NET "TEN_DIGIT<3>"   LOC = "C7" ;   //DISPLAY.2D
    NET "TEN_DIGIT<4>"   LOC = "D7" ;   //DISPLAY.2E
    NET "TEN_DIGIT<5>"   LOC = "C6" ;   //DISPLAY.2F
    NET "TEN_DIGIT<6>"   LOC = "A8" ;   //DISPLAY.2G

    NET "ONE_DIGIT<0>"   LOC = "E10" ;  //DISPLAY.1A
    NET "ONE_DIGIT<1>"   LOC = "E9" ;   //DISPLAY.1B
    NET "ONE_DIGIT<2>"   LOC = "E8" ;   //DISPLAY.1C
    NET "ONE_DIGIT<3>"   LOC = "E6" ;   //DISPLAY.1D



    NET "ONE_DIGIT<4>"   LOC = "E7" ;   //DISPLAY.1E
    NET "ONE_DIGIT<5>"   LOC = "F11" ;  //DISPLAY.1F
    NET "ONE_DIGIT<6>"   LOC = "E11" ;  //DISPLAY.1G

    NET "DIP_SWITCHES<7>"   LOC = "D2" ;  //DIP7
    NET "DIP_SWITCHES<6>"   LOC = "C1" ;  //DIP6
    NET "DIP_SWITCHES<5>"   LOC = "F4" ;  //DIP5
    NET "DIP_SWITCHES<4>"   LOC = "G5" ;  //DIP4
    NET "DIP_SWITCHES<3>"   LOC = "F5" ;  //DIP3
    NET "DIP_SWITCHES<2>"   LOC = "E3" ;  //DIP2
    NET "DIP_SWITCHES<1>"   LOC = "F3" ;  //DIP1
    NET "DIP_SWITCHES<0>"   LOC = "E4" ;  //DIP0

    NET "DIP_SWITCHES<7>"   PULLUP;
    NET "DIP_SWITCHES<6>"   PULLUP;
    NET "DIP_SWITCHES<5>"   PULLUP;
    NET "DIP_SWITCHES<4>"   PULLUP;
    NET "DIP_SWITCHES<3>"   PULLUP;
    NET "DIP_SWITCHES<2>"   PULLUP;
    NET "DIP_SWITCHES<1>"   PULLUP;
    NET "DIP_SWITCHES<0>"   PULLUP;

Signal- und Komponentendeklaration

Als nächstes werden in dem Modul userapp (Datei userapp.vhd) die benötigten Signale vereinbart und die Komponente displayrom deklariert und instanziiert:
    architecture rtl of userapp is
    
    [...] 

      component displayrom 
        port (
          ADDR : in std_logic_vector(3 downto 0);
          DATA : out std_logic_vector(6 downto 0)
        );
      end component;

      -- Steuersignale
      signal bar_0_rd, bar_0_wr : std_logic;
      signal led_select, display_select, switches_select : std_logic;
      signal load_led_reg, load_display_reg : std_logic;
      signal oe_led_reg, oe_display_reg, oe_switches_reg, oe_dummy : std_logic;

      -- Register
      signal led_reg : std_logic;
      signal display_reg : std_logic_vector(7 downto 0);

    begin
    
      displayrom_inst2 : displayrom
      port map (
        ADDR  => display_reg (3 downto 0),
        DATA  => ONE_DIGIT
      );

      displayrom_inst1 : displayrom
      port map (
        ADDR  => display_reg (7 downto 4),

        DATA  => TEN_DIGIT
      );

      USER_LED <= led_reg;
 

Steuersignale

Die Signale, die das LogiCore PCI Interface zur Verfügung stellt, werden schrittweise miteinander verknüpft und erzeugen so die entscheidenden Datenübernahme-Signale (load) und die Ausgangstreiber-Freigabesignale (output enable (oe)) der implementierten Datenregister.
Für mehr Flexibilität beim Routing in der Implementierungsphase des Designs und zur Reduktion der Logikstufen werden die Signale verarbeitet, sobald sie zur Verfügung stehen. Ein Nebeneffekt dieser Vorgehensweise ist ein übersichtlicher Entwurf.

Das niederwertigste Bit des BASE_HIT Vektors zeigt an, dass der aktuelle PCI-Transfer in den Adressraum des Basisadressregisters 0 zielt. Der Prozess decode_hit reagiert auf BASE_HIT(0), das nur für einen Takt gesetzt ist, indem er die Signale bar_0_rd bzw. bar_0_wr erzeugt, je nachdem, ob ein Lese- oder ein Schreibtransfer stattfindet. Die Signale bar_0_rd bzw. bar_0_wr behalten ihren Wert für die gesamte Datenphase der Transaktion (S_DATA = '1') bei und werden erst dann wieder zurückgesetzt.

    decode_hit : process (CLK, RST)
    begin
      if RST = '1' then
        bar_0_rd <= '0';
        bar_0_wr <= '0';
      elsif CLK'event and CLK = '1' then
        if BASE_HIT(0) = '1' then            
          bar_0_rd <= not S_WRDN;
          bar_0_wr <= S_WRDN;
        elsif S_DATA = '0' then
          bar_0_rd <= '0';
          bar_0_wr <= '0';
        end if;
      end if;
    end process;

Die Adressierung

Die folgende kombinatorische Logik ist ein Adressdekoder, der in dieser Anwendung separate Auswahlsignale für die implementierten Register des 4 KByte großen Gesamtadressraums ADDR(11 downto 2) generiert.
    led_select      <= '1' when ADDR(11 downto 2) = x"00" & "00" else '0';
    display_select  <= '1' when ADDR(11 downto 2) = x"00" & "01" else '0';
    switches_select <= '1' when ADDR(11 downto 2) = x"00" & "10" else '0';
PCI Speicherzugriffe erfolgen immer 32 Bit ausgerichtet auf voller Busbreite. Bei der Implementierung von I/O-Ports ist es mithilfe der Bits ADDR(1 downto 0) und des hier nicht weiter besprochenen Signals S_CBE(3 downto 0) möglich, auch einzelne Bytes zu adressieren bzw. den Datenbus byteweise zu maskieren.

Implementation der Datenregister


Abb.: Schaltbild eines der Register

Das entscheidende kritische Signal für die Datenübernahme in die implementierten Datenregister ist S_DATA_VLD. Ist es gesetzt, sind die Daten auf dem ADIO-Bus gültig.

    load_led_reg      <= bar_0_wr and S_DATA_VLD and led_select;
    load_display_reg  <= bar_0_wr and S_DATA_VLD and display_select;
Der Inhalt des adressierten Datenregisters soll auf den ADIO-Bus ausgegeben werden, wenn sich der Target-Automat des LogiCore PCI Interfaces im Datentransfer-Zustand befindet und ein Lesetransfer stattfindet.
    oe_led_reg      <= bar_0_rd and S_DATA and led_select;
    oe_display_reg  <= bar_0_rd and S_DATA and display_select;
    oe_switches_reg <= bar_0_rd and S_DATA and switches_select;
    oe_dummy        <= bar_0_rd and S_DATA and not (led_select or display_select or switches_select);
Jedes Datenregister wird durch einen separaten Prozess beschrieben. Ist das entsprechende Datenübernahme-Signal gesetzt, werden die Daten vom ADIO-Bus übernommen. Die DIP-Schalter werden direkt gelesen.
    write_led_reg : process (CLK, RST)
    begin
      if RST = '1' then
        led_reg <= '0';
      elsif CLK'event and CLK = '1' then
        if load_led_reg = '1' then
          led_reg <= ADIO(0);
        end if;
      end if;
    end process;

    write_display_reg : process (CLK, RST)
    begin
      if RST = '1' then
        display_reg <= x"00";
      elsif CLK'event and CLK = '1' then
        if load_display_reg = '1' then
          display_reg <= ADIO(7 downto 0);
        end if;
      end if;
    end process;
Die Ausgabe auf den ADIO-Bus wird mit kombinatorischer Logik realisiert. Bei jedem Lesezugriff sollen sich definierte Werte auf dem gesamten Bus befinden.


    ADIO <=  x"0000000" & "000" & led_reg  when oe_led_reg = '1'      else 
             x"000000" & display_reg       when oe_display_reg = '1'  else 
             x"000000" & DIP_SWITCHES      when oe_switches_reg = '1' else 
             x"00000000"                   when oe_dummy = '1'        else 
             (others => 'Z');

Datenflusssteuerung und Transfer-Terminierung: Always Ready, Single Transfers

Dieses einfache Beispiel ist immer zum Datentransfer bereit. Daher braucht die Datenphase eines Zugriffs nicht durch Wartezyklen verzögert werden (S_READY <= '1'). Darüber hinaus unterstützt es keine Bursttransfers. Ein burstfähiger Initiator wird jedoch einen Bursttransfer probieren, wenn er mehrere aufeinanderfolgende Register lesen oder schreiben will. Das Target muss daher immer sofort mit einem "Disconnect With Data" den Transfer terminieren (S_TERM <= '1'): Das erste (und letzte) Datenwort wird erfolgreich übertragen. Danach bricht das Target den Tansfer ab. Der burstwillige Initiator weiß nun, dass das erste Datenwort erfolgreich übertragen wurde. Er beginnt eine neue Transaktion mit dem darauffolgenden Datenwort.

Es ist aus Timing-Gründen (Optimierungsauswirkungen, siehe nächster Abschnitt) nicht erlaubt, die Signale S_READY und S_TERM statisch auf feste Pegel zu legen! Deshalb werden die Signale über D-Flipflops gesetzt, die nicht wegoptimiert werden können.

    term_control: process (RST, CLK)
    begin
      if RST = '1' then

        -- hier die invertierten Werte
        S_READY <= '0'; 
        S_TERM  <= '0';

      elsif CLK'event and CLK = '1' then

        -- hier die richtigen Werte
        S_READY <= '1';
        s_TERM  <= '1';

      end if;
    end process;

Unbenutzte Eingänge des LogiCore PCI Interfaces

Alle unbenutzten Eingänge des LogiCore PCI Interfaces müssen beschaltet werden. Bei ausschließlichen Target-Designs ist insbesondere die Initiator-Funktionalität des PCI-Interfaces korrekt abzuschalten.
Die einfache Lösung, alle unbenutzten Steuereingänge einfach auf logisch 0 oder 1 zu legen, kann ungewollte Nebeneffekte hervorrufen. In der Implementierungsphase können beim Mappen diese Signale herausoptimiert werden. Das ist für sich genommen eigentlich kein Problem, jedoch werden dann die Guide Files, die bei einigen Designs das richtige Timing sicherstellen, nicht mehr korrekt arbeiten. Die hier beschriebenen Entwürfe, basierend auf 33 MHz PCI Takt und 32 Bit Busbreite, sind so unkritisch, dass sie keine Guide Files benötigen. Das Design sollte sich trotzdem an die Empfehlungen halten.
Der folgende Code setzt die Signale auf die geforderten Pegel, ohne an dieser Stelle auf die Signale im einzelnen einzugehen:
    -- Card Bus CIS Pointer Daten / Subsystem ID Daten
    SUB_DATA    <= x"FFFFFFFF" ;
    
    -- ADIO-Bus immer mit LogiCore PCI Interface verbunden
    KEEPOUT     <= '0'; -- ADIO-Bus immer enabled

    -- Steuersignale für Konfigurationstransaktionen
    C_READY     <= '1';  
    C_TERM      <= '1';

    -- Initiator Steuersignale
    REQUEST     <= '0';
    REQUESTHOLD <= '0';
    CFG_SELF    <= '0';
 
    static_config: process (CLK, RST)
    begin
      if RST = '1' then 
        
        -- hier die invertierten Default-Werte, Kommentare siehe unten
        S_ABORT  <= '1';
        COMPLETE <= '0';
        M_WRDN   <= '1';
        M_READY  <= '0';
        M_CBE    <= "1001";
        INTR_N   <= '0';

      elsif (CLK'event and CLK='1') then 

        -- Signalisierung von schweren Fehlern nicht vorgesehen     
        S_ABORT  <= '0';

        -- ordnungsgemaesses Abschalten der Initiator-Funktionen
        COMPLETE <= '1';
        M_WRDN   <= '0';
        M_READY  <= '1';
        M_CBE    <= "0110";

        -- keine Interrupt-Funktion
        INTR_N  <= '1';

      end if;
    end process;


Autor: gkemnitz, Letzte Änderung: 14.04.2011 15:09:59


 TU Clausthal 2020  Impressum