Jump to content
IGNORED

Trying to Learn Assembly (Please Help an Idiot)


Recommended Posts

I'm baffled how, based on the responses, everyone seemed to zip through Andrew Davie's tutorials almost without issue while almost every line I write left me confused and usually with broken code.

 

So, instead of giving up as I very much want to do, I guess I'll ask for help and maybe I'll eventually learn it. Hopefully.

 

So, although Andrew seemed confident that creating an asymmetrical playfield with a full background would be so easy, he shouldn't even need to post the actual working code to make it happen, I have been struggling for over an hour to get anything to even show up. Please see attached files, because the problem could be anywhere and I have no clue.

 

Essentially, I don't feel the need for a full-on 40x192 playfield, so instead I made a 40x24 one and the code *should* be drawing each pfpixel 8 scanlines tall (which will hopefully make for square pfpixels).

 


      ;------------------------------------------------

      ; Do 192 scanlines of colour-changing (our picture)

       ldx #0  ; line #
NextLine
ldy #0

ALine   lda mockup_STRIP_0,x   ; PF0 left
       sta PF0
       lda mockup_STRIP_1,x   ; PF1 left
       sta PF1
       lda mockup_STRIP_2,x   ; PF2 left
       sta PF2

; delay as appropriate

       lda mockup_STRIP_3,x   ; PF0 right
       sta PF0
       lda mockup_STRIP_4,x   ; PF1 right
       sta PF1
       lda mockup_STRIP_5,x   ; PF2 right
       sta PF2

       sta WSYNC
iny
cpy #8

bne ALine
       inx
       cpx #24

       bne NextLine

 

It compiles, but I just get a blank with too many scanlines.

 

I'm sure that there will be a lot more questions.

basekernel.asm

mockup.asm

Edited by Cybearg
Link to comment
Share on other sites

1) Put include include "mockup.asm" at the top of the file with the other includes.

 

 

processor 6502

include "vcs.h"

include "macro.h"

include "mockup.asm"

 

2) What you are doing is best described as a macro. In the mockup.asm file change:

 

mockup

to:

 

MAC mockup

 

 

and at the end of the file put:

 

ENDM

 

You need spaces before the MAC and ENDM (end macro) statements. Every macro you create needs to wrapped with MAC and ENDM.

 

 

3) Put an ORG statement in basekernel.asm after the code.

 

 

; 30 scanlines of overscan...

 

ldx #0

Overscan sta WSYNC

inx

cpx #30

bne Overscan

jmp StartOfFrame

 

ORG $FF00

mockup

 

 

;------------------------------------------------------------------------------

ORG $FFFA

 

InterruptVectors

 

.word Reset ; NMI

.word Reset ; RESET

.word Reset ; IRQ

 

END

 

 

And now you can at least see the screen. I arbitrarily chose $FF00 as it is the last page in the bank. I like having the code fill up the bottom while having data at the end. You can see the next ORG is at $FFFA, so you still have some room left in there.

 

Next you'll have to learn about how long to delay the loop before updating the second half of the screen.

Link to comment
Share on other sites

I haven't tried assembling and testing the program, nor have I studied it in depth, but I see three issues in basekernel.asm:

 

(1) You aren't setting any colors, therefore everything on the screen will be black, since the Clear loop initializes the four color registers to 0 (black).

 

(2) The first instruction in StartOfFrame needs to be changed from LDA #0 to LDA #2 so the VBLANK will be turned on, not off. Having VBLANK turned on during vertical sync can interfere with the vertical sync signal. Personally, I like to start my kernels with the so-called

 

EDIT: (Duh, I forgot to finish that sentence!) Personally, I like to start my kernels with the so-called "overscan" instead of the vertical sync, although this is a matter of personal preference and most programmers seem to prefer starting with the vertical sync.

 

(3) In ALine, the comment that says "; delay as appropriate" needs to be replaced with code to delay the three LDA and STA instructions for the right half of the screen so they will occur at the appropriate times. You should probably also rearrange their order-- i.e., for the left side set them in the order PF0, PF1, and PF2; but for the right side set them in the order PF2, PF1, and PF0-- since you're drawing a reflected playfield. For a reflected playfield the second write to PF2 should occur at exactly cycle 48, then you can write to PF1 and PF0 after that.

 

