Re: [Amforth] Multitasking/emit/hold
Hello Erich, My original 2019 problem of "why can't I redirect emit to output to my LCD from a task" is perhaps most simply addressed by an addition to the documentation. Once (I) it is understood that the user area a task has access to needs to be explicitly populated, it all works as expected. Your solutions would cover such a documentation addition I think. > There is an area in EEPROM holding all these values. > Much to my surprise these values are stored at some odd location. Look > for "EE_INITUSER:" in main.lst (the assembler list file). It will > reveal this location: > > | ; default user area > > | EE_INITUSER: > > | 74 00 00 .dw 0 ; USER_STATE > > | 76 00 00 .dw 0 ; USER_FOLLOWER > > | 78 ff 10 .dw rstackstart ; USER_RP > In this case $74. So we can pick things from $74 plus some offset. > > If I want to do this in run-demo, then a Forth constant holding that > offset would be nice (change to AmForth asm). I think such a Forth constant would be a good addition, and gives the option of ee>ram in a custom task-init. > Making the HOLD area (and possibly TerminalInputBuffer) task local, > can be solved, no doubt. But I'm not convinced that this is the way to > go unless different tasks operate on separate output destinations. Making the various buffers (HLD, PAD, etc.) task local appeals to me, but (I think) it would mean changes rather than additions to AmForth and the "one resource" issue would always remain. > Now back to your original problem. I rephrase: "Wouldn't it be > nice if I have one or more background tasks running along and > printing their output as they go --- independantly of each > other"? In mitigation, each of my outputing tasks did have its own output device :) > My experiments reminded me very clearly, that more than one task > emitting output on the same destination can only be a bad idea. Agreed. Best wishes, Tristan On 06May20 20:33, Erich Wälde wrote: > > Hello Tristan, > > back to mulitasking on AmForth. > > I spend an afternoon to create a background task, which does some > output by itself (see code below). There is nothing really new in > this. If I equip the background tasks with base and pointers to emit > (and emit?), it does work, including pictured output of numbers. > > I gained a few insights along the way: > > 1. > the background task has any form I want. So noone can stop me from > writing > > | : run-demo > > | \ some init stuff > > | #10 base ! > > | \ loop > > | begin > > | \ repeated work goes here > > | again > > | ; > This "init" space can be used to set missing bits without any need to > change init-task. > > 2. > however: the real XT found in emit is hidden behind a defer. > > | ' emit defer@ > will not work in run-demo. > > First solution: store this value in a constant before defining > run-demo. > > > | ' emit defer@ constant emit.orig > > | > > | : run-demo > > | emit.orig is emit > > | ... > > | ; > > This works for the io functions emit emit? key key?. I expect this to > work for the prompts .ok .ready .error .input as well. > > But storing these values separately seems kind of odd. > > Second solution: There is an area in EEPROM holding all these values. > Much to my surprise these values are stored at some odd location. Look > for "EE_INITUSER:" in main.lst (the assembler list file). It will > reveal this location: > > | ; default user area > > | EE_INITUSER: > > | 74 00 00 .dw 0 ; USER_STATE > > | 76 00 00 .dw 0 ; USER_FOLLOWER > > | 78 ff 10 .dw rstackstart ; USER_RP > In this case $74. So we can pick things from $74 plus some offset. > > If I want to do this in run-demo, then a Forth constant holding that > offset would be nice (change to AmForth asm). > > Third solution (not tested) instead of > > | : task-init ( tib -- ) > > | dup tib>tcb over tib>size 0 fill \ clear RAM for tcb and stacks > > | ... > we could write something like > > | : task-init ( tib -- ) > > | dup tib>tcb over tib>size \ -- r-addr length > > | ee_user rot rot\ -- e-addr r-addr length > > | \ possibly 2/ to correct for cells > > | ee>ram > > | ... > instead. So we get all the missing stuff delivered. This needs the > same Forth constant as in 2. > > I expect this to break on targets other than avr8 --- at least for my > current lack of understanding, how/where this information is stored on > the other platforms. > > > Still reading? No, I have not even tried to go beyond what we did know > already. > > > However. > > I wrote: > > Now back to your original problem. I rephrase: "Wouldn't it be > > nice if I have one or more background tasks running along and > > printing their output as they go --- independantly of each > > other"? > > My experiments reminded me very clearly, that more than one task > emitting output on the same destination
Re: [Amforth] Multitasking/emit/hold
Hello Tristan, back to mulitasking on AmForth. I spend an afternoon to create a background task, which does some output by itself (see code below). There is nothing really new in this. If I equip the background tasks with base and pointers to emit (and emit?), it does work, including pictured output of numbers. I gained a few insights along the way: 1. the background task has any form I want. So noone can stop me from writing > | : run-demo > | \ some init stuff > | #10 base ! > | \ loop > | begin > | \ repeated work goes here > | again > | ; This "init" space can be used to set missing bits without any need to change init-task. 2. however: the real XT found in emit is hidden behind a defer. > | ' emit defer@ will not work in run-demo. First solution: store this value in a constant before defining run-demo. > | ' emit defer@ constant emit.orig > | > | : run-demo > | emit.orig is emit > | ... > | ; This works for the io functions emit emit? key key?. I expect this to work for the prompts .ok .ready .error .input as well. But storing these values separately seems kind of odd. Second solution: There is an area in EEPROM holding all these values. Much to my surprise these values are stored at some odd location. Look for "EE_INITUSER:" in main.lst (the assembler list file). It will reveal this location: > | ; default user area > | EE_INITUSER: > | 74 00 00 .dw 0 ; USER_STATE > | 76 00 00 .dw 0 ; USER_FOLLOWER > | 78 ff 10 .dw rstackstart ; USER_RP In this case $74. So we can pick things from $74 plus some offset. If I want to do this in run-demo, then a Forth constant holding that offset would be nice (change to AmForth asm). Third solution (not tested) instead of > | : task-init ( tib -- ) > | dup tib>tcb over tib>size 0 fill \ clear RAM for tcb and stacks > | ... we could write something like > | : task-init ( tib -- ) > | dup tib>tcb over tib>size \ -- r-addr length > | ee_user rot rot\ -- e-addr r-addr length > | \ possibly 2/ to correct for cells > | ee>ram > | ... instead. So we get all the missing stuff delivered. This needs the same Forth constant as in 2. I expect this to break on targets other than avr8 --- at least for my current lack of understanding, how/where this information is stored on the other platforms. Still reading? No, I have not even tried to go beyond what we did know already. However. I wrote: > Now back to your original problem. I rephrase: "Wouldn't it be > nice if I have one or more background tasks running along and > printing their output as they go --- independantly of each > other"? My experiments reminded me very clearly, that more than one task emitting output on the same destination can only be a bad idea. In the end you get garbled output. Unless of course, you lock access to this one resource using semaphores. Thats one more thing to work out. "this one resource" starts pointing to a better understanding. This is the classical "several clients/tasks compete for a single resource" problem. This is what an operating system kernel must organize all the time. Any access to (any) resource must go through one instance. So we enter the world of locks and queues and privileges and priorities and what not. Use case: Task-1 acquires the "output on uart0"-lock. { Task-1 prepares the output Task-1 emits the output } possibly more than once Task-1 releases the lock. This also includes the fact, that emitting e.g. on uart, takes long. You copy one byte to the output register, and then basically you call pause. A task switch occurs at this point. Probably several task switches later, the next byte can be emitted. For some applications this might work. For other maybe not. Making the HOLD area (and possibly TerminalInputBuffer) task local, can be solved, no doubt. But I'm not convinced that this is the way to go unless different tasks operate on separate output destinations. Having cooperative multitasking basically offers that one task holds the cpu and calls pause only after it is done. But you need to check carefully, that pause isn't called in unexpected places. Having one task to emit output, and all the other report to this one? Enter the world of queues, dynamic memory allocation and locks. :-/ Well well. I hate to say that, but this seems like a slippery slope. And no, I'm neither a good system programmer nor living in kernel space. So my understanding of this sort of problems is very limited. I want to see if semaphores can help in some cases. One more thing: in my rs485-bus project, I hit a similar problem, of course: several sensor stations sending their values whenever their local clock says "it's time", to the one resource called rs485 bus. No thanks. My solution thus far is: The bus master is soliciting data from each station, when the bus master's clock says "it's time". And I have spend some effort to silence the
Re: [Amforth] Multitasking/emit/hold (was: Redirect EMIT from within a task)
Hello Tristan, Tristan Williams writes: > Hello Erich, > > Within task-init from multitask.frt I think a task's entire tcb/user > area is filled with zeros and then only the values from the task's > (flash) tib are copied across to the task's tcb/user area. A value for > BASE is not stored within the tib. Only sp0, sp0-- and rp0 are stored > in the task's tib. > > : tib>tcb ( tib -- tcb ) @i ; > : tib>rp0 ( tib -- rp0 ) i-cell+ @i ; > : tib>sp0 ( tib -- sp0 ) i-cell+ i-cell+ @i ; > : tib>size ( tib -- size ) > dup tib>tcb swap tib>sp0 1+ swap - > ; > > : task-init ( tib -- ) > dup tib>tcb over tib>size 0 fill \ clear RAM for tcb and stacks > dup tib>sp0 over tib>tcb #6 + ! \ store sp0in tcb[6] > dup tib>sp0 cell- over tib>tcb #8 + ! \ store sp0-- in tcb[8], tos > dup tib>rp0 over tib>tcb #4 + ! \ store rp0in tcb[4] > tib>tcb task-sleep\ store 'pass' in tcb[0] > ; You have clearly spend more time on this, I see! Populating base won't be difficult, but I wonder what other candidates are there. > > I believe the interpreter user area is fully populated from eeprom > at boot time, but all other tasks rely on the programmer to fill in > what is relevant to their tasks. I did not appreciate that included a > value for BASE, but I do now. That's what I had in mind. COLD avr8/words/cold.asm does jump to PFA_WARM. WARM common/words/warm.asm does call XT_INIT_RAM init-ram avr8/words/init-ram.asm does the copy loop > | XT_INIT_RAM: > | .dw DO_COLON > | PFA_INI_RAM: ; ( -- ) > | .dw XT_DOLITERAL > | .dw EE_INITUSER > | .dw XT_UP_FETCH > | .dw XT_DOLITERAL > | .dw SYSUSERSIZE > | .dw XT_2SLASH > | .dw XT_EE2RAM > | .dw XT_EXIT something like " eeinituser up@ sysusersize 2/ ee>ram " where " : ee>ram 0 do over @e over ! cell+ swap loop 2drop ; " no garantees. But there is ee>ram, which could be called in task-init, too, before storing sp0 rp0 ... > >> Having said that I feel inclined to add another: "Wouldn't it be >> nice, if I could run a second commandline task (quit) on an >> existing second serial connection (thing atmega644pa or >> similar)"? Thus effectively creating a *Two User AmForth on one >> AtMega644pa*? Actually I do have a use case for this. And I have >> started to implement something in small steps[2]: > > Would the two users have separate dictionaries? Good question! I haven't really thought about that. But I had in mind to prepend a new (limited) dictionary for that second serial connection, in order to make it deal correctly with the incoming data (well data wrapped in forth syntax). Thank you for your input. Cheers, Erich -- May the Forth be with you ... ___ Amforth-devel mailing list for http://amforth.sf.net/ Amforth-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/amforth-devel
Re: [Amforth] Multitasking/emit/hold (was: Redirect EMIT from within a task)
Hello Erich, Within task-init from multitask.frt I think a task's entire tcb/user area is filled with zeros and then only the values from the task's (flash) tib are copied across to the task's tcb/user area. A value for BASE is not stored within the tib. Only sp0, sp0-- and rp0 are stored in the task's tib. : tib>tcb ( tib -- tcb ) @i ; : tib>rp0 ( tib -- rp0 ) i-cell+ @i ; : tib>sp0 ( tib -- sp0 ) i-cell+ i-cell+ @i ; : tib>size ( tib -- size ) dup tib>tcb swap tib>sp0 1+ swap - ; : task-init ( tib -- ) dup tib>tcb over tib>size 0 fill \ clear RAM for tcb and stacks dup tib>sp0 over tib>tcb #6 + ! \ store sp0in tcb[6] dup tib>sp0 cell- over tib>tcb #8 + ! \ store sp0-- in tcb[8], tos dup tib>rp0 over tib>tcb #4 + ! \ store rp0in tcb[4] tib>tcb task-sleep\ store 'pass' in tcb[0] ; I believe the interpreter user area is fully populated from eeprom at boot time, but all other tasks rely on the programmer to fill in what is relevant to their tasks. I did not appreciate that included a value for BASE, but I do now. > Having said that I feel inclined to add another: "Wouldn't it be > nice, if I could run a second commandline task (quit) on an > existing second serial connection (thing atmega644pa or > similar)"? Thus effectively creating a *Two User AmForth on one > AtMega644pa*? Actually I do have a use case for this. And I have > started to implement something in small steps[2]: Would the two users have separate dictionaries? kind regards, Tristan ___ Amforth-devel mailing list for http://amforth.sf.net/ Amforth-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/amforth-devel
[Amforth] Multitasking/emit/hold (was: Redirect EMIT from within a task)
Hello Tristan, thanks for your message. My below answers/comments only regard AVR8. I have currently no idea, how this is similar or different in the other 3 targets. > I revisited "Redirecting emit from within a task in AmForth", > as I still like the idea of having a self contained task that can > perform its own io, but without having to write my own version of > pictured numeric output. It sure would be nice, if this worked without a hitch. Agreed! > It turned out that redirecting emit was not the problem. The xt for > the new emit was correctly being stored in the task's user area > (user+14) and was being called correctly by emit within the task. What > I had not appreciated at the time was that (user+12) which holds the > task's base value is zero by default. A base of value '0' is not good. However, it seems a little more convoluted: The USER area is filled from a snippet stored in EEPROM. The EEPROM values are defined in "avr8/amforth-eeprom.inc". In trunk the relevant snippet looks like this: > | ; default user area > | EE_INITUSER: > | .dw 0 ; USER_STATE > | .dw 0 ; USER_FOLLOWER > | .dw rstackstart ; USER_RP > | .dw stackstart ; USER_SP0 > | .dw stackstart ; USER_SP > | > | .dw 0 ; USER_HANDLER > | .dw 10 ; USER_BASE > | > | .dw XT_TX ; USER_EMIT > | .dw XT_TXQ ; USER_EMITQ > | .dw XT_RX ; USER_KEY > | .dw XT_RXQ ; USER_KEYQ > | .dw XT_SOURCETIB ; USER_SOURCE > | .dw 0; USER_G_IN > | .dw XT_REFILLTIB ; USER_REFILL > | .dw XT_DEFAULT_PROMPTOK > | .dw XT_DEFAULT_PROMPTERROR > | .dw XT_DEFAULT_PROMPTREADY > | .dw XT_DEFAULT_PROMPTINPUT So base is set to #10. When setting up a user area, these values are copied over. If you end up with the value zero in USER_BASE, it either was overwritten afterwards, or it wasn't copied correctly to begin with. For that I need to investigate, how exactly a task area is setup. And propably read a lot of documentation along the way :-) > This meant that after . or u. , > emit was never called as the mcu had crashed in the pictured numeric > output routines (in #s I think, though it is # that fetches base) which > use base. > > Ironically, to establish this I did end up re-writing versions of the > pictured numeric output routines in Forth so that they wrote to part > of an extended task user area - so providing task specific pad and > numeric picture buffers that I could be (more) sure were not being > modified elsewhere. When this still crashed the mcu, I re-read > > http://amforth.sourceforge.net/TG/Architecture.html?highlight=user > http://amforth.sourceforge.net/TG/recipes/Multitasking.html > > and I realised that I was responsible for providing a newly created > task with more than just an xt for emit. task-init only does so much. > Once 10 was written to the task's base location (user+12), my versions > of . u. wrote correctly to the task's local buffers and the redirected > emit was called by type (from my numeric picture routines). AmForth's > . and u. also wrote correctly to the (shared) numeric picture buffer below > the (shared) pad. > > http://amforth.sourceforge.net/TG/recipes/Multitasking.html This piece of documentation was written by me a long time ago. It does need some brush up. Now back to your original problem. I rephrase: "Wouldn't it be nice if I have one or more background tasks running along and printing their output as they go --- independantly of each other"? I think, it would. Now, what can we do about it? What is /the/ problem? One problem I mentioned is that the HOLD area (the place where pictured output is stored before emitting it) is shared between all tasks, as of this time[1]. A similar problem occurs, if more than one task print to the same output, character by character: we probably end up with a little mess. Sometimes this is not a problem, other times it is. Option 1: create a semaphore to ensure, that only one task is accessing HOLD, until it's finished. I was convinced there is an example in the cookbook. But there isn't. What I found is this: > | common/lib/multitask-semaphore.frt > | common/lib/multitask-messages.frt That can serve as inspiration. Option 2: create a separate hold area for each task. Well. It surely can be done, and others have surely done so. I need to spend some time on the exact layout of the RAM space. Having said that I feel inclined to add another: "Wouldn't it be nice, if I could run a second commandline task (quit) on an existing second serial connection (thing atmega644pa or similar)"? Thus effectively creating a *Two User AmForth on one AtMega644pa*? Actually I do have a use case for this. And I have started to implement something in small steps[2]: - add a second interrupt service routine on incoming data on that second serial interface. solved. - add a second input ring buffer (below rx/key/key?) solved. - add a second set of rx/key/key?