Jump to content
IGNORED

CC65 and display list


Yaron Nir

Recommended Posts

Hi All,

 

i am trying to create antic mod 4 (graphics 12) using display list in CC65.

 

i want to use a "fixed" memory location for screen memory, as i DO NOT want to use the linker config option.

 

asi understood the DL can't cross the 1K boundary.

 

looking at this example:

https://github.com/cc65/cc65/blob/master/testcode/lib/atari/displaylist.c

 

i see that the screen memory is being defined locally as an array.

 

unsigned char DummyScreen[400];

 

and then it is used as a parameter to the DL:

void DisplayList = { DL_BLK1, DL_BLK2, DL_BLK3, DL_BLK4, DL_BLK5, DL_BLK6, DL_BLK7, DL_DLI(DL_BLK8), DL_LMS(DL_CHR40x8x1), DummyScreen, DL_HSCROL(DL_CHR40x10

 

....

 

and then poke it into location 560:

POKEW(560,(unsigned int)&dlStartScreen);

 

 

my question is , if i want to change the screen memory to be fixed for example i want my DL to start at address 0x8000.

what do i need to change? am i doing this right?

 

i tried changing to work with fixed address, but then i saw strange behaviors like random characters appearing on screen with no reasn and PMG was erased....

 

help?

 

 

 

Link to comment
Share on other sites

i want my DL to start at address 0x8000.

 

How are you protecting the compiler suite from not using that area of memory for something else?

 

Generally, the custom linker config is the way-to-go. An alternative approach has been to protect your dlist (and or screen memory) from crossing a page boundary by allocating a larger block and splicing out what you need, see here.

 

Additionally, if you are happy the area is 'free' then you could simply memcpy the dlist from your program area to the target area.

Link to comment
Share on other sites

char dl[] = {112,112,112,66,0,0x20,............displaylist memory starts at $ 2000
this is the test for $2000:
for(i=0; i<128; ++i) {

POKE(0x2000+i,i);
}

greeting
#include <peekpoke.h> 
 
#include <atari.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#define SAVMSC *(unsigned int *) 88         // Screen address
#define NMIEN  *(unsigned char *) 0xD40E    // NMI enable
#define SDMCTL *(unsigned char *) 559       // Antic DMA control shadow
#define SDLSTL *(unsigned int *) 560        // Display list start shadow
#define VDSLST *(unsigned int *) 0x200      // Display list interrupt vector
char dl[] = {112,112,112,66,0,0x20,130,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,65,0,0};
 
void main(void) {
  unsigned char i;
 
  dl[sizeof(dl)-2] = ((unsigned) &dl) % 256;
  dl[sizeof(dl)-1] = ((unsigned) &dl) / 256;
  SDMCTL = 0;                                       // turn off antic
  SDLSTL = (unsigned int) &dl;                      // new dlist address
  SDMCTL = 34;                                      // turn on antic
  
  for(i=0; i<128; ++i) {
    POKE(0x2000+i,i);
  }
 
  while(1);
  return;
}
Edited by funkheld
Link to comment
Share on other sites

STARTADDRESS: default = $3000 >>>>> >is startaddress

 

size = $CFFF - __STACKSIZE__ - __RESERVED_MEMORY__ - %S; >>>>> size with $CFFF

I own only the memory and must also manage it

 

 

FEATURES {
    STARTADDRESS: default = $3000;
}
SYMBOLS {
    __EXEHDR__:          type = import;
    __SYSTEM_CHECK__:    type = import;  # force inclusion of "system check" load chunk
    __AUTOSTART__:       type = import;  # force inclusion of autostart "trailer"
    __STACKSIZE__:       type = weak, value = $0800; # 2k stack
    __STARTADDRESS__:    type = export, value = %S;
    __RESERVED_MEMORY__: type = weak, value = $0000;
}
MEMORY {
    ZP:         file = "", define = yes, start = $0082, size = $007E;
 
# file header, just $FFFF
    HEADER:     file = %O,               start = $0000, size = $0002;
 
# "system check" load chunk
    SYSCHKHDR:  file = %O,               start = $0000, size = $0004;
    SYSCHKCHNK: file = %O,               start = $2E00, size = $0300;
    SYSCHKTRL:  file = %O,               start = $0000, size = $0006;
 
# "main program" load chunk
    MAINHDR:    file = %O,               start = $0000, size = $0004;
    MAIN:       file = %O, define = yes, start = %S,    size = $CFFF - __STACKSIZE__ - __RESERVED_MEMORY__ - %S;
    TRAILER:    file = %O,               start = $0000, size = $0006;
}
SEGMENTS {
    ZEROPAGE:  load = ZP,         type = zp;
    EXTZP:     load = ZP,         type = zp,                optional = yes;
    EXEHDR:    load = HEADER,     type = ro;
    SYSCHKHDR: load = SYSCHKHDR,  type = ro,                optional = yes;
    SYSCHK:    load = SYSCHKCHNK, type = rw,  define = yes, optional = yes;
    SYSCHKTRL: load = SYSCHKTRL,  type = ro,                optional = yes;
    MAINHDR:   load = MAINHDR,    type = ro;
    STARTUP:   load = MAIN,       type = ro,  define = yes;
    LOWBSS:    load = MAIN,       type = rw,                optional = yes;  # not zero initialized
    LOWCODE:   load = MAIN,       type = ro,  define = yes, optional = yes;
    ONCE:      load = MAIN,       type = ro,                optional = yes;
    CODE:      load = MAIN,       type = ro,  define = yes;
    RODATA:    load = MAIN,       type = ro;
    DATA:      load = MAIN,       type = rw;
    INIT:      load = MAIN,       type = rw,                optional = yes;
    BSS:       load = MAIN,       type = bss, define = yes;
    AUTOSTRT:  load = TRAILER,    type = ro;
}
FEATURES {
    CONDES: type    = constructor,
            label   = __CONSTRUCTOR_TABLE__,
            count   = __CONSTRUCTOR_COUNT__,
            segment = ONCE;
    CONDES: type    = destructor,
            label   = __DESTRUCTOR_TABLE__,
            count   = __DESTRUCTOR_COUNT__,
            segment = RODATA;
    CONDES: type    = interruptor,
            label   = __INTERRUPTOR_TABLE__,
            count   = __INTERRUPTOR_COUNT__,
            segment = RODATA,
            import  = __CALLIRQ__;
}
Edited by funkheld
Link to comment
Share on other sites

 

char dl[] = {112,112,112,66,0,0x20,130,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,65,0,0};

 

 

This is exactly what the OP is getting at... as your program changes, what guarantees that this display dlist isn't going to span a boundary?

 

#include <string.h>

#define SDMCTL *((unsigned char *) 559)       // Antic DMA control shadow
#define SDLSTL *((unsigned int *) 560)        // Display list start shadow

#define MY_DLIST 0x8000
#define MY_SCREEN 0x8100

char dl[] = {0x70,0x70,0x70,0x42,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0x41,0,0};

void main(void) {
  unsigned char i;

  dl[4] = MY_SCREEN % 256;
  dl[5] = MY_SCREEN / 256;
  dl[sizeof(dl)-2] = MY_DLIST % 256;
  dl[sizeof(dl)-1] = MY_DLIST / 256;
  SDMCTL = 0; // turn off antic - technically it would be good to wait for a vblank to effect the SDMCTL->DMACTL
  
  memcpy((void *)MY_DLIST, dl, sizeof(dl));
  
  for(i=0; i<128; ++i) {
    *((unsigned char *)MY_SCREEN+i) = i;
  }

  SDLSTL = (unsigned int) &dl;                      // new dlist address
  SDMCTL = 34;                                      // turn on antic
  
  while(1);
  return;
}
Link to comment
Share on other sites

sooo, using the reference code by wrathchild did the trick.

 

so basically, to whom it may concern, these are the steps to use a display list in CC65 WITHOUT using the cfg config linker file:

 

1. define 2 fixed addresses: 1 for display list, 1 for screen memory

example:

unsigned int txtDisplayList     = (unsigned int)0x8000;
unsigned int screenMemory  =  (unsigned int)0x8100; 
2. declare a char array of display list
example:
char dlStartScreen[] =  
{
112,  // BLANK
112, // BLANK
112, // BLANK
66, // tell the address of screen memory, 2 following zeros will be replaced by actual address
0,
0,
4, //  antic mode 4 char mod multicolor x 21 lines
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
65, // jmp to DL address, , 2 following zeros will be replaced by actual address 
0,
0
};
3. set the zero with the proper addresses, one for screen memory, other for jump display list.
example:
// set the screen memory address in the DL array to replace the 2 zeros at pos 4,5
dlStartScreen[4] = screenMemory % 256;
dlStartScreen[5] = screenMemory / 256;


// set the jump address in the DL array to replace the 2 zeros at pos last-1,last 
dlStartScreen[sizeof(dlStartScreen)-2] = txtDisplayList % 256;
dlStartScreen[sizeof(dlStartScreen)-1] = txtDisplayList / 256;

4. copy the array of display list into the fixed memory address

example:

memcpy((void *)txtDisplayList, dlStartScreen, sizeof(dlStartScreen)); 

5. set the display list

 

POKE(560,(unsigned int) &dlStartScreen);     

you're all set :)

 

 

thanks all, and i hope this small example will help someone....

 

 

Link to comment
Share on other sites

Yaron's example to use fixed addresses of 0x8000 and 0x8100 might work at first glance. But the runtime probably uses this memory, too, unless one adapts it with __RESERVED_MEMORY__.

 

So, depending on program size, and when during the execution the DL and display memory are being set up, the program can crash, or you get strange screen output (if heap or stack enters the 0x8000... area).

 

regards,

chris

Edited by sanny
Link to comment
Share on other sites

Linker.cfg is better option. No memory is wasted for original copy of the DL. LD65 also handles proper alignment. And overall memory layout can be adjusted to ensure no interference between the program and data. Good option is to define the DL in assembler file and dedicated segment while benefitting from separate compilation.

Link to comment
Share on other sites

Linker.cfg is better option. No memory is wasted for original copy of the DL. LD65 also handles proper alignment. And overall memory layout can be adjusted to ensure no interference between the program and data. Good option is to define the DL in assembler file and dedicated segment while benefitting from separate compilation.

 

Sure, but especially for beginners the config is a riddle and appears unhandy if you also have to provide loader segments for each individual data block.

(Thinking about a config generator which provides automatically memory and segments + header defined by a simpler syntax...?)

Link to comment
Share on other sites

Please explain, exactly "why" you don't want to use a changed linker config file. I'm just curious.

 

regards,

chris

 

as mentioned above, the cfg file is too complicated for me to understand now.

 

as for the asm suggestion. well, my current project is pure CC65 (c lang) and i am trying to prove something (atleast to myself :) )

so, no linkers, no asms

i want to be able to do what i need in pure CC65

 

hope that make sense....

Link to comment
Share on other sites

Yaron's example to use fixed addresses of 0x8000 and 0x8100 might work at first glance. But the runtime probably uses this memory, too, unless one adapts it with __RESERVED_MEMORY__.

 

So, depending on program size, and when during the execution the DL and display memory are being set up, the program can crash, or you get strange screen output (if heap or stack enters the 0x8000... area).

 

regards,

chris

 

i a curious, how can one use __RESERVED_MEMORY__ in CC65 code?

 

can you post an example?

Link to comment
Share on other sites

Linker.cfg is better option. No memory is wasted for original copy of the DL. LD65 also handles proper alignment. And overall memory layout can be adjusted to ensure no interference between the program and data. Good option is to define the DL in assembler file and dedicated segment while benefitting from separate compilation.

soo, this is a cfg file i've downloaded from one of the games posted here on AA called 'Viper' (a snake clone):

 

FEATURES {
    STARTADDRESS: default = $3900;
}
SYMBOLS {
    __STACKSIZE__ = $100; # small stack
    __RESERVED_MEMORY__: value = $1, weak = yes;
}
MEMORY {
    ZP:      start = $0082, size = $007E, type = rw, define = yes;
    HEADER:  start = $0000, size = $0006, file = %O;
    RAM:     start = %S,    size = $BC20 - __STACKSIZE__ - %S, file = %O;
    TRAILER: start = $0000, size = $0006, file = %O;
}
SEGMENTS {
    EXEHDR:   load = HEADER,  type = ro;
    LOWCODE:  load = RAM,     type = ro, define = yes, optional = yes;
    INIT:     load = RAM,     type = ro,               optional = yes;
    CODE:     load = RAM,     type = ro, define = yes;
    RODATA:   load = RAM,     type = ro;
    FONT:     load = RAM,     type = rw, align=$1000, define=yes;
    DATA:     load = RAM,     type = rw, align=$200, define=yes;
    BSS:      load = RAM,     type = bss, define = yes;
    HEAP:     load = RAM,     type = bss, optional = yes; # must sit just below stack
    ZEROPAGE: load = ZP,      type = zp;
    EXTZP:    load = ZP,      type = zp,               optional = yes;
    AUTOSTRT: load = TRAILER, type = ro;
}
FEATURES {
    CONDES: segment = INIT,
    type = constructor,
    label = __CONSTRUCTOR_TABLE__,
    count = __CONSTRUCTOR_COUNT__;
    CONDES: segment = RODATA,
    type = destructor,
    label = __DESTRUCTOR_TABLE__,
    count = __DESTRUCTOR_COUNT__;
    CONDES: type = interruptor,
    segment = RODATA,
    label = __INTERRUPTOR_TABLE__,
    count = __INTERRUPTOR_COUNT__;

if someone could tell me what the hell is this? and what do i need to change line by line, that could be a good starting point....

 

}
Link to comment
Share on other sites

i only see 2 lines that make sense that needs to be changed, other leave "as is":

FONT:     load = RAM,     type = rw, align=$1000, define=yes;
DATA:     load = RAM,     type = rw, align=$200, define=yes;

while i looked at the code i saw a segment was defined using the #pragma directive.

 

i just need to understand how to translate it to the example i have posted above....

 

can any help? step by step?

Link to comment
Share on other sites

Sure, but especially for beginners the config is a riddle and appears unhandy if you also have to provide loader segments for each individual data block.

 

Yes, it requires some learning. But if one knows what memory is, what addresses are, and so, it's no rocket science.

 

See https://cc65.github.io/doc/ld65.html#s5for documentation.

Link to comment
Share on other sites

soo, this is a cfg file i've downloaded from one of the games posted here on AA called 'Viper' (a snake clone):

 

Better start with the one you are currently using, the default atari.cfg of cc65 (https://github.com/cc65/cc65/blob/master/cfg/atari.cfg).

And maybe look at the other cfg files in this directory for "inspiration".

Link to comment
Share on other sites

 

Better start with the one you are currently using, the default atari.cfg of cc65 (https://github.com/cc65/cc65/blob/master/cfg/atari.cfg).

And maybe look at the other cfg files in this directory for "inspiration".

 

thanks chris, both links are helpful.

let's say i use the default atari.cfg from your link,

can you point me to where i need to "stash" my display list from the above posted code ?

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