Cybergoth Posted January 9, 2005 Share Posted January 9, 2005 Hi there! I noticed Andrew's example begins with vertical sync, but yours begins with vertical blank. I've seen other examples that begin with overscan. That's enough to confuse me. So my biggest misunderstanding at the moment is... how do I know where to begin? Do I control the tv or do I try to keep up with it? You control the TV yes. That's one of the coolest featrures of the VCS. It doesn't matter how you begin your loop, because after the first VSYNC sequence - you're synced. The order does matter though: NextScreen VSYNC VBLANK Kernel Overscan JMP NextScreen is as fine as NextScreen VBLANK Kernel Overscan VSYNC JMP NextScreen The layout I'd personally recommend though is starting the Kernel code right after the first ORG F000: ORG $F000 DoKernel Kernel ; Kernel Code here JMP Overscan CartridgeEntry CLEAN_START Init Code ; Initialisation code here NextScreen VSYNC VBLANK ; VBLANK calculations here JMP DoKernel Overscan ; Overscan calculations here JMP NextScreen Subroutines; Subroutines and data fill the rest Data ; ORG FFFC .word CartridgeEntry That way, with your kernel code being on top of the ROM space, it does not get shifted around, thus it's safe from all timing issues like crossing pages. For instance, if I move #2 into VSYNC in the middle of drawing the playfield, does the electron beam return to the top of the screen? I just did a test and that's what it looks like is happening. And is it the same for VBLANK and RSYNC as well? Precisely. The VBLANK register for example will switch the electron beam on and off immediately, wherever you want. Greetings, Manuel Quote Link to comment Share on other sites More sharing options...
Haydn Jones Posted January 11, 2005 Share Posted January 11, 2005 Hi, I'm new here, i have been enjoying the tutorials so far. I have run in to a problem. I dont know if the code is wrong, or if it is the new compiler/vcs.h file. I took the kernal from the previous tutorial and tride changeing the relavant block with the following.... ; 192 scanlines of picture... ldx #0 ldy #0 REPEAT 192; scanlines inx stx COLUBK nop nop nop dey sty COLUBK sta WSYNC REPEND This showed up the same as befor an stellax and z26 ; 192 scanlines of picture... ldx #0 ldy #0 REPEAT 192; scanlines nop nop nop nop nop nop nop nop nop nop inx stx COLUBK nop nop nop dey sty COLUBK sta WSYNC REPEND This showed blank on both, but the example binary worked fine. I am useing win 2k and the latest vcs.h macro.h and dasm. PS. I have made a pdf (ziped) of the colour charts for anyone intrested as they are not available any more from the link. colour_chart.zip Quote Link to comment Share on other sites More sharing options...
Cybergoth Posted January 11, 2005 Share Posted January 11, 2005 Hi there! This showed blank on both, but the example binary worked fine. The code between REPEAT and REPEND gets copied into the binary 192 times. That is in the second case 192*21=4032 Bytes! So you'll either need to read on and learn about loops or bankswitching. I'd suggest the first for the moment Greetings, Manuel Quote Link to comment Share on other sites More sharing options...
supercat Posted June 3, 2005 Share Posted June 3, 2005 jsr _rts ; 12 cycles, 3 bytes 306384[/snapback] This presupposes that the stack pointer is set somewhere in usable RAM. On most computers, this would be a reasonable assumption, but not on the 2600. Many programmers do weird tricky things like point the stack pointer at TIA registers. Using a "JSR KnownRTS" instruction in such cases will cause the processor to execute code in some 'random' spot. BTW, if the IRQ vector points to an RTI, a BRK instruction followed by an arbitrary byte could be used for a slightly more compact time-waster in cases where the there are 3 bytes available at the stack pointer. Quote Link to comment Share on other sites More sharing options...
Starman Posted June 21, 2005 Share Posted June 21, 2005 (edited) My turn Ok, I understand the mechanics of the VCS and controlling the TV. I've been a programmer since '83 with a touch of hardware engineering. Here's what's bugging me. I took the code example and tweaked it a bit because I wanted to know exactly what those sleep macros were doing. Here's my understanding so far: sleep 15 = 15 nop instructions. 1 nop instruction = 2 cycles. 15 nops = 30 cycles = 90 clock counts (3 clock counts/machine cycle) So based on that, I would expect the second "band" to start further from the left than my code shows. Looking at it blown up in Photoshop, there seems to be only one pixel between the left edge and the second band. That would mean the second band is starting on clock count 70, which is 20 cycles further to the left than I expected it to be. Also, the first and second band, both defined by "sleep 10", seem to be different sizes. Band 1 is 78 pixels across, band 2 is 90. I'm confused, and it's probably something real simple, too. If anyone can shed light on this, I'd appreciate it. Thanks. Mike processor 6502 include "vcs.h" include "macro.h" SEG ORG $F000 Reset StartOfFrame ; Start of vertical blank processing lda #0 sta VBLANK lda #2 sta VSYNC ; 3 scanlines of VSYNCH signal... sta WSYNC sta WSYNC sta WSYNC lda #0 sta VSYNC ; 37 scanlines of vertical blank... ldx #0 VBLANKTOP sta WSYNC inx cpx #37 bne VBLANKTOP ldx #192 ; 192 scanlines of picture... ldy #0 STY COLUBK STY VBLANK drawpictureloop sta WSYNC iny sty COLUBK sleep 15 stx COLUBK sleep 10 sty COLUBK sleep 10 dex stx COLUBK BNE drawpictureloop lda #%01000010 sta VBLANK ; end of screen - enter blanking ; 30 scanlines of overscan... ldx #0 OVERSCAN sta WSYNC inx cpx #30 bne OVERSCAN jmp StartOfFrame ORG $FFFA .word Reset ; NMI .word Reset ; RESET .word Reset ; IRQ END Edited June 21, 2005 by Starman Quote Link to comment Share on other sites More sharing options...
Cybergoth Posted June 21, 2005 Share Posted June 21, 2005 Hi there! sleep 15 = 15 nop instructions. 878320[/snapback] That's where you got confused. Sleep 15 = 15 cycles. It does 6 NOP + 1 DOP. Greetings, Manuel Quote Link to comment Share on other sites More sharing options...
supercat Posted June 21, 2005 Share Posted June 21, 2005 The layout I'd personally recommend though is starting the Kernel code right after the first ORG F000: ORG $F000 DoKernel Kernel ; Kernel Code here JMP Overscan ... ORG FFFC .word CartridgeEntry That way, with your kernel code being on top of the ROM space, it does not get shifted around, thus it's safe from all timing issues like crossing pages. I put my sprite data at $F000, padded out to a multiple of 256 bytes, but arranging for the kernel to be page-aligned is definitely a good idea. I personally prefer to JSR the kernel, though, at least for the programs I've written where one sometimes has to do calculations that don't really fit between display and VSync. I'm currently reworking one of my programs to try to increase the time available for calculation; I haven't figured out how best to handle the frame counting, but the basic concept is: Kernel: bit INTIM bpl Kernel1 Kernel1: stx xsave inc fct lda fct kwt2: bit INTIM bmi kwt2 sta WSYNC lsr bcs mainframe lda #$42 sta VSYNC sta WSYNC sta WSYNC lda #[whatever] sta INTIM64 [forget the name] lda fct clc adc #1 and #$7F sta fct sta WSYNC lda #$00 sta VSYNC ldx xsave rts mainframe: ; Main display kernel goes here (nine cycles after the last WSYNC) ... lda #[whatever] sta INTIM64 ldx xsave rts maincode: ; Periodically, in a time-consuming loop: bit INTIM bpl $+5 jsr Kernel1 ; A and Y trashed; X preserved ; BPL target is here This would allow user code to execute during both vertical blanking intervals. The values loaded into INTIM64 would have to be chosen so that the timer would expire about 150-200 cycles BEFORE the target horizontal blank. The user code would then have to ensure that it checked INTIM every 100 cycles or so [with a 3, 4, 5, or 6 cycle penalty each time it did so depending upon method]. It would be possible to save code space at the expense of time by making the kernel return if INTIM was not yet negative and calling it unconditionally. The extra 12 cycles overhead would seem unduly severe, however. Quote Link to comment Share on other sites More sharing options...
+Andrew Davie Posted June 22, 2005 Author Share Posted June 22, 2005 This would allow user code to execute during both vertical blanking intervals. The values loaded into INTIM64 would have to be chosen so that the timer would expire about 150-200 cycles BEFORE the target horizontal blank. The user code would then have to ensure that it checked INTIM every 100 cycles or so [with a 3, 4, 5, or 6 cycle penalty each time it did so depending upon method]. It would be possible to save code space at the expense of time by making the kernel return if INTIM was not yet negative and calling it unconditionally. The extra 12 cycles overhead would seem unduly severe, however. 878537[/snapback] '2600 Boulder Dash ® uses a slightly different technique, but along the same lines -- distributing complex calculations into the available calculating time in the 'vertical blank intervals'. In fact, these are the intervals where we are waiting for the timer to reach 0. Your method employs continuous checks. Boulder Dash ® uses a rough approximation (but always high) of the amount of time a particular bit of code will take (in TIM64T units) and pre-checks if there is enough time left on INTIM before proceeding with doing that bit of code. Something like this... lda INTIM cmp #TIME_REQUIRED_FOR_THIS_SECTION bcc notEnoughTime jsr DoThisSection notEnoughTime '2600 Boulder Dash ® subdivides the game logic into many small steps, and approximates (always high) the time requirement for each of these steps. By doing this, much work can be done inside the intervals while waiting for INTIM to zero, without many checks of the INTIM value being required while you are doing that work. As stated, just the one check at the start of each step -- and that check specific to the amount of TIM64T 'ticks' required for that step. As a reference point, to draw a 'character' in '2600 Boulder Dash ® takes about 12 TIM64T ticks maximum, and to process the slowest object (eg: boulder) is about 25 ticks. One niceity about this system is that if a step ends early, that's fine -- we just have to guarantee that it is never late. If it ends early, we just proceed on to the next step, first checking there are enough ticks left for IT to do its stuff. If there aren't, we're coming to the end of the delay timer anyway, so we return. Cheers A Quote Link to comment Share on other sites More sharing options...
supercat Posted June 22, 2005 Share Posted June 22, 2005 '2600 Boulder Dash ® uses a slightly different technique, but along the same lines -- distributing complex calculations into the available calculating time in the 'vertical blank intervals'. In fact, these are the intervals where we are waiting for the timer to reach 0. Your method employs continuous checks. Boulder Dash ® uses a rough approximation (but always high) of the amount of time a particular bit of code will take (in TIM64T units) and pre-checks if there is enough time left on INTIM before proceeding with doing that bit of code.878587[/snapback] Interesting. I can see that could offer a performance benefit over doing constant checks. If the code is broken up into pieces of about 64 cycles each, that benefit would amount to about 8%. Possibly worthwhile for programming wizards trying to eke out every possible bit of speed in a processor-intensive game, but not so import for mere mortals merely trying to avoid losing vsync while recalculating information for the start of a new level, etc. (users won't care if it takes 5 frames or 10 frames to place all the obstacles in a new level, but losing vsync is ugly). Quote Link to comment Share on other sites More sharing options...
Starman Posted June 22, 2005 Share Posted June 22, 2005 Thanks for the heads up on the sleep macro. I misread the macro. I got it working now. Mike Quote Link to comment Share on other sites More sharing options...
SavedByZero Posted October 25, 2019 Share Posted October 25, 2019 So what was the solve for the "Origin Reverse-indexed" issue with this chapter's code? Because I have it copied verbatim, and I'm getting the same error. I see people changing the code and saying "do it this way instead", which is cool, but I'm first curious to know why code that should compile, doesn't. Quote Link to comment Share on other sites More sharing options...
+Andrew Davie Posted October 25, 2019 Author Share Posted October 25, 2019 3 hours ago, SavedByZero said: So what was the solve for the "Origin Reverse-indexed" issue with this chapter's code? Because I have it copied verbatim, and I'm getting the same error. I see people changing the code and saying "do it this way instead", which is cool, but I'm first curious to know why code that should compile, doesn't. Please post your code/files here and we'll have a look-see. And, just being a bit pedantic... dasm is an assembler, not a compiler Quote Link to comment Share on other sites More sharing options...
SavedByZero Posted October 26, 2019 Share Posted October 26, 2019 (edited) 21 hours ago, Andrew Davie said: Please post your code/files here and we'll have a look-see. And, just being a bit pedantic... dasm is an assembler, not a compiler Can't shake the habits of my day job I suppose. I got it to work, but I'll be darned if I know how. I literally cut out the words at the end to get it to assemble, watched it show me a black screen, messed with it to make it more like the stuff I've seen here, ran into some assembly errors, put the words back, then ran it again and it worked, and I'd swear it's identical to what I started with except for the patterns of the draw routine as I messed with them to see what I could change. I do notice that the assembler is super sensitive to tabs and spacing for some things. Edited October 26, 2019 by SavedByZero Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.