The Coal Console– System Design Layout: System Software and the Boot Refine


AI Generated image of Start button on a keyboard

In the Cinder layout, we now have a CPU , an easy GPU (or a minimum of a display screen cpu) , and a PIA (outer user interface adapter) , which is enough for an easy however functional computer system. Last time , we looked at the overall memory design of the proposed system, and now we require some assembly code to start the device and get things up and running.

In the complying with areas, we’ll walk through the Coal startup procedure, leaving some better information, such as the applications of the different tool motorists for the GPU and PIA, for later on. We will certainly cover those in detail in their own write-ups.

The Cinder Manager Setting Program

Keep in mind that the Ember CPU is relatively straightforward compared to contemporary CPUs, particularly regarding security and shielded setting procedure It has only two operational settings: Individual Setting and Manager Mode, and the difference is extra functional than protective. That is to claim, individual setting is where the user application or game program will run, while manager setting is largely there to take care of interrupts, preliminary bootup, and offer some fundamental system routines like interfacing with the graphics, sound, and IO chips.

When a Coal CPU is powered on or restarted, it will certainly be in supervisor mode with disrupts handicapped. It will certainly start filling guidelines from memory at address 0xFFFF 0000, the begin of the last 64 k web page in its 32 -bit address room. It makes no presumptions about the memory layout, the style of the system, or any kind of affixed gadgets on the bus.

In the Ember Console, we have actually arranged the memory map to locate a 64 k ROM containing the system firmware at that high web page area. This firmware code will boot up the system, established any type of attached equipment devices on the bus, and carry out the distinct code-based custom disrupt handling system that Ember sustains.

Once the system is booted up, we get in a loophole which will certainly initially go back to, or start, the application or video game that has actually been filled into RAM, and after that wake up any time an interrupt from an equipment gadget like key-board or GPU activates, or when the individual application calls a TRAP guideline to cause a system routine, such as contacting the display, or altering the GPU graphics mode.

LLVM

The Supervisor Program, or system boot firmware, is created in Coal assembly code using a customized Ash target executed in LLVM (Although some case it’s not an acronym, the majority of people assume it stands for Low Level Virtual Machine) LLVM is a popular collection and collection of tools that support the development of compilers, linkers, and assemblers that promote turning any kind of sustained top-level language, like C++ or Rust, right into binary code that can operate on many processors. For Coal, we will only use 2 devices, llvm-mc (assembler) and lld (linker) With plug-ins that sustain Ember setting up code, we can produce, put together, and link programs to work on our freshly made CPU. We could even include support for compiling high-level languages like Corrosion to the Ash CPU someday making use of the LLVM Intermediate Depiction (IR)

Cinder Setting Up Code

Although recognizing assembly language is not strictly required to comply with in addition to the ideas listed below, a fundamental understanding does not hurt, so feel free to do a bit of Google looking if you need a refresher. Though it’s a custom-made language, the Ash application within llvm-mc roughly follows the Intel phrase structure , with some differences, so you could begin there.

_ beginning

Starting our SupervisorProgram.asm setting up file, we make use of the include instruction to put one more documents, SystemDefines.asm , which defines all the constants that will be utilized in our code. For instance, we use IRQ_StartAddr rather than directly making use of the actual address worth of the begin of the IRQ memory register. This makes the code much more legible (all instructions start with the dot, or period/full quit personality)

Next off, the international _ start directive informs the linker which tag in the resulting executable data will certainly be the starting address. When linking the code, we will appoint the address 0xFFFF 0000 to this label on the linker command line.

The label _ beginning : followed by a colon personality defines a location in the code that can be called, or branched to, from various other areas in the code either before or after the meaning of that tag. In this case, because of the international _ start directive over, it likewise notes the really first direction to be performed when the maker powers on.

 ; *********************************************************************** 
; Include the system specifies
include "SystemDefines.asm"
; Declare as International so the linker can find the access factor
; and set it to $FFFF 0000 by default for Coal
global _ begin
; ***********************************************************************
; _ beginning label notes the entry factor upon Reset
_ start:
; System setup (initial init the hardware and established some things)
brl systemInit
; ***********************************************************************
; Begin IRG/Trap/Exception Handling Supervisor Loop
startSupervisorLoop:
rtu; Go/Return To Customer Setting up until an interrupt or exception occurs

The very first actual equipment instruction is brl systemInit , which suggests BR anch with L ink Register to the label systemInit , which is defined later in the documents. When brl is implemented by the CPU, the present COMPUTER or program counter register will certainly be saved in the LR , or web link register. After that the program counter register will be incremented to the address represented by the label systemInit This will, in effect, create the CPU to execute the following direction after the systemInit label.

systemInit

The first step in initializing the system is to set various signs up to proper default worths, after that disable and get rid of any kind of spurious disrupts that could have been signaled when the maker is powered up. Storing zero in both IRQ_Status and IRQ_Enable makes sure that we do not immediately return to supervisor setting once we’re done establishing until there is a real interrupt to handle.

In Ash setting up language, the first register, or left-most (when there are multiple signs up in a direction) , is constantly the location register, which is where the outcome of the operation is kept. Thus, in the procedure mov r 1, no , the worth 0 is relocated into the r 1 register.

Next off, we pack the default pile addresses into the manager and individual SP , or pile pointer signs up, after that push the manager SP onto the stack, so we can recover it later on after calling different subroutines to boot up any type of equipment that is mounted.

 ; *********************************************************************** 
; Boot up the system
systemInit:
; ***********************************************************
; Disable/Clear disrupts
ldi r 13, IRQ_BaseAddr
st (r 13 +IRQ_Status), zero
st (r 13 +IRQ_Enable), zero
; Notification () over stands for a memory address, so the ST direction
; is saving absolutely no in the memory address indicated by the worth of
; r 13 plus the countered IRQ_Enable in bytes. In C, this could be written:
; struct IRQ {
; unsigned int IRQ_Status;
; unsigned int IRQ_Enable;
;};
; ***********************************************************
; Clear registers and established defaults
mov ur 1, absolutely no
mov ur 2, absolutely no
mov ur 3, no
mov ur 4, absolutely no
mov ur 5, zero
mov ur 6, absolutely no
mov ur 7, zero
mov ur 8, absolutely no
mov ur 9, absolutely no
mov ur 10, zero
mov ur 11, absolutely no
mov ur 12, zero
mov ur 13, no
mov r 1, no
mov r 2, no
mov r 3, absolutely no
mov r 4, absolutely no
mov r 5, absolutely no
mov r 6, absolutely no
mov r 7, absolutely no
mov r 8, zero
mov r 9, zero
mov r 10, zero
mov r 11, zero
mov r 12, absolutely no
mov r 13, zero
ldi sp, SUPERVISOR_StackStartAddr
ldi r 13, USER_StackStartAddr
mov usp, r 13
; Conserve existing LR to the pile, as we're mosting likely to call subroutines listed below
push lr

Proceeding below, the initial loop fills the CATCH table with reminders to a default trainer. The CATCH table is a selection of 256 32 -little bit guidelines, each indicating a trainer routine. When a customer application wants to call one of these regimens, it establishes the low 8 littles the r 1 register to the index of the wanted regimen in the table, after that in addition establishes various other signs up to worths standing for specifications to those routines as needed, and ultimately implements a trap guideline, which will create an interrupt. The supervisor trainer then calls the routine, eventually returning control to the user program, with any return values conserved back in those signs up.

Next off, one more loophole additionally fills the IRQ Table with reminders to a default trainer. Just like with the TRAP table, these 32 access stand for a handler for each of the possible interrupt bits in the IRQ status register.

Filling the tables with a default trainer will guarantee any kind of interrupt or trap, even if it just returns and removes the flags, will not collapse the system. Notice below that the default handlers are just an rtl direction, which returns to the caller using the LR register. Any systems booted up later, consisting of the user application, can include their trainers as required by changing the default trainer tip in the table.

 ; *********************************************************** 
