Jump to content

Photo

Learn Assembly in 8 hours with bB and the ASDK

Framework Tutorial RAD Assembly bB Assembly tutorial lightcycles gamegrid programming

13 replies to this topic

#1 Mr SQL OFFLINE  

Mr SQL

    Stargunner

  • 1,754 posts

Posted Thu Mar 28, 2013 12:30 PM

titlepagelabel_2.jpg

LEARN ASSEMBLY IN 8 HOURS
with bB and the ASDK

Tutorial Intro

This tutorial will teach you 6502 Assembly programming for the Atari 2600 using a RAD Framework that abstracts the hardware so you can quickly marshal high level objects to build games like batari BASIC.

Due to the similarities we will use BASIC examples side by side and use the bB compiler to illustrate (and to help with creating Assembly when preferred).

Peruse and complete these short lessons over time or stay up all night drinking coffee and taking breaks to play Defender :)


#2 Mr SQL OFFLINE  

Mr SQL

    Stargunner

  • Topic Starter
  • 1,754 posts

Posted Thu Mar 28, 2013 12:38 PM

Lesson one: Keep Thinking Abstractly

Assembly allows you to work at a very low level but you don’t have to! We will not be coding advanced and esoteric algorithms to manipulate the exotic hardware of the VCS.

Instead of counting cycles we’ll be on the game grid, racing the beam with our lightcycles:
Figure1_LightCycles.jpg

Marshalling high level objects with the ASDK:
The ASDK has the abstract sprite and missile objects already familiar from batari BASIC:

sprite0x, sprite0y, sprite1x, sprite1y, missile0x, missile0y, missile1y, missle1x

And a playfield object like bB, and a large virtual world object with additional properties:
Figure2_VirtualWorld.jpg
Like bB playfields, virtual worlds are defined in WYSIWYG format in your code and are loaded into RAM to render them malleable.

Virtual world pixels are x,y addressable using similar high level commands to pfpixel with extended properties to set pixels on and off the visible screen or pan the camera (also x,y addressable) to scroll anywhere around the virtual world.

Game loop and initialisation sections
Like bB, the ASDK provides a game loop for your code that runs every frame and an initialisation section that runs only on startup.

Getting started – Editing and Compiling Assembly
You already have all the tools you need! The ASDK is integrated in a single file along with your code and uses the same version of DASM that you already have installed with bB!

Best (easiest) Assembly coding practices
Get comfortable using a dual window setup as shown in lesson 2 where you can code your Assembly in one window and switch to another to quickly compile and then test it in Stella – it’s a best practice to do this with each block of code you add to your project to ensure it is working as expected; this is a good way to never have to open up the debugger in Stella or hook up an oscilloscope and multi-meter to determine what went wrong with your Assembly code.

#3 Mr SQL OFFLINE  

Mr SQL

    Stargunner

  • Topic Starter
  • 1,754 posts

Posted Thu Mar 28, 2013 1:00 PM

Lesson 2: Player sprites and missiles

Topics:
Make a sprite appear in the center of the screen
Load a different sprite besides the default
Edit sprites and expand the inline sprite library
Moving sprites around the screen using Assembly or bB
Sprite Animation using Assembly or bB


Make a sprite appear in the center of the screen
To make a sprite appear, simply set it’s X and Y coordinate variables to the desired positions.

To make a sprite vanish, you set it’s X and Y coordinate variables back to zero.

Since this is our first topic working with the ASDK we’re going to use this blank template to start our project and add our code to the initialisation and game loop sections:

Attached File  ASDKtutr.asm   72.65KB   162 downloads

Open it up for editing as shown below, find the initialisation section and add the purple code as shown:
Figure3_Assembly.jpg

Save it and then compile and launch it in the other window like this:
Figure4_Compile_Assembly.jpg

You should see the corner of the virtual world displayed in the playfield with two sprites in the center of the screen:
figure5_output.jpg

Here’s what’s going on with the purple code:
Figure6_purplecode.jpg

“LDA” is an Assembler command for loading the Accumulator which is like an internal temp variable on the CPU; there are three of these registers (A,X and Y) and access to regular variables must go through them.

“LDA #45” tells the 6502 to put the number 45 into the Accumulator
(note that the # prefix means we are using the number 45 rather than the contents of memory location 45; this is important).

The next instruction “STA player0y” does just what you think, it puts that 45 into a player sprite Y variable, player0y.

Then we load the accumulator with values for our sprites X axis variables spaced a bit apart so we can see them next to each other.

This isn’t very different in bB:
player0y=45:player0x=45:player0x=70:player1x=80

Later in this chapter we will use bB to compose and import a complex Assembly logic block for moving and animating our sprites, but first we will cover some more topics you will already find familiar.


Load a different sprite besides the default
In bB you must also load a sprite definition, we didn’t do that above but if you scroll down the init section a bit you will see this is done much the same way:
Figure7_Assembly.jpg

Again, here’s what’s going on with the purple code:

“LDY #0” is an Assembler command for loading the processor’s Y register with the number 0 (#0); in this case we are using the Y register to pass an argument to our sprite loading subroutines; “JSR” is just GOSUB.

I’ve split the screen to show the inline sprite image library section; you can probably guess that arguments #0 and #8 load images 1 and 2 respectively.

You can change the Y argument values to load different images from the sprite library either in the initialisation section like we are doing now, or on the fly in our game loop for animation sequences which we will do later J


Edit sprites and expand the inline sprite library
Go ahead and edit the WYSIWYG sprite definitions you see or add additional sprite definitions – there are about 10 in the inline library right now and there are room for 32 sprite images (it’s not a space issue just the constraints of the table size).


Moving sprites around the screen using Assembly or bB
We can use the bB compiler to write complex Assembly logic blocks for the ASDK both to help learn Assembly and for reusing complicated routines without rewriting them! This works out very well in practice.

Here’s a bB routine to move the player sprites all the way across and halfway across the screen, respectively:
Figure8_bBcode.jpg

We just compile it and insert the generated Assembly block into the Game loop section of the ASDK as shown:
Figure9_bBtoASM.jpg

Then we can save and compile our ASDK file and the code works fine!

Here’s what’s going on in the compiler generated code, it’s not too different than BASIC:

“LDA player1x” loads the player1x variable into the Accumulator,
“CMP #150” compares the contents of the Accumulator to the number 150.
“BNE .skipL014” means Branch if not equal to or “goto .skipL014 if player1x <> 150”
“INC player1x” means increment the player1x variable directly or “player1x = player1x+1”
The rest is the same.

Now let’s add some sprite animation!

Sprite animation using Assembly or bB
We already know that we can call loadplayer0 with an offset argument to load the selected image into the player0 sprite from the image library, but this function has no counterpart in the bB framework so what do we do if we want to model sprite animation with bB? We use a placeholder in place of this function and write the rest of our code:
Figure10_bBcode.jpg

Now we simply paste in the generated Assembly block like we did before, but we edit our code as shown to replace the dummy code with the function call to load the sprite, passing our f variable argument:
Figure11_bBtoASM.jpg
(Note: Don’t forget to also init f to 24 in the init section since we want f to start at image 4)

Compile the code and you’ve got the smooth animation demo!

Here’s what’s going on:

“LDA f” loads the Accumulator with the variable f
“CLC” clears the carry bit (we don’t want it when we add unless we’re doing 16-bit calcs)
“ADC #8” adds 8 to the contents of the Accumulator.
“STA f” stores the new value back in variable f.
“LDA f” redundantly loads var f back into the Accumulator (it’s still there)
“CMP #72” compares the Accumulator to the #72
“bne .skipL026” jumps around the next two statements if var f <>72 (from our CMP statement).
Those next two statements reinitialise var f to 24.

Chapter review
You’ve learned quite a bit of Assembly already! Now review your source code and compare it to the pure Assembly example used in the Abstract Assembly Development Kit Thread.

Your Assembly source should look like this at the conclusion of this lesson:
Attached File  ASDKtutr3.asm   74.26KB   140 downloads

And here is the Pure Assembly source from the ASDK thread to compare.

Note the Assembly in both examples is nearly the same (the bB compiler is excellent)!

However you will also notice the pure Assembly example is easier to follow with it’s friendly naming conventions; by contrast, the compiler cannot come up with descriptive and imaginative names but it does the next best thing by putting the bB code in comments (I always look at that).

It’s important to review both examples together; Assembly output from a compiler is more cryptic and confusing to review without the bB source. For this reason you should never dissemble in Stella; if you want to look at how an interesting classic was programmed search AtariAge instead – other developers have probably already dissembled, analysed and produced a friendly or well commented source listing; that’s the kind of Assembly you will absorb best.


Looking ahead
Some really fun things are coming up next in lesson 3, particularly as we connect our examples together!

Lesson 3 preview: Virtual Worlds and Playfields

Topics:
Load a different virtual world (just like loading another playfield in bB).
Edit virtual worlds (like editing a playfield with bB).
Set pixels anywhere in the virtual world
Check pixel status and flip them
Pan the camera to any part of the virtual world
Scrolling around the virtual world using Assembly or bB.


#4 Gemintronic OFFLINE  

Gemintronic

    Jason S. - Lead Developer & CEO

  • 8,854 posts

Posted Thu Mar 28, 2013 1:22 PM

Just by glancing this looks like good stuff. I'm coming dangerously close to understanding what you're saying about assembly in the tutorials. Will read and reread at home.

My only feedback is:

* Not everyone knows that RAD stands for Rapid Application Development :)
* Although I've heard rumors to the contrary Win 7/8 64 bit doesn't seem to have DOS edit. Maybe a more universal editor is needed even though I LOVE DOS EDIT with a passion.

#5 Mr SQL OFFLINE  

Mr SQL

    Stargunner

  • Topic Starter
  • 1,754 posts

Posted Thu Mar 28, 2013 4:02 PM

Thanks Loon! Notepad or any plain editor is fine, I prefer DOS edit because it has line numbers, lets you split the screen and has a fun retro feel. :)

I'm not going to recommend Emacs over Vim or vice versa ;)


#6 SeaGtGruff OFFLINE  

SeaGtGruff

    Quadrunner

  • 5,558 posts
  • Location:Georgia, USA

Posted Thu Mar 28, 2013 8:45 PM

I prefer DOS edit because it has line numbers, lets you split the screen and has a fun retro feel.

I started out in Atari 2600 programming with the old "2600 SDK" programming IDE (called "adev.exe"), which was rather similar to DOS Edit except it had menu options for assembling your 6502 code and running the resulting ROM files in an Atari 2600 emulator. It used to be available on the old DASM web pages, but disappeared many years ago-- although I do still have it on my Windows XP computer. :)

Edited by SeaGtGruff, Thu Mar 28, 2013 8:46 PM.


#7 Mr SQL OFFLINE  

Mr SQL

    Stargunner

  • Topic Starter
  • 1,754 posts

Posted Mon Apr 1, 2013 8:01 AM

I started out in Atari 2600 programming with the old "2600 SDK" programming IDE (called "adev.exe"), which was rather similar to DOS Edit except it had menu options for assembling your 6502 code and running the resulting ROM files in an Atari 2600 emulator. It used to be available on the old DASM web pages, but disappeared many years ago-- although I do still have it on my Windows XP computer. :)

SeaGtGruff,
I'd like to check out that editor, can you post the executable (guessing it's standalone)?

Did you learn Assembly previously or with the 2600 SDK, or by working with bB and it's objects?

#8 Joe Musashi OFFLINE  

Joe Musashi

    Moonsweeper

  • 305 posts

Posted Mon Apr 1, 2013 9:06 AM

Slightly off-topic, but since we are at that... For the ultimate retro feel kick I sometimes use Cathode as a terminal and emacs in text mode when doing 6502 programming.

Unfortunately, Cathode is Mac only and not free, but it's a lot of fun.

Cathode.png

#9 Mr SQL OFFLINE  

