Teletype recursion

I just picked up a used teletype, and have started to play around with it. One of the first things I thought about coding was an exponentially decaying envelope.

I started with the following code:

1:
X SUB X 1
IF NE X 0 : SCRIPT 2

2:
DEL 100 : SCRIPT 1

Then I tab over to live mode, and run the script
X 64
SCRIPT 1

If I print the value of X it says 62 (and remains that way). However, if I change the argument of DEL to 1 instead of 100 it works as expected (X counts down to 0 and then the recursion stops). So I am wondering what I am missing? Why does the recursion work as expected with a delay time of 1ms but not with a delay time of 100ms?

3 Likes

I also tried disabling the metronome but that didn’t help (i’m not sure why it would, but I thought the teletype might have an internal command queue that gets flushed on every cycle of the metronome?).

There is also something mentioned about infinite loop detection and script triggering in the docs, so maybe this is coming into play (even though there isn’t an infinite loop in this code)? And that doesn’t explain why it works with one delay setting and not another.

Perhaps I will need to dig into the source code, but that seems a bit overwhelming ATM…

1 Like

I have a vague memory of delays being fairly expensive, but in this case you should only have one delay at a time, so I wouldn’t expect a problem. Have you tried doing a binary search to figure at what value it stops working? Try 50, then 25, etc.

Very interesting in the outcome of this though

Ok, I tried your experiment. The recursive script triggering works correctly for delays from 1ms to 29ms. For delays of 30ms or larger it is broken.

1 Like

Just thought of a way around this - call script 1 from metro, and set the metro to 100 ms or whatever timing you want. Not that that gives us an answer to why this is breaking

Yes, that would work, but I’d like to know what is going on with my original scripts, since there are other things I might like to build involving recursion. I’d like to know if what I’m experiencing a bug? Or is it that I misunderstand something about how the teletype operates?

Here’s another experiment I tried:

1:
X SUB X 1
Y ADD Y 1
DEL Y : SCRIPT 2

2:
IF GT X 0 : SCRIPT 1

Then tab over to live mode and:

Y 30
X 100
SCRIPT 1

This seems to work. After the script finishes running X=0 and Y=130. In this example the delay keeps getting longer and it works. Now if I change the 2nd line in script 1 to “Y ADD Y 0”, and repeat the exact same commands in live mode it doesn’t work at all. What the heck!?

1 Like

Here’s a another experiment:

1:
X SUB X 1
Y ADD Y 1
Y MOD Y 2
Y ADD Y Z
DEL Y : SCRIPT 2

2:
IF GT X 0 : SCRIPT 1

Then tab over to live mode and:
Y 0
Z 50
X 100
SCRIPT 1

This seems to work for any value of Z. The value of Y cycles between Z and Z+1 on each recursion. So it seems that my initial recursion (see original post) works only if (a) the delay is shorter then 30ms; or (b) the delay bounces around a little on each iteration. Strange.

1 Like

This sounds like the infinite-loop detection is catching the script and terminating the process (to avoid a crash, that in this case, wouldn’t happen). I’m not sure exactly how the detection works, but I know it’s somewhat primitive – it’s super hard to make a perfect infinite loop protection scheme!

I’m guessing when you change the delay value each time the detection thinks it’s not stuck in loop, but I can’t explain the short delay times working (perhaps the detection has a timed element as well?). I’m sure @tehn or some others more familiar with that code could help out here.

Isn’t it basically impossible?

In computability theory, the Halting problem is the problem of determining, from a description of an arbitrary computer program and an input, whether the program will finish running or continue to run forever.

FYI, the loop detection needs to be rewritten… as it’s too naive and you can lock up your Teletype, see:

I’m going to rewrite it so that it will only allow a maximum depth of recursion instead, in theory this is really easy to do, but in practise I need to carefully think out how it will handle all the edge cases (like delays for example).

2 Likes

