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
Why is NOP encoded as MOV Y, Y ?
Last edited by bluechip on Tue Jan 31, 2023 10:43 am, edited 1 time in total.
Re: Why is NOP encoded as MOV Y, Y ?
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.
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.
Re: Why is NOP encoded as MOV Y, Y ?
Content removed as per next comment.
Last edited by bgolab on Tue Jan 31, 2023 7:13 am, edited 1 time in total.
Re: Why is NOP encoded as MOV Y, Y ?
The original post is referring to PIO in RP2040.
Re: Why is NOP encoded as MOV Y, Y ?
Oops. I was wondering about "X" and "Y"...The original post is referring to PIO
Re: Why is NOP encoded as MOV Y, Y ?
It was reasonably obvious when you refer to things like X , Y, JMP and sideset
Re: Why is NOP encoded as MOV Y, Y ?
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.
Re: Why is NOP encoded as MOV Y, Y ?
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
https://patents.justia.com/patent/20080109640
Rockets are loud.
https://astro-pi.org
https://astro-pi.org
-
- 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 ?
correct
I don't remember Y !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.
Re: Why is NOP encoded as MOV Y, Y ?
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)
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'.
Re: Why is NOP encoded as MOV Y, Y ?
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.hippy wrote: ↑Tue Jan 31, 2023 9:23 pmNot 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'.
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).
Re: Why is NOP encoded as MOV Y, Y ?
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 -
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 -
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")
... ...
- 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
Re: Why is NOP encoded as MOV Y, Y ?
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.
Re: Why is NOP encoded as MOV Y, Y ?
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()
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()