Development Tips

Just a few handy debug methods I’ve developed along the way ...

PR: Monitor Stack Values and Program Flow

This is really simple, yet I use it all the time to find my bugs. Used with VIM Mapping, it’s fast to use. I’m still a Forth beginner and I need all the help I can get, so being able to rapidly insert print statements to follow the stack and program flow, plus show nothing is left on the stack on word completion, has made debugging a lot easier for me.

: pr ( identifier -- identifier print stack ) . .s cr ;           \ A debugging stack printer

PR: Usage Example

\ Program Name: pr-example.fs  for Mecrisp-Stellaris by Matthias Koch and licensed under the GPL
\ This program: 'pr' is a debugging 'print statement' 
\ Hardware: STM32F0 Discovery board
\ Terminal: e4thcom by Copyright (C) 2013-2017 Manfred Mahlow and licensed under the GPL
\ Copyright 2017 t.porter <terry@tjporter.com.au> and licensed under the GPL
\
\ ...................... screenpic .....................
\   pr-example 
\   1 [0 ] 0 
\   2 [0 ] 0 
\   3 [1 ] 0 0 
\   4 [3 ] 0 0 0 0 
\   Loop#0 
\   5 [2 ] 0 0 0 
\   6 [1 ] 0 0 
\   2 [0 ] 0 
\   3 [1 ] 0 1 
\   4 [3 ] 0 1 1 1 
\   Loop#1 
\   5 [2 ] 0 1 1 
\   6 [1 ] 0 2 
\   2 [0 ] 0 
\   3 [1 ] 0 2 
\   4 [3 ] 0 2 2 2 
\   Loop#2 
\   5 [2 ] 0 2 2 
\   6 [1 ] 0 4 
\   2 [0 ] 0 
\   3 [1 ] 0 3 
\   4 [3 ] 0 3 3 3 
\   Loop#3 
\   5 [2 ] 0 3 3 
\   6 [1 ] 0 6 
\   7 [0 ] 0
\    ok.
\ ...................... screenpic .....................

 : pr . .s cr ;	 \ print the stack and follow program flow for debugging

 :  pr-example cr
   1 pr	 								   
      4 0 do
   2 pr	 
     I 
   3 pr
       dup dup
   4 pr
     ." Loop#" . cr
   5 pr
      +
   6 pr
      drop
      loop
   7 pr
 ;

pr-example

Balancing The Return Stack

Forth does have a ‘Local Variable’, it’s called The ‘Return Stack’, but it must be balanced or your program will crash.

Note

“Balancing the Return Stack” means making sure that the number of >R equals the number of R> within the same definition. Special care is needed with looping.

Why use the Return Stack to as a Local Variable, why not just use a Global Variable ?

  1. The example Split program below saves 24 Bytes over Split2 and in Embedded, memory is a finite resource and costly to increase.
  2. I prefer Words to be self contained if possible
  3. The challenge of not using Global Variables

Split Program

This program splits a number into digits, and utilises the Return Stack as a local variable. At first glance, the Return Stack doesn’t look balanced does it ?

: split
   >R                      \ Save the input on the Return Stack
   BEGIN
       R> ?DUP 0 > WHILE   \ Fetch from the Return Stack, duplicate because " 0 >" and "/MOD" both use it, then continue WHILE greater than zero. ?DUP is used instead of DUP to prevent a zero being left on the stack after the program has completed.
       10 /MOD >R . cr     \ divide by 10 and save the result on the Return Stack, print each remainder on a new line
   REPEAT
;

\ ------- Split program output ---------- \
123456789 split

9
8
7
6
5
4
3
2
1

Warning

It’s often convenient to be able to put a stack item temporarily in the return stack, but the following rules must be observed because if the Return Stack isn’t balanced, this is very bad and if it doesn’t crash now, it will cause problems later.

  • Data put on the return stack must be taken back within the same word.
  • Data put on the return stack outside a ?DO (DO) ... LOOP (+LOOP) cannot be accessed within the loop.
  • Data put on the return stack within a ?DO (DO) ... LOOP (+LOOP) must be taken back before leaving the loop.
  • Data cannot be on the return stack when executing I or J in a loop.

Dissasemble Split

Split is 64 bytes

see split