Mr SQL

    Stargunner

  • Topic Starter
  • 1,754 posts

Posted Mon Apr 1, 2013 3:22 PM

Slightly off-topic, but since we are at that... For the ultimate retro feel kick I sometimes use Cathode as a terminal and emacs in text mode when doing 6502 programming.

Unfortunately, Cathode is Mac only and not free, but it's a lot of fun.

Cathode.png

Your Cathode environment looks comfortable, I like the markup colours and the black background. The CRT effect is really cool, had me going for a minute :) Makes me want a real CRT instead of a flatscreen again!

#10 Mr SQL OFFLINE  

Mr SQL

    Stargunner

  • Topic Starter
  • 1,754 posts

Posted Tue Apr 2, 2013 10:28 AM

Lesson 3: Virtual Worlds and Playfields


Topics:
Load a different virtual world (just like loading another playfield in bB).
Edit virtual worlds (like editing a playfield with bB).
Set pixels anywhere in the virtual world
Check pixel status and flip them
Pan the camera to any part of the virtual world
Scrolling around the virtual world using Assembly or bB.


Load a different virtual world
If you scroll down further in the initialisation section you will see the call to load the large virtual world into RAM:
Figure12_Assembly.jpg

Note: The “Superchip” used is the double superchip, or CBS RAM which sports 256 bytes instead of 128; this is important because the large virtual worlds are 240 bytes in size!

There are two other predefined virtual worlds that can be loaded with these descriptive calls:

CallBank1ToLoadCBSRAMwithLargeBitmapSayingREADYPLAYER1
CallBank1ToLoadCBSRAMwithGameScreenBitmap


Just edit the call in the initialisation section to load one of these other virtual worlds instead.

Editing Virtual Worlds
The virtual worlds are in WYSIWYG format similar to a bB playfield only “1” denotes the pixel is on and “0” means it’s off. Here is the first one, the other two are located below:
Figure13_VirtualWorldTable.jpg

There are twenty rows of 96 pixels, however the first four pixels are not visible for an effective virtual world resolution of 92x20 with a 20x10 playfield window that can be panned anywhere within.

Note that the “%” is just a prefix for the Assembler that means we are using binary and can be ignored for now (we will cover it later) since using binary to construct a bitmap image is already intuitive and visual; you can see the corner of the larger virtual world outlined above and if you look around the image you will see the word Atari and the other designs.

Play around with changing and customising the bitmap image.

Adding more virtual worlds
When we get to the lesson on bank switching (optional), you will be able to add more virtual worlds as they live in the second bank. For now you have three virtual worlds you can customise.

Building dynamic virtual worlds
It is possible to have an algorithm construct the virtual world just like with bB playfields; samples should be easily adaptable as follows.

Setting pixels on the virtual world
Figure2_VirtualWorld.jpg
bitx and bity are the x and y coordinate variables for the virtual world.
getbitstatus is the function to call.

As this is a complex function, an argument is also passed in the Accumulator:

0 – Invert the bit; off if it was on, and on if it was off.
1 – Always turn the bit on, and pass back its previous state in the Accumulator.

This gives us the ability to set, invert or poll specific pixels anywhere in the large virtual world.

Setting a pixel
Here’s an example setting pixel 1,1. Add this code to the game loop section of our latest Assembly file, asdktutr3.asm:

;---------Set pixel 1,1 on the Virtual World
lda #1
sta bitx
sta bity
lda #1; argument to pass: turn bit on
jsr getbitstatus

Compile your code and launch it in Stella to check; you should see that pixel 1,1 has been set:
Figure14_VirtualWorldPixelSet.jpg

Inverting pixels
Inverting a pixel instead of setting it done by passing a zero in the Accumulator instead of a 1, then the pixel will be flipped from whatever it’s previous state had been.

Poling pixels
Note that the function call returned the previous status of the bit in the Accumulator, it was off in this case so the value is zero; if we had wanted to know the status of the bit but didn’t want to set it we would simply set the pixel as per our example to get it’s status - if the status came back as previously clear, we could just call the function again to flip the pixel back off (the poling function is built from stacking a set with a possible invert). Here’s the example:

;---------check the status of a pixel on the virtual world:
; --------(using set and invert functions)
;---------Set pixel 1,1 on the Virtual World
lda #1
sta bitx
sta bity
lda #1; argument to pass: turn bit on
jsr getbitstatus ; set pixel 1,1
;------------------------
;--- the accumulator now has the previous status of the bit (0 or 1)
sta b ;store bit status in var b to work with it
;-- restore previous bit status with a 2nd call:
jsr getbitstatus ; invert pixel 1,1 OR set pixel 1,1 (depending upon argument)
;-------------------------

As you can see, we’ve built the poling function by just repeating the dual purpose set/flip function call again after first storing the target bit’s status in a variable.


Pan the Camera to any part of the virtual world
AtariText.jpg

For this example, we will slide the playfield over the Atari text in the large virtual world by setting the camera to position 42,9 in the virtual world:
Figure15PanCameraInVirtualWorldExample.jpg

Here’s what’s gong on with the purple code:

BITIndex is the x position variable for the camera, we’ve set it to 42.

The camera y position is a bit trickier, we are using an offset (BYTErowoffset) we increase by 12 for each y position increment, which is what the loop does; we could have done the calculation ourselves as well.

Note: There is a “hidden” Assembly instruction in the loop! DEX (decrement X) also does an automatic compare against zero:
“CPX #0”
This hidden comparison allows the BNE (branch if <>) to keep looping the code until X reaches zero.

Scrolling around the virtual world
For the next example, we’ll use the joystick controllers to scroll the camera around the virtual world! And we’ll use bB to help us build the Assembly code.

Using what we know to calculate BITIndex and BYTErowoffset this is pretty easy; we set up some aliases so we can use the ASDK variables for the camera and here we go:
Figure15B_bBExample.jpg

Compile your bB and copy the Assembly output block into the game loop section of the ASDK just as we did before:
Figure15C_bBtoAsmExample.jpg

