C multiplication syntax

Go To Last Post
12 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi guys,

 

I'm building a soft audio volume control. What is the best C syntax to use UC3A3 hardware in a fast and convenient way?

 

Basically, I'm doing A = A * V/C + D, where:

A = signed 32-bit audio sample where upper 24 bits are sent to DAC chip

V = volume variable, 16 or 32-bit signed or unsigned

C = scaling constant so that max(V/C) = 1

D = random dither applied in lower 8 bits of A prior to truncating

 

 

 

Cheers,

Børge

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It should not make any difference what your target is.

 

// if C is an integer
A = (A * V) / C + D;    // make sure that the intermediate (A * V) fits in int32_t
// if C is f-p
A = A * (V / C) + D;    // this is fine if C is a floating point constant or variable

The execution time will be trivial for either approach.   The second avoids overflow / underflow issues.

Since you will be using native int32_t variables,   overflow is less likely than a 16-bit AVR program.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

borge.strand wrote:
Basically, I'm doing A = A * V/C + D, where:

Can we assume you wrote that with BODMAS and left-to-right operation in mind? That is:

 

A = ((A * V)/C) + D

 

Or was the intention:

 

A = (A * (V/C)) + D

 

If the latter is the V/C some ratio that could be precalculated just once then applied to multiple invocations of * A ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

My advice would be:

1 - use parenthesis, always, even if you are absolutely sure of the operator precedence, still use parenthesis. 

It will make your (or someone else's) life easier when then look at the code again some time later, and will avoid mistakes.

 

2- Make sure that the result from each operation  will fit into it's destination.

 

3 - And, of course you know that 4 / 3 is 1 right? If you want a floating point answer - use (or cast) floating point operands!

 

**EDIT** Removed some compiler-speak that could have been confused with some nasty Windows macros.

SpiderKenny
@spiderelectron
www.spider-e.com

 

Last Edited: Mon. Mar 21, 2016 - 10:32 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I believe I'd like to go the way through a 64-bit signed temp. That way I don't loose precision in V (which can be forgiven) or A (which may not happen)

 

The mul instruction is 32<-32x32 while muls.d is 64<-32x32. I could code this up in asm with muls.d and then an asr on the result for the division by C. Knowing the range of the variables the result then ends up in the lower 32 bits of the 64-bit temp.

 

I'm working on a 64-bit asr now and plan to merge that with a muls.d. I just can't provoke the compiler to produce any muls.d.

 

 

Børge

 

 

S64 mmuulltt(S64 A) {

return A >> 28;

// Compiles into this. I've verified the shifts in Excel

//    lsr     r10, 28
//    or        r10, r10, r11 << 4
//    asr     r11, 28

}

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

borge.strand wrote:
. I could code this up in asm

Why when the C compiler is perfectly able to do this for you?

 

If you want to say to C "I'd like this done with this precision" then just typecast up one of the inputs to your desired width. So if you have:

uint32_t a,b,c;

and want:

c = (a * b) / 100000;

(say) but worry that the (a * b) might spill beyond 32 bits then use something like:

c = ((uint64_t)a * b) / 100000;

and the cast there will take the whole thing up to 64 bits.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Perhaps it's just me not understanding the asm that comes out of the compiler.

int64_t mmuulltt(int32_t A, int32_t V) {
  return ( (int64_t)(A * V) >> 28);
}

compiles into

mul     r11, r12        #  tmp26, A
asr     r10, r11, 28    #  tmp4, tmp26,
asr     r11, 31         # ,
retal   r12

Given the definition of mul in doc32000.pdf as 32<-32x32 I don't see clearly how (int64_t)(A * V) is executed with full precision.

 

 

Børge

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Because you have cast the result and not the individual operand.
Put the parentheses in the right place.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK! I was able to get what I wanted with

int32_t Cmult(int32_t A, int32_t V) {
	int64_t X = (int64_t)( (int64_t)A * (int64_t)V ) >> 28;
	return ( (int32_t)(X >> 0) );
}

Essentially, it multiplies A and V, does a serious right-shift and outputs the lower 32-bit word. This time it generates code with muls.d.

 

The asm generated by the C code and what I optimized by hand converged nicely. I'm forever impressed with the capabilities of the compilers!

 

My coding carreer started with asm, and to date I mentally use C as a collection of asm macros :-)

 

 

Børge

 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

borge.strand wrote:

return ( (int32_t)(X >> 0) );

 

What's the purpose of the >> 0 in the return line?

Shift right-by-zero will be optimised out by the compiler. 

SpiderKenny
@spiderelectron
www.spider-e.com

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Also, c-programmers avoid using 'X' as a variable name - dunno why, but it's kinda smirked on by most c programmers.

Unless of course, X refers to a co-ordinate in a graphics system, then it seems to be acceptable!

 

SpiderKenny
@spiderelectron
www.spider-e.com

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

borge.strand wrote:
My coding carreer started with asm, and to date I mentally use C as a collection of asm macros :-)

I think that's pretty true for most of use die-hard asm programmers!

SpiderKenny wrote:
dunno why, but it's kinda smirked on by most c programmers

because "real" C programmers use 'foo' and 'bar' perhaps? ;-)