Forum: FPGA, VHDL & Co. Timing Optimierung bei einem Schieberegister


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Peter (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hallo Leute,

Ich brauche Hilfe bei einem Timing Problem. Zunächst mal die 
Beschreibung meiner Logik:
Ich habe einen Bus der wie folgt aufgebaut ist:
  data_i        : in  std_logic_vector(DATA_WIDTH-1 downto 0);
  data_valid_i  : in  std_logic
  valid_bytes_i : in  std_logic_vector(DATA_WIDTH/8-1 downto 0);

data_i enthält die Daten, data_valid_i markiert ein gültiges Datum und 
valid_bytes_i markiert die gültigen Bytes von data_i.

Mein Problem ist, dass valid_bytes_i die Bytes durcheinander markieren 
kann. Also z.b. so 10110011. Es gibt also Null Bytes zwischen gültigen 
Daten. Ich möchte diese Null Bytes gerne rausschmeißen. Damit ich am 
Ende immer ein valid_bytes_i von 11111111 bekomme.
Das habe ich bisher wie folgt gelöst (Codeausschnitt):
      byte_cnt_u_v := byte_cnt_u;

      -- load old data_sr value
      data_sr_v    := data_sr;

      -- if valid slave port data
      if data_valid_i = '1' then
        for i in 0 to DATA_WIDTH/8-1 loop
          if valid_bytes_i(i) = '1' then  -- if byte is marked as valid
            -- shift valid byte into data_sr
            data_sr_v    := data_i((i+1)*8-1 downto i*8) & data_sr_v(DATA_WIDTH*2-1 downto 8);
            -- count valid bytes in shift register
            byte_cnt_u_v := byte_cnt_u_v + 1;
          end if;
        end loop;
      end if;
      -- save byte_cnt for next clk cycle
      byte_cnt_u <= byte_cnt_u_v;
     
      -- save data_sr value for next clk cycle
      data_sr    <= data_sr_v;
    
    if(byte_cnt_u_v >= DATA_WIDTH/8) then
    data_beat_ready_o <= '1';
    end if;


Die Idee ist, dass ich jedes Byte in einer Schleife durchlaufe und die 
gültigen Bytes in ein Schieberegister (data_sr_v) packe, welches doppelt 
so groß ist wie data_i. Des weiteren läuft ein Zähler mit der die Bytes 
im Schieberegister zählt. Wenn dieser die gewünschte Anzahl erreicht 
hat, kann das datum nach außen gegeben werden.
Dieses Konzept funktioniert soweit ganz gut.
Allerdings habe ich ein Problem wenn ich DATA_WIDTH auf 256 bit setzte.
Dann kann ich das Timing bei 200MHz nicht mehr schaffen.

Hat von euch einer eine Idee wie ich das ganze besser lösen könnte? Eine 
Lösung die das Timing entspannt.

Danke sehr

Gruß

Peter

von rdft (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Peter schrieb:
> Hat von euch einer eine Idee wie ich das ganze besser lösen könnte?

#Verwende ein schieberegister makro,
#mache kein paraleles load,
#benutze serdes primitive
#verzichte auf Mischen asynchronen und synchronen reset
#verzichte auf reset komplett
#optimiere PLL Routing
#Benutze multicycle pfade fürs parallele load

von Lothar M. (lkmiller) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Peter schrieb:
> bei einem Schieberegister
Ich hoffe, dir ist klar, dass das Problem nichts mit einem 
Schieberegister zu tun hat, sondern dass du hier offenbar viele 
Multiplexer optimieren musst und zwar bei 256 Bit am Eingang (also 32 
Bytes) vom 32:1, 31:1, 30:1 bis zum 2:1 Multiplexer. Das Schieberegister 
ist dann nur dahinter geschaltet.

> Hat von euch einer eine Idee wie ich das ganze besser lösen könnte? Eine
> Lösung die das Timing entspannt.
Derzeit hast du diese Multiplexer alle parallel implementiert und du 
berechnest das Ergebnis mit jedem Taktzyklus. Das muss vermutlich nicht 
sein, denn so schnell kannst du das Ergebnis eigentlich gar nicht 
hinausschieben. Der Knackpunkt ist also: wie oft bzw. wie schnell kommen 
(durchschnittlich) neue Daten? Mit jedem Takt? Mit jedem 2. oder jedem 
10. oder jedem 32. Takt?

BTW: hast du die Beschreibung schon mal simuliert? Zumindest in dem 
Codeschnipsel hier wird dein byte_cnt_u gar nie zurückgesetzt...

BTW2: ich kriege echt die Augenpest bei diesem Gaisler-Stil. Aber nur so 
kommt der Softwareprogrammierer zu seinen lieben Variablen...  ;-)

von Peter (Gast)


Bewertung
0 lesenswert
nicht lesenswert
rdft schrieb:
> Peter schrieb:
>> Hat von euch einer eine Idee wie ich das ganze besser lösen könnte?
>
> #Verwende ein schieberegister makro,
> #mache kein paraleles load,
> #benutze serdes primitive
> #verzichte auf Mischen asynchronen und synchronen reset
> #verzichte auf reset komplett
> #optimiere PLL Routing
> #Benutze multicycle pfade fürs parallele load

Danke ich werde die Tipps mal versuchen umzusetzten und melde mich dann 
wieder. Eine Frage zum Serdes. Diese logik wird nicht an den FPGA Pins 
angeschlossen. Das ist nur für intern. Kann man SERDES bausteine auch 
für interne logik benutzten? Und kannst du mir noch erklären was ein 
paralleles load genau ist? Meinst du die For schleife in einem takt?


Lothar M. schrieb:
> Peter schrieb:
>> bei einem Schieberegister
> Ich hoffe, dir ist klar, dass das Problem nichts mit einem
> Schieberegister zu tun hat, sondern dass du hier offenbar viele
> Multiplexer optimieren musst und zwar bei 256 Bit am Eingang (also 32
> Bytes) vom 32:1, 31:1, 30:1 bis zum 2:1 Multiplexer. Das Schieberegister
> ist dann nur dahinter geschaltet.
>
>> Hat von euch einer eine Idee wie ich das ganze besser lösen könnte? Eine
>> Lösung die das Timing entspannt.
> Derzeit hast du diese Multiplexer alle parallel implementiert und du
> berechnest das Ergebnis mit jedem Taktzyklus. Das muss vermutlich nicht
> sein, denn so schnell kannst du das Ergebnis eigentlich gar nicht
> hinausschieben. Der Knackpunkt ist also: wie oft bzw. wie schnell kommen
> (durchschnittlich) neue Daten? Mit jedem Takt? Mit jedem 2. oder jedem
> 10. oder jedem 32. Takt?

Das kann man so genau nicht sagen. Sie können mit jedem Takt kommen aber 
auch seltener. Habe schon alles gesehen. Manchmal 3 daten als burst. 
Aber durchschnittlich sollte es wohl mit jedem 2ten Takt sein. Nur muss 
ich mich darauf einstellen das es auch mit jedem Takt sein kann.
Ich muss die Daten nicht unbedingt mit dem nächsten Takt weitergeben, 
aber zulange möchte/sollte ich auch nicht brauchen.

>
> BTW: hast du die Beschreibung schon mal simuliert? Zumindest in dem
> Codeschnipsel hier wird dein byte_cnt_u gar nie zurückgesetzt...
Ähm ja das funktioniert schon. Der counter wird weiter unten 
zurückgesetzt. Zeige nur dieses Codeschnipsel hier weil ich an der 
Stelle Timing Probleme habe. Aber wie gesagt erst ab 256 bit busbreite

>
> BTW2: ich kriege echt die Augenpest bei diesem Gaisler-Stil. Aber nur so
> kommt der Softwareprogrammierer zu seinen lieben Variablen...  ;-)

Was ist denn hier ein schlechter vhdl stil? Die Variablen und die 
Forschleife brauche ich ja wenn ich das ganze möglichst performant und 
schnell lösen will oder nicht?

Danke vielmals für eure Antworten. :)


Gruß
Peter

von Lothar M. (lkmiller) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Peter schrieb:
> Aber durchschnittlich sollte es wohl mit jedem 2ten Takt sein.
256 Bits mit 100MHz? Das sind 25 Gb/s! Recht knackig. Irgendwo habe ich 
da noch einen Knoten im Hirn...
Wie bekommst du so viele Daten so schnell wieder weg?

> Aber durchschnittlich sollte es wohl mit jedem 2ten Takt sein.
Kannst du dir da ein paar Takte Latency erlauben und das System 
pipelinen? Also nicht alle Schritte auf einmal machen, sondern z.B. 
Multiplexerstufen hintereinander schalten und durch zwischengeschaltete 
Flipflops die Logikebenen reduzieren?

> Was ist denn hier ein schlechter vhdl stil?
Es verleitet Softwareprogrammierer dazu, zu meinen, man könne in VHDL 
progrommieren wie man in C programmiert. Aber letztendlich beschreibt 
dieses "VHDL-Programm" Hardware und muss vom Synthesizer in ein FPGA 
abgebildet werden können.

