NOP instructions being ignored. - Page 1
Forum section: electronics / general

author: ricko_uk
author_rank: Super Contributor
timestamp: March 18, 2021, 07:35:59 pm
content: Hi,I have the code below where for timing reasons I need to insert some NOP.I use MPLABX with XC32 for a PIC32MZ. I check the disassembly window and those NOPs appear. But on the scope, no matter how many NOPs, I put the delay is always the same.Any suggestions?As I said the nops appear in the disassembly but perhaps there is some optimization after (I doubt it). Are there ways to disable optimization for a specific chunk of code?Thank youwhile (1){TPH_CLK_Set();asm("NOP");asm("NOP");asm("NOP");TPH_CLK_Clear();asm("NOP");asm("NOP");asm("NOP");}

author: Karel
author_rank: Super Contributor
timestamp: March 18, 2021, 07:47:10 pm
content: I use:Code:_nop();It's what Microchip uses in their libs for PIC32MX.For example, this works fine:Code:void delay_us(int val){int i;#if CPU_HZ == 96000000for(i=0; i<val; i++){_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();_nop();}#else#error "Invalid value for CPU_HZ!"#endif}

author: Kleinstein
author_rank: Super Contributor
timestamp: March 18, 2021, 07:51:13 pm
content: Normally there should be no more optimization to remove the NOP.However with a high speed µC, the IO pins may be synchronized to a slower clock, and just 1 or 2 CPU clock cycles may not make a difference.

author: ejeffrey
author_rank: Super Contributor
timestamp: March 18, 2021, 07:52:00 pm
content: What delay are you seeing on the scope and what is the MCU clock frequency? How many NOPs have you tried? Is it possible the IO call is just so slow that you don't notice the difference between 1 and 5 NOPs? Have you tried a loop with a NOP in the body and many cycles?

author: ricko_uk
author_rank: Super Contributor
timestamp: March 18, 2021, 08:04:00 pm
content: Thank you all,I tried the _nop(); and it seems to kind of work but not quite...What happens is that the _nop(); after the TPH_CLK_Clear(); seem to be affecting the timing (i.e. the more I add the longer the signal stays low)BUT... no matter how many _nop(); I put after the TPH_CLK_Set(); whether 1 or 20, the pins stays high only for 1 cycle (20 ns).Never seen anything like this... any suggestions as to what might be happening?Thank you

author: DrG
author_rank: Super Contributor
timestamp: March 18, 2021, 08:13:38 pm
content: Quote from: ricko_uk on March 18, 2021, 08:04:00 pmadding my .02,for (i = 0; i < 10000; i += 1){_nop();}If you remove the _nop(); in the loop - IOW just a do nothing loop, XC32 will remove it - sadly I proved thatI wonder though if your issue is not in your clock routines, now that the nop business is sorted

author: ricko_uk
author_rank: Super Contributor
timestamp: March 18, 2021, 08:24:30 pm
content: Thank you DrG,not sure I understand what you mean by the issue not being there.I think that if the issue was somewhere else, the time the signal stays high after setting it to high would be longer, not shorter. Also no matter how many nos I put after setting the pin to high it only stays there for what it looks like a single instruction.Also I tried putting multiple high-nops-low-nops in sequence (with very many nops every time) to avoid the delays of the while(1) instrctions coming into play on the scope. i.e. so I can distinguish clearly when the delays are due to that high-nop-low-nop sequence. But that issue remains... not amount of nops increases the delay after the pin is set to high.Could you please elaborate more on what you think might be causing that?

author: JoeyG
author_rank: Regular Contributor
timestamp: March 18, 2021, 08:34:01 pm
content: PIC32MZ clock up to 200MHz+Could you slow the clock right down in an attempt to see the nop's in action.If this is the case, could you consider a hardware timer for the delay.

author: DrG
author_rank: Super Contributor
timestamp: March 18, 2021, 08:38:35 pm
content: Quote from: ricko_uk on March 18, 2021, 08:24:30 pmI just meant that the issue and syntax of nops was not a problem. I really don't know what else is going on. Also, I think I just wanted to say that when I was playing with some PIC32s, I was trying to visibly (with an LED) see a difference between 40MHz and 48Mhz clock setting and thought I might be going crazy because no matter how long I made a 'dead' loop, it acted like it was not even there....and then I discovered that the compiler believed it was smarter than me and removed the dead loop, but adding a nop fixed all of it. But between two phone calls, by the time I typed all that out - the nop issue was put to rest.

author: ricko_uk
author_rank: Super Contributor
timestamp: March 18, 2021, 08:49:38 pm
content: Thank you both,it gets even more bizarre...whether I put nops (even up to 30 or 50) after setting the pin high OR after setting the pin low OR in after both (i.e. after high and after low), the delay appears ONLY after the low...Does that give any other hints as to what might be happening?

author: Silenos
author_rank: Regular Contributor
timestamp: March 18, 2021, 08:57:55 pm
content: What that PIC32 instruction set manual is whispering you about the NOP?I'm not familiar with PIC architecture but pay attention what e.g. ARM cortex-m4 programming manual states:Quote

author: magic
author_rank: Super Contributor
timestamp: March 18, 2021, 09:29:20 pm
content: Quote from: ricko_uk on March 18, 2021, 08:49:38 pmIt looks like the compiler reorders the NOPs with IO instructions. (Or is that MIPS core capable of doing it by itself - I think not).This can be an effect of optimizations. Disassemble the generated binary again and see if it's the case. Then you will need to come up with some fix; in GCC you would useasm volatile ("NOP")but dunno about Microchip compilers.

author: Syntax Error
author_rank: Frequent Contributor
timestamp: March 18, 2021, 09:53:40 pm
content: My .02 cents. Is the NOP block atomic? If your intention is to spin clock cycles and hold the GPIO high/low for >3xCLK, the CPU has to avoid servicing any interupts or memory pointer activity. A very dirty-dirty trick too waste cpu cycles is increment and decrement a variable: i++ i-- i++ i--

author: ricko_uk
author_rank: Super Contributor
timestamp: March 18, 2021, 11:44:01 pm
content: Thank you all,I found out that there are two NOPs with the PIC32MZ... NOP and SSNOP.According to a document SSNOP always takes a clock cycle while NOP not necessarily. But according to another they are the same and SSNOP was just implemented for backwards compatibility.

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 19, 2021, 01:53:59 am
content: Quote from: ricko_uk on March 18, 2021, 11:44:01 pmI don't know the MIPS core they use in the PIC32MZ well enough to know that. But I wouldn't be surprised if the NOP instruction, as Silenos said for the Cortex-M4, would not necessarily take up one cycle at all depending on context. So just try with this SSNOP instruction.One thing to add though. If you're using the asm keyword, you should add the "volatile" qualifier next to it, otherwise the compiler might optimize it out depending on optimization level.Microchip-provided macros such as "_nop()" probably define them with the volatile qualifier as well.

author: DavidAlfa
author_rank: Super Contributor
timestamp: March 19, 2021, 03:31:52 am
content: When I have to face this, I just declare a volatile variable and do anything with it.Since it's volatile the compiler won't touch the code related to it.Although I've never seen a compiler optimizing asm lines.I always thought that the asm is threated as volatile, althought I could be wrong.

author: magic
author_rank: Super Contributor
timestamp: March 19, 2021, 06:24:46 am
content: Yes, GCC may reorder inline assembly with C code unless the assembly is declared "volatile".I suppose there are reasonable limits to reordering if the assembly touches C variables by means of input/output operands, but that's it. (And if you try to guess where a variable is and modify it from assembly directly, and you probably shouldn't, then breakage is likely IMO).NOP on MIPS is some dummy operation on the zero register, by the way. There is no explicit NOP instruction, although advanced cores may recognize and special-case the common NOP pattern.

author: Karel
author_rank: Super Contributor
timestamp: March 19, 2021, 07:01:51 am
content: For STM32 I use this:Code:#define _NOP() __asm__ __volatile__("nop")and this:Code:void delay_us(unsigned int val){unsigned int i;/** noinline* This function attribute prevents a function from being considered for inlining. If the function does not have side effects,* there are optimizations other than inlining that cause function calls to be optimized away, although the function call is live.* To keep such calls from being optimized away, put:*/asm ("");#if CPU_HZ == 80000000for(i=0; i<val; i++){_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();}#elif CPU_HZ == 32000000for(i=0; i<val; i++){_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();}#elif CPU_HZ == 16384000for(i=0; i<val; i++){_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();}#elif CPU_HZ == 16000000for(i=0; i<val; i++){_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();}#elif CPU_HZ == 8192000for(i=0; i<val; i++){_NOP();_NOP();_NOP();_NOP();}#elif CPU_HZ == 8000000for(i=0; i<val; i++){_NOP();_NOP();_NOP();_NOP();}#else#error "Invalid value for CPU_HZ!"#endif}

author: Siwastaja
author_rank: Super Contributor
timestamp: March 19, 2021, 08:03:25 am
content: Isn't XC32 just gcc?If yes, you have received a lot of wrong answers and a few correct ones.(Rebranded compilers make it more difficult to understand where to look for documentation.)In GCC, unless you define the inline asm as __volatile__, the compiler treats it as any other code, subject to optimization. This makes sense; you may have good reasons to use inline assembly in a way that intervenes with your C code (that's what inline assembly is for), and disabling all optimization would be stupid for many use cases. For example, your C code may load and store variables from/to memory, and a piece of inline assembly does a SIMD operation. In this case, it's wise to let the compiler reorganize the operations like it would do with a fully-C solution.So you disable optimization selectively, as usual.Obviously, being an operation that does nothing, a NOP will be optimized away. So, the last few answers with __asm__ __volatile__("nop") are likely the solution for your problem.

author: magic
author_rank: Super Contributor
timestamp: March 19, 2021, 08:10:46 am
content: I have never tested it nor read the source, but from reading the docs I'm under impression that GCC doesn't understand the contents of inline assembly. So it wouldn't optimize inside it, like removing NOPs. Hence my remark that using operands may prevent the assembly from being reordered with C code which accesses the same variables (to the extent required for correctness), but for any other inline assembly all bets are off unless declared volatile.

