Jump to content
Sign in to follow this  
JesperGravgaard

KickC 0.8.4 released

Recommended Posts

Hi everyone,

 

A new version of KickC has just been released. This release adds conio.h support for Atari XL/XE and improves conio.h on Commodore platforms. It fixes a lot of bugs and and adds a lot of new/improved ASM fragments. 

 

https://gitlab.com/camelot/kickc/-/releases

 

The new version also compiles the awesome benchmarks created by @fenrock out-of-the-box.

 

https://github.com/markjfisher/kickc-benchmarks

 

A huge thanks to all of you who have tried out KickC and given feedback, suggestions and questions! 

 

  • Like 5

Share this post


Link to post
Share on other sites

have now tried the pm in kickc in graphics mode 7.
you can move the pm with the q / w / e / s keys.

the program xex is only 368 byte big in kickc.

 

 

greeting

 

#pragma target(atarixl)
#pragma encoding(atascii)
#pragma zp_reserve(0x00..0x7f)

#include <atari-xl.h>
#include <printf.h>
#include <conio.h>

#define ICCOM_OPEN_CHANNEL          0x03;
#define ICCOM_PUT_BINARY_RECORD     0x0B;
#define ICCOM_CLOSE                 0x0C;
#define ICCOM_DRAW_LINE             0x11;

void * CIOV   = 0xE456;

struct ATARI_IOCB {
    char ICHID;
    char ICDNO;
    char ICCOM;
    char ICSTA;
    char* ICBA;
    char* ICPT;
    word ICBL;
    char ICAX1;
    char ICAX2;
    char ICAX3;
    char ICAX4;
    char ICAX5;
    char ICAX6;
};

struct ATARI_IOCB * const IOCB0 = 0x340;
struct ATARI_IOCB * const IOCB6 = 0x3A0;

char *sDrive = "S:";
char * const ATACHR = 0x2FB;
word i, pmgmem;
char j, px0 , py0;
  
char * ramto = 0x6a;
char * colorpm0 = 704;
char * colorpm1 = 705;
char * colorpm2 = 706;
char * colorpm3 = 707;

char y,y1;

const char pmdata[] = { 0,255,129,129,129,129,129,129,255,0};

void main() {
  
  graphics(7);
    color(1);
  
  for(y=0;y<10;y++) {
    y1=y1+10;
    plot(0, 0);
    drawTo(159,y1);
  }
  
  px0 = 100;
  py0 = 60;
  
  GTIA->GRACTL = 0;
  j = *ramto-24;
  ANTIC->PMBASE = j;

  pmgmem = j * 256;
  *SDMCTL = 46;
  
  GTIA->GRACTL = 3;
  GTIA->SIZEP0 = 0; 
  *colorpm0 = 202; 
  
  schiebe();
 
     while (1) {
    *CH = 0xff;

     while(*CH == 0xff) ;
     char keyPressed = *CH;

     switch(keyPressed) {
       case $2f:
       {
         px0 =px0-1;
         schiebe();
       };
       break;
       case $2a:
       {
         px0 =px0+1;
         schiebe();
       };
       break;
       case $2e:
       {
         py0 =py0-1;
         schiebe();
       };
       break;
       case $3e:
       {
         py0 =py0+1;
         schiebe();
       };
       break;
       default: ;
       break;
     }
   }
}

void schiebe(){
  char *pmadr = pmgmem + 512 + py0;
  
   for(y1=0;y1<10;y1++) {
    pmadr[y1] = pmdata[y1];
  }
  
  GTIA->HPOSP0 = px0 ;
}  

void closeChannel() {
    IOCB6->ICCOM = ICCOM_CLOSE;
    asm(clobbers "AXY") {
        ldx #$60 
        jmp CIOV 
    };
}

void graphics(char mode) {
    closeChannel();
    IOCB6->ICCOM = ICCOM_OPEN_CHANNEL;
    IOCB6->ICAX1 = 0x0C; 
    IOCB6->ICAX2 = mode;
    IOCB6->ICBA  = sDrive;
    asm(clobbers "AXY") {
        ldx #$60
        jmp CIOV
    };
}

