;********************************************************************** ;* Filename: stdmac.inc ;* Project: EPROM emulator ;* Date: 05 June 2005 ;* Version: 0.1 ;* Author: Philip Pemberton ;* ;* General purpose macros. Based on code by Olin Lathrop / Embed Inc. ;********************************************************************** ; *************************************************************** ; * Copyright (C) 2004, Embed Inc (http://www.embedinc.com) * ; * * ; * Permission to copy this file is granted as long as this * ; * copyright notice is included in its entirety at the * ; * beginning of the file, whether the file is copied in whole * ; * or in part and regardless of whether other information is * ; * added to the copy. * ; * * ; * The contents of this file may be used in any way, * ; * commercial or otherwise. This file is provided "as is", * ; * and Embed Inc makes no claims of suitability for a * ; * particular purpose nor assumes any liability resulting from * ; * its use. * ; *************************************************************** ; ;********************************************************************** ;** MACROS ;********************************************************************** ; ;******************** ; ; Macro INTR_OFF ; ; Globally disable interrupts without changing which interrupts are ; individually disabled. This macro together with INTR_ON can be used ; around small sections of code that need to run with interrupts off. ; ; This macro works around the interrupt off bug on some processors where ; an interrupt can occur immediately after interrupts are disabled. This ; causes interrupts to be re-enabled by the RETFIE in the interrupt service ; routine. The work around is to verify that interrupts were indeed ; disabled on the next instruction and loop back if they weren't. ; The assembler switch INTR_OFF_BUG is set to TRUE if this processor ; has the bug. ; intr_off macro local retry if fam_16 retry bcf INTCON, GIE if intr_off_bug btfsc INTCON, GIE jump retry endif exitm endif if fam_18 bcf INTCON, GIEH exitm endif error "INTR_OFF macro in STDMAC.INC not implemented for this processor" endm ; ;******************** ; ; Macro INTR_ON ; ; Globally enable interrupts without changing which interrupts are ; individually enabled. This macro together with INTR_OFF can be used ; around small sections of code that need to run with interrupts off. ; intr_on macro if fam_16 bsf INTCON, GIE exitm endif if fam_18 bsf INTCON, GIEH exitm endif error "INTR_ON macro in STDMAC.INC not implemented for this processor" endm ; ;******************** ; ; Macro INTR_OFF_LOW ; ; Disable low priority interrupts. ; ; Operation is undefined unless separate high and low interrupt ; priorities are enabled. This is not checked. ; ; It is an error to call this macro on a processor that is not ; capable of high and low priority interrupts. ; intr_off_low macro if fam_18 bcf INTCON, GIEl exitm endif error "INTR_OFF_LOW macro in STDMAC.INC not implemented for this processor" endm ; ;******************** ; ; Macro INTR_ON_LOW ; ; Re-enable low priority interrupts. ; ; Operation is undefined unless separate high and low interrupt ; priorities are enabled. This is not checked. ; ; It is an error to call this macro on a processor that is not ; capable of high and low priority interrupts. ; intr_on_low macro if fam_18 bsf INTCON, GIEl exitm endif error "INTR_ON_LOW macro in STDMAC.INC not implemented for this processor" endm ; ;*********************************************************************** ; ; Bank and page switching. ; ; The assembler variables CURRIB and CURRDB are used to keep track of the ; current indirect and direct register bank settings. Note that these ; are only assembly time, not run time variables. They reflect which ; bank is currently ASSUMED to be selected. The programmer must therefore ; play an active role in making sure these variables are set correctly ; if they are used. Proper use can avoid unneccessary bank switching ; instructions. ; ; On the 17Cxxx series, similar assembler variables CURRRB and CURRGB ; are used. CURRRB is the current special function register bank set ; by the low nibble of BSR. CURRGB is the current general register ; bank set by the high nibble of BSR. ; ; On the 18 family, CURRIB is not used because each FSR contains the ; full target address. There are no RAM banks on the 18 family. ; nobank equ -1 ;value to indicate current bank is unknown if fam_16 ;16Cxxx except 16C5xx ? currdb set nobank ;init current direct register bank assumption currib set nobank ;init current indirect register bank assumption endif if fam_18 ;18 family currdb set nobank ;init current direct register bank assumption endif ; ;******************** ; ; Inline macro functions related to register banks, code pages, and ; other addressing issues. ; ; ; BANKOF (ADR) ; ; Returns the direct register bank number containing the RAM address ADR. ; if fam_12 #define bankof(adr) (((adr) >> 5) & 3) endif if fam_16 #define bankof(adr) (((adr) >> 7) & 3) endif if fam_18 #define bankof(adr) (((adr) >> 8) & 15) endif ; ; IBANKOF (ADR) ; ; Returns the indirect register bank number containing the RAM address ADR. ; if fam_12 #define ibankof(adr) (((adr) >> 5) & 3) endif if fam_16 #define ibankof(adr) (((adr) >> 8) & 1) endif if fam_18 #define ibankof(adr) (0) ;this processor has no indirect banks endif ; ; BANKADR (BANK) ; ; Returns an address within the direct register bank BANK. An arbitrary ; address within the bank is returned, except that DBANKIF will not ; recognize it as an unbanked location. ; if fam_12 #define bankadr(bank) (((bank) & 3) << 5) endif if fam_16 #define bankadr(bank) (((bank) & 3) << 7) endif if fam_18 #define bankadr(bank) ((((bank) & 15) << 8) + h'FF' - (((bank) & 15) << 4)) endif ; ; IBANKADR (BANK) ; ; Returns an address within the indirect register bank BANK. ; if fam_12 #define ibankadr(bank) (((bank) & 3) << 5) endif if fam_16 #define ibankadr(bank) (((bank) & 1) << 8) endif if fam_18 #define ibankadr(bank) (0) ;this processor has not indirect banks endif ; ; INACCESSBANK (ADR) ; ; Returns 1 if ADR is within the access bank, 0 otherwise. Always ; returns 0 on processors that don't have an access bank. ; if fam_18 #define inaccessbank(adr) (((adr) <= h'7F') || ((adr) >= h'F80')) else #define inaccessbank(adr) (0) endif ; ; INBANKED (ADR) ; ; Returns 1 if ADR is in banked memory, 0 otherwise. Banked memory ; means some sort of bank settings must be properly set to access ; the memory. ; if fam_18 #define inbanked(adr) (((adr) >= h'80') && ((adr) <= h'F7F')) endif ; ; These constants provide addresses that are guaranteed to be within ; particular register banks. Note that the bank switching macros below ; take addresses, not bank numbers. These constants can be used to ; convert from bank numbers to addresses within the banks by using the ; #v(n) assembler syntax. For example, if "b" is a bank number, then: ; ; bank#v(b)adr ; ; is an address within bank b, which can be passed to the DBANKIF macro, ; for example. ; ii set 0 ;init loop counter while ii < nregbanks ;once for each possible direct register bank bank#v(ii)adr equ bankadr(ii) ii set ii + 1 ;advance to number of next register bank endw ; ;******************** ; ; Macro DBANK? ; ; Invalidate the current direct register data bank assumption. ; This macro produces no code, only sets assembly time state. ; ; On 17Cxxx processors, this sets both the special and general register ; bank assumptions to unknown. These processors make no distinction ; between a direct or indirect bank selection. ; dbank? macro currdb set nobank endm ; ;******************** ; ; Macro IBANK? ; ; Invalidate the current indirect register data bank assumption. ; This macro produces no code, only sets assembly time state. ; ; On 17Cxxx processors, this sets both the special and general register ; bank assumptions to unknown. These processors make no distinction ; between a direct or indirect bank selection. ; ibank? macro if fam_18 exitm ;this processor has no indirect banks endif currib set nobank endm ; ;******************** ; ; Macro UNBANK ; ; Invalidates all register bank assumptions. ; unbank macro dbank? ibank? endm ; ;******************** ; ; Macro DBANKIS ; ; Set the current direct register bank assumption to the bank containing ; ADR. This macro generates no code to switch the register bank. It only ; updates assembly time state. ; dbankis macro adr if fam_18 if inaccessbank(adr) currdb set nobank exitm endif endif currdb set bankof(adr) ;set assumed direct register bank number endm ; ;******************** ; ; Macro IBANKIS ; ; Set the current indirect register bank assumption to the bank containing ; ADR. This macro generates no code to switch the register bank. It only ; updates assembly time state. ; ibankis macro adr if fam_18 exitm ;this processor has no indirect banks endif currib set ibankof(adr) ;set assumed indirect register bank number endm ; ;******************** ; ; Macro DBANKIF ; ; Set the register bank for direct access to address ADR. This macro ; sets the RP0, RP1 bits of STATUS appropriately. The bank bits are ; not set if they are assumed to already be set correctly as indicated ; by the assembler variable CURRDB. CURRDB is updated to the new ; setting. ; ; On 18 family processors, this macro sets BSR. No code is emitted if ; and the bank setting is not altered if ADR is within the access bank, ; and can therefore be accessed regardless of the BSR setting. ; dbankif macro adr local newbank, ii if nregbanks <= 1 ;this processor only has one register bank ? exitm endif newbank set bankof(adr) ;find 0-N desired bank number ;***** ; ; 16 family devices. ; if fam_16 ii set (adr) & h'7F' ;make address offset within bank if (ii >= commregs_first) && (ii <= commregs_last) ;in unbanked region ? exitm endif if nregbanks > 1 ;bank bit 0 is meaningful ? if (currdb < 0) || (currdb > 3) || ((currdb & 1) != (newbank & 1)) ;need update ? if newbank & 1 bsf STATUS, RP0 else bcf STATUS, RP0 endif endif endif if nregbanks > 2 ;bank bit 1 is meaningful ? if (currdb < 0) || (currdb > 3) || ((currdb & 2) != (newbank & 2)) ;need update ? if newbank & 2 bsf STATUS, RP1 else bcf STATUS, RP1 endif endif endif currdb set newbank ;update current bank assumption exitm endif ;end of 16Cxxx case ;***** ; ; 18 family devices. ; if fam_18 if inbanked(adr) ;address is in banked memory ? if currdb != newbank ;not already in this bank ? movlb newbank ;switch to the new bank currdb set newbank ;update the current bank setting assumption endif endif exitm endif ;end of 18 family case error "DBANKIF macro in STDMAC.INC not implemented for this processor" endm ; ;******************** ; ; Macro DBANK ; ; Unconditionally set the direct access register bank for access to address ; ADR. The assembler state is updated. ; dbank macro adr dbank? ;invalidate current register bank assumption dbankif adr ;set the new register bank endm ; ;******************** ; ; Macro IBANKIF ; ; Set the register bank for indirect access to address ADR, if needed. ; The assembler variable CURRIB is assumed to indicate the current ; indirect register bank setting. CURRIB is updated. This macro ; sets the IRP bit of STATUS appropriately. ; ibankif macro adr local newbank, ii if fam_18 exitm ;this processor has no indirect banks endif newbank set ibankof(adr) ;find 0-N desired bank number ;***** ; ; 16Cxxx devices. ; if fam_16 if nregbanks <= 2 ;this processor only has one indirect register bank ? exitm endif ii set (adr) & h'7F' ;make address offset within direct bank if (ii >= commregs_first) && (ii <= commregs_last) ;in unbanked region ? exitm endif if nregbanks > 2 ;there is more than one indirect bank ? if (currib < 0) || (currib > 1) || (currib != newbank) ;need update ? if newbank & 1 bsf STATUS, IRP else bcf STATUS, IRP endif endif endif currib set newbank ;update current bank assumption exitm endif ;end of 16Cxxx case error "IBANKIF macro in STDMAC.INC not implemented for this processor" endm ; ;******************** ; ; Macro IBANK ; ; Unconditionally set the indirect access register bank for access to ; address ADR. CURRIB is updated. ; ibank macro adr ibank? ;invalidate current register bank assumption ibankif adr ;set the new register bank endm ; ;******************** ; ; Macro DEFRAM
; ; Set up for allocating RAM with subsequent RES directives. ADDRESS ; is the address that will be passed to DBANKIF to access any of the ; RAM defined in this section. If ADDRESS indicates normal banked ; memory, then the appropriate .BANKn linker section will be started ; with a UDATA directive. If ADDRESS is in common RAM, then the ; RAM will be in the .UDATA_SHR section. If ADDRESS is in the access ; bank of an 18 family, then the RAM will be allocated in the ; .UDATA_ACS section. ; ; If the new linker section would be the same as the linker section ; defined by the previous DEFRAM, then no new directives are written ; since only one occurrence of a linker section is allowed per source ; module. ; ; The assembler symbol DEFRAM_LAST is used to track linker section of ; the last DEFRAM invocation. This symbol has the following values: ; ; -1 - No previous DEFRAM section ; ; -2 - Last linker section was .UDATA_SHR ; ; -3 - Last linker section was .UDATA_ACS ; ; 0-N - Last linker section was .BANKn ; defram_last set -1 ;init to DEFRAM not previously invoked defram macro address local badr badr set address ;***** ; ; 16 family devices. ; if fam_16 local ii ii set badr & h'7F' ;make address offset within bank if (ii >= commregs_first) && (ii <= commregs_last) ;in unbanked region ? if defram_last == -2 ;already in this region ? exitm endif udata_shr ;start the new RAM linker section defram_last set -2 ;remember which linker section now in exitm endif ii set bankof(badr) ;make 0-N register bank number if defram_last == ii ;already in this linker section ? exitm endif .bank#v(ii) udata ;start the new RAM linker section defram_last set ii ;remember which linker section now in exitm endif ;end of 16 family case ;***** ; ; 18 family devices. ; if fam_18 local ii if ((badr >= 0) && (badr <= h'7F')) || ((badr >= h'F80') && (badr <= h'FFF')) if defram_last == -3 ;already in access RAM ? exitm endif udata_acs ;start the new RAM linker section defram_last set -3 ;remember which linker section now in exitm endif ;end of access RAM case ii set bankof(badr) ;make 0-N register bank number if defram_last == ii ;already in this linker section ? exitm endif .bank#v(ii) udata ;start the new RAM linker section defram_last set ii ;remember which linker section now in exitm endif ;end of 18 family case error "DEFRAM macro in STDMAC.INC not implemented for this processor" endm ; ;*********************************************************************** ; ; Subroutine linkage. ; ; Normally, subroutines preserve the general registers REG0 - REGn, ; and are free to trash other state, such as W, FSR, and the current ; bank settings. ; ; Subroutines external to a module are assumed to be on any page, whereas ; all code of a module is always on the same page. By convention, PCLATH ; is left pointing to the current code page. This means it must be set ; before and restored after external calls. ; ; On the 18 family, the GOTO and CALL instructions contain all address ; bits, so PCLATH and PCLATU do not matter. On these processors, PCLATH ; and PCLATU are not maintained to match the current execution address. ; They PCLATH and PCLATU must be explicitly set before PCL is modified, ; such as with computed GOTOs and some table operations. ; ;******************** ; ; Macro LEAVE ; ; This macro performs the "standard" operations on subroutine exit. ; These actions are: ; ; 1 - Return from the subroutine. ; ; 2 - Invalidate the current register bank assumptions. Note that this ; sets assembly state, not runtime state. The assumptions are therefore ; propagated in source code, not execution order. ; leave macro return ;return from the subroutine unbank ;invalidate all register bank assumptions endm ; ;******************** ; ; Macro GLBSUB ; ; Declare the start of a new global subroutine. will be declared ; as the global subroutine entry point label at the current location. ; glbsub macro name name global name unbank endm ; ;******************** ; ; Macro LOCSUB ; ; Just like GLBSUB except that the subroutine will not be global. It ; will only be known within its module. ; locsub macro name name unbank endm ; ;******************** ; ; Macro GLBENT ; ; Declare a global entry point that can be jumped to from other modules. ; glbent macro name name global name unbank endm ; ;******************** ; ; Macro LOCENT ; ; Declare a local entry point that can only be jumped to from within ; the same module. ; locent macro name name unbank endm ; ;******************** ; ; Macro SETPAGE
; ; Select the code page (by setting PCLATH) containing the indicated address. ; setpage macro adr if ncodepages <= 1 ;only one code page exists, no selection required ? exitm endif pagesel adr if fam_16 && (ncodepages == 2) ;need NOP bug workaround ? nop endif endm ; ;******************** ; ; Macro MYPAGE ; ; Sets PCLATH to the current code page, if this machine has multiple ; code pages. W may be trashed. ; mypage macro if ncodepages <= 1 ;only one code page exists, no need to set PCLATH ? exitm endif local here setpage here here endm ; ;******************** ; ; Macro GCALL ; ; Call a global subroutine, which could be anywhere in program memory. ; W is trashed between the caller and the subroutine, and can therefore not ; be used to pass data to the subroutine. The assumed register banks are ; set to invalid after the subroutine returns. ; ; No code page selection code is generated on machines that only have one ; code page. ; gcall macro subroutine extern subroutine setpage subroutine ;select the code page the subroutine is on call subroutine ;call the subroutine unbank ;invalidate register bank assumptions mypage ;restore PCLATH to the local page endm ; ;******************** ; ; Macro GCALLNR ; ; Just like GCALL except that the current program memory page is not restored ; after the subroutine returns. This saves unnecessary instructions if the ; current code page setting is not used by subsequent code. This could ; be the case, for example, if another global subroutine was called immediately ; afterwards. ; ; WARNING: This macro will cause subsequent code to fail if it does rely ; on the current program memory page setting. Local CALLs and GOTOs ; rely on the current page setting. ; gcallnr macro subroutine extern subroutine setpage subroutine ;select the code page the subroutine is on call subroutine ;call the subroutine unbank ;invalidate register bank assumptions endm ; ;******************** ; ; Macro MCALL ; ; Call a local subroutine within the same module (code section, actually). ; The subroutine is therefore guaranteed to be on the same code page. ; The assumed register banks are invalidated after the subroutine returns. ; mcall macro subroutine if fam_18 rcall subroutine ;use short call within the module else call subroutine ;call the subroutine endif unbank ;invalidate register bank assumptions endm ; ;******************** ; ; Macro GCALLWR ; ; Just like GCALL, except that W is preserved on return from the subroutine. ; Note that W is still trashed from the caller to the subroutine. ; gcallwr macro subroutine extern subroutine setpage subroutine ;select the code page the subroutine is on call subroutine ;call the subroutine unbank ;invalidate register bank assumptions if ncodepages > 1 ;code page selection is meaningful ? dbankif bankadr(0) extern tempw movwf tempw ;save W before it gets trashed by PAGESEL mypage ;restore PCLATH to the local page movf tempw, w ;restore W returned by subroutine endif endm ; ;******************** ; ; Macro GCALLR ; ; Just like GCALL, except that the subroutine is assumed to return a value ; in W. This return value will be stored in PUTRET. Note that PUTRET must ; be in global (any bank) memory unless the subroutine is known to explicitly ; set the direct register bank. W is trashed. The direct and indirect ; register bank settings are preserved from the subroutine. ; gcallr macro subroutine, putret extern subroutine setpage subroutine ;select the code page the subroutine is on call subroutine ;call the subroutine unbank ;invalidate register bank assumptions movwf putret ;save the subroutine return value mypage ;restore PCLATH to the local page endm ; ;******************** ; ; Macro MCALLR ; ; Just like MCALL, except that the subroutine is assumed to return a value ; in W. This return value will be stored in PUTRET. Note that PUTRET must ; be in global (any bank) memory unless the subroutine is known to explicitly ; set the direct register bank. W is trashed. The direct and indirect ; register bank settings are preserved from the subroutine. ; mcallr macro subroutine, putret if fam_18 rcall subroutine ;use short call within the module else call subroutine ;call the subroutine endif unbank ;invalidate register bank assumptions movwf putret ;save the subroutine return value endm ; ;******************** ; ; Macro GJUMP ; ; Jump to a location that could be anywhere in program memory. W is trashed ; before execution starts at the new location. The assumed register banks ; are set to invalid for source code immediately following the GJUMP. ; gjump macro adr setpage adr ;select code page of target goto adr ;jump to the target unbank ;invalidate register bank assumptions endm