Home
JAQForum Ver 24.01
Log In or Join  
Active Topics
Local Time 17:15 19 Sep 2026 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

Forum Index : Microcontroller and PC projects : New in V6.03.02b9 : create a CSUB from Basic without writing any C

Author Message
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 11879
Posted: 01:11pm 17 Sep 2026
Copy link to clipboard 
Print this post

mmb2csub.pdf

Every PicoMite program has at least one routine doing the real work, and most of the
time the interpreter spends there goes on *reading* the code rather than
running it. `mmb2csub` compiles that routine to machine code and puts it back
into your program as a CSUB:

```
:: assuming I am in the user-tools directory after unpacking the zip file
python mmb2csub.py ../examples/julia.bas PlotJulia
```

That rewrites the program: the original Basic routine is commented out, the CSUB is
appended, and **your call sites do not change** — MMBasic calls a CSUB exactly
as it calls a SUB. The Julia set demo included with the tool renders in 5.9
seconds instead of 119 on an RP2040, and 2.5 instead of 86 on an RP2350 - 20x
and 35x - and draws a byte-identical image either way.

**How it works.** The translation from MMBasic to C is done by `mmb2c.py`, a
complete MMBasic-to-C translator — the same one that, converted to C, runs as
a native application under the Fuzix port. It already understood MMBasic's
scope rules, string semantics and array layouts, which is the hard part. What
`mmb2csub` adds is the other half: a driver that picks one routine out of your
program and works out what it needs, and a runtime that maps the translated C
onto the firmware's *own* routines through the CallTable defined in PicoCFunctions.h.

That second part is what makes the result trustworthy. `SIN`, `MID$`, `STR$`,
`RGB` and the drawing commands inside a CSUB call the same firmware code the
interpreter calls, so a converted routine cannot quietly disagree with the
original about what `MID$` means — and the compiled blob stays small, because
none of it is duplicated.

**One converted program runs on every PicoMite.** The same file — same bytes
— works on the RP2040 and the RP2350 and on every firmware variant, because
the code is built for the Cortex-M0+, is position-independent, and finds the
firmware's routines through a table it locates at run time rather than an
address fixed when it was compiled. Tested both ways round: byte-identical
output from one file on a PicoMiteVGA (RP2040) and a PicoMiteHDMIWEB
(RP2350B). So a converted program can be posted or shipped exactly like any
other `.bas`.

**What to expect.** Loops, array work and arithmetic run 10–20x faster.
Routines that mostly call the firmware already — graphics, `SIN`, string
formatting — gain much less, because only the interpreting overhead goes away.
The tool tells you which yours is before you commit to anything: `--list`
compiles and links every routine in your program and reports what each would
cost, without changing a thing.

It is also honest about what it cannot do. Anything unsupported is refused by
name, before anything is written, rather than producing a CSUB that is subtly
wrong.

**Getting it:** `mmb2csub-6.03.02b9.zip`, attached to this release. It needs
Python, the Arm GNU toolchain (`arm-none-eabi-gcc`) — the same compiler used
to build the firmware — and one Python package (`pip install pyelftools`). The manual inside covers setting both up on Windows and Linux, and the recommended workflow.

Firmware
6.03.02b9 or later is required on the board.

Julia.bas as originally written
' julia.bas - Julia set, with plotjulia taking everything it needs as
' parameters so it is a self-contained routine (the first mmb2csub target).
Mode 2
CLS
map maximite
'Specify initial values
RealOffset = -1.30
ImaginOffset = -1.22
'------------------------------------------------*
'Set the Julia set constant [eg C = -1.2 + 0.8i]
CRealVal = -0.78
CImagVal = -0.20
'------------------------------------------------*
MAXIT=80 'max iterations
PixelWidth = MM.HRes
PixelHeight = MM.VRes
GAP = PixelHeight / PixelWidth
SIZE = 2.50
XDelta = SIZE / PixelWidth
YDelta = (SIZE * GAP) / PixelHeight
' MAP() has no CSUB CallTable slot, and in a fixed mode its sixteen values are
' constants - so look them up once here and pass the table in.
Dim mp%(15)
For i% = 0 To 15 : mp%(i%) = map(i%) : Next i%
plotjulia PixelWidth, PixelHeight, XDelta, YDelta, RealOffset, ImaginOffset, CRealVal, CImagVal, MAXIT, mp%()
' the rendered image, for comparing this against the CSUB version byte for byte.
' SAVE IMAGE records the framebuffer and ignores the colour map, so what lands in
' the file is exactly what plotjulia computed.
Save Image "julia.bmp"
Do
 a$ = Inkey$
