top of page

The Ember Console — System Architecture Design: System Software and the Boot Process

  • Writer: Tom Gambill
    Tom Gambill
  • May 28
  • 3 min read
Start button on a keyboard]

In the Ember design, we now have a CPU, a simple GPU (or at least a display processor), and a PIA (peripheral interface adapter), which is sufficient for a basic yet operational computer. Previously, we looked at the overall memory layout of the proposed system, and now we require some assembly code to initiate the machine and get it up and running.


In the following sections, we’ll walk through the Ember startup process, leaving some finer details, such as the implementations of the various device drivers for the GPU and PIA, for later. We will cover those in detail in their own articles.


The Ember Supervisor Mode Program

Remember that the Ember CPU is relatively simple compared to modern CPUs, particularly in terms of security and protected mode operation. It has only two operational modes: User Mode and Supervisor Mode, and the distinction is more operational than protective. That is to say, user mode is where the user application or game program runs, while supervisor mode is primarily there to handle interrupts, initial bootup, and provide basic system routines, such as interfacing with graphics, sound, and I/O chips.


When an Ember CPU is powered on or restarted, it will be in supervisor mode with interrupts disabled. It will begin loading instructions from memory at address 0xFFFF0000, the start of the last 64k page in its 32-bit address space. It makes no assumptions about the memory layout, the system's architecture, or any attached devices on the bus.


In the Ember Console, we have arranged the memory map to locate a 64k ROM containing the system firmware at that high page location. This firmware code initializes the system, sets up any attached hardware devices on the bus, and implements the unique code-based custom interrupt handling system that Ember supports.


Once the system is initialized, we enter a loop which will first return to, or start, the application or game that has been loaded into RAM, and then wake up any time an interrupt from a hardware device like keyboard or GPU triggers, or when the user application calls a TRAP instruction to trigger a system routine, such as writing to the screen, or changing the GPU graphics mode.


LLVM

The Supervisor Program, also known as system boot firmware, is written in Ember assembly code using a custom Ember target implemented in LLVM (although some claim it’s not an acronym, most people assume it stands for Low-Level Virtual Machine). LLVM is a popular library and collection of tools that support the creation of compilers, linkers, and assemblers, facilitating the conversion of any supported high-level language, such as C++ or Rust, into binary code that can run on numerous processors. For Ember, we will only use two tools, llvm-mc (assembler) and lld (linker). With plug-ins that support Ember assembly code, we can create, assemble, and link programs to run on our newly designed CPU. We could even add support for compiling high-level languages like Rust to the Ember CPU someday using the LLVM Intermediate Representation (IR).


Ember Assembly Code

Although understanding assembly language is not strictly necessary to follow along with the concepts below, a basic understanding doesn’t hurt, so feel free to do a bit of Google searching if you need a refresher. Though it’s a custom language, the Ember implementation within llvm-mc roughly follows the Intel syntax, with some differences, so you might start there.


_start

Starting our SupervisorProgram.asm assembly file, we use the .include directive to insert another file, SystemDefines.asm, which defines all the constants that will be used in our code. For example, we use IRQ_StartAddr instead of directly using the actual address value of the start of the IRQ memory register. This makes the code more readable (all directives begin with the dot, or period/full stop character).


Next, the .global _start directive tells the linker which label in the resulting executable file will be the starting address. When linking the code, we will assign the address 0xFFFF0000 to this label on the linker command line.


The label _start: followed by a colon character defines a location in the code that can be called, or branched to, from other locations in the code either before or after the definition of that label. In this case, because of the .global _start directive above, it also marks the very first instruction to be executed when the machine powers on.




Comments


Post: Blog2_Post
  • Mastodon
  • Facebook
  • LinkedIn

©2021 by IARI Ventures

bottom of page