void position(word x, char y) {
    *COLCRS = x;
    *ROWCRS = y;
}

void plot(word x, char y) {
    position(x, y);
    IOCB6->ICCOM = ICCOM_PUT_BINARY_RECORD;
    IOCB6->ICBL = 0;
    asm(clobbers "AXY") {
        ldx #$60
        lda ATACHR
        jmp CIOV
    };
}

void drawTo(word x, char y) {
    position(x, y);
    IOCB6->ICCOM = ICCOM_DRAW_LINE;
    IOCB6->ICAX1 = 0x0C; 
    IOCB6->ICAX2 = 0;
    asm(clobbers "AXY") {
        ldx #$60
        jmp CIOV
    };
}

void color(char c) {
    *ATACHR = c;
}

void waitkey() {
    while(!kbhit()) ;
    clrkb();
}

 

kickcpm.jpg

Edited by funkheld
  • Like 3

Share this post


Link to post
Share on other sites

Practice makes perfect. Also checkout var-- and var++ (or --var and ++var, which used to be faster on some non-optimizing C compilers). You can also factor out schiebe() from all the case statements.

 

Edit: good to see you use CIO.

 

Edit2: your defines do not need a semicolon (;) at the end.

 

They work now because

IOCB6->ICCOM = ICCOM_OPEN_CHANNEL;

turns into

IOCB6->ICCOM = 0x03;;

 

But consider this

#define A 42;

int main(int argc, char**argv) {
    int a2;
    
    a2 = A * 2;
    
    return 0;
}

where the a2 line turns into this

    a2 = 42; * 2;

which is invalid.

 

HTH HAND

(google that :) )

Edited by ivop

Share this post


Link to post
Share on other sites
3 hours ago, ivop said:

Edit2: your defines do not need a semicolon (;) at the end.

This is my fault as I supplied most of this code in a quick example of opening graphics modes with CIO.

But it's great to see PMs added to it.

 

Thanks for pointing out an example of why not to use them, which explains why the line

IOCB6->ICAX1 = 0x0C; // (IOCB_ICAX_READ | IOCB_ICAX_WRITE)

doesn't work :D 

Edited by fenrock
  • Like 2

Share this post


Link to post
Share on other sites

what effect does the "const" have?

------------------------------------

char * const ATACHR = 0x2FB;

char * ATACHR = 0x2FB;

------------------------------------

 

greeting

Share this post


Link to post
Share on other sites

const after the * makes the pointer constant, which means you cannot assign to it, only dereference.

 

Share this post


Link to post
Share on other sites
50 minutes ago, ivop said:

const after the * makes the pointer constant, which means you cannot assign to it, only dereference.

 

Which (I believe) in turn is used by the compiler to know that it doesn't have to assign this to a memory location, and can optimise its use.

  • Like 1

Share this post


Link to post
Share on other sites

sometimes addresses from the system are constant and sometimes not, why?

 

------------------------------

char * const FILDAT = 0x2FD;
char * ROWCRS = 0x54;
word * COLCRS = 0x55;

char * const ATACHR = 0x2FB;
char * const CH = 0x2FC;

void * CIOV   = 0xE456;
-----------------------------

 

greeting.

 

 

Edited by funkheld
  • Sad 1

Share this post


Link to post
Share on other sites

Wrong bit order explanation in comment.

 

