kenb001
Posts: 12
Joined: Tue Mar 16, 2021 3:39 pm

PIO and Micropython syntax question

Wed May 05, 2021 4:33 pm

I need to count down through some retro memory from 0x7FF to 0x780 while setting memory control lines. I have this working, except the address counts run from 0x7FF to 0. I can read the dual port sram fine and the waveforms are good on a logic analyzer.

What I can't figure out is how to count down to 0x780 and start over at 0. I'm reading a 2K SRAM, but only 0x780 locations are actually used.

This is the code that works 0x780 to 0
def get_vdata():
wrap_target()

# y = number of memory locations to count down from top
label ("top")
#mov(y, 0x780)
mov(x, 0x7FF) # we begin counting down from the top of vram
label ("spin")
mov (pins, x) # put the address on the bus (check this)
nop() .side(0b01)
nop() .side(0b00)
in_(pins, 8) # read the 8 data bits
mov (isr, x) # vdata is in the isr, now shift in the address.
push
irq(block, rel(0)) [1] # signal handler to read data
nop() .side(0b01)
nop() .side(0b11)
jmp (x_dec, 'spin') # jump if not zero and decrement address
jmp (not_x, 'top') # completed final vram address. start over

I've tried quite a few things (which is why the Y register was loaded but now commented out), but I'm going in circles. I wonder if someone might give me a hint or two.

Thanks!

danjperron
Posts: 3790
Joined: Thu Dec 27, 2012 4:05 am
Location: Québec, Canada

Re: PIO and Micropython syntax question

Wed May 05, 2021 4:56 pm

Use the pull function to transfer it to register x or y

Code: Select all

    pull()
    mov(x,osr)

Code: Select all

 self.sm.init(YourPIO,.......)
            self.sm.put(10000)
           self.sm.active(1)

horuable
Posts: 118
Joined: Sat Mar 06, 2021 12:35 am

Re: PIO and Micropython syntax question

Wed May 05, 2021 5:58 pm

kenb001 wrote: What I can't figure out is how to count down to 0x780 and start over at 0
Did you mean start over at 0x7ff?

The problem with your code is that mov works differently than what you think. In this case, it does something weird that happens to work just by coincidence. In general to set x or y register to a value greater than 31 you have to use FIFO as @danjperron suggested (for less or equal you can use set).

kenb001
Posts: 12
Joined: Tue Mar 16, 2021 3:39 pm

Re: PIO and Micropython syntax question

Wed May 05, 2021 7:45 pm

yes, start over at 0x7FF.

that clears up some questions i had. i'll do as @danjperron suggested and see if i can get that working.

thanks!

danjperron
Posts: 3790
Joined: Thu Dec 27, 2012 4:05 am
Location: Québec, Canada

Re: PIO and Micropython syntax question

Wed May 05, 2021 8:23 pm

Code: Select all

 in_(pins, 8) # read the 8 data bits
    mov (isr, x) # vdata is in the isr, now shift in the address.
I.M.O. isr will contain only x !

Since you don't use y

Code: Select all

    pull
    mov y,osr
    label ("top")
    mov x,y
    label ("spin")

kenb001
Posts: 12
Joined: Tue Mar 16, 2021 3:39 pm

Re: PIO and Micropython syntax question

Thu May 06, 2021 12:22 am

edit:

actually, the mov(isr,x) does work as i intended. i forgot to mention that i'm loading the isr with the data pins and then appending the vram address--apologies for that oversight. this works and i confirmed with a logic analyzer.

still, you helped me quite a bit with the pull suggestion. i'd been banging on this for too long and totally missed that. looking at it fresh this morning i have it working. i'm counting down to zero, but throwing away the extra data/addresses in the main program loop.

thanks again for the help!

horuable
Posts: 118
Joined: Sat Mar 06, 2021 12:35 am

Re: PIO and Micropython syntax question

Fri May 07, 2021 11:11 am

You can make your SM to loop from 0x7FF to 0x780 by using osr to store starting address between loops and move it to x to reset it, while y keeps the last address.

Here's an example program how that can be achieved, you should be able to modify it to do what you want. Mind you that for some reason it throws an error at the beginning so the printed addresses seem not to start from 0x7FF, but they really do. Also, y has to be set to 0x77F, due to x being decremented before comparison.

Code: Select all

from rp2 import asm_pio, StateMachine

@asm_pio()
def loop_test():
    mov(x, osr)
    label("start")
    nop() [31]
    nop() [31]
    nop() [31]
    nop() [31]
    mov(isr, x) [31]
    push() [31]
    irq(block, rel(0))
    jmp(x_dec, "dummy")
    label("dummy")
    jmp(x_not_y, "start")

i=0
def handler(sm):
    global i
    print("{} value: 0x{:x}".format(i, sm.get()))
    i += 1

sm = StateMachine(0, loop_test, freq=2000)
# Set y to hold last address
sm.irq(handler)
sm.put(0x77F)
sm.exec("pull()")
sm.exec("mov(y, osr)")
# Set OSR to first address, it will get loaded to x on every loop start
sm.put(0x7FF)
sm.exec("pull()")
sm.active(1)


kenb001
Posts: 12
Joined: Tue Mar 16, 2021 3:39 pm

Re: PIO and Micropython syntax question

Fri May 07, 2021 12:55 pm

@horuable thanks for this! i'd tried a similar approach by keeping count of the addresses in the main program and then reloading x and y when ready to restart from the top. intead of sm.exec (which i should have used) i used sm.active to disable and then re-enable the state machine. it worked, but was very slow and with 3 second gaps between the address runs in the pio. the more i have to do in micropython, the slower things become, of course.

i'll try this and see how it does.

the project is to read out video ram in a running vintage computer and then send the address and data to another system via uart, a raspberry pi 4B or similar, where it will be parsed and handed to pycurses. this will allow screen updates via ssh as well as injecting keystrokes back into the vintage computer. this amounts to a remote console, or allowing the computer to run headless.

for this to be useful, the updates have to be fairly quick, at least one screen update per second (1920 video addresses), though faster than that is the target. all of the pieces are more or less working, as well as my custom pcb which attaches to the main video board.

i figured this project to be good one for me to learn some micropython and about the pio--and so far i've picked up quite a bit.

i appreciate the help here and with my previous questions.

horuable
Posts: 118
Joined: Sat Mar 06, 2021 12:35 am

Re: PIO and Micropython syntax question

Fri May 07, 2021 2:27 pm

You can make it run quite fast by using autopull to shift address and data from SM. I think you should be able to squeeze all of it into around 8 PIO instructions, so you'll be reading a single address+data packet at around 15.5 MHz or faster if you overclock Pico. Combine it with DMA (which IMO is a must in this case) and it's looking quite good.
kenb001 wrote:read out video ram in a running vintage computer
I have no idea how these systems works, so excuse me if some of my questions are dumb, but I really don't know.
  1. How fast is vram updated?
  2. How is vram address controlled?
  3. What happens when Pico and the computer try to access vram at the same time?
I almost forgot. Here's the same program as before, but using autopush to output x. For some mysterious reason it has even more problems than before and may require running several times, or even restarting Pico, to run as it should. It's just a demo though so I didn't bother with avoiding this.

Code: Select all

from rp2 import asm_pio, StateMachine, PIO

@asm_pio(autopush = True, push_thresh = 8, in_shiftdir = PIO.SHIFT_LEFT)
def loop_test():
    mov(x, osr)
    label("start")
    nop() [31]
    nop() [31]
    nop() [31]
    nop() [31]
    in_(x, 12)
    nop() [31]
    irq(block, rel(0))
    jmp(x_dec, "dummy")
    label("dummy")
    jmp(x_not_y, "start")

i=0
def handler(sm):
    global i
    print("{} value: 0x{:x}".format(i, sm.get()))
    i += 1


sm = StateMachine(0, loop_test, freq=2000)
sm.irq(handler)
sm.put(0x77F)
sm.exec("pull()")
sm.exec("mov(y, osr)")
sm.put(0x7FF)
sm.exec("pull()")
sm.active(1)


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

Re: PIO and Micropython syntax question

Fri May 07, 2021 3:20 pm

