6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Wed May 08, 2024 5:39 am

All times are UTC




Post new topic Reply to topic  [ 21 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Sat Aug 12, 2023 5:18 pm 
Offline

Joined: Sat Mar 11, 2017 1:56 am
Posts: 276
Location: Lynden, WA
I learned 6502 assembly "on the fly" whilst creating the monitor/OS for my home-brew. Over 1000 lines of code later, one can likely see that I was at various stages of understanding as I wrote the code over many months. I have working code, but it's almost certainly messy by competent standards.

My first question is how "local" (I know, misuse of the term, but I'm sure you understand what I mean here) variables are properly implemented. I currently just .ds a new memory location every-time a subroutine needs a temporary variable. So I have a lot of memory devoted to variables that don't need to exist outside the subroutine.

Obviously the answer is to reuse memory locations, but what's the cleanest way to do this? I can think of two methods. One, give a specific memory location multiple label names. So routine A calls location $0001 "foo", and routine calls the same location "fee".

My other idea is to just permanently name a bunch of locations generic names that suggest common tasks. "LoopStop", "Count", "Temp_A", Temp_B", ect. So I just have a pool of variables named for common tasks that every routine makes use of.

What is the usual method here?

My other question concerns label naming for code locations. I find I am constantly wanting to use label names that are already in use somewhere else. "End:" for instance is a label name that makes sense in pretty much every sub-routine. My current solution is to have prefixes with the routine name in them. So "Write_End:" and "Read_End:" for instance. Is this the common method? Is there a better one?


Top
 Profile  
Reply with quote  
PostPosted: Sat Aug 12, 2023 5:36 pm 
Offline

Joined: Wed Mar 22, 2023 3:58 am
Posts: 37
Location: teh interwebz
Generic loop and counter names wouldn't work with nested loops, which could be a major debugging headache if the nesting isn't obvious, i.e., one routine has a loop and calls another routine that has a loop.


Top
 Profile  
Reply with quote  
PostPosted: Sat Aug 12, 2023 5:41 pm 
Offline

Joined: Sat Mar 11, 2017 1:56 am
Posts: 276
Location: Lynden, WA
I imagine I could use the stack to deal with nested loops sharing variables, but then I would have to add that stack management to any routine that uses loops. Perhaps not a big deal?


Top
 Profile  
Reply with quote  
PostPosted: Sat Aug 12, 2023 5:44 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10800
Location: England
In my experience, it's very rare to use the stack for data storage, in the sense of locals or parameters. It's not unknown, but it's rare.

The X or Y registers are commonly used as loop counters, of course. And it's already necessary to keep track of which routines are using registers: generally only leaf routines, or perhaps those one level up. Keeping track of multiple uses of zero page locations is a similar kind of thing.

All that said, I've read some disassemblies but never written anything very complicated. Reading disassemblies might well be good practice, though.

As for labels, some assemblers have local labels, or a syntax for forward or backward branches, which are helpful things, if your assembler has them


Top
 Profile  
Reply with quote  
PostPosted: Sat Aug 12, 2023 6:41 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8432
Location: Southern California
These matters are addressed in my treatise on 6502 stacks ("stack" plural, not just the page-1 hardware stack), at http://wilsonminesco.com/stacks/ .  The matter of local variables and environments is particularly addressed on the http://wilsonminesco.com/stacks/loc_vars.html page; but the groundwork is laid in previous pages, so I hope you'll go through it in order.  The forming of nestable program flow control structures, without needing labels (meaning there won't be label conflicts), is a couple of pages later at http://wilsonminesco.com/stacks/pgmstruc.html and in the section of my site that's dedicated to structure macros, at http://wilsonminesco.com/StructureMacros/ .  This is not beginner stuff, but maybe intermediate.  In most cases, the macros do not bring any penalty at all in run speed or memory taken, because they assemble exactly the same thing you would do by hand; it's just that now you can better see what you're doing, and you'll get far less bugs, you'll become more productive, get better code maintainability, etc..  They have the effect of raising the level of the language without any penalties in efficiency.  There are various examples around the site of this kind of programming.  I mostly keep programming examples as short and general-purpose as I can; but a couple of places there are longer examples that come to mind are the bottom of the page that gives example source code for bit-banging SPI at http://wilsonminesco.com/6502primer/SPI.ASM, and the last 40% of the page on simple multitasking methods at http://wilsonminesco.com/multitask/ .  You'll find very few labels, because they aren't needed.

I have general programming tips in that page of the 6502 primer, at http://wilsonminesco.com/6502primer/PgmTips.html .

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Sat Aug 12, 2023 8:02 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8176
Location: Midwestern USA
Dan Moos wrote:
I imagine I could use the stack to deal with nested loops sharing variables, but then I would have to add that stack management to any routine that uses loops. Perhaps not a big deal?

Unfortunately, using the stack as a scratchpad (“scratchpad” memory is what you are seeking) is, at best, cumbersome with the 6502, due to the lack of any stack-relative addressing modes.  Garth’s stacks article on his site would be your best reference for workarounds.

BigEd wrote:
In my experience, it's very rare to use the stack for data storage, in the sense of locals or parameters.

I routinely use the 65C816 stack as a scratchpad, usually by adjusting the stack pointer to reserve some stack space and then point direct page at that space.  The result is the stack space is usable for both variable storage and setup of pointers.  When the function finishes, stack cleanup is as simple as restoring DP (direct page pointer) and SP (stack pointer) to their entry values.

Much of my code passes parameters into functions via the stack.  With the 65C816, it’s simple and convenient, and by using the aforementioned technique of relocating direct page to the stack, it’s easy to read call parameters.  It’s also a simple matter to return values to the caller, either by rewriting the parameters themselves, rewriting the entry values of the registers, or both.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Sat Aug 12, 2023 8:05 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10800
Location: England
But as you surely realise, we weren't talking about the '816.


Top
 Profile  
Reply with quote  
PostPosted: Sat Aug 12, 2023 8:29 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8176
Location: Midwestern USA
BigEd wrote:
But as you surely realise, we weren't talking about the '816.

BigEd wrote:
In my experience, it's very rare to use the stack for data storage, in the sense of locals or parameters.

Nor were you. :D

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Sun Aug 13, 2023 1:14 am 
Offline

Joined: Fri Mar 18, 2022 6:33 pm
Posts: 442
Dan Moos wrote:
My other question concerns label naming for code locations. I find I am constantly wanting to use label names that are already in use somewhere else. "End:" for instance is a label name that makes sense in pretty much every sub-routine. My current solution is to have prefixes with the routine name in them. So "Write_End:" and "Read_End:" for instance. Is this the common method? Is there a better one?
I don't have a wide experience with assemblers, but the one I use (VASM) has local labels:

Code:
_Rec_char:
    lda     OUTPUT_REG
.wait:
    lda     STATUS_REG
    bne     wait
    rts

_Send_char:
    pha
.wait:
    lda     STATUS_REG
    beq     wait
    pla
    sta     OUTPUT_REG
    rts
I expect most assemblers can do something similar.

_________________
"The key is not to let the hardware sense any fear." - Radical Brad


Top
 Profile  
Reply with quote  
PostPosted: Sun Aug 13, 2023 5:58 am 
Offline

Joined: Mon Jan 19, 2004 12:49 pm
Posts: 681
Location: Potsdam, DE
For assemblers which don't have a local label, I have found the approach of including the name of the routine in which the label is required to be handy:
For example, a routine 'read_serial' might contain labels prefixed with RS_.

Neil


Top
 Profile  
Reply with quote  
PostPosted: Sun Aug 13, 2023 8:22 am 
Offline

Joined: Sat Jan 02, 2016 10:22 am
Posts: 197
I generally define some locations in zero page for "throw away" pointers and such. With labels like tempW1 or tempB3 to make it clear what they are.

They're purely used for here and now actions so it doesn't matter if they're over-written by subroutine calls. Anything needing more permanence than that will get unique locations, not necessarily in page zero though.

A comment in the code will cover what they're being used for if it's not obvious from context.


Top
 Profile  
Reply with quote  
PostPosted: Sun Aug 13, 2023 7:37 pm 
Offline
User avatar

Joined: Tue Mar 05, 2013 4:31 am
Posts: 1373
I think "best practices" can be fairly broad with assembly language. As the 65(C)02 has some specific memory areas that are "pre-defined" (Page Zero, Stack, Reset/NMI/IRQ(BRK) vectors) there are certain limitations imposed that you need to work around.

For any assembly code I do for the 65C02, I start by defining a memory map for the space I (think) I need (RAM and ROM), loosely define page zero usage and determine how much stack space the code might require. Any I/O designs also need to be included for register space, buffers, etc.

As for coding it all up, I tend to have most constants, variables, buffers, I/O devices, etc. defined in a separate file that is included in the actual assembly. I also comment what those defines are. Here's a copy of the page zero locations used for my C02 BIOS:

Code:
;       - BIOS variables, pointers, flags located at top of Page Zero
BIOS_PG0        .EQU    PGZERO_ST+48            ;Start of BIOS page 0 use ($D0-$FF, 48 bytes)
;
;       - BRK handler routine
PCL             .EQU    BIOS_PG0+00             ;Program Counter Low index
PCH             .EQU    BIOS_PG0+01             ;Program Counter High index
PREG            .EQU    BIOS_PG0+02             ;Temp Status Reg
SREG            .EQU    BIOS_PG0+03             ;Temp Stack ptr
YREG            .EQU    BIOS_PG0+04             ;Temp Y Reg
XREG            .EQU    BIOS_PG0+05             ;Temp X Reg
AREG            .EQU    BIOS_PG0+06             ;Temp A Reg
;
;       - 28L92 IRQ handler pointers and status
ICNT_A          .EQU    BIOS_PG0+07             ;Input buffer count
IHEAD_A         .EQU    BIOS_PG0+08             ;Input buffer head pointer
ITAIL_A         .EQU    BIOS_PG0+09             ;Input buffer tail pointer
OCNT_A          .EQU    BIOS_PG0+10             ;Output buffer count
OHEAD_A         .EQU    BIOS_PG0+11             ;Output buffer head pointer
OTAIL_A         .EQU    BIOS_PG0+12             ;Output buffer tail pointer
;
ICNT_B          .EQU    BIOS_PG0+13             ;Input buffer count
IHEAD_B         .EQU    BIOS_PG0+14             ;Input buffer head pointer
ITAIL_B         .EQU    BIOS_PG0+15             ;Input buffer tail pointer
OCNT_B          .EQU    BIOS_PG0+16             ;Output buffer count
OHEAD_B         .EQU    BIOS_PG0+17             ;Output buffer head pointer
OTAIL_B         .EQU    BIOS_PG0+18             ;Output buffer tail pointer
UART_IRT        .EQU    BIOS_PG0+19             ;SC28L92 Interrupt Status byte
;
;       - Real-Time Clock variables
; These are repurposed for adding a Realtime clock chip (DS1501/DS1511)
; The Ticks, Seconds, Minutes and Hours remain the same in function.
; The 16-bit Days variable is replaced however.
; - The DAY_DATE is a new variable. To minimize Page Zero usage, it has two functions
;       Bits 0-4 represent the days of the Month 1-31
;       Bits 5-7 represent the Day of the Week, 1-7 (Saturday=1)
; The Months are handled by the upper 4 bits of the MONTH_YEAR variable
; The Century is handled by a the Year (0-255) and the lower 4 bits of the MONTH_YEAR variable
;
TICKS           .EQU    BIOS_PG0+20             ;Number of timer countdowns = 1 second (100)
SECS            .EQU    BIOS_PG0+21             ;Seconds: 0-59
MINS            .EQU    BIOS_PG0+22             ;Minutes: 0-59
HOURS           .EQU    BIOS_PG0+23             ;Hours: 0-23
DAY_DATE        .EQU    BIOS_PG0+24             ;Day: (bits 5-7) Date: (bits 0-4)
MONTH_CENTURY   .EQU    BIOS_PG0+25             ;Month: (bits 4-7) Century: (bits 0-3)
YEAR            .EQU    BIOS_PG0+26             ;Century 0-255 plus 4 bits as noted above
RTC_TEMP        .EQU    BIOS_PG0+27             ;Temp work byte for updating shared variables
;
;       - Delay Timer variables
MSDELAY         .EQU    BIOS_PG0+28             ;Timer delay countdown byte (255 > 0)
SETMS           .EQU    BIOS_PG0+29             ;Set timeout for delay routines - BIOS use only
DELLO           .EQU    BIOS_PG0+30             ;Delay value BIOS use only
DELHI           .EQU    BIOS_PG0+31             ;Delay value BIOS use only
;
;       - Count variables for 10ms benchmark timing
MS10_CNT        .EQU    BIOS_PG0+32             ;10ms Count variable
SECL_CNT        .EQU    BIOS_PG0+33             ;Seconds Low byte count
SECH_CNT        .EQU    BIOS_PG0+34             ;Seconds High byte count
;
;       - Address and pointers for IDE Interface
LBA_ADDR_LOW    .EQU    BIOS_PG0+35             ;LBA Transfer Address low byte
LBA_ADDR_HIGH   .EQU    BIOS_PG0+36             ;LBA Transfer Address high byte
;
LBA_XFER_CNT    .EQU    BIOS_PG0+37             ;LBA Transfer Count 1-xx (check RAM space!)
LBA_LOW_BYTE    .EQU    BIOS_PG0+38             ;LBA Block number bits 0-7
LBA_HIGH_BYTE   .EQU    BIOS_PG0+39             ;LBA Block number bits 8-15
LBA_EXT_BYTE    .EQU    BIOS_PG0+40             ;LBA Block number bits 16-23
;
BIOS_XFERL      .EQU    BIOS_PG0+41             ;BIOS Move Routine low byte
BIOS_XFERH      .EQU    BIOS_PG0+42             ;BIOS Move Routine high byte
BIOS_XFERC      .EQU    BIOS_PG0+43             ;BIOS Block Count moved (needs to be set)
;
IDE_STATUS_RAM  .EQU    BIOS_PG0+44             ;IDE RAM-Based Status
;
SPARE_B0        .EQU    BIOS_PG0+45             ;Spare byte 0
SPARE_B1        .EQU    BIOS_PG0+46             ;Spare byte 1
;
;       - Timer/Counter Match flag for Delay/Benchmark
MATCH           .EQU    BIOS_PG0+47             ;Bit7 used for Delay, Bit6 used for Benchmark
                                                ;Bits 3,2,1 used for IDE Interrupt Handler
;


In the Monitor code, some variables are marked as TEMP1, TEMP2, etc., but also includes comments for which routines use them, as in some cases they are shared to conserve page zero space. You as the programmer get to make sure that routines used don't clobber things that are in use by other routines... which really isn't difficult... shown below:

Code:
;
PGZERO_ST       .EQU    $A0                     ;Start of Monitor Page 0 use ($A0-$CF, 48 bytes)
;
BUFF_PG0        .EQU    PGZERO_ST+00            ;Default Page zero location for Monitor buffers
;
INBUFF          .EQU    BUFF_PG0+00             ;Input Buffer - 4 bytes ($A0-$A3)
DATABUFF        .EQU    BUFF_PG0+04             ;Data Buffer - 6 bytes ($A4-$A9)
;
;       - 16-bit variables:
HEXDATAH        .EQU    PGZERO_ST+10            ;Hexadecimal input
HEXDATAL        .EQU    PGZERO_ST+11
BINVALL         .EQU    PGZERO_ST+12            ;Binary Value for HEX2ASC
BINVALH         .EQU    PGZERO_ST+13
COMLO           .EQU    PGZERO_ST+14            ;User command address
COMHI           .EQU    PGZERO_ST+15
INDEXL          .EQU    PGZERO_ST+16            ;Index for address - multiple routines
INDEXH          .EQU    PGZERO_ST+17
TEMP1L          .EQU    PGZERO_ST+18            ;Index for word temp value used by Memdump
TEMP1H          .EQU    PGZERO_ST+19
TEMP2L          .EQU    PGZERO_ST+20            ;Index for Text entry
TEMP2H          .EQU    PGZERO_ST+21
PROMPTL         .EQU    PGZERO_ST+22            ;Prompt string address
PROMPTH         .EQU    PGZERO_ST+23
SRCL            .EQU    PGZERO_ST+24            ;Source address for memory operations
SRCH            .EQU    PGZERO_ST+25
TGTL            .EQU    PGZERO_ST+26            ;Target address for memory operations
TGTH            .EQU    PGZERO_ST+27
LENL            .EQU    PGZERO_ST+28            ;Length address for memory operations
LENH            .EQU    PGZERO_ST+29
;
;       - 8-bit variables and constants:
BUFIDX          .EQU    PGZERO_ST+30            ;Buffer index
BUFLEN          .EQU    PGZERO_ST+31            ;Buffer length
IDX             .EQU    PGZERO_ST+32            ;Temp Indexing
IDY             .EQU    PGZERO_ST+33            ;Temp Indexing
TEMP1           .EQU    PGZERO_ST+34            ;Temp - Code Conversion routines
TEMP2           .EQU    PGZERO_ST+35            ;Temp - Memory/EEPROM/SREC routines - Disassembler
TEMP3           .EQU    PGZERO_ST+36            ;Temp - EEPROM/SREC routines
CMDFLAG         .EQU    PGZERO_ST+37            ;Command Flag, bit specific, used by many routines
OPXMDM          .EQU    PGZERO_ST+38            ;Saved Opcode/Xmodem Flag variable
;
;       - Xmodem transfer variables
CRCHI           .EQU    PGZERO_ST+39            ;CRC hi byte  (two byte variable)
CRCLO           .EQU    PGZERO_ST+40            ;CRC lo byte - Operand in Disassembler
CRCCNT          .EQU    PGZERO_ST+41            ;CRC retry count - Operand in Disassembler
PTRL            .EQU    PGZERO_ST+42            ;Data pointer lo byte - Mnemonic in Disassembler
PTRH            .EQU    PGZERO_ST+43            ;Data pointer hi byte - Mnemonic in Disassembler
BLKNO           .EQU    PGZERO_ST+44            ;Block number
;
;        - Macro Loop Counter variables
LPCNTL          .EQU    PGZERO_ST+45            ;Loop Count low byte
LPCNTH          .EQU    PGZERO_ST+46            ;Loop Count high byte
;
;       - Spare Monitor byte for future use
SPARE_M0        .EQU    PGZERO_ST+47            ;Spare Monitor page zero byte
;


For overall memory mapping on the 65C02, I start with ROM from the top down, which includes a JMP table to access core routines, and buffers, soft vectors and such from the bottom up. I also put all hardware devices on page $FE for my hardware designs. This tends to give me a larger contiguous free RAM space for any other code (user code for DOS/65, etc.). For Page Zero, I define space I use for BIOS, Monitor, Other programs from the top down. This provides the largest contiguous Page Zero free space starting at $0000 for other user program usage.

Not sure if this helps much, but I also tend to heavily comment code, just so I can go back to it years later and get a quick read on what it all does. This is not the same as commenting each code instruction, but if you were to look at the C02 Pocket BIOS and Monitor source code, you'll get an idea of what I'm referring to.

_________________
Regards, KM
https://github.com/floobydust


Top
 Profile  
Reply with quote  
PostPosted: Thu Aug 31, 2023 11:45 pm 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
Local labels are quite a common assembler feature - though not a universal one, and various syntaxes are used. I tend to adopt ca65's syntax (ca65 is the assembler included with cc65, a C compiler targeting the 6502 family), as follows:
Code:
; Standard labels
; These have global validity within the file, and can be exported to be visible to the linker.
; Typically use these to mark subroutine entry points and locations of static data.
PrintHexByte:
  PHA
  LSR A
  LSR A
  LSR A
  LSR A
  JSR PrintHexDigit
  PLA
  AND #$F
  JMP PrintHexDigit

; Cheap local labels
; These have validity in-between standard labels, and can be used to implement loops and escapes within a subroutine.
; They do not conflict with identically named labels on the far side of a standard label.
  BNE @end
@end:
  BEQ @end

; Anonymous (unnamed) labels
; These are very useful for implementing small loops and if-else blocks, since you don't have to come up with a name for each one.
  CPX #0
  BEQ :++   ; branches to next-but-one anonymous label (at RTS)
: DEX
  BNE :-    ; branches to previous anonymous label (at DEX)
: RTS


Top
 Profile  
Reply with quote  
PostPosted: Fri Oct 27, 2023 2:43 pm 
Offline

Joined: Tue Jul 05, 2005 7:08 pm
Posts: 993
Location: near Heidelberg, Germany
Btw - as a little teaser... xa65 will get in 2.4. also cheap and unnamed local labels with ca65 syntax.
Should be out in a week or two.

André

_________________
Author of the GeckOS multitasking operating system, the usb65 stack, designer of the Micro-PET and many more 6502 content: http://6502.org/users/andre/


Top
 Profile  
Reply with quote  
PostPosted: Fri Oct 27, 2023 7:16 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8176
Location: Midwestern USA
fachat wrote:
Btw - as a little teaser... xa65 will get in 2.4. also cheap and unnamed local labels with ca65 syntax.
Should be out in a week or two.

Didn’t know you were still maintaining xa65.  Cool!

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 21 posts ]  Go to page 1, 2  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 11 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: