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.
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.
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...
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.
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.
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; |
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.