Newbie 6502 assembly questions...

Discuss all aspects of programming here. From 8-bit through to modern architectures.
Post Reply
jregel
Posts: 134
Joined: Fri Dec 20, 2013 6:39 pm
Location: Gloucestershire
Contact:

Newbie 6502 assembly questions...

Post by jregel » Tue Feb 12, 2019 10:26 pm

I’m having a bit of a dabble in assembler on the BBC, and wanted to double check a few things before I get in too deep.

My experiment is to write a series of 16x16 tiles to the screen in mode 1.

I’ve defined, in BASIC, a block of memory to contain the tile data:

DIM tiledata 4096

Each tile 64 bytes, so I want to calculate an offset depending on which tile I want to lookup, and then add this offset to the base address of “tiledata” to find the starting address for the tile I want to copy.

My reading so far suggests that the way to do this is to use two bytes in zero page to contain the base address of the tiledata memory, then add the offset to it, and finally LDA it using indirect addressing.

Is that right?

If so, what’s the correct way to get the address of “tiledata” as defined in BASIC?

Thanks
BBC Master Turbo
Retroclinic External Datacentre
VideoNuLA
PiTubeDirect with Pi Zero

User avatar
dv8
Posts: 243
Joined: Mon Jun 22, 2009 9:07 pm
Contact:

Re: Newbie 6502 assembly questions...

Post by dv8 » Tue Feb 12, 2019 11:22 pm

jregel wrote:
Tue Feb 12, 2019 10:26 pm
My reading so far suggests that the way to do this is to use two bytes in zero page to contain the base address of the tiledata memory, then add the offset to it, and finally LDA it using indirect addressing.
Is that right?
Yes, LDA (zp),Y will allow you to access the tile data indexed by Y once you have the tile address in a zero-page pointer.
jregel wrote:
Tue Feb 12, 2019 10:26 pm
If so, what’s the correct way to get the address of “tiledata” as defined in BASIC?
One way would be to add tiledata as an extra argument when you CALL your machine code routine. You could then pick up the value from the CALL parameter block at &0600.

But it would probably be easier just to store the address directly in a zero-page location, e.g.

!&70=tiledata:?&72=tilenum

The machine code routine could then calculate the tile address like this:

\ Multiply tile number by 64
LDA #0
LSR &72:ROR A
LSR &72:ROR A
\ Add to the base address
ADC &70:STA &70
LDA &72:ADC &71:STA &71

&70/71 now contains a pointer to the start of the tile and can be accessed with LDA (&70),Y

I'll leave it as an exercise to figure out how two right shifts can multiply by 64 and why I've (intentionally) left out a CLC before the ADC.

jregel
Posts: 134
Joined: Fri Dec 20, 2013 6:39 pm
Location: Gloucestershire
Contact:

Re: Newbie 6502 assembly questions...

Post by jregel » Sun Feb 17, 2019 10:34 pm

Thanks for the clarification that my use of zero page is basically correct.

I hadn't thought of using indirection operators to set the value of the address directly, but it seems like the most straightforward way to do it.

My experimental code does a bunch of ASL and ROL instructions to get the multiplier, so I was thrown a bit by you going the other way. However, after several evenings(!) of pen and paper, and later, whiteboard, I think I've got the multiplying by 64 sorted. I need a bit more time to make sure I understand it, but will follow up in a couple of days.

Re: the absence of a CLC before the ADC, which ADC were you referring to? (or was it both?)

My follow on question though is that while your approach is more efficient than mine, it isn't intuitive to me (vs the ASL/ROL instructions). Would you say that your method is common and straightforward, intermediate level, or deliberately very clever? I'm just trying to get a sense of just how much of my brain will need rewiring!

Thanks
BBC Master Turbo
Retroclinic External Datacentre
VideoNuLA
PiTubeDirect with Pi Zero

User avatar
tricky
Posts: 3674
Joined: Tue Jun 21, 2011 8:25 am
Contact:

Re: Newbie 6502 assembly questions...

Post by tricky » Sun Feb 17, 2019 10:51 pm

