0
\$\begingroup\$

A couple of questions regarding the word size: STM32 series have 32-bit wide words.

  • Considering that, what happens when we try to store something more than 32 bits or even uint64_t for instance? I gave it a try and defined uint64_t a = 4294967299; and what I see in the memory is 00000003. What does that mean? Is it truncating?

  • an address stores 32 bits, isn't it? then why do I see in the resultant bytes (0x00000003) stored at &a different addresses every byte? so from MSB, 0x00 is at 0x20017FE8, the second byte 0x00 is at 0x20017FE9, ... and the last 0x3 is at 0x20017FEA. Shouldn't each set of 32 bits have a distinct address?

Edit:

a side question: SRAM is a part of the MCU (which contains the cortex Mx processor). Is little/big endian related to the processor or the memory? The value that I see in memory seems to be big endian but according to the RM, it's little endian

Tried this code out to test the endianness:

uint64_t a = 0x00008a5d78456301;
uint32_t b = (uint32_t) a;    // 0x78456301

shouldn't b store the first 32 bits (from smallest address) instead of the latter 32?

Any visuals would help a lot.

\$\endgroup\$
  • \$\begingroup\$ 4294967299 = 0x0000_0001_0000_0003. STM32 CPUs are little-endian. A 64bits integer will occupy 8 bytes in memory. For example, from 0x20017FE8 to 0x20017FEF. Bytes in memory will be 03 00 00 00 01 00 00 00 (from ...FE8 to ...FEF) \$\endgroup\$ – TEMLIB Jun 3 at 22:28
  • \$\begingroup\$ Something is not right. uint64_t should be able to store that number. I think you have made a mistake in interpreting or displaying the memory contents. What happens behind the scenes is that two 32 bit registers are ganged together to make a 64 bit register. So in order to see the actual value stored in memory you need to look at both registers. \$\endgroup\$ – mkeith Jun 3 at 22:29
  • 1
    \$\begingroup\$ The C language has various rules about type promotions, and sometimes it may happen that you accidentally truncate a number before it finds its way into a 64 bit variable type. But that simple direct assignment should work. \$\endgroup\$ – mkeith Jun 3 at 22:30
  • 1
    \$\begingroup\$ @mkeith so I do see: 0x20017FE8 00000003 00000001 in the memory so I guess that's what you mean by ganging together to make a 64 bit register? \$\endgroup\$ – Pokloha Jun 3 at 22:33
  • \$\begingroup\$ Shouldn't each set of 32 bits have a distinct address? The addresses are for bytes, so each byte will have a distinct address. \$\endgroup\$ – muyustan Jun 3 at 22:45
5
\$\begingroup\$

Memory addresses are byte addresses. A word is made up of multiple bytes. An STM32 can (with some caveats) read 4 bytes (32 bits) at once, but each of those bytes has a separate address.

When you declare a uint64_t, you get an 8-byte (64-bit) variable. You're only looking at the lower four bytes, so what you see is correct. 4,294,967,299 in hexadecimal is:

0x00000001_00000003

If the variable is stored at address 0x20017000, the bytes will be:

+------------+------------+
|  Address   | Byte value |
+------------+------------+
| 0x20017000 | 0x03       |
| 0x20017001 | 0x00       |
| 0x20017002 | 0x00       |
| 0x20017003 | 0x00       |
| 0x20017004 | 0x01       |
| 0x20017005 | 0x00       |
| 0x20017006 | 0x00       |
| 0x20017007 | 0x00       |
+------------+------------+

This is for a little-endian CPU. If you add 1 to your uint64_t, it should increment the first byte (the one at 0x20017000).

If you try a larger number like 10^17, you should get:

0x01634578_5D8A0000

+------------+------------+
|  Address   | Byte value |
+------------+------------+
| 0x20017000 | 0x00       |
| 0x20017001 | 0x00       |
| 0x20017002 | 0x8a       |
| 0x20017003 | 0x5d       |
| 0x20017004 | 0x78       |
| 0x20017005 | 0x45       |
| 0x20017006 | 0x63       |
| 0x20017007 | 0x01       |
+------------+------------+

If you do a 32-bit read from address 0x20017000, you'll see 0x5D8A0000. This is expected and intentional -- the CPU doesn't know what you're storing in that memory!

