Jump to content

DanBoris

Members
  • Content Count

    1,086
  • Joined

  • Last visited

Blog Entries posted by DanBoris

  1. DanBoris
    One of my favorite programming languages for the Atari 8-bit computers is OSS Action!. Action is a compiled, structured programming language similar to C or Pascal. One of the impressive features of Action is it’s very fast an efficient compiler. Not only does it compile programs quickly, it produced pretty tight machine code executables. I thought it would be interesting to take a look at some of the machine code that the compiler produces.
     
    First lets look at the classic “Hello World” program. This Action code simply prints HELLO WORLD:
     

    PROC MAIN() PRINTE("HELLO WORLD") RETURN
     
    The resulting assembly code would look like this:
     

    246E: JMP $2471 2471: JMP $2480 2474: .byte #$0B 2475: .string "HELLO WORLD" 2480: LDX #$24 2482: LDA #$74 2484: JSR $A46C 2487: RTS
     
    The program starts with two JMP instructions. Although this isn’t very efficient, it’s at the start of the program so isn’t a big problem. The first JMP jumps to the MAIN procedure which is the starting point for all Action programs. The second JMP is the start of the MAIN procedure and it needs to jump over the static text that stored before the procedure.
     
    Next we have the static text we will be printing. The first byte contains the length of the string and the rest the actual string data. After the string we have the actual code for the procedure. It simply loads the address of the string into the X and A registers and calls a library subroutine to do the printing.
  2. DanBoris
    I've been working on a Pong related project and I thought I would post a screen shot:
     

     
    Yes, this may look like any one of 1000 different Pong simulation programs that people have written, but this one it a little bit different. The screen shot you see here was generated by doing a chip level simulation of the Pong circuit using a general purpose digital logic simulation engine. Here a little sample of the code used to define one of the score counters:
     

    Me.AddPart(New Nor2("F3B", Node("L"), Node("MISSED"), Node(132))) Me.AddPart(New IC7490_4bit("C7", Node(132), Node("SRST"), Node("SRST"), Node("SCORE1_1"), Node("SCORE1_2"), Node("SCORE1_4"), Node("SCORE1_8"))) Me.AddPart(New IC74107("C8A", Node(1), Node(1), Node("SCORE1_8"), Node("/SRST"), Node("SCORE1_10"),Node("/SCORE1_10"))) Me.AddPart(New NAnd3("D8A", Node("SCORE1_1"), Node("SCORE1_4"), Node("SCORE1_10"), Node("/STOPG1")))
     
    So, can you actually play Pong on this? You would be able to except for the problem of speed which I always knew would be a major hurdle to logic level simulation. Currently the program takes about 15 seconds to render each frame on an Athlon 64 3000+, and although this performance can probably be increased it's a long way from being truly playable.
     
    My goals on the project are two fold. First to provide a proof of concept that other people may be able to take and optimize to get better performance. Second to provide an accurate way of simulating these old discrete logic arcade games so we can see how they looked and behaved, and possibly use this information to write accurate ports of the games that run at full speed.
     
    I still have a lot of cleanup work to do on the program but then I plan to release the source and executable. After that I'd like to add some more games to it, possibly Breakout next.
     
    Dan
  3. DanBoris
    One of the games I remember from the good old days with my Atari 800 computer is the dungeon crawler Telengard by Avalon Hill. The game was written in Basic so I thought it would be interesting to take a look at the code for the game. One of the most interesting parts is the way the maze if generated. The dungeon in the game is very large, it has 50 level and each level is 200 by 200 rooms. The dungeon is also the same every time you play so you can map it out as you go along. I was really curious how the achieved this.
     
    The programmer did it by using a pseudo-random algorithm to generate the map. This results in a map that is complex enough to be interesting, but not so complex that it's unplayable.
     
    This is the formula that is used to determine the appearance of each room:
     

    XO = 1.6915 YO = 1.4278 ZO = 1.2462 q = x * XO + y * YO + z * ZO + x * YO + y * ZO + z * XO hi = q And &HFF q = x * y * ZO + y * z * XO + z * x * YO If (q And 3) = 0 Then q = (q / 4) And &HF If q > 9 Then q = q - 9 hi = hi + q * 256 End If
     
    XO, YO, and ZO are constants
    z = dungeon level
    x,y = room position in level
     
    This formula is run for each room that is displayed and the result, hi, is interpreted as follows:
     
    bits 0,1: Upper wall: 0,1 = nothing, 2 = door, 3 = wall
    bits 2,3: Left wall: 0,1 = nothing, 2 = door, 3 = wall
    bits 8-11: If not 0 then there is something special in the room
     
    1 = Inn
    2 = Pit
    3 = Teleporter
    4 = Stairway
    5 = Alter
    6 = Fountain
    7 = Cube
    8 = Throne
    9 = Box
     
    The bottom wall and right wall of a room come from the left and top wall of adjacent rooms. There is also code that knows to cap the right side and bottoms of the rooms at the edge of the maze.
     
    You can take a look at the full commented Basic code for Telengard on my web site along with a VB.NET program that allows you to browse the maze.
     
    http://www.atarihq.com/danb/Telengard.shtml
  4. DanBoris
    In the discussion of the ball horizontal control circuit, supercat proposed a simplified circuit that would perform the same function. This is the circuit I believe he is describing:
     
    starfire.zip
     
    It actually comes pretty close to working, and it does use less chips, but instead of generating 3 speeds it generates 4.
     
    The VSYNC input will make sure the circuit is only enabled for the first four lines on the screen. For each count on the hit counter the MOVE signal will be enabled for either 1,2,3 or 4 lines as follows:
     
    Count Move high on line(s)
    0-3 2
    4-7 1,2
    8-11 2,3,4
    12-15 1,2,3,4
     
    I may still be possible to modify this further to get it to work exactly like the original Pong worked.
  5. DanBoris
    As supercat observed in my last entry, I totally missed this part the first time around. Better late then never...
     
    _pesco_pacman_v8.zip
     
    For anyone who has played Pong, you may remember that the more times you volley the ball back and forth, the faster the ball will move. This counter counts those volleys. Each time the ball is hit by either player the counter will increment until it reaches a count of 12, at which point it will be disabled by NAND gate E1. The counter is reset by the RST SPEED signal which we will discuss at a later date
     
    There are three counter states that are decoded by the logic gates B1, E1 and H1.:
     
     

    Counter H1(11) H1(3) 0-3 0 0 4-11 0 1 12-15 1 1
     
     
    At the beginning of each video frame these outputs are gated through two more NAND gates (H1) by the VRESET signal where they are applied to the clear inputs on J/K flip-flops H2. The flip-flips are clocked by the /256H at the start or each horizontal line. When the counter is in the range of 0-3, the MOVE signal will be high for one horizontal line, in the range 4-11, it will be high for two horizontal lines, and in the range of 12-15, three horizontal lines. The more lines the MOVE signal stays high for, the faster the ball will move.
     
    Let’s take a look at how this works for the 12-15 counter state. Initially after VRESET the Q output of both flip-flops will be low (due to the clear) which will result in MOVE being high. At the start of the next horizontal line when /256H goes high, since the J and K inputs of the first flip/flip are high, it will toggle setting it’s Q output high. The J and K inputs of the second flip/flop are both low so its state will not change. After the first clock pulse MOVE is still high. On the second clock pulse, the first flip-flip will toggle again, but since the J input of the second one is high, its output will go high. After the second clock pulse MOVE is still high. On the third clock pulse the first flip-flop will toggle again, settings its Q output high, and the second flip-flop will remain unchanged. Both inputs of NAND H4 are now high so its output goes low. The MOVE signal will now stay low until the next VRESET.
  6. DanBoris
    It's been a while since I did an update to my Pong circuit analysis, but I just get distracted by other project far to easily!
     
    The next section is the hit sound, the sound made when the ball is hit.
     
    _corrected_pesco_pacman_roms_v6a.zip
     
    When the ball hits either paddle the /HIT signal will go low which will clear C2 and set the /Q output high. For /HIT to go low VPOS256 has to be high, so there won’t be another positive edge on VPOS256 to clock the counter until the next frame. On the next positive edge of VPOS256 after HIT as gone high the sound will be turned off. I haven’t worked out the exact timing on this but it should only stay enabled for one or two frames. The /Q output enables NAND gate C3 and allow the VPOS16 signal to pass through to the sound output.
  7. DanBoris
    The next couple of circuits are used to generate the sounds for the game. The first sound circuit is for the score sound. This sound is played whenever either player misses the ball.
     
     
     
    When a player misses the ball the /MISS signal will go low which will trigger the 555 timer G4. When triggered the output of G4 will go high and stay high for 242ms at which point the output will return low. When the output of the timer is high the 32V signal will be allowed to pass through NAND C3 to the sound output. 32V comes from the vertical counter and has a constant frequency of 213Hz.
  8. DanBoris
    The reply to my last entry explaining the transistor portion of the game control circuit allowed me to work out the complete opertation of this section.
     
    When the coin switch is activated it momentarily pulls /SRST low which resets the score counters. The two C9 inverters generating an active high reset signal which is also used to reset the score counters. When /SRST goes low it pulls the base of Q2 low which turns on Q2, which pulls the base of Q1 high turning Q1 on. With Q1 on the base of Q2 will be pulled low so even when the coin switch turns off Q2 and Q1 will stay on. At this point the game has been reset so /STOPG1 and /STOPG2 (Stop Game) are both high, setting pin 2 of D2 low. Pin 3 of D2 is being held low by Q1 so /ATRACT is high turning off the attract mode.
     
    The game will stay in the state until one of two things happens.
     
    1. If either score counter reaches the game ending score, /STOPG1 or /STOPG2 will go low, setting pin 3 of B2 high which will turn on the attract mode through D2. This will also put a low on pin 8 of E4 which turns off Q2 which turns off Q1.
     
    2. If the Antenna input goes high as a result of a static shock to the machine, Q3 will turn on pulling the base of Q1 low, which turns off Q1 which turns off Q2.
     
    After either of these situations has occurred Q2 and Q1 will be off, putting a high on pin 3 of D2 and keeping the game in attract mode. The game will stay in the state until the coin switch is activated again and the whole process starts over.
  9. DanBoris
    The ball vertical motion is controlled by a slip counter just like the horizontal.
     
     
     
    The counter is clocked by the /HSYNC signal so it will increment once per line. Since /VBLANK goes to the ENT input of B3 it will stop the count during VBLANK. When the two stages of the counter reach 255 the load signal will be triggered by B2. The upper stage of the counter is loaded with 0, and the lower portion is loaded with the output of the vertical control circuit. The values from the vertical control result in the following numbers of counts:
     
    7 – 248
    8 – 247
    9 – 246
    10 – 245
    11 – 244
    12 – 243
    13 – 242
     
    There are 245 visible lines on the screen (excluding the VBLANK region), so a value of 10 from the vertical control will result in no vertical motion, a value less then 10 will move the ball down on the screen, and a value greater then 10 will move the ball up on the screen.
     
    When the counter reaches a value of 252 the output of E2 will go low and stay low until the counter resets 4 counts later, making the ball 4 pixels high.
  10. DanBoris
    I am hoping that I can get a little help figuring this section out. I am pretty good with digital analysis, but my transistor theory is a little (ok a lot) rusty. The purpose of this section is to control the game state. Here are the things that I do know about this circuit:
     
    - When either /STOPG1 or /STOPG2 goes low, indicating that one of the score counters has reached the game ending score, the output of B3 (STOP G) will go high which stops the game. The high on STOP G will also set /ATRACT low, putting the game into Attract mode.
     
    - When a coin is inserted, COIN SW is momentarily closed which takes /SRST low, resetting the score counters.
     
    - Antenna is an external input that is used to detect a static shock to the machine. In theory is should prevent someone from shocking the machine into giving them a free game.
     
    I would appreciate any help deciphering how the rest of this circuit works.
     

  11. DanBoris
    The first step in counting the score is to determine when one player or the other has missed the ball. This turns out to be very easy to detect because the only graphic object that can move off the visible screen is the ball. The circuit shown below ANDs the horizontal component of the video with the HBLANK signal to create the /MISS signal. Whenever there are graphics being displayed during horizontal blank, /MISS will go low indicating that the ball went off the screen. /MISS next passes through E1 where it is ANDed with /ATTRACT, this prevents the scores from incrementing during the attract mode. The final output signal is /MISSED which goes low when the ball is missed.
     

     
    The next parts of the score counter are two almost identical circuits that are used to keep track of the score, one for each player. When L is low, it means that the ball is moving right. When MISSED goes low indicating that the player missed the ball, the output of F5 will go high which will increment the score counter C7. The counter is actually composed of two chips, C7 which is a decade counter (which means it counts 0 to 9) and J/K flip flop C8 which provides the 10’s digit of the score. The final function of this circuit is to determine when the game is over based on the score. Pong can be configured to play games up to a score of either 11 or 15, this is set by switch SW1A. When the switch is in the 11 position, D8 ANDs together the 1 and the 10 output of the counter thus ending the game at 11. When the switch is in the 15 position, D8 ANDs together 1, 4 and 10, ending the game at 15. When the end score is reached /STOPG1 goes low ending the game
     
     

  12. DanBoris
    Let's take a break from looking at Action! math and take a look at procedure calls. We will start up with something that is trivially simple:
     

    Proc test() Return Proc main() Test() Return
     
     

    0E61: 4C 64 0E JMP $0E64 0E64: 60 RTS 0E65: 4C 68 0E JMP $0E68 0E68: 20 61 0E JSR $0E61 0E6B: 60 RTS
     
    Our main procedure starts at 0E68 and it begins with a call to procedure Test using a JSR. The procedure Test starts at 0E61 which immediately jumps to the RTS since the procedure is empty. What I haven’t been able to figure out is why the JMP instruction is there since it doesn’t jump over anything. I have yet to find a case where it actually jumps over something.
     
    Next let’s look at how simple parameter passing is done:
     

    Proc test(byte I) Return Proc main() Test(1) Return
     
     

    0E88: .BYTE #$00 0E89: 4C 8C 0E JMP $0E8C 0E8C: 8D 88 0E STA $0E88 0E8F: 60 RTS 0E90: 4C 93 0E JMP $0E93 0E93: A9 01 LDA #$01 0E95: 20 89 0E JSR $0E89 0E98: 60 RTS
     
    This is the same as the first example, but now we pass a single BYTE parameter to the procedure. Here is an area where Action! does a good job at optimizing. Since there is only a single BYTE being passed it uses the most efficient way possible, it just passes it in the accumulator. At 0E93 the value 1 is loaded into the accumulator then the procedure is called. At 0E8C that value passed is stored in the local variable I. You will notice that space for this variable is allocated before the start of the procedure code, so this doesn’t answer the mystery of the JMP instruction.
  13. DanBoris
    In my last post I showed how the Action! compiler produces some pretty optimized code for CARD math under certain circumstances. This time I will show the more general case which should be pretty familiar to anyone who has done 6502 programming.
     
    Here is the Action! program and it’s dis-assembly:
     

    CARD I PROC MAIN() I=2 I=I+2 RETURN
     

    0E6C: .BYTE 00,00 0E6E: 4C 71 0E JMP $0E71 ;I=2 0E71: A0 00 LDY #$00 0E73: 8C 6D 0E STY $0E6D 0E76: A9 02 LDA #$02 0E78: 8D 6C 0E STA $0E6C ;I=I+2 0E7B: 18 CLC 0E7C: AD 6C 0E LDA $0E6C 0E7F: 69 02 ADC #$02 0E81: 8D 6C 0E STA $0E6C 0E84: AD 6D 0E LDA $0E6D 0E87: 69 00 ADC #$00 0E89: 8D 6D 0E STA $0E6D 0E8C: 60 RTS
     
    Most of what is here we have discussed before so I won’t go into great detail. As you can see since I is initialized to the value 2 the INY optimization can’t be used so two loads and stores are performed. It’s interesting to note that a different register is used for each byte. I am not sure why the compiler chooses to do this, although in the end it doesn’t affect program size or performance.
     
    The add part of the program is standard 6502 16-bit math, adding the lower byte, then adding the upper byte which also handles and carry from the lower byte.
  14. DanBoris
    I can’t believe it’s been over a year since my last blog post, time sure does fly! I thought it was about time to get back to some posts and continue my Action! language topic.
     
    Last time I talked about BYTE math, this time we will start looking at CARDinal math. In Action the CARD data type is a two byte unsigned value. Here is the first piece of Action! code:
     

    CARD I PROC MAIN() I=1 I=I+1 RETURN
     
    Here is the resulting disassembly:
     

    0E6A: .BYTE #$00,#$00 0E6C: 4C 6F 0E JMP $0E6F 0E6F: A0 00 LDY #$00 0E71: 8C 6B 0E STY $0E6B 0E74: C8 INY 0E75: 8C 6A 0E STY $0E6A 0E78: EE 6A 0E INC $0E6A 0E7B: D0 03 BNE $0E80 0E7D: EE 6B 0E INC $0E6B
     
    We start at memory location $E6A where two bytes are set aside for variable I, and then as usual we have a JMP to that start of the code.
     
    The next four instructions assign the value 1 to I and you can see a nice little optimization here. First a 0 is put into the high byte of the variable. The low byte needs to have 1 in it and since the compiler knows that Y already contains 0 in can simply use the increment Y instruction to get a 1. This will save two bytes over using LDY #$01.
     
    The next three instructions handle the I=I+1. Just like in the BYTE math the compiler knows that +1 is just an increment so uses the INC instruction on the low byte of the variable instead of doing an ADC. The branch checks for an overflow and if there is one we then increment the high byte of the variable.
  15. DanBoris
    One of the trickier parts of the 7800’s MARIA graphics chip is understanding how to setup the display lists and display list list (DL and DLL). Even trying to put a single sprite on the screen can be tricky. In this article I will explain how to setup these data structures to get a simple sprite on the screen.
     
    In this example we will take a very narrow scenario just to explain the basics of how to get a sprite on the screen. I will show how to display a single 16 line high by 1 byte wide sprite. If you want to see complete code on how to do this take a look at my 7800 Sprite Demo source code. For more details on the DLL and DL structure see the 7800 Software Guide. Both these files are available on my site http://www.atarihq.com/danb/a7800.shtml.
     
    The first step is to setup the Display List List (DLL). Since we will be using 16 line high sprites the zones will also be 16 lines high, so this will give us 12 zones for a total of 192 lines. The first byte of each DLL entry will be $4F. This will enable 16 line holey DMA (I will explain this later) and an offset of 15 which corresponds to the 16 line height of the zone. The second and third bytes of the DLL are just a pointer to the Display List (DL) for this zone. For the purpose of this discussion we won’t worry about this address.
     
    Before we move onto how to setup the DL entries lets first have a refresher on how graphics data is setup in memory on the 7800. The graphics data for a sprite is NOT stored in sequential bytes in memory. Each line of graphics data for a sprite is stored on sequential pages (a page is 256 bytes) of memory, and it’s stored upside down. Let’s say we want to store our sprite data in the memory block between $A000 and $AFFF. For our first sprite the data for the first line of the sprite would be stored at location $AF00, the second line and $AE00, the third at $AD00, and so on until the last line is in location $A000. If we wanted to define a second sprite we would just move up one location in each page, so the first line would be in $AF01, the second in $AE01, and so on until the last line is in location $A001.
     
    Now let’s talk about how the DL entries would be setup. We will look at two scenarios, the first being where the sprite starts on the first line of a zone so it’s entirely in one zone, and the second scenario where it starts on the second line of a zone so it spans two zones. I am just going show how to use 5 byte DL entries, not the 4 byte entries.
     
    We start with the first scenario where the sprite in entirely in a single zone. The first thing we need to do it determine which zone the sprite is in. This is done simply by dividing the vertical position by 16 which is the same as taking the top 4 bits of the position. Once we know what zone the sprite is in we can start building the DL entry.
     
    The first byte of the DL entry is the low byte of the address of the sprite data. The data for the first sprite starts at location $A000 (even though it’s upside down we still say it starts at the lowest address), so this byte would be $00. The second byte will be $40. This just sets up the display mode and is not important for the purposes of this article. The third byte is the high byte of the sprite data, so it would be $A0 in this case. The fourth byte specifies the palette and the width, we will make this $1F and again the details are not important. Finally the fifth byte is the horizontal position of the sprite.
     
    The second scenario is a little more complicated. Since the sprite doesn’t fall entirely in one zone there will have to be a DL entry in two different zones. Both of these entries will be the same as the first scenario with the exception of the high byte of the graphics data (byte 3).
     
    To understand how these values will be determines you need to understand how the MARIA chip draws the screen. At the start of each DLL entry the offset value is loaded into a register in the chip. When MARIA goes to fetch graphics data it adds the offset value to the high byte of the graphics data. When it completes a line and moves to the next one it de-increments the offset value. So in the first scenario, on the first line the offset will be $0F which will get added to the high byte $A0, making an address of $AF00. This is why the graphics data is stored upside down.
     
    To have our sprite start on the second line of a zone instead of the first we would set the upper address to $A1. We are getting this by taking the lower 4 bits of the vertical position and adding it to $A0. When MARIA draws the first line it will add the offset of $0F to $A1 to get an address of $B000 for the first byte, when it draws the second line the offset of $0E will be added to $A1 so the second line of the zone would come from $AF00 which is the first line of the sprite data. But if we are taking the first line from $B000 wouldn’t this cause ‘garbage’ to be displayed at the top of the zone? No, because this is where holey DMA comes in to play. When 16 line holey DMA is enabled and MARIA fetches data from memory, if address bit 12 is high then zero is returned instead of data that is in memory. $B000 has bit 12 high so the graphics data will be zero for the first line of the zone.
     
    The first zone will contain the first 15 lines of the sprite, but the final line of the sprite will appear in the next zone. Again, the only difference from the first DL entry will be the high address of the graphics data. In this case we would set the high byte to $91. When the first line of this zone is drawn the offset will be $0F added to $91 = $A000 which is the last line of our sprite data. On the second line of the zone the offset will be $0E added to $91 = $9F00. Once again holey DMA will take effect since bit 12 is high so the graphics data will be zero. This will continue to the end of the zone.
     
    That is the basics of how to setup sprite data in the display lists. If we were doing more then one sprite we would iterate through each sprite repeating the process above and appending the DL entries to the end of each DL. Finally, once we have finished creating all the DL entires we would need to go back and terminate each DL. This is done by adding a final entry where the 2nd byte is zero.
  16. DanBoris
    Next let’s look at some simple byte variable manipulation. Here is the first sample Action program:
     

    BYTE I PROC MAIN() I=1 I=I+2 RETURN
     
    And the assembly code
     

    24B3: .byte $00 24B4: JMP $24B7 24B7: LDY #$01 24B9: STY $24B3 24BC: CLC 24BD: LDA $24B3 24C0: ADC #$02 24C2: STA $24B3 24C5: RTS
     
    This code starts at memory location $24B3 which is where the global variable I is stored. Next we have a jump instruction that gets us to the MAIN procedure. Like last time this is a little inefficient but not a big problem at the start of the program.
     
    Next we load the immediate value 1 into I. I assume they use the Y register for this so that A can be reserved for math operations which will allow for some optimizations under certain conditions. I have another example that I will post at some point that shows this. In this situation by using Y they actually miss out on the opportunity for an optimization. If A had been used the LDA would not have been necessary. Finally a normal 6502, 8-bit add sequence is executed and the result stored by in I.
     
    Now let’s look at almost the same program, but it will add one to I instead of two:
     

    BYTE I PROC MAIN() I=1 I=I+1 RETURN
     
     

    0E58: .byte #$00 0E59: JMP $0E5C 0E5C: LDY #$01 0E5E: STY $0E58 0E61: INC $0E58 0E64: RTS
     
    As you can see the code is quite a bit smaller. The compiler recognized that I=I+1 is an increment so it uses the INC instruction on the memory location where I is stored resulting in a savings of three instructions.
     
    You may notice that this example is at a different memory location then the first one. I am not actually sure why this happened. I have been compiling these examples using an emulator and those two examples were compiled at different times so it’s possible that I had the emulator configured differently each time.
  17. DanBoris
    I’ve recently been doing some research into how scrolling games work on the 7800. I have created a demo program that shows a method for doing horizontal and vertical fine scrolling controlled with the player one joystick. I’ve tested this program on an emulator but on the actual hardware. The attached ZIP files contains the source and binary that can be used with an emulator.
     
    Display List
     
    The display list for the demo consists of 24, 8 line high regions. Each region will display 41 characters in the 320D mode using indirect graphics. Each region has two DL entries one to display the first 20 characters, the second to display the remaining 21. Two DLs are needed because you can’t display more the 32 characters with a single DL.
     
    Horizontal Scrolling
     
    The horizontal scrolling turns out to be pretty easy because of the way the 7800 handles wrap around of the graphics. A horizontal position of 0 – 159 is visible on the screen but anything beyond 159 won’t be displayed. If you position a graphic with an hpos just below 255 the beginning of the graphic won’t be displayed but the rest will. So to do horizontal scrolling it’s simply a matter of changing the hpos of the two dl entries on each line. You will notice that the second DL entry has 21 characters instead of 20. This is necessary because even though the screen is 40 characters wide 41 different characters will be displayed during scrolling with partial characters at either end of the line.
     
    Vertical Scrolling
     
    Vertical scrolling is a little trickier. Here we need to end up with partial characters at the top and bottom of the screen during scrolling. The cropping of the top row can easily be handled by varying the height of the top region between 1 and 8 lines. As the top region gets smaller the bottom region will needs to get bigger so its height is set to (8 – top region height). Simply changing the height of last region won’t actually give us the result we need, because as the region get smaller the top of the characters will be cropped off not the bottom. To compensate for this we need to adjust the character base pointer for just the last zone. For each line smaller we make the last region we need to move the character base pointer up one page. We can achieve this using a display list interrupt on the last zone to set the modified character base, then another one in the next zone to get it back to the default.
     
    Course Scrolling
     
    Note that what I am demonstrating in this program is just fine scrolling. Once I have scrolled a full character width or height in a specific direction I reset the scrolling back to the start of the character. It gives the appearance of continuous scrolling but I am really just repeatedly scrolling over the same characters. You could layout the entire character map for a screen in RAM or ROM then scroll the screen over that map. When you reached the end of a fine scroll you would reset the scroll and then update all the memory pointers in the display list to course scroll to the next row of column of the map.
  18. DanBoris
    Occasionally questions come up about the special “hidden” control register in the 7800, and I usually end up searching back through the forum archive for the answer, so I thought I’d finally document it somewhere I can find it!
     
    The purpose of this control register is to switch the 7800 from 7800 mode to 2600 mode. The register can be written to using any address between $0000 and $001F. This address range overlaps the TIA so once the register is set you need to set the lock bit (see below) before you can use the TIA.
     
    There are 4 bits in the register and they work as follows:
     
    Bit 0 (lock mode) – When set to 1 this bit locks the control register so that no more writes will affect it. The only way to clear the lock is to power cycle the console.
     
    Bit 1 (MARIA Enable) - 1 = enable MARIA (also enables system RAM), 0 = disable MARIA (only RIOT RAM is available). 0 also disables the EXTRAM signal from the expansion connector.
     
    Bit 2 (EXT) - 0 = enable BIOS at $8000-$FFFF, 1 = disable BIOS / enable cartridge
     
    Bit 3 (TIA-EN) - 1 = enable TIA video pull-ups (video output is TIA instead of MARIA) and also disables 2 button joystick mode, 0 = disable TIA video pull-ups (video output is MARIA instead of TIA).
     
    Besides the functions controlled by the register bits, it also controls the HALT input to the 6502. When the 7800 is first powered up HALT is blocked from getting to the 6502, but after 2 writes to the control register (the data written doesn’t make a difference), the HALT input will be enabled. I am not sure why this function exists. I can only speculate that it’s to prevent the MARIA from spuriously halting the 6502 until it’s properly initialized.
  19. DanBoris
    Looks like we have finally come to the last Pong circuit we need to cover. I just looked back at the archive for this blog and was shocked when I realized that I have been doing this Pong circuit description for almost 2 years! I know I was going through this pretty slowly but never realized I had taken that long. On to the final circuit...
     
     
     
    This circuit is used to generate a sound whenever the ball hits the top or bottom of the screen. During normal play /SERVER is high so the flip/flop won’t be cleared. When the vertical portion of the ball video (VVID) is high in the VBLANK area the ball has gone off the top or bottom of the screen which will cause the Q output of F3 to go high. This will allow VPOS32 to pass through C3 to generate the hit sound. When the ball leave the VBLANK area, VVID will be low during VBLANK setting Q low and turning off the sound. Between serves the /SERVER signal will be low keeping the Q output of F3 low. This prevents the ball from making the hit sound as it is move into position for the next serve.
  20. DanBoris
    The next thing I want to talk about is the attract mode, which is the mode the game is in when it’s not being played, designed to “attract” new players. Most of the schematics for this section I have posted already, so I will just describe how attract effects each section. The attract signal is generated by the game control circuit and is fed as both an active high and active low signal to various other circuits in the game.
     
    In the paddle circuit, /ATTRACT holds the preset pin of flip-flop H3 low, which holds pin 6 low and thus the output of NAND gate G3 high, which prevents the paddles from being displayed.
     
    While in attract mode the ball will bounce continuously around the screen bouncing off not just the top and bottom of the screen, but of the sides. The vertical ball control works pretty much the same as in play mode, but the ATTRACT signal hold the A5 and B5 flip-flops cleared which is equivalent to the ball having hit the top part of the paddle. This keeps the ball moving vertically at its sharpest angle.
     
    In the horizontal direction when the ball goes off either side of the screen the /MISS signal goes low as usual, but the /ATTRACT signal on the input of NAND gate E1 prevents the MISSED from going low, which prevents the score from incrementing. The /MISS signal will trigger timer G4 which brings the signal SC high and reverse the balls direction through NAND gate C1, but unlike in play mode, the /ATTRACT signal will prevent the horizontal ball counter from being cleared so it will always be displayed. I neglected to show this particular gate in the Ball Horizontal Counter circuit, so here it is.
     
    jerky.zip
  21. DanBoris
    We have almost looked at the entire Pong circuit, just a few miscellaneous sections to go. This is the server timer circuit
     
    2puck.zip
     
    During normal play the output of F4, /RUN and STOP G will all be low which will make the output of E5 high, which will keep the SERVE output of B5 low. When the ball is missed, /MISS will go low, pin 6 of E6 will go high which resets the ball speed counter. Pin 3 of E6 will then go low which triggers the 555 timer and sets the output high, and sets the output of E5 low. This low output will clear B5, setting SERVE high which will hold the horizontal ball counter in reset. After 1.7 seconds the output of the 555 timer will return low, setting the output of E5 high. This provides for a small delay between missing the ball and the next serve. The SERVER output of B5 will remain high until the PAD1, at which point SERVE will go low releasing the reset on the horizontal ball counter.
     
    The use of PAD1 as a trigger to release the reset on the ball counter may seem a little odd, but it turns out to be a clever trick to get the ball back to the middle of the screen for the next serve. When the reset on the counter is released, the ball counter will be at zero and the horizontal screen counter will be at 128, because that’s the horizontal position of paddle 1. If you look back at the section on the ball horizontal counter you will see that the ball in enabled when the counter reaches a count of 508. When the horizontal screen counter reaches 455 the ball counter will be at 327. There will then be 80 counts during HBLANK when the ball counter doesn’t increment. After HBLANK, the ball counter will count 181 more times, thus displaying the ball at location 261. Since the net is at 256 this starts the ball very close to the net.
  22. DanBoris
    While studying the score counters I was having a hard time figuring out how the score was credited to the correct player, it seemed that misses on the left side where scoring on the left side instead of the right. Turns out that I had a mistake in an earlier section, Ball Horizontal Control (Part 1). Turns out that I had the /HIT1 and /HIT2 counters reversed, the circuit should look like this:
     

     
    This is the proper description of how this circuit should work.
     
    The flip/flop H3 is used to control the direction of the ball. When the ball hits the left paddle, /HIT1 will go low, which will set pin 8 of H3 high, indicating that the ball should now move right. When the ball hits the right paddle, /HIT2 will go low, which will set pin 9 of H3 high, indicating that the ball should now move left. When MOVE is low, Aa will be 0 and Bb will be 1 which keeps the ball stationary.
     
    When L and MOVE are high, Aa will be 1 and Bb will be 1 which causes the ball to move left. When R and MOVE are high, Aa will be 1 and Bb will be 0 which causes the ball to move right. We will discuss the function of the CLK input to H3 in a later.
     
    The L and R signals indicate which direction the ball is currently moving and this is important to understanding the way the score counters work.
×
×
  • Create New...