The first ADC, but maybe that was part of the exercise.
I would write it like that, but I wouldn't recommend it until you start thinking like that.
You may find yourself needing the bytes or cycles, but probably not in your first few projects.
You will probably do better to concentrate on readability in case you want someone else to read your code to start with.
I do enjoy micro optimisations and have been known to rearrange many hundreds of bytes of code to swap a jmp for a b?? Just to save 1 byte at the end of a project.

User avatar
dv8
Posts: 243
Joined: Mon Jun 22, 2009 9:07 pm
Contact:

Re: Newbie 6502 assembly questions...

Post by dv8 » Mon Feb 18, 2019 11:43 am

jregel wrote:
Sun Feb 17, 2019 10:34 pm
My experimental code does a bunch of ASL and ROL instructions to get the multiplier, so I was thrown a bit by you going the other way.
If your code is a bunch of 6 ASL:ROL instruction pairs then that is a good first attempt. The main concept to grasp is that multiplying and dividing by powers of two translates directly into bit shifting. There's no need to use a general purpose multiply routine.
jregel wrote:
Sun Feb 17, 2019 10:34 pm
Re: the absence of a CLC before the ADC, which ADC were you referring to? (or was it both?)
Like tricky says, it was referring to the first one. The carry needs to be clear before starting the addition.
jregel wrote:
Sun Feb 17, 2019 10:34 pm
My follow on question though is that while your approach is more efficient than mine, it isn't intuitive to me (vs the ASL/ROL instructions). Would you say that your method is common and straightforward, intermediate level, or deliberately very clever? I'm just trying to get a sense of just how much of my brain will need rewiring!
It isn't a particularly clever optimisation but, like many things in programming, it can be confusing at first until you 'get it'. I would say it's a typical approach taken by anyone familiar enough with 6502, so probably intermediate level.

Think of it like multiplying a number by 5 in decimal. You could do it the long way, like we're taught in school, but at some point you realise that it's quicker and easier to first multiply by 10 (just add a 0 at the end of the number) and then divide by 2.

As an experiment, try multiplying a number by 256 (8 shifts left). Put any number in the low byte and zero in the high byte. Perform the shifts and see what numbers you end up with in the high and low bytes. Now think of a quicker way to achieve the same result. Finally, think of a way of getting from x256 to x64.

Tricky's point about optimisations is well taken and, ultimately, you should go with the approach that you're most comfortable with. Getting it to work is the most important first step. The other stuff can come later.

User avatar
kieranhj
Posts: 808
Joined: Sat Sep 19, 2015 10:11 pm
Location: Farnham, Surrey, UK
Contact:

Re: Newbie 6502 assembly questions...

Post by kieranhj » Mon Feb 18, 2019 3:22 pm

One thing you’ll quickly feel is how long-winded anything beyond 8-bit maths can be on the 6502. If you can afford the RAM then a precalculated lookup table is almost always going to be quicker & more convenient, particularly when separated into low and high byte tables not paired.

Also to generalise massively (!!) there are very few general solutions in 6502 - pretty much everything has to be hand coded for the specific task required - but that is most of the fun!
Bitshifters Collective | Retro Code & Demos for BBC Micro & Acorn computers | https://bitshifters.github.io/

cmorley
Posts: 968
Joined: Sat Jul 30, 2016 7:11 pm
Location: Oxford
Contact:

Re: Newbie 6502 assembly questions...

Post by cmorley » Mon Feb 18, 2019 3:57 pm

kieranhj wrote:
Mon Feb 18, 2019 3:22 pm
One thing you’ll quickly feel is how long-winded anything beyond 8-bit maths can be on the 6502.
One trick when speed isn't essential is to use the BASIC ROM as a library for functions. They are all listed in books like the BASIC ROM User Guide (PDF on the forum I think... reminds me I have 2 copies and need to sell one on!). Set up your numbers and JSR to the BASIC ROM. Hey presto arithmetic, trig functions, random numbers etc all available to your assembler program. BASIC uses 32 bit signed integers.

It can be useful to get things started when you just want your code to do something instead of spending ages searching the internet for 6502 routines. :)

jregel
Posts: 134
Joined: Fri Dec 20, 2013 6:39 pm
Location: Gloucestershire
Contact:

