__sfr32 causes instruction destication register to be wrong for the MSB.
Assembly output of main.c compile sdcc -mmcs51 main.c.
000000 E4 [12] 192 clr a
193 000001 F5 A7 [12] 193 mov ((_SFR_DATA_U32 >> 0) & 0xFF),a
194 000003 F5 A6 [12] 194 mov ((_SFR_DATA_U32 >> 8) & 0xFF),a
195 000005 F5 A5 [12] 195 mov ((_SFR_DATA_U32 >> 16) & 0xFF),a
196 000007 F5 FF [12] 196 mov ((_SFR_DATA_U32 >> 24) & 0xFF),a
197 197 ; ../../main.c:6: while(1);
mov ((_SFR_DATA_U32 >> 24) & 0xFF),a generate F5 FF but is should generate F5 A4.
Seen on
I see that the main.c has changed
A4intoA3but still the same issue. It keeps generatingF5 FF.I also see it in reverse
mov a, (SFR_DATA_U32),E5 FF.FFlocation shouldA4and in the
lst-file it get this notationLooks like a problem in the assembler. For comparison, I tried with upstream asxxxx 6.10, but there the upper two bytes of the address of the sfr are 0x00, which looks even more wrong. I'm not that familiar with this stuff, but I guess there is some way to enable support for 32-bit relocations currently missing?
P.S.: I can also reproduce the bug as reported (0xff MSB) on my Debian GNU/Linux testing system using SDCC built from current trunk.
Last edit: Philipp Klaus Krause 2026-10-18
I'm a bit confused by this, though: AFAIK, we only have an 8-bit address space for
__sfr? So shouldn't that be a 32-bit wide sfr at 0xa7 to 0xaa (with SDCC emitting a warning that 0xa3a5a6a7 is out of sfr range)? So this would actually be a compiler issue for even trying to emit those shifts to get the upper bytes of the address? I'm not an expert on mcs51, though.Is this a question to me or other maintainers?
I don't have an answer for that.
I think that is correct.
Our hardware Realtek RTL8372, 10GBit network chip has internal DW8051-core and they interface thru SFRs.
Networkchip settings are written/read via 32-bit value thru sfr a4..a7.
So a other sfr address don't help us.
__sfr32 also generated better code, most noticeable then the values are zero. Then is used
clr a; mov sfr_x ainstead ofmov sfr_x 0x00I hope this answer your questions!
Last edit: RvD 2026-10-18
To anyone. While I am an SDCC dev, I'm not that familiar with mcs51, and how
__sfr32is supposed to work there. @maartenbrock is the mcs51 port maintainer.P.S.: I'm a bit surprised by how
__sfr16and__sfr32work for mcs51, but apparently this approach (the C code from this issue is correct), and SDCC allows to combine arbitrary locations into a 32-bit__sfrfor mcs51. At least this justifies having a separate keyword vs. using types for wide__sfr. Clearly what happens in the assembler then doesn't make sense, so I wonder when this broke, and why it was not found earlier? After all, it must have been working when__sfr32was introduced long ago?Last edit: Philipp Klaus Krause 2026-10-19
Yes,
__sfr16and__sfr32are special. Because sfr space cannot be accessed indirectly and many sfr-pairs are at non-adjacent locations historically and with mixed endianness on top, I introduced these with a special meaning of their__ataddress.The LSB of the address specifies the address of the functional LSB, The higher bytes of the address specify the addresses of the higher functional bytes. Code generation often becomes much more efficient if it can handle the larger integers.
Consider the standard timers 0 and 1: Their sfr's are at:
0x8A TL0 timer 0 low byte
0x8B TL1 timer 1 low byte
0x8C TH0 timer 0 high byte
0x8D TH1 timer 1 high byte
You can define a 16 bit sfr to access each timer as a 16 bit integer.
The 8052 standard timer 2 does have adjacent addresses for TMR2L and TMR2H in little-endian order, just like DPTR is in adjacent DPL and DPH in little-endian order. But beware if you write self-modifying code, because LJMP/LCALL/MOV DPTR have a big-endian value. There are also mcs51 derivatives with 24 bit, 32 bit or even 40 bit sfr's. Or with big endian 16 bit sfr's.
The assembler and linker need to support 32 bit addresses for this to work and they did back then when I introduced this. But asxxxx later started to mask the higher bits and this is now merged in sdas. That it even performs sign extension (0xFF) is quite weird.
What we of course miss deerly is a good regression test that can check the generated address. The obvious 'just take the address of the sfr32 and compare it' is not allowed because sfr's cannot be accessed indirectly.
Other, commercial, compilers also have
sfr16but none support non-adjacency AFAIK.Last edit: Maarten Brock 2026-10-19
I see. Quite different from the requirements of other ports more familiar to me.
Regarding tests: how about using some R/W SFR supported by uCsim to make a
__sfr32that aliases with some__sfr16and__sfr? We could write the__sfr32, the read it as two__sfr16and four__sfr, and check if the values match (and the other way round).the fix is to change
exprmasks(3);toexprmasks(4);insdas/as8051/i51mch.cUnfortunately this completely breaks relocation in the linker.
is incorrectly relocated to (note the extra 00 and the byte stream is shifted by 1:
I am trying to track it down but it is not easy to follow through all the globals in asxxx.
I think I found why
exprmasks(4)breaks the relocations, and a smaller fix.The 0xFF comes from
rngchk()in asexpr.c: it sign-extends every value to the width set byexprmasks(), and the 8051 port callsexprmasks(3), so 0xa4a5a6a7 becomes 0xFFA5A6A7 before>> 24is applied.exprmasks(4)fixes that, buta_bytesalso controls how many address bytes go into the T lines. The sdas 8051 single byte relocation inoutrb()(asout.c, "sdas mcs51 specific") marks the entry asR_BYT3and writesa_bytesbytes, so witha_bytes = 4the linker takes three bytes and the fourth one stays in the stream. That is the extra 00 gorlik saw.The two widths don't have to be the same. The attached patch splits
exprmasks()into the address width and a newvalmasks()for the arithmetic precision, and has the 8051 port useexprmasks(3)plusvalmasks(4). The .rel format and the linker are untouched.Tested on 4.6.0 with the reproducer and a slightly bigger one with relocatable code and xdata (the 4.5.0 sdas8051 with the same change gives the same bytes for the reproducer): all four
__sfr32bytes now get a4..a7 (and a7..a4 for the reversed list), and the .ihx differs from the unpatched build only in those address bytes, so the relocations are unchanged. A small assembler test with negative constants, 24-bit values,.ifconditions and byte selections produces identical .rel files with and without the patch; only>> 24of a value with bit 23 set changes, which is the point. I also built a real 8051 firmware (RTLPlayground for the RTL8373, about 130 KB of code in four banks) with__sfr32in its register write macro: the two binaries differ in exactly the 72 operand bytes that were 0xFF, and the segment sizes are identical. Going from four byte writes to the__sfr32saves 236 bytes of code there, and the firmware built this way runs on the switch: the registers it writes at boot read back the same as with the byte writes, and the MAC table, LACP and STP come up as before.One thing the patch doesn't touch: the linker still masks symbol values to 24 bits, so the .map shows the
__sfr32as 00A5A6A7. That is cosmetic, since the compiler defines the sfr in every module and the assembler resolves it locally.Patch against current trunk attached; the same change applies to 4.5.0 and 4.6.0 with different offsets.