Project Ember

Homebrew Retro-Inspired 32-bit CPU And Video Game System

Public Chat
Similar projects worth following
Project Ember is an evolving long-term research project. The plan is to develop a homebrew CPU, GPU, and ultimately video game system and teaching tool for anyone interested in the intricacies of video game development software and hardware. Drawing inspiration from early home video game systems and computers like the Atari 2600, Colecovision, Nintendo NES, as well as early home computers such as the Atari 800, Commodore 64, and Amiga, Ember embraces the technology of the time, but with a nod to features of more modern game systems with a 32-bit architecture, larger memory space, and modern development tools including LLVM compiler integration and source-level debugger.

The idea for Project Ember came about while watching YouTube videos of all the awesome projects people were doing with breadboard-based computers using 6502 and 6800 CPUs, arcade emulators running in hardware on FPGAs, and old conference videos from Hackaday, GDC (Game Developer Conference), and many local hacker gatherings. The kicker was watching Ben Eater and his videos on 6502 and discrete circuit breadboard computers, timing circuits, and more. He explains things in a way that all the years of formal learning didn’t, this is where it finally clicked.

From there it was just a matter of learning everything there was to know about game and computer system design, programming, and implementation, both in software using emulation and in hardware. Once I felt I had a grasp on how I might accomplish the task, I began researching previous CPU and GPU designs (Though in our case retro ‘GPU’s are more like video chips than what we think of modern GPUs), learning the details of each and every home video game console and computer of the 1970s and 80s, as well as many of the arcade boards of the time. 

Armed with this newfound knowledge, I set out to design my first CPU. I made some initial executive decisions, like 32-bit, little-endian, load/store RISC architecture, etc. Some of these decisions, like 32-bit, are more for ease of development than historical accuracy, since programming in assembly on 8-bit CPUs was not done for fun back in the day, but because there was no other option for low-cost hardware. If the people programming for the Atari 2600 could have done so on a 32-bit CPU, I’m certain they would have as well! Once I had the basic instruction set designed, I created a very simple assembler and emulator for the CPU to test it out in theory.

The next logical step was to design a video chip. I decided to go with something very similar to the TI 9918 (as many of the systems of the time did) with various tiled display modes and some sprites. Also, since each display mode is a separate entity, I could just implement one at a time, the first being a 80x44 tiled text mode with a 16x16 character 1-bit font containing up to 256 tiles. The first half of the initial font tileset was text and special characters, leaving the second half for graphics characters and other things. Later, I added sprites and other modes which allowed for moving objects on the screen, color tilesets, and scrolling or rotating backgrounds.

With a basic CPU and GPU implemented in the emulator, I next added an interrupt controller and IO support. This allowed keyboard and gamepad input. Keyboard support meant I could write a basic monitor program to display memory values or run programs, basically allowing interactions with the machine when running in the emulator.

At this point, I had the basics needed to implement simple applications and games, albeit without sound. However, my simple assembler was not up to the task. After some additional research, I decided to implement a native back-end assembler for Ember32 assembly language in LLVM. There were other suites I could have used, like gcc, however, longer-term I wanted to be able to write higher-level language applications for the system. Not only C/C++, but also RUST, Swift, or any number of others. These languages are primarily supported on LLVM, so that is what I decided to use.

Integrating Ember into an LLVM backend was considerably more complicated than I imagined, based on what little documentation and information I managed to find on the web. What I thought would take a few weekends, ended up taking many weeks of hacking, with my day job and other distractions. However, I did manage to get it working and output Ember32 Elf files. 

The next step was to implement an ELF loader in the emulator, and also support for DWARF debugging info in the ELF. So now I can load and run LLVM-generated ELF executables in the Ember emulator, and use the runtime debugger to step through the source running on...

