boot‐room - retrotruestory/M1DEV GitHub Wiki

Magic-1 Architecture Key Insights

From the documentation, I now understand that:

  1. Boot Process:

    • Starts at 0x0000 in supervisor mode with paging disabled
    • Memory map with paging disabled: 64K device space with overlapping ROM/RAM
    • First 32K (0x0000-0x3FFF): Overlapping EPROM and SRAM (selectable via switch)
    • 0x4000-0x7FFF: Always SRAM
    • 0xFF80-0xFFFF: Memory-mapped I/O devices
  2. Memory-Mapped I/O:

    • 0xFFF0-0xFFFF: UART #0
    • 0xFFE0-0xFFEF: UART #1
    • 0xFFD0-0xFFDF: Real-time clock
    • 0xFFC0-0xFFCF: 2-digit hex display
    • 0xFFB0-0xFFBF: IDE interface
    • 0xFFA0-0xFFAF: Front panel switch block
  3. Booting Sequence:

    • Bootloader loads and initializes paging
    • Then loads "Milo" (Minix Loader) or monitor
    • Milo loads Minix from disk

Boot ROM Implementation

Let's now implement the boot ROM to correctly initialize the system and start the boot process:

#ifndef BOOT_ROM_H
#define BOOT_ROM_H

#include <cstdint>
#include <string>
#include <vector>
#include <memory>

class Memory;
class CPU;

class BootROM {
public:
    BootROM(std::shared_ptr<Memory> memory, std::shared_ptr<CPU> cpu);
    ~BootROM();
    
    // Load the boot ROM into memory
    bool loadROM(const std::string& filename);
    
    // Generate a simple boot ROM for testing
    bool generateTestROM();
    
private:
    std::shared_ptr<Memory> memory;
    std::shared_ptr<CPU> cpu;
    std::vector<uint8_t> romData;
    
    // Internal helper methods
    bool loadROMFromFile(const std::string& filename);
    void createSimpleBootLoader();
};

#endif // BOOT_ROM_H
#include "boot_rom.h"
#include "memory.h"
#include "cpu.h"
#include <fstream>
#include <iostream>

BootROM::BootROM(std::shared_ptr<Memory> memory, std::shared_ptr<CPU> cpu)
    : memory(memory), cpu(cpu) {
}

BootROM::~BootROM() {
}

bool BootROM::loadROM(const std::string& filename) {
    return loadROMFromFile(filename);
}

bool BootROM::generateTestROM() {
    createSimpleBootLoader();
    
    // Load the test ROM into memory at address 0x0000
    if (memory) {
        for (size_t i = 0; i < romData.size(); ++i) {
            memory->write8(static_cast<uint16_t>(i), romData[i]);
        }
        return true;
    }
    return false;
}

bool BootROM::loadROMFromFile(const std::string& filename) {
    std::ifstream file(filename, std::ios::binary);
    if (!file) {
        std::cerr << "Failed to open ROM file: " << filename << std::endl;
        return false;
    }
    
    // Get the file size
    file.seekg(0, std::ios::end);
    std::streamsize size = file.tellg();
    file.seekg(0, std::ios::beg);
    
    // Resize the ROM data buffer
    romData.resize(size);
    
    // Read the file
    if (!file.read(reinterpret_cast<char*>(romData.data()), size)) {
        std::cerr << "Failed to read ROM file: " << filename << std::endl;
        return false;
    }
    
    // Load the ROM into memory at address 0x0000
    if (memory) {
        for (size_t i = 0; i < romData.size(); ++i) {
            if (i < 0x4000) { // Only load into ROM area (0x0000-0x3FFF)
                memory->write8(static_cast<uint16_t>(i), romData[i]);
            }
        }
        return true;
    }
    
    return false;
}

void BootROM::createSimpleBootLoader() {
    // Create a minimal boot sequence that initializes the hardware 
    // and reads sector 2 from the disk to find the image table
    
    // Based on the documentation, replicating basic bootloader behavior
    romData = {
        // Interrupt vector table
        0x8B, 0x20,       // sbr +32         ; Branch over interrupt vector
        0x00, 0x00,       // defw 0x0000     ; IRQ5 (RTC)
        0x00, 0x00,       // defw 0x0000     ; IRQ4 (unassigned)
        0x00, 0x00,       // defw 0x0000     ; IRQ3 (UART0)
        0x00, 0x00,       // defw 0x0000     ; IRQ2 (UART1)
        0x00, 0x00,       // defw 0x0000     ; IRQ1 (IDE)
        0x00, 0x00,       // defw 0x0000     ; IRQ0 (unassigned)
        0x00, 0x00,       // defw 0x0000     ; DMA req
        0x00, 0x00,       // defw 0x0000     ; SYSCALL
        0x00, 0x00,       // defw 0x0000     ; 0x9
        0x00, 0x00,       // defw 0x0000     ; Overflow trap
        0x00, 0x00,       // defw 0x0000     ; Privilege violation
        0x00, 0x00,       // defw 0x0000     ; Breakpoint
        0x00, 0x00,       // defw 0x0000     ; Non-writeable page
        0x00, 0x00,       // defw 0x0000     ; Page not present
        0x00, 0x00,       // defw 0x0000     ; 0xF
        
        // Initialization code
        0x90, 0x00, 0x00, // ld.16 a,0x0000  ; Clear A
        0xD0,             // copy msw,a      ; Set MSW (interrupts off, system mode)
        0x90, 0x80, 0x00, // ld.16 a,0x8000  ; Stack address
        0xD1,             // copy sp,a       ; Set stack pointer
        0x90, 0x00, 0x00, // ld.16 a,0x0000  ; Data pointer base
        0xD2,             // copy dp,a       ; Set data pointer
        
        // Initialize UART
        0x90, 0xFF, 0xF0, // ld.16 a,0xFFF0  ; UART0 base address
        0x91, 0x80,       // ld.8 a,0x80     ; DLAB=1
        0xD7, 0x03,       // st.8 a,(0x03)   ; Set DLAB in LCR
        0x91, 0x0C,       // ld.8 a,0x0C     ; Divisor for 9600 baud
        0xD7, 0x00,       // st.8 a,(0x00)   ; Set divisor LSB
        0x91, 0x00,       // ld.8 a,0x00
        0xD7, 0x01,       // st.8 a,(0x01)   ; Set divisor MSB
        0x91, 0x03,       // ld.8 a,0x03     ; 8N1, DLAB=0
        0xD7, 0x03,       // st.8 a,(0x03)   ; Set LCR
        
        // Initialize IDE controller
        0x90, 0xFF, 0xB0, // ld.16 a,0xFFB0  ; IDE base address
        0x91, 0x04,       // ld.8 a,0x04     ; Reset command
        0xD7, 0x07,       // st.8 a,(0x07)   ; Send reset command
        
        // Wait loop (simple delay)
        0x90, 0x10, 0x00, // ld.16 a,0x1000  ; Delay count
        0x41,             // dec a           ; Decrement
        0x22, 0xFC,       // brnz -4         ; Loop until zero
        
        // Select master drive
        0x91, 0xE0,       // ld.8 a,0xE0     ; LBA mode, master drive
        0xD7, 0x06,       // st.8 a,(0x06)   ; Write to drive select register
        
        // Read sector 2 (image table)
        0x91, 0x01,       // ld.8 a,0x01     ; Read 1 sector
        0xD7, 0x02,       // st.8 a,(0x02)   ; Set sector count
        0x91, 0x02,       // ld.8 a,0x02     ; LBA low (sector 2)
        0xD7, 0x03,       // st.8 a,(0x03)   ; Set LBA low
        0x91, 0x00,       // ld.8 a,0x00     ; LBA mid
        0xD7, 0x04,       // st.8 a,(0x04)   ; Set LBA mid
        0x91, 0x00,       // ld.8 a,0x00     ; LBA high
        0xD7, 0x05,       // st.8 a,(0x05)   ; Set LBA high
        0x91, 0x20,       // ld.8 a,0x20     ; READ SECTORS command
        0xD7, 0x07,       // st.8 a,(0x07)   ; Execute command
        
        // Display "Magic-1" on UART
        0x90, 0x00, 0x80, // ld.16 a,0x0080  ; Address of message
        0x91, 0x4D,       // ld.8 a,'M'
        0xD7, 0xF0,       // st.8 a,(0xFFF0) ; Output to UART
        0x91, 0x61,       // ld.8 a,'a'
        0xD7, 0xF0,       // st.8 a,(0xFFF0) ; Output to UART
        0x91, 0x67,       // ld.8 a,'g'
        0xD7, 0xF0,       // st.8 a,(0xFFF0) ; Output to UART
        0x91, 0x69,       // ld.8 a,'i'
        0xD7, 0xF0,       // st.8 a,(0xFFF0) ; Output to UART
        0x91, 0x63,       // ld.8 a,'c'
        0xD7, 0xF0,       // st.8 a,(0xFFF0) ; Output to UART
        0x91, 0x2D,       // ld.8 a,'-'
        0xD7, 0xF0,       // st.8 a,(0xFFF0) ; Output to UART
        0x91, 0x31,       // ld.8 a,'1'
        0xD7, 0xF0,       // st.8 a,(0xFFF0) ; Output to UART
        0x91, 0x0D,       // ld.8 a,'\r'
        0xD7, 0xF0,       // st.8 a,(0xFFF0) ; Output to UART
        0x91, 0x0A,       // ld.8 a,'\n'
        0xD7, 0xF0,       // st.8 a,(0xFFF0) ; Output to UART
        
        // Stop execution
        0x00              // halt            ; Stop execution
    };
}
⚠️ **GitHub.com Fallback** ⚠️