Goldschmidt division implementation12. Dec '13
Introduction
The Goldschmidt division algorithm may seem straightforward, but the implementation might get quite complex. In this case Goldschmidt divisor is composed of:
Bit-vector based 4:2 counter
Carry lookahead adder
Reciprocal approximation circuit
Radix-4 Booth encoder
Booth encoder based multiplier
Registers to hold intermediate N, D and F values
Controller circuit which counts iterations
Glue code
Adders and counters are not discussed in this article since they've been covered in various others.
Booth encoder
The Booth encoder converts multiplier bit triplets into addition and subtraction operations of multiplicand:
library ieee;
use ieee.std_logic_1164.all;
-- Consider multiplication of a and b:
-- d is radix-4 booth encoded bits of digit of the multiplier b
-- a is the bit vector of multiplicand
-- n is the 2's complement of a
-- m is the multiplication of d and a
entity booth4 is
Port ( a : in std_logic_vector (15 downto 0);
d : in std_logic_vector (2 downto 0);
m : out std_logic_vector (16 downto 0));
end booth4;
architecture behavioral of booth4 is
component cla is
generic (N : integer := 16);
port (
a : in std_logic_vector (N-1 downto 0);
b : in std_logic_vector (N-1 downto 0);
ci : in std_logic;
s : out std_logic_vector (N-1 downto 0);
co : out std_logic
);
end component;
-- Signals for two's complement
signal ai : std_logic_vector(15 DOWNTO 0);
signal an : std_logic_vector(15 DOWNTO 0);
begin
-- Two's complement of a,
-- assuming that of course compiler takes care of
-- getting rid of duplicate two's complement circuits
ai <= not a;
cla_stage: cla generic map (N=>16) port map(
a => ai, b => "0000000000000001", ci => '0', s => an
);
-- Dummy lookup table
with d select
m <=a(15) & a when "001", -- Sign expanded addition
a(15) & a when "010", -- Same as previous
a & "0" when "011", -- Add double
an & "0" when "100", -- Subtract double
an(15) & an when "101", -- Sign expanded subtraction
an(15) & an when "110",
"00000000000000000" when others;
end behavioral;
Booth encoder based multiplier
library ieee;
use ieee.std_logic_1164.all;
entity multiplier is
port (
a : in std_logic_vector (15 downto 0);
b : in std_logic_vector (15 downto 0);
m : out std_logic_vector (31 downto 0)
);
end multiplier;
architecture behavioral of multiplier is
-- Booth encoder
component booth4 is
port (
a : in std_logic_vector (15 downto 0);
d : in std_logic_vector (2 downto 0);
m : out std_logic_vector (16 downto 0)
);
end component;
-- Bit-vector based 4:2 counter
component counter42 is
generic (N : integer := 24);
port (
a : in std_logic_vector (N-1 downto 0);
b : in std_logic_vector (N-1 downto 0);
c : in std_logic_vector (N-1 downto 0);
d : in std_logic_vector (N-1 downto 0);
s : out std_logic_vector (N-1 downto 0);
co : out std_logic_vector (N-1 downto 0)
);
end component;
-- Carry lookahead adder
component cla is
generic (N : integer := 32);
port (
a : in std_logic_vector (N-1 downto 0);
b : in std_logic_vector (N-1 downto 0);
ci : in std_logic;
s : out std_logic_vector (N-1 downto 0);
co : out std_logic
);
end component;
-- First Booth digit
signal d0 : std_logic_vector(2 downto 0);
-- Products
signal p0 : std_logic_vector(16 downto 0);
signal p1 : std_logic_vector(16 downto 0);
signal p2 : std_logic_vector(16 downto 0);
signal p3 : std_logic_vector(16 downto 0);
signal p4 : std_logic_vector(16 downto 0);
signal p5 : std_logic_vector(16 downto 0);
signal p6 : std_logic_vector(16 downto 0);
signal p7 : std_logic_vector(16 downto 0);
-- Sign extension
signal se0 : std_logic_vector(23 downto 0);
signal se1 : std_logic_vector(23 downto 0);
signal se2 : std_logic_vector(23 downto 0);
signal se3 : std_logic_vector(23 downto 0);
signal se4 : std_logic_vector(23 downto 0);
signal se5 : std_logic_vector(23 downto 0);
signal se6 : std_logic_vector(23 downto 0);
signal se7 : std_logic_vector(23 downto 0);
-- Intermediary sums and carries
signal s1 : std_logic_vector(23 downto 0);
signal s2 : std_logic_vector(23 downto 0);
signal co1 : std_logic_vector(23 downto 0);
signal co2 : std_logic_vector(23 downto 0);
-- Final sum and carry
signal s3 : std_logic_vector(31 downto 0);
signal co3 : std_logic_vector(31 downto 0);
-- Padded sums abd carries
signal s1p : std_logic_vector(31 downto 0);
signal s2p : std_logic_vector(31 downto 0);
signal co1p : std_logic_vector(31 downto 0);
signal co2p : std_logic_vector(31 downto 0);
signal co3p : std_logic_vector(31 downto 0);
begin
-- Pad first booth digit
d0 <= b( 1 downto 0) & "0";
-- Product stages
p0_stage: booth4 port map(a=>a, d=>d0, m=>p0);
p1_stage: booth4 port map(a=>a, d=>b( 3 downto 1), m=>p1);
p2_stage: booth4 port map(a=>a, d=>b( 5 downto 3), m=>p2);
p3_stage: booth4 port map(a=>a, d=>b( 7 downto 5), m=>p3);
p4_stage: booth4 port map(a=>a, d=>b( 9 downto 7), m=>p4);
p5_stage: booth4 port map(a=>a, d=>b(11 downto 9), m=>p5);
p6_stage: booth4 port map(a=>a, d=>b(13 downto 11), m=>p6);
p7_stage: booth4 port map(a=>a, d=>b(15 downto 13), m=>p7);
-- Reduced sign extension, assuming that compiler takes
-- care of getting rid of constants of course
se0 <= "0000" & (not p0(16)) & p0(16) & p0(16) & p0;
se1 <= "0001" & (not p1(16)) & p1 & "00";
se2 <= "01" & (not p2(16)) & p2 & "0000";
se3 <= not(p3(16)) & p3 & "000000";
se4 <= "000001" & (not p4(16)) & p4;
se5 <= "0001" & (not p5(16)) & p5 & "00";
se6 <= "01" & (not p6(16)) & p6 & "0000";
se7 <= not(p7(16)) & p7 & "000000";
-- Add first 4 products
counter_stage1: counter42 port map(
a => se0, b => se1, c => se2, d => se3,
s => s1, co => co1);
-- Add remaining 4 products
counter_stage2: counter42 port map(
a => se4, b => se5, c => se6, d => se7,
s => s2, co => co2);
-- Pad carries
s1p <= "00000001" & s1;
s2p <= s2 & "00000000";
co1p <= "0000000" & co1 & "0";
co2p <= co2(22 downto 0) & "000000000";
-- Add shifted operands
counter_stage3: counter42 generic map (N=>32) port map(
a => s1p,
b => s2p,
c => co1p,
d => co2p,
s => s3,
co => co3
);
-- Pad carries
co3p <= co3(30 downto 0) & "0";
-- Add sum and carries
cla_stage2: cla port map(
a => s3, b => co3p, ci => '0', s => m
);
end behavioral;
Reciprocal approximator
The reciprocal approximator circuit determines approximate value of the inverse of a number.
library ieee;
use ieee.std_logic_1164.all;
entity reciprocal is
port (
b : in std_logic_vector (15 downto 0);
r : out std_logic_vector (15 downto 0)
);
end reciprocal;
architecture behavioral of reciprocal is
begin
-- Note that this is not the most compact solution,
-- but it leaves room for experimentation.
r <= "1100000000000000" when b = "0000000000000000" else
"0110000000000000" when b(15 downto 1) = "000000000000000" else
"0011000000000000" when b(15 downto 2) = "00000000000000" else
"0001100000000000" when b(15 downto 3) = "0000000000000" else
"0000110000000000" when b(15 downto 4) = "000000000000" else
"0000011000000000" when b(15 downto 5) = "00000000000" else
"0000001100000000" when b(15 downto 6) = "0000000000" else
"0000000110000000" when b(15 downto 7) = "000000000" else
"0000000011000000" when b(15 downto 8) = "00000000" else
"0000000001100000" when b(15 downto 9) = "0000000" else
"0000000000110000" when b(15 downto 10) = "000000" else
"0000000000011000" when b(15 downto 11) = "00000" else
"0000000000001100" when b(15 downto 12) = "0000" else
"0000000000000110" when b(15 downto 13) = "000" else
"0000000000000011" when b(15 downto 14) = "00" else
"0000000000000001" when b(15) = '0' else
"0000000000000000";
end behavioral;
Glue code
Goldschmidt division algorithm glues together all previously learned components:
library ieee;
use ieee.STD_LOGIC_1164.all;
use std.textio.all;
entity goldschmidt_division is
port (
a : in std_logic_vector (15 downto 0); -- Dividend
b : in std_logic_vector (15 downto 0); -- Divisor
c : in std_logic_vector (2 downto 0); -- Iteration count
clk : in std_logic;
rst : in std_logic;
q : out std_logic_vector (15 downto 0) -- Quotinent
);
end goldschmidt_division;
architecture behavioral of goldschmidt_division is
-- Booth multiplier
component multiplier is
port (
a : in std_logic_vector (15 downto 0);
b : in std_logic_vector (15 downto 0);
m : out std_logic_vector (31 downto 0)
);
end component;
-- Carry lookahead adder
component cla is
generic (N : integer := 16);
port (
a : in std_logic_vector (N-1 downto 0);
b : in std_logic_vector (N-1 downto 0);
ci : in std_logic;
s : out std_logic_vector (N-1 downto 0);
co : out std_logic
);
end component;
-- Reciprocal approximator
component reciprocal is
port (
b : in std_logic_vector (15 downto 0);
r : out std_logic_vector (15 downto 0)
);
end component;
-- Counter register
signal counter : std_logic_vector(2 downto 0);
signal counter_decremented : std_logic_vector(2 downto 0);
-- Intermediate output signals
signal nop : std_logic_vector(31 downto 0);
signal dop : std_logic_vector(31 downto 0);
signal dos : std_logic_vector(15 downto 0);
signal don : std_logic_vector(15 downto 0);
-- Current iteration outputs
signal no : std_logic_vector(15 downto 0);
signal do : std_logic_vector(15 downto 0);
signal fo : std_logic_vector(15 downto 0);
-- Previous iteration registers
signal np : std_logic_vector(15 downto 0);
signal dp : std_logic_vector(15 downto 0);
signal fp : std_logic_vector(15 downto 0);
-- Initial reciprocal
signal ir : std_logic_vector(15 downto 0);
begin
-- Dump output process
dump_proc: process
file OUTPUT_FILE : text open write_mode is "dump.txt";
variable output_line: LINE;
begin
for i in 1 to 10 loop
wait until rising_edge(clk);
write(output_line,to_bitvector(no));
writeline(output_file,output_line);
end loop;
end process;
-- Registers for N, D and F
sync_process: process(clk, counter)
begin
if (rst = '1') then
counter <= c;
np <= a;
dp <= b;
fp <= ir;
elsif rising_edge(clk) then
counter <= counter_decremented;
np <= no;
dp <= do;
fp <= fo;
end if;
end process;
-- Have initial reciprocal always ready to go
initial_reciprocal: reciprocal port map(b=>b, r=>ir);
-- Decrement iteration counter
counter_decremented <=
"110" when counter = "111" else
"101" when counter = "110" else
"100" when counter = "101" else
"011" when counter = "100" else
"010" when counter = "011" else
"001" when counter = "010" else
"000";
-- Multiply input F and input N
n_stage: multiplier port map(a=>fp, b=>np, m=>nop);
-- Multiply input F and input D
d_stage: multiplier port map(a=>fp, b=>dp, m=>dop);
-- Round output N
no <= nop(23 downto 8);
-- Round output D
dos <= dop(23 downto 8);
-- Find two's complement of output D
don <= not dos;
-- Subtract D from 2
f_stage: cla port map(a=>"0000001000000000", b=>don, ci=>'1', s=>fo);
do <= dos;
-- Bind outputs
q <= no;
end behavioral;
Conclusion
The main drawbacks of such simplistic design is that the intermediate multiplication results are truncated and there is precision loss for every iteration. In this case the division of 86 by 7 should result in number approximately 12.285714(285714), but after 4th iteration the precision loss starts to affect the result and it deviates from that number:
8.062500000
10.832031250
12.140625000
12.328125000
12.375000000
12.421875000
12.468750000
12.515625000
12.562500000
12.609375000
12.656250000
12.703125000
12.750000000
12.796875000
12.843750000
12.890625000
12.937500000
12.984375000
13.031250000
On Xilinx Spartan 3E XC3S500E the latency of this circuit was 21.872ns which means that we should be able to operate it on base frequency of:
The user of course has to take care of feeding the correct number of iterations to the circuit and delaying the output reading operation by that amount of cycles.