There are some clever tricks which can be used to preset X or Y to any 32-bit value. This is my code for decrementing X from 0x7FF to 0x780 then repeating ...

Code: Select all

from rp2 import StateMachine, asm_pio
import time

@asm_pio()
def CountDown():
   # Set Y to 780-1 = 77F - 0111 0111 1111
   set(y, 0x07)
   in_(y, 32)   # 21 x 0 then 3 x 1
   in_(y, 4)    # 1  x 0 then 3 x 1
   in_(y, 2)    # 2  x 1
   in_(y, 2)    # 2  x 1
   mov(y, isr)
   # Set X to 7FF - 111 1111 1111
   set(x, 0x0F)
   in_(x, 32)  # 21 x 0 then 4 x 1
   in_(x, 3)   # 3  x 1
   in_(x, 4)   # 4  x 1
   mov(x, isr)
   # Report this X
   label("report")
   mov(isr, x)
   push(block)
   # Decrement X
   jmp(x_dec, "next")
   label("next")
   # Report again if not reached Y
   jmp(x_not_y, "report")
   
sm = rp2.StateMachine(0, CountDown)
sm.active(1)

while True:
  print(hex(sm.get()))
  time.sleep(0.1)

horuable
Posts: 118
Joined: Sat Mar 06, 2021 12:35 am

Re: PIO and Micropython syntax question

Fri May 07, 2021 3:48 pm

I admit, that's very clever and I probably wouldn't think of doing it this way. Two things I don't like about it are: 1. it's wasting many instructions of the precious 32 we have and 2. it could significantly slow down the SM.

As a side note, using a while loop to read out data is waaaaay better idea than using ISR, at least in this case. After this change my code is not throwing errors anymore and seems to run a bit faster.
By the way, you don't have to use time.sleep() in the loop since get() will stall if there's no data to read anyway.

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

Re: PIO and Micropython syntax question

Fri May 07, 2021 4:28 pm

Yes, I hadn't optimised and I agree it would be better to do the one-off setting of Y from a 'pull' or an 'exec'. An instruction can be saved in setting X, maybe more, but I don't think that's too detrimental as only done once on the commencement of every countdown. It wasn't tailored to the task in hand, just showing an option if wanting to do that. Updated version below.

The 'time.sleep' is simply so I can see numbers scroll by, not have Thonny choke when it gets large updates.

Code: Select all

from rp2 import StateMachine, asm_pio
import time

@asm_pio()
def CountDown():
   # Set X to 7FF - 111 1111 1111 from 0111 0111 1111
   set(isr, 0) # 21 x 0
   in_(y, 7)   # 7  x 1
   in_(y, 4)   # 4  x 1
   mov(x, isr)
   # Report this X
   label("report")
   mov(isr, x)
   push(block)
   # Decrement X
   jmp(x_dec, "next")
   label("next")
   # Report again if not reached Y
   jmp(x_not_y, "report")
   
sm = rp2.StateMachine(0, CountDown)
sm.put(0x780 - 1)
sm.exec("pull()")
sm.exec("mov(y, osr)")
sm.active(1)

while True:
  print(hex(sm.get()))
  time.sleep(0.1)
Update : Down to three PIO instructions to set X ... - Nope; fell for the assembler allowing something which wasn't actually doing what I was wanting to be done
Last edited by hippy on Sat May 08, 2021 10:14 am, edited 3 times in total.

horuable
Posts: 118
Joined: Sat Mar 06, 2021 12:35 am

Re: PIO and Micropython syntax question

Fri May 07, 2021 5:01 pm

hippy wrote: The 'time.sleep' is simply so I can see numbers scroll by, not have Thonny choke when it gets large updates.
Yeah, my bad, I should have read the code more carefully.

Now that I think of it, your method has a big advantage in not holding osr hostage so it can be used normally. I guess it all comes down to what the user needs. Anyway, I'm glad you've shown this since it's a very useful trick.

kenb001
Posts: 12
Joined: Tue Mar 16, 2021 3:39 pm

Re: PIO and Micropython syntax question

Fri May 07, 2021 5:37 pm

this is great feedback! actually answers some other questions i had about pio programming...

answering questions:

1. i don't recall how fast the computer updates the vram... the vintage rams are 4 2114 static rams, which aren't very fast. the z80 only writes to the video ram when it has something to update. otherwise a motorola 6845 CRTC device is constantly spinnting through video memory and updating the CRT at 60 Hz. the dual port static ram i'm using to replace the 4 2114 chips has an access time of ~55ns -- way overkill for this.

2. more specifically, in this system the video ram sits at the top of memory, 0xFFFF and runs down 2K, even though only 1920 addresses are actually used. to access the video ram for z80 writes and reads, a bit is set a port 0xFF which maps out the system memory in that 2k space and maps in the vram.

3. what happens if both the z80/crtc and the pico access the same address? in this dual port sram, there is some simple logic that throws busy flags that external logic needs to handle. the worst that happens now is i'll get a garbage character on either the pico or the CRT, or both. i picked this sram to start with because it's available in DIP which means it's easy to bend pins up for changes. the next rev will go to a more advanced dual port sram where port A, the computer system side, always wins and port B gets a wait signal. it's a PLCC device.

with the current code, just sending addresses and sample characters, i'm getting a screen update about every second. not shabby at the moment. i hope to hook it all back up to the real hardware later today and see how its working. also, i want to take a good look at the code examples both of you have provided. thanks again for that!
Last edited by kenb001 on Sun May 23, 2021 11:50 am, edited 1 time in total.

horuable
Posts: 118
Joined: Sat Mar 06, 2021 12:35 am

Re: PIO and Micropython syntax question

Sat May 08, 2021 10:28 am

Ok, thanks for your answers, I have a better understanding of your system now. Using dual port ram is very clever indeed.

But let me tell you about my crazy idea where Pico does all the work without additional hardware...

The way I see it you could use two SMs to do it. From my very quick research on the original ram there should be a CS signal that goes low every time data is accessed and I base this idea around it, so if I'm wrong you can stop reading now.

The first SM would have to monitor this CS signal and see what address is being accessed. If the address is 0x7FF it would then signal the second SM by setting some additional pin low. From then it would still monitor the address to see when it hits 0x780 (or 0x77F I guess) to tell the second SM it should stop readings by setting this additional pin high. It would then function as a "gate" for the second SM.

The second SM would wait for the gate signal to go low and when it happens it would have to monitor CE to see when the next address is ready. Then it would read data (or better yet: address + data in one go) and push it to FIFO. The readings should stop as soon as the gate goes high again.

Then Pico can take data from FIFO and send it to the Pi which would do the parsing and display.

Of course, that is just a basic sketch of how it could work. I haven't checked if it's possible or how exactly the programming would look like and I haven't figured out what would be the best way to get data from FIFO (simple loop with get()? DMA? something else?).
Also, some tweaks might be necessary to make sure timing requirements are observed.

kenb001
Posts: 12
Joined: Tue Mar 16, 2021 3:39 pm

Re: PIO and Micropython syntax question

Sun May 09, 2021 12:51 pm

@horuable, that's a clever idea! you'd be good with this retro stuff--i actually tried something similar in various ways with other fast microprocessors and also the pico (when you were answering an earlier question a month or so ago).