kickc/include/atari-antic.h

    // Non-Maskable Interrupt (NMI) Enable
    // Enables Non-Maskable Interrupts.
    // 7 6 5 4 3 2 1 0
    // 1 - - - - - - - RESET: Enable Reset key interrupt
    // - 1 - - - - - - VBI: Enable Vertical Blank Interrupt
    // - - 1 - - - - - DLI: Enable Display List Interrupt
    // The Operation System sets NMIEN to the default $40hex/64dec during the power up routines.
    // The NMI service routines first vector through $FFFAhex/65530dec which determines the cause and then transfers control to the interrupt service routine.
    // If NMIEN's DLI bit is set when ANTIC encounters a Display List instruction with the DLI modifier bit set, then ANTIC triggers the DLI on the last scan line of that Display List instruction mode line.
    // When NMIEN's VBI bit is set, ANTIC will signals a Vertical Blank Interrupt at the end of processing the JVB (Jump vertical blank) at the end of the Display List.
    // The OS jumps through VVBLKI ($0222hex/546dec) to begin the OS VBI Service Routine, and the OS VBI Routine exits with a jump through VVBLKD ($0224hex/548dec).
    // By default VVBLKI points to the OS jump vector SYSVBV ($E45Fhex/58463dec) to begin the Vertical Blank Interrupt, and VVBLKD points to the OS jump vector XITVBV ($E462hex/58466dec).
    char NMIEN;

    // Non-Maskable Interrupt (NMI) Status / Reset
    // Contains information about which NMI occured.
    // Any value written to NMIRES resets the bits in NMIST which indicate the reason for the most recent Non-Maskable Interrupt.
    // 7 6 5 4 3 2 1 0
    // 1 - - - - - - - RESET: Reset key interrupt
    // - 1 - - - - - - VBI: Vertical Blank Interrupt
    // - - 1 - - - - - DLI: Display List Interrupt
    char NMIST;

https://www.atariarchives.org/mapping/memorymap.php#54286

54286          	D40E          	NMIEN

     (W) Non-maskable interrupt (NMI) enable. POKE with 192 to
     enable the Display List Interrupts. When BIT 7 is set to one, it
     means DL instruction interrupt; any display list instruction where
     BIT 7 equals one will cause this interrupt to be enabled at the
     start of the last video line displayed by that instruction. When BIT
     6 equals one, it allows the Vertical Blank Interrupt and when BIT
     5 equals one, it allows the RESET button interrupt. The RESET
     interrupt is never disabled by the OS. You should never press
     RESET during powerup since it will be acted upon.

     NMIEN is set to 64 ($40) by the OS IRQ code on powerup,
     enabling VBI's, but disabling DLI's. All NMI interrupts are
     vectored through 65530 ($FFFA) to the NMI service routine at
     59316 ($E7B4) to determine their cause.

     Bit          7    6     5    4   3   2   1   0
     Interrupt:  DLI  VBI  RESET  .... unused .....   

54287          	D40F          	NMIRES

     (W) Reset for NMIST (below); clears the interrupt request
     register; resets all of the NMI status together.

                              NMIST

     (R) NMI status; holds cause for the NMI interrupt in BITs 5, 6 and
     7; corresponding to the same bits in NMIEN above. If a DLI is
     pending, a jump is made through the global RAM vector
     VDSLST (512; $200). The OS doesn't use DLI's, so 512 is
     initialized to point to an RTI instruction and must be changed by
     the user before a DLI is allowed.

     If the interrupt is not a DLI, then a test is made to see if the
     interrupt was caused by pressing RESET key and, if so, a jump is
     made to 58484 ($E474). If not a RESET interrupt, then the system
     assumes the interrupt was a VBLANK interrupt, and a jump is
     made through VVBLKI at 546 ($222), which normally points to
     the stage one VBLANK processor. From there it checks the flag at
     CRITIC (66; $42) and, if not from a critical section, jumps
     through VVBLKD at 548 ($224), which normally points to the
     VBLANK exit routine. On powerup, the VBLANK interrupts are
     enabled while the display list interrupts are disabled. See the end
     of the memory map for a description of the VBLANK procedures.
     For IRQ interrupts, see location 53744 ($D20E).

 

Edited by zbyti
memory map link
  • Like 1

Share this post


Link to post
Share on other sites
1 hour ago, funkheld said:

sometimes addresses from the system are constant and sometimes not, why?

 

------------------------------

char * const FILDAT = 0x2FD;
char * ROWCRS = 0x54;
word * COLCRS = 0x55;

char * const ATACHR = 0x2FB;
char * const CH = 0x2FC;

void * CIOV   = 0xE456;
-----------------------------

 

greeting.

 

 

That's just wrong. A pointer to fixed location should be constant.

 

For example:

 

*ROWCRS = 15;

 

Should become:

 

lda #15

sta $54

 

If the pointer is not constant, the compiler doesn't know, so it will first load the pointer in a zero page pair, and then use (zp),y:

 

lda rowcrs_pointer

sta zp

lda rowcrs_pointer+1

sta zp+1

ldy #0

 

lda #15

sta (zp),y

 

Or if the non-constant pointer is assigned/optimized to zero page location already, it becomes:

 

ldy #0

lda #15

sta (rowcrs_pointer),y

 

 

 

Edited by ivop
  • Like 1

Share this post


Link to post
Share on other sites
1 hour ago, ivop said:

That's just wrong. A pointer to fixed location should be constant.

 

it's entirely possible some of these are my mistakes as I contributed conio-atari.h to kickc and I was learning as I went along :) 

 

I think kickc recognises pointers to fixed addresses that don't change in the life of the program as constant anyway and automatically replaces them.

 

Here are the current definitions:

 

// Atari ZP registers
// 1-byte cursor row
char * ROWCRS = 0x54;
// 2-byte cursor column
word * COLCRS = 0x55;

 

The following does actually compile down correctly in kickc

#pragma target(atarixl)
#include <atari-xl.h>
#include <conio.h>

void main() {
    *ROWCRS = 0;
}

 

  // Atari ZP registers
  // 1-byte cursor row
  .label ROWCRS = $54
.segment Code
main: {
    // /home/markf/dev/personal/atari/projects/kickc-io/./src/foo.c:6
    lda #0
    sta ROWCRS
    // /home/markf/dev/personal/atari/projects/kickc-io/./src/foo.c:7
    rts
}

I've locally made them const and it still works.

I'll talk to @JesperGravgaard and get them fixed with a PR.

 

 

Edited by fenrock
  • Like 1

Share this post


Link to post
Share on other sites

But before you made it const, the generated code is not right, because the pointer can change.

 

char * ROWCRS = 0x54;

ROWCRS = 1234;

*ROWCRS = 0;

 

Edit: oh sorry, I see, you said it might detect that it doesn't change over the life time of the program. To what does this code compile then?

 

Edited by ivop

Share this post


Link to post
Share on other sites
42 minutes ago, ivop said:

But before you made it const, the generated code is not right, because the pointer can change.

 

char * ROWCRS = 0x54;

ROWCRS = 1234;

*ROWCRS = 1; // i changed this deliberately

 

I think we're agreeing :)

 

When the variable is (incorrectly) not marked as const, yes you can change the pointer and the generated code changes to accommodate the new code.

But kickc didn't need to declare it as a ZP variable to do the work, it optimized the code in place to "store 0 at 0x54". In your new case it generates:

main: {
    // /home/markf/dev/personal/atari/projects/kickc-io/./src/foo.c:6
    lda #0
    sta $54
    // /home/markf/dev/personal/atari/projects/kickc-io/./src/foo.c:8
    lda #1
    sta $4d2
    // /home/markf/dev/personal/atari/projects/kickc-io/./src/foo.c:9
    rts
}

However, if you mark it const, the compiler errors with:

const variable may not be modified ROWCRS

which is why I said I'd raise a PR to mark them constant, but it isn't strictly needed because as it says in the kickc manual:

Quote

The compiler is quite good at detecting constants automatically, so it is not strictly necessary to declare any constants

 

Edited by fenrock
expanded example
  • Like 2

Share this post


Link to post
Share on other sites

Oh, it's pretty cool that it detects that the pointer has changed and still does lda/sta!

37 minutes ago, fenrock said:

but it isn't strictly needed because as it says in the kickc manual:

The compiler is quite good at detecting constants automatically, so it is not strictly necessary to declare any constants

True. But you might want to run the same code on different compilers (benchmarks), and not every compiler is as good at detecting constants I suppose. Some compilers need more guidance. And it is proper C ;)

Edited by ivop
  • Like 1

Share this post


Link to post
Share on other sites

KickC tries to optimize the code as well as it can by analyzing the code thoroughly to identify variables that are actually constants. One of the methods is uses is converting the entire program to single static assignment form, which effectively changes the program (by introducing new variables) to ensure that each variable is only assigned once. This makes the compiler quite good at constant detection.

However, it will compile faster and leave less room for mistakes if the programmer marks pointers as constant. Also @ivop has a point on creating portable code.

Even with the best current compiler methods, nothing beats a programmer that has a thorough look at the code and rewrites it to achieve optimal performance.

Edited by JesperGravgaard
  • Like 2

Share this post


Link to post
Share on other sites

@JesperGravgaard

 

Thank you for working on this.

 

I wanted to ask, for my own knowledge, what are the differences with cc65? One of the great features seems to be that KickC provides better optimizations, right? 

What other there are other more important features compared to cc65? 

 

I checked your reference manual (very nice by the way).

 

 

Share this post


Link to post
Share on other sites

@Blues76 Here are some of the differences.

The most significant difference is the modern compiler optimization techniques KickC uses to generate fast ASM from your C-code.

A second difference is that KickC can generate very small binaries, since it only includes code for the functions that are actually used. This includes functions in libraries. If you include a library and only use a single function the compiler will only generate ASM for that one function. It even optimizes the library functions based on the parameters you pass, so if you only call the library function once or always pass the same parameters it will optimize the ASM based on the constant parameter.

A third difference is that the generated ASM is very readable, so you can directly compare your C-program to the ASM it generates. As an example
This C-code: https://gitlab.com/camelot/kickc/-/blob/master/src/test/kc/examples/atarixl/rasterbars.c 
compiles to this ASM-code: https://gitlab.com/camelot/kickc/-/blob/master/src/test/ref/examples/atarixl/rasterbars.asm
 

A minor difference is the ability to initialize data structures using inline KickAssembler macro code. Kick Assembler has a great macro language, so this allows for very easy initialization of look-up tables.

char SINTABLE[0x100] = kickasm {{ .fill $100, round(127.5+127.5*sin(2*PI*i/256)) }};


CC65 on the other hand is a lot more mature than KickC. It very rarely fails. It has better library support. There are more examples and information available online.

  • Like 3
  • Thanks 1

Share this post


Link to post
Share on other sites

JesperGravgaard, this is great new addition to the list of Atari programming languages and cross-compilers. Very neat, fast, optimized...

 

And I have one "strange" question. Maybe I am not familiar with console syntax of Java system, but I have troubles with naming a filename to be compiled.

 

Something like:

kickc d:\atari\projects\tests\examples\testdata.c -a

results in error:

File not found d:\atari\projects\tests\examples\testdata.c

 

All other, relative paths do work perfectly. For example:

kickc ..\examples\atarixl\rasterbars.c -a

works as expected. I posted here because I need a way of declaring path as described on top.

 

Thank you for this new amazing tool!

Gury

 

  • Like 1

Share this post


Link to post
Share on other sites

Awesome! I've already forked it and sent a PR :)

 

I'd like to also be able to create "init" blocks as separate segments.

I suppose the easiest way to do that will be creating a pure data segment with just ".word $2e2,$2e3,<address>".

I'll have a look at if this would be an easy thing to add to the plugin to allow an easier syntax.

 

EDIT:

Ooohhh, you've already add _RunAddr, I'll send a PR for "_InitAddr" to do a inter-segment load.

 

Edited by fenrock
_InitAddr
  • Like 1

Share this post


Link to post
Share on other sites
2 hours ago, Gury said:

 

kickc d:\atari\projects\tests\examples\testdata.c -a

results in error:

File not found d:\atari\projects\tests\examples\testdata.c

 

@Gury You have found a bug in the current version of KickC!

The code loading source files looks through a list of search folders - but does not consider that the file name may be absolute.

 

I have fixed it, and the fix will be in the next release. https://gitlab.com/camelot/kickc/-/issues/576

Thank you for finding the problem!

Edited by JesperGravgaard

Share this post


Link to post
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...
Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...