Forum: FPGA, VHDL & Co. Register in VHDL effizien implementieren?


von Martin K. (mkohler)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,

In meinem Design habe ich mehrere Module, welche über einen Satz von 
8/16/32 Bit Registern gesteuert werden.
Diese Registerblöcke bilden alle Steuer- und Statusleitungen der 
nachgeschalteten Logik ab, so dass über die uC-Zugriffe auf die Register 
der Ablauf gesteuert werden kann.

Intern sind die einzelnen Registerblöcke über einen 32Bit 
bidirektionalen Datenbus mit der uC-Zugriffslogik verbunden, der Zugriff 
auf die Blöcke wird über diese Kontrollsignale erledigt:
1
        -- Steuerung Lese/Schreibvorgang auf den Registern
2
        INT_REG_A                           : in    STD_LOGIC_VECTOR(7 downto 2);
3
        INT_D                               : inout STD_LOGIC_VECTOR(31 downto 0);
4
        INT_CYCLE_access                    : in    STD_LOGIC;
5
        INT_CYCLE_dir                       : in    STD_LOGIC;
6
        INT_CYCLE_WRITE_ACCESS_impuls       : in    STD_LOGIC;
INT_REG_A : adressiert block-intern das gewünschte Register
INT_D : bidirektionaler Datenbus
INT_CYCLE_access : ist aktiv, wenn dieser Block adressiert wurde
INT_CYCLE_dir : 0=read, 1=write Zyklus
INT_CYCLE_WRITE_ACCESS_impuls : Impuls zur Übernahme der Schreibdaten

Soweit, so schlecht ;-)
Der bidirektionale Datenbus ist sicher noch ein Problem, das werde ich 
noch anpacken.

Meine Frage zielt nun mehr darauf ab, wie solche Register effizient 
implementiert werden können.

Das Lesen der Register wird in einem kombinatorischen process erledigt, 
wo direkt auf INT_D() ausgegeben wird, falls die richtige Stelle 
adressiert ist:
1
        -- dieses Modul adressiert?
2
        -- gueltiger Adressbereich selektiert?
3
        -- -> nur unterste 8 32Bit-Register implementiert
4
        if INT_CYCLE_access = '1' and INT_REG_A(7 downto 5) = "000" then
5
            -- Decodierung Bit4..2
6
            case INT_REG_A(4 downto 2) is
7
                -- Counter Control Register
8
                when "000" => 
9
                        if INT_CYCLE_dir = '0' then
10
                            -- Read Zugriff
11
                            INT_D(2 downto 0)   <= COUNTER_CONTROL_sig(2 downto 0);
12
                            INT_D(7 downto 3)   <= "00000"; -- Byte mit 0 fuellen
13
                            INT_D(10 downto 8)  <= NULLSIG_CONTROL_sig(2 downto 0);
14
                            INT_D(11)           <= '0'; -- Byte mit 0 fuellen
15
                            INT_D(14 downto 12) <= NULLSIG_STATE(2 downto 0);
16
                            INT_D(15)           <= '0'; -- Byte mit 0 fuellen
17
                        else
18
                            -- Write Zugriff
19
                            COUNTER_CONTROL_REG_write <= '1';
20
                        end if;

Für das Lesen des Registers wird im kombinatorischen process das Signal 
COUNTER_CONTROL_REG_write gesetzt, so dass im separaten Prozess der 
Register Inhalt übernommen werden kann:
1
    -- Counter Control Register schreiben
2
    process(INT_CLK)
3
    begin
4
        if rising_edge(INT_CLK) then
5
            if COUNTER_CONTROL_REG_write = '1' and INT_CYCLE_WRITE_ACCESS_impuls = '1' then
6
                COUNTER_CONTROL_sig(2 downto 0) <= INT_D(2 downto 0);
7
                NULLSIG_CONTROL_sig(2 downto 0) <= INT_D(10 downto 8);
8
            end if;
9
        end if;
10
    end process;

Funktionieren tun die Register einwandfrei, ich habe nur den dringenden 
Verdacht, dass sich das Ganze einfacher beschreiben liesse.
Auf das Problem aufmerksam geworden bin ich u.a. durch die in diesem 
Thread gezeigten Logik-Kaskaden:
Beitrag "View RTL Schematic: "falsche" Darstellung des Designs"

Richtig lustig wird es dann, wenn nicht wie hier nur 8 sonder 16 
Register implementiert werden.

Frage:
- Wie implementiert ihr eure Register im FPGA? Ähnlich wie ich oder 
komplett anders?

Anschlussfrage: Wie entferne ich den bidirektionalen Bus am besten?
INT_D in INT_D_in und INT_D_out auftrennen und von INT_D_in lesen ist ja 
noch kein Problem.
Schreiben auf INT_D_out ist nun aber immer noch problematisch. Der Bus 
ist zwar nicht mehr bidirektional, für die Beschreibung brauche ich aber 
trotzdem einen Tristate Output. Sehe ich das falsch? Oder wie wird so 
etwas direkt mit MUX beschrieben?

von Matthias F. (flint)


Lesenswert?

Hmm, meine Registerbank gibts nur FPGA intern und geht nicht direkt an 
die Ausgänge, darum kann ich diesbezüglich nichts raten.

Mir ist dein Tristate-Output nicht ganz geheuer. Funktioniert das schon 
in HW oder noch nur in der Simulation. Es kommt mir so vor als würden 
bei dem Code so wie er dasteht die Ports von uC und FPGA gegeneinander 
treiben, zumindest in dem kurzen Zeitfenster wo der uC einen Lesevorgang 
auslöst und der FPGA kombinatorisch den Ausgang setzt.

Ich glaube auch, dass es nicht gut ist, die Register ohne FFs 
(insbesondere Output FFs) dazwischen zu schalten, auf die Ausgänge zu 
legen.

Insofern wäre mein Tipp: Behalte deinen bidirektionalen Port aber schau 
dir an, wie man den auf dem FPGA, das du verwendest, ansteuern soll und 
implementier das, wobei die Registerdaten noch in Output FFs gepuffert 
werden. Dann sollte auch das Timing kein Problem machen, wenns mal 
größer wird.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Die gepostete Beschreibung deutet eigentlich nicht auf eine derart tiefe 
Verschachtelung hin...
Das müssten ziemlich viele "if..elsif...elsif..." sein  :-/
Auch eine concurrent-Zuweisung mit vielen "else ... when" kann sowas 
ergeben. Aber wie gesagt, das kann aus diesem Codefragment noch nicht 
erkannt werden...

Was sind die Signale, die in diese Gatterkette hineingehen? Kommen die 
aus dem INT_REG_A(7..2)?

von Martin K. (mkohler)


Angehängte Dateien:

Lesenswert?

Matthias F. schrieb:
> Hmm, meine Registerbank gibts nur FPGA intern und geht nicht direkt an
> die Ausgänge, darum kann ich diesbezüglich nichts raten.
Da habe ich mich wohl nicht klar ausgedrückt.
Das oben gezeigte Modul ist eines von mehreren Register-Modulen. Diese 
(insgesamt 11) Module habe einen eigenen Adressbereich im external 
Memory des uC und werden über eine im FPGA implementierte Zugriffslogik 
angesteuert.
> Mir ist dein Tristate-Output nicht ganz geheuer. Funktioniert das schon
> in HW oder noch nur in der Simulation.
Ja, das funktioniert in Hardware synchron am uC mit einem 48MHz Clock.
> Es kommt mir so vor als würden
Die (nicht gezeigte) Zugriffslogik regelt das.
> Insofern wäre mein Tipp: Behalte deinen bidirektionalen Port aber...
Zu spät ;-) Ich bin nun daran, den bidirektionalen Bus auf einen 
separaten D_IN und D_OUT Bus umzuschreiben - Aufwand hält sich in engen 
Grenzen.
Auszug aus dem neuen COUNTERRegister.vhd:
1
    process (INT_REG_A, INT_CYCLE_access, INT_CYCLE_dir,
2
...
3
    begin 
4
        INT_D_OUT_sig                <= (others => '0');
5
        COUNTER_CONTROL_REG_write    <= '0';
6
  ...
7
        -- -> nur unterste 8 32Bit-Register implementiert
8
        if INT_CYCLE_access = '1' and INT_REG_A(7 downto 5) = "000" then
9
            -- Decodierung Bit4..2
10
            case INT_REG_A(4 downto 2) is
11
                -- Counter Control Register
12
                when "000" => 
13
                        if INT_CYCLE_dir = '0' then
14
                            -- Read Zugriff
15
                            INT_D_OUT_sig(2 downto 0)   <= COUNTER_CONTROL_sig(2 downto 0);
16
                            INT_D_OUT_sig(7 downto 3)   <= "00000"; -- Byte mit 0 fuellen
17
...
18
    NULLSIG_MODE(1 downto 0)        <= NULLSIG_CONTROL_sig(1 downto 0);
19
    NULLSIG_DIR                     <= NULLSIG_CONTROL_sig(2);
20
    -- Daten ausgeben wenn dieses Modul adressiert
21
    INT_D_OUT(31 downto 0) <= INT_D_OUT_sig(31 downto 0) when INT_CYCLE_access = '1' else (others => 'Z');
22
-------------------------------------------------------
23
end COUNTERRegister_arch;
Das Tristate Problem ist nun aber trotzdem noch nicht gelöst: Jedes 
meiner 11 Module hat nun einen D_OUT Bus, welcher auf die Interface 
Logik schreiben will.
Wie werde ich den Tristate da jetzt auch noch los?

Zum Problem mit den vielen hintereinandergeschalteten Gattern:
(siehe hier: Beitrag "Re: View RTL Schematic: "falsche" Darstellung des Designs")
Das kam daher, dass im kombinatorischen für alle Bits von INT_D ein 'Z' 
als Defaultwert zugewiesen wurde. Sobald ich den Defaultwert auf '0' 
ändere wird die Zuordnungslogik wesentlich breiter und weniger tief. Die 
Anzahl der verwendeten Gatter ändert sich aber nur unwesentlich.

Die "neue" RTL Darstellung ist im Anhang gezeigt.

Nun zu dir, Lothar...
Lothar Miller schrieb:
> Die gepostete Beschreibung deutet eigentlich nicht auf eine derart tiefe
> Verschachtelung hin...
Siehe oben. Es hing wohl davon ab, dass im case nicht jedes Mal die 
ganzen 32 Bit geschrieben wurden. Somit wurden die einzelnen Bits von 
INT_D unterschiedlich behandelt (bei der Gatter-Kaskade handelte es sich 
übrigens um die Zuweisung auf INT_D(0).

> Das müssten ziemlich viele "if..elsif...elsif..." sein  :-/
> Auch eine concurrent-Zuweisung mit vielen "else ... when" kann sowas
> ergeben.
> Aber wie gesagt, das kann aus diesem Codefragment noch nicht
> erkannt werden...
ganzes File im Anhang reichte nicht? Müsste auch die komplette uC 
Interface Logik dazu?

> Was sind die Signale, die in diese Gatterkette hineingehen? Kommen die
> aus dem INT_REG_A(7..2)?
Das INT_REG_A() enthält direkt die 32Bit uC Adressen.
INT_CYCLE_access wiederum ist nur dann aktiv, wenn über die höheren uC 
Adressen der richtige Adressblock ausgewählt ist. Beide Signale sind 
abgeclockt. Da sollte es eigentlich nicht Probleme geben.

Ich werde den uC Interface Teil in den nächsten Beiträge trotzdem kurz 
erläutern.

von Martin K. (mkohler)


Lesenswert?

Zur uC Interface Logik:
Jedes Modul erhält ein eigenes INT_CYCLE_access Signal, diese werden wie 
folgt generiert:
1
    -- Step Counter 1 Access:         0x4200..0x42FF
2
    COUNTER_1_ACCESS_DETECTED       <= '1' when INT_nCS = '0' and INT_A(15 downto 8)    = X"42"     else '0';

INT_REG_A(7 downto 2) ist nichts anderes als eine direkte, abgeclockte 
Zuweisung des Teilvektors uC_ADDR(15 downto 0) -> uC Adresse

INT_CYCLE_dir hängt ziemlich direkt am /RD, /CS und /OE des uC:
1
    -- Daten Richtung bestimmen
2
    -- 0: Read Zugriff, 1: Write Zugriff
3
    INT_DATA_DIR_sig     <= '0' when (INT_nCS = '0' and INT_nRD = '0' and INT_nOE = '0') else '1';

und noch der Write Impuls:
dieser wird aus dem /WR des uC generiert, jeweils nur dann, wenn ein 
Zugriff auf einen gültigen Block erfolgte und nur auf die 32Bit Adressen
1
    -- Write Zugriff, Daten aus 32Bit Interface Register in Register der Module uebernehmen
2
    INT_WRITE_ACCESS_impuls <= '1' when (INT_DATA_DIR_sig = '1' and INT_nWR = '0' and INT_ACCESS_DETECTED_sig = '1' and INT_ACCESS_DETECTED_old = '0') else '0';

von Martin K. (mkohler)


Lesenswert?

Nochmals zur Gatter-Kaskade:
Wahrscheinlich realisiert XST hier nicht, dass es sich bei den 
Zuweisungen auf INT_D_OUT eigentlich um einen 32Bit n-zu-1 MUX handelt, 
bei welchem halt nicht jedesmal alle Bits geschrieben werden.

Würde es wohl Ressourcen sparen, diesen MUX explizit als solchen zu 
formulieren, d.h. die Zuweisungen auf INT_D_OUT separat zu erledigen und 
nebenläufig die Write-Impulse zu generieren?

Oder sollte das ganze zur Ressourcenoptimierung in einen synchronen 
process reingepackt werden?

von Bernd G. (bege)


Lesenswert?

Hallo Martin,

ich habe ein ähnlichen Aufbau in meinem Design (mehrere Sub-Einheiten 
mit eigenen Registerbänken).

Ich benutze einen geteilten Datenbus INT_D_in, INT_D_out wobei alle 
Ausgänge über ein großes Oder-Gatter zusammengeschaltet sind. Das 
bedingt natürlich, daß immer nur eine 'Einheit' selektiert ist und die 
anderern im nicht-selektierten Zustand einen Null-Vektor auf ihren 
Ausgang legen.

In Vhdl sieht das dann etwa so aus:
1
INT_D_out <= INT_D_out1 or INT_D_out2 or INT_D_out3 ...

Und in der Sub-Einheit:
1
IND_D_out1 <= Reg_Out1 when ((CS1 = '1') and (read_access = '1')) else (others => '0');

Gruß Bernd

von Michael (Gast)


Lesenswert?

Die Tristate-Leitungen sind das Ausgänge über die I/O Bank, oder handelt 
es sich um interne Signale?

Die Spartan3 haben noch echte interne Tristate-Buffer,
Spartan3A und 3E kennen so etwas nicht mehr - hier wird tristate über 
Logik abgebildet :-///.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

> Die Spartan3 haben noch echte interne Tristate-Buffer
Wo im DB steht das?

Spartan II hatten noch TBUF,
ab S3 wurden solche Busse von Multiplexern abgelöst

von Christian R. (supachris)


Lesenswert?

Bernd Geyer schrieb:
> Hallo Martin,
>
> ich habe ein ähnlichen Aufbau in meinem Design (mehrere Sub-Einheiten
> mit eigenen Registerbänken).
>
> Ich benutze einen geteilten Datenbus INT_D_in, INT_D_out wobei alle
> Ausgänge über ein großes Oder-Gatter zusammengeschaltet sind. Das
> bedingt natürlich, daß immer nur eine 'Einheit' selektiert ist und die
> anderern im nicht-selektierten Zustand einen Null-Vektor auf ihren
> Ausgang legen.

Genau so machen wir das auch. Lebare Register per "Chip-Select" aus 
einem 6 zu 64 One Hot Decoder legen ihre Daten wenn sie selektiert sind 
auf den Ausgang des Moduls, im Top-Level sind die einfach alle 
Oder-verfknüpft. Funktioniert sehr gut. Der One-Hot-Decoder wird vom XST 
Slice-sparend in den BlockRam als ROM abgebildet (wenn Block RAM 
verfügbar).

von Michael (Gast)


Lesenswert?

Sorry Lothar - du hast recht,

ich habe damals den 2E programmiert - der hatte TBUFs.
Die komplette 3er Serie hat diese nicht mehr.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.