EDIT: (4) Either move the include for mockup.asm to the end of basekernel.asm, just before the "ORG $FFFA" line, or put a "JMP StartOfFrame" command just before the include for mockup.asm.

Edited by SeaGtGruff
Link to comment
Share on other sites

1) Put include include "mockup.asm" at the top of the file with the other includes.

 

 

processor 6502

include "vcs.h"

include "macro.h"

include "mockup.asm"

 

2) What you are doing is best described as a macro. In the mockup.asm file change:

 

mockup

to:

 

MAC mockup

I disagree. It looks to me like mockup.asm is nothing more than screen data for the playfield, the intent being to create a base program with easily-changed screen data-- just change the data in mockup.asm and the entire playfield changes when the program is reassembled. As such, the include for mockup.asm could be moved to the end of basekernel.asm, just before ORG $FFFA. In fact, I think it *should* be moved there, since there's no code to jump over the data, hence the initialization code will fall through into the screen data instead of falling through into StartOfFrame as it should.

Link to comment
Share on other sites

I disagree. It looks to me like mockup.asm is nothing more than screen data for the playfield, the intent being to create a base program with easily-changed screen data-- just change the data in mockup.asm and the entire playfield changes when the program is reassembled. As such, the include for mockup.asm could be moved to the end of basekernel.asm, just before ORG $FFFA. In fact, I think it *should* be moved there, since there's no code to jump over the data, hence the initialization code will fall through into the screen data instead of falling through into StartOfFrame as it should.

 

It is just screen data, but I don't know what you are disagreeing to? Unless it's where he wanted to stick it?

 

Personally, I wouldn't have used a macro at all, and I would have just stuck the date in the assembly file the ORG at $FF00 I placed instead of having two files.

 

 

In retrospect I think you are thinking of doing this:

 

ORG $F000

include "mockup.asm"

 

Which is a similar idea. I had an ORG in there as well. I agree it can be pushed all the way to the back as well, but as you add more data it is a pain to keep adjusting the ORG statement. I like to set a page and when it fills up move the ORG one more page down. Or just use ALIGN.

Link to comment
Share on other sites

It is just screen data, but I don't know what you are disagreeing to?

I'm disagreeing with where you said "What you are doing is best described as a macro." It isn't a macro, it's just screen data. Putting it at the beginning of the program (ahead of Reset) would move it out of the way and also ensure that the data lines up with a page boundary, so that's fine-- but if you do that, it should go between "ORG $F000" and Reset. I generally stick data at the end of the code, rather than at the beginning or in the middle, which is why I suggested putting it just after the overscan and before "ORG $FFFA."

 

EDIT: I answered too quickly to your second post and didn't see where you mentioned putting it after "ORG $F000."

Edited by SeaGtGruff
Link to comment
Share on other sites

As far as what I said about the loop where the playfield is being drawn, some delay timing (NOPs or whatever) should be inserted between the left side of the playfield and the right side, so the second STA PF2 occurs at cycle 48, but you also need to be sure the overall timing is kept stable-- i.e., that the loop always starts at a particular cycle so the writes will always occur at the expected times.

Link to comment
Share on other sites

I'm disagreeing with where you said "What you are doing is best described as a macro." It isn't a macro, it's just screen data. Putting it at the beginning of the program (ahead of Reset) would move it out of the way and also ensure that the data lines up with a page boundary, so that's fine-- but if you do that, it should go between "ORG $F000" and Reset. I generally stick data at the end of the code, rather than at the beginning or in the middle, which is why I suggested putting it just after the overscan and before "ORG $FFFA."

 

EDIT: I answered too quickly to your second post and didn't see where you mentioned putting it after "ORG $F000."

 

The reason I would rather put it in a macro is so that I can see all the include files directly at the top. That way I can quickly see what files are being used. But then again, I wouldn't have used a macro or include in this case if I was writing it. I would just have stuck it all in 1 file as it's so short.

Link to comment
Share on other sites

The reason I would rather put it in a macro is so that I can see all the include files directly at the top. That way I can quickly see what files are being used. But then again, I wouldn't have used a macro or include in this case if I was writing it. I would just have stuck it all in 1 file as it's so short.