Now compile your Assembly and test it in Stella! You should notice two things – you can’t scroll quite all the way to the left, there was a bug in the bB code we converted, it should have been “>0” not “>1”. I’ve fixed it in the Assembly output above. I left the other bug in because it allows us to scroll down past the edge of the virtual world and view other memory areas, some of which is active RAM.

Let’s take a look at the three new Assembly instructions the compiler generated:
“bit SWCHA”
“BMI” – branch if minus (if bit 7 is set)
“BVS” – branch if overflow bit is set

The bit instruction is examining the bits on the joystick port SWCHA and the two new branch instructions act on the different bits that are set when you move the joystick; we’re going to be reviewing bitwise operations later but for now you can reuse this block of Assembly like a template to hook up the joystick for your games.

Chapter Review
You now know Assembly well enough to write loops, code game logic, manipulate variables and marshal high level objects; start building some demo’s and games and you will get even better at it for the simple fact that you are actually using it!

There are many paths to learning Assembly but we learn best by doing, and achieving enlightenment on the gamegrid is the most fun :)
Figure16_Matrix.jpg

#11 Mr SQL OFFLINE  

Mr SQL

    Stargunner

  • Topic Starter
  • 1,754 posts

Posted Sun Apr 28, 2013 12:21 PM

Lesson 4: Advanced Assembly - Binary and BITwise operations


Topics:
Introduction to Advanced Assembly Language programming
Converting numbers to Binary and back again
Implied BITwise operations - leveraging the binary flags and commands
Direct BITwise operations – AND OR EOR
BIT Marshalling operations – ROL ASL ROR LSR
Hexadecimal (optional)
Binary operations beyond 8-bit (optional*)
How to tabulate game scores beyond 250,000 with just 12 bits*




Introduction to Advanced Assembly Language programming
You already know enough Assembly to code loops and game logic, but we’ve purposely glossed over using the binary condition codes while still leveraging some of them for game logic; we will now explore how the branch operations you’ve used actually run based on the processor’s condition code flags that are automatically set via different Assembly commands.

MPU.jpg
In this scene from the Walt Disney movie Tron, Sark interfaces with the Master Control Program to check the condition codes on the MPU (a retro term for CPU) to determine if Flynn should be given a LightCycle and sent to the GameGrid.


Converting Numbers to Binary and Back Again
With this exercise we must deviate from abstract thinking to go low level; here it is (and again you’ll find you already know the answers):

Binary numbers are just base 2 instead of our normal base 10 just as you learned in school. And like base 10, the most significant digits are on the left.

Refresher - with base 10, each digit increases by the power of 10 so if we look at the number 100 we’ve got a 1 in the 100’s column, and zero’s in the 10’s and 1’s column. For 112 we also have a 1 and a 2 in the 10’s and 1’s columns respectively. We know this intuitively so we don’t really have to think about it. With base 2 each column represents a power of 2 instead of a power of 10 but the rest is just the same:
So 100 becomes 1100100 in base 2; here’s the breakdown for the math:

0 in the 1’s column, 0 in the 2’s column, 1 in the 4’s column, 0 in the 8’s column, 0 in the 16’s column, 1 in the 32’s column and 1 in the 64’s column or:
64+32+0+0+4+0+0 = 100!

Notice we’re only using 7 bits in the example above; an 8-bit CPU like the 6502 can work with numbers up to 255 directly:

11111111 = 128+64+32+16+8+4+2+1 (255).
We get around this limitation by using additional bits with various constructs that we create; for a conceptual example of this, each is a row of pixels in the large virtual world is 92 pixel bits:
We could therefore store a number in each 92-bit row greater than what even a 64-bit CPU is capable of working with directly.

A simpler construct is most often built with two bytes (8-bit’s each) for storing 16-bit values up to 65,535 (1111111111111111).


Implied BITwise operations - leveraging the binary flags and commands
The various instructions (commands) of the 6502 change the value of the flag bits in the condition code register. This is a special construct where we don’t care what the number adds up to but only what the individual bits are since each of the bits are used as single binary variables.

That’s right: We can use this construct in our own code too (and everyone does, we’ll cover that next) when we want to turn a single byte into 8 binary variables. It’s important to Do The Hustle here because memory was expensive in the 70’s and we’ve only got 128 bytes of system RAM on the Atari!

Here is a list of the bit variables (also know as flag variables) in the condition code register that can get set and cleared from the different Assembly instructions:

BIT Variable: Description:
Z Zero Flag (bit 1)
C Carry Flag (bit 0)
V Overflow Flag (bit 6)
N Negative Flag (bit 7)

(There are three more but these aren’t needed; one just flags decimal mode which is odd and the other two deal with Interrupts and are superfluous; code runs in a loop so we don’t actually need a special interrupt to interrupt).

We worked with the branch instructions BEQ (branch if equal) and BNE (branch if not equal); these operations work off of the zero flag bit variable, which gets set via many Assembly instructions:

LDA #5
CMP #5
BEQ dothisroutine

In the straightforward example above we are loading the Accumulator with the number 5 and then comparing it to 5 – you can bet the code will always branch to “dothisroutine” because when you issue a CMP compare, the zero flag variable is set when it matches.

You may also recall that the CMP is curiously optional when comparing to zero:

LDA #0
;CMP #0 – commented out because it’s already been done!
BEQ dothisroutine

That’s because the zero flag is automatically set or cleared when the accumulator is loaded depending upon weather the value in the accumulator is zero or not.

Many other instructions such as INC (increment a variable [memory location]) and DEX (decrement the X register) will likewise automatically set or clear the zero flag depending upon the value in the target register or memory location. See the 6502 Reference Guide online for a complete list of the 6502 instructions and how they affect the flag/bit variables in the condition code register; knowing how and when these are set is invaluable for advanced Assembly programming – for example it’s OK to issue a STA, STY or STX command to store a register to a variable (memory location) before checking for a condition code flag that was previously set because those instructions don’t change any of the cc flags but you most often will need to examine the flag bits before issuing additional instructions or they will be lost.

Before we make the transition to direct BITwise operations, here’s one that’s a little bit of both:

BIT myvar

This instruction copies bits 6 and 7 of the target variable/memory location directly to bits 6 and 7 of the the cc register; it also set’s the zero flag like an AND mask which we’ll cover next, but unlike AND it drops the result in the bit bucket behind the CPU rather than updating the Accumulator.

We can then run our conditional branch logic right off of the bit 6 and 7 binary variables with:
BVS, BVC, BMI and BPL depending upon how these bits were set:

BVS – branch if bit variable 6 is set
BVC – branch if bit variable 6 is clear
BMI – branch if bit variable 7 is set
BPL – branch if bit variable 7 is clear

These branch commands can have other connotations, but performed after issuing BIT myvar as in our example, this is precisely what they mean.

Note on BMI and BPL: These always work off of the 8th or most significant bit (bit 7) and it is possible to create a negative number construct by reducing our otherwise 8-bit values from 255 to (–)127 through (+)127 as follows:

11111111 = -127
01111111 = +127

Can you see what we did here? We’ve created an obtuse construct where the fist bit holds the sign.

If you start thinking about these get back on your LightCycle; vague and esoteric concepts like negative numbers have no place on the GameGrid:
TankPrograms.jpg
Flynn didn’t care about negative numbers and he didn’t use them to write all those Tank programs; neither should you.

Note: a slight variation on this concept is used to jump backwards and forwards in your code every time you issue a branch instruction but this is also a meaningless construct you shouldn’t waste your time with; no one ever counts forwards or backwards 127/128 positions in memory to see if their branch commands will work – we just change them to the long branch equivalent whenever the DASM Assembler tells us “branch out of range on line xxx”. Actually you can’t do that either because the 6502 has no long branch instructions!

What you do is instead is test for the opposite condition and branch just around a JMP instruction which has the power to get wherever you were trying to go (or anywhere in memory for that matter). For example:

LDA #5
CMP #5
BEQ yourroutine

Has to be changed to this type of format if the Assembler complains that the branch to “yourroutine” is out of range:

LDA #5
CMP #5
BNE jumparoundbecausethebranchwastoofaraway
JMP yourroutine
Jumparoundbecausethebranchwastoofaraway


Direct BITwise operations AND OR EOR
These are powerful and most often applied with a bit mask using the % binary indicator. Here’s a quick refresher on DASM specific indicators:

LDA 255; no indicators at all so we load the Accumulator with whatever value is located in memory location “255”.

LDA #255; we’ve indicated we want to use a number instead of a memory location, so we load the Accumulator with the number 255.

LDA #$FF; this is the same as the above but we want to use a hexadecimal format so we load the Accumulator with the number FF (255 decimal, we’ll optionally cover hex later; it’s not required at all).

LDA #%01101000; again this is the same, but we want to use a binary format so we load the Accumulator with the number 01101000 (100 decimal like we explored earlier); this is the way we will apply a bit mask to set check or flip any of the specific bits in a byte:

LDA myvar
AND #%00000001 ; is the first bit set?
BEQ firstbitclear
JSR firstbitset
firstbitclear

Here’s what’s happening: We’ve loaded A with the variable/memory location myvar, and And’d it with a bit mask of just bit 0 “#%00000001”

Suppose myvar (now in the Accumulator) contained the number 100, or #%01101000 in binary:

00000001 (our bit mask)
AND
01101000 (value in the Accumulator)
00000000 (revised value in the Accumulator)

This is so because AND only sets the target bits if both the source bit AND the corresponding bit from the bit mask are set (quite intuitive).

Our next instruction branches to “firstbitclear” because the first bit was clear, but what if the value in the Accumulator was 101 (01101001) instead of 100?

00000001 (our bit mask)
AND
01101001 (value in the Accumulator)
00000001 (revised value in the Accumulator)

As you guessed, the code would instead jump to the subroutine “firstbitset”; that’s all there is to bit masking – you can use an AND bitmask to check for each of the bits in a byte individually (such as when you’d like to create a group of bit variables out of a single byte).

To set a target bit in a byte we can use OR:

LDA myvar
ORA #%00000001; set first bit

Suppose myvar (now in the Accumulator) contained the number 100 (01101000 in binary):

00000001 (our bit mask)
OR
01101000 (value in the Accumulator)
01101001 (revised value in the Accumulator)

As you can see, OR sets the target bits if either bit is set; our OR bit mask here specifies that bit 1 must be set but we could set more than one bit at a time if we like:

10001011 (our bit mask)
OR
01101000 (value in the Accumulator)
11101011 (revised value in the Accumulator)

As you can see a bit set in either expression will become set in the OR result.

To flip a target bit in a byte, we use an exclusive OR, EOR:

LDA myvar
EOR %11111111

Suppose myvar (now in the Accumulator) contains zero (00000000):

11111111 (our bit mask)
EOR
00000000 (value in the Accumulator)
11111111 (revised value in the Accumulator)

Now suppose myvar contains 255 (11111111):

11111111 (our bit mask)
EOR
11111111 (value in the Accumulator)
00000000 (revised value in the Accumulator)

An exclusive or flips the target bits because it literally means either or, but not both (again quite intuitive).


BIT Marshalling operations – ROL ASL ROR LSR
The bit marshalling operations are quite powerful; ASL and LSR will shift the bits left and right respectively:

ASL can also be used to multiply by two in one step (the 6502 has no MUL instruction)
000000010 (2) becomes 00000100 (4).

LSR can be used to divide by two in one step:
00000100 (4) becomes 00000010 (2).

If a bit falls off the end the carry flag bit variable is set, and ROL and ROR are similar variations except they push the carry flag bit variable into the binary expression instead of an empty bit; this is great for working with multiple bytes.


Hexadecimal (Optional)
Nevermind; all you need to know for advanced Assembly programming is binary because the DASM Assembler will translate it for you anyway (you produce a Binary when Assembling, not a Hexagonal and the internal operations are all binary).

