A compiler and framework for creating Atari 2600 games using C#. It uses the .NET Compiler Platform (Roslyn) to compile C# files, and Mono.Cecil to compile the resulting CIL into 6502 assembler macros.
The first iteration of this project compiled C# directly to 6502 assembly.
The second iteration of this project compiled CIL directly to 6502 assembly.
The third (and current) iteration of this project instead compiles CIL to custom macros for the 6502 assembler. This offers a higher level of abstraction to compile to, and makes it easier to optimize the results. We're also now using 6502.Net as the assembler instead of DASM, so every part of the compiler is running on .NET.
The ultimate goal is to add all the features needed for me to port my attempt at a 2600 game to C#. Along the way, I'd like to support as many useful C# features as possible.
Performance won't be as good as programs written in 6502 assembly. But my hope is that the convenience of being able to use C# will be a worthwhile tradeoff for simpler games.
Progress will likely be slow since I have other personal projects I'm also working on.
The samples that currently exist are largely just for developing and testing specific features. The compiler's feature set is not stable yet, so any major samples would likely become obsolete.
Below is an example of how you could, at the time of writing, write a program using the StandardTemplate
to draw ascending then descending color values.
using VCSFramework;
using VCSFramework.Templates.Standard;
using static VCSFramework.Registers;
namespace Samples
{
[TemplatedProgram(typeof(StandardTemplate))]
public static class StandardTemplateSample
{
private static byte BackgroundColor;
[VBlank]
public static void ResetBackgroundColor() => BackgroundColor = 0;
[Kernel(KernelType.EveryScanline)]
[KernelScanlineRange(192, 96)]
public static void KernelAscend()
{
ColuBk = BackgroundColor;
BackgroundColor++;
}
[Kernel(KernelType.EveryScanline)]
[KernelScanlineRange(96, 0)]
public static void KernelDescend()
{
ColuBk = BackgroundColor;
BackgroundColor--;
}
}
}
This produces the following output:
An incomplete list of supported features in no particular order. Instructions may have various limitations, which I'll try to note.
- ⭕ Primitive Types
- ✔️
bool
- ✔️
byte
- ❌
sbyte
- ❌
ushort
- ❌
short
- ✔️
- ❌ Array types
- ✔️ Pointer Types
- ⭕ Custom Types
- ✔️ Value Types
- ✔️ Single-byte types
- ⭕ Multi-byte types (Not supported by all instructions yet)
- ✔️ Composite types (struct-in-struct)
- ✔️ Generic types
- ⭕ Reference Types
- ✔️ Static types
- ❌ Instance types (Probably never)
- ⭕ Static Members
- ✔️ Fields
- ❌ Properties
- ✔️ Methods
- ⭕ Instance Members
- ✔️ Fields
- ❌ Properties
- ✔️ Methods
- ✔️ Value Types
- ⭕ Ease of Development
- ⭕ Inline Assembly
- ✔️ Static field aliasing
- ⭕ ROM Data Access
- ⭕
RomData<>
struct- ✔️ Constant indexing of types >8-bit in size
- ❌ Non-constant indexing of types >8-bit in size
- ❌
foreach
support
- ⭕
- ⭕ Program Templates
- ✔️
RawTemplate
- ⭕
StandardTemplate
- ✔️ VBlank callback
- ✔️ Overscan callback
- ⭕ Kernel callback
- ✔️
Manual
- ✔️
EveryScanline
- ⭕
EveryEvenNumberScanline
/EveryOddNumberScanline
- ✔️
- ✔️ Kernel scanline range
- ❌ Screen switching support
- ✔️
- ⭕ Inline Assembly
- ⭕ CIL OpCodes
- ⭕ Arithmetic
- ⭕ Addition (
add
, 8-bit only) - ⭕ Subtraction (
sub
, 8-bit only) - ❌ Division
- ❌ Multiplication
- ⭕ Addition (
- ⭕ Bitwise
- ⭕ Or (
or
) (Operands must be same type and 8-bit) - ⭕ Negate (
neg
) (Operand must be 8-bit)
- ⭕ Or (
- ⭕ Branching
- ✔️ Branch if true (
brtrue
,brtrue.s
) - ✔️ Branch if false (
brfalse
,brfalse.s
) - ✔️ Unconditional branch (
br
,br.s
) - ✔️ Branch if less than (
blt
,blt.s
)
- ✔️ Branch if true (
- ⭕ Comparison
- ⭕ Equal (
ceq
) (Operands must be same type and 8-bit) - ⭕ Less than (
clt
) (Operands must be same type and 8-bit) - ❌ Greater than (
cgt.un
)
- ⭕ Equal (
- ⭕ Load
- ✔️ Argument (
ldarg
,ldarg.s
,ldarg.0
,ldarg.1
,ldarg.2
,ldarg.3
) - ✔️ Constant (
ldc.i4
,ldc.i4.s
,ldc.i4.0
,ldc.i4.1
,ldc.i4.2
,ldc.i4.3
,ldc.i4.4
,ldc.i4.5
,ldc.i4.6
,ldc.i4.7
,ldc.i4.8
) (up to 16-bit) - ❌ Element
- ✔️ Field (static) (
ldsfld
)- ✔️ Address (
ldsflda
)
- ✔️ Address (
- ⭕ Field (instance) (
ldfld
) (8-bit only)- ⭕ Address (
ldflda
) (Zero-page pointers only)
- ⭕ Address (
- ⭕ Indirect (
ldind.u1
) - ✔️ Local (
ldloc
,ldloc.s
,ldloc.0
,ldloc.1
,ldloc.2
,ldloc.3
)- ✔️ Address (
ldloca
)
- ✔️ Address (
- ✔️ Object (
ldobj
)
- ✔️ Argument (
- ⭕ Store
- ❌ Argument (
starg
,starg.s
) - ❌ Element
- ✔️ Field (static) (
stsfld
) - ✔️ Field (instance) (
stfld
) - ⭕ Indirect (
stind.u1
) - ✔️ Local (
stloc
,stloc.s
,stloc.0
,stloc.1
,stloc.2
,stloc.3
) - ❌ Object (
stobj
)
- ❌ Argument (
- ⭕ Miscellaneous
- ✔️ Call Method (
call
) - ⭕ Convert (
conv.i
,conv.u
,conv.u1
) (treated as NOPs, no extension toint32
) - ⭕ Duplicate (
dup
) (8-bit only) - ✔️ Initialize value type (
initobj
) - ⭕ Load String (
ldstr
) (Only supported for very specific scenarios, not general usage) - ✔️ NOP (
nop
) (Nothing emitted) - ✔️ Pop Stack (
pop
) - ✔️ Return (
ret
) (Pending function rework)
- ✔️ Call Method (
- ⭕ Arithmetic
Load the solution into Visual Studio Community 2019 and it should build and run fine.
Invoke VCSCompiler.exe --help
for current usage documentation. Documentation at the time of writing:
VCSCompilerCLI:
A compiler that compiles C# source code into a VCS (Atari 2600) binary.
Usage:
VCSCompilerCLI [options] [<arguments>...]
Arguments:
<arguments> A list of C# source files to compile.
Options:
--output-path <output-path> The path to save the compiled binary to. The same path with a different extension will be used for related files. If a path is not provided, temp files will be used. [default: ]
--emulator-path <emulator-path> Path of the emulator executable. If provided, it will be launched with the path to the output binary passed as an argument. [default: ]
--text-editor-path <text-editor-path> Path of the text editor executable. If provided, it will be launched with the path to the output ASM file passed as an argument. [default: ]
--disable-optimizations True to disable optimizations. Main use is to observe output of primitive VIL macros and stack operations. Unoptimized code generally will not run correctly due to excessive cycles consumed. [default: False]
--source-annotations <Both|CIL|CSharp|None> Whether to include C#, CIL, neither, or both source lines as comments above the VIL macros that they were compiled to. [default: CSharp]
--version Show version information
-?, -h, --help Show help and usage information
For running the binary, I recommend using Stella.
This project is licensed under the MIT License.