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

[Alpha] QASM - A Simplified CA65 with Debugger Output

[Alpha] QASM - A Simplified CA65 with Debugger Output
by on (#78845)
Edited 06/30/2011 - New version 0.07 introduces a character encoding feature that allows you to specify text as an ASCII string and have the assembler translate it to your custom encoding for you. Also numerous bug fixes, tweaks to the macro system and additional features to incbin and data structures added.

Well I finally got bored enough to write that assembler I've been talking about. Right now it is feature complete in terms of have-to-have features for an assembler. I will be testing the assembler by porting my current project over to it.

The motivation behind this is that I absolutely love TheFox's debugging extensions for Nintendulator, but there are several things I do not like about CA65. So I set out to write an assembler that is to my liking, but also outputs debugging information that is compatible with CA65. I have now accomplished this.

The assembler is written with Python 2.5, so it should be compatible with just about any system under the sun.

Quick points:

1. Traditional one-shot assembler, no intermediate object format
2. Uses segments like CA65, but they are specified within the code in a simpler format
3. Nested segment entry
4. Real lexical scopes that can be nested and referenced before they are defined
5. Anonymous scopes
6. Basic set of directives
7. Generates memory usage map
8. Generates debugging output that is compatible with TheFox's debugging extensions for Nintendulator, yay!
9. Supports macros and complex expressions
10. Supports conditional assembly
11. Now supports data structures

Project Page
Direct Download

If any of you give this a try (or just read the documentation) let me know what you think. I am very interested in what directives others would be interested in being implemented. I have only implemented those directives which I use in my own code, but that may not cover everyone.

by on (#78873)
That is pretty cool. :)

FYI the iNES header in nestest1 is invalid.

by on (#78875)
Why is the header invalid? It's the same one I use on all of my projects. I'd like to know how to fix it :D

Edit: Well I figured something out. I did not have the SRAM bit of the header set.

by on (#78938)
I have finished macro support and updated to version 0.02. The first post has been updated to reflect this and the download link has also been updated.

The documentation was also updated for macros. Here is the macro documentation.

by on (#78939)
qbradq wrote:
Why is the header invalid? It's the same one I use on all of my projects. I'd like to know how to fix it :D
ines.s says "mapper 1", but the .word value there is $1200 and should be $0010
$ file nestest1.nes
nestest1.nes: iNES1 ROM, Mapper 0x10, 16x16k PRG, 16x8k CHR, [Horz], [PC10]

by on (#78941)
Ah, right you are. I was using that .word instead of two .bytes just to test out the .word directive. I'll fix that now.

by on (#78944)
Wow, I implemented complex expressions over my lunch break :D That was a huge surprise to me. I thought it would take days. I guess having done it once before helped out a lot.

So this thing is ready to use for a largish program. I am going to start porting my project over to it now, and hopefully work out any bugs in the process. If anyone else finds this useful, has a bug report or feature request let me know.

by on (#78946)
I'm pretty impressed by people writing their own assemblers. To me it seems like a coders nightmare with all parsing and stuff. :)
Do you have any education that helped you with this project? Or is there a "Programming an assembler for dummies" book somewhere? :)

by on (#78948)
The one article that helped me the most was Let's Build a Compiler, by Jack Crenshaw. This is a very old article but it steps you through the process of writing a left-right parser in detail.

Converting this to an Assembler is straight forward once you understand how a two-pass assembler works.

On the first pass you are really just counting bytes and assigning labels their values. If you run into a label that has not yet been defined you assume it's value to be 0.

On the second pass you now know where all of those labels point to and you can do your real code generation. If you find an undefined label on this pass then you know it is an error.

by on (#78960)
Seems really good! will you support "Structs" sometime?

by on (#78963)
Sure, I can implement structs. Can you give me an example of the syntax? This is a feature I've never used in an assembler.

by on (#78968)
Something like:
.struct struct_players
    .res x NUM_PLAYERS
    .res y NUM_PLAYERS
    .res life NUM_PLAYERS

.res players struct_players

//Load player 3 x position
ldx #2
lda players.x, x

by on (#78970)
Oh cool! So you specify the data structure like in C, but then the assembler lays out the memory as a table like a traditional assembly programmer would.

I love this idea! This will make my project a heck of a lot easier to code. Right now I am doing things like this:

.res obj_posx_lo MAX_OBJ
.res obj_posx_hi MAX_OBJ

lda obj_posx_lo,x

Thanks for the idea! I'll get cracking on it.

by on (#78972)
You're welcome :)
Also I miss the .define .ifdef .ifndef .else labels (I readed there's a .define label but for segments) for conditional code.

.define pal

.ifdef pal
    .equ velocity 3
.ifdef ntsc
    .equ velocity 2

.ifdef debug
    .out "Debug release"
    .out "NonDebug release"

by on (#78973)
Wow, I completely forgot about conditional assembly. I'll change .define to .memory (it's more descriptive anyway) and add in the conditional assembly bits.

I need to restructure the assembler for all of these "skip this section if" conditions (like skip macro definitions if we are not in the macro pass). My current "for line in lines" approach is a little naive for all of this.

Thanks again for all of the inspiration! You keep reminding me of things I forgot I needed, or never thought of before.

by on (#78974)
I'm glad I can be of any help. If this assembler has all the features I have in NESHLA I'll try to switch to it (losing switch and while statements I guess)

Also on structs maybe it could be like:

.struct struct_players
    .res x 2
    .res y 2
    .res life

.res players struct_players NUM_PLAYERS

So it creates player X and Y as 2 * NUM_PLAYERS, this way you only use the number of objects once and every entry can have a different size.

by on (#78992)
Yea, that's what I assumed from your first post. Here's the example code I've written:

; Define a data structure
.datastruct object
   .res x      2   ; X position in pixels / 16
   .res y      2   ; Y position in pixels / 16
   .res vx      1   ; X velocity in pixels / 16
   .res vy      1   ; Y velocity in pixels / 16
   .res shape   1   ; Shape number

; Create a single object instance
.struct player object

; Create a table of objects
.struct game_objects object MAX_OBJECTS

; Define an initialized object instance
.data some_obj object x=0x0170, y=0x00a0, vx=48, vy=0, shape=7

; Define an initialized table of objects
.table object_templates object
   .data x=0x0170, y=0x00a0, vx=48, vy=0, shape=7
   .data x=0x0170, y=0x00a0, vx=96, vy=0, shape=4

These directives take care of an awful lot of problems I have when defining data for my game. I think this will make the whole process much smoother.

One thing I also like is that this will be easy for an external tool to parse the initialized data tables. For instance, I have a script that generates a tile set image based on definitions, graphics files and palette files. I could easily read the tile definitions in from the table definition.

Thanks for all of the inspiration! I now have a road map through version 0.05 :d

by on (#78995)
This is for discussion but it seemed it was easier to access data in "non interleaved" fashion, like:
instead of x,y,z,x,y,z,x,y,z

In interleaved you have:
ldx numplayer * sizeof(data)
lda data + offset(z), x
On non interleaved:
ldx numplayer * sizeof(z)
lda data.z, x

As struct sizes can be any number and variables inside a struct would usually be 1~4 bytes, seems that non-interleaved would be better.
(as stated on KNES C library recommendations)
Also you could access more than 256 bytes using only the x register (up to 256bytes per struct entry)

I don't know if it's clear what I'm saying...

by on (#78997)
What I am talking about is specifying the data in an interleaved fashion, but outputting it in a non-interleaved fashion.

So this code:

.table object_templates object
   .data x=0x0170, y=0x00a0, vx=48, vy=0, shape=7
   .data x=0x0170, y=0x00a0, vx=96, vy=0, shape=4

Would be equivalent to this code, but with an easier to understand syntax:

.scope object_templates
   x: .word 0x0170, 0x0170
   y: .word 0x00a0, 0x00a0
   vx: .byte 48, 96
   vy: 0, 0
   shape: 7, 4

This gives you the convenience of C-style structs and the runtime efficiency of tables.

by on (#78999)
Yeah, yeah, that's what I was talking about too :)

by on (#79093)
Updated to version 0.04 with conditional assembly support. Man, that was a major rewrite :D It will be a lot easier to implement new features now though.

The next thing I will work on is data structures. I want to use them when I port over my demo.

by on (#79114)
Cool, I'm waiting for that part to try to port over my nes framework to it.

by on (#79120)
Yea, definitely wait a bit. I have found and fixed a ton of bugs in version 0.04 that came from the rewrite. I am going to wait until I've ported over some of my own code with 0.05 before I release it so I can try to get some of the bugs out :D

by on (#79133)
I have realized that the whole data structure thing is a little mis-guided. It breaks if you allow fields to be larger than one byte. It is still very useful for that though. You just have to address your multi-byte values as individual bytes.

by on (#79135)
Updated to version 0.05 with support for data structures. I have ported my entire scrolling demo over to this assembler and fixed a lot of bugs in the process.

If anyone wants to try this out, now is the time. I am going to start working on my demo again using this assembler. Hopefully that will work out any more bugs that may be present.

by on (#79293)
Minor update, I added a syntax highlighting definition for notepad++ including folding.

by on (#80889)
Well I finally caught up on my documentation so I figured I'd go ahead and release version 0.7. Here's what's changed:

Version 0.7

* Macro parameter expressions are now scoped to the line invoking the macro, not the macro body
* Added offset and length parameters to the .incbin directive
* Added option to print the memory map to the screen following assembly
* Added support for custom character encodings
* Added the .encoding, .endencoding, .char, .range and .text directives
* Fixed a bug that created invalid addresses when forward-referencing zero-page labels
* Fixed a bug that prevented the use of labels within macros defined outside of a segment block
* Added .__size__ default labels for data structure definitions, objects, object arrays and data tables
* Added .__count__ default labels for objects, object arrays and data tables
* Added .__size__ and .__count__ default labels to all .res labels

Version 0.06

* Fixed missed requirement: macro bodies are scopes without an associated label address
* More useful error messages for bad named value pairs
* Fixed a crash bug that occurred when a macro parameter name masked the name of a label within the macro's parent scope
* Added the .error directive
* Added support for multiple symbols to the .ifdef and .ifndef directives

I am very happy with the progress made on the assembler. As I continue to use it for my projects I keep adding nice features and finding the bugs. Hopefully this will be ready to use by others soon :D