Re: Newbie 6502 assembly questions...

Post by jregel » Fri Feb 22, 2019 1:17 pm

Thanks everyone for the words of wisdom!

I'm taking Tricky's advice focussing on readability at this stage, so I'm just trying to get a good result, without trying to optimise it yet. I also like the idea of calling the BASIC routines when appropriate.

A couple of evenings ago, I managed to draw a line of tiles(!):
01.PNG
And then last night, succeeded in drawing a grid:
02.PNG
So it's going very slowly, but I'm feeling very pleased with myself right now. :-)

One question I do have: If I run the code, then manually call the address of PROG% (as above), it works. If I put the "CALL PROG%" statement in the BASIC program, it errors with " at line 2160" which is the line containing the CALL statement. Please can someone shed some light on this?

Thanks!
BBC Master Turbo
Retroclinic External Datacentre
VideoNuLA
PiTubeDirect with Pi Zero

cmorley
Posts: 968
Joined: Sat Jul 30, 2016 7:11 pm
Location: Oxford
Contact:

Re: Newbie 6502 assembly questions...

Post by cmorley » Fri Feb 22, 2019 2:12 pm

jregel wrote:
Fri Feb 22, 2019 1:17 pm
So it's going very slowly, but I'm feeling very pleased with myself right now. :-)

One question I do have: If I run the code, then manually call the address of PROG% (as above), it works. If I put the "CALL PROG%" statement in the BASIC program, it errors with " at line 2160" which is the line containing the CALL statement. Please can someone shed some light on this?
Great stuff. Can you post a minimal example with your problem?

I just tried:

Code: Select all

10 P%=&C00
20 [
30 LDA#65:JSR&FFEE
40 RTS
50 ]
60 P%=&C00
70 CALL P%
It prints an A as expected.

User avatar
BigEd
Posts: 2591
Joined: Sun Jan 24, 2010 10:24 am
Location: West
Contact:

Re: Newbie 6502 assembly questions...

Post by BigEd » Fri Feb 22, 2019 3:25 pm

Similarly, but possibly closer to jregel's code:

Code: Select all

   10 DIM PROG% 999
   20 P%=PROG%
   30 [
   40 LDA#65:JSR&FFEE
   50 RTS
   60 ]
   70 CALL PROG%
Does the code have any effect, or is the error the only thing which happens?

User avatar
dv8
Posts: 243
Joined: Mon Jun 22, 2009 9:07 pm
Contact:

Re: Newbie 6502 assembly questions...

Post by dv8 » Fri Feb 22, 2019 3:28 pm

jregel wrote:
Fri Feb 22, 2019 1:17 pm
One question I do have: If I run the code, then manually call the address of PROG% (as above), it works. If I put the "CALL PROG%" statement in the BASIC program, it errors with " at line 2160" which is the line containing the CALL statement. Please can someone shed some light on this?
It's actually failing in both cases with a blank error message (BASIC adds the 'at line' message if you're running a program).
You can tell it failed when called manually because there's a couple of blank lines between the CALL command and the following > prompt.

An error happens when the CPU executes a BRK instruction (opcode 00) so your code has probably jumped into an area of memory filled with zero.

Without seeing the code it's difficult to say what the problem is but look out for:

* JMP/JSR to areas of memory outside your program
* the use of an indirect JMP (addr) instead of absolute JMP addr
* overwriting the stack memory at &100-&1FF
* unbalanced stack operations such as mismatched JSR/RTS or PHA/PLA etc.

In particular, check if you unintentionally JMP into a routine (instead of JSR) then RTS from it, or if you don't return from a routine and it wanders off into the empty memory after your code.

User avatar
tricky
Posts: 3674
Joined: Tue Jun 21, 2011 8:25 am
Contact:

Re: Newbie 6502 assembly questions...

Post by tricky » Fri Feb 22, 2019 3:32 pm

You could run your code on an emulator and set a breakpoint at PROG%

jregel
Posts: 134
Joined: Fri Dec 20, 2013 6:39 pm
Location: Gloucestershire
Contact:

Re: Newbie 6502 assembly questions...

Post by jregel » Fri Feb 22, 2019 5:07 pm

dv8 wrote:
Fri Feb 22, 2019 3:28 pm
jregel wrote:
Fri Feb 22, 2019 1:17 pm
One question I do have: If I run the code, then manually call the address of PROG% (as above), it works. If I put the "CALL PROG%" statement in the BASIC program, it errors with " at line 2160" which is the line containing the CALL statement. Please can someone shed some light on this?
It's actually failing in both cases with a blank error message (BASIC adds the 'at line' message if you're running a program).
You can tell it failed when called manually because there's a couple of blank lines between the CALL command and the following > prompt.

An error happens when the CPU executes a BRK instruction (opcode 00) so your code has probably jumped into an area of memory filled with zero.

Without seeing the code it's difficult to say what the problem is but look out for:

* JMP/JSR to areas of memory outside your program
* the use of an indirect JMP (addr) instead of absolute JMP addr
* overwriting the stack memory at &100-&1FF
* unbalanced stack operations such as mismatched JSR/RTS or PHA/PLA etc.

In particular, check if you unintentionally JMP into a routine (instead of JSR) then RTS from it, or if you don't return from a routine and it wanders off into the empty memory after your code.
Thanks for the suggestions, all.

It was missing a PLA before the final RTS.
BBC Master Turbo
Retroclinic External Datacentre
VideoNuLA
PiTubeDirect with Pi Zero

User avatar
BigEd
Posts: 2591
Joined: Sun Jan 24, 2010 10:24 am
Location: West
Contact:

Re: Newbie 6502 assembly questions...

Post by BigEd » Fri Feb 22, 2019 5:37 pm

Kudos to dv8 there I think.

jregel
Posts: 134
Joined: Fri Dec 20, 2013 6:39 pm
Location: Gloucestershire
Contact:

Re: Newbie 6502 assembly questions...

Post by jregel » Sun Mar 03, 2019 3:25 pm

A few more quick questions as I continue my dive into assembler, primarily around doing things “correctly”…

If I have the following code:

Code: Select all

10 DIM PROG 100
20 SCREEN=&3000
30 P%=PROG
40 [
50 LDA #0
60 STA SCREEN
70 RTS
80 ]
90 CALL PROG
Firstly, as the DIM statement reserves 101 bytes, does it matter if the variable is PROG or PROG%? Does the data type actually make a difference?

Secondly, should I store the address of the screen in an integer variable, SCREEN%, in preference to the real variable, SCREEN? I would assume this would save more memory (4 byte integer vs 5 byte real), and also store the address &3000 in a more easily read format?

Finally, if I use an integer variable, SCREEN%, am I effectively wasting two bytes per every variable, since it’s only using the lower two bytes? I've seen this referenced in one of the assembly books I've read, but that seems like a waste of memory.

I was planning on defining a bunch of memory addresses as variables in BASIC to allow for use as labels in the assembler and make things more readable. Is this reasonable, or am I missing a better approach?

Thanks, as ever, for the guidance!
BBC Master Turbo
Retroclinic External Datacentre
VideoNuLA
PiTubeDirect with Pi Zero

User avatar
tricky
Posts: 3674
Joined: Tue Jun 21, 2011 8:25 am
Contact:

Re: Newbie 6502 assembly questions...

Post by tricky » Sun Mar 03, 2019 6:17 pm

BITD we would use the shortest possible variable names to save memory, but now I would say that if you run out of memory, swap to beebasm and keep your variables meaningful.

jregel
Posts: 134
Joined: Fri Dec 20, 2013 6:39 pm
Location: Gloucestershire
Contact:

Re: Newbie 6502 assembly questions...

Post by jregel » Tue Mar 05, 2019 4:31 pm

Thanks Tricky. I'm nowhere near running out of memory yet, just trying to get a firmer understanding about how to do things right, primarily around how BASIC and the assembler interact.
BBC Master Turbo
Retroclinic External Datacentre
VideoNuLA
PiTubeDirect with Pi Zero

User avatar
tricky
Posts: 3674
Joined: Tue Jun 21, 2011 8:25 am
Contact:

