Data Memory Paging Management, Part 2 concluded - Embedded.com

Data Memory Paging Management, Part 2 concluded



This concludes the Data Memory Paging Management two-part article from ESP January 2000, and February 2000. The articles explain a method to detect any potential paging errors in assembly programs.

Now let’s examine how this can be used to create more complex, more useful subroutines. For example, UpdateChecksum could be rewritten as shown in Listing 2.

Listing 2: UpdateChecksum rewritten to demonstrate more complex and useful subroutines
UpdateChecksum: ;{Page = P'}
A<-Page ;{Page = P' and A = P'}
X<-A reg rE ;{Page = P' and rE = P'}
A<-X imm 0 ;{Page = P' and rE = P'}
Page<-A ;{Page = 0 and rE = P'}
A<-X reg ChecksumReg_P0 ;{Page = 0 and rE = P'}
A<-A+X reg DataReg ;{Page = 0 and rE = P'}
X<-A reg ChecksumReg_P0 ;{Page = 0 and rE = P'}
A<-X reg rE ;{Page = 0 and A = P'}
Page<-A ;{Page = P'}
RETURN


Note that rE is unpaged; therefore, it can be referenced without knowing exactly which page is active at the time. This new logical structure allows us to write subroutines that need a particular register page to be active, yet can be called from anywhere in the program without disrupting the active page at that point in the program. It is up to the programmer, of course, to ensure that rE does not become corrupt. If a second subroutine were to try to use this same trick of preserving the active page with register rE, and the second subroutine CALL ed UpdateChecksum , then rE would be corrupted before the RETURN of the newer subroutine.

The ideal implementation of this form of paging management would be a stack. Upon entering a subroutine, the current page would be added to the stack. The page may be manipulated as needed in the body of the subroutine. Immediately before RETURN ing from the subroutine, the old value of Page should be retrieved from the stack.

Reset and interrupt vectors

Every processor has a reset vector, a place where the processor starts executing instructions when it is reset (for instance, immediately after it receives power for the first time). Sometimes this is a fixed location in program memory, sometimes some memory is used to hold the address of the reset vector.

The processor may be designed to start in a known page, or it may not. If the active page after a reset is unknown, then the comment at the address vector must be ;#### . If the active page is set to 0 after every reset, the comment at the reset vector must have a # in the first character of the comment.

You could think of a piece of code (not stored in program memory) like this:

ImaginaryCode:	;{Page = (possible values of	  Page on reset)}	GOTO  imm     ResetVector

This code is nowhere in the program memory, but it could be thought of as implicit code in the processor itself.

If a processor has interrupts, it usually has instructions for enabling and disabling the interrupts. I shall add a new bit type to the pseudo-assembly language, called INTE (Interrupt Enable). Interrupts are enabled by the instruction:

X<-1	bit	INTE

and disabled with the instruction:

X<-0	bit	INTE

A processor with interrupts also has one or more interrupt vectors. Some have automatic context saving, while others do not. Some of those that have automatic context saving may even set the active page to some known value when entering an interrupt. In any case, there is usually an instruction to return from interrupts. For this pseudocode, let that instruction be RETINT .

If the processor has automatic context saving that includes storing the value of Page , and Page is set to some fixed value when the interrupt service routine (ISR) is called, matters are simple. Start the ISR with a comment indicating the proper page, and use RETINT to finish the ISR.

If the interrupt doesn’t use any paged registers, this situation is also easy. You should never write to Page in such an ISR . The interrupts don’t even need to be considered for paging tracking, though a comment to this effect should be put in the source code. If any future modifications to the code require access to paged variables ISR , this comment could save a lot of time that would otherwise be spent examining the code before modifications and/or testing and debugging the new code after modifications.

If the processor has automatic context saving that includes storing the value of Page , but does not set Page to some fixed value, you have to be careful with the beginning of your ISR . Any page that may be active at any time the interrupt is enabled may be active at the beginning of the ISR . Note that if interrupts are enabled when the processor is reset, any page which may be active on reset may also be active at the start of any ISR . Thus, if the page is unknown on reset and interrupts are enabled on reset, an ISR may start with any page active. On the other hand, if it is known that only one page is active while the interrupt is enabled, the interrupt must start with that page.

If the processor’s context saving does not include Page, you have to be careful with the beginning of your ISR (as in the previous case) as well as with the end of your ISR . If only one page is supposed to be active when the interrupt service routine is called, then that page must be the only one active just before the RETINT .

The general case for handling paging with interrupts, when there is no automatic context saving, uses dynamic scope, like I described for subroutines. The current active page should be stored in an unpaged register immediately upon entering the interrupt service routine. Immediately before RETINT , the value should be restored. A particular snafu occurs when using this technique with interrupts. A s I’ve discussed, the same register cannot be used to store the page in two different subroutines when one subroutine calls the other. (This also means that the same register cannot be used by both an ISR and a subroutine that is executed when interrupts are enabled.) Similarly, if the processor has low-priority and high-priority interrupts, the same register cannot be used in one of both types of ISR . If a low-priority interrupt starts using a register, and a high-priority interrupt occurs, the high-priority ISR will begin and finish executing before the low-priority ISR gets any further. In this way, that very important register could become corrupt.

If resources in the processor allow it, you could use a stack, too. Push the current page at the beginning of the interrupt service routine, and pull it just before RETINT .

Semi-formalism?

This article is inspired by formal programming methods. Total adherence to formal methods requires well trained programmers, and would make system performance extremely well defined, with absolutely no bugs. It would also mean that there would be a long, complicated mathematical expression (pages long in most cases) distributed with each program—and it would probably be indecipherable to any businessman or marketer. This is a good academic ideal, but it isn’t practical for embedded systems design. (Not enough research has been conducted in formalizing such aspects of programming as real-time operation.) Neither is this ideal marketable (except to computer science and mathematics experts, who could understand and appreciate the formal specification).

Partial adherence, though academics may cringe, can be effective — especially if it is applied in critical areas, where a greater proportion of the benefits can be reaped with a smaller proportion of the drawbacks. I have found paging to be just such a critical area. Consistency, ease of maintenance, development speed (once the developers get accustomed to it), and fewer bugs (to the point of none at all, depending on how far you take it) are some of the benefits that come directly from the use of formal methods. These benefits go to the developers, the project manager, the people who rely on the program, and the programmers who need to add a new feature five years down the road. The drawbacks involved in following these guidelines are a requirement for discipline (though hardly approaching the discipline required for full formality), and probably a slower initial development speed.

This article is meant as a guide to getting your paging right. It is not a complete guide for all processors. For example, many processors have indirect register addressing, an issue I haven’t touched on here. My guide may be used as a guarantee of correct paging if such things as indirect addressing are not used, or as an aid to keep paging correct if those features are used.

Other topics I haven’t covered in this article include assembly instructions such as GOTO reg rA (or X<-A reg PC where PC is the program counter), and even CALL reg rA . Part of the reason I’ve avoided these subjects is that I consider the use of instructions such as these to be bad programming practice, and so never use them myself. I haven’t covered the Page<-A instruction where A has an unknown value for the same reason — I consider that notation to be poor programming practice too.

I hope the method I’ve recorded here will help other assembly language programmers. These practices have proven themselves very effective in my experience.

Hugh O’Byrne graduated from University College Dublin in 1995 with a BS in computer science. He’s currently pursuing an MS in mathematics.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.