Loop While a$ = ""
end
'
' w, h    picture size in pixels
' xd, yd  the step in the complex plane per pixel
' rOfs, iOfs   top-left corner of the view
' cRe, cIm     the Julia constant C
' mit          iteration limit
' mp%()        the sixteen colours, already resolved through MAP()
sub plotjulia w, h, xd, yd, rOfs, iOfs, cRe, cIm, mit, mp%()
Local X, Y, CX, CY, Zr, Zi, COUNT, new_Zr, new_Zi
'Loop processing - visit every pixel
For X = 0 To (w - 1)
 CX = X * xd + rOfs
 For Y = 0 To (h - 1)
   CY = Y * yd + iOfs
   Zr = CX
   Zi = CY
   COUNT = 0
'    Begin Iteration loop
   Do While (( COUNT <= mit ) And (( Zr * Zr + Zi * Zi ) < 4 ))
     new_Zr = Zr * Zr - Zi * Zi + cRe
     new_Zi = 2 * Zr * Zi + cIm
     Zr = new_Zr
     Zi = new_Zi
     COUNT = COUNT + 1
   Loop
   Pixel X,Y,mp%( COUNT Mod 16)
 Next Y
Next X
end sub


After running the csub generator. Note: the original basic, the C source and comments in the csub are all removed by using the option --lean in the generator command
' julia.bas - Julia set, with plotjulia taking everything it needs as
' parameters so it is a self-contained routine (the first mmb2csub target).
Mode 2
CLS
map maximite
'Specify initial values
RealOffset = -1.30
ImaginOffset = -1.22
'------------------------------------------------*
'Set the Julia set constant [eg C = -1.2 + 0.8i]
CRealVal = -0.78
CImagVal = -0.20
'------------------------------------------------*
MAXIT=80 'max iterations
PixelWidth = MM.HRes
PixelHeight = MM.VRes
GAP = PixelHeight / PixelWidth
SIZE = 2.50
XDelta = SIZE / PixelWidth
YDelta = (SIZE * GAP) / PixelHeight
' MAP() has no CSUB CallTable slot, and in a fixed mode its sixteen values are
' constants - so look them up once here and pass the table in.
Dim mp%(15)
For i% = 0 To 15 : mp%(i%) = map(i%) : Next i%
plotjulia PixelWidth, PixelHeight, XDelta, YDelta, RealOffset, ImaginOffset, CRealVal, CImagVal, MAXIT, mp%()
' the rendered image, for comparing this against the CSUB version byte for byte.
' SAVE IMAGE records the framebuffer and ignores the colour map, so what lands in
' the file is exactly what plotjulia computed.
Save Image "julia.bmp"
Do
 a$ = Inkey$
