Finding the minimum absolute value for a set of integers - Page 1
Forum section: electronics / general

author: hamster_nz
author_rank: Super Contributor
timestamp: August 19, 2020, 09:17:17 am
content: I'm playing around with LDPC codes and I'm looking for an small & fast way (in HDL) of finding the minimum absolute value in a small list of signed integers.Something like this:Code:int8_t min_abs(int8_t a, int8_t b, int8_t c){int out = a;if(abs(b) < abs(out))out = b;if(abs(c) < abs(out))out = c;return out;}Anybody got any ideas?

author: AndyC_772
author_rank: Super Contributor
timestamp: August 19, 2020, 09:39:20 am
content: The great thing about HDL synthesis tools is they'll work this out for you. Your code needs to define the required behaviour - which you've already done - but the job of working out how to implement this using the underlying logic isn't yours to have to do.In other words, all functionally equivalent HDL should synthesize to the same implementation and resources.For the simple case of inputs a, b, c, your function is OK and I wouldn't change it.For a more general case of 'n' integers I'd use a variable and a loop, eg.Code:VARIABLE low : INTEGER;low := input_var(0);FOR i = 1 TO n-1 LOOPIF abs(input_var(i)) < abs(low) THENlow := input_var (i);END LOOP;Note: this purely combinatorial approach won't scale well, and you'll need to pipeline it if it can't keep up with your clock speed. Does your design require zero latency?

author: Berni
author_rank: Super Contributor
timestamp: August 19, 2020, 09:55:20 am
content: It depends on your input data and required performance.If its is for a small number of parallel inputs, with latency being important but space is not then you probably want to do all the comparisons in parallel and generate a signal that muxes out the right result all in combinational logic.If you have a large number of inputs and size is important but speed not, then you implement just a single comparison and run the next data sample trough it on each clock cycle until you get trough all of them. Much like a CPU would do it one at a time.If you have a large number of inputs, need speed, but don't have the space for it then you would need a combination of the two approaches. Do as many comparisons as possible in parallel but then cycle the data samples trough it in chunks over a few clock cycles.Typically the interface that feeds the data in is also a significant factor in choosing the method. If the input bus is very serial in nature then you pick a method that also processes the data in a more serial way to minimize conversion and buffering.

author: hamster_nz
author_rank: Super Contributor
timestamp: August 19, 2020, 09:58:36 am
content: It is just a toy design, to get familiar with the theory, but for this part I want zero latencyAt the moment I think pipelining doesn't make that much sense, as it is an iterative process - I need to finish the complete set of parallel calculations before I can start the next iteration. So unpipelined at 100MHz is equal to a two stage pipelined at 200MHz.

author: brucehoult
author_rank: Super Contributor
timestamp: August 19, 2020, 10:08:30 am
content: Quote from: hamster_nz on August 19, 2020, 09:17:17 amIt's not exactly small but you can obviously do it in O(n) size and O(log n) logic depth using a tree of 2-input min_abs() functions:Code:int8_t min_abs(int first, int n) {if (n == 1)return input_var;else {int left = n/2;uint8_t a = min_abs(first, left);uint8_t b = min_abs(first+left, n-left);return abs(a) < abs(b) ? a : b;}}

author: brucehoult
author_rank: Super Contributor
timestamp: August 19, 2020, 10:19:21 am
content: Quote from: Berni on August 19, 2020, 09:55:20 amI don't even know how you'd do that for min_abs()OK, you can compare everything with everything -- which is O(n^2) size, so it had *better* be not many inputs -- and output just a 1 bit result from each comparison, and then select the row that has all 0 outputs (or all 1s).I don't think this is going to beat my tree approach in speed (logic depth) for any N bigger than 3 or 4, and it's going to be far more area.