20000398: B500  push { lr }
2000039A: B440  push { r6 }
2000039C: CF40  ldmia r7 { r6 }
2000039E: BC08  pop { r3 }
200003A0: 3F04  subs r7 #4
200003A2: 603E  str r6 [ r7 #0 ]
200003A4: 461E  mov r6 r3
200003A6: 2E00  cmp r6 #0
200003A8: D001  beq 200003AE
200003AA: 3F04  subs r7 #4
200003AC: 603E  str r6 [ r7 #0 ]
200003AE: 2E00  cmp r6 #0
200003B0: CF40  ldmia r7 { r6 }
200003B2: DD11  ble 200003D8
200003B4: 3F04  subs r7 #4
200003B6: 603E  str r6 [ r7 #0 ]
200003B8: 260A  movs r6 #A
200003BA: 2082  movs r0 #82
200003BC: 0180  lsls r0 r0 #6
200003BE: 3039  adds r0 #39
200003C0: 4780  blx r0  --> /mod
200003C2: B440  push { r6 }
200003C4: CF40  ldmia r7 { r6 }
200003C6: 2086  movs r0 #86
200003C8: 01C0  lsls r0 r0 #7
200003CA: 3021  adds r0 #21
200003CC: 4780  blx r0  --> .
200003CE: 2097  movs r0 #97
200003D0: 0180  lsls r0 r0 #6
200003D2: 3033  adds r0 #33
200003D4: 4780  blx r0  --> cr
200003D6: E7E2  b 2000039E
200003D8: BD00  pop { pc }

Alternative Split2 Progran using Variable

0 variable split-1

: split2
    split-1 !
    BEGIN
        split-1 @ ?DUP 0 > WHILE
        10 /MOD split-1 ! . cr
   REPEAT
;

Dissasemble Split2

Split2 is 88 bytes long

see split2

200003B6: 2080  movs r0 #80
200003B8: 0500  lsls r0 r0 #14
200003BA: 30E0  adds r0 #E0
200003BC: 0080  lsls r0 r0 #2
200003BE: 6246  str r6 [ r0 #24 ]
200003C0: CF40  ldmia r7 { r6 }
200003C2: B500  push { lr }
200003C4: 2080  movs r0 #80
200003C6: 0500  lsls r0 r0 #14
200003C8: 30E0  adds r0 #E0
200003CA: 0080  lsls r0 r0 #2
200003CC: 6A43  ldr r3 [ r0 #24 ]
200003CE: 3F04  subs r7 #4
200003D0: 603E  str r6 [ r7 #0 ]
200003D2: 461E  mov r6 r3
200003D4: 2E00  cmp r6 #0
200003D6: D001  beq 200003DC
200003D8: 3F04  subs r7 #4
200003DA: 603E  str r6 [ r7 #0 ]
200003DC: 2E00  cmp r6 #0
200003DE: CF40  ldmia r7 { r6 }
200003E0: DD15  ble 2000040E
200003E2: 3F04  subs r7 #4
200003E4: 603E  str r6 [ r7 #0 ]
200003E6: 260A  movs r6 #A
200003E8: 2082  movs r0 #82
200003EA: 0180  lsls r0 r0 #6
200003EC: 3039  adds r0 #39
200003EE: 4780  blx r0  --> /mod
200003F0: 2080  movs r0 #80
200003F2: 0500  lsls r0 r0 #14
200003F4: 30E0  adds r0 #E0
200003F6: 0080  lsls r0 r0 #2
200003F8: 6246  str r6 [ r0 #24 ]
200003FA: CF40  ldmia r7 { r6 }
200003FC: 2086  movs r0 #86
200003FE: 01C0  lsls r0 r0 #7
20000400: 3021  adds r0 #21
20000402: 4780  blx r0  --> .
20000404: 2097  movs r0 #97
20000406: 0180  lsls r0 r0 #6
20000408: 3033  adds r0 #33
2000040A: 4780  blx r0  --> cr
2000040C: E7DA  b 200003C4
2000040E: BD00  pop { pc }

One Way to Determine if The Return Stack is Balanced

In this case, I used a variable (rb) to tally my Return Stack pushes and pulls. A ‘rb’ total of 0 shows the Return Stack is balanced.

Return Stack Operation code Description
Push >R rb @ 1+ rb ! Add one to ‘rb’
Pull R> rb @ 1- rb ! Subtract one from ‘rb’

Split Program with added Return Stack Accounting

Every Return Stack ADD is worth 1 and REMOVE is worth -1. A total of 0 shows the Return Stack is balanced, so no visual comparison is required.

0 variable rb

: split-rbalance cr
>R rb @ 1+ rb !
BEGIN
  R> rb @ 1- rb !
  ?DUP 0 > WHILE
  10 /MOD
  >R rb @ 1+ rb !
  .  cr
REPEAT
cr
rb @ 0= if
."  The Return Stack is balanced " cr cr
else
."  UNBALANCED ! The Return Stack is NOT balanced! rb =  " rb @ . cr cr
then
;

Return Stack Accounting Output

123456789 split-rbalance

 9
 8
 7
 6
 5
 4
 3
 2
 1

 The Return Stack is balanced

ok.