author: Siwastaja
author_rank: Super Contributor
timestamp: March 19, 2021, 08:21:26 am
content: Quote from: magic on March 19, 2021, 08:10:46 amGCC does not understand the contents, but (exactly for that reason) the programmer must give the list of input operands, output operands, and clobbers (basically side effects). Given all those lists are empty, the programmer is conveying to GCC that this piece of inline assembly has no effect on anything, hence it could be optimized away.But we don't need to guess, it's all in GCC's documentation:Quotehttps://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html(emphasis mine)So according to this, volatile keyword should not be needed. Now it's beyond anybody's guess if it has been always this way for each and every version of GCC. I have never tried NOP operations without explicit __volatile__.

author: magic
author_rank: Super Contributor
timestamp: March 19, 2021, 09:34:30 am
content: You are right, and it's even worseQuoteSo volatile isn't guaranteed to help.They also give friendly advice that if you want a bunch of instructions to execute in one sequence, your best bet is to put them in one asm block.

author: Siwastaja
author_rank: Super Contributor
timestamp: March 19, 2021, 09:58:28 am
content: This isn't surprising, because modern C isn't supposed to be a "portable assembler", which it originally kind-of was.A piece of C language code can't be "linearly" transformed into assembly code; or it could, but it would be extremely inefficient.Mixing these two different paradigms isn't trivial, as you can't really say in which "order" the C language runs. (For the same reason, single-stepping in a debugger is a lousy way to debug code.)

author: westfw
author_rank: Super Contributor
timestamp: March 19, 2021, 10:04:31 am
content: All these compiler reordering possibilities can be checked simply checking the object code produced.OP should do that...

author: Siwastaja
author_rank: Super Contributor
timestamp: March 19, 2021, 10:17:53 am
content: Yes, give-S -fverbose-asmto GCC command line and look at the output.

author: brucehoult
author_rank: Super Contributor
timestamp: March 19, 2021, 10:18:55 am
content: Quote from: westfw on March 19, 2021, 10:04:31 am.. in the actual binary file downloaded to the microcontroller, not just in a .o file, as the linker can also do optimisations.

author: Kleinstein
author_rank: Super Contributor
timestamp: March 19, 2021, 10:50:16 am
content: Usually the optimization the linker does is not about code details, but leaving out functions that are never called. So essentially only reducing the code size and not the actual way the program runs.Usually the ASM ouput should be after the compilers optimizations - this a main point of the output, so one could check if the optimizations are correct.

author: ricko_uk
author_rank: Super Contributor
timestamp: March 19, 2021, 02:55:05 pm
content: Thank you all,did a compiler update and it works with the _nop() as you would expect in the first place.Thank you all for the infos, learned other things in the process.

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 19, 2021, 05:43:24 pm
content: Relying on the compiler or specific behaviors isn't the right approach though IMHO.If you need small pauses without having to resort to a peripheral timer, I suggest using a loop with a system timer. I don't really know the PIC32MZ, so I don't know what this timer is, but on Cortex-M MCUs, you can use the DWT cycle counter (DWT_CYCCNT) for this purpose. I'm sure there's something like this on MIPS cores.Of course since there's a small overhead with such a loop, if you need extremely short pauses (like 1 or 2 cycles, this won't work, but getting pauses this short in a repeatable and sure way on a modern CPU is not a reasonable expectation anyway.)

author: brucehoult
author_rank: Super Contributor
timestamp: March 20, 2021, 12:39:13 am
content: Quote from: Kleinstein on March 19, 2021, 10:50:16 amThat depends on what linker you use and the arguments you pass to it.Modern GNU toolchains give you the option of using Link Time Optimisation (LTO), which combines all .o files as if they'd been in a single source file, and does things such as inlining and any subsequent optimisations enabled by that.That's not done by default, but if you didn't write the Makefile yourself (or at least read it very carefully) then you don't know whether it's being done or not. I would expect many canned IDEs for embedded work *are* enabling LTO these days.

author: rounin
author_rank: Regular Contributor
timestamp: March 26, 2021, 09:25:01 pm
content: I usually add memory to the gcc asm clobber list in addition to volatile to help prevent instruction reordering. I've only done this in few cases though but it has seemed to work. My snippets had pipeline flushing / mem barrier instructions in them too, so may not apply to a bare nop like here.

author: Doctorandus_P
author_rank: Super Contributor
timestamp: March 27, 2021, 05:44:33 pm
content: Quote from: westfw on March 19, 2021, 10:04:31 amIndeed.In this thread I also saw that the processor used is a 200+MHz capable thing.Such processors often have different clock domains with different parts (such as I/O) running at different clock frequencies, Usually also configurable. The core of such processors often runs at a very low voltage (such as 1V) to get to such high frequencies. I/O is "pumped up" to 3V3 and is often not even capable of working at the same frequency as the processor core.The low core voltage can be generated on the chip itself, and only briefly mentioned in the datasheet (if at all).

author: ricko_uk
author_rank: Super Contributor
timestamp: March 28, 2021, 01:31:17 am
content: Thank you all for the additional infos.