Loop While a$ = ""
end
'
' w, h    picture size in pixels
' xd, yd  the step in the complex plane per pixel
' rOfs, iOfs   top-left corner of the view
' cRe, cIm     the Julia constant C
' mit          iteration limit
' mp%()        the sixteen colours, already resolved through MAP()
/*
' --- mmb2csub: PlotJulia replaced by a CSUB; original follows
sub plotjulia w, h, xd, yd, rOfs, iOfs, cRe, cIm, mit, mp%()
Local X, Y, CX, CY, Zr, Zi, COUNT, new_Zr, new_Zi
'Loop processing - visit every pixel
For X = 0 To (w - 1)
 CX = X * xd + rOfs
 For Y = 0 To (h - 1)
   CY = Y * yd + iOfs
   Zr = CX
   Zi = CY
   COUNT = 0
'    Begin Iteration loop
   Do While (( COUNT <= mit ) And (( Zr * Zr + Zi * Zi ) < 4 ))
     new_Zr = Zr * Zr - Zi * Zi + cRe
     new_Zi = 2 * Zr * Zi + cIm
     Zr = new_Zr
     Zi = new_Zi
     COUNT = COUNT + 1
   Loop
   Pixel X,Y,mp%( COUNT Mod 16)
 Next Y
Next X
end sub
*/

' --- mmb2csub: generated code for PlotJulia
CSUB PlotJulia FLOAT, FLOAT, FLOAT, FLOAT, FLOAT, FLOAT, FLOAT, FLOAT, FLOAT, INTEGER
00000022
'__aeabi_dadd
4C03B510 69E46824 682434A4 BD1047A0 E000ED08
'__aeabi_dsub
4C03B510 69E46824 682434A8 BD1047A0 E000ED08
'__aeabi_dmul
4C03B510 69E46824 682434A0 BD1047A0 E000ED08
'__aeabi_dcmplt
4C04B510 69E46824 682434B0 0FC047A0 46C0BD10 E000ED08
'__aeabi_dcmple
4C05B510 69E46824 682434B0 1E4347A0 0FC04318 46C0BD10 E000ED08
'__aeabi_dcmpge
4C04B510 69E46824 682434B0 43C047A0 BD100FC0 E000ED08
'PlotJulia
B097B5F0 22009214 93154C8F 00066823 911369DB 605A6FDB 6823601A 6FDF69DB
4295683D 2080D111 01406B1B 21804798 60386823 014969DB 605D6FDB 69DB6823
685A6FDB D900428A 6823605D 68302200 69DB6871 685B6FDB 4B7C9312 FF96F7FF
23002200 910D900C 93019200 990D980C 69DB6823 9A006FDD F7FF9B01 2800FFB5
2000D105 9B122100 B017606B 68ABBDF0 60AB3301 D103059B 69DB6823 47986A9B
99019800 681A9B14 F7FF685B 9B1CFF79 685B681A FF60F7FF 910F900E 22009913
68496808 F7FF4B61 2200FF61 90102300 92029111 98109303 68239911 6FDD69DB
9B039A02 FF80F7FF D1082800 99019800 4B562200 FF40F7FF 91019000 68ABE7B6
60AB3301 D103059B 69DB6823 47986A9B 99039802 681A9B15 F7FF685B 9B1DFF41
685B681A FF28F7FF 9F0F9E0E 23002200 91059004 93079206 003B0032 00390030
FF2EF7FF 9B059A04 91099008 00190010 FF26F7FF 900A6823 9806910B 69DD9907
681A9B20 F7FF685B 2800FF31 9A0AD00B 98089B0B F7FF9909 2200FEFF F7FF4B34
2800FF19 9800D12D 002B9901 681B33EC 681E3588 4798682B 00056823 99039802
338869DB 4798681B 90046823 980669DB 001A9907 32FC3388 6CD7681B 22104798
47B82300 00C09B21 9904581A 47B00028 99039802 4B1D2200 FECEF7FF 91039002
6FEAE779 33016893 059B6093 6823D103 6A9B69DB 9A0A4798 98089B0B F7FF9909
9B1EFEC5 685B681A FEB6F7FF 003B0032 91099008 00390030 FEAEF7FF 9B059A04
FEBEF7FF 681A9B1F F7FF685B 2200FEA5 91059004 99079806 F7FF4B04 9E08FE9D
90069F09 E7779107 E000ED08 3FF00000 40100000
End CSUB

' The C this was compiled from, for reference. Regenerate with:
'     python mmb2csub.py julia.bas PlotJulia
'
'/* Generated by mmb2csub.py from ../examples/julia.bas, PlotJulia.
' * Do not edit - regenerate instead. */
'#include "mmcsub.h"
'
'
'static void f_plotjulia(MMFLOAT *p_w, MMFLOAT *p_h, MMFLOAT *p_xd, MMFLOAT *p_yd, MMFLOAT *p_rofs, MMFLOAT *p_iofs, MMFLOAT *p_cre, MMFLOAT *p_cim, MMFLOAT *p_mit, MMINTEGER *p_mp, const MMINTEGER *__b_mp) {
'    unsigned __mark = mm_mark(); (void)__mark;
'    MMFLOAT v_x = 0;
'    MMFLOAT v_y = 0;
'    MMFLOAT v_cx = 0;
'    MMFLOAT v_cy = 0;
'    MMFLOAT v_zr = 0;
'    MMFLOAT v_zi = 0;
'    MMFLOAT v_count = 0;
'    MMFLOAT v_new_zr = 0;
'    MMFLOAT v_new_zi = 0;
'    {
'        MMFLOAT __lim2 = ((((*p_w)) - (1.0)));
'        for (v_x = 0.0; v_x <= __lim2; v_x += 1) {
'            mm_poll();
'            v_cx = ((((v_x) * ((*p_xd)))) + ((*p_rofs)));
'            {
'                MMFLOAT __lim3 = ((((*p_h)) - (1.0)));
'                for (v_y = 0.0; v_y <= __lim3; v_y += 1) {
'                    mm_poll();
'                    v_cy = ((((v_y) * ((*p_yd)))) + ((*p_iofs)));
'                    v_zr = v_cx;
'                    v_zi = v_cy;
'                    v_count = 0.0;
'                    while (((((((v_count) <= ((*p_mit)))) & ((((((((v_zr) * (v_zr))) + (((v_zi) * (v_zi)))))) < (4LL)))))) != 0) {
'                        mm_poll();
'                        v_new_zr = ((((((v_zr) * (v_zr))) - (((v_zi) * (v_zi))))) + ((*p_cre)));
'                        v_new_zi = ((((((2.0) * (v_zr))) * (v_zi))) + ((*p_cim)));
'                        v_zr = v_new_zr;
'                        v_zi = v_new_zi;
'                        v_count = ((v_count) + (1.0));
'                    }
'                    mm_pixel(mm_toint(v_x), mm_toint(v_y), p_mp[(int)(mm_mod(mm_toint(v_count), 16LL))]);
'                }
'            }
'        }
'    }
'    mm_release(__mark);
'}
'
'/* CSUB entry - the interpreter hands us one pointer per argument. */
'long long PlotJulia(void *a0, void *a1, void *a2, void *a3, void *a4, void *a5, void *a6, void *a7, void *a8, void *a9)
'{
'    mm_scratch_reset();
'    f_plotjulia((MMFLOAT *)a0, (MMFLOAT *)a1, (MMFLOAT *)a2, (MMFLOAT *)a3, (MMFLOAT *)a4, (MMFLOAT *)a5, (MMFLOAT *)a6, (MMFLOAT *)a7, (MMFLOAT *)a8, (MMINTEGER *)a9, 0);
'    return 0;
'}

Edited 2026-09-17 23:14 by matherp
 
tom_g
Newbie

Joined: 21/02/2023
Location: Switzerland
Posts: 26
Posted: 10:49am 18 Sep 2026
Copy link to clipboard 
Print this post

Hi Peter,

this is a quantum leap for fast execution of MMBasic programs. Thank you indeed for the new Py tool !

I am not into Unix - as I understand, except for preemptive multitasking and for C programmers, what are the benefits of Unix in comparison to the latest Basic version ?

With my respect, best regards Thomas
 
Print this page


To reply to this topic, you need to log in.

The Back Shed's forum code is written, and hosted, in Australia.
© JAQ Software 2026