@rp2.asm_pio()

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
superace
Posts: 10
Joined: Thu Jan 17, 2019 1:57 am

@rp2.asm_pio()

Post by superace » Wed Aug 24, 2022 12:54 pm

Hi,
Either I am doing something illegal or I just do not get it.
MicroPython v1.19.1 on 2022-06-18; Raspberry Pi Pico with RP2040
Thonny

How come this code prints in REPL
>>>
inside
inside
>>>

Code: Select all

from machine import Pin
import rp2

@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink_1hz():
    print ("inside")
    # Cycles: 1 + 7 + 32 * (30 + 1) = 1000
    set(pins, 1)
    set(x, 31)                  [6]
    label("delay_high")
    nop()                       [29]
    jmp(x_dec, "delay_high")

    # Cycles: 1 + 7 + 32 * (30 + 1) = 1000
    set(pins, 0)
    set(x, 31)                  [6]
    label("delay_low")
    nop()                       [29]
    jmp(x_dec, "delay_low")


# Create and start a StateMachine with blink_1hz, outputting on Pin(25)

#sm = rp2.StateMachine(0, blink_1hz, freq=2000, set_base=Pin(25))
#sm.active(1)
As you see the last 2 rows are commented out so I do not get why the blink_1hz function is run... So I would expect no printed output at all, and why do I get 2 rows?

TheSilverBullet
Posts: 50
Joined: Thu Jul 07, 2022 7:40 am

Re: @rp2.asm_pio()

Post by TheSilverBullet » Wed Aug 24, 2022 1:32 pm

Code: Select all

import rp2
@rp2.asm_pio()
def whatever():
    print('Cough … decorator.')
    print('Cough … closure.')

User avatar
jimmo
Posts: 2754
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia
Contact:

Re: @rp2.asm_pio()

Post by jimmo » Wed Aug 24, 2022 1:56 pm

superace wrote:
Wed Aug 24, 2022 12:54 pm
How come this code prints in REPL
There's quite a lot of magic in how the rp2.asm_pio decorator works. The summary is that it executes your function, providing it with a scope that includes functions like "set", "label", etc. And when your code calls those functions it emits the corresponding machine code (which can then be loaded into a given state machine).

(In other words -- the PIO doesn't execute your Python function. Rather the asm_pio decorator executes your function immediately as teh decorator is applied, and that's why the print statements run straight away).

See https://github.com/micropython/micropyt ... p2.py#L235 for the implementation.
TheSilverBullet wrote:
Wed Aug 24, 2022 1:32 pm
print('Cough … decorator.')
print('Cough … closure.')
TheSilverBullet: I think you're alluding to the same thing... can't quite tell though.

TheSilverBullet
Posts: 50
Joined: Thu Jul 07, 2022 7:40 am

Re: @rp2.asm_pio()

Post by TheSilverBullet » Wed Aug 24, 2022 1:59 pm

jimmo wrote:
Wed Aug 24, 2022 1:56 pm
TheSilverBullet: I think you're alluding to the same thing... can't quite tell though.
Yeah, I have to admit, it was a bit cryptic.
Food for thought, helps the discovery and learning process …

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

Re: @rp2.asm_pio()

Post by hippy » Tue Aug 30, 2022 5:44 pm

jimmo wrote:
Wed Aug 24, 2022 1:56 pm
(In other words -- the PIO doesn't execute your Python function. Rather the asm_pio decorator executes your function immediately as teh decorator is applied, and that's why the print statements run straight away).
And, because it's a two pass assembler, executes it twice, causing the 'print' within it to be executed twice.

This is a simplified CPython program equivalent of the RP2 assembler which can be run at a command line to illustrate what's going on ...

Code: Select all

emit = []

def nop   ()     : emit.append("nop")
def label (name) : emit.append(name + ":")
def jmp   (name) : emit.append("jmp " + name)

def asm_pio(**kw):
  def inner(func):
    global emit
    emit = []; func()
    emit = []; func()
    return emit
  return inner

@asm_pio()
def MyAsm():
  print("--Inside MyAsm--")
  label("loop")
  nop()
  jmp("loop")

# print("Executable PIO code is : {}".format(MyAsm))
Note that the two 'func()' calls within 'inner' are what executes your 'MyAsm' routine twice, causing the "--Inside MyAsm--" to be printed twice, even with the 'print' commented out.

User avatar
jimmo
Posts: 2754
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia
Contact:

Re: @rp2.asm_pio()

Post by jimmo » Tue Aug 30, 2022 11:37 pm

hippy wrote:
Tue Aug 30, 2022 5:44 pm
And, because it's a two pass assembler, executes it twice, causing the 'print' within it to be executed twice.
Yes good point!

Good example with the demo version... when we get around to writing more docs about it, then that would probably be a worthwhile thing to add!

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

Re: @rp2.asm_pio()

Post by hippy » Wed Aug 31, 2022 12:52 pm

My view is that RP2.py needs to be nuked from orbit and completely rewritten to allow specification of free-form PIO assembler, viz -

Code: Select all

@rp2.asm_pio()
def MyAsm():
  """
  start: nop
         jmp  start
  """
I'd be even more happier with having StateMachine do the assembly -

Code: Select all

MyAsm = """
  start: nop
         jmp  start
  """

sm = StateMachine(0, MyAsm)
I appreciate backwards compatibility is an issue but it is possible to support all of the above and retain backwards compatibility.

Even if we simply stick with what we have I believe it still needs nuking, or at least a very significant rewrite, to allow error checking to prevent things like 'set(x, 255)' and 'mov(x, 255)' mistakes and many others.

User avatar
jimmo
Posts: 2754
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia
Contact:

Re: @rp2.asm_pio()

Post by jimmo » Thu Sep 01, 2022 2:29 am

hippy wrote:
Wed Aug 31, 2022 12:52 pm
I appreciate backwards compatibility is an issue but it is possible to support all of the above and retain backwards compatibility.
I think partly this was to make the PIO look similar tot he existing @asm_thumb feature in MicroPython. At the end of the day, much of this is about using the existing lexer+parser that we have for Python.
hippy wrote:
Wed Aug 31, 2022 12:52 pm
or at least a very significant rewrite, to allow error checking to prevent things like 'set(x, 255)' and 'mov(x, 255)' mistakes and many others.
Isn't this mostly just a matter of adding more argument checking to the set() and mov() functions? Why does that require a significant rewrite? (I didn't write rp2.py and haven't had much experience with it, so this may be a silly question).

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

Re: @rp2.asm_pio()

Post by hippy » Thu Sep 01, 2022 10:35 am

jimmo wrote:
Thu Sep 01, 2022 2:29 am
hippy wrote:
Wed Aug 31, 2022 12:52 pm
or at least a very significant rewrite, to allow error checking to prevent things like 'set(x, 255)' and 'mov(x, 255)' mistakes and many others.
Isn't this mostly just a matter of adding more argument checking to the set() and mov() functions? Why does that require a significant rewrite? (I didn't write rp2.py and haven't had much experience with it, so this may be a silly question).
Better argument checking is what's needed, but that requires being able to determine that named constants were used where they should have been used and not where they shouldn't be used. That's hard because, by the time the arguments pass to the code generating functions, they have all been converted to integers so those routines can't tell if they were named or not.

For example 'set(x, null)' and 'mov(x, 3)' are both accepted but neither do what the person writing those presumably expected.

My view is that error checking should catch all mistakes to protect the coder from themselves. To achieve that myself I defined named constants as a class type rather then integer so I could check they were names of registers and then check if valid for the specific command. That wasn't overly complicated but did require quite a number of changes.

Other reasons for rewriting would be to make it single pass with fixups, and to support things like 'dec(x)' and 'inc(x)' which currently require 'jmp' to a following label to be specified. What the SDK assembler accepts has been tightened up but the MicroPython RP2 hasn't kept up so now accepts things the SDK won't.

I got most of this working as proof of concept but it needs completing and a rewrite to make it production quality, and also needs extensive testing to prove correctness and backwards compatibility. I haven't found the time to complete that.

Post Reply