But fine if you want to know it it’s simple - it’s just base 16 and for added fun you can go on to learn base 8, base 24 and then build a Tetrahedron so that you can shower in cool quantum fluids like Liquid Helium.

There is a valid reason for using Hexadecimal though; if you typed in a BASIC listing in the 70’s or 80’s you probably saw data statements like “DATA FF, FE, FD” which saved magasine space over “DATA 255, 254, 253” – that’s 3 bytes per byte vs. 4 or a 25% saving (3 pages of code for you to type in vs. 4).

This author wrote a shifting algorithm 30 years ago that saved even more magasine space to the tune of 1.2 bytes per byte vs. 3 for Hexadecimal notation. That translates to about a page of code to type in vs. 3 or 4 for the Hexadecimal and Decimal variations - with programming there’s always another way.

So here it is, we can translate binary into hexadecimal quite easily as follows:

11111111
=F F

See how it works? The first four bits are the first letter/digit, 0-F and then the next four bits are then second letter/digit. Now build a Tetrahedron :)


Binary Operations Beyond 8-bit
You don’t need these to build Tank programs; let’s move on to the only place you’ll really need them - for the game score (and even there we can do a better job if we just think what would Neo do? and change the rules a bit).
NeoFlying.jpg
Neo changing the rules of the Matrix.


How to tabulate game scores beyond 250,000 with just 12 bits
We already covered how two bytes can be used to represent a 16-bit number up to 65,535 but this still isn’t really big enough for the score unless you want to award the player a paltry point or 10 at a time and that’s simply not so much fun; players like to score 100 points at a time or more – chances are you’re a player too so you know exactly what I’m talking about.

Here’s the concept we’ll ignore because memory’s expensive anyway and this score doesn’t go high enough:

Byte Byte
11111111 11111111 (or 65,535)
(bits in order of most significant to least significant).

Instead we’ll award players at least 100 points at a time with a byte and ½ (4 bits are also known as a Nybble by the way):

Byte Nybble
11111111 0000 (or 255,000)

Here’s how it works: the first byte represents thousands and we increment it everytime the Nybble reaches 10 (and clear the Nybble).

For each increment of the Nybble before 10 we add 100’s. It’s that simple:

Byte Nybble
11111110 0011 (or 254,300)

Note that the maximum value of the score becomes 255,900 before the player flips it (an admirable goal). This is more than large enough for any Tank program.

Here’s a variation if you want players to receive score increments as small as 50 points instead of 100 – use a phat Nybble (5 bits) and add 50 for each increment of the Nybble up until it reaches 20 whereupon it is cleared and we increment the byte representing 1,000’s.



Chapter Review:

DrinkEnergyPrograms.jpg
Congradulations! There was alot to absorb in this lesson but it was easy because you already knew the concepts and more importantly you were already writing games in Assembly.

Take a break and have some coffee, then get back on your LightCycle; the remaining chapters on Bankswitching and Memory management are just semantics - all we have left is to dot the i's and cross the t's.

#12 Mr SQL OFFLINE  

Mr SQL

    Stargunner

  • Topic Starter
  • 1,754 posts

Posted Thu May 2, 2013 2:50 PM

Lesson 5: Advanced Assembly – Bank Switching with JCVD


Topics:
JCVD
JCVD’s Roundhouse Kick
Senator McComb - Same Matter can’t Occupy Same Space
Now let’s implement



JCVD
Here’s JCVD in the Science-Fiction Block Buster, TimeCop; he’s going to teach us how to bankswitch:

TimeCop.jpg

JCVD’s Roundhouse Kick
Here’s JCVD’s Roundhouse Kick; it’s pretty good :)
roundhouse.jpg


Senator McComb - Same Matter can’t Occupy Same Space
Here’s Senator McComb’s current and future self starting to self destruct after JCVD purposely violates the one and only rule of Bank Switching with a Roundhouse Kick; same matter, can’t occupy same space:
SenatorMcComb.jpg

Now Let’s implement it

SimplifiedBankSwitching.jpg
(the JCVD Bank Switching Model)

If you peruse the JCVD bankswitching model at top of BANK0 and BANK1 in the ASDK Framework you will see nearly identical blocks for every subroutine; it’s commented to show you which of the mirrored instructions will never execute – rest assured they cannot because the same matter cannot occupy the same space. Therefore, they simply become placeholders.

The commands LDA $FFF8 and LDA $FFF9 switch between bank 0 and bank 1, but you end up in exactly the same spot you were at in the previous bank hence the comments as to which instructions do and do not execute; since we’ve used all the same instructions and kept everything at the top we now have nothing to calculate and nothing to count!

Note that we’ve simply jumped around our bankswitching block in both banks at startup since we don’t need to actually call the routines, until we need to actually call the routines.


Chapter Review
Same matter can’t occupy same space, so don’t do the Roundhouse unless you want your code to self destruct; that’s it :)

#13 Mr SQL OFFLINE  

Mr SQL

    Stargunner

  • Topic Starter
  • 1,754 posts

Posted Wed May 15, 2013 7:39 AM

Lesson 6: Addressing Modes with Johnny Mnemonic

Topics:
Introducing Johnny Mnemonic
Johnny trades ROM for more RAM
A friendly Dolphin restores Johnny’s ROM
Johnny teaches 6502 Addressing Modes


Introducing Johnny Mnemonic


JohnnyMnemonic.jpg
Here’s Johnny Mnemonic in the Sci-Fi Block Buster, Johnny Mnemonic. He’s going to teach us the Addressing Modes of the 6502, but first here’s a little bit about Johnny, and about Mnemonics:

Mnemonics are the actual Assembly instructions/commands that you’ve been writing Assembly with all along! We didn’t bother addressing them until Johnny came on board because you already know what they are and you use them all the time - a mnemonic device is simply a learning technique that allows you to remember something. Think how you used “Roy G. Biv” to remember the colours of the spectrum; it’s exactly the same with Assembly instructions. LDA is the intuitive mnemonic for the target operation Load the Accumulator just as STY is for Store the Y Register.


Johnny trades ROM for more RAM

JohnnyMindLinkPrototype.jpg

Here you can see Johnny using an advanced prototype of the Atari Mindlink (“The State of the Art for the State of your Mind”); you won’t want to try this particular Mindlink prototype though – it’s wet-wired directly into Johnny’s brain (just like that input jack he has in the Matrix) and enough brain tissue was destroyed installing it that Johnny lost a large chunk of long term memory (ROM as we like to call it). What did he gain? RAM, and only a paltry 80 GB at that – you’re better off with a microSD card today but remember this is Sci-Fi and the future, isn’t what it used to be in 1994 :)


A Friendly Dolphin Restores Johnny’s ROM

JonesTheDolphin.jpg
Don’t worry; Jones the friendly Dolphin is sporting an improved version of Atari’s Mindlink, and he links it up to Johnny’s Mindlink to restore all of his lost ROM. Johnny also has a lot of other cool gadgets including a software RAM Doubler that he’s going to teach us about in the next chapter but right now he’s going to teach the Addressing Modes of the 6502!



Johnny Mnemonic teaches 6502 Addressing Modes
JohnnyTeachesAddressingModes.jpg
(Johnny using the Atari Mindlink)

Addressing is mostly semantics and you can probably figure it out looking at code examples at this point but Johnny is going to share the details anyway. For better transfer, always wear your Atari Mindlink like Johnny is doing.


Examples of immediate Addressing Mode:

LDA #100; Load the Accumulator (A register) with the number 100 using decimal
LDY #%01100100; Load the Y register with the number 100 using binary

Immediate addressing means we are literally loading an immediate value.


Examples of Zero Page Addressing (optional concept):

LDA 100; load the Accumulator (A register) with whatever value is contained in memory variable location 100, using decimal notation.

STY %01100100; store the Y register in memory/variable location 100 using binary notation.

Zero page explained: The zero page is a “direct page” that allows slightly faster access to low memory on the VCS; this concept is not very important and you can consider it no different than absolute addressing.


Absolute Addressing:

It functions identically to zero page addressing, just slightly slower and you’ll switch context automatically whenever you address a memory location (RAM or ROM) beyond the direct page (note that the direct page comprises only the first 256 bytes of address space). Again, you really don’t have to differentiate between these two modes; consider that Johnny only cares when he’s got a severe bottleneck! :)


Indexed Absolute Addressing:

The X or Y registers are used as an offset to the target memory location.
STA 500,X ; puts Accumulator contents in memory location 500+X (useful!)
LDA 55, Y ; loads Accumulator with memory location 55+Y*


Indexed Zero Page Addressing:

Nevermind; it functions just like Indexed Absolute Addressing just a tad faster and the switch is automatic when you target low memory below 256. *Note that example changes from zero page indexed addressing when Y is 200 or less, to Indexed Absolute Addressing when Y is greater than 200 (Johnny doesn't care and neither should you).


Indirect Addressing:

Johnny has more important things to think about and literally jumps away from this one; Jump (goto) is the only operation that uses it, and transparently at that.


Indexed Indirect Addressing:

Johnny generally, doesn’t care.


Relative Addressing:

Johnny doesn’t care; if you get a branch out of range error compiling, you already know what to do.



Chapter Review

IKNOWKUNGFU.jpg
And you do too, especially if you wore your Atari Mindlink for this lesson! Look out for Johnny again in the next (optional) chapter on getting the most out of the ASDK; It’s loads of fun again when Johnny breaks out a RAM Doubler, all in software, for the VCS! :)

#14 Mr SQL OFFLINE  

Mr SQL

    Stargunner

  • Topic Starter
  • 1,754 posts

Posted Mon May 20, 2013 7:50 PM

Lesson 7: Getting the most out of the ASDK


Topics:
Taking a closer look at the ASDK
The ASDK Memory Map
The Phantom Hardware
The Dual Kernel
Handling Kitchen Sink code in the Primary Kernel
ASDK Enhancements – Disengaging the Primary Kernel to stop flicker
Johnny Mnemonic explains the ASDK RAM Doubler
Extra Memory exists beyond the edge of the Virtual World


Taking a closer look at the ASDK

You’re already familiar with using the high level function calls in the ASDK; behind these function calls the ASDK provides phantom hardware for 4-way scrolling and double buffering – both screen buffers (the playfield and the virtual world) are comprised entirely of RAM (low and hi system RAM, respectively).

The idea behind the phantom hardware is to allow Assembly developers to focus more abstractly on writing fun games just like you could on the 1976 Fairchild Channel F or on any of the 80’s Home Computers instead of focusing on esoteric minutia like counting and squeezing cycles for instructions and addressing modes to maxamise every last bit of performance from the CPU; this is only necessary with the VCS when writing a traditional kernel (the ASDK has two kernels, more on that later) and now that you know advanced Assembly programming you are equipped to look into that if you are so inclined (consider that starting there first would be akin to reading a physics book by starting backwards from the last page – not the best idea).


Here’s what the ASDK Memory Map looks like:


RAM map.jpg

Low RAM

As you probably know, the VCS has only 128 bytes of RAM which is referred to as low RAM since it is in the “zero page”, otherwise know as the first 256 bytes of address space.


High RAM

High RAM comes in a few different flavours but always as a memory chip hardware enhancement built into the cartridges; the most common is Sara also know as the super-chip which provides an additional 128 bytes of RAM. The second most common is CBS RAM, also know as the Double-Super-Chip, which provides an additional 256 bytes of RAM. Both of these formats usually afford additional ROM as well hence we were able to bank switch with JCVD to get more game code onto the cartridge.

Since we are using the Double-Super-Chip we have 256 bytes of high RAM in addition to the 128 bytes of low RAM for a total of 384 bytes of RAM, and three 4K banks of ROM for a total of 10.5 K of ROM. The reason for this discrepancy (10.5 K as opposed to 12 K) is that the 256 bytes of RAM costs us 512 bytes of ROM in each bank as unlike low RAM, it must use separate address space for read (LDA/LDY/LDX) operations and write (STA/STX/STY) operations (Sara does this too but only ½ as much). That’s fine, there’s still plenty of ROM space in the first two banks for your code, and the third bank is completely empty.


