\ --- INTERPRETER --- \ Send a zero length message to host. All commands that don't return \ any payload use this before returning to the interpreter loop. If \ there is a payload, it needs to be properly terminated with \ tx-wait-ack. : ack 0 tx-handshake tx-wait-ack ; \ control xts : jsr push receive2 TOSH ! TOSL ! ; \ This is ack + 1 byte payload. : reply \ byte -- 1 tx-handshake transmit tx-wait-ack ; \ Emit is for async console logging from Forth code. It works for \ code invoked through jsr/ack. \ After sending the jsr/ack command, the host will start polling for \ commands coming from the target. An empty message sent to #0xFF \ means that the jsr/ack command has finished. \ All non-empty messages sent to #xFF are dumped as characters to the \ console by the host, after which the host goes back to a listening \ state. Before it does that it will send a command to keep the \ ping-pong going. This is most likely a NOP, but we'll use the \ proper interpret-packet word to interpret it before yielding control \ back to forth code that called emit. : emit \ byte -- reply : emit-handshake interpret-packet ; \ NOTE: console-on/off is disabled. fix this somewhere else. : ferase flash erase ack ; : fprog flash program ack ; \ token 0 is nop, so a stream of zeros can be used as soft interrupt \ Except for nop and reset, all tokens return at least one byte result \ to synchronize on. The protocol has Remote Procedure Call (RPC) \ semantics, but implements it without the need for simultaneous \ buffering and handling. \ These are macros to lower stack usage: keep only one cell to return \ to the interpreter loop from a jsr command. \ token -- : interpret-cmd #x0F and route ack . receive/ack . reply . jsr/ack . lda . ldf . ack . jsr . n@a+ . n@f+ . n!a+ . n!f+ . chkblk . stackptr . ferase . fprog ; : interpret-msg receive 0= if ; then \ empty messages are NOP, other are commands receive interpret-cmd ; \ start interpreting the byte stream : receive2 receive receive ; : receive/ack receive ack ; : jsr/ack jsr ack ; \ Bytecode interpreter main loop. The zero length message is ignored, \ all other messages are interpreted and assumed to be of correct \ length. ( This is debug: target doesn't need to second-guess the host. ) macro : 0= #xFF + drop nc? ; \ n -- ? : 0? -1 addlw 1 addlw z? ; \ n -- n ? : sets machine flags forth \ The first byte in the packet is the device address. We are #x00, \ any other we need to forward if we're a hub. : interpret-packet \ Handle protocol preamble. All other code needs to properly \ terminate transmission. rx-handshake receive 1 - nc? if \ 0 = us drop interpret-msg ; then \ forward to other (only for hub nodes) forward-msg ; : interpreter begin interpret-packet again \ Block transfer. These take the size from the command input to make \ the host -> target protocol context-free, and send out message \ length to do the same for target -> host protocol. : rdh receive dup tx-handshake ; : n@f+ rdh for @f+ transmit next tx-wait-ack ; : n@a+ rdh for @a+ transmit next tx-wait-ack ; : n!f+ receive for receive !f+ next ack ; : n!a+ receive for receive !a+ next ack ; \ pointer initialization : lda receive2 a!! ack ; : ldf receive2 f!! ack ; \ program block memory check : chkblk 255 64 for @f+ and next reply ; \ Dump data stack size. Since the data stack is a real stack to the \ host, the target needs to provide information about the bottom. The \ easiest way to do this is to just return the size, and let the host \ pull out the data. : stackptr FSR0L @ reply ; \ Place a breakpoint in Forth code. : break ack interpreter ; \ Debugger control. This is executed from the console. The \ interpreter at the point of the executed command is 2 levels deep: \ one for the loop, and one for jsr/ack. The rest are tail calls. : continue pop pop ;