
See the README one level up about where to find the reference manual
for the stm32f4 and schematics for the board.

This is an experiment due to a post on the bare metal forum at Raspberry
Pi.

VFP issues with denormal numbers  posted by mstorsjo

I need to try this against another processor, but have not yet.  What
is going on is when an operation on a denormal or subnormal number
(a number so small that it cannot be represented, think of
0.0000..something with so many zeros there is not enough negative
exponent) the operating system is called to handle the exception.
Upon return the next operation does not complete properly.

In IEEE-754 single precision float (look it up on wikipedia) an exponent
of zeros is a special number it either indicates the number zero or a
denormal number.

    hexstring(m4add(0x3F800000,0x00000000,0x3F801230,0x3F801110));
    hexstring(m4add(0x00012345,0x00000111,0x3F801230,0x3F801110));
    hexstring(m4add(0x00000111,0x00012345,0x3F801230,0x3F801110));

0x3F800000 is a normal number 1.0 or something along those lines
0x00000000 is a normal number with a special exponent it is the value zero.
The other numbers that start with 0x3F8 are also just fine they are a
smidge larger than 0x3F800000.

This line has no denormals
    hexstring(m4add(0x3F800000,0x00000000,0x3F801230,0x3F801110));

.globl m4add
m4add:
    vmov s0,r0
    vmov s1,r1
    vmov s2,r2
    vmov s3,r3
    vadd.f32 s4,s0,s1
    vadd.f32 s5,s2,s3
    vmov r0,s5
    bx lr

the assembly floating point code adds the first two numbers then
adds the second two numbers and returns the result for the second number

The output of this program is

12345678
400011A0
00012345
00000111
12345678

so 0x400011A0 is supposed to be the result of the floating point numbers
0x3F801230 and 0x3F801110 added together, you were expecting 0x3F802340
perhaps, well it doesnt quite work that way, but if you take that 0x11A0
portion of the result and look at it as binary

11A0 =
00010001101000000
then break that into hex values along different boundaries
0 0010 0011 0100 0000
there is the 0x234

these two lines do have denormals, both of the first two numbers
in the first add are denormals.
    hexstring(m4add(0x00012345,0x00000111,0x3F801230,0x3F801110));
    hexstring(m4add(0x00000111,0x00012345,0x3F801230,0x3F801110));

Bear with me for a second on this tangent.  In my blinker07 example I
showed one way to move/create an exception table at address 0x00000000
in arm address space.  My program expects to be loaded at 0x8000 as a
normal raspberry pi linux kernel.img is.  This example shows another
solution.  if you look at the encoding of the branch and branch link
instruction (same encoding, one bit distinguishes link or not) you will
see 0xEAxxxxxx where xxxxxx is a value that indicates the offset.

805c:   eb000094    bl  82b4 <notmain>

0x82b4 - 0x805C = 0x258
0x258 / 4 = 0x96
0x96 - 2 = 0x94

The short answer is I am placing a bunch of branch plus 0x8000 instructions
at the first locations in memory so 0x0000 will branch to 0x8000, 0x0004
will branch to 0x8004 and so on.  Then I have a more proper table at 0x8000

.globl _start
_start:
    b reset
    b undef
    bl other
    bl other
    bl other
    bl other
    bl other
    bl other
    bl other
    bl other
    bl other
    bl other
    bl other
    bl other
    bl other
    bl other

Yes there are too many items there, doesnt hurt.  I used the other
routine (I didnt have the b undef in there all of the lines other than
reset were bl other) to figure out which exception was being hit.
Once I figured out it was the undefined instruction.

I simply had the undef handler return as if the instruction had been
handled

undef:
    movs pc,lr

You begin to see what mstorsjo found.  the instruciton following the
bad operation is messed up.

through more experiments I found more interesting information.  In
an old ARM ARM at least, the undefined is supposed to fill in the link
register with the address of the instruction after.  to verify that

based on the disassembly

000080b4 <m4add>:
    80b4:   ee010a10    vmov    s2, r0

Before calling m4add I put this line in notmain()

PUT32(0x80B4,0xFFFFFFFF);

You indeed get the bad instruction 0xFFFFFFFF.

so letting the program run (remove the 0xFFFFFFFF thing).

    80bc:   ee012a10    vmov    s2, r2
    80c0:   ee013a90    vmov    s3, r3
    80c4:   ee302a20    vadd.f32    s4, s0, s1
    80c8:   ee712a21    vadd.f32    s5, s2, s3
    80cc:   ee120a90    vmov    r0, s5
    80d0:   e12fff1e    bx  lr


00000BAD
EE712A21

which is the second add...strange. rearrange the movs so that the
second add gets the denormals

    vmov s2,r0
    vmov s3,r1
    vmov s0,r2
    vmov s1,r3

00000BAD
EE120A90

    80c4:   ee302a20    vadd.f32    s4, s0, s1
    80c8:   ee712a21    vadd.f32    s5, s2, s3
    80cc:   ee120a90    vmov    r0, s5

which is the floating point instruction after the bad one.  interesting.
Lets add some nops

    nop
    nop
    vadd.f32 s4,s0,s1
    nop
    nop
    vadd.f32 s5,s2,s3
    nop
    nop
    vmov r0,s5
    nop
    nop

00000BAD
EE120A90

no change, interesting, lets put back the vmovs

00000BAD
EE712A21

So what it appears to be doing is giving you the address to the floating
point instruction after the problem.

One more experiment


.globl m4add
m4add:
    vmov s0,r0
    vmov s1,r1
    vmov s2,r2
    vmov s3,r3
    vadd.f32 s4,s0,s1
    b skipper
    vmov r0,s5
    bx lr

skipper:
    vadd.f32 s5,s2,s3
    vmov r0,s5
    bx lr

00000BAD
EE712A21

    80c4:   ee302a20    vadd.f32    s4, s0, s1
    80c8:   ea000001    b   80d4 <skipper>
    80cc:   ee120a90    vmov    r0, s5
    80d0:   e12fff1e    bx  lr

000080d4 <skipper>:
    80d4:   ee712a21    vadd.f32    s5, s2, s3
    80d8:   ee120a90    vmov    r0, s5
    80dc:   e12fff1e    bx  lr

good luck writing a handler for that if you work your way backward
from 0x80d4 the prior float instruction is vmov.  Basically good luck
figuring out the prior float instruction based on the link register given.


So putting it all back together


    hexstring(0x12345678);
    hexstring(m4add(0x3F800000,0x00000000,0x3F801230,0x3F801110));
    hexstring(m4add(0x00012345,0x00000111,0x3F801230,0x3F801110));
    hexstring(m4add(0x00000111,0x00012345,0x3F801230,0x3F801110));
    hexstring(m4add(0x3F801230,0x3F801110,0x00000111,0x00012345));
    hexstring(m4add(0x3F800000,0x00000000,0x3F801230,0x3F801110));
    hexstring(0x22222222);
    hexstring(m4add2(0x3F801230,0x3F801110,0x3F800000,0x00000000));
    hexstring(m4add2(0x00000111,0x00012345,0x3F801230,0x3F801110));
    hexstring(m4add2(0x3F801230,0x3F801110,0x00000111,0x00012345));
    hexstring(0x12345678);

12345678
400011A0
00012345
00000111
3F801230
3F800000
22222222
3F801230
00000111
3F801230
12345678

From the ARM1176jzfs TRM

The VFP11 coprocessor handles exceptions, other than inexact exceptions,
imprecisely with respect to both the state of the ARM11 processor and
the state of the VFP11 coprocessor. It detects an exceptional instruction
after the instruction passes the point for exception handling in the
ARM11 processor. It then enters the exceptional state and signals the
presence of an exception by refusing to accept a subsequent VFP
instruction. The instruction that triggers exception handling bounces
to the ARM11 processor. The bounced instruction is not necessarily the
instruction immediately following the exceptional instruction. Depending
on sequence of instructions that follow, the bounce can occur several
instructions later.


So this means that the instruction after is not unexpected.

The exception bit in the FPEXC register is set, until it is cleared
the fpu appears not to work.  So I modified the undefined handler
to restore the FPEXC to have the SIMD/FPU enabled and the exception
bit cleared.

undef:
    push {r0}
    mov r0,#0x40000000
    fmxr fpexc,r0
    pop {r0}
    subs pc,lr,#4

In order to properly preserve the r0 register I went ahead and setup
the undefined stack pointer up front.

    ;@ (PSR_UND_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS)
    mov r0,#0xDB
    msr cpsr_c,r0
    mov sp,#0x00100000

    ;@ (PSR_SVC_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS)
    mov r0,#0xD3
    msr cpsr_c,r0

So now it works better

    hexstring(0x12345678);
    hexstring(m4add(0x3F800000,0x00000000,0x3F801230,0x3F801110));
    hexstring(m4add(0x00012345,0x00000111,0x3F801230,0x3F802220));
    hexstring(m4add(0x00000111,0x00012345,0x3F801230,0x3F803330));
    hexstring(m4add(0x3F801230,0x3F801110,0x00000111,0x00012345));
    hexstring(m4add(0x3F800000,0x00000000,0x3F801230,0x3F804440));
    hexstring(0x22222222);
    hexstring(m4add2(0x3F801230,0x3F805550,0x3F800000,0x00000000));
    hexstring(m4add2(0x00000111,0x00012345,0x3F801230,0x3F806660));
    hexstring(m4add2(0x3F801230,0x3F807770,0x00000111,0x00012345));
    hexstring(0x12345678);


12345678
400011A0
40001A28
400022B0
400022B0 <-- stale result
40002B38
22222222
400033C0
400033C0 <-- stale result
400044D0
12345678

So the non-denormal operations worked.  The denormal operations dont
actually execute so the floating point register is not changed, to
demonstrate this.

.globl m4vmov
m4vmov:
    vmov s4,r0
    vmov s5,r0
    bx lr

    hexstring(0x12345678);
    hexstring(m4add(0x3F800000,0x00000000,0x3F801230,0x3F801110));
    hexstring(m4add(0x00012345,0x00000111,0x3F801230,0x3F802220));
    m4vmov(0xABCDABCD);
    hexstring(m4add(0x00000111,0x00012345,0x3F801230,0x3F803330));
    m4vmov(0xABCDABCD);
    hexstring(m4add(0x3F801230,0x3F801110,0x00000111,0x00012345));
    m4vmov(0xABCDABCD);
    hexstring(m4add(0x3F800000,0x00000000,0x3F801230,0x3F804440));
    hexstring(0x22222222);
    hexstring(m4add2(0x3F801230,0x3F805550,0x3F800000,0x00000000));
    m4vmov(0xABCDABCD);
    hexstring(m4add2(0x00000111,0x00012345,0x3F801230,0x3F806660));
    m4vmov(0xABCDABCD);
    hexstring(m4add2(0x3F801230,0x3F807770,0x00000111,0x00012345));
    hexstring(0x12345678);


12345678
400011A0
40001A28
400022B0
ABCDABCD <-- stale
40002B38
22222222
400033C0
ABCDABCD <-- stale
400044D0
12345678