The Phantom Hardware

The phantom hardware leverages 60 bytes of low RAM and all 256 bytes of high RAM as shown and about 2 K of our available ROM space (most of which is out of the way in bank 1) to provide double buffering, 4-way hardware scrolling and also a RAM Doubler, all in software (Johnny will be telling us all about the RAM Doubler presently – it’s a neat trick).


The Dual Kernel

Because it takes a lot of processor time to emulate physical hardware, a dual Kernel is employed. The primary kernel provides the phantom hardware and has plenty of space for kitchen sink code - it functions more like the game kernels in those other machines! The secondary kernel is tightly packed and full of comments about counting cycles and squeezing instructions - racing the beam like a traditional VCS kernel.


Handling Kitchen Sink Code in the Primary Kernel

In addition to providing plenty of space for the phantom hardware, the primary kernel can literally handle the kitchen sink – you can put a ton of code in there and never have to worry. This is because the primary kernel eats an entire frame. This results in flicker which can be mesmerising on an antique tube television thanks to phosphor persistence but somewhat annoying on newer LCD displays (though somewhat better on plasma). Not to worry – you can now engage and disengage the primary kernel at will for a flicker free display!

Note: If you don’t have the openGL drivers for the Windows emulator Stella, then scrolling will not look very good but this is not an issue stemming from the ASDK; it works just fine on the real hardware and on the Linux emulators (go Linux - it’s a good idea anyway).


ASDK Enhancements – Disengaging the Primary Kernel for a flicker free display

In the enhanced version of the ASDK, the gameloop for your code has been moved to one of the vertical blanks – nothings changed and you still have plenty of time, just not an ocean of it. Search for the phrase “Kitchen sink” and you’ll find the old gameloop area still intact. You can put any heavy duty routines in there or high overhead code that will execute every time you engage the primary kernel. Of course you’ll also want to engage it for scrolling the display, but flicker is far less noticeable when scrolling anyway because everything is moving! It’s kind of the best of both worlds if you only engage it for scrolling.

The code screenshots from Scrollout7 (a scrolling breakout game) below illustrate engaging and disengaging the primary kernel for scrolling, and also illustrate some other things you can do with the primary kernel like sandwiching routines between calls to the twin rendering engines (useful as illustrated for copying the screen to the tiny sprite game screen the mini pong player is using):

ASDKenhancementscreen1.png
In the screenshot above, the purple code shows that the variable scrollvirtualworldtoggle is set to 0 to disengage the primary kernel, eliminating flicker. Note the comments above show how the gameloop has been moved outside the sea of time, thought there’s still penty of it for your game code.

ASDKenhancementscreen2.png
In the screenshot above, we’ve scrolled down just a bit and the purple code is determining if the player wants to scroll the virtual world (it’s under player control for this game). If they do the primary engines are reengaged by setting the variable scrollvirtualworldtoggle to 1. Note that just below this the code is increasing it’s internal framerate delay (we don’t want the game to take place at hyperspeed so it uses a frame delay) whenever the virtualworld is reengaged; this allows the speed to remain constant because things happen half as fast whenever the primary kernel is engaged (speed is never an issue either way because half of “fast”, is still plenty fast).

ASDKenhancementscreen3.png
You can still put “the kitchen sink” in the primary kernel just as before and it will execute whenever the primary kernel is engaged.

ASDKenhancementscreen4.png
And you can also still sandwich routines between calls to the twin rendering engines that live in the primary kernel; note how the purple comment shows that the update to the mini pong players screen (with tiny pong pixels) lives there.


Johnny Mnemonic Explains the ASDK RAM Doubler

RAMdoubler.jpg
Johnny knows about RAM doublers! In the picture above, Johnny is using a 2x RAM Doubler to double the amount of available RAM in the memory implant wet-wired to his brain. This is just like the RAM Doubler in the ASDK except that Johnny’s RAM Doubler can result in severe synaptic-seepage which isn’t any fun; the RAM Doubler in the ASDK is fun! :)

As discussed earlier, the primary Kernel has an ocean of time for your code and, for it’s internal routines which include the RAM Doubler.

The intuitive WYSIWYG panoramic bitmaps wouldn’t be possible without the primary kernel design, even a single playfield screen would have to be stored partially flipped backwards and sprites would need to be stored upside down.

There wouldn’t be room for the RAM Doubler either which would mean using twice as much memory for the video RAM; the twin rendering engines actually expand (decompress) the 30 bytes of playfield memory in the second display buffer into 60 bytes, and also flip parts of it backwards. Once disengaged, the secondary kernel needs this awkward bloated format because it literally hasn’t much time for anything besides madly racing the beam to paint pixels and push sprites.

Consider this:

Without the RAM Doubler we would need 480 bytes of high RAM (and we don’t have it!) for the virtual world buffer as well as 480 bytes for each panoramic playfield image stored in ROM (there are three by default and more can be added). As you can see, this approach would quickly eat up way too much memory! More problems would ensure since we can only handle tables up to 256 bytes in size so in addition to flipping some of the data backwards and breaking the intuitive bB style graphics format, we would have to split it up into multiple tables making it less abstract and less fun to work with.


Extra Memory exists beyond the edge of the Virtual World

For those willing to explore, extra memory awaits in high RAM at the edge of the virtual world:

Figure2_VirtualWorld.jpg

There are four unused bits at the left of each row of pixels; these can be used to store up to 20 4-bit variables or 80 bit variables or any combination thereof; care must be taken not to corrupt the other 4 bits in the target bytes they reside in however since those other bits are actively used as pixels!

MatrixReloaded.jpg
Note that Reloading the Matrix of the virtual world will white wash this extra memory.





Also tagged with one or more of these keywords: Framework Tutorial, RAD, Assembly, bB, Assembly tutorial, lightcycles, gamegrid, programming

0 user(s) are browsing this forum

0 members, 0 guests, 0 anonymous users