NathanY3G / rp2040-pio-emulator

RP2040 emulator for the testing and debugging of PIO programs
Apache License 2.0
40 stars 7 forks source link

Communication examples #133

Open kamocat opened 8 months ago

kamocat commented 8 months ago

It would be nice to have a complete example of testing a protocol. UART send and receive would be fairly simple.

This is partially addressed in https://github.com/NathanY3G/rp2040-pio-emulator/issues/123 and https://github.com/NathanY3G/rp2040-pio-emulator/issues/121, but they don't mention how to get data out of the FIFO.

I'm also not sure if the emulator allows the incoming signals to respond to the outputs. It looks like it's only fed the time. This makes it hard to test bidirectional protocols like I2C for 1-Wire.

NathanY3G commented 8 months ago

Hi @kamocat :wave:

Thanks for raising these great points. I will endeavor to add some examples soon - UART is a good suggestion :+1:

For the second point, I believe this could be addressed if the emulator passed additional information into the input_source function / callable. I will give this some more thought instead of making a change now. Do you believe this approach would work for you?

kamocat commented 8 months ago

Yes, that is a good approach.

aaronjamt commented 8 months ago

Hi @kamocat 👋

Thanks for raising these great points. I will endeavor to add some examples soon - UART is a good suggestion 👍

For the second point, I believe this could be addressed if the emulator passed additional information into the input_source function / callable. I will give this some more thought instead of making a change now. Do you believe this approach would work for you?

I think changing the input_source method to take the current_state object would likely be the fastest/easiest method... the one consideration would be that it's a breaking change. Can we maybe continue to pass the clock as the first argument, but add a keyword arg if the method supports it? Can either do a try/catch (try to pass it, otherwise fall back to just the clock) or maybe use inspect.getargspec(input_source) to check what keyword args are actually supported by the callable.

NathanY3G commented 8 months ago

Thanks for your input @aaronjamt. Rest assured, I have no intention to make breaking changes... even though this library hasn't made it to version 1.0! I believe we are thinking on similar lines. Before I merge a solution tomain, I will put a reference to my branch on this PR for you both.

aaronjamt commented 8 months ago

In the mean time, the main program could store the state in a global var after every call to emulate, then use that in the input_source method... not ideal of course but until there's a proper solution in place that might be a decent option

kamocat commented 8 months ago

The OOP solution is to create a class for the state machine, which is configured up front and then passes the data around. However, this means breaking changes.

The functional programming solution is to use a partial function. This doesn't require any changes to the library and allows the data to be passed via a variable in the for loop. EDIT: Sorry for not thinking of this earlier. I was excited to copy the function signature of rp2pio.StateMachine and hadn't considered that there might be another way.

NathanY3G commented 8 months ago

It was a good suggestion @kamocat :slightly_smiling_face:

NathanY3G commented 8 months ago

After trying a few different ideas, I have implemented a solution on the input-source-signature branch. This avoids any breaking changes by using type hints to support the following signatures.

(int) -> int
(State) -> int

If no type hint is specified then a warning is output but the existing behaviour is preserved i.e. an int is passed to the input_source.

kamocat commented 7 months ago

I have an example for the rx fifo where I print out the value and the clock. It uses print(), but maybe you can think of a way to put it on the plot?

import matplotlib.pyplot as plt
import seaborn as sns
from pioemu import clock_cycles_reached, emulate, State
import adafruit_pioasm
from collections import deque

program = adafruit_pioasm.assemble("""
; A crystal oscillator and counter
    set pins, 1
    mov x, ~ x
loop:
    wait 1 pin 0
    set pins, 0
    mov isr, ~ x
    push noblock
    wait 0 pin 0
    set pins, 1
    jmp x-- loop
""")

pin_value_series = []

for _, state in emulate(program, stop_when=clock_cycles_reached(72 + 1)):
    pin_value_series.append((state.clock, state.pin_values & 0x01))
    rx = state.receive_fifo
    if len(rx) > 0:
        print(f'{state.clock}: {rx.pop()}')

x_values = [clock for clock, logic_level in pin_value_series]
y_values = [logic_level for clock, logic_level in pin_value_series]

sns.set_style("darkgrid")
figure = plt.figure(figsize=(16, 1))
plot = sns.lineplot(x=x_values, y=y_values, drawstyle="steps-pre")
plot.set_xlabel("Clock cycles", fontsize = 16)
_ = plot.set_xticks(range(0, x_values[-1]))
plt.tight_layout()
plt.show()
NathanY3G commented 7 months ago

Hi @kamocat. I will take a look after work and see if I can offer up some ideas. The combination of Jupyter and Maplotlib is both powerful and flexible - the limiting factor tends to be the imagination!

NathanY3G commented 7 months ago

I have got some ideas to share with you :slightly_smiling_face:

  1. At the moment the clock is internal to the program, consequently each wait instruction executes within a single clock cycle. If the clock was externalised (example below) then you could plot the wait states on the same graph as the clock. If you then added the point in time when the FIFO is loaded then you could have 3 or 4 data series that might make for an interesting plot.
def clock_source(state: State):
    "Provides a clock source with a 50% duty-cycle derived from the PIO clock"
    return state.clock % 8 >= 4
  1. You could build on the above by making the clock source configurable. I have used Jupyter notebooks which contain UI controls and even support interactive graphs.

  2. I have the start of some other ideas which I will share after a bit more thought.

If you would like some links to example notebooks or code snippets please just ask :slightly_smiling_face:

kamocat commented 7 months ago

Yes, I think examples would be helpful. I was thinking of printing the data words left-aligned to the clock edge they were put in the FIFO, but that raises questions about font size and clipping box size that will require tweaking for each example.

NathanY3G commented 7 months ago

I will take a look this weekend for some good examples and get back to you.

NathanY3G commented 7 months ago

Hi @kamocat. Just a quick update to let you know that I have not forgotten! I intended to provide some examples soon.

NathanY3G commented 5 months ago

I have added a new example which enables GTKWave to be used for the visualisation of PIO programs.

kamocat commented 3 months ago

Hi Nathan, thank you for the example. It looks like it reads the PIO source from a file and outputs FST? Nice! I don't have GTKView for Windows at the moment, but it looks like I could either change this to output VCD or add FST to my to-do list for Pulseview 😀

NathanY3G commented 3 months ago

Thanks @kamocat :smiley:

PulseView is a great tool! As you said, it should be possible to adapt the example to output VCD instead. There is also an existing fst2vcd tool which might work. However, if you hit any issues I am happy to try and help :smiley: