Heute widme ich mich der Optimierung einer kleinen Pipeline, die ich für ein Projekt erstellt habe. Die Pipeline ist dafür verantwortlich, den Adress-Offset eines Sprites zu berechnen und zu prüfen, ob dieses in der aktuell angeforderten Zeile sichtbar ist.
Bisher setze ich darauf, dass das Register Rebalancing seine Arbeit erledigt. Dies funktioniert auch, doch verbleiben klarerweise noch einige Optimierungsmöglichkeiten.
Zum Einstieg erst mal den aktuellen Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
| library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
use work.SpriteRom.all;
entity VerticalSpritePipeline is
generic (
--@ Width of the Y position (Line) register
G_Y_Width : integer := 10;
--@ The height of the sprite in pixels
G_Sprite_Height : integer := 16;
--@ Width of the sprite offset (Line address) register
G_Offset_Width : integer := 8;
--@ The pipeline stages for the calculating pipeline (multiply by 2 for the the latency of the pipeline)
G_PipelineStages : integer := 2
);
port (
--@ Clock signal; (**Rising edge** triggered)
I_CLK : in std_logic := '0';
--@ Clock enable signal (**Active high**)
I_CE : in std_logic := '1';
--@ @virtualbus VSpritePipeline-OP @dir In Vertical sprite pipeline operation interfacee
--@ AXI like ready; (**Synchronous**, **Active high**)
O_VSpritePipeline_OP_Ready : out std_logic := '0';
--@ AXI like valid; (**Synchronous**, **Active high**)
I_VSpritePipeline_OP_Valid : in std_logic := '0';
--@ The line to check if the sprite is in the line visible.
I_VSpritePipeline_OP_Y_Request : in std_logic_vector(G_Y_Width - 1 downto 0) := (others => '0');
--@ The sprite Y position to check if the sprite is in the line visible.
I_VSpritePipeline_OP_Y_Sprite : in std_logic_vector(G_Y_Width - 1 downto 0) := (others => '0');
--@ @end
--@ @virtualbus VSpritePipeline-Result @dir Out Vertical sprite pipeline result interface
--@ AXI like ready; (**Synchronous**, **Active high**)
I_VSpritePipeline_Ready : in std_logic := '0';
--@ AXI like valid; (**Synchronous**, **Active high**)
O_VSpritePipeline_Valid : out std_logic := '0';
--@ Indicates if the sprite is visible in the line.
O_VSpritePipeline_IsVisible : out std_logic := '0';
--@ The calculated offset address of the sprite.
O_VSpritePipeline_Offset : out std_logic_vector(G_Offset_Width - 1 downto 0) := (others => '0')
--@ @end
);
end entity VerticalSpritePipeline;
architecture Rtl of VerticalSpritePipeline is
--@ Line to check if the sprite is in the line visible
signal R_Y_Request : std_logic_vector(G_Y_Width - 1 downto 0) := (others => '0');
--@ The sprite Y position to check if the sprite is in the line visible
signal R_Y_Sprite : std_logic_vector(G_Y_Width - 1 downto 0) := (others => '0');
--@ Calculated visibility signal
signal C_IsVisible : std_logic := '0';
--@ The calculated offset address of the sprite
signal C_Offset : std_logic_vector(G_Offset_Width - 1 downto 0) := (others => '0');
--@ Pipeline enable signal
signal S_CalculatingPipeline_Enable : std_logic := '0';
begin
--@ Pipeline controller for the calculating pipeline
I_CalculatingPipelineCtrl : entity work.PipelineController
generic map(
G_PipelineStages => G_PipelineStages * 2
)
port map(
I_CLK => I_CLK,
I_CE => I_CE,
O_Enable => S_CalculatingPipeline_Enable,
I_Valid => I_VSpritePipeline_OP_Valid,
O_Ready => O_VSpritePipeline_OP_Ready,
O_Valid => O_VSpritePipeline_Valid,
I_Ready => I_VSpritePipeline_Ready
);
--@ Input register for the Y position of the sprite
I_Y_InputRegister : entity work.PipelineRegister
generic map(
G_PipelineStages => G_PipelineStages,
G_Width => G_Y_Width,
G_RegisterBalancing => "forward"
)
port map(
I_CLK => I_CLK,
I_Enable => S_CalculatingPipeline_Enable,
I_Data => I_VSpritePipeline_OP_Y_Sprite,
O_Data => R_Y_Sprite
);
--@ Input register for the line to check if the sprite is in the line visible
I_YToCheck_InputRegister : entity work.PipelineRegister
generic map(
G_PipelineStages => G_PipelineStages,
G_Width => G_Y_Width,
G_RegisterBalancing => "forward"
)
port map(
I_CLK => I_CLK,
I_Enable => S_CalculatingPipeline_Enable,
I_Data => I_VSpritePipeline_OP_Y_Request,
O_Data => R_Y_Request
);
--@ Combinatory process to calculate the visibility and offset of the sprite.
P_CalculateVisibility : process (R_Y_Sprite, R_Y_Request)
variable V_Y_Sprite : unsigned(R_Y_Sprite'range);
variable V_Y_Request : unsigned(R_Y_Request'range);
variable V_SpriteYBottom : unsigned(R_Y_Sprite'range);
variable V_OffsetLine : integer;
variable V_Offset : unsigned(C_Offset'range);
begin
V_Y_Sprite := unsigned(R_Y_Sprite);
V_Y_Request := unsigned(R_Y_Request);
V_SpriteYBottom := V_Y_Sprite + to_unsigned(G_Sprite_Height - 1, R_Y_Sprite'length);
if V_Y_Request >= V_Y_Sprite and
V_Y_Request <= V_SpriteYBottom then
C_IsVisible <= '1';
else
C_IsVisible <= '0';
end if;
V_OffsetLine := to_integer(V_Y_Request - V_Y_Sprite);
-- pragma translate_off
if V_OffsetLine < 0 or V_OffsetLine >= K_SPRITE_ROW_OFFSETS'length then
V_OffsetLine := 0;
end if;
-- pragma translate_on
V_Offset := to_unsigned(K_SPRITE_ROW_OFFSETS(V_OffsetLine), C_Offset'length);
C_Offset <= std_logic_vector(V_Offset);
end process;
--@ Output register for the visibility of the sprite
I_IsVisible_OutputRegister : entity work.PipelineRegister
generic map(
G_PipelineStages => G_PipelineStages,
G_Width => 1,
G_RegisterBalancing => "backward"
)
port map(
I_CLK => I_CLK,
I_Enable => S_CalculatingPipeline_Enable,
I_Data(0) => C_IsVisible,
O_Data(0) => O_VSpritePipeline_IsVisible
);
--@ Output register for the offset of the sprite
I_Offset_OutputRegister : entity work.PipelineRegister
generic map(
G_PipelineStages => G_PipelineStages,
G_Width => G_Offset_Width,
G_RegisterBalancing => "backward"
)
port map(
I_CLK => I_CLK,
I_Enable => S_CalculatingPipeline_Enable,
I_Data => C_Offset,
O_Data => O_VSpritePipeline_Offset
);
end architecture;
|
Wie man erkennt, wird das Aufteilen der Berechnung aktuell vollständig der Synthese überlassen. Nun geht es darum, eine performante Aufteilung der Berechnung zu finden.
In dem Berechnungsprozess finden wir zwei relevante Berechnungen und einen Vergleich:
Berechnung der unteren Sprite-Grenze
1
| V_SpriteYBottom := V_SpriteY + to_unsigned(G_Sprite_Height - 1, R_Y_Sprite'length);
|
Vergleich der Y-Position mit der Sprite-Grenze (Sichtbarkeitsprüfung)
1
2
| if V_YToCheck >= V_SpriteY and
V_YToCheck <= V_SpriteYBottom then
|
Berechnung des Address-Offsets
1
| V_OffsetLine := to_integer(V_YToCheck - V_SpriteY);
|
Da die Operationen Nr. 2 und 3 auf das Ergebnis der ersten Berechnung angewiesen sind, werden wir diese als Erstes in einer getrennten Stufe durchführen.
Als Vorarbeit dazu arbeiten wir unsere Register zuerst einmal um. Zum einen passen wir die Benennung an und zum anderen definieren wir die Anzahl der Stufen nicht mehr dynamisch, sondern statisch.
Hierzu passen wir den Pipeline-Controller an:
1
2
3
4
5
6
7
8
9
10
11
12
13
| INST_VSpritePipeline_Ctrl : entity work.PipelineController
generic map(
G_PipelineStages => 3 -- TODO
)
port map(
I_CLK => I_CLK,
I_CE => I_CE,
O_Enable => O_VSpritePipelineCtrl_Enable,
I_Valid => I_VSpritePipeline_OP_Valid,
O_Ready => O_VSpritePipeline_OP_Ready,
O_Valid => O_VSpritePipeline_Valid,
I_Ready => I_VSpritePipeline_Ready
);
|
Und die beiden Eingangsregister, welche auf eine Stufe verkleinert wurden:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| INST0_VSpritePipeline_Y_Sprite : entity work.PipelineRegister
generic map(
G_Width => G_Y_Width
)
port map(
I_CLK => I_CLK,
I_Enable => O_VSpritePipelineCtrl_Enable,
I_Data => I_VSpritePipeline_OP_Y_Sprite,
O_Data => R0_Y_Sprite
);
INST0_VSpritePipeline_Y_Request : entity work.PipelineRegister
generic map(
G_Width => G_Y_Width
)
port map(
I_CLK => I_CLK,
I_Enable => O_VSpritePipelineCtrl_Enable,
I_Data => I_VSpritePipeline_OP_Y_Request,
O_Data => R0_Y_Request
);
|
Wir führen ein neues kombinatorisches und ein registriertes Signal C_Y_Bottom_Sprite bzw. R_Y_Bottom_Sprite ein, auf welches die Berechnung Nr. 1 geleitet wird. Die Berechnung wird gleichzeitig als concurrent signal assignment durchgeführt. Dies hat den Vorteil, dass wir die Berechnung nicht in einen Prozess packen müssen.
1
2
3
4
| --@ Calculate the bottom Y position of the sprite
C_Y_Bottom_Sprite <= std_logic_vector(
unsigned(R_Y_Sprite) + to_unsigned(G_Sprite_Height - 1, G_Y_Width)
);
|
Weiter benötigen wir ein neues Register für diese Berechnung:
1
2
3
4
5
6
7
8
9
10
| INST_VSpritePipeline_Y_Bottom_Sprite : entity work.PipelineRegister
generic map(
G_Width => G_Y_Width
)
port map(
I_CLK => I_CLK,
I_Enable => O_VSpritePipelineCtrl_Enable,
I_Data => C_Y_Bottom_Sprite,
O_Data => R_Y_Bottom_Sprite
)
|
Weiterhin brauchen müssen wir die Grundlagen unserer Berechnung ebenfalls eine Stufe weiter mitnehmen. Hierfür erstellen wir uns weitere Signale und erneut zwei Register:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| INST1_VSpritePipeline_Y_Sprite : entity work.PipelineRegister
generic map(
G_Width => G_Y_Width
)
port map(
I_CLK => I_CLK,
I_Enable => O_VSpritePipelineCtrl_Enable,
I_Data => R0_Y_Sprite,
O_Data => R1_Y_Sprite
);
INST1_VSpritePipeline_Y_Request : entity work.PipelineRegister
generic map(
G_Width => G_Y_Width
)
port map(
I_CLK => I_CLK,
I_Enable => O_VSpritePipelineCtrl_Enable,
I_Data => R0_Y_Request,
O_Data => R1_Y_Request
);
|
In der nächsten Stufe haben wir nun die registrierten Signale R1_Y_Sprite, R1_Y_Request und R_Y_Bottom_Sprite zur Verfügung. Die Operationen 2 und 3 aus unserer Liste können parallel durchgeführt werden. Hierzu verwenden wir die schon vorhandenen Signale C_IsVisible und C_Offset:
1
2
3
4
5
6
7
8
9
10
11
12
| --@ Calculate the visibility of the sprite
C_IsVisible <= '1' when (
(unsigned(R1_Y_Request) >= unsigned(R1_Y_Sprite)) and
(unsigned(R1_Y_Request) <= unsigned(R_Y_Bottom_Sprite))
) else '0';
--@ Calculate the offset address of the sprite
C_Offset <= std_logic_vector(
to_unsigned(
K_SPRITE_ROW_OFFSETS(to_integer(unsigned(R1_Y_Request) - unsigned(R1_Y_Sprite))),
C_Offset'length)
);
|
Diese beiden Werte sind nun unsere Ergebnisse der Pipeline und müssen letztmalig im Ausgangsregister gespeichert werden. Hierzu verwenden wir die schon vorhandenen Register und passen sie nur leicht an:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| INST_IsVisible_OutputRegister : entity work.PipelineRegister
generic map(
G_Width => 1
)
port map(
I_CLK => I_CLK,
I_Enable => O_VSpritePipelineCtrl_Enable,
I_Data(0) => C_IsVisible,
O_Data(0) => O_VSpritePipeline_IsVisible
);
INST_Offset_OutputRegister : entity work.PipelineRegister
generic map(
G_Width => G_Offset_Width
)
port map(
I_CLK => I_CLK,
I_Enable => O_VSpritePipelineCtrl_Enable,
I_Data => C_Offset,
O_Data => O_VSpritePipeline_Offset
);
|
Zum Schluss müssten wir nun noch G_PipelineStages auf 3 setzen. Vorausschauend habe ich diesen Wert oben aber schon gesetzt, sodass wir hier nichts mehr anpassen müssen und das ``– TODO` entfernen können.
Der Code sieht nun wie folgt aus:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
| library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
use work.SpriteRom.all;
entity VerticalSpritePipeline is
generic (
--@ Width of the Y position (Line) register
G_Y_Width : integer := 10;
--@ The height of the sprite in pixels
G_Sprite_Height : integer := 16;
--@ Width of the sprite offset (Line address) register
G_Offset_Width : integer := 8;
--@ The pipeline stages for the calculating pipeline (multiply by 2 for the the latency of the pipeline)
G_PipelineStages : integer := 2
);
port (
--@ Clock signal; (**Rising edge** triggered)
I_CLK : in std_logic := '0';
--@ Clock enable signal (**Active high**)
I_CE : in std_logic := '1';
--@ @virtualbus VSpritePipeline-OP @dir In Vertical sprite pipeline operation interfacee
--@ AXI like ready; (**Synchronous**, **Active high**)
O_VSpritePipeline_OP_Ready : out std_logic := '0';
--@ AXI like valid; (**Synchronous**, **Active high**)
I_VSpritePipeline_OP_Valid : in std_logic := '0';
--@ The line to check if the sprite is in the line visible.
I_VSpritePipeline_OP_Y_Request : in std_logic_vector(G_Y_Width - 1 downto 0) := (others => '0');
--@ The sprite Y position to check if the sprite is in the line visible.
I_VSpritePipeline_OP_Y_Sprite : in std_logic_vector(G_Y_Width - 1 downto 0) := (others => '0');
--@ @end
--@ @virtualbus VSpritePipeline-Result @dir Out Vertical sprite pipeline result interface
--@ AXI like ready; (**Synchronous**, **Active high**)
I_VSpritePipeline_Ready : in std_logic := '0';
--@ AXI like valid; (**Synchronous**, **Active high**)
O_VSpritePipeline_Valid : out std_logic := '0';
--@ Indicates if the sprite is visible in the line.
O_VSpritePipeline_IsVisible : out std_logic := '0';
--@ The calculated offset address of the sprite.
O_VSpritePipeline_Offset : out std_logic_vector(G_Offset_Width - 1 downto 0) := (others => '0')
--@ @end
);
end entity VerticalSpritePipeline;
architecture Rtl of VerticalSpritePipeline is
--@ Line to check if the sprite is in the line visible
signal R0_Y_Request : std_logic_vector(G_Y_Width - 1 downto 0) := (others => '0');
signal R1_Y_Request : std_logic_vector(G_Y_Width - 1 downto 0) := (others => '0');
--@ The sprite Y position to check if the sprite is in the line visible
signal R0_Y_Sprite : std_logic_vector(G_Y_Width - 1 downto 0) := (others => '0');
signal R1_Y_Sprite : std_logic_vector(G_Y_Width - 1 downto 0) := (others => '0');
--@ The bottom Y position of the sprite
signal C_Y_Bottom_Sprite : std_logic_vector(G_Y_Width - 1 downto 0) := (others => '0');
signal R_Y_Bottom_Sprite : std_logic_vector(G_Y_Width - 1 downto 0) := (others => '0');
--@ Calculated visibility signal
signal C_IsVisible : std_logic := '0';
--@ The calculated offset address of the sprite
signal C_Offset : std_logic_vector(G_Offset_Width - 1 downto 0) := (others => '0');
--@ Pipeline enable signal
signal O_VSpritePipeline_Ctrl_Enable : std_logic := '0';
begin
INST_VSpritePipeline_Ctrl : entity work.PipelineController
generic map(
G_PipelineStages => 3
)
port map(
I_CLK => I_CLK,
I_CE => I_CE,
O_Enable => O_VSpritePipeline_Ctrl_Enable,
I_Valid => I_VSpritePipeline_OP_Valid,
O_Ready => O_VSpritePipeline_OP_Ready,
O_Valid => O_VSpritePipeline_Valid,
I_Ready => I_VSpritePipeline_Ready
);
INST0_VSpritePipeline_Y_Sprite : entity work.PipelineRegister
generic map(
G_Width => G_Y_Width
)
port map(
I_CLK => I_CLK,
I_Enable => O_VSpritePipeline_Ctrl_Enable,
I_Data => I_VSpritePipeline_OP_Y_Sprite,
O_Data => R0_Y_Sprite
);
INST0_VSpritePipeline_Y_Request : entity work.PipelineRegister
generic map(
G_Width => G_Y_Width
)
port map(
I_CLK => I_CLK,
I_Enable => O_VSpritePipeline_Ctrl_Enable,
I_Data => I_VSpritePipeline_OP_Y_Request,
O_Data => R0_Y_Request
);
--@ Calculate the bottom Y position of the sprite
C_Y_Bottom_Sprite <= std_logic_vector(
unsigned(R0_Y_Sprite) + to_unsigned(G_Sprite_Height - 1, G_Y_Width)
);
INST_VSpritePipeline_Y_Bottom_Sprite : entity work.PipelineRegister
generic map(
G_Width => G_Y_Width
)
port map(
I_CLK => I_CLK,
I_Enable => O_VSpritePipeline_Ctrl_Enable,
I_Data => C_Y_Bottom_Sprite,
O_Data => R_Y_Bottom_Sprite
);
INST1_VSpritePipeline_Y_Sprite : entity work.PipelineRegister
generic map(
G_Width => G_Y_Width
)
port map(
I_CLK => I_CLK,
I_Enable => O_VSpritePipeline_Ctrl_Enable,
I_Data => R0_Y_Sprite,
O_Data => R1_Y_Sprite
);
INST1_VSpritePipeline_Y_Request : entity work.PipelineRegister
generic map(
G_Width => G_Y_Width
)
port map(
I_CLK => I_CLK,
I_Enable => O_VSpritePipeline_Ctrl_Enable,
I_Data => R0_Y_Request,
O_Data => R1_Y_Request
);
--@ Calculate the visibility of the sprite
C_IsVisible <= '1' when (
(unsigned(R1_Y_Request) >= unsigned(R1_Y_Sprite)) and
(unsigned(R1_Y_Request) <= unsigned(R_Y_Bottom_Sprite))
) else '0';
--@ Calculate the offset address of the sprite
C_Offset <= std_logic_vector(
to_unsigned(
K_SPRITE_ROW_OFFSETS(to_integer(unsigned(R1_Y_Request) - unsigned(R1_Y_Sprite))),
C_Offset'length)
);
INST_IsVisible_OutputRegister : entity work.PipelineRegister
generic map(
G_Width => 1
)
port map(
I_CLK => I_CLK,
I_Enable => O_VSpritePipeline_Ctrl_Enable,
I_Data(0) => C_IsVisible,
O_Data(0) => O_VSpritePipeline_IsVisible
);
INST_Offset_OutputRegister : entity work.PipelineRegister
generic map(
G_Width => G_Offset_Width
)
port map(
I_CLK => I_CLK,
I_Enable => O_VSpritePipeline_Ctrl_Enable,
I_Data => C_Offset,
O_Data => O_VSpritePipeline_Offset
);
end architecture;
|
Im Vergleich mit der vorherigen Version haben wir nun eine vorgelagerte Berechnung (1. Stufe) und die Berechnung der Sichtbarkeit und des Offsets (2. Stufe) parallelisiert. Dies sollte uns eine kleine Verbesserung hinsichtlich der Performance bringen. In meinem gesamten Design wurde damit die Pipeline vom Flaschenhals zum hinsichtlich des Timings unbeachtlichen Teil.
Innerhalb der Synthese (Prä Place&Route) haben wir eine erhebliche Verbesserung auf dem eingesetzte Spartan 3 erreicht:
Vorher: Minimum period: 5.739ns (Maximum Frequency: 174.246MHz)
Nachher: Minimum period: 4.857ns (Maximum Frequency: 205.888MHz)
Daher haben wir die Pipeline beschleunigt und den Code vereinfacht.