Hi everybody! I'm working on a project that's quite long (about 10K lines of code) for a moving-message-LED-display controlled by a 16C73 that has a lot of features. It's working nicely and it's almost finished. While working on it I developed an incredibly-simple-but-suprisingly- useful method for the allocation of registers. Without it I would have had a hard work integrating all its components. It consists of several macros of almost unreadable definition comprised in a <200 line include file I call "ALOCATOR.MAC". When you use them the rest of the code is very readable and the registers are allocated and overlapped almost optimally. And the idea is *very* simple. So much that I don't know how I didn't think of it before! (...and I don't believe nobody else has..) I've been thinking of trying to write an article but I desperately need to finish this project cause I am having some trouble with my finances. On the other hand you might have noticed that I am not a native English-speaker and I don't have much confidence in the quality of a work of mine written in English... Instead, I like the idea of explaining it on this list. I see no point in keeping it to myself! I'm starting on a separate message, and if you find it interesting I would very much appreciate your feedback. Andres A METHOD FOR PIC ASSEMBLY CODE INTEGRATION AND REUSE - Part I As you may guess from the fancy title, the method I'm suggesting deals with modularization of programs, which means to view a system (the program) as a structured collection of components (routines or sets of routines). Most of the structure is defined by a client-server relation between components. If for a given a component there is another that uses it we say that the first is the server and the second the client in this relationship. A component may have several clients and several servers too. It is illustrative to graph this structure in an orderly manner, putting the clients on top of the servers. For example: TEXT_DISPLAY | | --------------- ------------------ | | | | FILLING_A_TEXT_BUFFER------------- READING_A_TEXT_BUFFER | | | | | ------------- ------------ | | | | | KEYBOARD_INTERFACE SERIAL_INTERFACE EEPROM_SUPPORT A critical issue in programming a system like this is the allocation of variables. By that I mean giving an address to each variable each component uses. If you use no strategy at all and allocate everything by hand using "EQU"s you will have to do some work every time you create a new variable. Lets say you are working with the keyboard interface module in the previous example and need a new variable for it. What address will you use? You would have to run through all the other source code to find an unused one. Or you could do the same thing with less work if you allocate the registers in order and write down the next free one. But what happens when you want to use the keyboard interface in another project, maybe with other components you had also already programmed? It's a lot of work, and it's a routine that requires none of your creativity. Hey! What do you have your PC for? The same scheme can be automatized in several ways. One is what Microchip suggests with the use of unseeded "CBLOCK"s. With this methods the assembler will linearly assign addresses to all variables at compile-time. That solves the problems as long as you don't run out of registers. If you do run out of registers the solution will surely be overlapping (assigning the same address to) variables that are never "active" at the same time. That's where the method I'm suggesting comes to play. It is a way of automatizing this job without sacrifizing any modularity. The implementation of the method consists of several macros that give a high-level-language look to the declaration of variables in your code and make most of the job for you. I'll give you just a tip here and I'll continue in other messages. The question is: What variables can you overlap? Recall the previous graph. You can't be sure if you can overlap variables belonging to "FILLING_A_TEXT_BUFFER" with others from "KEYBOARD_INTERFACE" because both components might be active at the same time. I mean, "FILLING..." could be active waiting for "KEYBOARD..." to perform a service, and "KEYBOARD..." would then be active performing it. But notice that surely "KEYBOARD..." and "SERIAL_INTERFACE" will never be active at the same time! So you may overlap their variables. This has some exceptions to which I will refer to later. That is the heart of the method, I recognize it is an almost stupid idea and that I'm not discovering America but it took quite a while for me to sit down, think and develop it, and its conclusions turned out being very useful to me. If I don't receive complaints for using so much of this space I'll continue later. I will be glad to receive feedback from you. Do you have any other strategies for doing this? Can you see something wrong in what I'm saying? Did you think of this too? Regards! Andres A METHOD FOR PIC ASSEMBLY CODE INTEGRATION AND REUSE - Part 2 First of all I'd like to publicly thank again for all the positive words many of you had towards my first message. In my last posting I ended by giving an example of a case in which you may overlap variables. We had the following structure as an example: TEXT_DISPLAY | | --------------- ------------------ | | | | FILLING_A_TEXT_BUFFER------------- READING_A_TEXT_BUFFER | | | | | ------------- ------------ | | | | | KEYBOARD_INTERFACE SERIAL_INTERFACE EEPROM_SUPPORT I said that you shouldn't ovelap the variables belonging to "FILLING_A_TEXT_BUFFER" with those from "KEYBOARD_INTERFACE" because both components might be active at the same time. By "active" I mean that the component is performing a service for a client. For example, if in this structure the program flow is in the "KEYBOARD_INTERFACE" that component will be active and "FILLING_A_TEXT_ BUFFER" too because it's waiting for "KEYBOARD..." to perform the service it requested (no other component could have requested a service from "KEYBOARD..." with this structure). The rule is to overlap variables that belong to components that are never active at the same time, with some exceptions to which I'll soon refer. To check for the validity of this assertion lets make an analogy with an Algol-like-high-level-language (one that makes use of a data stack) running on a language-oriented-machine (one that HAS a data stack!). If a routine is active it has a stack frame belonging to it where it keeps its local variables. When this routine calls a server its frame is kept on the stack and the server's frame is placed on top. So when the program flow is in the server both routines are active, as in the example, and the memory for their local variables is not overlaped. What happens when the server ends its job, the flow returns to the original routine and it calls another server? The first server's stack frame is wiped out and the newer server's is put on top of the original routine's. So you may have some memory space being shared by both servers. But there was no problem about it because both routines were not active at the same time, and if one never calls the other, and never calls another routine that calls the other, and never calls another routine that calls another routine... etc. that calls the other, then you can assure that you will *never* need separate memory locations for these routines' variables. Our example shows this behaviour for {"KEYBOARD...", "SERIAL..." and "EEPROM..."} and for {"FILLING..." and "READING..."}. But there is no need to inspect the client-server structure of a given system to apply the method, I'm explaining this only to introduce its logic but its application is much simpler. I'll again begin with an example and generalize later on my next posting. Lets say each component has the following quantity of local variables: "KEYBOARD_INTERFACE": 5 "SERIAL_INTERFACE": 3 "EEPROM_SUPPORT": 3 "FILLING_A_TEXT_BUFFER": 7 "READING_A_TEXT_BUFFER": 4 "TEXT_DISPLAY": 3 I'll start from the bottom of the structure. Where will I put the variables belonging to the first three components? I already said I could overlap them. Recall the method to which I refered on my last posting that assigned consecutive addresses to variables and could be implemented with "CBLOCK"s. I said that it was OK as long as you did not run out of registers, because it did not overlap any. So lets add this feature to that method. But first lets agree on something, when you use consecutive addresses it's the same to build from the bottom to the top incrementing the addresses or to do it from top to bottom decrementing them. This time I'll choose the second option, but for no particular reason. So, if the maximum register address is 127, the variables belonging to "KEYBOARD..." will occupy positions 123 to 127. And the other two will also begin in 127 as I have no problem in ovelapping them! "SERIAL..." will use 125 to 127 and "EEPROM..." too. Now the responsibility of not overlapping that allocated space is passed to the components on top. This makes sense if you think that the definition of a component includes information of its servers but not of its clients. Where can I put "FILLING..."'s variables? They shouldn't overlap the first three allocated spaces, so they could start at 122 being the next free position that meets this condition. The variables will go from 116 to 122. "READING..."'s variables must not overlap only "EEPROM..."'s, so they can start at 124, occupying from 121 to 124. "TEXT..."'s variables shouldn't overlap any of the others' so they may start at 115 and run down to 113. The general rule then is (if you're using a "decremental model" as in this case): allocate beggining from the least of the routine's servers' allocated positions minus one. This is a job for a macro. I'll continue later, but I guess you can see much of the point already. I also owe you the explanation of the exceptions for this scheme. Once again, I'll appreciate any opinions. Regards! Andres A METHOD FOR PIC ASSEMBLY CODE INTEGRATION AND REUSE - Part 3 I ended my last message by giving a golden rule for the allocation of variables: begin to allocate from the least of the routine's servers' allocated positions minus one (if you are using a "decremental model"). So, what do we need to define a component's variables? We need: * Their names (for their sizes I use only bytes but you could make a scheme in which you could fix the variable's sizes) * Someway of knowing which variables belong to each component, this can be done with some sort of "frame delimiters". * We need to know what servers the component has. A way of doing this could be, for "FILLING..." in my previous posting's example: FRAME FILLING USES KEYBOARD USES SERIAL USES EEPROM BYTE fillingsVarNo1 BYTE fillingsVarNo2 ...etc FEND FILLING I think it looks pretty good. "FRAME", "USES", "BYTE" and "FEND" are macros that implement the method I've been describing on my previous echos. Before showing the macros I'll talk about the exeptions for this allocation scheme. I started the reasoning that ended with it by saying that you could overlap variables as long as the components to which they belong are never active at the same time. This is not true with some variables. Lets say you have a counter of some sort. It consists of two routines "COUNT" and "INFORM". The first, when called, increments the count, and the latter informs its current value. There's a piece of information (the count value) that must be kept even when the routines are not active. So you shouldn't overlap this variable with others although their components might not share activity. This fact is not a result of the method or a derivative of Microchip's architecture, it is inherent to the type of variable and, for that reason, high-level languages provide support for them. In my opinion, the cleanest way to deal with them is what Ada and OOP languages do with instance variables. See how they have to do with a certain state the component has that outlives the execution of its routines. The C language, on the other hand, supports this kind of variables with the "static" modifier. In compiler theory a variable is "static" if its destination and size is already defined at compile-time. It's very probable that an implementation of C will deal with variables declared as static by placing them on a location defined at compile time, differently from automatic variables (those which were not defined as static) that are placed dynamically on the stack as I briefly explained on my last posting. In my opinion the architects of the C language shouldn't have used a term that relies on a particular implementation and refers to a low-level issue, and has not much to do with the real behaviour the programmer is asking for. But this is just another criticism to C to be added to the long list... I started using "STATIC" too as a name for the macro to support these variables, but I don't want to imitate what I think is wrong so I'm switching to "PERSISTENT". I shouldn't use "STATIC" also because, strictly speaking, all the variables in the method are static! So when the byte to declare should outlive the component's execution I put "PERSISTENT variable" instead of "BYTE variable". As you may expect, "PERSISTENT" is another macro. Where to put these persistent variables? We shouln't overlap them with anything, so the best we can do is assign consecutive addresses for them that are apart from those we used for the automatic variables. I said before that it was the same to assign upwards from the bottom than downwards from the top as I eventually did, so to pile the persistent variables we could use the first option this time. For example, if both "KEYBOARD..." and "EEPROM..." have also got a persistent variable each, and the first available location is number 32, we could assign one in register 32 and the other in 33. Persistent allocation consumes more RAM resources as it won't be overlapped. Most of the times the life of persistent data, although not bounded by the execution of the routines to which they belong, can be bounded to the execution of a certain component. For example in OOP instance variables overlive the execution of the methods that make use of them but are created and destroyed in conjunction with the objects they belong to. This sort of thing is not implemented in this method, persistent variables live through the whole execution, and as we could save RAM there seems to be room for improvement here. Before ending, notice that a "memory full" condition will occur if, during compilation, the incrementing count of positions for persistent variables collides with the minimum decrementing count for automatic ones. Now I'm ready to list the most important macros, "FRAME", "USES", "BYTE", "PERSISTENT" and "FEND". I'll be doing it in a separate message. I'll be back soon! Andres adjordj@aleph.fi.uba.ar A METHOD FOR PIC ASSEMBLY CODE INTEGRATION AND REUSE - Part 4 ----------------------- cut here ---------------------------------------- ; **************************************************************** ; * MACROS FOR THE ALLOCATION OF VARIABLES * ; **************************************************************** ; ; Andres Djordjalian ; EMail: adjordj@aleph.fi.uba.ar ; NOEXPAND ; I strongly suggest it as listings could get rather ; long and unreadable with macro expansions ; ************************************* ; * FRAME * ; ************************************* FRAME MACRO rutina IFDEF alocTemp #undefine alocTemp ENDIF #define alocTemp rutina#v(CERO)#v(CLAVE) VOLATILES0 SET H'7F' VOLATILES1 SET H'FF' alocTemp SET 0 ENDM ; ************************************* ; * USES * ; ************************************* USES MACRO rutina #undefine alocTemp #define alocTemp rutina#v(CERO)#v(CLAVE) IF VOLATILES0 > alocTemp VOLATILES0 SET alocTemp ENDIF #undefine alocTemp #define alocTemp rutina#v(UNO)#v(CLAVE) IF VOLATILES1 > alocTemp VOLATILES1 SET alocTemp ENDIF ENDM ; ************************************* ; * BYTE variable * ; ************************************* BYTE MACRO variable IF VOLATILES#v(RAM_PAGE) > PERSISTENTES#v(RAM_PAGE) variable EQU VOLATILES#v(RAM_PAGE) IF VOLATILES#v(RAM_PAGE)