Forum: FPGA, VHDL & Co. Komplexe Pipeline mit AXI-Stream: Ready-Signal kombinatorisch?


von dzopf (Gast)


Lesenswert?

Hallöle,
nachdem ich mich eine Weile mit Xilinx-Cores beschäftigt habe, und 
bereits komplexe Pipelines mit den Float-Generatoren gebaut habe, wollte 
ich nun alles mal selber machen und mir eine Pipeline bauen, die eine 
Multiplikation von 2 Posit-Nummern durchführt. Im Idealfall würde ich 
gerne die DSPs vom FPGA nutzen (per Inferenz) und AXI-Streams anbieten, 
die - wie die Float-IPs von Xilinx - in jeder Pipeline-Stufe einen Wert 
haben können, und dies auch funktioniert, wenn der Consumer danach nicht 
ready ist. Ein erster Prototyp existiert, dieser hat jedoch das Problem, 
dass ein sehr langer kombinatorischer Pfad entsteht, da alle 
ready-Signale jeder Pipeline-Stufe miteinander kombiniert werden, und 
das mehrstufig. Xilinx löst das Problem irgendwie, ich komme nur auf die 
Idee, jede Stufe mit einem "fully registered slice" auszustatten, 
welches die ready-Signale abkoppelt, dafür jedoch pro Pipeline-Stufe die 
doppelten Register braucht. Versteht einer, was ich meine und kann mir 
einen Tipp geben? Ich plane noch, die Pipeline zu verlängern (zurzeit 6 
Stufen) und würde gerne bei möglichst geringem Ressourcen-Aufwand die 
höchste Performance in Taktrate.

von daniel__m (Gast)


Lesenswert?

dzopf schrieb:
> fully registered slic

Hi,

das sind im Prinzip kleine, 2-stufige Fifos, welche das ready-Signal 
aufbrechen und somit den "Snake-Path" verkleinern.

von dzopf (Gast)


Lesenswert?

Und das scheint mir sehr ineffizient zu sein, um nur das ready-Signal 
aufzusplitten. Außerdem stellt sich mir die Frage, ob ich bei diesen 
Register Slices überhaupt eine DSP mit allen internen Registern 
inferieren kann...

von daniel__m (Gast)


Lesenswert?

dzopf schrieb:
> sehr ineffizient

das ist relativ und kommt auf die Umsetzung an. Sofern "nur" Register 
en-masse verwendet werden, ist es weniger kritisch, da die "billig" sind 
und üblicherweise keine Problem darstellen.

Alternativ habe ich es so umgesetzt, dass ich am Ende ein Fifo packe und 
die Eingangsdaten "fire-and-forget" durch die Module schicke. Das Fifo 
steuert das Losschicken der Daten und muss natürlich die richtige 
Mindestgröße haben, dass es im Worst-Case alle Daten der Module 
aufnehmen kann (Latenz), wenn just in dem Moment das Down-Stream-Modul 
sein Ready wegnimmt.

von Weltbester FPGA-Pongo (Gast)


Lesenswert?

dzopf schrieb:
> ürde gerne bei möglichst geringem Ressourcen-Aufwand die
> höchste Performance in Taktrate.

Was ist bei Dir ein "geringer Bedarf" und was "eine hohe Taktrate"? Die 
DSPs kann man alternierend mit 500MHz takten (multi Cycle) sequenziell 
300MHz. So schnell sind die AXI-Busse normal nicht.

von dzopf (Gast)


Lesenswert?

Die Nutzung der DSPs sind nur ein Beispiel gewesen, mir ging es auch um 
die Frage, wie man lange kombinatorische Pfade, die bei Pipelines 
aufgrund des ready-Signals entstehen, vermeidet. Die Idee mit der FIFO 
am Ende klingt schonmal gut, aber wird bei komplexeren Pipelines nicht 
immer funktionieren (bspw. wenn die Pipeline divergiert und mehrere 
FIFOs füttert, müsste man alle FIFOs auf deren Acceptance prüfen - wobei 
das bei registrierten Almost-Full-Signalen eigentlich hinhauen sollte). 
Meine Designs sind bisher immer sehr sparsam an Registern gewesen, bzw. 
war die Anzahl der LUTs immer höher. Bei aktuellen Bausteinen (habe den 
Cyclone 10 GX als Grundlage) ist die Anzahl der Register ja doppelt so 
hoch wie die der LUTs, es sollte also nichts dagegen sprechen, jede 
Pipeline-Stufe mit zwei Register-Sätzen auszustatten. Ich habe mich 
daran versucht, ein Code-Pattern zu entwickelt, um meine bisherige 
Implementierung möglichst einfach zu erweitern, aber ohne viel 
Boilerplate-Code wird das leider nichts. Ich arbeite nach der 
Gaisler-Methode, siehe Beispiel. Hier ist das alles noch sehr einfach, 
wird aber kompliziert, wenn die Pipeline-Stufe mehr als nur eine 
einfache Operation durchführt. Zurzeit versuche ich mich an einem 
Pattern, das auch bspw. eine Summation von einer Zahlenfolge durchführt, 
dabei aber auch zwei Output-Registersätze besitzt.
1
library ieee;
2
use ieee.std_logic_1164.all;
3
use ieee.numeric_std.all;
4
5
entity multiplier is
6
  generic(
7
    DATA_WIDTH : positive := 16
8
  );
