This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

ATALAN - new programming language for 6502

ATALAN - new programming language for 6502
by on (#70331)
Hi,

I'm developing new high level programming language for 6502 processors - ATALAN. You can find out more about it at http://atalan.kutululu.org/.

The language is still under development but it can already be used to develop games (even by someone else than me :-) ) See http://atalan.kutululu.org/projects.html
I would also claim, that it already produces very good code.

ATALAN currently supports 8-bit ATARI line of computers and I would like to expand the support to other platforms and to lure some programmers from other platforms to try the language.

Is there someone willing to try to create (with my extensive help, of course) platform definition files for NES system?

by on (#70333)
Does it support 65C816 (SNES) or HuC6280 (PC Engine)? Those would probably be more interesting for me than plain 6502 right now. But I might check it out when I finally get around to doing something on the Atari 8-bits (I already own an AtariMax 8Mbit flash cart).

Personally I'm not such a big fan of using Python-style block-delimiting by indentation :P

by on (#70338)
I on the other hand am a fan of Python-style indentation, though some of the syntax (such as for loops) could be made a bit more Python-like to ease the transition.

Bug report with tetris:
  • Randomizer "RANDOM and 7" appears biased toward 't', as both fixed tetrominoes 0 and 7 are T. If your RNGs are limited to only binary-power-of-two ranges, there's still a way to make an unbiased randomizer: choose one of the four least recently used of the seven one-sided tetrominoes.
  • I didn't see any code to nudge the piece one cell to the right or left if a rotation overlaps. Pretty much every Tetris game since 1999 has wall kick.
  • There's only one 'rotation' array from one fixed tetromino to its rotation, not one for each rotation direction. Pretty much every Tetris game since 1989 has bidirectional rotation.
  • It has the same name as the products of The Tetris Company. GNOME had to change its Tetris clone (formely "Gnometris") to be called "Quadrapassel".

Another hardware difference:

"out s@$8000:array(0..39,0..23) of byte"

That looks like how it's done on a GBA, but text output on the NES is a bit more complicated than that. It's a lot more like a TMS9918 series VDP used in the ColecoVision console and MSX computer. One must first wait for vertical blank, write to one port to seek to an address in video memory, and write one or more bytes to it. And either all the writes need to fit into the first 2270 CPU cycles after the VBI or the screen needs to be turned off. Copying the sprite display list from RAM to video memory ordinarily takes about 520 of these cycles.

by on (#70341)
@mic_:

Currently only 6502 is supported, but processor too is defined in platform file, so it should be possible to define other processors (at least in theory, surely there will be some problems when doing transition from 1 supported processor to 2 :-) ).

Defining another processor would just make it bigger adventure.

As for indentation, Atalan supports syntax with parentheses, in such case it ignores indent.

So you can use:

if x = 1
"a"
"b"

or

if x = 1 (
"a"
"b"
)

or

if x = 1 "a" "b"

whichever suits you better.

@tepples:

Nice spots for just short overview :-)

Tetris is not really meant as a full game, just as an example of using Atalan.
So yes, randomizer is biased and there is no nudge etc. but I believe there is no point in making the example more complicated than necessary.

As for hardware difference: that's why I am trying to find someone to help me with the NES support.

I do not thing, we should try to provide library that would allow the Tetris example to be compatible with both Atari and NES. That would severely limit both Atari and NES capabilities available in Atalan, as only common subset of the two systems features would be avalable. That would be in my opinion almost useless.

by on (#70342)
Just some things that caught my attention when skimming the reference:

* No 0xFF or FFh hex constants?
* No /* */ and //-style comments?
* No shift operators?!?
* No negation operator?
* "Relational operator may be chained, so it is possible to write for example 10<100 etc." huh?
* So if I write a = b and c I get bitwise AND, but if I write if b and c I get boolean AND? What if I want bitwise and in an expression used as a condition? Seems very error-prone.

by on (#70344)
@mic_:

I quicly skimmed the documentation on HuC6280 CPU at http://shu.emuunlim.com/download/pcedocs/pce_cpu.html and I believe support for this processor should be relatively easy to provide.
I'm only not sure about the paged memory. I'm not sure, how should automatic support for this work.

@blargg:

- hex constants are $FF
- // comment is done using ;
- shift operators are not yet supported directly, arithmetic shifts can be done using multiplication or division by two
- binary negation can be done using xor, logical negation is supported
- chained relational operators are like 10 < x <= 100 (will repair the documentation - HTML swallowed the <)
- bitwise vs. logical operators - it is possible to combine them, but it proves to be error prone, I'm considering switching to different names for binary operators (bitand, bitor ?)

by on (#70354)
Somethings I never understood about high-level languages?

What is all this "hello world" stuff?, why do you learn it first?, and what does it have to do with the NES?

How do all these valuables get stored? Like a + b = c? Where in the memory is "a" "b" and "c" and how does it avoid writting over them by accident? If you write something like "#$00 = $0000" is there a chance you might be writing over "a" by accident?

by on (#70356)
psycopathicteen wrote:
Somethings I never understood about high-level languages?

Was that a question?

Quote:
What is all this "hello world" stuff?, why do you learn it first?, and what does it have to do with the NES?

The whole "hello world" deal comes from the notion that the most basic thing you can do in any programming language is display a message, so looking at how that's achieved in a new language might be an interesting first contact with it.

On the NES this doesn't make much sense, because it doesn't have a default character set or a built-in function that displays messages, so the fact that you have to include those in the hello world program will make it much more complex than the average hello world program. That's in assembly though, a high-level language might make things easier.

Quote:
Where in the memory is "a" "b" and "c"

Those are variables. The compiler probably assigns those names to free memory locations.

Quote:
and how does it avoid writting over them by accident?

Uh... just pay attention to the variable names you use?

Quote:
"#$00 = $0000"

That statement doesn't make any sense in any language.

by on (#70358)
It seems the main difference between NES and Atari 8-bit is the memory map. There's 2kB of RAM, but is often expanded by putting another 8kB at $6000-$7FFF. $8000 and above is all ROM, of course, and paged in whatever way one could want for larger sizes. Given a choice, I would say to go with the UNROM mapper, $8000-$BFFF is paged, $C000-$FFFF is fixed to the last 16kB.

Excluding all the graphics and other I/O differences, if you think it could be usable on the Atari 5200, then should be just as good on NES. As long as you don't use decimal mode. It was patented, so it was removed when they copied the 6502.

by on (#70360)
rudla.kudla wrote:
- shift operators are not yet supported directly, arithmetic shifts can be done using multiplication or division by two

But does it optimize the multiplies into shifts, or does it call a multiply function?

Quote:
- bitwise vs. logical operators - it is possible to combine them, but it proves to be error prone, I'm considering switching to different names for binary operators (bitand, bitor ?)

Python uses '&' and '|' for bitwise operators and 'and' and 'or' for operators that cast both sides to Boolean.

psycopathicteen wrote:
What is all this "hello world" stuff?, why do you learn it first?

Displaying "hello world" is there to confirm that your toolchain, init code, and text drawing code are in order, so that you can print messages to the screen about the state of the rest of your program.

tokumaru wrote:
Was that a question?

Probably not. Try replacing '?' with ':' in that.

Quote:
On the NES [displaying hello world] doesn't make much sense, because it doesn't have a default character set or a built-in function that displays messages

The NES has more of a default character set than some other consoles, if only because the NES supports CHR ROM. You don't have to copy the font into RAM first; you can just wait for warm-up, throw up a palette, clear a nametable, write "hello world" to a port on the PPU, and turn rendering on.

by on (#70362)
tepples wrote:
rudla.kudla wrote:
- shift operators are not yet supported directly, arithmetic shifts can be done using multiplication or division by two

But does it optimize the multiplies into shifts, or does it call a multiply function?

Take a look at processor/m6502/m6502.atl in the source code. Apparently this file literally implements the language for the 6502, from high-level operators down to the basic instructions, creating a mapping between them, and optimizations for specific cases. I didn't see any arithmetic right shift, so I'm not sure whether it even supports signed division (and whether it's algebraic or round-to-negative-infinity). I'm actually wondering whether it can even differentiate between signed and unsigned values for operators.

I believe this one translates a*b into mul r,a,b (where r is a temporary to place the result into)

;"*"@10:binary = #mul r,a,b

Then mul is implemented for the general case
Code:
rule mul %A:card, %B:byte, %C:byte = instr
        let _a, %B
        let _x, %C
        call _sys_mul8
        let %A, _TEMPW2

Optimizations are made for specific cases, like *2, *3, *4, etc.
Code:
rule mula %A:byte, 2    = instr
        let _a, %A      ; lda %B
        mul _a, _a, 2   ; asl

rule mula %A:byte, 3    = instr
        let _a, %A      ; lda %B
        mul _a, _a, 2   ; asl
        let _c, 0
        add _a, _a, %A

rule mul %A:byte, %B:byte, 4    = instr
        let _a, %B      ; lda %B
        mul _a, _a, 2   ; asl
        mul _a, _a, 2   ; asl
        let %A, _a      ; sta %A

And finally, some actual instructions, like
Code:
rule let _a,%A:byte     = "   lda %A"
rule mul _a,_a,2        = "   asl"
rule add _a,_a,%A:byte  = "   adc %A"

This to me is the most fascinating part of the language. In a very flexible way, it goes from high-level operators down to assembly generation and optimization, all in the same kind of specification syntax.

by on (#70377)
Quote:
I quicly skimmed the documentation on HuC6280 CPU at http://shu.emuunlim.com/download/pcedocs/pce_cpu.html and I believe support for this processor should be relatively easy to provide.
I'm only not sure about the paged memory. I'm not sure, how should automatic support for this work.


I'd think most 6502-based systems have some kind of paging/banking. It's typically up to the programmer to handle the bank switches. What would be needed for the HuC6280 are some kind of intrinsic functions in ATALAN to be able to access the special bank switching functions (TAM/TMA). One would also like to have similar ones for the block-move instructions (TII/TIA/TIN...).
And preferably the code generator/optimizer should be aware of the new instructions available (CLA/STZ/DEA/INA/BBR...) and use them when applicable.

by on (#70379)
@blargg:

You perfectly grasped the idea behind Atalan :-) In future, I would like to target even some other processors (like Z80). Also any programmer knowing an assembler should be able to provide it's own translation rules, so you are not stuck, when something is not as optimal as you would like to.

Signed numbers can be supported - in fact rules match against intervals like 0..255 or -128..127 etc. so it is possible to provide special rules for any numeric interval.

I have currently some rudimentary support for signed numbers, but multiplication and division are not supported yet. I do not currenlty have the necessary routines for signed multiplication and division.

However this is again one of the strong points of rule based translation. If your application is computation heavy (some 3d), you may create fast versions of arithmetic routines and let the compiler use them instead of default built-in routines.

@mic_:
Quote:
I'd think most 6502-based systems have some kind of paging/banking. It's typically up to the programmer to handle the bank switches. What would be needed for the HuC6280 are some kind of intrinsic functions in ATALAN to be able to access the special bank switching functions (TAM/TMA). One would also like to have similar ones for the block-move instructions (TII/TIA/TIN...).


I was at least thinking about assigning every array to some bank and automatically switching them on when they are accessed.

Manual bank switching should be possible just using translation rules and macros.

Quote:
And preferably the code generator/optimizer should be aware of the new instructions available (CLA/STZ/DEA/INA/BBR...) and use them when applicable.


Custom translation rules should be able to handle this easily.

by on (#70405)
Quote:
Signed numbers can be supported - in fact rules match against intervals like 0..255 or -128..127 etc. so it is possible to provide special rules for any numeric interval.

Very interesting. It allows more semantic information to go from a high level down to optimization. One thing about optimization, though, can it do it to entire basic blocks, or is it only at the statement level? For example, if I incremented a variable then immediately decremented it, is there any way that could be optimized out entirely?

by on (#70415)
blargg wrote:
One thing about optimization, though, can it do it to entire basic blocks, or is it only at the statement level? For example, if I incremented a variable then immediately decremented it, is there any way that could be optimized out entirely?

Adding support for peephole optimization would depend on keywords in the language to mark a variable as having side effects on read or write. For example, C uses volatile for this. Otherwise, it'd optimize out the first of two writes to $2005 or $2006 and the second through eighth reads from $4016.

by on (#70417)
Quote:
One thing about optimization, though, can it do it to entire basic blocks, or is it only at the statement level? For example, if I incremented a variable then immediately decremented it, is there any way that could be optimized out entirely?


Optimizations work on whole blocks and more. Some optimizations (for example live variable analysis) even perform data flow analysis, so they work with multiple blocks.

Optimizations are performed at assembler level, i.e. after translation from 'high level' instructions to 'assembler' instructions are done. This means, that even unnecessary parts of multibyte instructions can be removed etc. Also theoretically it can try to optimize assmbler code in the future :-)

by on (#70418)
Quote:
Adding support for peephole optimization would depend on keywords in the language to mark a variable as having side effects on read or write. For example, C uses volatile for this. Otherwise, it'd optimize out the first of two writes to $2005 or $2006 and the second through eighth reads from $4016.


It is possible to mark variable as IN (it can return different values in succesive reads) or OUT (writing to variable has some side effect). Optimizer will not optimize out writes to out registers or reads from in registers.

by on (#70420)
As an example of quality of generated code, see for example this simple program and it's translation to 6502 code.

Code:
; Horizontal Stars
; Demo program for ATALAN programming language
;(c) 2010 Rudla Kudla

i:0..242            ; number of lines with stars
hpos:array(242) of byte
speed:array(242) of 1..4
col:array(242) of color

;Turn off screen, so we have nice black deep space

DMACTL = 0
GRACTL = 0

;Initialize start positions, speeds and colors

for i
   hpos(i)  = RANDOM
   speed(i) = (RANDOM and 3) + 1
   col(i)   = RANDOM or 3         ;make the star color light enough

;Setup sprite 0

player_gfx(0) = 1
player_size(0) = 0

until STRIG(0) = pressed

   until VCOUNT = 4
      wait'line
      
   for i
      player_col2(0) = col(i)
      player_x(0) = hpos(i)
      hpos(i) = hpos(i) + speed(i)
      wait'line

;Turn off sprite and turn on screen

player_gfx(0) = 0
player_gfx(1) = 0
GRACTL = 0
DMACTL = 34


6502:

Code:

RANDOM equ 53770
VCOUNT equ 54283
WSYNC equ 54282
button__state__pressed equ 0
STRIG equ 644
DMACTL equ 559
player_size equ 53256
player_x equ 53248
player_gfx equ 53261
player_col2 equ 53266
GRACTL equ 53277
   org $2e0
   dta a($2000)
   org $2000
;### stars.atl(12) DMACTL = 0
   lda #0
   sta DMACTL
;### stars.atl(13) GRACTL = 0
   sta GRACTL
;### stars.atl(17) for i
   tax
_lbl2:
;### stars.atl(18)    hpos(i)  = RANDOM
   lda RANDOM
   sta hpos,x
;### stars.atl(19)    speed(i) = (RANDOM and 3) + 1
   and #3
   clc
   adc #1
   sta speed,x
;### stars.atl(20)    col(i)   = RANDOM or 3         ;make the star color light enough
   lda RANDOM
   ora #3
   sta col,x
   inx
   cpx #243
   jne _lbl2
;### stars.atl(24) player_gfx(0) = 1
   lda #1
   sta player_gfx
;### stars.atl(25) player_size(0) = 0
   lda #0
   sta player_size
;### stars.atl(40) player_gfx(0) = 0
   jmp _lbl3
_lbl10:
;### stars.atl(32)    for i
   jmp _lbl5
_lbl7:
;### stars.atl(30)       wait'line
   sta WSYNC
_lbl5:
;### stars.atl(29)    until VCOUNT = 4
   lda VCOUNT
   cmp #4
   jne _lbl7
_lbl6:
   lda #0
   tax
_lbl9:
;### stars.atl(33)       player_col2(0) = col(i)
   lda col,x
   sta player_col2
;### stars.atl(34)       player_x(0) = hpos(i)
   lda hpos,x
   sta player_x
;### stars.atl(35)       hpos(i) = hpos(i) + speed(i)
   clc
   adc speed,x
   sta hpos,x
;### stars.atl(36)       wait'line
   sta WSYNC
   inx
   cpx #243
   jne _lbl9
_lbl3:
;### stars.atl(27) until STRIG(0) = pressed
   lda STRIG
   cmp #button__state__pressed
   jne _lbl10
_lbl4:
   lda #0
   sta player_gfx
;### stars.atl(41) player_gfx(1) = 0
   sta player_gfx+1
;### stars.atl(42) GRACTL = 0
   sta GRACTL
;### stars.atl(43) DMACTL = 34
   lda #34
   sta DMACTL
_lbl11:
   jmp _lbl11
hpos:
   .ds 243
speed:
   .ds 243
col:
   .ds 243
   icl 'P:\atalan\atalan\platform\atari\atari.asm'

by on (#70421)
I do see some optimization here:
Code:
;### stars.atl(34)       player_x(0) = hpos(i)
   lda hpos,x
   sta player_x
;### stars.atl(35)       hpos(i) = hpos(i) + speed(i)
   clc
   adc speed,x
   sta hpos,x

What happened here?
Code:
_lbl6:
   lda #0
   tax
_lbl9:
   lda col,x

by on (#70425)
Quote:
What happened here?

I'm guessing, this:
Code:
  inx
   cpx #243
   jne _lbl9

by on (#70426)
Optimizer currently has rule, that it won't change short instruction to longer one (i.e. tax to ldx #0). In this situation, the rule should be more complicated - it should recognize, the lda #0 would not b needed if ldx #0 is used. But that is to be done, yet.

Optimizer is not perfect yet, but I believe, the produced code is already 'presentable'. :-)

Btw. it is possible to write interrupt routines in Atalan.

by on (#70427)
@mic_:

Your guess is correct.
Loop optimizer decided x could be used throughout the loop and does not need to be reloaded - it's value was originally in a. The lda #0 tax is the last remaining artifact of this.

by on (#70635)
You inspired me to develop optimization for these cases. :-) Now the code looks like this (see how that same optimization applied at the beginning of the source code):

Code:
RANDOM equ 53770
VCOUNT equ 54283
WSYNC equ 54282
button__state__pressed equ 0
STRIG equ 644
DMACTL equ 559
player_size equ 53256
player_x equ 53248
player_gfx equ 53261
player_col2 equ 53266
GRACTL equ 53277
   org $2e0
   dta a($2000)
   org $2000
;### stars.atl(12) DMACTL = 0
   ldx #0
   stx DMACTL
;### stars.atl(13) GRACTL = 0
   stx GRACTL
;### stars.atl(17) for i
_lbl2:
;### stars.atl(18)    hpos(i)  = RANDOM
   lda RANDOM
   sta hpos,x
;### stars.atl(19)    speed(i) = (RANDOM and 3) + 1
   and #3
   clc
   adc #1
   sta speed,x
;### stars.atl(20)    col(i)   = RANDOM or 3         ;make the star color light enough
   lda RANDOM
   ora #3
   sta col,x
   inx
   cpx #243
   jne _lbl2
;### stars.atl(24) player_gfx(0) = 1
   lda #1
   sta player_gfx
;### stars.atl(25) player_size(0) = 0
   lda #0
   sta player_size
;### stars.atl(40) player_gfx(0) = 0
   jmp _lbl3
_lbl7:
;### stars.atl(30)       wait'line
   sta WSYNC
_lbl5:
;### stars.atl(29)    until VCOUNT = 4
   lda VCOUNT
   cmp #4
   jne _lbl7
_lbl6:
   ldx #0
_lbl9:
;### stars.atl(33)       player_col2(0) = col(i)
   lda col,x
   sta player_col2
;### stars.atl(34)       player_x(0) = hpos(i)
   lda hpos,x
   sta player_x
;### stars.atl(35)       hpos(i) = hpos(i) + speed(i)
   clc
   adc speed,x
   sta hpos,x
;### stars.atl(36)       wait'line
   sta WSYNC
   inx
   cpx #243
   jne _lbl9
_lbl3:
;### stars.atl(27) until STRIG(0) = pressed
   lda STRIG
   cmp #button__state__pressed
   jne _lbl5
_lbl4:
   lda #0
   sta player_gfx
;### stars.atl(41) player_gfx(1) = 0
   sta player_gfx+1
;### stars.atl(42) GRACTL = 0
   sta GRACTL
;### stars.atl(43) DMACTL = 34
   lda #34
   sta DMACTL
_lbl11:
   jmp _lbl11
hpos:
   .ds 243
speed:
   .ds 243
col:
   .ds 243
   icl 'P:\atalan\atalan\platform\atari\atari.asm'

by on (#70779)
- A minor thing: personally, I like the C stardard for comparison and attribution. Like...

Code:
 int A = 0; /* A becomes zero */

while( A == 0 ) { /* compares A with zero */
}

by on (#71707)
Great project. I learned Python at school and would really like to give this one a try.
Does anyone know a good emulator to test my atalan code?

by on (#71716)
I'd say FCEU (the plain version) because it has an easy to use debugger.

by on (#91802)
So, I had a go at trying out Atalan this weekend, and thought I'd summarize my experience with it for the benefit of others.

I approached it with a quite positive attitude. While some of the syntax might seem strange at first, I did find it rather elegant, and saving a lot of characters to write compared to C/Java syntax.

I decided to try and port the simple map editor I have written for my current game project, which allows you to do some simple editing of the map for easier debugging. This seemed like a good example of something I could try out a HLL for: performance is not a big issue and it requires a lot of repetitive code that becomes hard to maintain in asm. Besides, since it's not something you'd need in the final product, you wouldn't become dependent on it for the rest of your game.

But when trying out the version available from the website, switching the platform to nes with the '-p nes' option would make the compiler crash with a "Atalan has stopped working" windows dialog. Compiling for the default atari target did work though. (for 'hello world' and the other included examples at least)

Not wanting to give up just yet, I checked out the SVN source from the Atalan source repository trunk. And with this one, the 'hello world' example finally worked. However, when trying out the part of my map editor that I had re-written in Atalan, I got the same old compiler crash. Eventually, I tracked down that this code:

Code:
const SpriteImageEditCursor:array = (
    4,
    0,EDIT_CURSOR_TILEID&$FF,$00,0
    0,EDIT_CURSOR_TILEID&$FF,$40,8
    8,EDIT_CURSOR_TILEID&$FF,$80,0
    8,EDIT_CURSOR_TILEID&$FF,$C0,8
    )


...should really look like this:

Code:
const SpriteImageEditCursor:array = (
    4,
    0,EDIT_CURSOR_TILEID bitand $FF,$00,0
    0,EDIT_CURSOR_TILEID bitand $FF,$40,8
    8,EDIT_CURSOR_TILEID bitand $FF,$80,0
    8,EDIT_CURSOR_TILEID bitand $FF,$C0,8
    )


But I sure would have expected this to result in a syntax error rather than a crashing compiler.

Anyways, now I was somewhat back on track to re-write my assembly code in Atalan. Atalan uses the syntax of the MADS assembler for the assembly code it outputs, while my project uses CA65. But fortunately, rewriting the m6502.atl and nes.atl files to produce code in CA65 syntax wasn't that hard, and was one of the things I found exciting about the Atalan project - to easily be able to tune the backend for your own needs. It did help that I know Polish as a second language though, since the MADS documentation is in Polish and I had to find out how certain directives worked to know which CA65 counterparts they should use instead. :)

One strange error I never understood was how defining a byte value at a certain memory location like this:
Code:
joyP0@$15:byte


And then using it like this:
Code:
if joyP0 bitand JOY_SELECT <> 0
    CopyMapToSRAM
    return $80

Would produce "Logic error: Cannot infer type of result of operator 'bitand'." from the compiler (very common error message that kept creeping up in a lot of other cases as well)

The solution I found by trial and error was to replace the declaration with:
Code:
in joyP0@$15:byte

Which is really only supposed to be needed for hardware registers where you don't want the compiler to optimize away multiple reads from it.

I had read a lot about Atalan's optimization features, so I was very eager to inspect the resulting code. Here's the routines I wrote one-by-one:

* Atalan source *
Code:
UploadEditCursorCHR:proc =
    addr:0..65535
    addr = EDIT_CURSOR_TILEID*16
    PPU_DATA_ADR = hi addr
    PPU_DATA_ADR = lo addr
    for i:0..7
        PPU_DATA = 0
    for i:0..7
        PPU_DATA = EditCursorCHR(i)


* Assembly output *
Code:
.proc UploadEditCursorCHR
;### editmap.atl(55)     addr = EDIT_CURSOR_TILEID*16
   lda #240
   sta UploadEditCursorCHR__addr+0
   lda #31
   sta UploadEditCursorCHR__addr+1
;### editmap.atl(56)     PPU_DATA_ADR = hi addr
   sta PPU_DATA_ADR
;### editmap.atl(57)     PPU_DATA_ADR = lo addr
   lda #240
   sta PPU_DATA_ADR
;### editmap.atl(58)     for i:0..7
   lda #0
   sta _s1__i
_lbl6:
;### editmap.atl(59)         PPU_DATA = 0
   lda #0
   sta PPU_DATA
   inc _s1__i
   lda _s1__i
   cmp #8
   jne _lbl6
;### editmap.atl(60)     for i:0..7
   ldx #0
_lbl8:
;### editmap.atl(61)         PPU_DATA = EditCursorCHR(i)
   lda EditCursorCHR,x
   sta PPU_DATA
   inx
   cpx #8
   jne _lbl8
   rts
.endproc


First, I would have expected the stores to the intermediate 'addr' variable to be optimized out by the compiler. But even more strange is how the first loop writing zeros to $2007 produces much worse code than the one copying the contents of my array.

Then finally, a small part of my original asm routine that updates the cursor movement in the map editor:

* Atalan source *
Code:
UpdateMapEditor:proc >modifiedFlags:byte =
    DrawEditCursor

    if joyP0 bitand JOY_SELECT <> 0
        CopyMapToSRAM
        return $80
    else if (joyP0 bitand JOY_LEFT <> 0) and editCursorX > 0
        dec editCursorX
    else if (joyP0 bitand JOY_RIGHT <> 0) and editCursorX < 15
        inc editCursorX
    else if (joyP0 bitand JOY_UP <> 0) and editCursorY > 0
        dec editCursorY
    else if (joyP0 bitand JOY_DOWN <> 0) and editCursorY < 14
        inc editCursorY

    return 0


* Assembly output *
Code:
.proc UpdateMapEditor
;### editmap.atl(71)     DrawEditCursor
   jsr DrawEditCursor
;### editmap.atl(73)     if joyP0 bitand JOY_SELECT <> 0
   lda joyP0
   and #root__JOY_SELECT
   sta UpdateMapEditor___37
   jeq _lbl9
;### editmap.atl(74)         CopyMapToSRAM
   jsr CopyMapToSRAM
;### editmap.atl(75)         return $80
   jmp _exit32766
_lbl9:
;### editmap.atl(76)     else if (joyP0 bitand JOY_LEFT <> 0) and editCursorX > 0
   lda joyP0
   and #root__JOY_LEFT
   sta UpdateMapEditor___39
   jeq _lbl11
   lda #0
   cmp editCursorX
   jcs _lbl11
   dec editCursorX
   jmp _exit32766
_lbl11:
;### editmap.atl(78)     else if (joyP0 bitand JOY_RIGHT <> 0) and editCursorX < 15
   lda joyP0
   and #root__JOY_RIGHT
   sta UpdateMapEditor___40
   jeq _lbl12
   lda editCursorX
   cmp #15
   jcs _lbl12
   inc editCursorX
   jmp _exit32766
_lbl12:
;### editmap.atl(80)     else if (joyP0 bitand JOY_UP <> 0) and editCursorY > 0
   lda joyP0
   and #root__JOY_UP
   sta UpdateMapEditor___41
   jeq _lbl13
   lda #0
   cmp editCursorY
   jcs _lbl13
   dec editCursorY
   jmp _exit32766
_lbl13:
;### editmap.atl(82)     else if (joyP0 bitand JOY_DOWN <> 0) and editCursorY < 14
   lda joyP0
   and #root__JOY_DOWN
   sta UpdateMapEditor___42
   jeq _exit32766
   lda editCursorY
   cmp #14
   jcs _exit32766
   inc editCursorY
_exit32766:
   rts
.endproc


The 'jmp' commands to _exit32766 could obviously be optimized away with an 'rts'. Also, the 'return N' commands don't seem to work at all since the return values aren't stored anywhere.

But most importantly, we again see lots of intermediate results being written to temporary variables for no good reason. For just this short piece of code, there are no less than 4 of them.

So this looks like the real showstopper to me and I probably won't attempt to convert the rest of my mapeditor to Atalan. While I could live with some of the slower/larger code produced, it seems like the memory usage will just explode in Atalan code. While it is true that this is only temporary variable space, and Atalan did correctly re-use the same memory areas in other routines in my short program, the unnecessary memory stores could easily eat up most of your zeropage memory in a more complex routine. And really, ~16 bytes should be enough temporary space for most projects I could think of.

So my conclusion would be that Atalan is a very fascinating proof-of-concept, and if it did perform what the documentation suggests, it would likely be the language I'd choose for non-performance critical AI code in my game. But at this point, the implementation is just not mature enough to be usable in practice. I hope that it reaches that stage in a few years from now, but looking at the source code repository and the discussion groups, development seems to have been ramping down quite a bit during the last year.

Still, I recommend anyone who's thinking about implementing a compiler for the 6502 (or any other low-level system for that matter) to have a serious look at Atalan for inspiration. If you do like to have a go at language implementation in your spare time, improving Atalan could be a good start. But personally, I don't want to get stuck in yet another time-consuming sideproject that keeps me from actually working on my project :)

by on (#91810)
Bananmos wrote:
Anyways, now I was somewhat back on track to re-write my assembly code in Atalan. Atalan uses the syntax of the MADS assembler for the assembly code it outputs, while my project uses CA65. But fortunately, rewriting the m6502.atl and nes.atl files to produce code in CA65 syntax wasn't that hard, and was one of the things I found exciting about the Atalan project - to easily be able to tune the backend for your own needs. It did help that I know Polish as a second language though, since the MADS documentation is in Polish and I had to find out how certain directives worked to know which CA65 counterparts they should use instead. :)

To whom it may concern, CA65 has an undocumented pseudo-op ".dbg" that CC65 uses to add debug line information (among other things) to asm generated from C code. With some changes to Atalan (I doubt it's possible just by modifying the templates?) it should be possible to use it to get source level debugging of Atalan code in emulators like NESICIDE and NintendulatorDX.

Just throwing that out there since it's probably not common knowledge.

by on (#91813)
Thanks for trying Atalan. I'm sorry your experience has not been better. Atalan is still work in progress (and will probably be forever :-) ) and not much code has been written in it, therefore it is not impossible (or even hard) to encounter error in parsing. They are usually easy to fix, and I will try to use your example to fix the error.

Optimizations are similar story. I only recently introduced support for processor flags, and not everything has been tuned up (again, having test examples helps a lot). For example 'STA' storing the result of 'AND' into local variable has not been removed, because it modifies flags and they are used in next instruction. Of course the same flag has been set by 'AND', but teaching this the compiler is one of hundreds little things that must be done. Now that I have the input, I may be able to do it.

As for strange 'Logic error: Cannot infer type ' errors. This is side effect of very interesting (I believe) and very experimental feature of Atalan. It does interval type inferecing. The algorithm is complex and not yet complete and often needs much better error messages. So what happens in your case is, that Atalan fails to infer type of the variable joyP0 at that exact position. When you mark the variable as input, Atalan always takes the defined interval as the value range.

All in all, although I would very much like someone to keep trying to use Atalan and thus give me the valuable inputs I'm currently missing, I would not recommend anyone to try to use it for 'real' development. Atalan is now just for the brave. :-)

I am now trying to develop some bigger game in Atalan for Atari while fixing any errors I encounter in the process and implementing necessary features. After I finish this, I may try to port the game for NES, and that may be the right moment to jump the train for others.

by on (#91815)
Well one input from me regarding parsing problems in general:
Unless you want to dedicate a lot of your development time to writing a lexalizer/parser for the pure sake of learning, it is IMO an almost insane waste of time to write your own parser.

Parsing source code is one of the things in compiler programming that is very simple to write a first working implementation for, yet extremely hard and tedious to get bugfree. I'm willing to bet that replacing your own parsing code with Flex/Bison will make life much simpler for both you and Atalan users. Not to mention the fact that when the rest of the Atalan backend is so configurable and data-driven, it sure would be nice if the scanning&parsing would be as easy for other people to understand and experiment with :)

Quote:
As for strange 'Logic error: Cannot infer type ' errors. This is side effect of very interesting (I believe) and very experimental feature of Atalan. It does interval type inferecing. The algorithm is complex and not yet complete and often needs much better error messages. So what happens in your case is, that Atalan fails to infer type of the variable joyP0 at that exact position. When you mark the variable as input, Atalan always takes the defined interval as the value range.


I did not understand this very well - would you mind explaining it in more detail? :)

i.e., what is this "interval type inferecing"? And why does it only respect the "defined interval" (the :byte tag I presume?) if the variable is marked with 'in'? This one is really puzzling me.

by on (#91818)
Atalan tries to infer set of possible values of the result of every operator in the
code. So it not only deduces, that the variable should be integer, but also
the range of the integer.

The following examples use type assert.
assert x:min..max makes sure the compiler at specified line
inferred the range of variable x to be min..max.

Code:
;Basic type inference
in x:10..20
y = x
assert y:10..20   ;For input variables, Atalan uses the defined type
y = x * 2
assert y:20..40
y = y + 5
assert y:25..45
z = y - 30
assert z:-5..15

;Conditions are recognized
in x:10..20
y = x * 2
assert y:20..40
if y > 30
    y = 30
assert y:20..30
if y <= 25 then y = 26
assert y:26..30

;Some loops are supported
x = 10
while x < 100
    inc x
    assert x:11..100
assert x:100..100
x = 3
while x <= 23
    inc x
    assert x:4..24
assert x:24..24
 
;Dependent varibles in loop may be computed
y = 1
for x:0..7
   inc y
assert y:9..9


Benefits of type inferencing

- You don't need to declare types of local variables, leading to shorter,
more readable code. It is easier to introduce a new local variable,
so you can create some extra ones, with meaningfull names, which
will further improve code readability.

- With exact interval type inferencing, the compiler may statically check
array bounds errors, incorrect asignment errors etc.

- Inferred types may be used for optimizations.

- With proper interval type inferenced, compiler can produce correct code
in most situations. For example when computing expression like (a * b) / 128
where a and b are in range 0..127, compiler will correctly use 2 byte temporary
variable for the result of multiplication, even if the final result is only byte again.

- Types may be inferred for procedure arguments and local variables (these are two
diferents algorithms).

by on (#91820)
Ok, I think I see what you mean... basically, you're saying that the compiler tracks the allowable range of variables from the range in their definitions to be able to produce more optimized code, as some of the bits should be known to be zero?

That makes sense to me, but I still can't understand how this results in that
Code:
in joyP0@$15:byte


Should be any different from
Code:
joyP0@$15:byte


Both have the same 'byte' range (0..255) after all, so how come the 'in' keyword is required? How can that change the behavior?

Also, the inferring of ranges did not seem to do what I would have expected in the code I tried:
Code:
editCursorX:0..15
editCursorY:0..15
DrawEditCursor:proc =
    DrawSpriteImage editCursorX*16 editCursorY*16 SpriteImageEditCursor


This would produce a call to a system routine for multiplication, even though the range is 0..15 multiplied by 16, and therefore fits within a byte and be achievable with four simple 'asl' instructions.

But changing it to
Code:
editCursorX:0..15
editCursorY:0..15
DrawEditCursor:proc =
    screenX:0..255 = editCursorX*16
    screenY:0..255 = editCursorY*16
    DrawSpriteImage screenX screenY SpriteImageEditCursor


Would produce the four 'asl' instructions for each multiplication, as I expected. But the temporary variables will also result in redundant writes to RAM, as in the previous examples.

Perhaps I am missing something, but it seems that at least in the trunk source code of Atalan, this range tracking isn't working as it should?

EDIT: I guess what you said here:
Quote:
- Types may be inferred for procedure arguments and local variables (these are two
diferents algorithms).


...could be the reason why the behavior changes when I introduce the intermediate variable? i.e., the inferring works for locals but *not* for procedure arguments?...

by on (#91821)
The inferring does not work with global variables. That's what you use in your first example.
Improved optimization
by on (#92190)
Being provoked by previous posts, I have decided to implement new optimizations (and thus introduce new bugs) instead of fixing bugs. :-)

For this code:


Code:
use NES

const EDIT_CURSOR_TILEID = 100

const EditCursorCHR:array() = 0,1,2,3,4,5,6,7

ppu.ptr = EDIT_CURSOR_TILEID*16 

for i:0..7 ppu.data = 0
for i:0..7 ppu.data = EditCursorCHR#i


Atalan now produces this:


Code:
ppu__data_adr equ 8198
ppu__data equ 8199
   opt h-
   .byte('N', 'E', 'S', $1A, $02, $01,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00)
   org $8000
RESET:
   sei
   cld
   ldx #$FF
   txs
;### opti_nesx2.atl(7) ppu.ptr = EDIT_CURSOR_TILEID*16 
   lda #6
   sta ppu__data_adr
   lda #64
   sta ppu__data_adr
;### opti_nesx2.atl(9) for i:0..7 ppu.data = 0
   ldx #248
   lda #0
_lbl6:
   sta ppu__data
   inx
   jne _lbl6
;### opti_nesx2.atl(10) for i:0..7 ppu.data = EditCursorCHR#i
   ldx #248
_lbl8:
   lda EditCursorCHR-248,x
   sta ppu__data
   inx
   jne _lbl8
   rts
EditCursorCHR:
;### opti_nesx2.atl(5) const EditCursorCHR:array() = 0,1,2,3,4,5,6,7
   dta 0
   dta 1
   dta 2
   dta 3
   dta 4
   dta 5
   dta 6
   dta 7
   icl 'P:\atalan\src\atalan\platform\NES\NES.asm'
   icl 'P:\atalan\src\atalan\cpu\m6502\m6502.asm'
   .align $FFF9,$00
NMI:
    rti
   org $FFFA
   .dw NMI
   .dw RESET
   .dw 0

by on (#92444)
I am running Vista 32-bit.

When I first tried "atalan -P nes snake" Atalan complained about missing MCVCR100.dll.

I downloaded and installed Microsoft Visual C++ 2010 Redistributable Package.

It still errors out with this information:
Code:
Problem signature:
  Problem Event Name:   APPCRASH
  Application Name:   atalan.exe
  Application Version:   0.0.0.0
  Application Timestamp:   4d84e642
  Fault Module Name:   atalan.exe
  Fault Module Version:   0.0.0.0
  Fault Module Timestamp:   4d84e642
  Exception Code:   c0000005
  Exception Offset:   00002650
  OS Version:   6.0.6002.2.2.0.768.3
  Locale ID:   1033
  Additional Information 1:   03d5
  Additional Information 2:   c62ce54d72177dd32c2e4ef275293885
  Additional Information 3:   c99c
  Additional Information 4:   af763b56a93ceca5ccfc6d5f0fa3b625


I put all the snake.* files into the same folder as atalan.exe. What could I be doing wrong?

by on (#92621)
rudla.kudla wrote:
As for indentation, Atalan supports syntax with parentheses, in such case it ignores indent.
Haskell allows both explicit {;} and implicit by layout. I always use the explicit mode although the layout mode is more commonly used by Haskell programmers in general. If your programming language does not need delimiters between commands (such as semicolons) then what you gave in example should be working too; that is good too

And, where is document of macros?

by on (#92628)
Quote:
I put all the snake.* files into the same folder as atalan.exe. What could I be doing wrong?


Sounds like the same issues I was having whenever I tried compiling anything with the "-p NES" option.

The only solution I found was ignoring the win32 build on the webpage and building it from the latest source grabbed from the repository.