Read more »

  • FPGA Test Harness

    Toma day ago 0 comments

    In preparation for ALU development on the FPGA, I decided to hook up all the external pins of the Spartan Edge board to LEDs and switches so I can do at least a bit of debugging. Ultimately, once I have more of a working CPU, I can likely interact through the ESP32, I2C, or something. But for now, it will be more hands-on. First, though, I had to solder on the headers for various connections (using my brand new digital microscope!), as the Spartan Edge leaves those off initially as verious user options to install as needed.

    Soldering the Spartan Edge

    The Spartan Edge board has various connections to the Spartan-7 FPGA and the ESP32 onboard. Many of the IO pins from the Spartan-7 are laid out and configured with resistors to be used with an Arduino Uno, and a few are directly wired. We have 14 pins from the Arduino Digital IO headers, and 10 pins from the FPGA directly. Unfortunately, the Analog pins are instead wired directly to the ESP32, which is all fine, but since we're not using that right now it isn't helpful. 

    One very unhelpful note is that there are NO 3.3v pins at all (at least on any of these headers) to get VCC_3v3 from the board! I did some checking, and the ONLY place you can get regulated VCC_3v3 from the board is the two Grove connectors, which are intended to be used for I2C or whatever, and the JTAG connector which I need in order to program the FPGA. I didn't have any Grove connectors, so I'll need to wait for them to come in later this week. Unfortunately, the pins are too small to get female jumper wires to stay put.

    First Pass Debug Connector for Spartan Edge

    For now, I can at least set up the breadboard with some LEDs and switches. My first attempt was to just place the board directly on two short breadboards, which would be way cleaner, however, I soon realized that all the pins I need are on one side of the board, and if I run jumper wires to the breadboard, I can't use the top row of connections if the board pins are connected. So, on to plan B...

    Now I have the FPGA board plugged into the breadboard, mostly just to hold it in place, as I am not currently using any of the analog or signal pins from that bottom Arduino header. I then run ribbons of IO pins to the breadboard. Initially, I'm running the Arduino 0-13 pins as output driving the LEDs in sets of 8, 4, and 2, and the 10 FPGA pins to switches in 8 and 2. Here they are just tied to Ground, since I don't have the VCC side wired yet, so the switched inputs float when not grounded...resulting in noise, but I can at least see they are working.

    There are also some switches, two pushbuttons, and a few LEDs on the board if I need those. I figure I can use the pushbuttons to step through test cases.

    Next up, get some ALU functionality coded up on the FPGA so I can step through some logic on the test connections.

  • HDMI "Mode 0" Text Output functional on the Spartan Edge FPGA

    Tom01/16/2022 at 18:58 0 comments

    While I have been working on the emulator and assembler, I have also been playing around with the Seeed Spartan Edge FPGA. After watching an untold number of videos on You Tube, and reading through a few (unfortunately too few) GitHub projects, I have managed to implement an HDMI output example in Verilog for the Spartan 7 FPGA.

    Spartan Edge FPGA

    It currently only supports a single resolution at a time, set to 1280x720 for now. I have tentatively called "Mode 0" (the only one at this point really) as an 80x45 character text mode using a 16x16 bit ASCII font. For this test case, I store both the default VRAM contents (80x45 bytes) and the font (128x2x16...half the ASCII character set, plus 2 by 16 bytes for each character bitmap) in block RAM on the FPGA.

    FPGA HDMI Output
    The next step for the FPGA project is to create some code to write data to the dual-port block RAM, so the HDMI output can display text. I figure the first step for that is to work on the ALU and CPU pipeline implementation. That will at least allow some part of reading and writing to memory.

  • Display Output and Keyboard Interrupts Working in Emulator

    Tom01/15/2022 at 20:20 0 comments

    I now have the emulator back to the point I was before I started integrating the LLVM assembler and linker! This means when the emulator is running it outputs to a virtual 1280x720 text display (80x45 16x16 characters for now) with a very simple monitor program that can read keyboard input. The display and keyboard controller work through CPU interrupts.

    I will go into detail on how this works once I get my Medium Blog caught up. I'm still going through early design at this point at a very high level, but I will get way more detailed soon.

  • Quick Progress Update

    Tom01/10/2022 at 18:00 0 comments

    Been a while, thought I should just give a heads up. I have the emulator and LLVM assembler working now with nearly all the instructions, including branch and memory access. The debugger now supports stepping and breakpoints for supervisor-level code (think kernel/firmware)...need to add support now for user-level code (applications), then I can hook up the rendering and keyboard interrupts again (broken since the change from my hacky assembler days to LLVM)

    I have also been making progress with the FPGA. I worked over the weekend to get a simple Mode0 HDMI output working. I can now at least fill the screen with the letter 'A'! I know, sounds like not much, but for me it's a milestone. I now need to get the Block RAM VRAM working, issues with clock timing I need to work out...then I can output text on the HDMI screen attached to the FPGA. Next step will be to start coding the ALU for the CPU.

    Cheers, Tom

  • Emulator/Debugger now working with LLVM-produced *.elf files

    Tom12/23/2021 at 22:40 0 comments

    With a few days off for the holidays, I finally had some time to get the debugger working again. It now supports the common DWARF debugging standard segment that is contained in the ELF file written by LLVM-MC and LLD.

    I originally coded it for the simple assembler that I wrote for the CPU, but the system quickly outgrew that and I decided to implement a "real" assembler. I ended up going with LLVM-MC over a separate assembler like VASM so I could more easily implement higher-level languages at some point. That was painfully complex, but it is now working, at least for a small subset of the Ember instruction set.

    I can now set breakpoints, step through the code in the emulator, as well as view and edit registers and memory, directly in the emulator window. 

    Next, I need to finish up the instruction set ISA, add all the remaining instructions to the LLVM TableGen scripts, then update the emulator for the new instructions and I should be able to get working on some firmware/OS code. 

  • LLVM Assembler and Linker Functional

    Tom12/11/2021 at 18:59 0 comments

    It only took most of the summer and the fall...working a few hours here and there...but I finally have a working assembly path from Ember assembly files to a compiled elf file using LLVM-MC and LLD. Albeit with just a few instructions so far, like branches, load immediate, and a few ALU ops, basically enough to test the code and some encoding patterns. It's relatively straightforward to add more of the same instruction types to the TableGen scripts, so I can add more later as needed for simple test code. The most difficult part was handling the Fixups in the assembler and the equivalent Relocations in the linker. Such a pain! I will write up the details of that at some point in my Ember Blog.

    Ember Emulator Debugger
    Ember Emulator Debugger

    The next step is to implement ELF loading in the Ember CPU Emulator, then and integrate DWARF with the Debugger. My original Ember Assembler was fairly limited, and a complete hack. It was something I just threw together last year to get things up and working quickly and to allow me to test emulation and encoding. The project was quickly outgrowing its capabilities, which is why I started looking for a "real" assembler to integrate with. I ended up going with LLVM, maybe not the best choice for my first try, but I eventually got it working. The primary reason for going with LLVM is that I want to ultimately support high-level languages, especially Rust, which runs on llvm, along with other languages like Swift and C.

    Assembling Ember ASM

    To assemble asm files into native encoded Ember binary, I use llvm-mc, which is part of the llvm compilation chain, the part that normally converts llvm bytecode (generated from a high-level language like C or RUST) into native machine code. What you effectively do is convert your native instruction pneumonics into llvm bytecode instruction by instruction, then turn them into encoded native OpCodes.

    The following is an example disassembly output of the llvm-mc encoding of some asm code (Don't mind the actual instructions, since they clearly wouldn't run, they are just testing encoding). On the left are the instructions, on the right is the encoding, along with descriptions of the Fixups that are noted (the LDI instructions need addresses for the labels that will come from the Linker, and since the LDI instructions encode to two Opcodes [LDI+LDIH], the BRA instructions need updated target offsets after the file has been parsed).

    .set ZERO, 0
    .set MAX_UINT32, 4294967295
    .set MAX_UINT16, 65535
    .set MAX_INT16, 32767
    .set MIN_INT16, 32768   ;  Line comment
    .set ScanDelay, 4
            .globl  _start
            bra     _start                          ; encoding: [A,A,0b01AAAAAA,0x10]
                                            ;   fixup A - offset: 0, value: _start, kind: fixup_ember_branch
    testStuff                       ; encoding: [A,A,0b01AAAAAA,0x15]
                                            ;   fixup A - offset: 0, value: testStuff, kind: fixup_ember_branch
            ldih    r0,     $ffff                   ; encoding: [0xff,0xff,0x04,0x64]
            ldi     r0,     $1234                   ; encoding: [0x34,0x12,0x00,0x64]
            ldi     r1,     $ffffffff               ; encoding: [0xff,0xff,0x08,0x64]
            ldih    r1,     $ffffffff               ; encoding: [0xff,0xff,0x0c,0x64]
            ldi     r1,     $7fff                   ; encoding: [0xff,0x7f,0x08,0x64]
            ldi     r3,     $0                      ; encoding: [0x00,0x00,0x18,0x64]
            ldih    r3,     $4d2                    ; encoding: [0xd2,0x04,0x1c,0x64]
            ldi     r4,     systemInit              ; encoding: [A,A,0x20,0x64]
                                            ;   fixup A - offset: 0, value: systemInit, kind: fixup_ember_ldi_label_addr_lo
            ldih    r4,     systemInit              ; encoding: [A,A,0x24,0x64]
                                            ;   fixup A - offset: 0, value: systemInit, kind: fixup_ember_ldi_label_addr_hi
            ldi     r4,     _start                  ; encoding: [A,A,0x20,0x64]
                                            ;   fixup A - offset: 0, value: _start, kind: fixup_ember_ldi_label_addr_lo
            ldih    r4,     _start                  ; encoding: [A,A,0x24,0x64]
                                            ;   fixup A - offset: 0, value: _start, kind: fixup_ember_ldi_label_addr_hi
            ldis    r5,     $ffffffff               ; encoding: [0xff,0xff,0x29,0x64]
            ldis    r5,     $ffff                   ; encoding: [0xff,0xff,0x29,0x64]
            brl.eq  systemInit                      ; encoding: [A,A,0b11AAAAAA,0x14]
                                            ;   fixup A - offset: 0, value: systemInit, kind: fixup_ember_branch
            brl     r12                             ; encoding: [0x00,0x30,0x00,0x14]
            bra     testStuff                       ; encoding: [A,A,0...
    Read more »

  • Seeed Spartan Edge FPGA Up and Running

    Tom12/07/2021 at 04:25 0 comments

    I know I'm jumping ahead a bit, but I was so excited to get this up and running on the first try!

    Seed Spartan Edge with JTAG Programmer and HDMI Display

    I first tried one of the Vivado blink programs to test the whole compile and download to device process which worked fine. Then I found this excellent demo project from fellow hacker SmartPerson over on GitHub. He also has a great series of videos on YouTube detailing exactly where he got the source and modified it for this board. He did great work, as it ran on the FPGA first try. It's not a game, just displays some images, text, and sprites and moves them, but it demonstrates the use of Block RAM, the HDMI port, and interfacing a pin to the ESP32 chip on the board.

    Now I just need to get some time to hack on it, change some things up.

  • Getting Started

    Tom12/03/2021 at 03:02 0 comments

    Hi all, I am working to get some content up. My day job is keeping me busy, and in what time I have, I have been fighting with LLVM on the Ember assembler and linker, as well as fiddling with my new FPGA board. I am determined to learn Verilog, but mostly end up just fighting with JTAG and how to even set the thing up...what a pain!

    Anyway, in the meantime, you can check out some posts I'm working on going back to the beginning of the Ember CPU design. It might be too high level for some, but I wanted to get to the basics. If I overly simplify things, you can always just skip ahead, but I have found again and again, that when someone assumes everyone is on the same level, some will get lost...I've been that lost person enough times to know...

    So, my plan is to keep this site fairly low-level, with notes on my progress, links to code, design documents, etc. while going into much more detail on my Medium blog. You can find the first post here, and there will be more to come soon.

View all 8 project logs

Enjoy this project?



Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates