home *** CD-ROM | disk | FTP | other *** search
-
-
-
-
-
-
-
- Notes on Turbo Modula-2
-
- by Steven Hirsch
-
- Original release: 01/25/87
-
- Intro:
-
- My company, a contractor in the Audio-visual and communications
- field, had undertaken a project for a large industrial client
- which would involve micro-computer based scheduling and control
- of video recorders. In late September we made the decision on a
- Z80 CP/M environment, and had chosen Modula-2 as the development
- language.
-
- We began with FTL Modula-2 from Workman & Assoc., as it was the
- only native-code compiler available at that time. By late
- October, panic was starting to set in. I had uncovered many bugs
- in the compiler/linker implementation and, despite Workman's
- polite concern, no fixes were ever brought to this programmer's
- attention. I was on the verge of re-writing the program (some
- 5,000 lines+ at this point..) in Pascal MT+ when the impending
- release of Borland's TM-2 was announced. Echelon, Inc., when
- they learned of my predicament, agreed to help out; shipping me a
- copy of the software, with a 3" stack of galley-proof sheets for
- documentation.
-
- I planned on moving into the computer room for an extended stay,
- figuring that the conversion process would be painful... It's
- nice to be wrong once in a while. The conversion to TM-2 was
- accomplished in one Saturday afternoon, with the largest effort
- being the implementation of 'SET OF CHAR'. FTL supports sets with
- up to 1024 members, while TM2 has the more traditional (for
- micro's) limitation of 16!
-
- To make a long story short, we delivered the controller on-
- schedule. The software has been absolutely bullet-proof (so
- far..), and the customer is delighted! In the course of this
- ordeal, I found myself being the first individual in the field
- to:
-
- a) Develop a complete product in TM-2.
- b) Use the assembler interface heavily.
- c) Use interrupt driven task-switching.
- d) Decipher the manual..
-
- The assember interface, in particular, seems to be causing a
- great deal of confusion in the user's community. I therefore am
- writing this (somewhat rambling) tome to help those wrestling
- with the compiler as we speak!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Assembly Language Modules
-
- As mentioned in the manual, TM-2 will allow one to create modules
- written in Z80 assembly language. I will limit this discussion to
- the technique of converting .REL files to .MCD, as the 'CODE'
- method (p.194 of the manual) has some fatal constraints (though
- it contains some valuable hints on parameter-passing!).
-
- The use of an assembler capable of generating a Microsoft-format
- REL file is essential (I use the SLR Systems Z80ASM tool, and
- have found it to be the best of the genre. Be sure to specify the
- /6 or /7 switch when using this program!).
-
- In certain sections of the low-level I/O code, I was able to
- cross-assemble 6502 code using the Microsoft ALDS assembler,
- which digests 8080, Z80, and 6502 mnemonics - producing a
- standard .REL file. The target environment is a dual-processor
- system, and some of the I/O primitives needed to be handled by
- the 6502 host... Intricacies of the control project will be dealt
- with in a future article, to be published (I hope..) by one of
- the hacker-oriented mags.
-
- Parameters are passed to the assembly routine on the stack,
- pushed on in left-to-right sequence (as they appear in the
- DEFINITION module). If the parameter is passed by value, (ie. the
- called procedure is not able to return a value or affect the
- parameter..) and is word-sized, it is placed on directly. Things
- are thus straight-forward for CARDINAL, INTEGER, ADDRESS, WORD,
- and CHAR types. In the case of BYTE or CHAR, the most signifigant
- byte of the word will be cleared to zero. Note that ARRAY[0..1]
- OF CHAR, although two bytes in length, will not be treated this
- way! It must be noted that I have not worked at all with floating
- point, or longs. A general technique for determination of the
- passing method will be described later.
-
- Any parameter passed by reference (called procedure able to
- affect it's value..) gets a one-word pointer to itself placed on
- the stack. This applies across the board to all types, although
- open array types are passed with additional information, as we'll
- see.
-
- Enumerated types are passed by their ORD value (word length!)
- within the enumeration, ie. if we have this situation:
-
- TYPE
- Junk = (foo, bar, other, things);
-
- And the procedure:
- PROCEDURE DoSomething( formalparm : Junk );
- exists.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- DoSomething(bar);
- Will cause the word 01h to be placed on the stack!
-
- Following the above trend:
- DoSomething(foo);
-
- would generate the value 0h, etc.
-
- To reiterate, and clarify things, the procedure:
- PROCEDURE DoSomething( VAR formalparm: Junk );
-
- Is called from this situation:
-
- VAR
- stuff : Junk;
-
- BEGIN
- stuff := foo;
- DoSomething(stuff);
-
- The DoSomething routine will find a one-word pointer to the
- location of the variable 'stuff' pushed on the stack. At that
- location will be the word-value '0000h'.
-
- Structured types such as ARRAY, RECORD, etc. are always passed
- via pointer. I believe that, when passed by value, the compiler
- creates a 'copy' of the variable prior to the call and passes a
- pointer to it; although I am not 100% sure of this fact.
- Responsibility for knowing the physical size and internal
- structure of the referenced object lies completely on the
- programmer, when dealing with assembler modules. The manual
- outlines the storage sizes of the various types, although the
- information is scattered across several sections.
-
- The exact method of declaration is extremely important when
- dealing with ARRAY types; a declaration of the form:
-
- PROCEDURE WorkWithString( VAR formalparm : ARRAY OF CHAR );
-
- will be called with an extra parameter on the stack. First a
- pointer to the array is pushed on, followed by a word value
- representing the index (normal to zero) of the last element in
- the array. Note that the programmer is responsible for
- translating this index into a relative byte-offset. If one is
- dealing with ARRAY OF WORD, it is necessary to double the index.
- I believe that the same method is used for multi-dimensional open
- array's, although this programmer has not investigated it.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- If a declaration specifies the size of the array, the extra
- parameter is not present - only the address is passed. Be very
- careful when passing an odd-length text string (or ARRAY OF
- BYTE!) to a procedure expecting ARRAY OF WORD. The compiler will
- round up it's idea of the string-length to the next word boundry,
- which may cause your assembly code to step on an adjacent, and
- unrelated, variable!
-
- After all parameters are placed on the stack, the return address
- is pushed on over them. The actual 'call' to the routine is
- performed by a 'JP (HL)' instruction, which means that 'HL'
- contains a pointer to the start of your assembly routine upon
- entry. This correlates to the explanation of 'CODE'! Although
- your assembly code need not preserve any registers, the return
- address must be kept track of.
-
- My favorite method for dealing with parameters and return address
- is outlined in the accompanying file 'CLOCKIO.MAC'. Along with
- it's .DEF file, this is the low-level module used to communicate
- with the real-time clock in our controller. The RTC talks to us
- via a PIO chip, which is memory mapped into the Z80's address
- space. Without getting sidetracked into the hardware, this code
- should serve as an outline for interested programmers. It is
- called with two strings variables as parameters, returning with
- time and date loaded into them as ASCII strings (MM/DD/YR, and
- HH:MM). Note that we are not making use of the length index, as
- it is assumed to be a known constant - this does not represent
- the worlds greatest programming (Blush..), and the module ought
- to trap this situation.
-
- All procedure entry points are defined as public symbols. I
- prefer the '::' method, but 'PUBLIC symbol1, symbol2, etc.' may
- suit some folk's style better. One difficulty arises, in that
- there is no apparent way to cause the 'module body' intialization
- code (in our case the clock locator) to be executed before pas-
- sing control to the main program. I solved this by creating a
- 'dummy' module (in standard non-assembler Modula) containing only
- the call to 'INITCLK' in it's body, thus guaranteeing that
- 'INITCLK' is entered prior to any read-time calls. If the clock
- is not found, a message is displayed on the screen, and the
- program terminates to CP/M.
-
- If the assembler routine has been defined as a function
- procedure, it's value (which **must** be a non-structured type,
- of one-word length!) is pushed onto the stack before executing
- the return.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- The easiest way to get a feel for what is passed to a procedure,
- and how, is to write a dummy assembler module which pops a number
- of words off the stack, stores them in a known 'safe' area of
- RAM, and terminates with a RST 38H. The assembly code is linked
- together with an M-code module, which feeds it known parameters.
- The .COM file is executed under a debugger (I used the excellent
- Z8E tool, which is available in the public domain). When the RST
- break-point is hit, one may inspect the assigned storage area to
- find out what's what. DO NOT make the mistake of immediately
- terminating with a RST and inspecting the stack with the
- debugger, as it's stack becomes intertwined with the program
- being handled, causing endless confusion.
-
- No discussion of the assembler interface is complete without
- mentioning the fact that the REL.MCD utility requires GOBS of TPA
- to run, and will not successfully link to a .COM file either! A
- standard Z3 system does not have the TPA to convert even the
- smallest .REL file. I was forced to re-boot the computer under
- vanilla CP/M (what's that?), in order to perform this function.
- Borland has been notified of this anomoly, but I wouldn't hold
- one's breath for a solution.
-
- Some other miscellaneous points:
-
- - One must limit the name of any assembler routines to seven
- characters or less (public symbol limitation of the Microsoft
- assemblers!), or REL.MCD cannot figure out where to assign entry
- points.
-
- - Case does not seem to be an issue. I defined all entry points
- in upper case, in the assembly code, and in mixed case in the
- definition module. No problems occured, although if you have a
- procedure Foo and FOO in the same program it may get confused!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Interrupt-driven Task Switching
-
- This was another venture into the great unknown of incomplete
- documentation. After digesting some half-dozen books on Z80
- assembly language, and at least that many on Modula-2, I
- discovered an incredible dearth of hard information on
- interrupts. Most texts on assembly coding gloss over it, or
- ignore it completely - while the Modula texts sort of mention
- that it 'can be done but you really don't need to know about
- it'. After many days of frustration, during which I became
- intimately aquainted with the reset button on the target machine,
- I figured out how it 'can be done'.
-
- The Z80 has three modes of interrupt response, IM0, IM1, and IM2.
- On reset, the CPU comes up with non-maskable interrupts disabled,
- and in mode 0. Your BIOS may have other ideas on the mode, so be
- **careful** with any assumptions here! In the case of our target
- machine, the BIOS made no use of interrupts whatever, and
- executed a DI instruction (no interrupts) on warm-boot.
-
- Z80 mode 0, and mode 2, expect a portion of the interrupt vector
- to appear on the data buss, generally placed there by the
- interrupting device. Our clock had no means of doing so, and
- these methods were subsequently ruled out. Mode 1 seemed to fit
- the bill; it causes a transfer to the vector at 38h (RST 7),
- pushing the address of the next pending instruction on the stack
- before doing so (no information is needed from the data buss).
- The TM2 manual points out that the programmer is responsible for
- setting up the correct interrupt mode, and indicates that the
- IOTRANSFER procedure will initialize the interrupt vector.
- Unfortunately, it does not bother to mention what interrupt mode
- it is assuming!
-
- A bit of investigation disclosed that IOTRANSFER places a one-
- word vector to the interrupt code at the address specified in
- it's third calling parameter (see the manual!). That's all, just
- the address, no jump instruction. This seemed to imply that TM2
- was designed to operate with Mode 2, using vectored interrupts.
- After some head-scratching, I realized that it would be possible
- to create a short section of code ORG'ed at 38h, then bias the
- vector passed to IOTRANSFER, causing it to patch the interrupt
- address into a 'JP' (or 'CALL') instruction in the stub. This
- would then enable the use of Mode 1 interrupts. The files
- INTHANDL (.def and .mod), and MAIN.MOD illustrate one method for
- doing this, along with a suggested method for setting up an
- interrupt-driven co-routine. Also, some handy primitives appear
- in the CLOCKIO.MAC listing.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Once the IOTRANSFER vector has been established, clock interrupts
- cause immediate transfer of control to the instruction following
- the IOTRANSFER statement. TM2 saves all registers and, more
- critically, resets it's stack and heap to opposite ends of the
- 'workspace' established for it. Any references to static objects
- (at the module level) will be valid, and any Modula procedures
- may be re-entered at will if they rely upon local parameters
- (remember you will permanently affect statics!). The function
- DISPOSE will operate properly for dynamic variables in the non-
- interrupt heap, however NEW will grab it's space from the
- (probably limited!) interrupt heap. The previous fact will hasten
- a fatal stack collision, if not minded carefully!
-
- Although TM2 routines may be re-entered, under the defined
- conditions, CP/M cannot. As an interrupt may occur from within
- the operating system, one must learn to think of the o/s as a
- sleeping giant - not to be disturbed. It is assumed that any
- time-sensitive BIOS code would lock out interrupts completely. In
- our controller, we used our own handlers to 'talk' to the
- hardware peripherals, avoiding the o/s completely! Just remember
- that the foreground task needs to resume from the interrupt as if
- nothing had occured. Anything that does occur to static data (or
- peripheral devices) must be thought through carefully, or
- unexpected side-effects will manifest themselves.
-
- Delightfully, TM2 takes care of all housekeeping with regard to
- which modules may receive interrupts. Generally the main module
- will not be a 'monitor', ie. it will have interrupts enabled.
- This is the normal state for a TM2 module. If one desires to lock
- out interrupts, the IMPLEMENTATION module is declared like this:
-
- IMPLEMENTATION MODULE NoInterruptsAllowed[n];
-
- Any postive integer between 1 and 9 may be used, all will have
- the effect of locking out interrupts within the module. Any non-
- monitor modules called from the above module inherit it's
- interrupt status. Unless your interrupt scheme is re-entrant, it
- is suggested that the interrupt handler loop be in a module which
- declares as a monitor. Naturally, one may use the Z80EI and Z80DI
- routines in my CLOCKIO code. Any mayhem created is then your
- responsibility!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Other Subjects!
-
- Version control can easily become a headache in a large project.
- One is always able to remember the chronological sequence of
- compilation when only a few modules are in existance. My project
- grew to 30+ modules, and tracking the effects of definition
- module changes became a nightmare. This deserves a whole tirade
- to itself:
-
- For the past year and a half I have made extensive use of Plu-
- Perfect software's DateStamper program. It is one of those
- utilities that, once you use it, you can't believe you ever got
- along without it. Theoretically one could simply look for and
- re-compile all modules in a given project which post-dated the
- modified definition module (being sure to note it's date-stamp
- prior to changing it!). Wrong. For reasons which are still under
- investigation, TM2 breaks so many rules of civilized CP/M
- behavior that the DateStamper becomes absolutely unreliable. From
- what I have been able to determine, the editor (at least) will
- open a file with a specific FCB referenced, and then close it
- with a reference to an FCB in a different location. Whether it
- moves the FCB, copies it, or whatever, is unknown. This causes no
- problems with CP/M, however DateStamper is unable to track this
- sort of thing, and files will not reliably get their 'modify'
- stamp updated! This leads to files with blank modify fields,
- although they have a create date (for the most part, more later).
-
- A little digression is necessary here. I do most of my
- development programming on a one-meg ramdisk, moving all the work
- files (and the compiler) to it on startup. Periodically during a
- day's work, my usual habit is to run DateSweep, selecting for all
- files modified that day, and copy them to a back-up diskette.
- DateSweep will only copy a file if the target disk either doesn't
- contain that file or, if it does contain it with an earlier
- datestamp. If no modify stamp is present on source and target, it
- will not copy it at all. This seemed to prevent the use of
- DateSweep.
-
- A quick inspection led me to believe that the 'create' datestamp
- was being handled correctly, and I contacted Bridger Mitchell to
- see if a mod could be made to DateSweep, causing it to use the
- later of 'Create' or 'Modify'. Bridger responded promptly with a
- version which did this, asking me to Beta test it.. At first
- everything seemed to go well, files which had been updated were
- backed up according to their create dates before shutting off the
- computer (and killing the Ramdisk) - I thought. About one day
- into this method, I was rudely made aware that source and object
- code which had been de-bugged were exhibiting old problems again.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Once all the moaning and groaning had subsided (and hardcopy of
- the correct code was typed in manually), I began a full-scale
- investigation of this phenomena. The bottom line is BE CAREFUL!!.
- TM2 is extremely erratic in it's interface with DateStamper.
- Sometimes all goes well, and the create date jives with when the
- source or object code originated. Sometimes the output file has
- no date at all, sometimes it inherits an invalid date from the
- previous incarnation of the file... phooey! This is definitely
- attributable to the compiler. I have not had one bit of such
- misbehavior through the entire course of previous development
- projects.
-
- ZRDOS's archive function does detect writes to files, so I am
- forced to resort to a kludgy method. The archive bit on all
- ramdisk files is set during setup. Before backup, I examine them
- manually to check for valid stamps. This requires keeping an
- accurate log of all changes, and the time which one performed
- them at! Why don't I just use the AC utility to copy any changed
- files? AC does not copy the stamp, and it will make it seem as if
- all modified files were copied at the same time, no use for
- tracking compilation order.
-
- If versions get out-of-sync on a many moduled project, it's a
- bear! The FTL compiler is supplied with a poorly documented and
- buggy utility called PRECEDEN.COM. After some modification, I
- have found it useful in creating the re-compilation sequence for
- any given definition file which becomes changed. Workman's manual
- states that 'the supplied utility programs may be freely
- distributed as linked programs'. This seems fairly generous, and
- I will be uploading the corrected object code for these files
- (with instructions) in the near future. I am not expecting any
- change in TM2's file protocol, but Bridger has a copy of it and
- if anyone can create a workaround, he can.
-
- If the reader has had the patience to stay with this so far,
- please advise me of any areas demanding additional explanation.
- If I have mis-represented any aspect of the above subjects, bring
- this to my attention also. I check in regularly with ZNODE
- Central, Downspout, Lilliput (System 1), and Sage's boards. New
- facts brought to light will help all of us!
-
- Good Modularity!
-
- Steve
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-