|
FVM is both a variant of the Forth Languag (target) and a Cross-Development Environment (host).
The goal is to provide a Forth-based environment, suitable for CPU, MPU or DSP with very little memory. FVM is currently ported to the MSP430 F1121, which has only 4K of code (Flash), and 256 bytes of RAM. I added also support Linux/x86 as target: no need for additional hardware, and convenient for prototyping some code.
This Forth-like language does not try to follow
any kind of
The Interactive Cross-Development environment is running on a host (PC running Linux or FreeBSD), communicating with the device running FVM using a kind of umbilical link (JTAG, serial, Ethernet, mapped memory, etc.). The development environment does not include a simulator, so the only way to use the compiler is to connect it to a real hardware (currently only the Texas-Instruments fet430x110 board is supported). I added recently some x86 support, so any PC running Linux could be used for prototyping (in this case, communication between the host and the ‘target’ –actually a linux process- is done through TCP/IP).
Although FVM could be easily ported to any CPU, MPU or DSP (16-bits model only for now), we have chosen to focus for now only on the Texas-Instrument MSP430.
In the following documentation, we’ll refer to:
-
Froth is the language running in the Target: very close to the
- FVM is the cross-development environment, running on the host (PC Linux or FreeBSD)
Froth is a Stack-Based langage, and use the next Stacks and Registers:
- a Parameter Stack: used to exchange data between definitions (a definition can be compared somewhat as a procedure or a function, but a variable, a constant are also a definition in Froth).
- a Return Stack: mainly used to save/restore call contexts.
- an Address Register (AREG), to reference memory.
- a pointer (IP) into the code stream, known as the “Interpret Pointer”.
The FVM cross-compiler generates addresses
codes that are interpreted at run-time by an optimized address
interpreter. This common
The code of the Inner Interpreter (e.g. the Address Interpreter) is here (MSP430) and also here (x86/Linux).
Code for arithmetic (16-bit) is here and also here (x86/Linux).
Code for stack management (16-bits) is here and also here (x86/Linux).
Although Froth is derived from the Forth language
and proudly assume its heritage, it is not really strictly speaking
-
the host cross-compiler (fvm-host)
is written entirely in C, and not in
- the compiler is always compiling into the target
* either an anonymous definition (un-named definition)
* or a named definition (colon definition)
- when ; (semi-colon) is parsed:
1. if this is an anonymous definition: the compiler either execute the code in the target, then 'forget' it
2. if this is a named definition, the cross-compiler just update his internal pointers ('compilation')
- a side-effect to this model is that in order to compile some code, the compiler must connect to a target - could be real hardware, or a simulator.
- memory access is managed trough an Address Register (see >A, A>, !, @): addresses are not explicitly provided on the stack but make use of the Address Register.
-
The build> does>
construction scheme is really different from the fig-forth model, and allow use
of local (automatic) variables, named field structures, and some
object-oriented functionalities (methods).
Note: only automatic variables are currently implemented for now.
- It is not allowed to redefine a definition.
- The compiler does stack-balancing checking (only the parameters stack for now, but the return stack should be done too soon). That should avoid stack underflow issues at run-time. A drawback is that the stack protocol should be the same for every branches of the control structures. For instance, the next definition : test IF 10 ENDIF + ; is not allowed in Froth, but the next definition will be : test IF 10 ELSE 20 ENDIF + ;
Download the full .tar.gz source archive, then use the Make. For instance:
~/temp$ wget -q -E
http://singla.us/cgi-bin/viewcvs.cgi/fvm430/fvm430.tar.gz?tarball=1
~/temp$ mv fvm430.tar.gz\?tarball\=1 fvm430.tar.gz
~/temp$ tar -z -f
fvm430.tar.gz -x
~/temp$ cd fvm430
~/temp/fvm430$ make
All binaries and DLLs are under fvm/bin, and you must define an environment variables named FVM_PATH:
~/temp/fvm430$ cd fvm/bin
~/temp/fvm430/fvm/bin$
export PATH=~/temp/fvm430/fvm/
Download then install the .rpm file Note that the rpm install the package under /var/fvm430. You may want to change permissions inside this directory, if you want to change or create some examples (under /var/fvm430/tests):
~/temp$ wget -q http://singla.us/rpms/fvm430-0.1-36.i386.rpm
~/temp$ su
Password:
/home/osingla/temp# rpm -qa | grep fvm430
fvm430-0.1-36
/home/osingla/temp# rpm -e fvm430-0.1-36
/home/osingla/temp# rpm -Uvh
fvm430-0.1-36.i386.rpm
Preparing...
########################################### [100%]
1:fvm430 ###########################################
[100%]
/home/osingla/temp# exit
~/temp$ cd /var/fvm430/bin/
/var/fvm430/bin$
You’ll need to connect your PC to the fet430x110 board. If you do not have such board, you can use the x86 forth target linux process (see “the easy way”): in this case, there is a process to start first (fvm86), which create a pseudo-target (x86).
Start the FVM development environment (fet430x110 board, see the .gdbinit-fet430x110 gdb startup script):
~$ cd /var/fvm430/bin/
/var/fvm430/bin/$ export FVM_PATH=/var/fvm430/
/var/fvm430/bin/$ ./fvm_host
--target-port=0 --profile="MSP430F1121A" \
--umbilical="fet430x110"
--editor="/usr/X11R6/bin/nedit" --goto-line-split
\
--goto-line-option="-line" --mass-erase
The target-port=0 options means the 1st parallel port (jtag connection). Both the profile and the umbilical options are implemented as DLL, so it’s critical to have the right path setup (through the environment variable FVM_PATH – although if it does not exist, the default is the one we have specified).
Sounds too complicated ? Then just start the next shell script:
~$ /var/fvm430/bin/start-fet430x110
If you do not have a fet430x110 board, you then can start the x86 target process (see the .gdbinit-86pclinux gdb startup script):
~$ cd /var/fvm430/bin/
/var/fvm430/bin/$ ./fvm86 &
/var/fvm430/bin/$ export FVM_PATH=/var/fvm430/
/var/fvm430/bin/$ ./fvm_host --target-port=localhost:8086 --profile="86pclinux" --umbilical="pclinux" --editor="/usr/X11R6/bin/nedit" --goto-line-split --goto-line-option="-line" --mass-erase --kernel=pc86/kernel.fvm --startup=pc86/startup.fvm
Note: you may want to customize the source editor that will be used to edit the sources (spawned by FVM). The shell script assume you have nedit in /usr/X11R6/bin.
Sounds too complicated ? Then just start the next shell script:
~$ /var/fvm430/bin/start-fvm86
If the host can connect to the target (either real hardware or x86 process-target), you should see something like this:
FVM v0.1.35 -
compiled on
Profile: 86pclinux
RAM: start=0200 end=A000 len=9DFF
Boot ROM:
start=0C00 end=1000 len=0400
Flash IM:
start=1000 end=1100 len=0100 Segments: sz=0080 nb=2
Flash PM:
start=1100 end=0000 len=EF00 Segments: sz=0200 nb=120
Anodef: start=0208
end=0216 len=000F
rstack: start=0256
end=0219 len=003E
pstack: start=0270
end=0257 len=001A
Switching from code
address 1100 to code address F800 .
Label next is F85B
at F89B ...
Starting Kernel...
Kernel started...
........................................................
Label next is F85B
at FC0F ................
Switching back from
code address FCB0 to code address 1100 ......
Target: 82 words
anodef0=0208 anodef=0208 - cp0=1100 cp=11B2 - dp=0270
areg=0270
Stack empty
Current stack
effect: parameters:0, 0 return:0, 0
Current number of
CPU cycles:0
Use command 'help'
to get some help!
Froth is *always*
compiling: use ; (semicolon) to execute!
(0)
Use command 'help' to get some help!
(0)
(0) shows the number of data currently available on the Parameters Stack of the Target. Enter something like:
(0) 10 30 +
(0)
Basically you asked the cross-compiler to generate some bytecode into the target to perform the next operations:
- push the literal value (decimal) 10 on the Parameter Stack
- push the literal value (decimal) 30 on the Parameter Stack
- pop the 2 top values from the stack, add them, and push back the result
Why we still see (0) ?
We should see (1), meaning that there is a value on top of the Parameters Stack
into the Target!
That’s because, until we ask the anonymous definition to be executed into the Target, the cross-compiler just generate bytecode into the Target. We can see the bytecode generated for the Anonymous Definition by entering this command:
(0) ??
Then we should see something like this:
(0) 10 30 +
(0) ??
Target: 109 words
anodef0=0208 anodef=0210 - cp0=F000 cp=F0F2 - dp=0270 areg=0270
Stack empty
Current stack effect: parameters:0,+1 return:0, 0
0208: F962 (lit32) 1966090
020E: FA2E +
(0)
Note: you should normally see (lit16) 10 (lit16) 30, but actually as the optimizer was active when I typed this, the compiled choose to compile the two 16-bits literals as one 32-bits literal.
The command ?? is very useful to see the current anonymous definition compiled into the target. Now let’s run this anonymous definition, using the semi-colon definition:
(0) ;
(1)
This is what we expected to see: the (1) means that there is a value available on top of the Parameters Stack into target. The command ?? could be used to show the Stack into the target. We can use also the definition . (dot) that pop the top value from the stack and display it:
(1) . 40
(0)
Creating a Named Definition is not very different from creating an Anonymous Definition: the first step is obviously to declare a name for the new definition. This is done using the colon definition (:). As a simple example, let?s create a definition that expects 2 integer values (A and B) from the Parameters Stack, and then return (A+B/2:
(0) reset-optimize
(0) : (3A+5B)/2 5 * swap 3 * + 2 / ;
(0)
The definition name can contains any symbol different from the space (as the space is used as symbol separator). Also, the new definition must neither be a number (for instance :10 is not allowed), or an existent definition. We have used also set-tokenize, to ask the compiler to generate a bytecoded definition (as there is a limited room for bytescodes in a 8-bits VM implementation, not all definitions can be tokenized).
We can list the current definitions defined into the Target using the host command */words/*:
(0) words
(0)
the host word decompcan be used to decompile a given definition:
(0) decomp (3A+5B)/2
Code: start=F0F2 end=F10C
stack_effect=2,-1 rstack_effect=0, 0
F0F2: F8BE (:)
F0F4: F8CA (lit) 5
F0F8: FA86 *
F0FA: F8E4 swap
F0FC: F8CA (lit) 3
F100: FA86 *
F102: FA2E +
F104: F8CA (lit) 2
F108: FAAE /
F10A: F8C4 (;)
(0) 50 20 (3A+5B)/2
(0) ??
Target: 110 words
anodef0=0208 anodef=0212 - cp0=F000 cp=F10C - dp=0270 areg=0270
Stack empty
Current stack effect: parameters:0,+1 return:0, 0
0208: F8CA (lit) 50
020C: F8CA (lit) 20
0210: F0F2 (3A+5B)/2
(0) ;
(2) .s
Stack (2): 250 2
(2) whatis (3A+5B)/2
User 'colon' definition: start=F0F2 end=F10C Parameters:2,-1 Return:0, 0
(2)
One of the most interesting feature of Forth is the power of the BUILD>..DOES>.. construction. Basically, using these two definitions, you can create a definition that will create other definitions. Therefore, you’ll have to define 2 things:
- what will be performed at compile-time (e.g. when new definition is created)
- what will be performed at run-time (e.g. when new definition will be executed).
There is a 3rd definition that is always used with BUILD> DOES>, which is ALLOT: used to allocate memory.
Let’s take a 1st very simple example. We’ll create a definition called CONST which can be used to create new literal constant values (16-bits). Note that FVM offers a similar built-in word (CONSTANT), which is doing the very same thing. We’ll use CONST to demonstrate the purpose and use of BUILD> DOES>.
: const
build> 1 allot !
does> @ ;
10 const ten
100 const cent
: test ten cent + ;
(0) test ;
(1) . 110
(0)
- After build> we define what will be executed when the definition will be created (usually it’s compilation-time, but with FVM, that’s not always true!). We do reserve one cell of memory (one cell is 16-bits, depending the hardware supported by the compiler): 1 allot. Note that allot assigns the value of the Address Register to the new allocated memory [the Address Register is not Forth standard and used by definitions such @ (fetch) and ! (store)]. Then we use ! (store) which save in memory the value given when const is called.
- After does> we define what will be executed when the definition will be called. It’s very important to know that the Address Register is then set to the strarting address of the memory which has been allocated. Therefore in our example, we have just to read (@) to get back the value.
Here is another example: we’ll create a definition called INT (for Integer) that can be used to create 16-bits integer variables:
: int
build> 1 allot
does> ;
int x
int y
: test1 10 x ! 100 y ! x @ y @ + ;
: test2 int a int b 5 a ! 7 b ! a @ b @ + ;
(0) test1 ;
(1) . 110
(0) test2 ;
(1) .
(0) 12
A more complex example: we’ll create a definition called DATE, that can be used to hold date information. We have somewhat extended the BUILD> DOES> concept to support named fields and some pretty limited object oriented functionalities:
: date-weekday ( AREG> day month year )
blah blah ... ;
: date
build>
3 allot (day month year --- day month year)
rot (day month year --- month year day)
!+ (month year day --- month year)
swap (month year --- year month)
!+ (year month --- year)
!
does>
@+ @+ @ ( --- day month year)
fields>
field: day offset: 0
field: month offset: 1
field: year” offset: 2
methods>
method: weekday is: date-weekday
;
14 1 1789 date french-revolution
23 5 1961 date my-birthday
: test1 my-birthday.year french-revolution.year - ;
(0) test1 ;
(1) . 172
(0) my-birthday.weekday ;
Currently, FVM is provided either as source file (tar.gz file, under CVS, you then will have to compile the things), or as a RPM (binaries, DLLs, compiler files, examples).
To compile the application, first read the file readme.1st, but basically, just run the Makefile (gnu make) at the top directory. The compiler, and all the DLLs will then be built. The MSP430 kernel is also built, I wrote a complete MSP430 assembler, therefore all the low-level definitions written in assembler are defined using CODE..ENDCODE. MSP430 use several DLLs: I have chosen to ‘externalize’ several components, such the umbilical communication –serial, tcpip, jtag-, the assembler and the hardware profile. The whole idea is to have a stable core cross-compiler, that can be extended to handle different targets, even with a different type of CPU, by just providing several DLLs on the command-line.
I compiled and tested FVM under Linux (RedHat 8.0, 9.0, Fedora and also Mandrake), and also under FreeBSD (v4.3, v5.0). I did not tested it under FreeBSD since many months, so it will likely require some modifications (likely for the // interface).
Although not mandatory, the ctags and cproto utilities are used by the Makefiles if present (in /usr/local/bin). Actually, I do believe that cproto could be mandatory (if you download the code using CVS, I do not include under CVS all the prototypes files, as they can be built using cproto: make protos).
You will need glib2 : I use it for several features (such managing binary trees –for the symbols management- and tab completion). You will need also curses.
What do you need to run and test the cross-compiler suite ?
- either you have the fet430x110 board (the fet430x110 cost only $49): the interactive cross-compiler must then be connected to it (// cable, jtag)
- if not, you can use the fvm86 target process: this standalone Linux process allocate 64K of memory, mmap to address 0, and with then listen to request coming from the host FVM compiler (TCP port 8086): read/write memory, execute memory. This is a cheap emulator, but it works fine, and allow to prototype any code which is not tied to the MSP430 architecture. Note that I did not tried to make any optimisations, for instance the inner forth interpreter (next) is really horrible… Also, I have created some definitions that give access to the linux kernel calls (sys0, sys1, etc.), see linux.fvm
To see how to start the cross-compiler, check here
C Sources |
svn co
https://fvm430.svn.sourceforge.net/svnroot/fvm430 fvm430 |
Anonymous SVN access |
This software is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
Please take a look at the GNU Web Pages for a copy of the GPL license: http://www.gnu.org/licenses/gpl
|