bluechip
Posts: 4
Joined: Fri Aug 20, 2021 1:16 pm

Why is NOP encoded as MOV Y, Y ?

Mon Jan 30, 2023 10:41 pm

Hi,

I guess this is an academic question... But why is `NOP` encoded as `MOV Y, Y` ...IE. not `MOV X, X` or `JMP next` (etc) ?

Edit: This is with regard to the PIO unit(s), not the main CPU core(s).

I realise, given the "1-Cycle" nature of every instruction, and the fact that sidesets and [delay] slots can be added to any instruction, there difference to the programmer is entirely semantic ...But I wonder if the choice was a 'toss of a coin', or if there were silicon considerations. EG. I would not be surprised to hear something like "This is the instruction that uses the least number of transistors, and is therefore the cheapest in terms of power consumption." ...or some other 'silicon use' rationalisation.

BC
Last edited by bluechip on Tue Jan 31, 2023 10:43 am, edited 1 time in total.

WestfW
Posts: 263
Joined: Tue Nov 01, 2011 9:56 pm

Re: Why is NOP encoded as MOV Y, Y ?

Tue Jan 31, 2023 2:25 am

Cortex-m0 actually has a defined NOP instruction (0xBF00), but apparently the original 32bit ARMs (pre-cortex, like the ARM7 TDMI and the full 32bit ARM instructions) didn't. Somehow they standardized on "mov r8, r8" (0x46c0), and the common assemblers and compilers still use that for "NOP"...
Wikipedia says that the Thumb16 NOP is 0xB000, which is "ADD SP, #0", but I can't say that I've ever seen that on Cortex M0.

bgolab
Posts: 431
Joined: Sat Jan 30, 2021 12:59 pm
Location: Krakow, PL

Re: Why is NOP encoded as MOV Y, Y ?

Tue Jan 31, 2023 6:04 am

Content removed as per next comment.
Last edited by bgolab on Tue Jan 31, 2023 7:13 am, edited 1 time in total.

dp11
Raspberry Pi Engineer & Forum Moderator
Raspberry Pi Engineer & Forum Moderator
Posts: 1103
Joined: Thu Dec 29, 2011 5:46 pm

Re: Why is NOP encoded as MOV Y, Y ?

Tue Jan 31, 2023 7:07 am

The original post is referring to PIO in RP2040.

WestfW
Posts: 263
Joined: Tue Nov 01, 2011 9:56 pm

Re: Why is NOP encoded as MOV Y, Y ?

Tue Jan 31, 2023 8:39 am

The original post is referring to PIO
Oops. I was wondering about "X" and "Y"...

bluechip
Posts: 4
Joined: Fri Aug 20, 2021 1:16 pm

Re: Why is NOP encoded as MOV Y, Y ?

Tue Jan 31, 2023 10:42 am

WestfW wrote:
Tue Jan 31, 2023 8:39 am
The original post is referring to PIO
Oops. I was wondering about "X" and "Y"...
Thank you for answering. My apologies for not making my question clear in the first place.

dp11
Raspberry Pi Engineer & Forum Moderator
Raspberry Pi Engineer & Forum Moderator
Posts: 1103
Joined: Thu Dec 29, 2011 5:46 pm

Re: Why is NOP encoded as MOV Y, Y ?

Tue Jan 31, 2023 10:58 am

It was reasonably obvious when you refer to things like X , Y, JMP and sideset

hippy
Posts: 13358
Joined: Fri Sep 09, 2011 10:34 pm
Location: UK

Re: Why is NOP encoded as MOV Y, Y ?

Tue Jan 31, 2023 3:48 pm

bluechip wrote:
Mon Jan 30, 2023 10:41 pm
I guess this is an academic question... But why is `NOP` encoded as `MOV Y, Y` ...IE. not `MOV X, X` or `JMP next` (etc) ?
Not entirely academic.

I believe a 'JMP next' was initially chosen but I recall it being said that caused problems, presumably when the last instruction in memory or the instruction before a 'WRAP'.

As to why 'MOV Y,Y' rather than 'MOV X,X' - no idea, but it does feel like the 'Y' version is an even less likely instruction a user would use than the' X' version, which could have made it the preferred choice.

jdb
Raspberry Pi Engineer & Forum Moderator
Raspberry Pi Engineer & Forum Moderator
Posts: 2808
Joined: Thu Jul 11, 2013 2:37 pm

Re: Why is NOP encoded as MOV Y, Y ?

Tue Jan 31, 2023 6:18 pm

One thing to note is that if you have many ways of encoding "instruction that does nothing except use a cycle" and then explicitly designate one form as the canonical NOP, then you have a certain amount of latitude to add side-effects to other incarnations of "NOP" later on.

https://patents.justia.com/patent/20080109640
Rockets are loud.
https://astro-pi.org

kilograham
Raspberry Pi Engineer & Forum Moderator
Raspberry Pi Engineer & Forum Moderator
Posts: 1431
Joined: Fri Apr 12, 2019 11:00 am
Location: austin tx

Re: Why is NOP encoded as MOV Y, Y ?

Tue Jan 31, 2023 7:03 pm

hippy wrote:
Tue Jan 31, 2023 3:48 pm
bluechip wrote:
Mon Jan 30, 2023 10:41 pm
I guess this is an academic question... But why is `NOP` encoded as `MOV Y, Y` ...IE. not `MOV X, X` or `JMP next` (etc) ?
Not entirely academic.

I believe a 'JMP next' was initially chosen but I recall it being said that caused problems, presumably when the last instruction in memory or the instruction before a 'WRAP'.
correct
As to why 'MOV Y,Y' rather than 'MOV X,X' - no idea, but it does feel like the 'Y' version is an even less likely instruction a user would use than the' X' version, which could have made it the preferred choice.
I don't remember Y !

hippy
Posts: 13358
Joined: Fri Sep 09, 2011 10:34 pm
Location: UK

Re: Why is NOP encoded as MOV Y, Y ?

Tue Jan 31, 2023 9:23 pm

kilograham wrote:
Tue Jan 31, 2023 7:03 pm
hippy wrote:
Tue Jan 31, 2023 3:48 pm
I believe a 'JMP next' was initially chosen but I recall it being said that caused problems, presumably when the last instruction in memory or the instruction before a 'WRAP'.
correct
And I've just run into that :( ...

Code: Select all

from   rp2     import asm_pio, StateMachine
import time

@asm_pio()
def Trucking():
  set(x, 10)

  wrap_target()
  mov(isr, x)
  push(block)
  jmp(x_dec, "next")   #  <===========
  label("next")
  wrap()

  label("Oops")
  set(x, 31)
  mov(isr,x)
  push(block)
  jmp("Oops")

sm = StateMachine(0, Trucking)
sm.active(1)

for n in range(20):
  print(hex(sm.get()))
  time.sleep(1)
It seems that upon the first decrement the code falls through the 'wrap' and doesn't go to the 'wrap_target'.

Not sure I have ever seen that behaviour explicitly and clearly documented or described but it's a PITA when one has created a pseudo 'dec(x)' opcode which performs 'JMP X_DEC, adr + 1'. It means having to check if it's immediately before a 'wrap' and, if it is, adjust its destination to the 'wrap_target'.

And that is why 'JMP next' is no use as a 'NOP'.

arg001
Posts: 198
Joined: Tue Jan 23, 2018 10:06 am

Re: Why is NOP encoded as MOV Y, Y ?

Wed Feb 01, 2023 12:34 pm

hippy wrote:
Tue Jan 31, 2023 9:23 pm
Not sure I have ever seen that behaviour explicitly and clearly documented or described but it's a PITA when one has created a pseudo 'dec(x)' opcode which performs 'JMP X_DEC, adr + 1'. It means having to check if it's immediately before a 'wrap' and, if it is, adjust its destination to the 'wrap_target'.
I think it's pretty clear in 3.5.2 of the datasheet. And you wouldn't really want it the other way, as another very common case is the JMP X-- to create an n-cycle loop, which does want the JMP to be taken: you could easily have such a loop at the foot of your main loop so you want it to wrap after the nth cycle but not before. Even special-casing JMP X-- pc+1 (where pc+1 equals the wrap address) wouldn't be helpful as you may be deliberately branching out of the main (wrapped) loop to a label that just happens to end up after the wrap point.

No disputing that it's a potential gotcha though!

Probably the best solution would be to add the DEC X pseudo-op to pioasm where it should be aware of the wrap position and able to omit the code accordingly - rather than a macro that can't.