Re: Newbie 6502 assembly questions...

Post by tricky » Tue Mar 05, 2019 4:39 pm

I used to use % to assemble faster, but I don't know if it was actually faster :lol:

fuzzel
Posts: 426
Joined: Sun Jan 02, 2005 1:16 pm
Location: Cullercoats, North Tyneside
Contact:

Re: Newbie 6502 assembly questions...

Post by fuzzel » Tue Mar 19, 2019 8:24 pm

I too would like to learn assembly language. Can anyone recommend a book to get started ?
Preferably one that's been pdf'd otherwise it might be hard to track down.

User avatar
dv8
Posts: 243
Joined: Mon Jun 22, 2009 9:07 pm
Contact:

Re: Newbie 6502 assembly questions...

Post by dv8 » Tue Mar 19, 2019 8:48 pm


fuzzel
Posts: 426
Joined: Sun Jan 02, 2005 1:16 pm
Location: Cullercoats, North Tyneside
Contact:

Re: Newbie 6502 assembly questions...

Post by fuzzel » Tue Mar 19, 2019 9:25 pm

By a funny coincidence I was just reading the first ever issue of the Micro User a couple of days ago and noticed a review of Assembly Language Programs for the BBC Microcomputer by said Ian Birnbaum (I remembered the unusual name). Just rereading the article it says "its rivals will have a great deal to do to be better than this". So top pick thanks !

fuzzel
Posts: 426
Joined: Sun Jan 02, 2005 1:16 pm
Location: Cullercoats, North Tyneside
Contact:

Re: Newbie 6502 assembly questions...

Post by fuzzel » Sun Apr 14, 2019 5:57 pm

Can anyone explain the difference between LSR and ROR (and also of course ASL and ROL) ?
I understand I can use them to double or halve numbers but they both produce the same result.

User avatar
0xC0DE
Posts: 290
Joined: Tue Mar 19, 2019 7:52 pm
Location: The Netherlands
Contact:

Re: Newbie 6502 assembly questions...

Post by 0xC0DE » Sun Apr 14, 2019 6:16 pm

ASL and LSR shift bits left and right respectively.
ROL and ROR rotate bits left and right respectively.
The difference between shifting and rotating is the use of the carry flag.
Rotating: the carry flag is shifted into the byte and the bit shifted out is moved to the carry. So you rotate through the carry bit.
Shifting: the bit shifted out is moved to the carry. But the previous carry bit is NOT shifted in. Instead, a 0 bit is shifted in.

So:
ASL: C <- [76543210] <- 0
LSR: 0 -> [76543210] -> C
ROL: (new)C <- [76543210] <- (old)C
ROR: (old)C -> [76543210] -> (new)C

Note, there is no ASR instruction. Instead, use ROR with C set to bit 7 of the byte to be shifted.
There is no LSL instruction either because it is equal to ASL.
0xC0DE
:idea: Follow me on Twitter :idea: Visit my YouTube channel featuring my demos for Acorn Electron and BBC Micro

fuzzel
Posts: 426
Joined: Sun Jan 02, 2005 1:16 pm
Location: Cullercoats, North Tyneside
Contact:

Re: Newbie 6502 assembly questions...

Post by fuzzel » Sun Apr 14, 2019 7:11 pm

Thanks (I think!). I'll do some examples to get my head round it.

User avatar
BigEd
Posts: 2591
Joined: Sun Jan 24, 2010 10:24 am
Location: West
Contact:

Re: Newbie 6502 assembly questions...

Post by BigEd » Sun Apr 14, 2019 7:19 pm

It might help to SEC before shifting or rotating.

Whatever A and C, nine rotates gets you back to where you started. But 8 shifts will clear A, and a ninth shift will clear C too.

Just possibly you'll find this tutorial and in-browser emulator helpful:
http://skilldrick.github.io/easy6502

User avatar
tricky
Posts: 3674
Joined: Tue Jun 21, 2011 8:25 am
Contact:

Re: Newbie 6502 assembly questions...

Post by tricky » Sun Apr 14, 2019 9:22 pm

Also, try on 16bit numbers.

Post Reply