Endianness is a property of the CPU. (The SRAM probably doesn't do byte addressing at all -- it reads and writes whole memory words at a time.) Cortex-Ms don't load or store more than 32 bits in once access, so it would be up to the compiler how to order the two halves of the 64-bit value in SRAM. You might have a big-endian CPU (although those are pretty rare), but the compiler could still put the lower 32 bits first in memory. In that case, you might see something like:

0x01634578_5D8A0000

+------------+------------+
|  Address   | Byte value |
+------------+------------+
| 0x20017000 | 0x5d       |
| 0x20017001 | 0x8a       |
| 0x20017002 | 0x00       |
| 0x20017003 | 0x00       |
| 0x20017004 | 0x01       |
| 0x20017005 | 0x63       |
| 0x20017006 | 0x45       |
| 0x20017007 | 0x78       |
+------------+------------+

A fully big-endian 64-bit value (big-endian byte order and word order) would go like this:

0x01634578_5D8A0000

+------------+------------+
|  Address   | Byte value |
+------------+------------+
| 0x20017000 | 0x01       |
| 0x20017001 | 0x63       |
| 0x20017002 | 0x45       |
| 0x20017003 | 0x78       |
| 0x20017004 | 0x5d       |
| 0x20017005 | 0x8a       |
| 0x20017006 | 0x00       |
+------------+------------+

Note that, while the CPU can do byte addressing just fine, JTAG debuggers may handle it poorly. If you're looking at a memory window in an IDE, the IDE is probably doing 32-bit reads and manually splitting up the bytes afterward. Try having the CPU store each byte in a separate 32-bit variable, then look at those variables in the memory window to see what's really going on.

EDIT: Your code is wrong. This:

uint32_t b = (uint32_t)a;

casts a to a uint32_t, which just truncates it to 32 bits. If you want to see what's in memory, you can do:

uint32_t *addr = (uint32_t *)&a;
uint32_t w0 = a[0];  // == *a
uint32_t w1 = a[1];  // == *(a + 1)

This will read 32 bits from the address of a (let's say it's 0x20017000), and 32 bits from the word after that in memory (0x20017004). If you want to read the bytes, the simplest way is:

uint8_t *addr = (uint8_t *)&a; //If this is 0x20017000...
uint32_t b0 = addr[0]; //address 0x20017000
uint32_t b1 = addr[1]; //address 0x20017001
uint32_t b2 = addr[2]; //address 0x20017002
uint32_t b3 = addr[3]; //address 0x20017003
uint32_t b0 = addr[4]; //address 0x20017004
uint32_t b1 = addr[5]; //address 0x20017005
uint32_t b2 = addr[6]; //address 0x20017006
uint32_t b3 = addr[7]; //address 0x20017007

Remember, when you do pointer arithmetic in C, the size of the data type is taken into account. So if a is a uint32_t, then &a + 1 is 32 bits (4 bytes) later in memory. If it's a uint8_t, then &a + 1 is 8 bits (1 byte) later in memory.

As for why your CPU uses byte addresses instead of word addresses... By definition, a byte is the smallest addressable unit of memory. If each memory address held 32 bits, then a byte would be 32 bits on that system. Bytes are 8 bits for historical reasons (meaning software compatibility). To simplify: because that way each byte could hold one character of (English) text.

(There are still some DSPs with 16-bit or 32-bit bytes, but those are special-purpose processors.)

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ thanks for the detailed response. so I tried creating a 64 bit variable with a value 0x00008a5d78456301 and read 32 bits into a different variable, and I see it stores 78456301. and in the memory, I see -> imgur.com/a/8QbMMd0. I am sort of confused cause 78456301 should have a higher address than former 32 bits, as you stated. \$\endgroup\$ – Pokloha Jun 3 at 23:08
  • \$\begingroup\$ That is odd. Can you edit your question to include the code you used and your method for reading the memory contents? \$\endgroup\$ – Adam Haun Jun 3 at 23:15
  • \$\begingroup\$ edited. also, why does each byte have a distinct address as opposed to every 32 bits having one distinct address? to visualize, i'm referring to imgur.com/a/38IP8i7. and endianness is CPU's property so when you want to write to SRAM, the processor takes care of the endianness first and then writes to it? \$\endgroup\$ – Pokloha Jun 3 at 23:29
  • \$\begingroup\$ @Pokloha considering your test results and the information on this answer (see the illustration after "If you try a larger number like 10^17, you should get:"), I think they are consistent. Could not understand why @ AdamHaun called it "odd" \$\endgroup\$ – muyustan Jun 4 at 0:17
  • 1
    \$\begingroup\$ @muyustan Ah, I misread Pokloha's original comment and image since he reversed the byte order from my answer. You're right; that is consistent with a normal little-endian CPU. His code is still wrong, though. :-) \$\endgroup\$ – Adam Haun Jun 4 at 1:31
0
\$\begingroup\$
uint64_t a = 0x1234567811223344;
uint64_t b;
uint64_t fun0 ( void )
{
    b = 0xAABBCCDDEEFF1234;
    return(0x1001200230034004);
}

Disassembly of section .text:

00000000 <fun0>:
   0:   e92d0030    push    {r4, r5}
   4:   e59f302c    ldr r3, [pc, #44]   ; 38 <fun0+0x38>
   8:   e28f5018    add r5, pc, #24
   c:   e8950030    ldm r5, {r4, r5}
  10:   e28f1018    add r1, pc, #24
  14:   e8910003    ldm r1, {r0, r1}
  18:   e8830030    stm r3, {r4, r5}
  1c:   e8bd0030    pop {r4, r5}
  20:   e12fff1e    bx  lr
  24:   e1a00000    nop         ; (mov r0, r0)
  28:   eeff1234    mrc 2, 7, r1, cr15, cr4, {1}
  2c:   aabbccdd    bge feef33a8 <fun0+0xfeef33a8>
  30:   30034004    andcc   r4, r3, r4
  34:   10012002    andne   r2, r1, r2
  38:   00000000    andeq   r0, r0, r0

Disassembly of section .data:

00000000 <a>:
   0:   11223344            ; <UNDEFINED> instruction: 0x11223344
   4:   12345678    eorsne  r5, r4, #120, 12    ; 0x7800000

Technically a number of the cortex-m cores can be compiled little endian and/or big or selected runtime. But I have not see a big endian one yet, and it would be unwise anyway. ARMs native mode is little endian. Tools default to little endian. Not sure why it is even a question.

The sram itself doesnt matter, the sram can be configured however the chip vendor wants so long as they get and put the data on the bus correctly.

uint64_t is part of C so whether it is a PIC or AVR or something cortex-m based you still get 64 bits whatever size the memory is, whatever its endianness. Variables are high level language notions and have nothing to do with the underlying architecture that implements them so long as it functionally implements the high level code correctly.

Smells like you are going to abuse the language if asking this question.

Answer

little endian.

The language supports 64 bit variables so the compiled code will.

Your test is endian-less it doesnt demonstrate endian-ness, but it does show the correct answer.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ possible typing error in your first line of text after the code snippet? \$\endgroup\$ – muyustan Jun 4 at 2:31
0
\$\begingroup\$

A side-note about integer conversions in C, is that they work on a higher abstraction level than the raw binary representation. The rule is (C11 6.3.1.3):

Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.

In this case the conversion should be done "as if subtracting UINT32_MAX+1 from the value until it is in range" - in practice this is truncating the value to 32 bits.

It doesn't matter whether you explicitly force a conversion through a cast: uint32_t b = (uint32_t) a; or just do uint32_t b = a; and get implicit conversion through the rule of simple assignment. Either is endianess-independent and the above conversion rule applies in both cases.

Had you done something wild and crazy like uint32_t b = *(uint32_t*)&a; however, this would be endianess-dependent (and highly questionable code for several other reasons).

| improve this answer | |
\$\endgroup\$
-1
\$\begingroup\$

4294967299(dec) = 1 0000 0003‬ (hex)

Now 64 bits are 8 bytes (8 x 8 bits = 64). Therefore the number 4294967299(dec) is divided into 8 bytes 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00.

So the last byte can't be the 4-th byte.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ so you're basically saying to make 64 bit register, two 32-bit registers are sort of used to store 32 bits in each. Not sure what you meant by the last line... \$\endgroup\$ – Pokloha Jun 3 at 22:35
  • \$\begingroup\$ @Pokloha Yes ,8 bytes are used, You said: "and the last 0x3 is at 0x20017FEA" so, the 4-th (0x03) is not the last. \$\endgroup\$ – Marko Buršič Jun 3 at 22:40
  • \$\begingroup\$ that's what I referred to: imgur.com/a/TvYjhPV. the last byte of the first 32-bit register \$\endgroup\$ – Pokloha Jun 3 at 22:43
  • \$\begingroup\$ Then it should be 0x07 byte,not the 0x03 byte, regardless of the register length. \$\endgroup\$ – Marko Buršič Jun 3 at 22:53
  • \$\begingroup\$ Nothing common with endianess. \$\endgroup\$ – P__J__ Jun 7 at 0:46
-1
\$\begingroup\$

The answer chosen is wrong.

It is a C feature reagrdless the endianess. Then you store the larger data type info the smaller one it gets truncated.

in out case dest32 = src64 & 0xffffffff

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.