author: brucehoult
author_rank: Super Contributor
timestamp: August 19, 2020, 10:37:29 am
content: e.g. for 4 inputsCode:wire0[0..7] = input_var[0..7]wire1[0..7] = input_var[0..7]wire2[0..7] = abs(wire0[0..7]) < abs(wire1[0..7]) ? wire0[0..7] : wire1[0..7]wire3[0..7] = input_var[0..7]wire4[0..7] = input_var[0..7]wire5[0..7] = abs(wire3[0..7]) < abs(wire4[0..7]) ? wire3[0..7] : wire4[0..7]wire6[0..7] = abs(wire2[0..7]) < abs(wire5[0..7]) ? wire2[0..7] : wire5[0..7]output_var[0..7] = wire6[0..7]Or for 32:Code:wire0[0..7] = input_var[0..7]wire1[0..7] = input_var[0..7]wire2[0..7] = abs(wire0[0..7]) < abs(wire1[0..7]) ? wire0[0..7] : wire1[0..7]wire3[0..7] = input_var[0..7]wire4[0..7] = input_var[0..7]wire5[0..7] = abs(wire3[0..7]) < abs(wire4[0..7]) ? wire3[0..7] : wire4[0..7]wire6[0..7] = abs(wire2[0..7]) < abs(wire5[0..7]) ? wire2[0..7] : wire5[0..7]wire7[0..7] = input_var[0..7]wire8[0..7] = input_var[0..7]wire9[0..7] = abs(wire7[0..7]) < abs(wire8[0..7]) ? wire7[0..7] : wire8[0..7]wire10[0..7] = input_var[0..7]wire11[0..7] = input_var[0..7]wire12[0..7] = abs(wire10[0..7]) < abs(wire11[0..7]) ? wire10[0..7] : wire11[0..7]wire13[0..7] = abs(wire9[0..7]) < abs(wire12[0..7]) ? wire9[0..7] : wire12[0..7]wire14[0..7] = abs(wire6[0..7]) < abs(wire13[0..7]) ? wire6[0..7] : wire13[0..7]wire15[0..7] = input_var[0..7]wire16[0..7] = input_var[0..7]wire17[0..7] = abs(wire15[0..7]) < abs(wire16[0..7]) ? wire15[0..7] : wire16[0..7]wire18[0..7] = input_var[0..7]wire19[0..7] = input_var[0..7]wire20[0..7] = abs(wire18[0..7]) < abs(wire19[0..7]) ? wire18[0..7] : wire19[0..7]wire21[0..7] = abs(wire17[0..7]) < abs(wire20[0..7]) ? wire17[0..7] : wire20[0..7]wire22[0..7] = input_var[0..7]wire23[0..7] = input_var[0..7]wire24[0..7] = abs(wire22[0..7]) < abs(wire23[0..7]) ? wire22[0..7] : wire23[0..7]wire25[0..7] = input_var[0..7]wire26[0..7] = input_var[0..7]wire27[0..7] = abs(wire25[0..7]) < abs(wire26[0..7]) ? wire25[0..7] : wire26[0..7]wire28[0..7] = abs(wire24[0..7]) < abs(wire27[0..7]) ? wire24[0..7] : wire27[0..7]wire29[0..7] = abs(wire21[0..7]) < abs(wire28[0..7]) ? wire21[0..7] : wire28[0..7]wire30[0..7] = abs(wire14[0..7]) < abs(wire29[0..7]) ? wire14[0..7] : wire29[0..7]wire31[0..7] = input_var[0..7]wire32[0..7] = input_var[0..7]wire33[0..7] = abs(wire31[0..7]) < abs(wire32[0..7]) ? wire31[0..7] : wire32[0..7]wire34[0..7] = input_var[0..7]wire35[0..7] = input_var[0..7]wire36[0..7] = abs(wire34[0..7]) < abs(wire35[0..7]) ? wire34[0..7] : wire35[0..7]wire37[0..7] = abs(wire33[0..7]) < abs(wire36[0..7]) ? wire33[0..7] : wire36[0..7]wire38[0..7] = input_var[0..7]wire39[0..7] = input_var[0..7]wire40[0..7] = abs(wire38[0..7]) < abs(wire39[0..7]) ? wire38[0..7] : wire39[0..7]wire41[0..7] = input_var[0..7]wire42[0..7] = input_var[0..7]wire43[0..7] = abs(wire41[0..7]) < abs(wire42[0..7]) ? wire41[0..7] : wire42[0..7]wire44[0..7] = abs(wire40[0..7]) < abs(wire43[0..7]) ? wire40[0..7] : wire43[0..7]wire45[0..7] = abs(wire37[0..7]) < abs(wire44[0..7]) ? wire37[0..7] : wire44[0..7]wire46[0..7] = input_var[0..7]wire47[0..7] = input_var[0..7]wire48[0..7] = abs(wire46[0..7]) < abs(wire47[0..7]) ? wire46[0..7] : wire47[0..7]wire49[0..7] = input_var[0..7]wire50[0..7] = input_var[0..7]wire51[0..7] = abs(wire49[0..7]) < abs(wire50[0..7]) ? wire49[0..7] : wire50[0..7]wire52[0..7] = abs(wire48[0..7]) < abs(wire51[0..7]) ? wire48[0..7] : wire51[0..7]wire53[0..7] = input_var[0..7]wire54[0..7] = input_var[0..7]wire55[0..7] = abs(wire53[0..7]) < abs(wire54[0..7]) ? wire53[0..7] : wire54[0..7]wire56[0..7] = input_var[0..7]wire57[0..7] = input_var[0..7]wire58[0..7] = abs(wire56[0..7]) < abs(wire57[0..7]) ? wire56[0..7] : wire57[0..7]wire59[0..7] = abs(wire55[0..7]) < abs(wire58[0..7]) ? wire55[0..7] : wire58[0..7]wire60[0..7] = abs(wire52[0..7]) < abs(wire59[0..7]) ? wire52[0..7] : wire59[0..7]wire61[0..7] = abs(wire45[0..7]) < abs(wire60[0..7]) ? wire45[0..7] : wire60[0..7]wire62[0..7] = abs(wire30[0..7]) < abs(wire61[0..7]) ? wire30[0..7] : wire61[0..7]output_var[0..7] = wire62[0..7]

author: mfro
author_rank: Regular Contributor
timestamp: August 19, 2020, 12:38:41 pm
content: I think that is less dependend onwhatalgorithm you use to calculate the smallest value as onwhenyou do this.These values must get into the FPGA somehow (probably one after the other?).If you compare values while you receive them, you can easily set up a parallel process that checks if what you have stored so far as smallest value is larger and replace this with the current value.When you later need this smallest value, you'll have it in zero time since it's already calculated.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 19, 2020, 04:51:19 pm
content: If you can't pipeline this for any reason (or pipelining would not get you better timings as you hinted), I guess the easiest approach would be to implement this as a simple function.If the number of integers is/can be made a power of two, then you can use a binary tree structure, which will limit the number of comparison levels.A naive approach (as suggested in a previous post) would require N - 1 comparisons and N - 1 comparison levels.A binary tree approach will require N - 1 comparisons and log2 N levels, which should get you (much) better timings unless the number of integers is smaller than 4. (So how small is small? In your example, with 3 values, obviously this won't make a difference.) [This is basically what Bruce suggested I think.]I would personally implement this (on first intention) as a recursive function in VHDL, which easily maps to a tree structure with most synthesis tools.Pseudo-code:Code:* T is an array of N signed integers (N a power of 2)FindMin(T) =* if N = 2 => T if abs(T) < abs(T), T otherwise* if N > 2 => (N0 = FindMin(T[0..N/2-1]), N1 = FindMin(T[N/2..N-1])), N0 if abs(N0) < abs(N1), N1 otherwiseIf N is not a power of 2, you could use a mixed algorithm to deal with this while still limiting the number of levels.I don't know if you can find a fancy algorithm than would require less than N - 1 comparisons?Note: in VHDL, this could be something like this (for N a power of two):Code:constant MinAbs_DataWidth : integer := 16;subtype MinAbs_Data_t is signed(MinAbs_DataWidth - 1 downto 0);type MinAbs_DataArray_t is array (integer range <>) of MinAbs_Data_t;function MinAbs(DataSet : MinAbs_DataArray_t) return MinAbs_Data_t isvariable N0, N1, Result : MinAbs_Data_t;variable iMid : integer;beginif DataSet'length > 2 theniMid := (DataSet'low + DataSet'high) / 2;N0 := MinAbs(DataSet(0 to iMid - 1));N1 := MinAbs(DataSet(iMid to DataSet'high));elseN0 := DataSet(DataSet'low);N1 := DataSet(DataSet'high);end if;if abs(N0) < abs(N1) thenResult := N0;elseResult := N1;end if;return Result;end MinAbs;

author: hamster_nz
author_rank: Super Contributor
timestamp: August 19, 2020, 10:45:51 pm
content: Quote from: mfro on August 19, 2020, 12:38:41 pmThey come in one value at a time, are assembled into large blocks (say 1024 values), and then I need to do a lot of these operations.It's for Belief Propagation in Low Density Parity Check codes.... I need to find the least certain bits feeding into a check bit, and then this value is then used to reinforce or reduce certainty of the other bits involved in the this check bit.Things like figure 4 & 5 ofhttps://www.mitre.org/sites/default/files/publications/13-1730.pdfI've rapidly coming to the conclusion that moving from two's complement to sign+magnitude will make things much simpler. The abs() followed with a comparison on n bits becomes just a magnitude comparison of n-1 bits. Just means I get a +0 and a -0, and the final addition that updates the certainty becomes a tiny bit more complex (because it is no longer a simple saturating two's compliment addition).(This isn't a real-world project, just something to keep me busy...)

author: Berni
author_rank: Super Contributor
timestamp: August 20, 2020, 05:19:13 am
content: Yep then you just do the min search algorithm where the values come in one by one.Then its just a matter of comparing the value with the current minimum and overwriting it if smaller. You also write down the index at the same time so once it gets to 1024 you just spit out this best minimum and index value. Takes very little logic to do as you only need one compare and one register to hold the temporary result.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 20, 2020, 06:39:05 pm
content: Well sure! If you get the "values" one by one (so I'm assuming at different clock pulses), then you naturally get a pipeline! Sounds obvious then to compute the minimum as you get the values rather than within just one clock pulse after you have gotten them all! Just requires one n-bit comparator, one n-bit mux and one n-bit register.

author: hamster_nz
author_rank: Super Contributor
timestamp: August 20, 2020, 08:12:35 pm
content: It's for LDPC. The thousand bits come in scored like a survey response from - 15,'it's a zero' through 0 'no idea' to 15 'it's a one'.Each bit is involved in say four parity calculations (so maybe 1000 different parity sets across the 1000 bit block).The process to check and correct the data involves taking the all of the members of a party set, finding the least certian (that is where the min(abs()) is used), then updating the probabilities to either reinforce or weaken the bit's probability. This is done forall 1000 parity checks in parallel, before the probability update occurs.This is repeated many times to resolve errors and propagate corrections. During this process bits flip and errors are resolved, until either all parity checks are correct (the data is recovered) or the data is lost.So it's not finding the min(abs()) of a long list, it is finding the min(abs()) of a 1000 different short lists in parallel.