9
  port(
10
    clk_i     : in  std_ulogic;
11
    reset_i   : in  std_ulogic;
12
    -- Input A
13
    a_data_i  : in  std_ulogic_vector(DATA_WIDTH - 1 downto 0);
14
    a_valid_i : in  std_ulogic;
15
    a_ready_o : out std_ulogic;
16
    -- Input B
17
    b_data_i  : in  std_ulogic_vector(DATA_WIDTH - 1 downto 0);
18
    b_valid_i : in  std_ulogic;
19
    b_ready_o : out std_ulogic;
20
    -- Output C
21
    c_data_o  : out std_ulogic_vector(2 * DATA_WIDTH - 1 downto 0);
22
    c_valid_o : out std_ulogic;
23
    c_ready_i : in  std_ulogic
24
  );
25
end entity multiplier;
26
27
architecture rtl of multiplier is
28
  -- type declarations
29
  type output_fsm_t is (S_0, S_1, S_2);
30
31
  type reg_t is record
32
    -- Output state machine
33
    fsm       : output_fsm_t;
34
    -- Output-Sets
35
    set0_data : std_ulogic_vector(2 * DATA_WIDTH - 1 downto 0);
36
    set1_data : std_ulogic_vector(2 * DATA_WIDTH - 1 downto 0);
37
  end record reg_t;
38
39
  -- reset definition
40
  constant REG_RESET : reg_t := (
41
    fsm       => S_0,
42
    set0_data => (others => 'U'),
43
    set1_data => (others => 'U')
44
  );
45
46
  -- functions
47
  function mult(a, b : std_ulogic_vector(DATA_WIDTH - 1 downto 0))
48
  return std_ulogic_vector is
49
  begin
50
    return std_ulogic_vector(unsigned(a) * unsigned(b));
51
  end function mult;
52
53
  -- signal declarations
54
  signal r, rin : reg_t := REG_RESET;
55
begin
56
  comb : process(all)
57
    variable n : reg_t;
58
  begin
59
    n := r;
60
61
    n.set0_data := mult(a_data_i, b_data_i);
62
    n.set1_data := r.set0_data;
63
64
    case r.fsm is
65
      when S_0 =>
66
        if a_valid_i and b_valid_i then
67
          n.fsm := S_1;
68
        end if;
69
70
      when S_1 =>
71
        if a_valid_i and b_valid_i and c_ready_i then
72
          n.fsm := S_1;
73
        elsif a_valid_i and b_valid_i then
74
          n.fsm := S_2;
75
        elsif c_ready_i then
76
          n.fsm := S_0;
77
        else
78
          n.set0_data := r.set0_data;
79
        end if;
80
81
      when S_2 =>
82
        if c_ready_i then
83
          n.fsm := S_1;
84
        else
85
          n.set1_data := r.set1_data;
86
        end if;
87
    end case;
88
89
    rin <= n;
90
91
    -- Outputs
92
    case r.fsm is
93
      when S_0 =>
94
        a_ready_o <= b_valid_i;
95
        b_ready_o <= a_valid_i;
96
97
        c_data_o  <= (others => 'U');
98
        c_valid_o <= '0';
99
100
      when S_1 =>
101
        a_ready_o <= b_valid_i;
102
        b_ready_o <= a_valid_i;
103
104
        c_data_o  <= r.set0_data;
105
        c_valid_o <= '1';
106
107
      when S_2 =>
108
        a_ready_o <= '0';
109
        b_ready_o <= '0';
110
111
        c_data_o  <= r.set1_data;
112
        c_valid_o <= '1';
113
    end case;
114
  end process comb;
115
116
  regs : process(clk_i, reset_i)
117
  begin
118
    if reset_i then
119
      r <= REG_RESET;
120
    elsif rising_edge(clk_i) then
121
      r <= rin;
122
    end if;
123
  end process regs;
124
end architecture rtl;

von daniel__m (Gast)


Lesenswert?

Per review würde ich sage, das sieht doch vielversprechend aus.

Wenn es um mehrere in- / outputs geht, evtl. wird es vereinfacher, wenn 
man erst per glue-logic die in-Streams zu einem Stream synchronisiert 
und auch ausgangsseitig erst im 2. Schritt per glue-logic aufsplittet.

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.