> Die Variablen und die Forschleife brauche ich ja wenn ich das ganze
> möglichst performant und schnell lösen will oder nicht?
Was sollte sich an dieser HDL-Beschreibung durch Variablen oder gar eine 
Schleife (die der Synthesizer ja in paralelle hintereinandergeschaltete 
Hardware ausrollen muss!) beschleunigen lassen?
Nein, wenn du was "möglichst performant" machen willst, dann brauchst du 
eine Idee, wie du das mit Hardware (und zwar mit Logik/LUTs sowie 
Flipflops und RAM-Blöcken, mehr hast du im Grunde nicht) "möglichst 
performant" lösen könntest. Und wenn diese Idee funktioniert, dann 
nimmst du eine HDL (Verilog, VHDL oder sonstwas) und beschreibst die 
Funktion deiner Lösung mit dieser Sprache.

Ich revidiere übrigens meine Aussage mit den Multiplexern von oben: es 
sind nicht unterschiedlich große Multiplexer nötig, sondern 256 maximal 
große 32:1 Multiplexer, weil ja jedes Byte an jede Stelle in dem Wort 
verschoben werden kann.

> Dann kann ich das Timing bei 200MHz nicht mehr schaffen.
Auf welchem Zielsystem? Hast du da 4er oder 6er LUTs?
Mit 6er-LUTs würde ein 32:1 MUX nämlich nur 1 Logikebene brauchen und 
wäre im Prinzip pfeilschnell.

: Bearbeitet durch Moderator
von Christoph Z. (christophz)


Bewertung
0 lesenswert
nicht lesenswert
Peter schrieb:
> Hat von euch einer eine Idee wie ich das ganze besser lösen könnte? Eine
> Lösung die das Timing entspannt.

Die gültigen Bytes in ein FIFO füllen anstatt diese vielen Multiplexer.

von Lothar M. (lkmiller) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Christoph Z. schrieb:
> Die gültigen Bytes in ein FIFO füllen anstatt diese vielen Multiplexer.
Und wie soll das Auswählen der gültigen Bytes für den Fifo ohne 
Multiplexer gehen?

von Peter (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Lothar M. schrieb:
> Peter schrieb:
>> Aber durchschnittlich sollte es wohl mit jedem 2ten Takt sein.
> 256 Bits mit 100MHz? Das sind 25 Gb/s! Recht knackig. Irgendwo habe ich
> da noch einen Knoten im Hirn...
> Wie bekommst du so viele Daten so schnell wieder weg?
>
Am Eingang ist ein PCIE Gen3 IP Core der als Ausgangsinterface die oben 
beschriebenen Ports hat. Ich möchte die Daten in einen DMA füttern. 
Dieser kann aber nur voll gültige Daten akzeptieren. Also ohne Null 
Bytes dazwischen.
Da ja meist nicht alle Btyes pro Datum gültig sind, verringert sich ja 
die Datenrate. Und das mit dem durchschnittlich 2 Takte pro Datum ist 
wohl auch eher übertrieben. Das Problem ist nur das auch mal ein Burst 
kommen kann mit vielleicht 10 zusammenhängenden Packeten. Also 10x256bit 
auf 10 zyklen. Und danach längere Pause. Ich bin vom Worst case 
ausgegangen, mit 1 datum pro cycle. Ich dachte mir ich bau eine Logik 
die auch für andere Projekte eingesetzt werden kann. Und da kann 
vielleicht mal diese krasse Anforderung kommen.

>> Aber durchschnittlich sollte es wohl mit jedem 2ten Takt sein.
> Kannst du dir da ein paar Takte Latency erlauben und das System
> pipelinen? Also nicht alle Schritte auf einmal machen, sondern z.B.
> Multiplexerstufen hintereinander schalten und durch zwischengeschaltete
> Flipflops die Logikebenen reduzieren?

Wie schnell ich die Daten weitergebe spielt eigentlich gar keine große 
Rolle. Nur kann ich den PCIE Core nicht ausbremsen. Der feuert wie er 
lust hat.
Kannst du mir dein konzept vielleicht grob an einem Code Beispiel 
erläutern? Das wäre sehr hilfreich für mich :)


>> Dann kann ich das Timing bei 200MHz nicht mehr schaffen.
> Auf welchem Zielsystem? Hast du da 4er oder 6er LUTs?
> Mit 6er-LUTs würde ein 32:1 MUX nämlich nur 1 Logikebene brauchen und
> wäre im Prinzip pfeilschnell.

Es ist ein Xilinx Zynq Device. also 6er LUTs.
Mit 128 bit busbreite schaffe ich das Timing sogar mit 200Mhz.

Danke sehr

Gruß

Peter

von Achim S. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Die mittlere Datenrate ist also deutlich geringer als die 
Burst-Datenrate, und eine gewisse Latenz ist nicht dramatisch?

Dann bau vor deine Stufe einen kleinen Pufferspeicher. Ein BRAM als FIFO 
verschaltet reicht aus, um die Daten schnell genugt vom IP-Core 
abzunehmen. Und auf der anderen Seite des FIFOs bearbeitest du die Daten 
halt so schnell, wie dein FPGA kann (solange es nur für die mittlere 
Datenrate ausreichend ist).

von Peter (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hallo nochmal,

ich hab jetzt mal einen anderen Ansatz gewählt. Den möchte ich euch mal 
kurz vorstellen. Ich shifte jetzt pro clock cycle nur einmal. Somit muss 
bei 256 bit busbreite 32 mal shiften (weil 32 bytes).

Hier mal der Code

LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.numeric_std.ALL;
ENTITY test IS
  GENERIC (
    DATA_WIDTH : NATURAL := 256
  );
  PORT (
    -- clk,reset
    aclk : IN std_ulogic;
    aresetn : IN std_ulogic;

    -- input
    data_valid_i : IN std_ulogic;
    data_i : IN std_ulogic_vector(DATA_WIDTH - 1 DOWNTO 0);
    valid_bytes_i : IN std_ulogic_vector(DATA_WIDTH/8 - 1 DOWNTO 0);

    -- output
    data_valid_o : OUT std_ulogic;
    data_o : OUT std_ulogic_vector(DATA_WIDTH - 1 DOWNTO 0);
    valid_bytes_o : OUT std_ulogic_vector(DATA_WIDTH/8 - 1 DOWNTO 0)
  );
END ENTITY test;

ARCHITECTURE rtl OF test IS

  TYPE data_t IS ARRAY (0 TO DATA_WIDTH/8 - 1) OF std_ulogic_vector(DATA_WIDTH - 1 DOWNTO 0);
  TYPE valid_bytes_t IS ARRAY (0 TO DATA_WIDTH/8 - 1) OF std_ulogic_vector(DATA_WIDTH/8 - 1 DOWNTO 0);
  TYPE array_pos_t IS ARRAY (0 TO DATA_WIDTH/8 - 1) OF INTEGER RANGE 0 TO DATA_WIDTH - 8;
  SIGNAL data_array : data_t := (OTHERS => (OTHERS => '0'));
  SIGNAL valid_bytes_array : valid_bytes_t := (OTHERS => (OTHERS => '0'));
  SIGNAL array_pos : array_pos_t;
  SIGNAL cnt_valid_bytes : INTEGER RANGE 0 TO DATA_WIDTH/8 := 0;
  SIGNAL sr_array_pos : INTEGER RANGE 0 TO DATA_WIDTH * 2 := 0;
  SIGNAL data_sr : std_ulogic_vector(DATA_WIDTH * 2 - 1 DOWNTO 0) := (OTHERS => '0');

BEGIN
  start_stage : PROCESS (aclk)
  BEGIN
    IF rising_edge(aclk) THEN 
      data_array(0) <= data_i;
      valid_bytes_array(0) <= valid_bytes_i;
      array_pos(0) <= 0;
      IF (data_valid_i = '1') THEN
        IF (valid_bytes_i(0) = '1') THEN
          array_pos(0) <= 8;
        END IF; 
      END IF;
    END IF;
  END PROCESS;
 
  gen_stages : FOR index IN 0 TO DATA_WIDTH/8 - 3 GENERATE
    stages : PROCESS (aclk)
    BEGIN
      IF rising_edge(aclk) THEN
        valid_bytes_array(index + 1) <= valid_bytes_array(index);
        data_array(index + 1) <= data_array(index);
        array_pos(index + 1) <= array_pos(index);
        IF (valid_bytes_array(index)(index + 1) = '1') THEN
          data_array(index + 1)(array_pos(index) + 7 DOWNTO array_pos(index)) <= data_array(index)(15 + index * 8 DOWNTO 8 + index * 8);
          array_pos(index + 1) <= array_pos(index) + 8;
        END IF; 
      END IF;
    END PROCESS;
    END GENERATE gen_stages;
 
    end_stage : PROCESS (aclk)
      VARIABLE cnt : unsigned (DATA_WIDTH/8 - 1 DOWNTO 0);
    BEGIN
      IF rising_edge(aclk) THEN
        data_array(DATA_WIDTH/8 - 1) <= (OTHERS => '0');
        array_pos(DATA_WIDTH/8 - 1) <= array_pos(DATA_WIDTH/8 - 2);
 
        IF (valid_bytes_array(DATA_WIDTH/8 - 2)(DATA_WIDTH/8 - 1) = '1') THEN 
          data_array(DATA_WIDTH/8 - 1)(array_pos(DATA_WIDTH/8 - 2) + 7 DOWNTO array_pos(DATA_WIDTH/8 - 2)) <= data_array(DATA_WIDTH/8 - 2)(DATA_WIDTH - 1 DOWNTO DATA_WIDTH - 8);
        END IF;
 
        IF (array_pos(DATA_WIDTH/8 - 2) /= 0) THEN
          data_array(DATA_WIDTH/8 - 1)(array_pos(DATA_WIDTH/8 - 2) - 1 DOWNTO 0) <= data_array(DATA_WIDTH/8 - 2)(array_pos(DATA_WIDTH/8 - 2) - 1 DOWNTO 0);
        END IF;
 
        cnt := (OTHERS => '0');
        FOR i IN 0 TO DATA_WIDTH/8 - 1 LOOP
          cnt := cnt + unsigned'("0" & valid_bytes_array(DATA_WIDTH/8 - 2)(i));
        END LOOP;
        cnt_valid_bytes <= to_integer(cnt);
      END IF;
    END PROCESS;
 
    output_stage : PROCESS (aclk)
 
    BEGIN
      IF rising_edge(aclk) THEN
        data_valid_o <= '0';
        valid_bytes_o <= (OTHERS => '0');
        data_o <= (OTHERS => '0');
        IF (sr_array_pos >= DATA_WIDTH) THEN
          data_valid_o <= '1';
          data_o <= data_sr(DATA_WIDTH - 1 DOWNTO 0);
          valid_bytes_o <= (OTHERS => '1');
          data_sr(sr_array_pos - 1 DOWNTO 0) <= data_array(DATA_WIDTH/8 - 1) & data_sr(sr_array_pos - 1 DOWNTO DATA_WIDTH);
          sr_array_pos <= sr_array_pos - DATA_WIDTH + cnt_valid_bytes * 8; 
        ELSE
          data_sr(sr_array_pos + DATA_WIDTH - 1 DOWNTO sr_array_pos) <= data_array(DATA_WIDTH/8 - 1);
          sr_array_pos <= sr_array_pos + cnt_valid_bytes * 8; 
        END IF;
      END IF;
    END PROCESS;
 

END ARCHITECTURE rtl;


Nach dem ersten Blick in der Simulation schaut es so aus als ob es 
funktioneirt. Mit dem Timing habe ich jetzt auch keine Probleme mehr.
Allerdings verbrauche ich wie erwartet viele Resourcen da ich immer noch 
Monstermultiplexer zuammen baue :D

CLB/CARRY  1
CLB/LUT          9576
CLB/SRL          282
REGISTER/SDR  8189

Das ist jetzt bei 256 bit busbreite.
Leider fällt mir nichts besseres ein.

Was haltet ihr davon? Bin für jede Kritik dankbar


Gruß

Peter

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.

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