Interesting. A maximum recursion depth seems like a better way to handle this (at least then the results might be somewhat predictable).

Or what about actually allowing infinite loops, which can be desirable in some contexts. For example, I might start a few infinite loops with slightly different delays to do some Steve Reich like phasing… I could put in edge conditions (e.g., GT X 0) that let me stop those loops from live mode by setting the value of X. And perhaps there could be something like a CTL-C command (like in a unix shell) to break out/kill a process in case an infinite loop gets started by accident.

If I wanted to fork the firmware and disable the infinite loop detection would that be very easy (any hints on how to go about it)? I’m not familiar with the code base, or how to best go about digging into it. If I disabled the infinite loop detection, I realize I might have to reflash the firmware in case an infinite loop gets triggered from an initialization script.

1 Like

If you’re a coder, then start here by reading through the read me on this page:

That will get you up to speed on how to build and install a firmware.

On the current master branch of the Teletype firmware, look here for the loop detection:

I’d seen that previously when browsing the code relating to this thread. Am I interpreting this correctly if I summarise it as simply a script cannot call itself?

Yes, I think so.
(20 chars)

Browsing the code, I can’t really see where the infinite loop detection is happening. It doesn’t look like the script_caller is doing that (It just stops the same script from calling itself AFAIK). My guess might have been that infinite loop detection happens in the validate function, though I can’t really tell:

Is it possible to hook up a JTAG programmer to the Teletype, and set breakpoints/step through the code in GDB? Seems to me like this might be an easier path to understanding the program flow and understanding what is going on…

No JTAG alas :cry:

If you want to get rid of the recursion limit, change:

int8_t script_caller;
void tele_script(uint8_t a) {
    if (!script_caller) {
        script_caller = a;
        run_script(a - 1);
    }
    else if (a != script_caller) {
        run_script(a - 1);
    }

    script_caller = 0;
}

to

void tele_script(uint8_t a) {
    run_script(a - 1);
}

Yes, as mentioned in the GitHub ticket, it’s basic and needs replacing.

Does that help?

Thanks, I see how that protects a script from calling itself. I guess what I want to know, though, is why does my original recursion work with a 1ms delay and not with a 100ms delay…?

First up, I think this is probably abusing the delay system in ways it wasn’t supposed to be.

But if you want to dig in and have a look, this is where I would start…

I don’t think it has anything to do with script recursion (I apologise, I didn’t really read the whole thread when I first commented). Instead I’d concentrate on the delay code.

Look in the following places:

  • src/ops/delay.c
  • tele_tick in src/teletype.c
  • clockTimer_callback in module/main.c

You can see that tele_tick is called from clockTimer_callback to process the delays, it increments time by RATE_CLOCK and that RATE_CLOCK is defined to be 10.

Surprisingly 10 is the magic number. If I run your initial example with delay time of less than 10 it works, but if it’s 10 or more it doesn’t, and furthermore the delay icon on the live screen stays lit up. All of this makes me think that there is a bug in the delay code, if we’re really unlucky some sort of race condition. :

If you’ve got an FTDI cable and can solder you could try entertaining yourself with some good ol’ fashioned printf debugging and see if that get’s you anywhere.

I’d love to help more, not enough time sadly.

Great, thanks for the additional pointers. I do have an ftdi cable and can solder. Are there instructions somewhere on where to solder a header/which pins are TX/RX, etc…?

P.s. I just looked at the (over a year old) thread on the v2 teletype firmware proposal. I think the proposed autotrigger (AT) functionality, would pretty much replace my need to do recursion… It would be really nice if this functionality was implemented one day. (I might like to help with that but I have very little time myself these days)

I bought my Teletype with the header preinstalled (by request).

Mine is on my table at the moment while I do some firmware dev, here is a terrible quality photo of the reverse (reset button also soldered in underneath):

AFAIK it’s standard FTDI pinout, details on the cable are on the libavr32 read me.

Thanks for the photo.