Einfache Sättigungsarithmetik in VHDL

Für ein Projekt an der Uni habe ich mit zwei Kommilitonen ein Malprogram („FPGArt“) auf einem FPGA implementiert. Dabei wird eine Maus per PS/2-Schnittstelle angebunden und das Bild schließlich per VGA ausgegeben. Zu dem Projekt selber später vielleicht mehr, hier jetzt nur ein kleines VHDL-Snippet das man vielleicht man gebrauchen kann.

In dem Projekt wird regelmäßig die relative Bewegung der Maus abgefragt und zu dem alten Wert addiert. Natürlich hat das Bild eine begrenze Größe und der Mauszeiger muss in diesen Grenzen von 800×600 gehalten werden. Es wird also eine sog. Sättigungsarithmetik benötigt: Sollte das Ergebnis der Addition größer als 800 (bzw. 600) sein, oder kleiner als 0 wird es auf den Grenzwert gesetzt. Das ganze ist gar nicht so einfach in Hardware zu implementieren (zumindest ist uns nichts intelligentes eingefallen…).

In jedem Fall muss es ein Zwischenergebnis geben das ausreichend groß ist, um Überläufe überhaupt zu erkennen. Die Maus kann pro Intervall Maximal um 256 bewegt werden, entsprechend muss als ein Wert von 800+256 = 1056 gespeichert werden können. Mit 11 bit kommt man auf 2048, das reicht. Hiermit kann man auch Unterläufe erkennen: 0-256 = -256 was dann 1792 entspricht. Dieser Bereich überlappt auch nicht mit den 1056 nach oben. Es ist also immer einwandfrei erkennbar, dass ein Über/Unterlauf stattgefunden hat.

Unsere erste Idee war nun, einfach in jedem Takt zu prüfen, ob das Ergebnis größer als 800 ist (Überlauf) oder größer als 1792 (Unterlauf), und den Wert dann auf 800 bzw. 0 zu setzen. Leider lag dann noch einen Takt das falsche Ergebnis im Register, was nicht so gut war. Der folgende Code realisiert die Überprüfung direkt in der Addition. In Hardware wird ein ziemlicher Wust aus Multiplexern daraus, entsprechend ist das ganze vermutlich nicht besonders schnell. Es werden VHDL-Variablen benutzt um die Lesbarkeit zu erhöhen:


entity ps2_protocol is
generic (
MAX_X : integer := 800;
MAX_Y : integer := 600;
MAX_OVERFLOW : integer := 255
);
port (
);
end ps2_protocol;
architecture rtl of ps2_protocol is
signal x_ff, x_nxt : unsigned(10 downto 0); — 11 bit for overflow margin
signal y_ff, y_nxt : unsigned(10 downto 0);
begin
process(all)
variable x_tmp, y_tmp : unsigned(10 downto 0);
begin
x_nxt <= x_ff;
y_nxt <= y_ff;
— the following code is addition with saturation (in a somewhat hacky way, but without a pipeline step)
x_tmp := unsigned(signed(x_ff) + signed(delta_x); — update x position
x_nxt <= x_tmp;
if x_tmp >= MAX_X then
x_nxt <= to_unsigned(MAX_X, 11) – 1;
end if;
if x_tmp >= MAX_X + MAX_OVERFLOW then
x_nxt <= (others => '0');
end if ;
y_tmp := unsigned(signed(y_ff) – signed(delta_y); — update y position
y_nxt <= y_tmp;
if y_tmp >= MAX_Y then
y_nxt <= to_unsigned(MAX_Y, 11) – 1;
end if;
if y_tmp >= MAX_Y + MAX_OVERFLOW then
y_nxt <= (others => '0');
end if;
end process;
end architecture;

Man kann das bestimmt noch intelligenter lösen, vielleicht mit einem speziellen Addierer, der Sättigungsarithmetik unterstützt, aber so funktioniert es auf jeden Fall 😉

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert