Newbie: ASM on SAMC21 - Page 1
Forum section: electronics / general

author: NivagSwerdna
author_rank: Super Contributor
timestamp: March 01, 2023, 06:12:50 pm
content: So... I have the eight lines of a data bus distributed over various input pins of my two input ports PA and PB... I would like to map these 8 bits into a single value.I have a working slow version in C but I thought I might to try and accelerate the process using some ASM.... so 24hrs ago I did an Cortex M0+ Assembler crash course by watching various YouTube videos and doing some googling...I came up with...Code:asm("MOV %, #0\n\tTST %, #0x00000008\n\tBEQ l0\n\tORR %, %, #0x00000001\n\tl0:\n\tTST %, #0x00000010\n\tBEQ l1\n\tORR %, %, #0x00000002\n\tl1:\n\tTST %, #0x00000002\n\tBEQ l2\n\tORR %, %, #0x00000004\n\tl2:\n\tTST %, #0x00000100\n\tBEQ l3\n\tORR %, %, #0x00000008\n\tl3:\n\tTST %, #0x00000400\n\tBEQ l4\n\tORR %, %, #0x00000010\n\tl4:\n\tTST %, #0x00000020\n\tBEQ l5\n\tORR %, %, #0x00000020\n\tl5:\n\tTST %, #0x00000010\n\tBEQ l6\n\tORR %, %, #0x00000040\n\tl6:\n\tTST %, #0x00000004\n\tBEQ l7\n\tORR %, %, #0x00000080\n\tl7:\n\t":  "=r" (result):  "r" (pa_value),  "r" (pb_value): "cc");Now personally I think that is pretty cool but the compiler isn't so convinced..Code:build/default/production/_ext/1360937237/main.s:2639: Error: unshifted register required -- `tst r3,#0x00000008'build/default/production/_ext/1360937237/main.s:2641: Error: unshifted register required -- `orr r3,r3,#0x00000001'...build/default/production/_ext/1360937237/main.s:2667: Error: unshifted register required -- `tst r3,#0x00000004'build/default/production/_ext/1360937237/main.s:2669: Error: unshifted register required -- `orr r3,r3,#0x00000080'I don't explictly set a register so the compiler picked r3.... OKWhat does unshifted register required mean?Is the use of an immediate not supported on M0+?Thanks in advance

author: NivagSwerdna
author_rank: Super Contributor
timestamp: March 01, 2023, 06:36:03 pm
content: OK...So I think I have made some progress...I cannot use....Code:TST %, #0x0000001because it doesn't like the use of an immediate... a restriction of M0+ perhaps?Instead...Code:LDR r3, =#0x0000001TST %, r3seems to work; I'm not sure how to reference a register that isn't in the inputs or outputs so I arbitrary use r3 and add it to the clobber list.I also thought...Code:MOV r3, #0x00000400would be permissible but it seems you can only use 8-bits and there is no shifting? Is that another Cortex M0+ restriction?This gives me...Code:MOV %, #0LDR r3, =#0x00000008TST %, r3BEQ l0LDR r3, =#0x00000001ORR %, %, r3l0:LDR r3, =#0x00000010TST %, r3BEQ l1LDR r3, =#0x00000002ORR %, %, r3l1:LDR r3, =#0x00000002TST %, r3BEQ l2LDR r3, =#0x00000004ORR %, %, r3l2:LDR r3, =#0x00000100TST %, r3BEQ l3LDR r3, =#0x00000008ORR %, %, r3l3:LDR r3, =#0x00000400TST %, r3BEQ l4LDR r3, =#0x00000010ORR %, %, r3l4:LDR r3, =#0x00000020TST %, r3BEQ l5LDR r3, =#0x00000020ORR %, %, r3l5:LDR r3, =#0x00000010TST %, r3BEQ l6LDR r3, =#0x00000040ORR %, %, r3l6:LDR r3, =#0x00000004TST %, r3BEQ l7LDR r3, =#0x00000080ORR %, %, r3l7:Seems to compile at least; not as short as I hoped.

author: ataradov
author_rank: Super Contributor
timestamp: March 01, 2023, 06:39:40 pm
content: Can you post a better formatted version of this code? It is hard to read one line.Normally you don't need branched for this, all you need is shifts and masks and ors. And Cortex-M0+ instruction set is very limited, and there is no immediate version of the TST instruction. And yes, the only version of MOV takes 8-bit value.You can find full list of instruction in the ARMv6-M Architecture Reference Manual (ARM DDI 0419C), so you don't have to guess what instructions are available.Share your desired pin mapping.

author: eutectique
author_rank: Frequent Contributor
timestamp: March 01, 2023, 07:16:44 pm
content: IFIFYCode:asm("MOV %, #0 \n""TST %, #0x00000008 \n""BEQ l0 \n""ORR %, %, #0x00000001 \n""l0: \n""TST %, #0x00000010 \n""BEQ l1 \n""ORR %, %, #0x00000002 \n""l1: \n""TST %, #0x00000002 \n""BEQ l2 \n""ORR %, %, #0x00000004 \n""l2: \n""TST %, #0x00000100 \n""BEQ l3 \n""ORR %, %, #0x00000008 \n""l3: \n""TST %, #0x00000400 \n""BEQ l4 \n""ORR %, %, #0x00000010 \n""l4: \n""TST %, #0x00000020 \n""BEQ l5 \n""ORR %, %, #0x00000020 \n""l5: \n""TST %, #0x00000010 \n""BEQ l6 \n""ORR %, %, #0x00000040 \n""l6: \n""TST %, #0x00000004 \n""BEQ l7 \n""ORR %, %, #0x00000080 \n""l7: \n":  "=r" (result):  "r" (pa_value),  "r" (pb_value): "cc");In Cortex-M0 instruction set, operands oftstandorrcan only be registers, see therelated chapterfrom the ARM manual. But you have already found it experimentally.

author: Siwastaja
author_rank: Super Contributor
timestamp: March 01, 2023, 07:28:36 pm
content: Could you also post the "slow" C solution, and the optimization level setting you used to compile it? Just in case, we could see some obvious problem there, such as the classic doing multiple RMW operations on a peripheral register (which is qualified volatile in the header files) when you should use a temporary variable. Or compiling with something else than -O2 or -O3.

author: ataradov
author_rank: Super Contributor
timestamp: March 01, 2023, 08:06:56 pm
content: So, the mapping is (A3 B4 A1 A8 B10 A5 A4 A2).The most optimal assembly here would be a series of "LSR A/B, #N" and "LSR R, #1". Where N is selected such that it shifts out the next bit into the carry flag. Then LSR would shift it into the result (R). Then shift by 24 bits to adjust the result into the lower byte.You will need multiple copies of the registers because the bits are not ordered, but they are only slightly unordered, so it would still be efficient. Ideally you would use ROR, but there is no immediate version of it.And in this case for register B it is better to use LSL - all the bits would be sequential, so only one copy of B would be required. For A this would need two copies.

author: NivagSwerdna
author_rank: Super Contributor
timestamp: March 01, 2023, 08:08:50 pm
content: Thanks for the tips on formatting... this is nicer... also rather than loading the shifting bit to OR I now use another register and shift...Code:uint8_t readData() {uint32_t pa_value = PORT_REGS->GROUP.PORT_IN;uint32_t pb_value = PORT_REGS->GROUP.PORT_IN;uint32_t result;asm(" MOV %, #0 \n"" MOV r4, #1 \n"" LDR r3, =#0x00000008 \n"" TST %, r3 \n"" BEQ l0 \n"" ORR %, %, r4 \n""l0: \n"" LSL r4, r4, #1 \n"" LDR r3, =#0x00000010 \n"" TST %, r3 \n"" BEQ l1 \n"" ORR %, %, r4 \n""l1: \n"" LSL r4, r4, #1 \n"" LDR r3, =#0x00000002 \n"" TST %, r3 \n"" BEQ l2 \n"" ORR %, %, r4 \n""l2: \n"" LSL r4, r4, #1 \n"" LDR r3, =#0x00000100 \n"" TST %, r3 \n"" BEQ l3 \n"" ORR %, %, r4 \n""l3: \n"" LSL r4, r4, #1 \n"" LDR r3, =#0x00000400 \n"" TST %, r3 \n"" BEQ l4 \n"" ORR %, %, r4 \n""l4: \n"" LSL r4, r4, #1 \n"" LDR r3, =#0x00000020 \n"" TST %, r3 \n"" BEQ l5 \n"" ORR %, %, r4 \n""l5: \n"" LSL r4, r4, #1 \n"" LDR r3, =#0x00000010 \n"" TST %, r3 \n"" BEQ l6 \n"" ORR %, %, r4 \n""l6: \n"" LSL r4, r4, #1 \n"" LDR r3, =#0x00000004 \n"" TST %, r3 \n"" BEQ l7 \n"" ORR %, %, r4 \n""l7: \n":  "=r" (result):  "r" (pa_value),  "r" (pb_value): "r3","r4","cc");return result & 0xFF;}The old code is as follows...Code:uint8_t readData() {uint8_t temp = 0;// D7 13temp |= PIN_13_Get();temp <<= 1;// D6 10temp |= PIN_10_Get();temp <<= 1;// D5 09temp |= PIN_09_Get();temp <<= 1;// D4 07temp |= PIN_07_Get();temp <<= 1;// D3 08temp |= PIN_08_Get();temp <<= 1;// D2 12temp |= PIN_12_Get();temp <<= 1;// D1 15temp |= PIN_15_Get();temp <<= 1;// D0 14temp |= PIN_14_Get();return temp;}Each Pin get is abstracted by Harmony as something like this... (Group 0 is PA, Group 1 is PB; the shifts differ by pin obv)Code:#define PIN_13_Get() (((PORT_REGS->GROUP.PORT_IN >> 2U)) & 0x01U)I don't normally ramp up the optimisation but at level 2...Code:.section.text.readData,code.align1.p2align 2,,3.globalreadData.syntax unified.code16.thumb_func.fpu softvfp.typereadData, %functionreadData:.LFB87:.loc 1 572 20 is_stmt 1 view -0.cfi_startproc@ args = 0, pretend = 0, frame = 0@ frame_needed = 0, uses_anonymous_args = 0.loc 1 633 5 view .LVU437.LVL38:.loc 1 636 5 view .LVU438.loc 1 636 57 is_stmt 0 view .LVU439movsr2, #130.loc 1 636 74 view .LVU440movsr3, #1.loc 1 572 20 view .LVU441push{r4, lr}.LCFI7:.cfi_def_cfa_offset 8.cfi_offset 4, -8.cfi_offset 14, -4.loc 1 648 57 view .LVU442movsr4, #160.loc 1 636 57 view .LVU443lslsr2, r2, #23ldrr0, [r2, #32].LVL39:.loc 1 637 5 is_stmt 1 view .LVU444.loc 1 640 57 is_stmt 0 view .LVU445ldrr1, [r2, #32].loc 1 636 66 view .LVU446lsrsr0, r0, #2.LVL40:.loc 1 636 74 view .LVU447andsr0, r3.loc 1 640 66 view .LVU448lsrsr1, r1, #4.loc 1 640 74 view .LVU449andsr1, r3.loc 1 637 10 view .LVU450lslsr0, r0, #1.LVL41:.loc 1 640 5 is_stmt 1 view .LVU451.loc 1 640 10 is_stmt 0 view .LVU452orrsr0, r1.LVL42:.loc 1 641 5 is_stmt 1 view .LVU453.loc 1 644 57 is_stmt 0 view .LVU454ldrr1, [r2, #32].loc 1 641 10 view .LVU455lslsr0, r0, #1.LVL43:.loc 1 644 5 is_stmt 1 view .LVU456.loc 1 644 66 is_stmt 0 view .LVU457lsrsr1, r1, #5.loc 1 644 74 view .LVU458andsr1, r3.loc 1 644 10 view .LVU459orrsr0, r1.LVL44:.loc 1 645 5 is_stmt 1 view .LVU460.loc 1 648 57 is_stmt 0 view .LVU461ldrr1, [r2, r4].loc 1 645 10 view .LVU462lslsr0, r0, #1.LVL45:.loc 1 648 5 is_stmt 1 view .LVU463.loc 1 648 66 is_stmt 0 view .LVU464lsrsr1, r1, #10.loc 1 648 75 view .LVU465andsr1, r3.loc 1 648 10 view .LVU466orrsr0, r1.LVL46:.loc 1 649 5 is_stmt 1 view .LVU467.loc 1 652 57 is_stmt 0 view .LVU468ldrr1, [r2, #32].loc 1 649 10 view .LVU469lslsr0, r0, #1.LVL47:.loc 1 652 5 is_stmt 1 view .LVU470.loc 1 652 66 is_stmt 0 view .LVU471lsrsr1, r1, #8.loc 1 652 74 view .LVU472andsr1, r3.loc 1 652 10 view .LVU473orrsr0, r1.LVL48:.loc 1 653 5 is_stmt 1 view .LVU474.loc 1 656 57 is_stmt 0 view .LVU475ldrr1, [r2, #32].loc 1 653 10 view .LVU476lslsr0, r0, #1.LVL49:.loc 1 656 5 is_stmt 1 view .LVU477.loc 1 656 66 is_stmt 0 view .LVU478lsrsr1, r1, #1.loc 1 656 74 view .LVU479andsr1, r3.loc 1 656 10 view .LVU480orrsr0, r1.LVL50:.loc 1 657 5 is_stmt 1 view .LVU481.loc 1 660 57 is_stmt 0 view .LVU482ldrr1, [r2, r4].loc 1 664 57 view .LVU483ldrr2, [r2, #32].loc 1 660 66 view .LVU484lsrsr1, r1, #4.loc 1 660 74 view .LVU485andsr1, r3.loc 1 657 10 view .LVU486lslsr0, r0, #1.LVL51:.loc 1 660 5 is_stmt 1 view .LVU487.loc 1 660 10 is_stmt 0 view .LVU488orrsr0, r1.LVL52:.loc 1 661 5 is_stmt 1 view .LVU489.loc 1 664 66 is_stmt 0 view .LVU490lsrsr2, r2, #3.loc 1 664 74 view .LVU491andsr3, r2.loc 1 661 10 view .LVU492lslsr0, r0, #1.LVL53:.loc 1 664 5 is_stmt 1 view .LVU493.loc 1 666 5 view .LVU494.loc 1 666 12 is_stmt 0 view .LVU495orrsr0, r3.loc 1 667 1 view .LVU496@ sp neededpop{r4, pc}.cfi_endproc.LFE87:.sizereadData, .-readDataSo this is the version (optimisation 0) I was looking at before I started writing my own...Code:.section.text.readData,code.align1.globalreadData.syntax unified.code16.thumb_func.fpu softvfp.typereadData, %functionreadData:.LFB87:.loc 1 572 20.cfi_startproc@ args = 0, pretend = 0, frame = 8@ frame_needed = 1, uses_anonymous_args = 0push{r7, lr}.LCFI34:.cfi_def_cfa_offset 8.cfi_offset 7, -8.cfi_offset 14, -4subsp, sp, #8.LCFI35:.cfi_def_cfa_offset 16addr7, sp, #0.LCFI36:.cfi_def_cfa_register 7.loc 1 633 13addsr3, r7, #7movsr2, #0strbr2, .loc 1 636 47movsr3, #130lslsr3, r3, #23.loc 1 636 57ldrr3, [r3, #32].loc 1 636 66lsrsr3, r3, #2.loc 1 636 74movsr2, #1andsr3, r2.loc 1 636 10uxtbr1, r3addsr3, r7, #7addsr2, r7, #7ldrbr2, orrsr2, r1strbr2, .loc 1 637 10addsr2, r7, #7addsr3, r7, #7ldrbr3, addsr3, r3, r3strbr3, .loc 1 640 47movsr3, #130lslsr3, r3, #23.loc 1 640 57ldrr3, [r3, #32].loc 1 640 66lsrsr3, r3, #4.loc 1 640 74movsr2, #1andsr3, r2.loc 1 640 10uxtbr1, r3addsr3, r7, #7addsr2, r7, #7ldrbr2, orrsr2, r1strbr2, .loc 1 641 10addsr2, r7, #7addsr3, r7, #7ldrbr3, addsr3, r3, r3strbr3, .loc 1 644 47movsr3, #130lslsr3, r3, #23.loc 1 644 57ldrr3, [r3, #32].loc 1 644 66lsrsr3, r3, #5.loc 1 644 74movsr2, #1andsr3, r2.loc 1 644 10uxtbr1, r3addsr3, r7, #7addsr2, r7, #7ldrbr2, orrsr2, r1strbr2, .loc 1 645 10addsr2, r7, #7addsr3, r7, #7ldrbr3, addsr3, r3, r3strbr3, .loc 1 648 47movsr3, #130lslsr3, r3, #23.loc 1 648 57movsr2, #160ldrr3, [r3, r2].loc 1 648 66lsrsr3, r3, #10.loc 1 648 75movsr2, #1andsr3, r2.loc 1 648 10uxtbr1, r3addsr3, r7, #7addsr2, r7, #7ldrbr2, orrsr2, r1strbr2, .loc 1 649 10addsr2, r7, #7addsr3, r7, #7ldrbr3, addsr3, r3, r3strbr3, .loc 1 652 47movsr3, #130lslsr3, r3, #23.loc 1 652 57ldrr3, [r3, #32].loc 1 652 66lsrsr3, r3, #8.loc 1 652 74movsr2, #1andsr3, r2.loc 1 652 10uxtbr1, r3addsr3, r7, #7addsr2, r7, #7ldrbr2, orrsr2, r1strbr2, .loc 1 653 10addsr2, r7, #7addsr3, r7, #7ldrbr3, addsr3, r3, r3strbr3, .loc 1 656 47movsr3, #130lslsr3, r3, #23.loc 1 656 57ldrr3, [r3, #32].loc 1 656 66lsrsr3, r3, #1.loc 1 656 74movsr2, #1andsr3, r2.loc 1 656 10uxtbr1, r3addsr3, r7, #7addsr2, r7, #7ldrbr2, orrsr2, r1strbr2, .loc 1 657 10addsr2, r7, #7addsr3, r7, #7ldrbr3, addsr3, r3, r3strbr3, .loc 1 660 47movsr3, #130lslsr3, r3, #23.loc 1 660 57movsr2, #160ldrr3, [r3, r2].loc 1 660 66lsrsr3, r3, #4.loc 1 660 74movsr2, #1andsr3, r2.loc 1 660 10uxtbr1, r3addsr3, r7, #7addsr2, r7, #7ldrbr2, orrsr2, r1strbr2, .loc 1 661 10addsr2, r7, #7addsr3, r7, #7ldrbr3, addsr3, r3, r3strbr3, .loc 1 664 47movsr3, #130lslsr3, r3, #23.loc 1 664 57ldrr3, [r3, #32].loc 1 664 66lsrsr3, r3, #3.loc 1 664 74movsr2, #1andsr3, r2.loc 1 664 10uxtbr1, r3addsr3, r7, #7addsr2, r7, #7ldrbr2, orrsr2, r1strbr2, .loc 1 666 12addsr3, r7, #7ldrbr3, .loc 1 667 1movsr0, r3movsp, r7addsp, sp, #8@ sp neededpop{r7, pc}.cfi_endproc.LFE87:.sizereadData, .-readDataThe optimised (level 2) version isn't actually that bad!

author: NivagSwerdna
author_rank: Super Contributor
timestamp: March 01, 2023, 08:11:40 pm
content: Mapping as follows:

author: ataradov
author_rank: Super Contributor
timestamp: March 01, 2023, 08:13:22 pm
content: This mapping is confusing, but see my previous post for the most optimal version. It should be doable in ~20 instuctions.

author: NivagSwerdna
author_rank: Super Contributor
timestamp: March 01, 2023, 08:21:02 pm
content: The only reason I haven't used shifts is that I have multiple targets to support and hence I'm trying to automatically generate this rather than hand craft... so for example my next mapping on the same hardware but a different target environment has...FYI I'm only worried about reading the Data bits at the moment; for writing I have a table based approach.The bits do actually look reasonably ordered (the address ones are ordered but backwards)

author: ataradov
author_rank: Super Contributor
timestamp: March 01, 2023, 08:27:05 pm
content: Well, it is up to you what level of optimization/portability you want.Those screenshots are hard to parse. It is easier if you write out what bits from what ports should end up in the final result.And if I was looking for a universal but configurable solution, I'd just write a python script that would generate assembly based on the required bit patern.

author: ataradov
author_rank: Super Contributor
timestamp: March 01, 2023, 08:28:59 pm
content: But also, more universal is a series like this:Code:mov, tmp, A/Bshr tmp, #bit_indexshr R, #1Do this for each bit. 100% universal and takes 24 instructions. Bit_index is such that the bit shifts into the carry flag.

author: NivagSwerdna
author_rank: Super Contributor
timestamp: March 01, 2023, 08:35:15 pm
content: No SHR in Cortex M0+ ?

author: ataradov
author_rank: Super Contributor
timestamp: March 01, 2023, 08:39:38 pm
content: LSR.

author: NivagSwerdna
author_rank: Super Contributor
timestamp: March 01, 2023, 08:57:15 pm
content: I can see how ROR (or some of the other shifts) would allow me to do shift and get a bit into the C flag... but I cannot see any instruction that allows me to rotate/shift the carry flag out?I guess I could ADCS and then rotate? (2 instructions)

author: ataradov
author_rank: Super Contributor
timestamp: March 01, 2023, 09:02:47 pm
content: Yeah, my bad. Rotate instructions shift out the carry flag, but do not shift it in. And RRX is not in ARMv6. I'll need to think what is the actual best way of doing this.Yes, it looks like ADC followed by a shift/rotate is the best way of doing it.Wait, ADC to itself would essentially multiply by 2. So ADC R, R would add R to itself, shifting it and then C is added to the lower bit. You just need to start shifting with bit 7 (msb) of the result.Also, you can use ROR and not have to load the register again, You can load any bit into C without loss of information. The calculation of rotation offsets is a bit tricky, since it is relative.And even better, you can use shifts, but dump the result into a dummy register. You are only interested in the carry bit. This ways you have 2 instructions per bit and straightforward bit index specification.

author: _pat_
author_rank: Contributor
timestamp: March 01, 2023, 09:08:02 pm
content: In order to rotate a bit out of 1 reg and into another you need the RRX instruction, which M0+ doesn't have. Indeed M0+ makes this awkward in number of ways.So far the fastest that I can see with M0+ would beCode:MOV %, #0 ; flatten resultLSLS %, %, #29 ; move A2 to MSB %BPL bit6 ; if it's not set then skipADD %, %, #1 ; if it is set then copy it in.bit6LSL %,%, #1 ; shift result leftLSLS %, %, #27 ; move A4 to MSB %BPL bit5 ; if it's not set then skipADD %, %, #1 ; if it is set then copy it in.bit5LSL %,%, #1 ; shift result leftLSLS %, %, #26 ; move A5 to MSB %BPL bit4 ; if it's not set then skipADD %, %, #1 ; if it is set then copy it in.bit4LSL %,%, #1 ; shift result leftLSLS %, %, #21 ; move B10 to MSB %BPL bit3 ; if it's not set then skipADD %, %, #1 ; if it is set then copy it in.bit3LSL %,%, #1 ; shift result leftLSLS %, %, #23 ; move A8 to MSB %BPL bit2 ; if it's not set then skipADD %, %, #1 ; if it is set then copy it in.bit2LSL %,%, #1 ; shift result leftLSLS %, %, #30 ; move A1 to MSB %BPL bit1 ; if it's not set then skipADD %, %, #1 ; if it is set then copy it in.bit1LSL %,%, #1 ; shift result leftLSLS %, %, #27 ; move B4 to MSB %BPL bit0 ; if it's not set then skipADD %, %, #1 ; if it is set then copy it in.bit0LSL %,%, #1 ; shift result leftLSLS %, %, #28 ; move A3 to MSB %BPL done ; if it's not set then skipADD %, %, #1 ; if it is set then copy it in.done33 instructions. There may be other optimisations. If you have spare flash then you could do something likeCode:MOV %, #0x60 ; A6 and A7 do not existLSR %, %, #1 ; A1 is the lowest bitBIC %, %, % ; flush A6, A7LSLS, %, %, #21 ; move B10 to MSBBPL skipADD %, %, #0x40 ; A7=B10.skip7LSLS %, %, #6 ; move B4 to MSBBPL skip6ADD %, %, #0x20 ; A6=B4.skip6LDR %, LUT ; get LUT base addressLDRB %, [%, %] ; load LUT entryThat's 11 instructions but does need a 256 byte LUT.Now if this was full blown ARM with conditional execution on every instruction..... hey hoCheers,Pat.

author: ataradov
author_rank: Super Contributor
timestamp: March 01, 2023, 09:12:35 pm
content: Quote from: _pat_ on March 01, 2023, 09:08:02 pmBranches are slow. Shift into C and then ADC to collect those carries would be be smaller and faster.And flash lookup is also subject to wait states. Direct code would be faster. Although, the code overall is smaller, so it might end up a bit faster in the end.

author: ataradov
author_rank: Super Contributor
timestamp: March 01, 2023, 09:28:20 pm
content: So, the shortest code would beCode:lsrs tmp,A/B,#bit_indexadc R, Rrepeated 8 times. A/B and bit_index directly select the bit. Bit index is +1 from the actual bit in the register, of course.I think this should work.

author: _pat_
author_rank: Contributor
timestamp: March 01, 2023, 09:59:57 pm
content: ataradov,Branches are indeed slow due to pipeline stalls. But we don't have full blown ARM conditional execution on every instruction in Cortex M0+. It even looks like the IT instruction does not exist.For some reason I had it in my mind that the shifts didn't set the C flag, but a quick check of the M0+ instruction set confirms that they do, so you could use that.Flash may encounter wait states, but I'd be surprised if it was 22 cycles. Now compared to using the C flag, well that could be different.The ADC is not quite complete as shown. It should be :ADC %, %, %Ie add the result to itself (doubling it / logical shift left) and then add the carry.So yes, given that it is possible to shift out to the carry, and it is possible to double and add carry, that could be the most efficient. It needs 16 cycles, vs 7 (using that optimisation) plus flash wait state for the LUT version. If the flash is fast then the LUT wins, if it is slow then the ALU wins.Cheers,Pat.

author: ataradov
author_rank: Super Contributor
timestamp: March 01, 2023, 10:02:31 pm
content: ADC in CM0 only takes Rdn and Rm, assemblers will accept the full form, but it is not needed, two registers are enough.

author: _pat_
author_rank: Contributor
timestamp: March 01, 2023, 10:27:25 pm
content: Alex,I did notice that the full form instructionrequiresthat one of the source registers is also the destination register, which obviously offers an opportunity to abbreviate it, but it was still shown in its full form - I must not have noticed the abbreviated version so apologies for that. I would still tend to err on the side of the full form instruction for the sake of portability. As shown it should work on Thumb, Thumb2 and ARM.In any case, NivagSwerdna should have what is likely to be the most optimal solution available on Cortex M0+ nowWith a more comprehensive instruction set you could use a UBFX followed by some BFIs... but not here.Cheers,Pat.

author: brucehoult
author_rank: Super Contributor
timestamp: March 01, 2023, 10:31:03 pm
content: Quote from: NivagSwerdna on March 01, 2023, 06:12:50 pmI bet properly-written C will match anything you manage in assembly language.Also: doing so much code in inline asm is an abomination. Use a proper function in an .s file.

author: ataradov
author_rank: Super Contributor
timestamp: March 01, 2023, 10:35:07 pm
content: Quote from: brucehoult on March 01, 2023, 10:31:03 pm100% not happening in this case. There is no way compiler can come with a better version than the last proposed in this thread.Quote from: brucehoult on March 01, 2023, 10:31:03 pmI disagree. This is not that much code and with good constraints, it lets compiler pick the registers instead of saving them in the assembly function.Also, inline assemebly can be as clean as standalone and I personally prefer it to standalone even for big code. With raw string literals the code ends up being clean without any of that "\n" after each line. Here is example of 1000+ lines of inline assembly that is as readable as sandalonehttps://github.com/ataradov/open-5012h/blob/master/trigger.cAnd it was much easier to integrate it with C this way.

author: NivagSwerdna
author_rank: Super Contributor
timestamp: March 01, 2023, 11:15:26 pm
content: Perfect. I have to concentrate on my day job but will get back to this at the weekend. The ROR/ADDC approach looks good and is easily automatable.Incidentally I do use LUTs for the writes. I have a pair of tables for Address HI, Address LO and DATA; in fact I have one table for bits to set and another for bits to clear. Although it takes space I think that is pretty efficient.

author: brucehoult
author_rank: Super Contributor
timestamp: March 02, 2023, 02:34:27 am
content: Quote from: ataradov on March 01, 2023, 10:35:07 pmGah, ok, on CM0 you're right.On anything at all with full ARM mode, right back to ARM1 (or Thumb2) you can do...Code:mov one,#1and tmp, one, A/B, lsr #bitorr R, tmp, R, lsl #1.. repeat 8 times... which is easily expressible in C.

author: ataradov
author_rank: Super Contributor
timestamp: March 02, 2023, 02:59:00 am
content: Yes, even for CM4 straightforward code compiles into 16 instructions (UBFX/ORR) -https://godbolt.org/z/o6oxG1oj7Clang and GCC produce pretty different code, but same number of instructions.And even for CM0+, GCC generates 33 instructions and Clang 30, but it uses push / pop. Unless this is something really well optimized otherwise, this difference with assembly code is not likely to matter in practice.

author: Siwastaja
author_rank: Super Contributor
timestamp: March 02, 2023, 06:48:41 am
content: Just fix the C version first to get a better baseline reference. It is colossally slow because you are forcing the compiler to read the peripheral input register from memory for every bit separately, this is a huge amount of overhead. Just read each IN register once, to a variable instead. I bet it gets order of magnitude faster.Also optimization level -O0 is a useless joke, you should never ever use it in actual code. Compiler will generate utter crap with it, it's not designed to be used. If unsure, just use -O2. (My personal preference is -Os by default, and -O3 in selected functions.)

author: NivagSwerdna
author_rank: Super Contributor
timestamp: March 02, 2023, 02:19:58 pm
content: OK... so to be fair to the ASM haters...At -O2 with C written in a hint-ful way...Code:uint8_t readData() {uint32_t pa_value = PORT_REGS->GROUP.PORT_IN;uint32_t pb_value = PORT_REGS->GROUP.PORT_IN;return (((pa_value & 0x00000008) == 0x00000008) ? 0x01 : 0)| (((pb_value & 0x00000010) == 0x00000010) ? 0x02 : 0)| (((pa_value & 0x00000002) == 0x00000002) ? 0x04 : 0)| (((pa_value & 0x00000100) == 0x00000100) ? 0x08 : 0)| (((pb_value & 0x00000400) == 0x00000400) ? 0x10 : 0)| (((pa_value & 0x00000020) == 0x00000020) ? 0x20 : 0)| (((pa_value & 0x00000010) == 0x00000010) ? 0x40 : 0)| (((pa_value & 0x00000004) == 0x00000004) ? 0x80 : 0);}givesCode:.section.text.readData,code.align1.p2align 2,,3.globalreadData.syntax unified.code16.thumb_func.fpu softvfp.typereadData, %functionreadData:.LFB87:.loc 1 572 20 is_stmt 1 view -0.cfi_startproc@ args = 0, pretend = 0, frame = 0@ frame_needed = 0, uses_anonymous_args = 0.loc 1 574 5 view .LVU437.loc 1 574 14 is_stmt 0 view .LVU438movsr2, #130.loc 1 575 14 view .LVU439movsr1, #160.loc 1 574 14 view .LVU440lslsr2, r2, #23ldrr3, [r2, #32].LVL38:.loc 1 575 5 is_stmt 1 view .LVU441.loc 1 575 14 is_stmt 0 view .LVU442ldrr2, [r2, r1].LVL39:.loc 1 577 5 is_stmt 1 view .LVU443.loc 1 572 20 is_stmt 0 view .LVU444push{r4, lr}.LCFI7:.cfi_def_cfa_offset 8.cfi_offset 4, -8.cfi_offset 14, -4.loc 1 577 60 view .LVU445lslsr0, r3, #28.loc 1 578 60 view .LVU446lsrsr4, r2, #3subsr1, r1, #158andsr1, r4.loc 1 577 60 view .LVU447lsrsr0, r0, #31.loc 1 578 10 view .LVU448orrsr0, r1.loc 1 582 60 view .LVU449movsr1, #32andsr1, r3.loc 1 582 10 view .LVU450orrsr0, r1.loc 1 580 60 view .LVU451movsr1, #8lsrsr4, r3, #5andsr1, r4.loc 1 582 10 view .LVU452orrsr0, r1.loc 1 581 60 view .LVU453movsr1, #16lsrsr2, r2, #6.LVL40:.loc 1 581 60 view .LVU454andsr2, r1.loc 1 582 10 view .LVU455orrsr0, r2.loc 1 579 60 view .LVU456movsr2, #4lslsr1, r3, #1andsr2, r1.loc 1 582 10 view .LVU457orrsr0, r2.loc 1 583 60 view .LVU458movsr2, #64lslsr1, r3, #2andsr2, r1.loc 1 583 10 view .LVU459orrsr0, r2.loc 1 584 60 view .LVU460movsr2, #127lslsr3, r3, #5.LVL41:.loc 1 584 60 view .LVU461bicsr3, r2.loc 1 584 10 view .LVU462orrsr0, r3uxtbr0, r0.loc 1 585 1 view .LVU463@ sp neededpop{r4, pc}.cfi_endproc.LFE87:.sizereadData, .-readData

author: cv007
author_rank: Frequent Contributor
timestamp: March 02, 2023, 02:33:48 pm
content: Speaking of bitfields in another thread, you can also make use of them in these cases. It most likely ends up the same size/speed, but it eliminates all the manual bit shifting-comparing to a compiler explorer link earlier in the thread-https://godbolt.org/z/E6565v3KTedit- could also create a macro to get the pins used defined outside the function-https://godbolt.org/z/avMzcTbcEand results can be easily compared to see if both producing the same results-https://godbolt.org/z/P1PqePM9Medit-Quoteand if you plug in your latest code into the online compiler (pc version where printf can show results), you will see that it doesn't match the results of the others. The use of the hex numbers tripped you up-| ((pa_value & 0x0001) == 0x0001) ? 0x04 : 0that was supposed to be PA1, not PA0 (PA1 is 0x0002, PA0 is 0x0001). In addition, you need more parens as the | precedence is messing with results-| (((pa_value & 0x0002) == 0x0002) ? 0x04 : 0)(all the early exits out of the function in the asm are a good clue something is wrong, which the missing parens is causing)Probably shows that the bitfield version is the easiest to get right the first time.Another, where a table of bitmasks are used to eliminate manual shifting, and using += instead of |= seems to help also-https://godbolt.org/z/PW5P7WsYh

author: NivagSwerdna
author_rank: Super Contributor
timestamp: March 02, 2023, 07:21:51 pm
content: You were too quick! I just created the automated version and realised my handcrafted version was wrong. I have corrected the post above... should be OK now?

author: NivagSwerdna
author_rank: Super Contributor
timestamp: March 02, 2023, 07:41:33 pm
content: This is starting to feel likeBut my favourite C version is now this...Code:// 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0// PA - - - - - - - - - - - - - - - - - - - - - - - D3 - - D5 D6 D0 D7 D2 -// PB - - - - - - - - - - - - - - - - - - - - - D4 - - - - - D1 - - - -uint8_t readData() {uint32_t pa_value = PORT_REGS->GROUP.PORT_IN;uint32_t pb_value = PORT_REGS->GROUP.PORT_IN;uint32_t data;data = (((pa_value & 0x00000008) == 0x00000000) ? 0x00 : 0x01 )| (((pb_value & 0x00000010) == 0x00000000) ? 0x00 : 0x02 )| (((pa_value & 0x00000002) == 0x00000000) ? 0x00 : 0x04 )| (((pa_value & 0x00000100) == 0x00000000) ? 0x00 : 0x08 )| (((pb_value & 0x00000400) == 0x00000000) ? 0x00 : 0x10 )| (((pa_value & 0x00000020) == 0x00000000) ? 0x00 : 0x20 )| (((pa_value & 0x00000010) == 0x00000000) ? 0x00 : 0x40 )| (((pa_value & 0x00000004) == 0x00000000) ? 0x00 : 0x80 );return (uint8_t)(data & 0xFF);}The assembly generated at -O2 looks similar to what I was aiming at...Code:.section.text.readData,code.align1.p2align 2,,3.globalreadData.syntax unified.code16.thumb_func.fpu softvfp.typereadData, %functionreadData:.LFB87:.loc 1 572 20 is_stmt 1 view -0.cfi_startproc@ args = 0, pretend = 0, frame = 0@ frame_needed = 0, uses_anonymous_args = 0@ link register save eliminated..loc 1 574 5 view .LVU437.loc 1 574 14 is_stmt 0 view .LVU438movsr2, #130.loc 1 575 14 view .LVU439movsr1, #160.loc 1 574 14 view .LVU440lslsr2, r2, #23ldrr3, [r2, #32].LVL38:.loc 1 575 5 is_stmt 1 view .LVU441.loc 1 575 14 is_stmt 0 view .LVU442ldrr2, [r2, r1].LVL39:.loc 1 577 5 is_stmt 1 view .LVU443.loc 1 579 5 view .LVU444.loc 1 579 60 is_stmt 0 view .LVU445lslsr0, r3, #28lsrsr0, r0, #31.loc 1 580 60 view .LVU446lslsr1, r2, #27bpl.L144movsr1, #2orrsr0, r1.L144:.loc 1 581 60 discriminator 4 view .LVU447lslsr1, r3, #30bpl.L145movsr1, #4orrsr0, r1.L145:.loc 1 582 60 discriminator 4 view .LVU448lslsr1, r3, #23bpl.L146movsr1, #8orrsr0, r1.L146:.loc 1 583 60 discriminator 4 view .LVU449lslsr2, r2, #21bpl.L147.LVL40:.loc 1 583 60 discriminator 4 view .LVU450movsr2, #16orrsr0, r2.L147:.loc 1 584 24 discriminator 4 view .LVU451movsr2, #32.loc 1 584 60 discriminator 4 view .LVU452tstr2, r3beq.L148orrsr0, r2.L148:.loc 1 585 60 discriminator 4 view .LVU453lslsr2, r3, #27bpl.L149movsr2, #64orrsr0, r2.L149:.loc 1 586 60 discriminator 4 view .LVU454lslsr3, r3, #29bpl.L150.LVL41:.loc 1 586 60 discriminator 4 view .LVU455movsr3, #128orrsr0, r3.L150:.LVL42:.loc 1 588 5 is_stmt 1 discriminator 4 view .LVU456.loc 1 588 12 is_stmt 0 discriminator 4 view .LVU457uxtbr0, r0.loc 1 589 1 discriminator 4 view .LVU458@ sp neededbxlr.cfi_endproc.LFE87:.sizereadData, .-readDataThe compiler goes for LSLS and then uses the N flag which is quite interesting.

author: cv007
author_rank: Frequent Contributor
timestamp: March 02, 2023, 08:18:22 pm
content: Use the online compiler instead of trying to decipher the raw asm with all the extra syntax.Test various versions to see what is generated, easily count cycles if wanted-https://godbolt.org/z/9dfYM5sG8If speed is king, use the fastest. If speed not critical and a handful of cycles makes no difference, use the easier to create and read where you can change pins easily without having to manually start fiddling with that function again.

author: NivagSwerdna
author_rank: Super Contributor
timestamp: March 02, 2023, 09:14:50 pm
content: Sorry I hadn't appreciated I could use -Os -mcpu=cortex-m0plusAnyway...Code:uint8_t readData_lotsofshifts(uint32_t a,uint32_t b) {uint32_t data;data = (a & (1<<3)) >> (3-0);data |= (b & (1<<4)) >> (4-1);data |= (a & (1<<1)) << (2-1);data |= (a & (1<<8)) >> (8-3);data |= (b & (1<<10)) >> (10-4);data |= (a & (1<<5)); // (5-5)data |= (a & (1<<4)) << (6-4);data |= (a & (1<<2)) << (7-2);return data;}That was hand calculated so I may have made some errors... but I guess that was a bit obvious and if bits were together in the right order could be merged together; in this configuration they are not but for others it might work better.

author: brucehoult
author_rank: Super Contributor
timestamp: March 02, 2023, 09:25:05 pm
content: Quote from: cv007 on March 02, 2023, 08:18:22 pmIndeed so.https://godbolt.org/z/rPbe1dPKdSix more instructions but no conditional branches probably wins. Not against the assembly language with 16 instructions instead of 32, but may not be noticeable.

author: cv007
author_rank: Frequent Contributor
timestamp: March 02, 2023, 10:38:46 pm
content: QuoteYour version is 31 excluding the 8 for the push/pop (and many versions end up about the same). The bmtable version with bpl branches is 25 excluding the return. The conditional branch is not hurting and you can simply count the instructions (if bpl taken, its 2, if not taken its still 2 with the extra adds instruction).

author: brucehoult
author_rank: Super Contributor
timestamp: March 03, 2023, 12:50:46 am
content: Quote from: cv007 on March 02, 2023, 10:38:46 pmBenefits of a short pipeline on this particular chip, would not always be true.Your table isn't adding any value, by the way. Deleting it and using ...Code:#define ADD_PIN_(pt,pn,bp) if( pt & (1<<pn) ) ret += 1<<bp;... produces identical machine code.

author: cv007
author_rank: Frequent Contributor
timestamp: March 03, 2023, 01:08:10 am
content: QuoteIt was from an earlier version that did not have the macros, and its original intention (in an earlier link) was another alternative to eliminate having to manually put in all the bit shifting code that is prone to mistakes, in addition to the struct bitfield alternative. With the macro help the shifting is reduced to only 2 that are a written once, so the table lost its purpose but was retained because... it was already there I guess.

author: cv007
author_rank: Frequent Contributor
timestamp: March 03, 2023, 04:01:32 pm
content: Not sure why the first mov r0,#0 is taking place, but isn't that clobbering the first arg?I would stick to some version where you can remove the pin definitions from the function, be able to read it later, and be able to move on to producing other code. Something such as-https://godbolt.org/z/fePnfhr7hAre the ~6 (or whatever) clocks worth the effort? If not, move on.

author: NivagSwerdna
author_rank: Super Contributor
timestamp: March 03, 2023, 05:44:58 pm
content: OK... i've moved onhttps://godbolt.org/z/x47YaW5oECode:uint8_t readData(uint32_t pa_value, uint32_t pb_value){uint32_t data;asm(" MOV %, #0 \n"" ASR r5, %, #3 \n"" ADC %, %, % \n"" ASR r5, %, #5 \n"" ADC %, %, % \n"" ASR r5, %, #6 \n"" ADC %, %, % \n"" ASR r5, %, #11 \n"" ADC %, %, % \n"" ASR r5, %, #9 \n"" ADC %, %, % \n"" ASR r5, %, #2 \n"" ADC %, %, % \n"" ASR r5, %, #5 \n"" ADC %, %, % \n"" ASR r5, %, #4 \n"" ADC %, %, % \n":  "=&r" (data):  "r" (pa_value),  "r" (pb_value): "r5", "cc");return (uint8_t)(data & 0xFF);}gives...Code:MOV r3, #0ASR r5, r0, #3ADC r3, r3, r3ASR r5, r0, #5ADC r3, r3, r3ASR r5, r0, #6ADC r3, r3, r3ASR r5, r1, #11ADC r3, r3, r3ASR r5, r0, #9ADC r3, r3, r3ASR r5, r0, #2ADC r3, r3, r3ASR r5, r1, #5ADC r3, r3, r3ASR r5, r0, #4ADC r3, r3, r3Thanks for the pointers. I've learnt a large amount in the last 48 hours! I do appreciate the motivation to stay out of asm, especially in terms of testing and maintainability.PSASRS would be required on platforms that didn't implictly set flags on an ASR

author: _pat_
author_rank: Contributor
timestamp: March 03, 2023, 11:38:25 pm
content: Quote from: NivagSwerdna on March 03, 2023, 05:44:58 pmThe "S" suffix was originally optional on pretty much everything, indicating that this instruction should update the PSR flags. The obvious exceptions were CMN, CMP, TST and TEQ - since they discard the result they'd be pretty useless if they didn't affect the PSR. Instead these had the optional P suffix....If the names Albion, Anna, Arabella, Arthur and Autumn mean anything to you then you'll probably also remember the use of TEQP R15,#mode