I wouldn't have done it that way, either-- but I think basekernel.asm and mockup.asm were taken from Andrew's tutorials (which I've never worked through), and I think his intent was to give an example where the reader could just change the data in mockup.asm to draw a different playfield. But there are some issues with basekernel.asm, the biggest being that it's incomplete-- e.g., "; delay as appropriate" was obviously intended to be replaced by some code, presumably NOPs, although the exact amount of delay needed would depend on how many other instructions (for drawing the sprites, missiles, and/or ball) were also being performed at that point in the code. As such, it isn't a complete example, it's really more of a "partial program" to illustrate how to draw an asymmetrical playfield, with the understanding and expectation that each reader will fill in the rest of the code with whatever other instructions they'd like to include-- not the least of which should be setting the color registers to something besides black! :D

Link to comment
Share on other sites

D'oh! I must be having a bad night-- I didn't notice that the playfield color IS being set in basekernel.asm! So the black screen is because the initialization is falling through into the playfield data and crashing the Atari.

 

Anyway, I'm modifying basekernel.asm to reflect my suggestions, and will post it shortly when I'm done.

Link to comment
Share on other sites

Here is a modified version of basekernel.asm that works. I moved some of the code around, renamed one of the line labels, changed the branching logic at the end of the playfield loop, changed the y index to decrement instead of increment, changed the Overscan and VerticalBlank to use the timer, and inserted some NOPs and BIT 0 instructions to get the timing right. I would have changed the x index to decrement as well, but at that time I wasn't sure what the playfield was going to look like and I didn't want it to be upside down!

basekernel.asm

basekernel.asm.bin

Link to comment
Share on other sites

I should probably find out what page this is about. Here is a list of links to the sessions:

 

www.randomterrain.com/atari-2600-memories-tutorial-andrew-davie-01.html#session_links

 

 

Please let me know if a file or files from this thread should be added to one or more of those pages.

 

 

Thanks.

Link to comment
Share on other sites

Here is a modified version of basekernel.asm that works. I moved some of the code around, renamed one of the line labels, changed the branching logic at the end of the playfield loop, changed the y index to decrement instead of increment, changed the Overscan and VerticalBlank to use the timer, and inserted some NOPs and BIT 0 instructions to get the timing right. I would have changed the x index to decrement as well, but at that time I wasn't sure what the playfield was going to look like and I didn't want it to be upside down!

 

Much appreciated! I'll have to look everything over closely to figure out what's what.

 

I've never understood something: why use nop commands? Why not sleep? Multiple nop commands waste space and do the exact same thing as sleep, right?

 

Also...

 

What does this do?


ldx #37*76/64
stx TIM64T

What is #37*76/64? I assume that's not actually doing high-end multiplication, so what does that mean?

 

And what's TIM64T?

 

Also, the point of the kernel was to have an asymmetrical playfield, so it should a circle in the center of the screen, not two halves as it seems to be. I guess I just need to fiddle more with extending the number of cycles?

 

EDIT: Well, I set the CTRLPF to 0 to disable mirroring but there's a large gap there. I've tried adjusting which PF gets what, but it still has a rather large gap there...

 

Agh, I just don't understand this.

Edited by Cybearg
Link to comment
Share on other sites

I've never understood something: why use nop commands? Why not sleep? Multiple nop commands waste space and do the exact same thing as sleep, right?

That's a good question! SLEEP is a macro, which is a special kind of thingy you're going to run into from time to time with assembly programs for the 2600. Perhaps the easiest way to understand what a macro is would be to think of it as a kind of abbreviation or shorthand for saying something else. But that's only partly right, because a macro can use arguments-- SLEEP being an example of such a macro. When you put a command like "SLEEP 13" in your assembly program (and assuming the SLEEP macro is defined within your program-- which it is if you're including the MACRO.H file), the assembler will substitute the "SLEEP 13" command for the code that's defined within the SLEEP macro. And since the SLEEP macro takes an argument, the code that gets inserted in place of the SLEEP command will vary based on the argument. For example, "SLEEP 2" will be replaced by a NOP command, whereas "SLEEP 4" will be replaced by two NOP commands in a row. Any even-numbered argument-- 2, 4, 6, 8, etc.-- will be replaced by half as many NOPs, since one NOP wastes 2 cycles. Any odd-numbered argument will be replaced by a certain number of NOPs plus either a "NOP 0" or a "BIT 0" command, which wastes 3 cycles.

 