; Set default Catch table array (Just established all to a default handler)
ldi r 1, # 63
ldi r 13, TRAP_TableStartAddr
ldi r 12, defaultTrapHandler
loopTrapTableInit:
st (r 13, r 12; create the tip to 4 access at a time
st (r 13 + 4, r 12
st (r 13 + 8, r 12
st (r 13 + 12, r 12
below r 1, r 1, # 1
bra.c doneTrapTableInit
include r 13, r 13, # 16
bra loopTrapTableInit
doneTrapTableInit:
; ***********************************************************
; Establish default IRQ table selection (Simply established all to a default handler)
ldi r 1, # 7
ldi r 13, IRQ_TableStartAddr
ldi r 12, defaultIRQHandler
loopIRQTableInit:
st (r 13, r 12; create the guideline to 4 access each time
st (r 13 + 4, r 12
st (r 13 + 8, r 12
st (r 13 + 12, r 12
sub r 1, r 1, # 1
bra.c InitGPU_Mode0
add r 13, r 13, # 16
bra loopIRQTableInit
EnablePIA:
; TODO: Apply PIA initialization and trainer
InitGPU_Mode0:
; TODO: Implement console screen application
InitConsole:
; TODO: Execute console display application
EnableInterrupts:
; Enable (Currently Supported) disrupts
ldi r 2, IRQ_EnableInterrupts
ldi r 1, IRQ_BaseAddr
st (r 1 +IRQ_Enable), r 2
ret; pop lr from the pile, after that return to that address
; Simply return by default (we'll had these with time)
defaultTrapHandler:
rtl
; Simply return by default (we'll had these with time, VBlank, PIA, etc)
defaultIRQHandler:
rtl

Adhering to the IRQ and Trap loopholes, we can call any kind of hardware initialization routines. We might initially initialize the PIA, so we can take care of keyboard, computer mouse, or gamepad input. Then, initialize the GPU and turn on a default Present Setting, which could present a splash screen. Next, we might boot up a terminal program if this is an advancement console. In the future, we can also initialize a network adapter, audio chip, or various other equipment that could be existing.

Managing Disrupts

Now we get to the interesting part! When an interrupt happens, such as a secret is pushed, a system timer gets to no, or the application calls a catch guideline, the G , or Global Interrupt Enable bit in the CC register is gotten rid of. The CPU immediately switches to manager mode and begins carrying out code, generally beginning right after the last rtu direction, before it switched over to customer mode. It is after that the work of the manager code to take care of the interrupt and return to customer mode to proceed running the program where it ended.

As we gone over formerly , unlike numerous conventional CPUs, which have an inflexible and pre-defined interrupt habits, Cinder interrupts are completely programmable, and actually requirement be dealt with by the manager program in some fashion. Cinder specifies some systems whereby interrupts can be applied, yet the actual actions is left totally as much as the system designer.

As a matter of fact, while the Ember CPU defines three usual sorts of disrupts: IRQs, Catches, and Exceptions, all 3 are managed identically regarding the CPU is concerned. First, the G bit is cleared, changing the CPU to manager setting, after that, depending upon the source of the interrupt, the T , X , or D bit is established. If no added little bits are established, the interrupt was an IRQ created by an outside physical device in the system, such as the GPU or PIA. If the T bit is established, the interrupt was a catch caused by user code executing a catch guideline. If the D, X, or any other bits are established, the interrupt was a divide-by-zero, prohibited guideline, or other exception produced in individual code.

IRQ

Considering That the Cinder CPU has just one physical interrupt pin to take care of all feasible exterior disrupts. We will make use of an external interrupt controller in the Ash Console. This allows us to define custom actions when various equipment in the system intends to interrupt the CPU for details instant jobs like attracting to the screen or reviewing a vital continue the keyboard.

To implement behaviors like showing which gadget or interrupt is happening, or making it possible for and disabling certain disrupts, we will map a couple of signs up right into the memory address room of the equipment. We won’t go too deep right into the application details of this hardware here, but you can think about them as some external little bit of logic on the virtual motherboard that triggers the IRQ pin on the Ember CPU when it detects an interrupt demand from a particular device, like the GPU, PIA, Sound, etc. After that subjects additional details of the interrupt to the CPU via memory addresses.

There are currently 2 registers specified: IRQ_Status and IRQ_Enable Each register is 32 little bits in dimension. Each little bit in these registers is hard-wired to a particular interrupt resource, with the lowest little bit being the highest top priority. This allows a total of 32 one-of-a-kind disrupts to be defined. Little bits set to 1 in the IRQ_Status register indicate that a specific interrupt is triggered, and matching 1 little bits in the IRQ_Enable register indicate that the interrupt is made it possible for. Therefore, to permit a particular interrupt to happen and cause the CPU, the matching bit in IRQ_Enable first requires to be set to 1

For this to function, the supervisor code dealing with disrupts need to first check the individual CC little bits, after that checked out the IRQ_Status register, inspecting each bit to figure out which disrupt trainers need to be called. The code snipit below programs this procedure thoroughly.

 ; ********************************************************************************************************************* 
; First look for IRQ Interrupt(s)
; Trainers must not modify R 1, R 2, R 3, or R 12, R 13
handleIRQ:
ldi r 13, IRQ_StartAddr; Start address of IRQ Signs up
ld r 3, (r 13 +IRQ_Enable); Read IRQ Enable bits from memory-mapped register
st (r 13 +IRQ_Enable), zero; Briefly disable all brand-new disrupts while we function (initial worth kept in r 3
ld r 2, (r 13 +IRQ_Status); Check out any kind of active IRQ Standing Little bits from memory-mapped register
and r 2, r 2, r 3; Mask out handicapped IRQs
bra.ge noIRQ; If bit 31 on both Enable & & Standing Registers are SET, we contend the very least one signalled IRQ ... or else we're done and can early out
; We contend the very least one active IRQ
lsl r 2, r 2, # 1; Clear the high/global bit by moving to the left,
lsr r 2, r 2, # 1; then back, so we simply have the IRQ little bits
mov r 1, absolutely no; R 1 will contain the IRQ index made use of to seek out the trainer address
lookingForIRQ:
tst r 2, # 1; Begin shifting little bits to the ideal
bra.ne foundIRQ; until we discover a 1 in bit 0
checkForNext:
lsr r 2, r 2, # 1
bra.eq doneIRQ; If there disappear 1 little bits, we're done
add r 1, r 1, # 1
bra lookingForIRQ; Or else, check the following little bit
foundIRQ:
lsl r 4, r 1, # 2; R 1 includes the IRQ handler index * 4 for array of straightened 4 -byte addresses
ld r 12, (r 4 +IRQ_TableStartAddr)
brl r 12; Branch with Link to the handler address
bra checkForNext; Check the following bit
doneIRQ:
st (r 13, no; Clear the Status Word memory register (This will additionally signal any outside tools that an interrupt was managed)
st (r 13 +IRQ_Enable), r 3; Reenable interrupts
bra startSupervisorLoop; If there are no more 1 bits, we're done
noIRQ:
st (r 13 +IRQ_Enable), r 3; Reenable interrupts

Trap

Unlike IRQ, the catch mechanism does not require any type of exterior hardware; it is completely software-defined, apart from the behavior of the two instructions catch and rtu In individual setting, the trap instruction right away switches the CPU to supervisor mode and establishes the T flag in the user CC register. In a similar way, the rtu guideline immediately sets the G bit and returns the CPU to individual mode. In both situations, the CPU continues performing instructions exactly where it left off in the other mode. Note that calling trap in manager mode, or calling rtu in user mode, will certainly have no result, they are treated like nop instructions.

 ; ********************************************************************************************************************* 
; Look for customer code TRAP calls
; User register ur 1 ought to contain the preferred handler index
handleUserTrap:
mov r 13, ucc; Read the individual Standing Register
tst r 13, CC_Trap; Look For the T (User Catch/ System Telephone call) little bit
bra.eq handleIllegal; Otherwise established, then we should be here for one more interrupt kind ...
ldi r 12, CC_Trap_Clear
and r 13, r 13, r 12
mov ucc, r 13; Clear the Trap bit
mov.b r 12, ur 1; Expect low 8 bits of uR 1 to consist of jump table handler index
lsl r 12, r 12, # 2; multiply by 4 to get address countered
ld r 11, (r 12 +TRAP_TableStartAddr)
brl r 11; Branch with Link to the trainer address
bra startSupervisorLoop

Similar to IRQs, nonetheless, we reach specify the actions when a catch instruction is carried out in customer setting. When we enter manager setting and identify that a trap occurred, we take a look at the value in the customer r 1 register. It ought to have an 8 -little bit value, which is an index right into a table of guidelines to handlers for numerous traps, or system calls. The system and the application can specify these handlers by setting a pointer in the table. When the handler is called, we clear the T bit, return to individual code, and wait for the following interrupt.

 ; ********************************************************************************************************************* 
; Check for prohibited direction exemption
handleIllegal:
tst r 13, CC_Illegal; Check for the X (Prohibited Instruction) little bit
bra.eq handleDivZero; If not established, after that we must be here for one more interrupt type ...
; Simply clear the flag and return for now
ldi r 12, CC_Illegal_Clear
and r 13, r 13, r 12
mov ucc, r 13; Clear the unlawful direction bit
bra startSupervisorLoop
; *********************************************************************************************************************
; Look for divide by zero exception (if sustained)
handleDivZero:
tst r 1, CC_DivZero; D - Divide By No
bra.eq startSupervisorLoop
; Just clear the flag and return for now
ldi r 12, CC_DivZero_Clear
and r 13, r 13, r 12
mov ucc, r 13; Clear the Div No bit
bra startSupervisorLoop

Exceptions

The 3rd kind of interrupt is an exception. An exception is an interrupt triggered by a mistake or breakdown of a guideline in customer code. The divide by absolutely no exemption is pretty obvious; a 0 was encountered in the divisor of a divide instruction (if supported) An additional is an unlawful instruction exception, which can be brought on by numerous points, though most commonly as a result of a malformed opcode, which can not be carried out by the CPU. That might be that it was a scheduled opcode, in need of support settings little bits in the direction, and so on.

Overview

To recap, the Ember CPU will begin implementing guidelines at address 0xFFFF 0000 when reset or powered on. We put our start-up firmware code in ROM at that area and initialize the system. This includes setup default values for registers, consisting of the manager and individual mode stack pointers, and booting up disrupts. Next off, we fill the IRQ and TRAP tables with tips to a default handler, and after that initialize the different hardware in the system, consisting of the GPU and PIA. Lastly, we go into a loop that will certainly deal with all interrupts, catches, and exceptions that may happen while carrying out customer code and start the user-mode application.

With the equipment and equipment initialized, we go into the interrupt trainer regimen, which begins with an rtu instruction, changing the CPU to user mode and starting the customer application at the RAM address we established throughout initialization 0x 00010000 Any time an interrupt of any kind occurs, the CPU changes back to supervisor mode and carries out the following guideline after rtu , which will certainly be our collection of handlers.

First, we look for an IRQ, by checking out the worth of IRQ_Status If any kind of little bits are established and enabled by OR ing with IRQ_Enable , we start calling any kind of handlers starting with the lowest little bit and working our method up till no more little bits are set. If no IRQ little bits are established, then avoid to the catch handler.

For user-mode catches, we inspect the T bit in the customer CC register. If it is set, we order the user r 1 register and utilize that as an index to call the proper handler. If the T bit is not established, we then try to find exemption flags.

In a similar way, if any exemption flags are set in the user CC register, like D or X , we call those handlers, after that clear all interrupt flags and signs up before returning to the rtu direction to wait on the next interrupt.

System Equipment Initialization

In the following series of write-ups, we will search in detail at the initialization and handlers for the GPU and PIA, then begin to compose a system display program, an extremely easy DOS-like command line program that can be beneficial in debugging and checking the device throughout growth.

Next messages in this collection:

  • The Flame GPU– Initial Design Component 3: Initializing the GPU [Coming Soon!]
  • The Ash PIA– Initial Design Component 2: Keyboard Input Handling [Coming Soon!]
  • The Ash Console– System Design Style: Implementing a System Screen Application [Coming Soon!]

Ash Design Collection

Back to the beginning of Task Ember:

Going Traditional: Creating A Custom Homebrew Retro Video Game Console From Scratch

https://buymeacoffee.com/emberproject

https://buymeacoffee.com/emberproject

Enjoying my material? Assistance me by getting me a coffee, clapping for this post, and following my web page. Thanks so much, and remain safe!

Resource link

Leave a Reply

Your email address will not be published. Required fields are marked *