the vram is accessed by both the z80 (during horizontal retrace) and the motorola 6845 cathode ray controller at all other times (if you're not familiar with these, there''s a couple of ap notes at the internet archive: search CRTC Motorola). my first idea was to let the crtc do the counting through vram for me and i'd grab the data when CE* went low. there are 1920 addresses, or characters displayed in an 80 x 24 matrix. each row is comprised of 10 scan lines, so the crtc is counting through 1,152,000 addresses each second. way too fast, so i built some logic to interrupt every 10th scan line--i don't care about the scan lines, and i figured this might be slow enough to work. even if i missed a character here or there, it'd build up pretty fast in a frame buffer. still too fast.

then the pico arrived! it's simplified things because the GPIOs are sequential in both external pins and a register. this saves a lot of time, but i still need to save more. so, then i used the z80 access signal where it enables the data bus into the vram and switches the address multiplexers so that it's connected to the vram instead of the crtc. reading the GPIO pins on the pico was still too slow and i started working with the PIO.

the PIO is fast enough, but now i have the opposite problem--it's too fast! there are also some problems with the z80 write approach--the dual port sram solves that. with it, i control the frequency of access, not the z80 or crtc.

why too fast? i've got to do something with the data i'm collecting. at the moment, i'm using a seral link to my laptop for testing running at 115,200 baud. this isn't fast enough to send data, address, SOF and EOF in a packet and update a full video screen/frame more than every couple of seconds. which is where i am now. SPI is fast enough, but i want to use a raspberry pi (a pi zero w would be nice) ultimately to process and make the data available, but it doesn't support spi slave mode. in the meantime, i've got some code in on the pico that i haven't tried yet that will work a little like ncurses. it'll keep an array of what the previous screen was and compare it to what it pulls from the FIFO. if it's different, then it sends it to the uart and updates the array. if not, it drops it and waits for the next vram read. this may improve my serial bandwidth enough to work well most of the time.

maybe i can leverage the other core, or dma somehow if i find away to ship the data over to a pi faster then i am now. @horuable i can post a block diagram, or a schematic of the retro video card, or even my schematic if you're interested. thanks again!

horuable
Posts: 118
Joined: Sat Mar 06, 2021 12:35 am

Re: PIO and Micropython syntax question

Tue May 11, 2021 2:21 pm

My next idea was to read data only when it was being written by z80, but it seems you've already tried it and had some problems. Can you tell more what was wrong? Maybe it can be solved somehow.

For speeding up transfer you might want to try setting a higher baudrate for UART. In theory, Pico can handle up to 921600 bps which is quite a bit faster than what you're using right now. I'm not sure if it is supported by MP though, or if Pi can handle it on the receiving end.
Another way is to try and use a USB connection. Pico can run in Full Speed mode with a maximum speed of 12 Mbps, giving even more considerable speedup. Once again though, I'm not sure about the details.
kenb001 wrote: it'll keep an array of what the previous screen was and compare it to what it pulls from the FIFO. if it's different, then it sends it to the uart and updates the array. if not, it drops it and waits for the next vram read.
This seems like a great idea! If I'm not wrong you could even do that completely in PIO using some clever tricks with DMA to make it completely hands-off (at least with UART). I'm still trying to work out the details, but I'm pretty sure it can be done. I'll try to keep you updated if I come up with something more specific.
kenb001 wrote: i can post a block diagram, or a schematic of the retro video card, or even my schematic if you're interested.
Sure, it should be interesting to look at whatever documentation you have.

kenb001
Posts: 12
Joined: Tue Mar 16, 2021 3:39 pm

Re: PIO and Micropython syntax question

Fri May 14, 2021 12:52 pm

funny.. we're thinking along similar lines here!

i have this working pretty well now. a couple of days ago i upped the baud rates. 230400 is solid and 460800 is good with just a few glitches. i'm fairly certain this is just noise gettng into my rs232 lines and will be solid once i go to twisted pair. even 230400 is an acceptable speed. 921600 is on the list to try once i change the wiring.

your dma idea sounds interesting. i'd like to see how that works even if just to learn something. :-)

you asked about what went wrong with reading when the z80 inputs data into the vram: timing was one. the other is that i have to read everything perfectly the first time. i don't get a chance to fix my frame buffer if i miss a character or several, or get a noise hit. using the dual port sram lets me count through however i want and correct any misses. i was going to have to rev my test board anyway and i just bit the bullet and went with the new sram to replace all the standard video memory.

one thing i don't have is a cursor. where would it be positioned? if i really, really want a cursor then i need to access the address lines feeding from the computer system into port A of the dp sram and gate on the cursor signal from the CRTC to capture that address. or, i could snoop the CRTC cursor registers (2) for the address. i'll have to add another pico if i want to do this. if i snoop the CRTC i'll need to move it to my board as that's the easiest way to get access to the signals i'd need. if i watch the address bus and cursor line, i can use the input addrsses to port A and run a flying lead back to the vdg card for cursor.

Return to “General”