So using three NOPs in a row is exactly the same as saying "SLEEP 6," and using a NOP followed by "BIT 0" is the same as saying "SLEEP 5." I used "BIT 0" instead of "NOP 0" because "NOP 0" is an "illegal" opcode.

 

What does this do?


ldx #37*76/64
stx TIM64T

What is #37*76/64? I assume that's not actually doing high-end multiplication, so what does that mean?

 

And what's TIM64T?

#37*76/64 is doing high-end math, but it isn't done in the assembly program-- it gets done by the assembler during the assembly process. The actual code will end up being "LDX #43" because 37*76/64=43.9375 and the assembler will drop the decimal places. What that code is doing is setting the timer for about 37 scan lines' worth of time. On the 2600 a scan line lasts 76 cycles, so 37 scan lines last 37*76=2812 cycles. But we can't set the timer to 2812 because the highest we can set it to is 255. On the other hand, we can set the timer to count down in intervals of 1 cycle, 8 cycles, 64 cycles, or 1024 cycles. We can't use 1-cycle intervals because 2812/1=2812, which is greater than 255. Likewise, we can't use 8-cycle intervals because 2812/8=351.5, which is still greater than 255. But 2812/64=43.9375, which is less than 255, so we can set the timer to 43 and tell it to count in 64-cycle intervals. That's what TIM64T is-- it's the location we write to when we want to set the timer and have it count down 64 cycles at a time. Since 43*64=2752, setting TIM64T to 43 will make the timer count down from 43 to 0 over a period of about 2752 cycles-- really 2754 cycles, because during the first cycle it will be 43, then in the second cycle it will be 42, after which it will decrement every 64 cycles, and then we need one more cycle before it rolls over from 0 to 255 and sets the timer interrupt flag. So if we set TIM64T to 43, the timer interrupt flag will get set (indicating that the timer has finished counting down) 2754 cycles later. Since there are 76 cycles in a scan line, that's 2754/76=36.2368 scan lines.

 

Also, the point of the kernel was to have an asymmetrical playfield, so it should a circle in the center of the screen, not two halves as it seems to be. I guess I just need to fiddle more with extending the number of cycles?

 

EDIT: Well, I set the CTRLPF to 0 to disable mirroring but there's a large gap there. I've tried adjusting which PF gets what, but it still has a rather large gap there...

 

Agh, I just don't understand this.

I suspected you were trying to draw a circle when I saw the program run. There's three ways to fix it:

 

