home *** CD-ROM | disk | FTP | other *** search
- WRITING ROMABLE CODE IN TURBO C:
-
- Updated 2/2/89 for Turbo C 2.0 by the author.
-
- I recently completed a consulting project to design a
- special-purpose controller using an embedded 8088 with firmware
- in ROM. To create the firmware, I developed a scheme for
- generating ROMable code from Turbo C.
-
- The scheme utilizes the following:
-
- 1. Rules for writing C code.
-
- 2. ROMIZE.C, a program to post-process the Turbo C Compiler
- assembler output.
-
- 3. INSJUMP.C, a program to patch the ROM binary code to
- insert a restart jump instruction.
-
- 4. BINTOHEX.C, a program to produce Intel-format files for
- downloading ROM images to an EPROM programmer.
-
- 5. C0ROMX.ASM, a replacement for C0.ASM, the startup code.
-
- In addition, the scheme requires the Turboc C command-line
- compiler, TCC.EXE; an assembler, Microsoft Version 3.0 or later,
- or equivalent (I use Microsoft V5.0); TLINK or MS-Link; and
- EXE2BIN (more about EXE2BIN later).
-
- The scheme involves the following steps:
-
- 1. C code is written following a few rules.
-
- 2. TCC is used to translate the C code to assembly.
-
- 3. ROMIZE is used to perform certain modifications to the
- assembly code.
-
- 4. The modified assembly code is assembled into a .OBJ file.
-
- 5. The .OBJ file is linked with the C0ROMX.OBJ startup code
- and with other .OBJ files (if needed; these may be from C code
- that has gone through the four above steps, or routines
- originally coded in assembly). This produces a .EXE file.
-
- 6. EXE2BIN converts the .EXE into a binary ROM image.
-
- 7. INSJUMP inserts the reset jump into the ROM image.
-
- 8. If necessary, the binary ROM image is converted into
- whatever format the particular EPROM programmer requires. (Mine
- is a plug-in card for the PC which takes binary format directly,
- so I don't have to do this step.)
-
- All these steps can be run in a .BAT or MAKE file, making the
- conversion of the original C code to ROM code almost automatic.
-
- RULES AND ASSUMPTIONS:
-
- The rules and assumptions for writing ROMable code using this
- scheme are as follows:
-
- 1. The Compact memory model (small code, large data) is used.
- This results in a maximum of 64K of code and initialized static
- variables (i.e. constants) but up to 1 MB of Data space.
-
- 2. Initialized static variables are considered constants, and
- may not be changed during program execution because they are
- stored in ROM.
-
- 3. Variables defined as "extern" are considered constants.
- Such variables would be used for font tables, for example. True
- "variables" (which change during program execution) may not be
- defined in one C module and declared as "extern" in another. The
- only means of inter-module variable passing is through function
- parameters and return values (a good programming practice
- anyway). IT IS OK to define variables in a C module and reference
- these as EXTRN's in assembler-coded modules, however.
-
- 4. Most of the Runtime Library routines may NOT be used,
- because they have been compiled with the wrong segment groupings
- for the ROM environment. Many of the library routines are not
- usable, anyway, because they pertain to the DOS environment and
- use system facilities that are not available in the ROM
- environment. Library routines that are needed will have to be
- recreated, either in C or assembly. You may want to order the
- Turbo C Runtime Library from Borland as a starting point. I
- ordered the Runtime Library several months into the project that
- stimulated the creation of this system, but I didn't really need
- it.
-
- I was able to compile and debug over 90% of my code (which
- was about 3,500 lines of C) using the Integrated Environment on a
- PC, then re-compile (with #DEFINES and #IFDEFs to accommodate the
- environmental differences) for ROM with little further debugging.
-
- SEGMENT USAGE IN TURBO C:
-
- Turbo C assigns uninitialized static variables to a segment
- called _BSS and initialized static variables (i.e., constants) to
- a segment called _DATA. These two segments are associated via a
- GROUP directive, and assumed, under normal circumstances, to be
- jointly addressable by the DS register. Instructions are
- generated in the _TEXT segment, and assumed addressable by the CS
- register.
-
- In the ROM environment, the constants in the _DATA segment
- and the code in the _TEXT segment are both located in the ROM and
- addressed by CS. Only BSS is addressed by DS. Therefore to
- convert the code generated by the compiler, ROMIZE is used as
- follows:
-
- WHAT ROMIZE DOES:
-
- ROMIZE makes two passes through the assembly source code
- generated by the command line compiler. In the first pass, it
- reads the code and accumulates a table of all symbols defined in
- the _DATA segment. There are only a few such symbols, since
- Turbo C places all string constants in a single symbol "s@", and
- refers to individual strings as "s@+n". Likewise, all
- non-string constants are part of a single symbol called "d@" and
- referred to by their displacements. There are however, a large
- number of references to these symbols in the code.
-
- In its second pass, ROMIZE re-reads the source code and
- writes it out with modifications. The modifications are:
-
- 1. The command that groups _BSS and DATA together into DGROUP
- is changed so that only _BSS is in DGROUP.
-
- 2. A new GROUP command is inserted creating CGROUP as _TEXT
- and _DATA.
-
- 3. The ASSUME statement is changed from
- ASSUME cs:_TEXT,ds:DGROUP
- to:
- ASSUME cs:CGROUP,ds:DGROUP
-
- 4. In any line of code with a reference to a symbol in the
- _DATA segment, the group reference is changed from DGROUP to
- CGROUP, or a CGROUP reference is added if no group name is
- present.
-
- 5. Preceding a line of code with a reference to a symbol in
- the _DATA segment may be a "mov x,ds" or a "push ds"instruction.
- This is changed to a "mov x,cs" or "push cs".
-
- 6. Following a line of code with a reference to a symbol in
- the _DATA segment may be a "push ds" instruction. This is changed
- to a "push cs".
-
- In order to do the looking backward and forward, ROMIZE
- maintains a three-line pipeline, reading new lines into the head
- of the pipe and writing lines from the end.
-
- The various segment naming options of the compiler do not
- produce the desired changes for the ROM environment. I would be
- happy to pursue this subject further with anyone who is curious
- about it.
-
- LINKING:
-
- You can use either TLINK or MS-LINK. The command line will,
- of course be different for each. The "Warning - no stack segment"
- will be produced by either. This is normal.
-
- You must read the link map carefully to verify that your code
- fit in the size of ROM you use. Consider the following partial
- link map:
-
- LINK : warning L4021: no stack segment
-
- Start Stop Length Name Class
- 00000H 05A74H 05A75H _TEXT CODE
- 05A80H 07DF7H 02378H _DATA DATA
- 07DF8H 0A530H 02739H _BSS BSS
- 0A531H 0A531H 00000H _BSSEND BSSEND
-
- Origin Group
- 0000:0 CGROUP
- 07DF:0 DGROUP
-
- The .bin file that came out of EXE2BIN for the module that
- produced this link map was A531 (hex) bytes long, however, this
- module does fit in 32K. The key indicator is the sum of the
- lengths of _TEXT and _DATA, and consequently, the starting
- address of DGROUP (_BSS). Here it is 7DF8, about 500 bytes
- under 32K.
-
- MORE ABOUT EXE2BIN:
-
- EXE2BIN is needed to convert the .EXE file into a binary
- image that can be transmitted to an EPROM programmer. It may need
- to perform "segment fixups" (segment relocation), if your program
- includes constructs like the following:
-
- static char *menu[] = {"Preview ","Add Before","Add After "};
-
- This is a convenient way to generate arrays of strings for
- such things as menus. This generates an array of pointers whose
- segment parts must be relocated by EXE2BIN to the segment address
- of the ROM. Therefore, when the EXE2BIN step is run, you will be
- prompted to enter a segment address (F800 hex in the attached
- example).
-
- For those users of versions of DOS (such as 3.3) that do not
- include EXE2BIN, there are several alternatives: Use EXE2BIN from
- DOS 2.0 (I think 2.1 will work also), or use EXE2BIN from DOS 3.0
- or 3.1 and patch it so it does not test for DOS version. The test
- is at the beginning of these programs and is easily defeated.
- Except for the version check, I know of no DOS version
- dependencies in these programs. (I did all my development under
- DOS 3.2, however, which still includes EXE2BIN).
-
- The EXE2COM program available on CompuServe and many bulletin
- boards may work in place of EXE2BIN (I have not tested it),
- EXCEPT when segment relocation is required (i.e., you use the
- above-described construct). It does not do segment relocation.
-
- The fact that EXE2BIN stops and prompts you for a segment
- relocation factor is an annoyance. You can use the PC Magazine
- KEY-FAKE.COM (see PCMagnet) to supply the required value in a
- batch or MAKE file. If KEY-FAKE is used in a MAKE file, you must
- have invoked KEY-FAKE at least once (without arguments is fine)
- before running MAKE. KEY-FAKE installs as a TSR the first time
- it is invoked, and apparently, Turbo MAKE doesn't like programs
- that do this, so if you invoke KEY-FAKE in a MAKE file
- without it already being resident Turbo MAKE terminates with an
- error 768.
-
- MORE HINTS:
-
- If you declare an array of strings as mentioned in the above
- discussion, the "static" storage class is not necessary if the
- array is defined outside of any function. If such an array is
- defined as local to (within) a function, then be certain to add
- "static", otherwise, Turbo C generates code to copy the array
- into the function's local variable space (the stack). This is
- unnecessary if the array is indeed a constant.
-
- Although you won't be using many (any) library functions, you
- may still want to include a number of standard header files.
- The library functions you recreate ought to use the same
- definitions as the library versions, anyway.
-
- "dos.h" is an especially useful item. Although chances are
- you won't be using any of the file I/O functions defined in it,
- it includes some macros and in-line functions that are EXTREMELY
- useful in the ROM environment such as MK_FP(), inport(),
- outport(), FP_SEG(), FP_OFF(), peek() and poke().
-
- FLOATING POINT LIMITATIONS:
-
- If you need to use floating point arithmetic, you may have a
- problem. If your target hardware includes a numeric co-processor
- then you will have to order the Runtime Library and modify the
- 8x87 support routines to run in the ROM environment. If you don't
- have an 8x87 and still want to use floating point, you can't
- because the Runtime Library does not include all of the floating
- point emulator source code.
-
- SAMPLE PROGRAMS:
-
- A simple C program which reads a value from an input port
- and writes a selected string to an output port is included. A
- Turbo Make file is provided to create the ROM from this code,
- which illustrates all of the necessary processing steps.
-
- STARTUP CODE -- C0ROMX.ASM:
-
- This code is simple, compared to the regular Turbo C startup
- code. About all that is required is to set up the DS, SS, and SP
- registers. The particular values for these will depend upon the
- amount and address space of your RAM. The code has an optional
- sequence to initialize static variables to 0, as is "normal" in
- C. I did not use such initialization because my particular
- application had battery-backed-up static RAM. Note that
- C0ROMX.ASM does not have a starting address in its END statement.
- The actual code startup is supplied by a JMP inserted by INSJUMP.
- Note also that the linking step specifies C0ROMX as the first
- object module. That guarantees it will start at relative address
- 0000 in the ROM.
-
- INSJUMP.C:
-
- The 8088 family processors start up after a RESET by
- executing code beginning at FFFF:0. INSJUMP inserts an inter-
- segment jump to relative location 0 at the ROM location that
- corresponds to FFFF:0. INSJUMP can process 32K (i.e. 27256) or
- 64K (27512) EPROMs. It is used as follows:
-
- INSJUMP filename [64K]
-
- If the second parameter "64K" is not present, INSJUMP assumes it
- is processing a 32K ROM. The JMP instruction is to F800:0, and
- it is inserted at location 7FF0 in the ROM. For a 64K ROM, the
- instruction is a JMP F000:0 inserted at ROM location FFF0.
-
- The .bin file generated by EXE2BIN may be less than 32K
- for a 32K ROM, or less than 64K, for a 64K ROM. INSJUMP
- extends the file out to 32K or 64K with FF's. Erased EPROMs
- contain all FF's, so unused locations will not have to be
- programmed, which will shorten programming time on most
- programmers.
-
- As supplied, INSJUMP also calculates a checksum and places
- it in the last two ROM bytes. My product has a self-test that
- includes ROM checksum on power-up. Other users may want to put
- additional data, such as a part number, in the ROM. This can be
- done with INSJUMP.
-
- WRITING ASSEMBLY-LANGUAGE FUNCTIONS:
-
- The only differences in writing assembly-language functions
- for the ROM environment versus DOS are segment groupings. The
- Turbo C User's guide suggests compiling a small C module to
- assembler as a starting point. For the ROM environment do the
- same thing, then process the assembler through ROMIZE.
-
- If your assembler functions have only a code segment called
- _TEXT, with no other segment definitions, and if they reference
- only local variables and parameters stored on the stack, then you
- can assemble them just once and use the identical .OBJ modules in
- both ROM and DOS versions of your main C program.
-
- BINTOHEX.C:
-
- This program converts a binary ROM image to Intel Hex format.
- It is has not been tested. If you need some other format, such
- as Data I/O, contact me.
-
-
- This has been a simple treatment of a complicated subject.
- I have tried to include as much information as I could think of,
- but there are undoubtedly a lot of pitfalls I haven't covered.
- Anyone who wishes to pursue this subject further is urged to
- contact me:
-
- Bob Haas
- P. O. Box 1455
- Tualatin, OR 97062-9557
- (503) 692-1593
- CompuServe User # 70357,3530