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/ioports
erfragt 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