Die bisher beschriebenen PCI-Targets unterstützen nur Single Transfers zur Übertragung von genau einem Datenwort pro Transferzyklus. Der Transfer besteht aus einer Adressphase gefolgt von genau einer Datenphase. Für jedes weitere Datenwort beginnt ein neuer Zyklus bestehend aus Adress- und Datenphase. Diese einfachen Transfers können effizienter gestaltet werden, wenn Datenwörter mit aufeinanderfolgenden Adressen übertragen werden sollen. Dann genügt es offensichtlich, zu Beginn des Transfers einmal die Startadresse in einer Adressphase zu senden und dann nur noch die aufeinanderfolgenden Datenwörter bis der gesamte Transfer abgeschlossen ist oder eine andere Unterbrechungsbedingung den Transfer stoppt. Dies sind die sogenannten Burst Transfers.
In diesem Kapitel wird der Entwurf "Single Transfer PCI Target mit 4 KByte RAM" auf Bursttransfers erweitert. Über die bloße Erweiterung des PCI-Interfaces hinaus werden die Burstlängen gemessen und auf den 7-Segment-Anzeigen ausgegeben. Die Module userapp und pcim_top müssen dazu um diese zusätzlichen I/O-Ports ergänzt und passende LOC-Constraints in der User Constraint Datei eingetragen werden. Für die 7-Segment-Anzeigen wird die Komponente displayrom aus dem "Board-Test"-Projekt recycelt.
ADIO (31 downto 0) (PCI ↔ Userapp.) |
Über diesen bidirektionalen Bus werden Daten- und Adresstransfer durchgeführt (Zeitmultiplex). |
ADDR_VLD (PCI → Userapp.) |
ADDR_VLD zeigt an, dass eine Adressphase, die ein beliebiges Target betreffen kann, auf dem PCI-Bus beobachtet wurde und diese Adresse nun auf dem internen ADIO-Bus verfügbar ist. (Einen Takt später steht sie dann auch gelatcht auf dem internen ADDR-Bus zur Verfügung.) |
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. |
S_WRDN (PCI → Userapp.) |
S_WRDN gibt die Richtung der aktuellen Target-Transaktion an.
|
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. |
S_DATA_VLD (PCI → Userapp.) |
S_DATA_VLD hat zwei Bedeutungen, die von der Richtung des Datentransfers abhängen:
|
S_SRC_EN (PCI → Userapp.) |
S_SRC_EN ist ein wichtiges Signal für den Burst-Lesezugriff. Es wird benutzt, um den internen Adresszähler zu erhoehen. Das PCI-Interface signalisiert so, dass es ein neues Datenwort braucht, wenn es in die nächste Datenphase (Steuersignal S_DATA) eintritt. Genaueres siehe unten. |
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 einzuügen. |
S_TERM (Userapp. → PCI) |
Der Datentransfer soll beendet werden. |
Die Kombination von S_READY und S_TERM realisiert die verschiedenen Möglichkeiten eines Targets, den Transfer "geordnet" zu beenden oder zu steuern.
Bedingung | S_TERM | S_READY | Erläuterung |
Wait | 0 | 0 | Einfügen von Wartezyklen |
Normal | 0 | 1 | Die Datenphase(n) werden normal, d.h. ohne zusätzliche Wartezyklen und Transfer-Abbruch 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 (Userapp. → PCI) |
S_ABORT dient der Signalisierung schwerer Fehler (Target Abort auf dem PCI-Bus). Ein schwerer Fehler leigt z.B. dann vor, wenn ein Burst-Transfer die obere Grenze des Adressbereichs des Targets überschreitet. |
Um dieses Beispiel möglichst klein zu halten, wird S_ABORT nicht verwendet. Überschreitet ein Bursttransfer die obere Grenze, kommt es stattdessen zu einem internen Adresszählerüberlauf. Der Transfer setzt sich an der Adresse 0 fort.
Die Abbildung zeigt die strukturelle Erweiterung. Da bei einem Bursttransfer im allgemeinen (der Single Transfer ist ein spezieller Burst Transfer der Länge 1) nicht mehr zu jedem Datenwort eine expilizite Adresse geliefert, sondern nur noch die Startadresse der Datenwortsequenz, muss das Target die aktuelle Adresse mit einem internen Adresszähler (Target Address Pointer) selbst berechnen. Der Target Address Pointer ist ein Register mit zwei taktsynchronen Operationen:
-- Steuersignale Target Address Pointer signal tap_inc, tap_ld : std_logic; -- Register fuer Target Address Pointer (10 Bit Adresszaehler 1 K (10 Bit) x 32 Bit = 4 KByte) signal tap : std_logic_vector(9 downto 0); [...] begin [...] tap_register: process (CLK, RST) begin if RST = '1' then -- Reset-Kosmetik tap <= (others => '0'); elsif CLK'event and CLK = '1' then if tap_ld = '1' then -- Target Address Pointer initialisieren tap <= ADIO(11 downto 2); elsif tap_inc = '1' then -- Target Address Pointer weiterzaehlen tap <= tap + '1'; end if; end if; end process;
tap_ld <= ADDR_VLD;
tap_inc <= (bar_0_wr and S_DATA_VLD) or (bar_0_rd and S_SRC_EN);
-- Deklaration der RAM-Komponente bram_1k_32b component bram_1k_32b is port ( ADDR : in std_logic_vector(9 downto 0); CLK : in std_logic; DIO : inout std_logic_vector(31 downto 0); OE : in std_logic; RST : in std_logic; WE : in std_logic ); end component; -- Steuersignale signal bar_0_rd, bar_0_wr : std_logic; signal ram_wr, ram_oe : std_logic; [...] begin [...] -- Instanziierung des RAMs ram_inst : bram_1k_32b port map ( ADDR => tap, -- Adressierung durch Target Address Pointer CLK => CLK, DIO => ADIO, OE => ram_oe, RST => RST, WE => ram_wr ); -- Ist das Target Ziel eines Transfers? Lesen oder Schreiben? 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; -- Schreibsignal fuer das RAM: -- "Schreibzugriff auf das Target" und "Daten auf ADIO gueltig" ram_wr <= bar_0_wr and S_DATA_VLD; -- Lesesignal fuer das RAM -- "Lesezugriff auf das Target" und "PCI-Interface in der Datenphase" ram_oe <= bar_0_rd and S_DATA;Die Bedienung der unbenutzten Eingänge des LogiCore PCI Interfaces wird unverändert übernommen, siehe VHDL-Code userapp.vhd.
Erinnerung: Aus Timing-Gründen (Optimierungsauswirkungen) ist es 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 <= '1'; elsif CLK'event and CLK = '1' then -- hier die richtigen Werte S_READY <= '1'; s_TERM <= '0'; end if; end process;
Die Abbildung zeigt die daraus resultierenden Verzögerungen bzw. Vorgriffe.
im Takt x | : | Das PCI Interface setzt SRC_EN. Die kombinatorische Logik erzeugt daraus das Signal tap_inc. |
Takt x + 1 | : | Der Prozess tap_register erkennt tap_inc = '1' und erhöht den Adresszähler tap um 1. |
Takt x + 2 | : | Das RAM übernimmt die neue Adresse (tap + 1) und stellt das entsprechende Datenwort auf den ADIO-Bus. |
Takt x + 3 | : | Das PCI-Interface übernimmt die Daten vom ADIO-Bus in sein Ausgangsregister. Damit liegen sie auf den PCI-Bus an. |
Takt x + 3 | : | Das Datenwort wird vom Initator des Transfers in seinen Eingangsflipflops erfasst. |
Auf die Problematik wird noch ausführlich beim Entwurf eines PCI-Masters eingegangen und kann im LogiCore PCI Design Guide nachgelesen werden.
[...] entity pcim_top is port ( [...] USER_BUTTON : in std_logic; ONE_DIGIT : out std_logic_vector(6 downto 0); TEN_DIGIT : out std_logic_vector(6 downto 0) ); end pcim_top; architecture rtl of pcim_top is [...] component userapp port ( [...] USER_BUTTON : in std_logic; ONE_DIGIT : out std_logic_vector(6 downto 0); TEN_DIGIT : out std_logic_vector(6 downto 0) ); end component; [...] USER_APP : userapp port map ( [...] USER_BUTTON => USER_BUTTON, ONE_DIGIT => ONE_DIGIT, TEN_DIGIT => TEN_DIGIT ); end rtl;In der Datei userapp.vhd muß die Portbeschreibung entsprechend erweitert werden:
[...] entity userapp is port ( [...] USER_BUTTON : in std_logic; ONE_DIGIT : out std_logic_vector(6 downto 0); TEN_DIGIT : out std_logic_vector(6 downto 0); ); end userapp; [...]Nun fehlen noch die zugehörigen Pin-Vereinbarungen am Ende der User Constraint Datei xc2s200fg456_32_33.ucf:
[...] NET "USER_BUTTON" LOC = "B1"; 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
-- Deklaration der 7-Segment-Decoder-Komponente displayrom component displayrom port ( ADDR : in std_logic_vector(3 downto 0); DATA : out std_logic_vector(6 downto 0) ); end component; -- Register zum Speichern der Byteanzahl der laengsten Burstsequenz signal burst_len_max : std_logic_vector(7 downto 0); [...] begin [...] burst_len_register: process (CLK, RST, USER_BUTTON) variable burst_len : std_logic_vector(7 downto 0); -- Burstlaengen-Zaehler begin if RST = '1' or USER_BUTTON = '0' then -- Reset, wenn Reset-Signal oder User-Button gedrueckt burst_len_max <= x"00"; burst_len := x"00"; elsif CLK'event and CLK = '1' then if tap_ld = '1' then -- internen Zaehler ruecksetzen, wenn Target Address Pointer geladen wird burst_len := x"00"; elsif tap_inc = '1' then -- internen Zaehler erhoehen, ggf. das neue Maximum speichern burst_len := burst_len + 1; if burst_len > burst_len_max then burst_len_max <= burst_len; end if; end if; end if; end process; -- Instanziierung der 7-Segment-Dekoder zur Ausgabe der max. Burstlaenge displayrom_inst2 : displayrom port map ( ADDR => burst_len_max(3 downto 0), DATA => ONE_DIGIT ); displayrom_inst1 : displayrom port map ( ADDR => burst_len_max(7 downto 4), DATA => TEN_DIGIT );