(1) Turn off the mirroring (as you say you've done). That means in addition to rearranging the second set of PF writes you'll also need to change the data around to compensate.

 

EDIT: Actually, in this case, all you need to do is change the second PF2 to PF0, and change the second PF0 to PF2. Leave the LDA commands alone-- just change the STA commands.

EDIT #2: Sorry, flip the LDA statements, too. I should have done it myself before trying to answer. I'm attaching the updated code.

 

(2) Leave the mirroring on and leave the second set of PF writes as they were, but change the data around.

 

Either way you need to redo the data for the last three strips. (EDIT: Sorry, not for the first solution; just swap the STA commands as indicated above.) However, drawing an aymmetric playfield without mirroring is actually easier as far as timing constraints, because you have more leeway as to when you need to update the PF registers for the right half.

 

(3) Leave the mirroring on but take out the second set of PF writes entirely. Then the right half of the playfield will be a mirror image of the left half. But since the exercise was to do an asymmetrical playfield, I guess you don't want to use the third option. But the playfield *is* an asymmetric playfield as it is, since the right half is different than the left half.

basekernel.asm

basekernel.asm.bin

Edited by SeaGtGruff
Link to comment
Share on other sites

 

Agh, I just don't understand this.

 

Wow. As a perspective from this outsider looking in, i hope i am wrong, but you seem to be headed for burning out in this hobby.

You have been a member for about a month and a half, starting with Heartbreak in batari Basic, then getting an expert to create an advanced mid-line color change kernel for you when your idea surpassed Basic's limitations. (Great original idea by the way.)

Then plowing into the fragile DPC+ Basic kernel, (which I think is an impressive tool to create professional looking games but it can't reach the all assembly language possibilities that can be done on the 2600) and having Windows operating system problems. Plus including inline assembly?

(Your game fix it is brilliant also, but you haven't really presented it or talked about it. It is an example of using the multisprite kernel and having a game nicer than seems possible with that kernel. If you could get that in DPC+ with colourful sprites, you would have something I would buy in the AtariAge store.)

Now, in a mere 5 weeks you are at Assembly asymetrical playfield programming?

Wow.

Edited by iesposta
Link to comment
Share on other sites

I've never understood something: why use nop commands? Why not sleep? Multiple nop commands waste space and do the exact same thing as sleep, right?

 

If you create a list file you can open it up and see what SLEEP actually does. It inserts NOP's and illegal three cycle NOP's to make up the "sleep" cycles. So you are still wasting bytes, except the macro is inserting the delay for you. You're not getting anything for free. The SLEEP macro is not always the most efficient way to delay time either.

 

 

And what's TIM64T?

That is one of the timers. It is the most used timer by far. You'll often find games using it in Vblank and Overscan. Some games also use it for the kernel (including BB).

 

The point of using a timer is that it is way easier to manage your game. Your code can vary several cycles in Vblank and Overscan from frame to frame. It is really convenient to set the timer, do all your stuff, and then finally wait by continuously polling the timer. All timer values are read through INTIM, and you can also test it for overflow through TIMINT.

 

 

Also, the point of the kernel was to have an asymmetrical playfield, so it should a circle in the center of the screen, not two halves as it seems to be. I guess I just need to fiddle more with extending the number of cycles?

 

If you got gaps, then it usually is a timing issue. What I most often do is open Stella's debugger, and step through the screen as it is getting drawn. You'll quickly see if something is taking too long or is happening too early. After seeing where the problem is then you can move stuff around or add/subtract delays to fix it.

 

 

Agh, I just don't understand this.

 

All I can say is that all assembly users have been there. :) As you progress you'll hit many of these Aggggghhhh's, but for each one you learn more and more, and then it all just clicks. It really isn't as bad as people make it out to be. I think there has always been entirely too much "assembly is hard, don't even try" going around. The one thing you'll need to get through it all is patience, not so much a brain. So if you find it getting frustrating work harder on yourself then the code. Take a step back in relax. Assembly is not fast at all, but in the end you can do wonderful things with it.

 

One natural thing to ask is why use an asymmetrical playfield at all? Well in the case of HeartBreak both halves of the playfield were not the same. The playfield had to be updated after the left half was drawn. It is possible to do this with a reflected playfield, but the updating to PF2 has to be done at a precise cycle. This wasn't possible in HeartBreak because the second write to PF2 had to be done at a time which conflicted with the color updating. The simple solution was to make the playfield asymmetrical so that this updating could be done earlier.

 

So again, the problem was a timing issue, where everything couldn't be updated all at once. An asymmetrical playfield helped HeartBreak overcome that obstacle, and three color updates were done on the top and bottom rows.

Link to comment
Share on other sites

Agh, I just don't understand this.

 

Cybearg,

read this thread and have a look at the scrolldraw Assembly example in the opening post:

http://www.atariage....evelopment-kit/

 

There's nothing required for setting timers, counting cycles, drawing an asymetric playfield or loading the playfield registers backwards!

 

Instead, the focus is on using Assembly at a very basic level to drive the game logic and loops while using familiar high level calls for setting pixels (like the pfpixel commands in bB) and for panning the camera to scroll the large play area (10x the size of the visible playfield) which is also defined similar to bB in simple WYSIWYG format.

 

IMO only once you've mastered Assembly should you attempt to use it to harness the unique and bizarre hardware of the VCS; it's a lot harder to do that all at once, kind of like trying to learn SQL by starting with correlated subqueries :)

Link to comment
Share on other sites

  • 3 weeks later...

"IMO only once you've mastered Assembly should you attempt to use it to harness the unique and bizarre hardware of the VCS; it's a lot harder to do that all at once."

 

That is a good point. Get a KIM or AIM 65 emulator and download the programming manuals for them. Do the programming excersices in them and learn the machine language basics of the 6502 first. I learned assembly language decades ago in the fairly friendly programming environment of the C64. Not sure I could have done it if the VCS was my first 6502 machine.

 

Here is a well done 6502 turtorial:

http://skilldrick.github.io/easy6502/

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...