Macro Assembly Extension
VK6TT opened this issue · 5 comments
Firstly, festive greetings to everyone. And thanks again Thomas et al for STM8 eForth. It's like a Christmas present every time I use it.
I didn't know what to call this issue but I hope the following is clear. I wanted to avoid the CALL and RET overhead of small assembly extensions like ]C!. I'm dong some time time sensitive coding and every cycle really counts!
This is as far as I've got. Close but not quite.
\ A utility to allow writing small assembly routines
\ which are then copied, not called, into other words
\ A completely fictitious example
\ : START! ( C -- ) \ save byte into register
\ [ 'START ]C! \ save byte to address 'START
\ ;
\ : SETUP ( -- ) \ initialize stuff
[ START! ]M! \ instead of calling START!, copy the compiled bytes into this word
\ ;
: ['] ( compilation:"name" --; run-time: -- xt) ' POSTPONE LITERAL ; IMMEDIATE
VARIABLE TEST
VARIABLE myHERE
: ]M! ( A -- ) \ copy bytes from A to this definition until $81 ( ret )
0 TEST !
BEGIN
DUP
C@ DUP
$81 = NOT
IF C, 1+ 0 \ fetch the char and save it to HERE
ELSE -1
THEN
1 TEST +! TEST 16 = OR \ 4test - no runaways
UNTIL
2DROP \ discard A and C
;
: myT1 [ 1 TEST ]C! ;
' myT1 1 DUMP
\ 1B7 35 1 1 50 81 35 1 1 50 35 1 1 50 4D 21 4 5__P_5__P5__PM!_ ok
4 bytes saving the literal 1 into the variable Test correctly, followed by the RET
HERE myHERE !
' myT1 DUP ]M! ]M!
So this works from the prompt e.g.
myhere @ 1 dump
\ 1BC 35 1 1 50 35 1 1 50 4D 21 4 64 75 6D 70 72 5__P5__PM!_dumpr ok
The assembly has copied into RAM starting at HERE correctly.
But when I want to write:
: myT2
['] myT1 ]M!
;
things go haywire. As far as I know I have pushed the XT address of myT1 onto the stack and then ]M! should execute. I've tried all manner of [ and ] and IMMEDIATE without luck. Can someone help me please?
Instead of writing code like this:
: TimOn
[ 1 TIM4_EGR 0 ]B! \ UG bit set to force reload of PSCR and ARR
[ 0 TIM4_SR ]C! \ clear status register
[ 9 TIM4_CR1 ]C! \ enable counter one shot mode
;
: TimOff [ 0 TIM4_CR1 ]C! \ disable counter
;
: Pulse@ \ ( -- ) time, now to falling edge
TimOn
[ $7200 _RX 2* + 1+ , PD_IDR , $FB C, ] \ wait till _rx is high
[ $7200 _RX 2* + , PD_IDR , $FB C, ] \ wait till _rx is LOW
\ I was using this but it was too slow.
\ Begin
\ rx? \ wait till rising edge
\ Until
\ Begin
\ rx? not \ wait till falling edge
\ Until
TimOff
;
I'd rather write it like the following and save some clock cycles but having defined the word I can then use it in many places without the CALL RET overhead.
: TimOn
[ 1 TIM4_EGR 0 ]B! \ UG bit set to force reload of PSCR and ARR
[ 0 TIM4_SR ]C! \ clear status register
[ 9 TIM4_CR1 ]C! \ enable counter one shot mode
;
: TimOff [ 0 TIM4_CR1 ]C! \ disable counter
;
: RX_1 [ $7200 _RX 2* + 1+ , PD_IDR , $FB C, ] ; \ wait till _rx is high
: RX_0 [ $7200 _RX 2* + , PD_IDR , $FB C, ] ; \ wait till _rx is LOW
: Pulse@ \ ( -- ) time, now to falling edge
[ TimOn ]M!
[ RX_1 ]M!
[ RX_0 ]M!
[ TimOff ]M!
;
`:` Pulse@ \ ( -- ) time, now to falling edge
[ TimOn ]M!
[ RX_1 ]M!
[ RX_0 ]M!
[ TimOff ]M!
;
It would be no surprise to learn this functionality already exists. Does anyone have any ides please?
Hi Richard, Merry Christmas!
This looks like a good idea - especially when combined with relative conditionals (>REL
). Also keep in mind that the majority of core words won't work. As a safeguard you might want to check for the JP
opcode ($CC
) and abort (instead of using TEST
?).
But when I want to write:
: myT2
['] myT1 ]M!
;
things go haywire. As far as I know I have pushed the XT address of myT1 onto the stack and then ]M! should execute. I've tried all manner of [ and ] and IMMEDIATE without luck. Can someone help me please?
Could it be that you mean to say [ ' myT1 ]M!
?
Hi Thomas
yes you are correct. I wanted to write [ ' myT1 ]M1 but it was throwing a compile only error.
With a bit of trial and error I had success. Adding some error checking because chances are I will forget the tick character.
: ]M! ( A -- ) \ copy bytes from A to this definition until $81 ( ret )
DEPTH 0= ABORT" Empty Stack"
0 TEST !
BEGIN
DUP
C@ DUP
$81 = NOT
IF C, 1+ 0 \ fetch the char and save it to HERE
ELSE -1
THEN
1 TEST +! TEST 18 = OR \ 4test - no runaways
UNTIL
2DROP \ discard A and C
]
;
Now I can capture a pulse with TIM4 with no overheads and if I wanted to I could use RAM for definitions only called by ]M! e.g
RAM
: TimOn
[ 1 TIM4_EGR 0 ]B! \ UG bit set to force reload of PSCR and ARR
[ 0 TIM4_SR ]C! \ clear status register
[ 9 TIM4_CR1 ]C! \ enable counter one shot mode
;
: TimOff [ 0 TIM4_CR1 ]C! \ disable counter
;
: _RX=1 [ $7200 _RX 2* + 1+ , PD_IDR , $FB C, ] \ wait till _rx is high
;
: _RX=0 [ $7200 _RX 2* + , PD_IDR , $FB C, ] \ wait till _rx is LOW
;
NVM
: Pulse@ \ ( -- ) time, now to falling edge
[ ' TimOn ]M! \ Turn on TIM4
[ ' _RX=1 ]M! \ wait till _rx is high
[ ' _RX=0 ]M! \ wait till _rx is LOW
[ ' TimOff ]M! \ Turn off TIM4
;
RAM
After a power cycle or COLD I lose TimON, TimOff, _RX=1 and _RX=0, but Pulse@ still works.
I only advocate ]M! in time critical applications or the unlikely(?) scenario of space constrained code.
It was my intention to only apply it to assembler extensions as used above. Your question about the JP opcode was though provoking. I know your thinking beyond my intended use but I haven't yet grasped why. Some sort of optimisation use or by using RAM as above in a headerless role?
If I test for JP then shouldn't I also test for CALL and CALLR?:
EXIT_OPC = 0x81 ; RET opcode
DOLIT_OPC = 0x83 ; TRAP opcode as DOLIT
CALLR_OPC = 0xAD ; CALLR opcode for relative addressing
BRAN_OPC = 0xCC ; JP opcode
CALL_OPC = 0xCD ; CALL opcode
Best regards
Richard
My final version for anyone interested or in need of an extra clock cycle or two:
\ TEST for a runaway situation e.g. you somehow had $8900 on the stack but it was unused and all $FF to end of memory.
VARIABLE TEST
: ]M! ( A -- ) \ copy bytes from A to this definition until $81 ( ret )
DEPTH 0= ABORT" Empty Stack"
0 TEST !
BEGIN
DUP
C@ DUP
$81 = NOT
IF C, 1+ 0 \ fetch the char and save it to HERE
ELSE -1
THEN
1 TEST +! TEST @ 16 = DUP
IF ." BYTE LIMIT !!!!" THEN
OR
UNTIL
2DROP \ discard A and C
]
;
Hi Richard,
It was my intention to only apply it to assembler extensions as used above. Your question about the JP opcode was though provoking. I know your thinking beyond my intended use but I haven't yet grasped why. Some sort of optimisation use or by using RAM as above in a headerless role?
I was indeed thinking of the general use case - a capability for in-lining code for performance reasons is nice, so I asked myself "what if this is a library function and the user doesn't know about the limitations?". Frugality is often a feature, and limitation can be intentional. A hazard in the hands of the layman, a tool in the hands of the master.
Hi Thomas
I'm the hazardous layman and I can only use a hammer. I would be flattered if this was extended to be a library function but the untested alterntive I would consider is
: ]INC ( A -- ) $3C C, C, ] ; IMMEDIATE \ add 1 to MSB of VARIABLE
: ]M! ( A -- ) \ copy max of 32 bytes from A to this definition until $81 ( ret )
DEPTH 0= ABORT" Empty Stack"
0 TEST !
DUP 32 + SWAP
DO
I C@ DUP \ fetch the char
$81 =
IF LEAVE THEN
C,
[ TEST ]INC
LOOP
TEST C@ 32 = IF ." BYTE LIMIT !!!!" THEN
;
Best Regards
Richard