Even that isn't 100% bullet proof against running the same code in two SMs with different wrap settings (something I've occasionally done).

hippy
Posts: 13358
Joined: Fri Sep 09, 2011 10:34 pm
Location: UK

Re: Why is NOP encoded as MOV Y, Y ?

Wed Feb 01, 2023 12:58 pm

Seems that any jump to the 'wrap' causes the 'wrap' to be fallen through.

I guess it makes sense because one might legitimately want to jump beyond a 'wrap' and jump back into the main code later, as in the code in the middle. The issue is that the code on the left is exactly equivalent to that in the middle but it's not particularly obvious it doesn't wrap when looking at it makes it seem that it should -

Code: Select all

  wrap_target()                 wrap_target()                   wrap_target()
  ...                           ...                             ...
  ...                           jmp(dec_x, "beyond")            jmp(dec_x, "beyond")
  jmp(dec_x, "last")            ...                             jmp(dec_x, "last")
  ...                           ...                             ...
  label("last")                 ...                             label("last")
  wrap()                        wrap()                          wrap()

                                label("beyond")                 label("beyond")
                                ...                             ...
The question is what a PIO Assembler should do when the user has specified what's on the left. It seems to me there are three possibilities -
  • Let them do it and leave them to figure out why it isn't doing what they expected
  • Prevent them from doing it by raising an error report
  • Automatically and silently correct the code so it does what they were expecting it to do
The existing PIO Assembler takes the first approach. I am inclined to auto-correct the code but that does get tricky. One wants to correct the left where label "last" is before the 'wrap' but not the middle where label "beyond" is after it, but uncorrected image code is the same, and "last" and "beyond" have the same address for the code on the right. I guess it means having to do some kind of tracking of where labels actually appear, before or after wrap.

arg001
Posts: 198
Joined: Tue Jan 23, 2018 10:06 am

Re: Why is NOP encoded as MOV Y, Y ?

Wed Feb 01, 2023 3:08 pm

hippy wrote:
Wed Feb 01, 2023 12:58 pm
The question is what a PIO Assembler should do when the user has specified what's on the left.
I'd be inclined to give a warning (or even an error) for the case of a label before a wrap pseudo-op in the source. Simply switching the order to have the label after the wrap leaves the output code the same but makes the source intuitive, so I don't see why you shouldn't force people to do that. I can't think of any case where putting a label before a wrap is helpful, as the only thing you can do with labels is jump to them, which as you've pointed out always jumps to the point after the wrap if there is one.

So on that basis, I think the only fixing up worth having is if you offer DEC_X as a pseudo-op rather than making people code it themselves.

(and I think one thing I've learned from this conversation is never be tempted to write DEC_X as a macro - if it's not available as a pseudo-op, code it in full so you can see the consequences!).

The check for label-before-wrap does need to be done at the point of parsing the source rather than the output generation as you seem to be wanting to do in your implementation, but hopefully that's not too bad as it's a very simple test that a label isn't allowed before a wrap with no instructions in between.

hippy
Posts: 13358
Joined: Fri Sep 09, 2011 10:34 pm
Location: UK

Re: Why is NOP encoded as MOV Y, Y ?

Wed Feb 01, 2023 4:36 pm

arg001 wrote:
Wed Feb 01, 2023 3:08 pm
I'd be inclined to give a warning (or even an error) for the case of a label before a wrap pseudo-op in the source.
I'm not inclined to do that - I am of the opinion that generated code should do what the user asked for and is expecting it to do, that compilers and assemblers are there to serve the user, not force them into complying with limitations which don't need to exist.

That it may not make a lot of sense to jump to a label immediately before a wrap there's no reason someone shouldn't if that's what they want to do, and it would have been done in anticipation that it would then wrap, rather than fall through.

I think I have resolved the issue. Looking at the timestamp of my previous post, it took almost three hours, but worth doing IMO ...

Code: Select all

  1 : 00 C088 | 110 00000 100 01000 | label()            | label("start")
  2 : 00 A042 | 101 00000 010 00010 | nop()              | nop()
  3 : 01 C08A | 110 00000 100 01010 | wrap_target()      | wrap_target()
  4 : 01 A042 | 101 00000 010 00010 | nop()              | nop()
  5 : 02 0000 | 000 00000 000 00000 | jmp(0x00)          | jmp("start")
  6 : 03 0001 | 000 00000 000 00001 | jmp(0x01)          | jmp("last")
  7 : 04 0008 | 000 00000 000 01000 | jmp(0x08)          | jmp("beyond")
  8 : 05 000E | 000 00000 000 01110 | jmp(0x0E)          | jmp("end")
  9 : 06 A042 | 101 00000 010 00010 | nop()              | nop()
 10 : 07 0041 | 000 00000 010 00001 | jmp(x_dec, 0x01)   | dec(x)
 11 : 08 C088 | 110 00000 100 01000 | label()            | label("last")
 12 : 08 C08B | 110 00000 100 01011 | wrap()             | wrap()
 13 : 08 C088 | 110 00000 100 01000 | label()            | label("beyond")
 14 : 08 A042 | 101 00000 010 00010 | nop()              | nop()
 15 : 09 0000 | 000 00000 000 00000 | jmp(0x00)          | jmp("start")
 16 : 0A 0001 | 000 00000 000 00001 | jmp(0x01)          | jmp("last")
 17 : 0B 0008 | 000 00000 000 01000 | jmp(0x08)          | jmp("beyond")
 18 : 0C 000E | 000 00000 000 01110 | jmp(0x0E)          | jmp("end")
 19 : 0D A042 | 101 00000 010 00010 | nop()              | nop()
 20 : 0E C088 | 110 00000 100 01000 | label()            | label("end")

Code: Select all

  0 : 00 A042 | 101 00000 010 00010 | nop()
  1 : 01 A042 | 101 00000 010 00010 | nop()
  2 : 02 0000 | 000 00000 000 00000 | jmp(0x00)
  3 : 03 0001 | 000 00000 000 00001 | jmp(0x01)
  4 : 04 0008 | 000 00000 000 01000 | jmp(0x08)
  5 : 05 000E | 000 00000 000 01110 | jmp(0x0E)
  6 : 06 A042 | 101 00000 010 00010 | nop()
  7 : 07 0041 | 000 00000 010 00001 | jmp(x_dec, 0x01)
  8 : 08 A042 | 101 00000 010 00010 | nop()
  9 : 09 0000 | 000 00000 000 00000 | jmp(0x00)
 10 : 0A 0001 | 000 00000 000 00001 | jmp(0x01)
 11 : 0B 0008 | 000 00000 000 01000 | jmp(0x08)
 12 : 0C 000E | 000 00000 000 01110 | jmp(0x0E)
 13 : 0D A042 | 101 00000 010 00010 | nop()
arg001 wrote:
Wed Feb 01, 2023 3:08 pm
(and I think one thing I've learned from this conversation is never be tempted to write DEC_X as a macro - if it's not available as a pseudo-op, code it in full so you can see the consequences!).
That's working fine as you can see in the above, and 'inc' is there as well. That does generate more than one opcode which may not be obvious to anyone using it so I will probably make having it available optional. Enhanced 'set' plus infinite and counter loops ...

Code: Select all

  1 : 00 A02B | 101 00000 001 01011 | mov(x, invert(null)) | set(x, -1)
  2 : 01 A0A3 | 101 00000 101 00011 | mov(pc, null)        | set(pc, 0)

  3 : 02 A029 | 101 00000 001 01001 | mov(x, invert(x))    | inc(x)
  3 : 03 0044 | 000 00000 010 00100 | jmp(x_dec, 0x04)     |
  3 : 04 A029 | 101 00000 001 01001 | mov(x, invert(x))    |

  4 : 05 0086 | 000 00000 100 00110 | jmp(y_dec, 0x06)     | dec(y)

  5 :                                                      | repeat()
  6 : 06 A042 | 101 00000 010 00010 | nop()                | nop()
  7 : 07 0006 | 000 00000 000 00110 | jmp(0x06)            | loop()

  8 : 08 E029 | 111 00000 001 01001 | set(x, 9)            | repeat(x, 10)
  9 : 09 A042 | 101 00000 010 00010 | nop()                | nop()
 10 : 0A 0049 | 000 00000 010 01001 | jmp(x_dec, 0x09)     | loop()

 11 : 0B 000B | 000 00000 000 01011 | jmp(0x0B)            | end()


Return to “General”