Jump to content
  • entries
  • comments
  • views

DPC+ARM - Part 8, Multiple Functions



NOTE: This blog series is obsolete.  Head on over to the Harmony/Melody Club where you'll find information on the new Linaro compiler and the new CDFJ coprocessor/bankswitch scheme that has many improvements over DPC+.


NOTE: This is an advanced Atari 2600 programming series. It will not cover basic things like triggering a Vertical Sync, what a Kernel is, why a timer is used in Vertical Blank, etc. If you need to learn that, check out with the following:

  • Collect - detailed development of a 2K game
  • 2600 Programming for Newbies - use the Sorted Table of Contents topic that's pinned at the top in order to easily access the tutorial topics in order.

Multiple Functions


The code has been revised so the 6507 can now request specific functions for the ARM code to run:

  • Initialize() - Run when Atari is first powered up.
  • Overscan() - Run during the game screen's overscan. Will eventually update the game state (player movement, sound effects, etc).
  • VerticalBlank() - Run during the game screen's vertical blank. Will eventually update the datastreams used to draw the game screen.
  • MenuOverscan() - Run during the menu screen's overscan. Will eventually use the joystick to update the options (players 1 or 2; TV Type of NTSC, PAL or SECAM; etc).
  • MenuVerticalBlank() - Run during the menu screen's vertical blank. Will eventually manipulate the menu's datastreams in order to display the selected options.

Datastream Revisions


The DS_ToARM datastream has had a few revisions:

DS_ToARM:ARMfunction:    ds 1    ; which function to runARMswcha:       ds 1    ; controller stateARMswchb:       ds 1    ; state of console switchesARMinpt4:       ds 1    ; state of left joystick's fire buttonARMinpt5:       ds 1    ; state of right joystick's fire button; this is part of DS_ToARM, but is actually used as "from the ARM to the 6507"ARMmode:        ds 1    ; tells 6507 to show menu or game screen

It now starts with a byte allocated for function - the 6507 code sets this to tell the ARM code which function to run.


The state of the right joystick's firebutton is now being saved.


Lastly mode, a return value, has been added to the end.


A new datastream has also been added. It's quite large, so I'm going to show just a snippet of it here:

MenuOptionsDatastream:    .word Collect2LogoF     ; 0  0  first value is unsigned char[] index    .word Collect2LogoE     ; 2  1      not actually used in the ARM code, but    .word Collect2LogoD     ; 4  2      keeping track of it helps us to make    .word Collect2LogoC     ; 6  3      sure each menu entry is exactly 20 bytes    .word Collect2LogoB     ; 8  4  second value is unsigned short int[] index    .word Collect2LogoA     ; 10 5      this is used in the ARM code to update    .word Collect2LogoColor ; 12 6      the datastream to show selected values    .byte %00000000         ; 14    PF0 - reversed    .byte %01000000         ; 15    PF1    .byte %00000000         ; 16    PF2 - reversed    .byte 100               ; 17    ball x location    .byte 25                ; 18    rows to show    .byte MM_TITLE_GAP      ; 19    scanline padding before next entry    ...      MENU_START_ID = (* - MenuOptionsDatastream) / 20            .word StartA            ; 0  0  first value is unsigned char[] index    .word StartE            ; 2  1      not actually used in the ARM code, but    .word StartD            ; 4  2      keeping track of it helps us to make    .word StartC            ; 6  3      sure each menu entry is exactly 20 bytes    .word StartB            ; 8  4  second value is unsigned short int[] index    .word StartA            ; 10 5      this is used in the ARM code to update    .word StartColor        ; 12 6      the datastream to show selected values    .byte %00000000         ; 14    PF0 - reversed    .byte %11000000         ; 15    PF1    .byte %00001100         ; 16    PF2 - reversed    .byte 0                 ; 17    ball x location    .byte 8                 ; 18    rows to show    .byte $80               ; 19    flags end of menu    

As you've probably surmised, this datastream controls the display of the main menu.





6507 Revisions, Fast Fetch mode


The InitSystem routine has been modified to turn on Fast Fetch mode:

        ldx #0         stx FASTFETCH

What this does is let you change code like this:

        lda DF0DATA ; 4 cycle instruction

To this:

        lda #<DF0DATA ; 2 cycle instruction

That 2 cycle savings is crucial for DPC+ kernels. There is a tradeoff though, you can no longer use lda # except when reading a DPC+ register. If this proves to be a problem, you could change the code to turn on Fast Fetch mode at the start of your kernel and turn it off at the end - though I don't recommend it as you'll have to be intimately aware of when you can and cannot use lda #.

6507 Revisions, CallArmCode


The 6507 code can trigger the execution of ARM code at 5 different places. Because CALLFUNCTION should be written towards the start of the cartridge ROM's addressing space, a new subroutine has been added at address $F080. This is the earliest it could exist as addresses $F000 - $F07F are the DPC+ registers.

CallArmCode:        ldx #<DS_ToARM        stx DF0LOW        ldx #>DS_ToARM        stx DF0HI        sty DF0WRITE        ; Y holds which function to call        ldx SWCHA           ; read state of both joysticks        stx DF0WRITE        ; save in ARMswcha         ldx SWCHB           ; read state of console switches        stx DF0WRITE        ; save in ARMswchb        ldx INPT4           ; read state of left joystick firebutton        stx DF0WRITE        ; save in ARMinpt4         ldx INPT5           ; read state of right joystick firebutton        stx DF0WRITE        ; save in ARMinpt5        ldx #$FF        stx CALLFUNCTION    ; runs main() in the C code        rts

Before calling this routine, your code must specify which function to run by setting the Y value. There are 5 constants used for this:

ARM_INIT        = 0ARM_OS          = 1ARM_VB          = 2ARM_MENU_OS     = 3ARM_MENU_VB     = 4

An example of calling Initialize():

        ldy #ARM_INIT        jsr CallArmCode     ; run Initialize() in the ARM code        

6507 Revisions, Main Menu


Routines have been added to display the main menu. I'm not going to go into detail here, but will point out a things for you to review in the source code:

  • There's 4 new constants that control the spacing of the menu elements. Try changing them to see what they do.
  • There's 8 new constants that define aliases for the DFxDATA registers. This make it easier to read the 6507 code and understand what it's doing.
  • New subroutine ShowTwoColorGraphic. This subroutine is the same as used in Stay Frosty 2 and Space Rocks to show a two color 48 pixel image, though it has been modified to use the new aliases for DFxDATA.
  • Search for the label KernelMenu: to find the main menu kernel.

6507 Revisions, Two Banks


6507 code has been revised to use bank 4 to draw the menu and bank 5 to draw the game screen. During Overscan processing, the ARM routines will change the value of mode, which is used to determine which mode the game is currently in. Mode 0 is active game, while mode 1 is at the menu. Additional modes can be added, such as one to show a high score screen or another to show an easter egg. After the ARM overscan routines are done, the 6507 code will check the value in mode to decide if it needs to switch to the other bank.


At this time the game screen is the same joystick controlled rainbow screen from Step 7.


The menu screen shows a menu, but it's just a static display at this point as the code is not in place to let you change the options.


6507 Revisions, Echo statements


In the prior build we had to manually keep track of Display Data usage for the ARM code. To make this easier to do, echo statements have been added to the end of the 6507 code - a subset of which are shown below:


    echo "// functions to run"    echo "#define ARM_INIT              ", [ARM_INIT]d    echo "#define ARM_OS                ", [ARM_OS]d    echo "#define ARM_VB                ", [ARM_VB]d    echo "#define ARM_MENU_OS           ", [ARM_MENU_OS]d    echo "#define ARM_MENU_VB           ", [ARM_MENU_VB]d    echo ""    echo "// datastream DS_ToARM"    echo "#define FUNCTION              queue[", [ARMfunction]d, "]"    echo "#define SWCHA                 queue[", [ARMswcha]d, "]"    echo "#define SWCHB                 queue[", [ARMswchb]d, "]"    echo "#define INPT4                 queue[", [ARMinpt4]d, "]"    echo "#define INPT5                 queue[", [ARMinpt5]d, "]"    echo "#define MODE                  queue[", [ARMmode]d, "]"    echo ""

These echo statements will output to the terminal/console when the 6507 code is compiled with dasm:

 // functions to run #define ARM_INIT               0 #define ARM_OS                 1 #define ARM_VB                 2 #define ARM_MENU_OS            3 #define ARM_MENU_VB            4  // datastream DS_ToARM #define FUNCTION              queue[ 0 ] #define SWCHA                 queue[ 1 ] #define SWCHB                 queue[ 2 ] #define INPT4                 queue[ 3 ] #define INPT5                 queue[ 4 ] #define MODE                  queue[ 5 ]

By default dasm will output the values in hexadecimal. In and of itself that's not a problem; however, dasm uses the prefix of $ to denote hexadecimal values while C uses 0x.


We could do a find/replace of $ for 0x - instead we'll just ask dasm to output the values in decimal, which does not use a prefix. This is done by using the brackets [ ] and the d.


If you make any revisions to the 6507 code that changes any of those values you'll need to manually copy the output and paste it into the defines.h file.



echo statement output


C Revisions


Function main() now uses the value in FUNCTION to select which function to run:

    switch (FUNCTION)   // 6507 code sets the value of FUNCTION    {        case ARM_INIT:      Initialize();        break;        case ARM_OS:        Overscan();          break;        case ARM_VB:        VerticalBlank();     break;        case ARM_MENU_OS:   MenuOverscan();      break;        case ARM_MENU_VB:   MenuVerticalBlank(); break;    }

The number of functions the 6507 can call can easily be expanded - for instance Space Rocks adds the functions EasterEggOverscan() and EasterEggVerticalBlank().


Right now these functions are extremely simple, so I won't be going into detail about them - but please review the code so you understand the what and why of it. Post any questions in the comments.


Task for you


Think up a new menu option and add it between Players and TV Type. Your new option should use the same scanline padding constant as the Players' option.


Don't forget to adjust the menu padding constants so that the Atari continues to output 262 scanlines. To check this use the frame stats feature in Stella - you can toggle that by pressing the keys COMMAND + L on the Mac, or ALT + L on Linux and Windows. You can learn more about this, and other Developer Keys, in Stella's help file. You can also access your local copy of the help file via Stella's Help menu.



Stella's frame stats


Upload your changes to the forum topic DPC+ ARM Development - Task(s) for you. Who knows, your new option might become part of this project!








  • Like 1


Recommended Comments

The code changes for Part 9 are done, I still need to finish the blog entry. I have family arriving tonight for an extended weekend, so it probably won't get posted until Monday night.


I hope to see some work uploaded to the Task(s) for you topic by then - the task in Part 9 builds upon the task in Part 8.

Link to comment

Now I have a question. :)


Could the ARM also run in parallel with the kernel? Could the DPC+ driver be rewritten so that is does not feed the 6507 with NOPs but real (kernel) code? Or is it fully utilized by feeding the queues?

Link to comment

It's too busy to run custom code while also feeding the 6507. From cd-w's blog entry, Perfect Harmony:


The software on the harmony cart is also very straightforward. It doesn't attempt to synchronize clocks with the Atari. Instead, it just sits in an permanent loop, servicing requests from the Atari as follows:


1) Wait for an address request from the Atari (A12 high).
2) Read address bus (A0-A11).
3) Fetch data from flash memory at the requested address (may perform bankswitching here).
4) Assert data on the data bus (D0-D7).
5) Wait until there is a change of address (A0-A12).
6) Repeat forever.
This approach is necessary as the cartridge port doesn't output the 6502 clocks (phi1 and phi2) and also doesn't have a R/W line to distinguish between reads and writes. The wait for A12 at the beginning has the effect of keeping the ARM and Atari loosely synchronized.


The ARM processor runs at 70MHz and the Atari runs at 1.2MHz, which sounds like the ARM would have plenty of overhead. Unfortunately this is not the case. The flash memory of the ARM takes 4 cycles to access, and the ARM can only perform I/O at a fraction of the full clock speed. Also, the data must be placed on the Atari data bus within a short time of A12 being asserted. It works out that the ARM is only just capable of keeping up with the Atari. If the Atari were any faster, then this approach would not work. Fortunately it does work, and batari has managed to implement all of the main bank-switching schemes on the Harmony.


emphasis added.

Link to comment

Thanks. Now I (vaguely) remember that.


Now the question is, why the ARM has to feed the 6507 at all. Is that only to listen to the address bus and feed the data bus for the queues? Executing code from (TIA-)RAM (like the spinning YinYang) works, so how about directly accessing ROM in a kernel which doesn't need any queues?

Link to comment

There's no connection between the 6507 and the ROM. The connections are this:

6507 <--> ARM <--> Flash (ROM) and SRAM (RAM)

The ARM emulates everything about the cartridge, not just the DPC+ data fetchers. This emulation includes the bank switching hardware, any expansion RAM, and the ROM - this includes the connection between the 6507 and the simplest 2K and 4K ROMs.

Link to comment

I see. So to make the 6507 more independent, one would have to put an EPROM (and bank switching logic or RAM if required) into the cart too. And then find a way that on demand either the ARM or traditional way ROM access can be used (ROMs could even be different in that case).


So the address and data bus would have to be switched back and forth "somehow". Not sure if that's even feasible.

Link to comment

Adding just a RAM chip would do as the 6507 already can easily share RAM since it only uses half of a clock cycle (the Apple ][s RAM was shared between the CPU and the video display circuitry) It really depends on how the ARM chip utilizes the address and data bus.

Link to comment

That's true. Commodore did the same thing, though on occasion the video chip would need some extra time resulting in the CPU pausing briefly. This resulted in the 64's 1541 floppy drive having to be slowed down a bit from the VIC-20's 1540.

Link to comment
Add a comment...

×   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.

  • Recently Browsing   0 members

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