In diesem Kapitel wird der Entwurf eines möglichst einfachen PCI-Busmasters (in der PCI-Terminologie: Initiator) beschrieben. Seine Aufgabe ist es, selbstständig den PCI-Bus anzufordern und das durch die DIP-Schalter dargestellte Byte an den Daten-I/O-Port der parallelen Schnittstelle des Rechners zu übertragen, sobald sich die DIP-Schaltereinstellung auf dem PCI-Board geändert hat. Da I/O-Portzugriffe im Gegensatz zu I/O-Speicherzugriffen nicht immer auf voller 32 Bit PCI-Busbreite erfolgen (der Datenport der parallelen Schnittstelle ist nur 1 Byte groß), wird hier auch die Bytemaskierung kurz erklärt.
C/BE[3:0] | PCI-Befehl |
0000 | Interrupt Acknowledge ein spezieller ix86-spezifischer Buszyklus, siehe Kapitel Der PCI Configuration Header |
0001 | Special Cycle erlaubt es, bestimmte Nachrichten (Messages) auf den Bus abzugeben. |
0010 | I/O Read Lesen von I/O-Ports |
0011 | I/O Write Schreiben auf I/O-Ports |
0100 | reserviert |
0101 | |
0110 | Memory Read Lesen im "normalen" Speicheradressraum |
0111 | Memory Write Schreiben im "normalen" Speicheradressraum |
1000 | reserviert |
1001 | |
1010 | Configuration Read Lesen im Konfigurationspeicher |
1011 | Configuration Write Schreiben in den Konfigurationsspeicher |
1100 | Memory Read Multiple ein spezieller Speicherlesebefehl für optimierten Speicherzugriff: Der Initiator möchte Daten aus dem Speicher im Umfang mehrerer Cache-Zeilen lesen. Wenn das Target (die Speichersteuerung) diesen Befehl unterstützt, wird es damit angewiesen, Speicherzeilen schon im voraus aus dem Speicher zu holen (prefetch) und für den Transfer bereitzuhalten. |
1101 | Dual Address Cycle In einem System mit 64 Bit Adressen benötigt ein 32 Bit Initiator zwei Adressphasen auf dem 32 Bit PCI-Bus, um die 64 Bit Zieladresse zu übertragen. Im ersten Schritt werden die unteren 32 Bit der Adresse zusammen mit dem "Dual Address Cycle"-Kommando geschickt, in der zweiten Phase dann die oberen 32 Bit mit dem eigentlichen PCI-Befehl. |
1110 | Memory Read Line ein spezieller Lesebefehl für optimierten Speicherzugriff: Eine ganze Cache-Zeile soll aus dem Speicher gelesen werden. |
1111 | Memory Wirte-and-Invalidate ein spezieller Speicherbefehl für optimierten Speicherzugriff: Der PCI-Initiator möchte Daten vom Umfang einer ganzen Cache-Zeile in den Speicher schreiben. Nun kann es sein, dass gerade diese Zeile im Cache oder im Write-Back Cache der PCI-Bridge vorhanden und als dirty markiert ist, also noch in den Speicher zurückgeschrieben werden müsste. Da der PCI-Initiator aber sowieso die gesamte Zeile im Speicher mit seinen Daten überschreiben wird, kann das Rückschreiben aus dem Cache in den Speicher vor dem PCI-Transfer des Initiators eingespart werden. Es genügt, die betreffende Zeile im Cache als ungültig zu markieren. |
Fazit: Für die meisten Anwendungen sind nur die PCI-Kommandos I/O-Read und I/O-Write bzw. Memory Read und Memory Write wichtig.
Beispiel: I/O-Ports der parallelen Schnittstelle im PC (Basisadresse z.B. 0x378)
Beispiel: Erlaubte I/O-Portadressierungen
ADDR[31:0] | C/BE[3:0] | Ergebnis |
0x00001000 | 1110 | nur das Byte 0x1000 |
0x000095A2 | 0011 | Bytes 0x95A2 und 0x95A3 |
0x00001510 | 0000 | alle vier Bytes 0x1510 bis 0x1513 |
0x0000AE21 | 0001 | Bytes 0xAE21 bis 0xAE23 |
M_ADDR_N (PCI → Userapp.) |
M_ADDR_N (negiert!) zeigt an, dass sich der Initiatorautomat im Adresszustand befindet. Die Userapplikation muss in dieser Zeit eine gültige Adresse auf dem ADIO-Bus und einen gültigen PCI-Befehl auf dem M_CBE-Bus erzeugen. |
M_DATA (PCI → Userapp.) |
Ist M_DATA gesetzt, befindet sich der Initiatorautomat im Datentransferzustand. |
M_DATA_VLD (PCI → Userapp.) |
M_DATA_VLD hat zwei Bedeutungen, die von der Richtung des Datentransfers abhängen:
|
CSR(39 downto 0) (PCI → Userapp.) |
CSR ermögilcht einen Lesezugriff auf die auf die jeweils 16 Bits des Command- und Status-Registers im PCI Configuration Header. Die Bits 32 bis 39 sind Transaktionsstatussignale, die sich (bis auf Bit 39) unmittelbar aus den PCI-Bus-Steuerleitungen ergeben:
|
M_CBE(3 downto 0) (Userapp. → PCI) |
Dieser Eingang erwartet einen gültigen PCI-Befehl während der Adressphase des Transfers (M_ADDR_N = '0') und die passenden Bytemaskierungen während den Datenphasen. |
M_WRDN (Userapp. → PCI) |
M_WRDN gibt die gewünschte Richtung des Transfers an (und darf nicht im Widerspruch zum PCI-Befehl stehen).
|
M_READY (Userapp. → PCI) |
Mit M_READY signalisiert die Userapplikation, ob sie bereit ist, Daten zu transferieren. Wenn nicht, kann sie mithilfe dieses Signals Wartezyklen zwischen der Adressphase und der ersten Datenphase auf dem PCI-Bus einfügen. Einmal gesetzt, darf innerhalb der Datenphase eines Bursttransfers M_READY nicht wieder gelöscht werden. |
REQUEST (Userapp. → PCI) |
REQUEST fordert das PCI Interface auf, den PCI-Bus anzufordern und einen Initiator-Transfer einzuleiten. |
COMPLETE (Userapp. → PCI) |
Dieses Signal informiert den Initiatorautomaten, dass er die laufende Transaktion beenden soll. Ein einmal gesetztes COMPLETE-Signal muss bis zum Ende der Datenphase (M_DATA = '1') gesetzt bleiben. |
entity ... is port ( [...] USER_BUTTON : in std_logic; ONE_DIGIT : out std_logic_vector(6 downto 0); DIP_SWITCHES : in std_logic_vector(7 downto 0) ); end ...;Die passenden Pinbelegungen müssen in der User Constraint Datei xc2s200fg456_32_33.ucf vereinbart werden:
NET "USER_BUTTON" LOC = "B1"; 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 dir : std_logic; signal command, bytemask : std_logic_vector(3 downto 0); signal write_address : std_logic_vector(31 downto 0); [...] -- Voreinstellungen write_address <= x"00000378"; -- Basisadresse parallele Schnittstelle (8 Bit I/O-Port) dir <= '1'; -- Transferrichtung Schreiben command <= "001" & dir; -- I/O Transfer bytemask <= "1110"; -- 8 Bit-Zugriff: Nur das unterste Byte schreiben.Die Basisadressen der parallelen Schnittstellen des PCs werden beim Systemstart angezeigt. Unter Linux können sie auch mit
cat /proc/ioportserfragt werden.
process (CLK, RST) begin if RST = '1' then M_WRDN <= not dir; elsif CLK'event and CLK = '1' then M_WRDN <= dir; end if; end process;
Die Abbildung zeigt den Automaten, der die PCI-Transaktion im Zusammenspiel mit dem PCI Interface koordiniert. Obwohl in diesem Beispielentwurf nur der Schreibtransfer benutzt wird, ist der Fall Lesezugriff in diesem Diagramm mit eingezeichnet und auch im Quelltext mit allen zugehörigen Steuersignalen auskommentiert ausgeführt.
Ausgangspunkt ist der Ruhezustand IDLE_S. Wenn das Startsignal start gesetzt ist, geht der Automat in den REQ_S (Request: Die Transferanforderung an das PCI Interface wird hier erstmals gesetzt.) und im nächsten Takt gleich weiter in den Zustand READ_S bzw. WRITE_S in Abhängigkeit von der gewünschten Transferrichtung, die das Signal dir repräsentiert. In diesem Zustand wird der gesamte PCI-Transaktion ausgeführt, im wesentlichen gesteuert durch die Signale M_DATA und M_ADDR_N, die die Adress- und die Datenphase der Transaktion anzeigen. Am Ende der Datenphase (m_data_fell = '1') gibt es drei mögliche Folgezustände:
Bemerkung 2: Der Automat ist nicht minimal. Die Zustände REQ_S, DONE_S und RETRY_S können im Prinzip herausoptimiert werden. Bei komplexeren Anwendungen jedoch braucht man sie meist für Transfervorbereitung und -abschluss. Darum sind sie auch hier vorhanden.
Der VHDL-Code in vhdl/userapp.vhd setzt diesen Automaten wie beschrieben um und wird daher hier nicht nochmals angegeben.
signal write_reg : std_logic_vector(7 downto 0); signal reg_oe : std_logic; signal start : std_logic; [...] process (CLK, RST) begin if RST = '1' then write_reg <= DIP_SWITCHES; start <= '0'; elsif CLK'event and CLK = '1' then if write_reg /= DIP_SWITCHES and state = IDLE_S then write_reg <= DIP_SWITCHES; start <= '1'; else start <= '0'; end if; end if; end process;Die Ausgabe von write_reg auf den ADIO-Bus und damit an das PCI Interface erfolgt, wenn sich der Steuerautomat im WRITE_S-Zustand befindet und das PCI Interface in der Datenphase des Transfers.
reg_oe <= '1' when state = WRITE_S and M_DATA = '1' else '0'; -- Daten auf den ADIO-Bus bei Schreibtransfers ADIO <= x"000000" & write_reg when reg_oe = '1' else (others => 'Z');
signal m_data_delay, m_data_fell : std_logic; [...] process (RST, CLK) begin if RST = '1' then m_data_delay <= '0'; elsif CLK'event and CLK = '1' then m_data_delay <= M_DATA; end if; end process; m_data_fell <= m_data_delay and not M_DATA;
process (RST, CLK) begin if RST = '1' then fatal <= '0'; retry <= '0'; elsif CLK'event and CLK = '1' then if M_ADDR_N = '0' then fatal <= '0'; retry <= '0'; elsif M_DATA = '1' then fatal <= CSR(39) or CSR(38); -- Master / Target Abort retry <= CSR(36); -- Target Disconnect Without Data end if; end if; end process;
REQUEST <= '1' when state = REQ_S else '0'; process (CLK, RST) begin if RST = '1' then COMPLETE <= '0'; elsif CLK'event and CLK = '1' then case state is when REQ_S | READ_S | WRITE_S => COMPLETE <= '1'; when others => COMPLETE <= '0'; end case; end if; end process;
ADIO <= write_address when M_ADDR_N = '0' and dir = '1' else (others => 'Z'); M_CBE <= command when M_ADDR_N = '0' else bytemask;
-- Master: always ready process (RST, CLK) begin if RST = '1' then M_READY <= '0'; elsif CLK'event and CLK = '1' then M_READY <= '1'; end if; end process; -- Card Bus CIS Pointer Daten / Subsystem ID Daten SUB_DATA <= x"00000000" ; -- 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 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'; S_READY <= '0'; S_TERM <= '0'; INTR_N <= '0'; elsif (CLK'event and CLK='1') then -- Target-Eingangssignale S_ABORT <= '0'; S_READY <= '1'; S_TERM <= '1'; -- keine Interrupt-Funktion INTR_N <= '1'; end if; end process;
component displayrom port ( ADDR : in std_logic_vector(3 downto 0); DATA : out std_logic_vector(6 downto 0) ); end component; signal start_nr : std_logic_vector(3 downto 0); [...] with state select state_nr <= "0000" when IDLE_S, "0001" when REQ_S, "0010" when READ_S, "0011" when WRITE_S, "0100" when DEAD_S, "0101" when RETRY_S, "0110" when DONE_S, "1111" when others; displayrom_inst : displayrom port map ( ADDR => state_nr, DATA => ONE_DIGIT );
Autor: gkemnitz, Letzte Änderung: 14.04.2011 15:09:59