Add if/for/while to PIO asm

RP2040 based microcontroller boards running MicroPython.
Target audience: MicroPython users with an RP2040 boards.
This does not include conventional Linux-based Raspberry Pi boards.
Post Reply
jgpeiro
Posts: 7
Joined: Wed Aug 05, 2015 8:27 am

Add if/for/while to PIO asm

Post by jgpeiro » Sun Jun 13, 2021 7:16 am

Im wondering if rp2.PIOASMEmit can be modified to handle simple C statements to use under @asm_pio as

Code: Select all

@asm_pio( set_init=rp2.PIO.OUT_LOW )
def prog():
    if_( x )
    set( pins, 1 )
    else_()
    set( pins, 0 )
    endif_()
To do this, you need to add such funcions in PIOASMEmit and handle during different passes.

Code: Select all

    def if_( self, cond ):
        if self.pass_ == 0:
            self.ifs[-1]["if"] = (cond, self.num_instr)
        else:
            cond = self.ifs[-1]["if"][0]
            instr= self.ifs[-1]["if"][1]
            if( self.ifs[-1]["elif"] ):
                instr = self.ifs[-1]["elif"][1]
            elif( self.ifs[-1]["else"] ):
                instr = self.ifs[-1]["else"][1]
            elif( self.ifs[-1]["end_if"] ):
                instr = self.ifs[-1]["end_if"][1]
            self.word( 0x0000 | cond << 5 | instr )
    def elif_( self, cond ):
        ...
    def else_( self ):
        ...
    def endif_( self ):
        ...
  
 _pio_funcs:
    # if condition constants
    "_x": 1,
    "_not_x_dec": 2,
    "_y": 3,
    "_not_y_dec": 4,
    "_x_eq_y": 5,
    "_not_pin": 6,
    "_osre": 7,
    # functions
    ...
        
    "if_": None,
    "elif_": None,
    "else_": None,
    "endif_": None,
Any idea on how to really implement such idea?

hippy
Posts: 130
Joined: Sat Feb 20, 2021 2:46 pm
Location: UK

Re: Add if/for/while to PIO asm

Post by hippy » Sun Jun 13, 2021 5:33 pm

It is possible. A run-time "if" would need to expand "if_(x)" to a correct sequence of PIO instructions behind the scenes. Assuming "if_(x)" is true when "x" is non-zero, something like ...

Code: Select all

if_( x )                    jmp(not_x, "L_true")
                            jmp("L_false")
set( pins, 1 )              label("L_true")
                            set(pins, 1)
                            jmp("L_done")
else_()                     label("L_false")
set( pins, 0 )              set(pins, 0)
endif_()                    label("L_done")
That's easy enough to do, though tracking state and carrying through 'sidesets' can be a pain. Re-ordering to save a 'jmp' can be done, but not easily without refactoring what already exists.

One can also add "repeat-until", "while-do", even "select-case".

But one practical problem is there are only 32 PIO instruction spaces available and it can be easy to run out once one gets liberal with using block-structured commands. The other is one loses visible determinism, can't simply see how many cycles will have been executed to get to a particular instruction, which will be a problem when trying to do timed loops.

I wouldn't be against it but am not sure there's much gain to be had. It might be useful for some use cases but I imagine it won't be for others.

I would personally invest effort in making the entire assembler easier to use, especially with respect to labels IMO ...

Code: Select all

asm("loop: if x")
asm("        set pins, 1")
asm("      else")
asm("        set pins, 0")
asm("      endif")
I have my 'asm' implemented and that's been a boon to me but it would be even better allowing code as a single multi-line string ...

Code: Select all

PioCode = """
loop: if x
        set pins, 1
      else
        set pins, 0
      endif
      jmp loop
"""
I am all for ripping up 'rp2.py' as it stands, fixing StateMachine so it can handle the above, getting rid of the @asm_pio decorator, having it do proper parsing and error checking and not using fudged MicroPython trickery to make it work.

But I might be biased having wasted more time than I needed to in trying to figure out why this worked -

Code: Select all

set(x, 1)
mov(isr, x)
but this optimised version did not, despite assembling without any errors -

Code: Select all

set(isr, 1)

hippy
Posts: 130
Joined: Sat Feb 20, 2021 2:46 pm
Location: UK

Re: Add if/for/while to PIO asm

Post by hippy » Sun Jun 13, 2021 6:29 pm

Quick proof of concept ...

Code: Select all

        self.ifs = []
        self.genlab = 0

Code: Select all

     def if_generic(self, cond):
        jmp(cond, str(self.genlab))
        jmp(str(self.genlab + 1))
        label(str(self.genlab))
        self.ifs.append([0, self.genlab + 1])
        self.genlab += 2
        
    def if_x(self):
        self.if_generic(1)
        
    def if_y(self):
        self.if_generic(3)
    
    def else_(self):
        if len(self.ifs) == 0:
            raise PIOASMError("No preceding 'if'")
        if self.ifs[-1][0] != 0:
            raise PIOASMError("Already had 'else'")
        self.genlab += 1
        jmp(str(self.genlab))
        label(str(self.ifs[-1][1]))
        self.ifs[-1] = [1, self.genlab]
    
    def endif_(self):
        if len(self.ifs) == 0:
            raise PIOASMError("No preceding 'if'")
        label(str(self.ifs[-1][1]))
        self.ifs.pop()

Code: Select all

        gl["if_x"] = emit.if_x
        gl["if_y"] = emit.if_y
        gl["else_"] = emit.else_
        gl["endif_"] = emit.endif_
And testing that ...

Code: Select all

@asm_pio()
def NativePio():
    jmp(not_x, "L_true")
    jmp("L_false")
    label("L_true")
    set(pins, 1)
    jmp("L_done")
    label("L_false")
    set(pins, 0)
    label("L_done")

print(NativePio)

Code: Select all

@asm_pio()
def BlockStructuredPio():
    if_x()
    set( pins, 1 )
    else_()
    set( pins, 0 )
    endif_()
    
print(BlockStructuredPio)

Code: Select all

@asm_pio()
def BlockStructuredAsmPio():
    asm("if x")
    asm("  set pins, 1")
    asm("else")
    asm("  set pins, 0")
    asm("endif")
    
print(BlockStructuredAsmPio)
And, Bingo!, identical code ...

Code: Select all

[array('H', [34, 4, 57345, 5, 57344]), -1, -1, 16384, 0, None, None, None]
[array('H', [34, 4, 57345, 5, 57344]), -1, -1, 16384, 0, None, None, None]
[array('H', [34, 4, 57345, 5, 57344]), -1, -1, 16384, 0, None, None, None]

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Add if/for/while to PIO asm

Post by pythoncoder » Tue Jun 15, 2021 10:10 am

hippy wrote:
Sun Jun 13, 2021 5:33 pm
...
But one practical problem is there are only 32 PIO instruction spaces available and it can be easy to run out once one gets liberal with using block-structured commands. The other is one loses visible determinism, can't simply see how many cycles will have been executed to get to a particular instruction, which will be a problem when trying to do timed loops...
Those issues would rule it out for me. Unlike a generic assembler, you're programming a state machine and you usually need to be able to see the exact sequence of states which will be executed.
Peter Hinch
Index to my micropython libraries.

hippy
Posts: 130
Joined: Sat Feb 20, 2021 2:46 pm
Location: UK

Re: Add if/for/while to PIO asm

Post by hippy » Tue Jun 15, 2021 11:06 am

One possible solution to the determinism problem would be to add an ability to express how many cycles there should be from one point to another and let the assembler figure it out, automatically inserting wait states as necessary.

I guess it could be possible in some cases to work out what the timing would be without the overhead code using standard wait state definitions and automatically adjust.

Something which just checks if the cycle timing is right, reports how far out if it not, would probably be useful even for standard PIO code, would aid the programmer figuring out what wait states to add.

All that would be easier to add in the command line 'pioasm' assembler than in the MicroPython 'rp2.py' assembler.

I have to admit I have no idea how much PIO code has to be deterministic, and how much doesn't. It may be that block-structured code wouldn't be getting used in deterministic code anyway so any deficiencies aren't important. I suppose it boils down to; does it give a good enough ROI to implement it ?

As someone who was an Assembler programmer for decades I find a lot of the PIO stuff unintuitive and there may be far more gain to be had in making what there is more intuitive and easier to use.

Post Reply