www.mikrocontroller.net

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


Autor: Martin Kohler (mkohler)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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:
        -- Steuerung Lese/Schreibvorgang auf den Registern
        INT_REG_A                           : in    STD_LOGIC_VECTOR(7 downto 2);
        INT_D                               : inout STD_LOGIC_VECTOR(31 downto 0);
        INT_CYCLE_access                    : in    STD_LOGIC;
        INT_CYCLE_dir                       : in    STD_LOGIC;
        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:
        -- dieses Modul adressiert?
        -- gueltiger Adressbereich selektiert?
        -- -> nur unterste 8 32Bit-Register implementiert
        if INT_CYCLE_access = '1' and INT_REG_A(7 downto 5) = "000" then
            -- Decodierung Bit4..2
            case INT_REG_A(4 downto 2) is
                -- Counter Control Register
                when "000" => 
                        if INT_CYCLE_dir = '0' then
                            -- Read Zugriff
                            INT_D(2 downto 0)   <= COUNTER_CONTROL_sig(2 downto 0);
                            INT_D(7 downto 3)   <= "00000"; -- Byte mit 0 fuellen
                            INT_D(10 downto 8)  <= NULLSIG_CONTROL_sig(2 downto 0);
                            INT_D(11)           <= '0'; -- Byte mit 0 fuellen
                            INT_D(14 downto 12) <= NULLSIG_STATE(2 downto 0);
                            INT_D(15)           <= '0'; -- Byte mit 0 fuellen
                        else
                            -- Write Zugriff
                            COUNTER_CONTROL_REG_write <= '1';
                        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:
    -- Counter Control Register schreiben
    process(INT_CLK)
    begin
        if rising_edge(INT_CLK) then
            if COUNTER_CONTROL_REG_write = '1' and INT_CYCLE_WRITE_ACCESS_impuls = '1' then
                COUNTER_CONTROL_sig(2 downto 0) <= INT_D(2 downto 0);
                NULLSIG_CONTROL_sig(2 downto 0) <= INT_D(10 downto 8);
            end if;
        end if;
    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?

Autor: Matthias F. (flint)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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)?

Autor: Martin Kohler (mkohler)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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:
    process (INT_REG_A, INT_CYCLE_access, INT_CYCLE_dir,
...
    begin 
        INT_D_OUT_sig                <= (others => '0');
        COUNTER_CONTROL_REG_write    <= '0';
  ...
        -- -> nur unterste 8 32Bit-Register implementiert
        if INT_CYCLE_access = '1' and INT_REG_A(7 downto 5) = "000" then
            -- Decodierung Bit4..2
            case INT_REG_A(4 downto 2) is
                -- Counter Control Register
                when "000" => 
                        if INT_CYCLE_dir = '0' then
                            -- Read Zugriff
                            INT_D_OUT_sig(2 downto 0)   <= COUNTER_CONTROL_sig(2 downto 0);
                            INT_D_OUT_sig(7 downto 3)   <= "00000"; -- Byte mit 0 fuellen
...
    NULLSIG_MODE(1 downto 0)        <= NULLSIG_CONTROL_sig(1 downto 0);
    NULLSIG_DIR                     <= NULLSIG_CONTROL_sig(2);
    -- Daten ausgeben wenn dieses Modul adressiert
    INT_D_OUT(31 downto 0) <= INT_D_OUT_sig(31 downto 0) when INT_CYCLE_access = '1' else (others => 'Z');
-------------------------------------------------------
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.

Autor: Martin Kohler (mkohler)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Zur uC Interface Logik:
Jedes Modul erhält ein eigenes INT_CYCLE_access Signal, diese werden wie 
folgt generiert:
    -- Step Counter 1 Access:         0x4200..0x42FF
    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:
    -- Daten Richtung bestimmen
    -- 0: Read Zugriff, 1: Write Zugriff
    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
    -- Write Zugriff, Daten aus 32Bit Interface Register in Register der Module uebernehmen
    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';

Autor: Martin Kohler (mkohler)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Bernd Geyer (bege)
Datum:

Bewertung
0 lesenswert
nicht 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:
INT_D_out <= INT_D_out1 or INT_D_out2 or INT_D_out3 ... 

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

Gruß Bernd

Autor: Michael (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 :-///.

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Christian R. (supachris)
Datum:

Bewertung
0 lesenswert
nicht 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).

Autor: Michael (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sorry Lothar - du hast recht,

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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [vhdl]VHDL-Code[/vhdl]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.