Immer wieder braucht man trotz aller Tricks einen Divisionsalgorithmus in VHDL. Leider geht das nicht so einfach wie eine Addition oder eine Multiplikation. Mit irgendwelchen Tools erzeugte Algorithmen sind u.U. nicht so ohne weiteres nicht portierbar, oder man möchte einfach nur mal sehen, wie sowas geht.
Hier habe ich eine Integer Division, die unter dem Namen Radix-2 Division bekannt ist, implementiert. Dieser Algorithmus benötigt einen Rechenschritt pro Bit. Siehe dazu auch hier bei der Uni Ulm.
Eine Division ist an sich nur eine wiederholte Subtraktion. Ähnlich wie in der schriftlichen Division, die wir aus der Schule kennen, beginnt auch die binäre Division bei den höchstwertigen Stellen und gibt pro Rechenschritt eine weitere Quotientenstelle aus. Der Divisionsweg teilt sich in in zwei Abschnitte auf:
1) Scheibe die oberen Bits des Dividenden in den Rest
2) Ziehe den Divisor von dem Wert im Rest ab.
Das MSB dieser Subtraktion wird ein Bit im Quotienten.
Dividend
---------- = Quotient und Rest
Divisor
Gestartet wird die Division, nachdem der Dividend und der Divisor zugewiesen wurden, mit dem Signal start='1'. Es reicht aus, wenn start für einen Taktzyklus aktiv ist. Während der Division ist busy='1', zum Abschluss der Division wird start kontrolliert und ggf. gewartet, bis start wieder auf '0' gegangen ist.
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity Divider is Generic ( b : natural range 4 to 32 := 32 ); -- Breite Port ( start : in STD_LOGIC; divisor : in STD_LOGIC_VECTOR (b-1 downto 0); dividend : in STD_LOGIC_VECTOR (b-1 downto 0); quotient : out STD_LOGIC_VECTOR (b-1 downto 0); remainder : out STD_LOGIC_VECTOR (b-1 downto 0); busy : out STD_LOGIC; clk : in STD_LOGIC); end Divider; architecture Behave_Unsigned of Divider is signal dd : unsigned(b-1 downto 0); -- dividend signal dr : unsigned(b-1 downto 0); -- divisor signal q : unsigned(b-1 downto 0); -- qoutient signal r : unsigned(b-1 downto 0); -- remainder signal bits : integer range b downto 0; type zustaende is (idle, prepare, shift, sub, done); signal z : zustaende; begin process variable diff : unsigned(b-1 downto 0); begin wait until rising_edge(clk); case z is when idle => if (start='1') then z <= prepare; busy <= '1'; end if; dd <= unsigned(dividend); dr <= unsigned(divisor); when prepare => q <= (others=>'0'); r <= (others=>'0'); z <= shift; bits <= b; -- Sonderfall: Division durch Null if (dr=0) then q <= (others=>'1'); r <= (others=>'1'); z <= done; -- Sonderfall: Divisor größer als Dividend elsif (dr>dd) then r <= dd; z <= done; -- Sonderfall: Divisor gleich Dividend elsif (dr=dd) then q <= to_unsigned(1,b); z <= done; end if; when shift => -- erst mal die beiden Operanden -- für die Subtraktion zurechtrücken if ( (r(b-2 downto 0)&dd(b-1)) < dr ) then bits <= bits-1; r <= r(b-2 downto 0)&dd(b-1); dd <= dd(b-2 downto 0)&'0'; else z <= sub; end if; when sub => if (bits>0) then r <= r(b-2 downto 0)&dd(b-1); dd <= dd(b-2 downto 0)&'0'; -- Rest minus Divisor diff := (r(b-2 downto 0)&dd(b-1)) - dr; if (diff(b-1)='0') then -- wenn kein Unterlauf --> Divisor passt noch rein --> MSB=0 --> 1 in Ergebnis einschieben q <= q(b-2 downto 0) & '1'; r <= diff; else -- wenn Unterlauf --> 0 einschieben, mit altem Wert weiterrechnen q <= q(b-2 downto 0) & '0'; end if; bits <= bits-1; else z <= done; end if; when done => busy <= '0'; -- Handshake: wenn nötig warten, bis start='0' if (start='0') then z <= idle; end if; end case; end process; quotient <= std_logic_vector(q); remainder <= std_logic_vector(r); end;
Die 32 Bit Implementation der Division benötigt laut Syntheseergebnis folgende Ressourcen:
Number of Slices 189
Number of Slice Flip Flops 138
Number of 4 input LUTs 358
Hier noch ein Auszug der Testbench mit den Sonderfällen am Anfang und einigen Divisionen danach.
clk <= not clk after 3 ns; tb : PROCESS BEGIN dividend <= std_logic_vector(to_unsigned(127,32)); divisor <= std_logic_vector(to_unsigned(127,32)); wait for 5 ns; start <= '1'; wait for 25 ns; start <= '0'; wait for 50 ns; dividend <= std_logic_vector(to_unsigned(127,32)); divisor <= std_logic_vector(to_unsigned(0,32)); wait for 5 ns; start <= '1'; wait for 25 ns; start <= '0'; wait for 50 ns; dividend <= std_logic_vector(to_unsigned(127,32)); divisor <= std_logic_vector(to_unsigned(255,32)); wait for 5 ns; start <= '1'; wait for 25 ns; start <= '0'; wait for 50 ns; dividend <= std_logic_vector(to_unsigned(1023,32)); divisor <= std_logic_vector(to_unsigned( 128,32)); wait for 5 ns; start <= '1'; wait for 25 ns; start <= '0'; wait for 222 ns; assert (to_integer(unsigned(quotient)) =7) report "Division fehlerhaft" severity failure; assert (to_integer(unsigned(remainder))=127) report "Division fehlerhaft" severity failure; dividend <= std_logic_vector(to_unsigned(1024,32)); divisor <= std_logic_vector(to_unsigned( 128,32)); wait for 5 ns; start <= '1'; wait for 25 ns; start <= '0'; wait for 222 ns; assert (to_integer(unsigned(quotient)) =8) report "Division fehlerhaft" severity failure; assert (to_integer(unsigned(remainder))=0) report "Division fehlerhaft" severity failure;
Das ganze sieht in der Wavedarstellung so aus: