2024-03-04 17:06:43 +00:00
//
// Archimedes.cpp
// Clock Signal
//
// Created by Thomas Harte on 04/03/2024.
// Copyright © 2024 Thomas Harte. All rights reserved.
//
# include "Archimedes.hpp"
# include "../../AudioProducer.hpp"
# include "../../KeyboardMachine.hpp"
# include "../../MediaTarget.hpp"
# include "../../ScanProducer.hpp"
# include "../../TimedMachine.hpp"
2024-03-19 15:34:10 +00:00
# include "../../../InstructionSets/ARM/Disassembler.hpp"
2024-03-04 17:08:46 +00:00
# include "../../../InstructionSets/ARM/Executor.hpp"
2024-03-06 14:54:39 +00:00
# include "../../../Outputs/Log.hpp"
2024-03-16 19:00:23 +00:00
# include "../../../Components/I2C/I2C.hpp"
2024-03-04 17:08:46 +00:00
2024-03-05 02:09:24 +00:00
# include <algorithm>
# include <array>
2024-03-09 03:54:42 +00:00
# include <set>
2024-03-05 02:09:24 +00:00
# include <vector>
2024-03-05 02:43:06 +00:00
namespace {
2024-03-06 14:54:39 +00:00
Log : : Logger < Log : : Source : : Archimedes > logger ;
2024-03-05 02:43:06 +00:00
enum class Zone {
LogicallyMappedRAM ,
PhysicallyMappedRAM ,
IOControllers ,
LowROM ,
HighROM ,
VideoController ,
DMAAndMEMC ,
AddressTranslator ,
} ;
constexpr std : : array < Zone , 0x20 > zones ( bool is_read ) {
std : : array < Zone , 0x20 > zones { } ;
for ( size_t c = 0 ; c < zones . size ( ) ; c + + ) {
const auto address = c < < 21 ;
if ( address < 0x200'0000 ) {
zones [ c ] = Zone : : LogicallyMappedRAM ;
} else if ( address < 0x300'0000 ) {
zones [ c ] = Zone : : PhysicallyMappedRAM ;
} else if ( address < 0x340'0000 ) {
zones [ c ] = Zone : : IOControllers ;
} else if ( address < 0x360'0000 ) {
zones [ c ] = is_read ? Zone : : LowROM : Zone : : VideoController ;
} else if ( address < 0x380'0000 ) {
zones [ c ] = is_read ? Zone : : LowROM : Zone : : DMAAndMEMC ;
} else {
zones [ c ] = is_read ? Zone : : HighROM : Zone : : AddressTranslator ;
}
}
return zones ;
}
2024-03-13 18:31:26 +00:00
template < int start , int end > struct BitMask {
static_assert ( start > = end ) ;
static constexpr uint32_t value = ( ( 1 < < ( start + 1 ) ) - 1 ) - ( ( 1 < < end ) - 1 ) ;
} ;
static_assert ( BitMask < 0 , 0 > : : value = = 1 ) ;
static_assert ( BitMask < 1 , 1 > : : value = = 2 ) ;
static_assert ( BitMask < 15 , 15 > : : value = = 32768 ) ;
static_assert ( BitMask < 15 , 0 > : : value = = 0xffff ) ;
static_assert ( BitMask < 15 , 14 > : : value = = 49152 ) ;
2024-03-05 02:43:06 +00:00
}
2024-03-04 17:06:43 +00:00
namespace Archimedes {
2024-03-18 01:55:19 +00:00
struct CMOSRAM : public I2C : : Peripheral {
} ;
2024-03-15 14:57:18 +00:00
/// Models a half-duplex serial link between two parties, framing bytes with one start bit and two stop bits.
struct HalfDuplexSerial {
static constexpr uint16_t ShiftMask = 0b1111'1110'0000'0000 ;
/// Enqueues @c value for output.
void output ( int party , uint8_t value ) {
parties_ [ party ] . output_count = 11 ;
2024-03-16 03:19:26 +00:00
parties_ [ party ] . input = 0x7ff ;
2024-03-15 14:57:18 +00:00
parties_ [ party ] . output = uint16_t ( ( value < < 1 ) | ShiftMask ) ;
}
/// @returns The last observed input.
uint8_t input ( int party ) const {
return uint8_t ( parties_ [ party ] . input > > 1 ) ;
}
static constexpr uint8_t Receive = 1 < < 0 ;
static constexpr uint8_t Transmit = 1 < < 1 ;
/// @returns A bitmask of events that occurred during the last shift.
uint8_t events ( int party ) {
const auto result = parties_ [ party ] . events ;
parties_ [ party ] . events = 0 ;
return result ;
}
bool is_outputting ( int party ) const {
return parties_ [ party ] . output_count ! = 11 ;
}
/// Updates the shifters on both sides of the serial link.
void shift ( ) {
const uint16_t next = parties_ [ 0 ] . output & parties_ [ 1 ] . output & 1 ;
for ( int c = 0 ; c < 2 ; c + + ) {
if ( parties_ [ c ] . output_count ) {
- - parties_ [ c ] . output_count ;
if ( ! parties_ [ c ] . output_count ) {
parties_ [ c ] . events | = Transmit ;
parties_ [ c ] . input_count = - 1 ;
}
parties_ [ c ] . output = ( parties_ [ c ] . output > > 1 ) | ShiftMask ;
} else {
// Check for a start bit.
if ( parties_ [ c ] . input_count = = - 1 & & ! next ) {
parties_ [ c ] . input_count = 0 ;
}
// Shift in if currently observing.
if ( parties_ [ c ] . input_count > = 0 & & parties_ [ c ] . input_count < 11 ) {
parties_ [ c ] . input = uint16_t ( ( parties_ [ c ] . input > > 1 ) | ( next < < 10 ) ) ;
+ + parties_ [ c ] . input_count ;
if ( parties_ [ c ] . input_count = = 11 ) {
parties_ [ c ] . events | = Receive ;
parties_ [ c ] . input_count = - 1 ;
}
}
}
}
}
private :
struct Party {
int output_count = 0 ;
int input_count = - 1 ;
uint16_t output = 0xffff ;
uint16_t input = 0 ;
uint8_t events = 0 ;
} parties_ [ 2 ] ;
} ;
static constexpr int IOCParty = 0 ;
static constexpr int KeyboardParty = 1 ;
// Resource for the keyboard protocol: https://github.com/tmk/tmk_keyboard/wiki/ACORN-ARCHIMEDES-Keyboard
struct Keyboard {
Keyboard ( HalfDuplexSerial & serial ) : serial_ ( serial ) { }
void update ( ) {
if ( serial_ . events ( KeyboardParty ) & HalfDuplexSerial : : Receive ) {
const uint8_t input = serial_ . input ( KeyboardParty ) ;
switch ( input ) {
case HRST :
// TODO:
case RAK1 :
case RAK2 :
serial_ . output ( KeyboardParty , input ) ;
break ;
2024-03-16 01:59:38 +00:00
case RQID :
serial_ . output ( KeyboardParty , 0x81 ) ; // TODO: what keyboard type?
break ;
default :
printf ( " Keyboard declines to respond to %02x \n " , input ) ;
break ;
2024-03-15 14:57:18 +00:00
}
}
}
private :
HalfDuplexSerial & serial_ ;
static constexpr uint8_t HRST = 0b1111'1111 ; // Keyboard reset.
static constexpr uint8_t RAK1 = 0b1111'1110 ; // Reset response #1.
static constexpr uint8_t RAK2 = 0b1111'1101 ; // Reset response #2.
static constexpr uint8_t RQID = 0b0010'0000 ; // Request for keyboard ID.
static constexpr uint8_t RQMP = 0b0010'0010 ; // Request for mouse data.
static constexpr uint8_t BACK = 0b0011'1111 ; // Acknowledge for first keyboard data byte pair.
static constexpr uint8_t NACK = 0b0011'0000 ; // Acknowledge for last keyboard data byte pair, selects scan/mouse mode.
static constexpr uint8_t SACK = 0b0011'0001 ; // Last data byte acknowledge.
static constexpr uint8_t MACK = 0b0011'0010 ; // Last data byte acknowledge.
static constexpr uint8_t SMAK = 0b0011'0011 ; // Last data byte acknowledge.
static constexpr uint8_t PRST = 0b0010'0001 ; // Does nothing.
} ;
2024-03-07 15:05:22 +00:00
struct Video {
void write ( uint32_t value ) {
const auto target = ( value > > 24 ) & 0xfc ;
switch ( target ) {
case 0x00 : case 0x04 : case 0x08 : case 0x0c :
case 0x10 : case 0x14 : case 0x18 : case 0x1c :
case 0x20 : case 0x24 : case 0x28 : case 0x2c :
case 0x30 : case 0x34 : case 0x38 : case 0x3c :
logger . error ( ) . append ( " TODO: Video palette logical colour %d to %03x " , ( target > > 2 ) , value & 0x1fff ) ;
break ;
case 0x40 :
logger . error ( ) . append ( " TODO: Video border colour to %03x " , value & 0x1fff ) ;
break ;
case 0x44 : case 0x48 : case 0x4c :
logger . error ( ) . append ( " TODO: Cursor colour %d to %03x " , ( target - 0x44 ) > > 2 , value & 0x1fff ) ;
break ;
case 0x60 : case 0x64 : case 0x68 : case 0x6c :
case 0x70 : case 0x74 : case 0x78 : case 0x7c :
logger . error ( ) . append ( " TODO: Stereo image register %d to %03x " , ( target - 0x60 ) > > 2 , value & 0x7 ) ;
break ;
case 0x80 :
logger . error ( ) . append ( " TODO: Video horizontal period: %d " , ( value > > 14 ) & 0x3ff ) ;
break ;
case 0x84 :
logger . error ( ) . append ( " TODO: Video horizontal sync width: %d " , ( value > > 14 ) & 0x3ff ) ;
break ;
case 0x88 :
logger . error ( ) . append ( " TODO: Video horizontal border start: %d " , ( value > > 14 ) & 0x3ff ) ;
break ;
case 0x8c :
logger . error ( ) . append ( " TODO: Video horizontal display start: %d " , ( value > > 14 ) & 0x3ff ) ;
break ;
case 0x90 :
logger . error ( ) . append ( " TODO: Video horizontal display end: %d " , ( value > > 14 ) & 0x3ff ) ;
break ;
case 0x94 :
logger . error ( ) . append ( " TODO: Video horizontal border end: %d " , ( value > > 14 ) & 0x3ff ) ;
break ;
case 0x98 :
logger . error ( ) . append ( " TODO: Video horizontal cursor end: %d " , ( value > > 14 ) & 0x3ff ) ;
break ;
case 0x9c :
logger . error ( ) . append ( " TODO: Video horizontal interlace: %d " , ( value > > 14 ) & 0x3ff ) ;
break ;
case 0xa0 :
logger . error ( ) . append ( " TODO: Video vertical period: %d " , ( value > > 14 ) & 0x3ff ) ;
break ;
case 0xa4 :
logger . error ( ) . append ( " TODO: Video vertical sync width: %d " , ( value > > 14 ) & 0x3ff ) ;
break ;
case 0xa8 :
logger . error ( ) . append ( " TODO: Video vertical border start: %d " , ( value > > 14 ) & 0x3ff ) ;
break ;
case 0xac :
logger . error ( ) . append ( " TODO: Video vertical display start: %d " , ( value > > 14 ) & 0x3ff ) ;
break ;
case 0xb0 :
logger . error ( ) . append ( " TODO: Video vertical display end: %d " , ( value > > 14 ) & 0x3ff ) ;
break ;
case 0xb4 :
logger . error ( ) . append ( " TODO: Video vertical border end: %d " , ( value > > 14 ) & 0x3ff ) ;
break ;
case 0xb8 :
logger . error ( ) . append ( " TODO: Video vertical cursor start: %d " , ( value > > 14 ) & 0x3ff ) ;
break ;
case 0xbc :
logger . error ( ) . append ( " TODO: Video vertical cursor end: %d " , ( value > > 14 ) & 0x3ff ) ;
break ;
case 0xc0 :
logger . error ( ) . append ( " TODO: Sound frequency: %d " , value & 0x7f ) ;
break ;
case 0xe0 :
logger . error ( ) . append ( " TODO: video control: %08x " , value ) ;
break ;
default :
logger . error ( ) . append ( " TODO: unrecognised VIDC write of %08x " , value ) ;
break ;
}
}
} ;
2024-03-11 18:49:03 +00:00
// IRQ A flags
namespace IRQA {
// The first four of these are taken from the A500 documentation and may be inaccurate.
static constexpr uint8_t PrinterBusy = 0x01 ;
static constexpr uint8_t SerialRinging = 0x02 ;
static constexpr uint8_t PrinterAcknowledge = 0x04 ;
static constexpr uint8_t VerticalFlyback = 0x08 ;
static constexpr uint8_t PowerOnReset = 0x10 ;
static constexpr uint8_t Timer0 = 0x20 ;
static constexpr uint8_t Timer1 = 0x40 ;
static constexpr uint8_t SetAlways = 0x80 ;
}
2024-03-13 01:23:22 +00:00
// IRQ B flags
namespace IRQB {
// These are taken from the A3010 documentation.
static constexpr uint8_t PoduleFIQRequest = 0x01 ;
static constexpr uint8_t SoundBufferPointerUsed = 0x02 ;
static constexpr uint8_t SerialLine = 0x04 ;
static constexpr uint8_t IDE = 0x08 ;
static constexpr uint8_t FloppyDiscInterrupt = 0x10 ;
static constexpr uint8_t PoduleIRQRequest = 0x20 ;
static constexpr uint8_t KeyboardTransmitEmpty = 0x40 ;
static constexpr uint8_t KeyboardReceiveFull = 0x80 ;
}
// FIQ flags
namespace FIQ {
// These are taken from the A3010 documentation.
static constexpr uint8_t FloppyDiscData = 0x01 ;
static constexpr uint8_t SerialLine = 0x10 ;
static constexpr uint8_t PoduleFIQRequest = 0x40 ;
static constexpr uint8_t SetAlways = 0x80 ;
}
2024-03-12 15:34:31 +00:00
2024-03-13 01:23:22 +00:00
namespace InterruptRequests {
2024-03-12 15:34:31 +00:00
static constexpr int IRQ = 0x01 ;
static constexpr int FIQ = 0x02 ;
} ;
2024-03-07 03:14:56 +00:00
struct Interrupts {
2024-03-12 15:34:31 +00:00
int interrupt_mask ( ) const {
return
( ( irq_a_ . request ( ) | irq_b_ . request ( ) ) ? InterruptRequests : : IRQ : 0 ) |
( fiq_ . request ( ) ? InterruptRequests : : FIQ : 0 ) ;
}
2024-03-11 18:49:03 +00:00
2024-03-13 15:02:52 +00:00
template < int c >
bool tick_timer ( ) {
if ( ! counters_ [ c ] . value & & ! counters_ [ c ] . reload ) {
return false ;
}
2024-03-07 16:39:26 +00:00
2024-03-13 15:02:52 +00:00
- - counters_ [ c ] . value ;
if ( ! counters_ [ c ] . value ) {
counters_ [ c ] . value = counters_ [ c ] . reload ;
2024-03-07 16:12:40 +00:00
2024-03-13 15:02:52 +00:00
switch ( c ) {
case 0 : return irq_a_ . apply ( IRQA : : Timer0 ) ;
case 1 : return irq_a_ . apply ( IRQA : : Timer1 ) ;
2024-03-15 14:57:18 +00:00
case 3 : {
serial_ . shift ( ) ;
keyboard_ . update ( ) ;
const uint8_t events = serial_ . events ( IOCParty ) ;
bool did_interrupt = false ;
if ( events & HalfDuplexSerial : : Receive ) {
did_interrupt | = irq_b_ . apply ( IRQB : : KeyboardReceiveFull ) ;
}
if ( events & HalfDuplexSerial : : Transmit ) {
did_interrupt | = irq_b_ . apply ( IRQB : : KeyboardTransmitEmpty ) ;
}
return did_interrupt ;
}
2024-03-13 15:02:52 +00:00
default : break ;
2024-03-07 16:39:26 +00:00
}
2024-03-15 14:57:18 +00:00
// TODO: events for timers 2 (baud).
2024-03-07 16:39:26 +00:00
}
2024-03-11 16:14:00 +00:00
2024-03-13 15:02:52 +00:00
return false ;
}
bool tick_timers ( ) {
bool did_change_interrupts = false ;
did_change_interrupts | = tick_timer < 0 > ( ) ;
did_change_interrupts | = tick_timer < 1 > ( ) ;
did_change_interrupts | = tick_timer < 2 > ( ) ;
did_change_interrupts | = tick_timer < 3 > ( ) ;
2024-03-11 16:14:00 +00:00
return did_change_interrupts ;
2024-03-07 16:12:40 +00:00
}
2024-03-13 01:23:22 +00:00
static constexpr uint32_t AddressMask = 0x1f'ffff ;
2024-03-16 03:19:26 +00:00
bool read ( uint32_t address , uint8_t & value ) {
2024-03-13 01:23:22 +00:00
const auto target = address & AddressMask ;
2024-03-14 14:43:51 +00:00
value = 0xff ;
2024-03-07 16:39:26 +00:00
switch ( target ) {
2024-03-13 01:23:22 +00:00
default :
logger . error ( ) . append ( " Unrecognised IOC read from %08x " , address ) ;
break ;
2024-03-07 16:12:40 +00:00
2024-03-13 01:23:22 +00:00
case 0x3200000 & AddressMask :
2024-03-18 15:09:29 +00:00
value = control_ | 0xc0 ;
2024-03-16 19:00:23 +00:00
value & = ~ ( i2c_ . clock ( ) ? 2 : 0 ) ;
value & = ~ ( i2c_ . data ( ) ? 1 : 0 ) ;
2024-03-18 15:09:29 +00:00
// logger.error().append("IOC control read: C:%d D:%d", !(value & 2), !(value & 1));
2024-03-07 16:12:40 +00:00
return true ;
2024-03-13 01:23:22 +00:00
case 0x3200004 & AddressMask :
2024-03-15 14:57:18 +00:00
value = serial_ . input ( IOCParty ) ;
2024-03-16 03:19:26 +00:00
irq_b_ . clear ( IRQB : : KeyboardReceiveFull ) ;
2024-03-15 14:57:18 +00:00
logger . error ( ) . append ( " IOC keyboard receive: %02x " , value ) ;
2024-03-13 01:23:22 +00:00
return true ;
2024-03-07 16:39:26 +00:00
// IRQ A.
2024-03-13 01:23:22 +00:00
case 0x3200010 & AddressMask :
value = irq_a_ . status ;
2024-03-16 01:34:39 +00:00
// logger.error().append("IRQ A status is %02x", value);
2024-03-13 01:23:22 +00:00
return true ;
case 0x3200014 & AddressMask :
value = irq_a_ . request ( ) ;
logger . error ( ) . append ( " IRQ A request is %02x " , value ) ;
return true ;
case 0x3200018 & AddressMask :
value = irq_a_ . mask ;
logger . error ( ) . append ( " IRQ A mask is %02x " , value ) ;
return true ;
2024-03-07 16:39:26 +00:00
// IRQ B.
2024-03-13 01:23:22 +00:00
case 0x3200020 & AddressMask :
value = irq_b_ . status ;
2024-03-16 01:34:39 +00:00
// logger.error().append("IRQ B status is %02x", value);
2024-03-13 01:23:22 +00:00
return true ;
case 0x3200024 & AddressMask :
value = irq_b_ . request ( ) ;
logger . error ( ) . append ( " IRQ B request is %02x " , value ) ;
return true ;
case 0x3200028 & AddressMask :
value = irq_b_ . mask ;
logger . error ( ) . append ( " IRQ B mask is %02x " , value ) ;
return true ;
2024-03-07 16:12:40 +00:00
2024-03-07 16:39:26 +00:00
// FIQ.
2024-03-13 01:23:22 +00:00
case 0x3200030 & AddressMask :
value = fiq_ . status ;
logger . error ( ) . append ( " FIQ status is %02x " , value ) ;
return true ;
case 0x3200034 & AddressMask :
value = fiq_ . request ( ) ;
logger . error ( ) . append ( " FIQ request is %02x " , value ) ;
return true ;
case 0x3200038 & AddressMask :
value = fiq_ . mask ;
logger . error ( ) . append ( " FIQ mask is %02x " , value ) ;
return true ;
2024-03-07 16:39:26 +00:00
// Counters.
2024-03-13 01:23:22 +00:00
case 0x3200040 & AddressMask :
case 0x3200050 & AddressMask :
case 0x3200060 & AddressMask :
case 0x3200070 & AddressMask :
2024-03-07 16:39:26 +00:00
value = counters_ [ ( target > > 4 ) - 0x4 ] . output & 0xff ;
2024-03-16 01:34:39 +00:00
// logger.error().append("%02x: Counter %d low is %02x", target, (target >> 4) - 0x4, value);
2024-03-07 16:39:26 +00:00
return true ;
2024-03-13 01:23:22 +00:00
case 0x3200044 & AddressMask :
case 0x3200054 & AddressMask :
case 0x3200064 & AddressMask :
case 0x3200074 & AddressMask :
2024-03-07 16:39:26 +00:00
value = counters_ [ ( target > > 4 ) - 0x4 ] . output > > 8 ;
2024-03-16 01:34:39 +00:00
// logger.error().append("%02x: Counter %d high is %02x", target, (target >> 4) - 0x4, value);
2024-03-07 16:12:40 +00:00
return true ;
}
2024-03-14 14:43:51 +00:00
return true ;
2024-03-07 16:12:40 +00:00
}
bool write ( uint32_t address , uint8_t value ) {
2024-03-13 01:23:22 +00:00
const auto target = address & AddressMask ;
2024-03-07 16:39:26 +00:00
switch ( target ) {
2024-03-13 01:23:22 +00:00
default :
logger . error ( ) . append ( " Unrecognised IOC write of %02x at %08x " , value , address ) ;
break ;
2024-03-07 16:39:26 +00:00
2024-03-13 18:31:26 +00:00
case 0x320'0000 & AddressMask :
2024-03-18 15:09:29 +00:00
// TODO: does the rest of the control register relate to anything?
// logger.error().append("TODO: IOC control write: C:%d D:%d", !(value & 2), !(value & 1));
2024-03-16 19:00:23 +00:00
control_ = value ;
i2c_ . set_clock_data ( ! ( value & 2 ) , ! ( value & 1 ) ) ;
2024-03-07 16:39:26 +00:00
return true ;
2024-03-13 18:31:26 +00:00
case 0x320'0004 & AddressMask :
2024-03-15 14:57:18 +00:00
logger . error ( ) . append ( " IOC keyboard transmit %02x " , value ) ;
serial_ . output ( IOCParty , value ) ;
2024-03-16 03:19:26 +00:00
irq_b_ . clear ( IRQB : : KeyboardTransmitEmpty ) ;
2024-03-13 01:23:22 +00:00
return true ;
2024-03-13 18:31:26 +00:00
case 0x320'0014 & AddressMask :
2024-03-07 16:39:26 +00:00
// b2: clear IF.
// b3: clear IR.
// b4: clear POR.
// b5: clear TM[0].
// b6: clear TM[1].
2024-03-12 15:34:31 +00:00
irq_a_ . clear ( value & 0x7c ) ;
2024-03-07 16:39:26 +00:00
return true ;
// Interrupts.
2024-03-13 18:31:26 +00:00
case 0x320'0018 & AddressMask : irq_a_ . mask = value ; return true ;
case 0x320'0028 & AddressMask : irq_b_ . mask = value ; return true ;
case 0x320'0038 & AddressMask : fiq_ . mask = value ; return true ;
2024-03-07 16:39:26 +00:00
// Counters.
2024-03-13 18:31:26 +00:00
case 0x320'0040 & AddressMask :
case 0x320'0050 & AddressMask :
case 0x320'0060 & AddressMask :
case 0x320'0070 & AddressMask :
2024-03-07 16:39:26 +00:00
counters_ [ ( target > > 4 ) - 0x4 ] . reload = uint16_t (
( counters_ [ ( target > > 4 ) - 0x4 ] . reload & 0xff00 ) | value
) ;
return true ;
2024-03-13 01:23:22 +00:00
2024-03-13 18:31:26 +00:00
case 0x320'0044 & AddressMask :
case 0x320'0054 & AddressMask :
case 0x320'0064 & AddressMask :
case 0x320'0074 & AddressMask :
2024-03-07 16:39:26 +00:00
counters_ [ ( target > > 4 ) - 0x4 ] . reload = uint16_t (
( counters_ [ ( target > > 4 ) - 0x4 ] . reload & 0x00ff ) | ( value < < 8 )
) ;
return true ;
2024-03-13 18:31:26 +00:00
case 0x320'0048 & AddressMask :
case 0x320'0058 & AddressMask :
case 0x320'0068 & AddressMask :
case 0x320'0078 & AddressMask :
2024-03-07 16:39:26 +00:00
counters_ [ ( target > > 4 ) - 0x4 ] . value = counters_ [ ( target > > 4 ) - 0x4 ] . reload ;
return true ;
2024-03-13 01:23:22 +00:00
2024-03-13 18:31:26 +00:00
case 0x320'004c & AddressMask :
case 0x320'005c & AddressMask :
case 0x320'006c & AddressMask :
case 0x320'007c & AddressMask :
2024-03-07 16:39:26 +00:00
counters_ [ ( target > > 4 ) - 0x4 ] . output = counters_ [ ( target > > 4 ) - 0x4 ] . value ;
return true ;
2024-03-13 18:31:26 +00:00
case 0x327'0000 & AddressMask :
logger . error ( ) . append ( " TODO: exteded external podule space " ) ;
return true ;
case 0x331'0000 & AddressMask :
logger . error ( ) . append ( " TODO: 1772 / disk write " ) ;
return true ;
case 0x335'0000 & AddressMask :
logger . error ( ) . append ( " TODO: LS374 / printer data write " ) ;
return true ;
case 0x335'0018 & AddressMask :
2024-03-18 01:55:19 +00:00
logger . error ( ) . append ( " TODO: latch B write: %02x " , value ) ;
2024-03-13 18:31:26 +00:00
return true ;
case 0x335'0040 & AddressMask :
2024-03-18 01:55:19 +00:00
logger . error ( ) . append ( " TODO: latch A write: %02x " , value ) ;
2024-03-13 18:31:26 +00:00
return true ;
case 0x335'0048 & AddressMask :
2024-03-18 01:55:19 +00:00
logger . error ( ) . append ( " TODO: latch C write: %02x " , value ) ;
2024-03-13 18:31:26 +00:00
return true ;
case 0x336'0000 & AddressMask :
logger . error ( ) . append ( " TODO: podule interrupt request " ) ;
return true ;
case 0x336'0004 & AddressMask :
logger . error ( ) . append ( " TODO: podule interrupt mask " ) ;
return true ;
case 0x33a'0000 & AddressMask :
logger . error ( ) . append ( " TODO: 6854 / econet write " ) ;
return true ;
case 0x33b'0000 & AddressMask :
logger . error ( ) . append ( " TODO: 6551 / serial line write " ) ;
return true ;
2024-03-07 16:39:26 +00:00
}
2024-03-14 14:43:51 +00:00
return true ;
2024-03-07 16:12:40 +00:00
}
2024-03-15 14:57:18 +00:00
Interrupts ( ) : keyboard_ ( serial_ ) {
2024-03-11 18:49:03 +00:00
irq_a_ . status = IRQA : : SetAlways | IRQA : : PowerOnReset ;
2024-03-07 16:39:26 +00:00
irq_b_ . status = 0x00 ;
fiq_ . status = 0x80 ; // 'set always'.
2024-03-18 01:55:19 +00:00
i2c_ . add_peripheral ( & cmos_ , 0xa0 ) ;
2024-03-07 03:14:56 +00:00
}
2024-03-07 16:39:26 +00:00
private :
2024-03-16 19:00:23 +00:00
// IRQA, IRQB and FIQ states.
2024-03-07 16:39:26 +00:00
struct Interrupt {
uint8_t status , mask ;
uint8_t request ( ) const {
return status & mask ;
}
2024-03-11 16:14:00 +00:00
bool apply ( uint8_t value ) {
status | = value ;
return status & mask ;
}
2024-03-12 15:34:31 +00:00
void clear ( uint8_t bits ) {
status & = ~ bits ;
}
2024-03-07 16:39:26 +00:00
} ;
Interrupt irq_a_ , irq_b_ , fiq_ ;
2024-03-16 19:00:23 +00:00
// The IOCs four counters.
2024-03-07 16:39:26 +00:00
struct Counter {
uint16_t value ;
uint16_t reload ;
uint16_t output ;
} ;
Counter counters_ [ 4 ] ;
2024-03-15 14:57:18 +00:00
2024-03-16 19:00:23 +00:00
// The KART and keyboard beyond it.
2024-03-15 14:57:18 +00:00
HalfDuplexSerial serial_ ;
Keyboard keyboard_ ;
2024-03-16 19:00:23 +00:00
// The control register.
uint8_t control_ = 0xff ;
// The I2C bus.
I2C : : Bus i2c_ ;
2024-03-18 01:55:19 +00:00
CMOSRAM cmos_ ;
2024-03-07 03:14:56 +00:00
} ;
2024-03-05 02:43:06 +00:00
/// Primarily models the MEMC.
2024-03-12 15:34:31 +00:00
template < typename IOCWriteDelegateT >
2024-03-04 17:08:46 +00:00
struct Memory {
2024-03-12 15:34:31 +00:00
Memory ( IOCWriteDelegateT & ioc_write_delegate ) : ioc_write_delegate_ ( ioc_write_delegate ) { }
int interrupt_mask ( ) const {
return ioc_ . interrupt_mask ( ) ;
}
2024-03-05 02:09:24 +00:00
void set_rom ( const std : : vector < uint8_t > & rom ) {
std : : copy (
rom . begin ( ) ,
rom . begin ( ) + static_cast < ptrdiff_t > ( std : : min ( rom . size ( ) , rom_ . size ( ) ) ) ,
rom_ . begin ( ) ) ;
}
2024-03-04 17:08:46 +00:00
template < typename IntT >
2024-03-13 18:31:26 +00:00
uint32_t aligned ( uint32_t address ) {
2024-03-11 01:56:19 +00:00
if constexpr ( std : : is_same_v < IntT , uint32_t > ) {
2024-03-13 18:31:26 +00:00
return address & static_cast < uint32_t > ( ~ 3 ) ;
2024-03-11 01:56:19 +00:00
}
2024-03-13 18:31:26 +00:00
return address ;
}
template < typename IntT >
bool write ( uint32_t address , IntT source , InstructionSet : : ARM : : Mode mode , bool ) {
2024-03-20 00:26:17 +00:00
// User mode may only _write_ to logically-mapped RAM (subject to further testing below).
if ( mode = = InstructionSet : : ARM : : Mode : : User & & address > = 0x200'0000 ) {
2024-03-11 16:14:00 +00:00
return false ;
}
2024-03-11 01:56:19 +00:00
2024-03-20 00:26:17 +00:00
switch ( write_zones_ [ ( address > > 21 ) & 31 ] ) {
2024-03-05 02:43:06 +00:00
case Zone : : DMAAndMEMC :
if ( ( address & 0b1110'0000'0000'0000'0000 ) = = 0b1110'0000'0000'0000'0000 ) {
2024-03-06 19:56:06 +00:00
// "The parameters are encoded into the processor address lines".
os_mode_ = address & ( 1 < < 12 ) ;
sound_dma_enable_ = address & ( 1 < < 11 ) ;
video_dma_enable_ = address & ( 1 < < 10 ) ;
switch ( ( address > > 8 ) & 3 ) {
default :
dynamic_ram_refresh_ = DynamicRAMRefresh : : None ;
break ;
case 0b01 :
case 0b11 :
dynamic_ram_refresh_ = DynamicRAMRefresh ( ( address > > 8 ) & 3 ) ;
break ;
}
high_rom_access_time_ = ROMAccessTime ( ( address > > 6 ) & 3 ) ;
low_rom_access_time_ = ROMAccessTime ( ( address > > 4 ) & 3 ) ;
page_size_ = PageSize ( ( address > > 2 ) & 3 ) ;
2024-03-13 18:31:26 +00:00
logger . info ( ) . append ( " MEMC Control: %08x -> OS:%d sound:%d video:%d refresh:%d high:%d low:%d size:%d " , address , os_mode_ , sound_dma_enable_ , video_dma_enable_ , dynamic_ram_refresh_ , high_rom_access_time_ , low_rom_access_time_ , page_size_ ) ;
2024-03-14 14:43:51 +00:00
map_dirty_ = true ;
2024-03-06 19:56:06 +00:00
return true ;
2024-03-06 14:54:39 +00:00
} else {
logger . error ( ) . append ( " TODO: DMA/MEMC %08x to %08x " , source , address ) ;
2024-03-05 02:43:06 +00:00
}
2024-03-06 19:56:06 +00:00
break ;
case Zone : : LogicallyMappedRAM : {
const auto item = logical_ram < IntT , false > ( address , mode ) ;
if ( ! item ) {
return false ;
}
* item = source ;
return true ;
} break ;
2024-03-07 16:39:26 +00:00
case Zone : : IOControllers :
2024-03-14 14:53:38 +00:00
// TODO: have I overrestricted the value type for the IOC area?
ioc_ . write ( address , uint8_t ( source ) ) ;
2024-03-12 15:34:31 +00:00
ioc_write_delegate_ . did_write_ioc ( ) ;
2024-03-07 16:39:26 +00:00
return true ;
2024-03-06 19:56:06 +00:00
case Zone : : VideoController :
2024-03-07 15:05:22 +00:00
// TODO: handle byte writes correctly.
vidc_ . write ( source ) ;
2024-03-06 19:56:06 +00:00
break ;
2024-03-05 02:43:06 +00:00
2024-03-06 14:54:39 +00:00
case Zone : : PhysicallyMappedRAM :
physical_ram < IntT > ( address ) = source ;
2024-03-06 19:56:06 +00:00
return true ;
case Zone : : AddressTranslator :
2024-03-14 14:53:38 +00:00
// printf("Translator write at %08x; replaces %08x\n", address, pages_[address & 0x7f]);
2024-03-06 20:31:07 +00:00
pages_ [ address & 0x7f ] = address ;
2024-03-14 14:43:51 +00:00
map_dirty_ = true ;
2024-03-05 02:43:06 +00:00
break ;
2024-03-04 17:08:46 +00:00
2024-03-06 14:54:39 +00:00
default :
2024-03-13 01:23:22 +00:00
// printf("TODO: write of %08x to %08x [%lu]\n", source, address, sizeof(IntT));
2024-03-06 14:54:39 +00:00
break ;
2024-03-04 17:08:46 +00:00
}
return true ;
}
template < typename IntT >
2024-03-11 16:14:00 +00:00
bool read ( uint32_t address , IntT & source , InstructionSet : : ARM : : Mode mode , bool ) {
2024-03-20 00:26:17 +00:00
// User mode may only read logically-maped RAM and ROM.
if ( mode = = InstructionSet : : ARM : : Mode : : User & & address > = 0x200'0000 & & address < 0x380'0000 ) {
2024-03-11 16:14:00 +00:00
return false ;
}
2024-03-04 17:08:46 +00:00
2024-03-05 02:43:06 +00:00
switch ( read_zones_ [ ( address > > 21 ) & 31 ] ) {
case Zone : : PhysicallyMappedRAM :
2024-03-06 14:54:39 +00:00
source = physical_ram < IntT > ( address ) ;
2024-03-05 02:43:06 +00:00
return true ;
2024-03-06 19:56:06 +00:00
case Zone : : LogicallyMappedRAM : {
if ( ! has_moved_rom_ ) { // TODO: maintain this state in the zones table.
2024-03-06 14:54:39 +00:00
source = high_rom < IntT > ( address ) ;
2024-03-06 19:56:06 +00:00
return true ;
}
const auto item = logical_ram < IntT , true > ( address , mode ) ;
if ( ! item ) {
return false ;
2024-03-06 14:54:39 +00:00
}
2024-03-06 19:56:06 +00:00
source = * item ;
return true ;
} break ;
case Zone : : LowROM :
2024-03-19 19:06:01 +00:00
// logger.error().append("TODO: Low ROM read from %08x", address);
2024-03-20 00:26:17 +00:00
source = IntT ( ~ 0 ) ;
2024-03-19 19:06:01 +00:00
return true ;
2024-03-06 14:54:39 +00:00
case Zone : : HighROM :
2024-03-07 03:14:56 +00:00
// Real test is: require A24=A25=0, then A25=1.
// TODO: as above, move this test into the zones tables.
2024-03-06 14:54:39 +00:00
has_moved_rom_ = true ;
source = high_rom < IntT > ( address ) ;
2024-03-06 19:56:06 +00:00
return true ;
2024-03-07 16:12:40 +00:00
case Zone : : IOControllers : {
if constexpr ( std : : is_same_v < IntT , uint8_t > ) {
2024-03-07 16:39:26 +00:00
ioc_ . read ( address , source ) ;
return true ;
2024-03-07 16:12:40 +00:00
} else {
// TODO: generalise this adaptation of an 8-bit device to the 32-bit bus, which probably isn't right anyway.
uint8_t value ;
2024-03-07 16:39:26 +00:00
ioc_ . read ( address , value ) ;
2024-03-07 16:12:40 +00:00
source = value ;
2024-03-06 19:56:06 +00:00
return true ;
}
2024-03-07 16:12:40 +00:00
}
2024-03-06 14:54:39 +00:00
default :
logger . error ( ) . append ( " TODO: read from %08x " , address ) ;
break ;
2024-03-04 17:08:46 +00:00
}
2024-03-06 14:54:39 +00:00
source = 0 ;
2024-03-18 18:08:08 +00:00
return false ;
2024-03-04 17:08:46 +00:00
}
2024-03-11 16:14:00 +00:00
bool tick_timers ( ) {
return ioc_ . tick_timers ( ) ;
2024-03-07 16:12:40 +00:00
}
2024-03-04 17:08:46 +00:00
private :
bool has_moved_rom_ = false ;
std : : array < uint8_t , 4 * 1024 * 1024 > ram_ { } ;
2024-03-05 02:09:24 +00:00
std : : array < uint8_t , 2 * 1024 * 1024 > rom_ ;
2024-03-07 03:14:56 +00:00
Interrupts ioc_ ;
2024-03-07 15:05:22 +00:00
Video vidc_ ;
2024-03-12 15:34:31 +00:00
IOCWriteDelegateT & ioc_write_delegate_ ;
2024-03-05 02:43:06 +00:00
2024-03-06 14:54:39 +00:00
template < typename IntT >
IntT & physical_ram ( uint32_t address ) {
2024-03-13 18:31:26 +00:00
address = aligned < IntT > ( address ) ;
address & = ( ram_ . size ( ) - 1 ) ;
return * reinterpret_cast < IntT * > ( & ram_ [ address ] ) ;
2024-03-06 14:54:39 +00:00
}
template < typename IntT >
IntT & high_rom ( uint32_t address ) {
2024-03-13 18:31:26 +00:00
address = aligned < IntT > ( address ) ;
2024-03-06 14:54:39 +00:00
return * reinterpret_cast < IntT * > ( & rom_ [ address & ( rom_ . size ( ) - 1 ) ] ) ;
}
2024-03-05 02:43:06 +00:00
static constexpr std : : array < Zone , 0x20 > read_zones_ = zones ( true ) ;
static constexpr std : : array < Zone , 0x20 > write_zones_ = zones ( false ) ;
2024-03-06 19:56:06 +00:00
// Control register values.
bool os_mode_ = false ;
bool sound_dma_enable_ = false ;
bool video_dma_enable_ = false ; // "Unaffected" by reset, so here picked arbitrarily.
enum class DynamicRAMRefresh {
None = 0b00 ,
DuringFlyback = 0b01 ,
Continuous = 0b11 ,
} dynamic_ram_refresh_ = DynamicRAMRefresh : : None ; // State at reset is undefined; constrain to a valid enum value.
enum class ROMAccessTime {
ns450 = 0b00 ,
ns325 = 0b01 ,
ns200 = 0b10 ,
ns200with60nsNibble = 0b11 ,
} high_rom_access_time_ = ROMAccessTime : : ns450 , low_rom_access_time_ = ROMAccessTime : : ns450 ;
enum class PageSize {
kb4 = 0b00 ,
kb8 = 0b01 ,
kb16 = 0b10 ,
kb32 = 0b11 ,
} page_size_ = PageSize : : kb4 ;
// Address translator.
//
// MEMC contains one entry per a physical page number, indicating where it goes logically.
// Any logical access is tested against all 128 mappings. So that's backwards compared to
// the ideal for an emulator, which would map from logical to physical, even if a lot more
// compact — there are always 128 physical pages; there are up to 8192 logical pages.
//
// So captured here are both the physical -> logical map as representative of the real
// hardware, and the reverse logical -> physical map, which is built (and rebuilt, and rebuilt)
// from the other.
// Physical to logical mapping.
2024-03-07 02:51:19 +00:00
std : : array < uint32_t , 128 > pages_ { } ;
2024-03-06 19:56:06 +00:00
// Logical to physical mapping.
struct MappedPage {
uint8_t * target = nullptr ;
uint8_t protection_level = 0 ;
} ;
2024-03-07 02:51:19 +00:00
std : : array < MappedPage , 8192 > mapping_ ;
2024-03-14 14:43:51 +00:00
bool map_dirty_ = true ;
2024-03-06 19:56:06 +00:00
template < typename IntT , bool is_read >
2024-03-07 03:00:34 +00:00
IntT * logical_ram ( uint32_t address , InstructionSet : : ARM : : Mode mode ) {
2024-03-14 14:43:51 +00:00
// Possibly TODO: this recompute-if-dirty flag is supposed to ameliorate for an expensive
// mapping process. It can be eliminated when the process is improved.
if ( map_dirty_ ) {
update_mapping ( ) ;
map_dirty_ = false ;
}
2024-03-13 18:31:26 +00:00
address = aligned < IntT > ( address ) ;
2024-03-07 02:51:19 +00:00
address & = 0x1ff'ffff ;
size_t page ;
// TODO: eliminate switch here.
switch ( page_size_ ) {
default :
case PageSize : : kb4 :
page = address > > 12 ;
address & = 0x0fff ;
break ;
case PageSize : : kb8 :
page = address > > 13 ;
address & = 0x1fff ;
break ;
case PageSize : : kb16 :
page = address > > 14 ;
address & = 0x3fff ;
break ;
case PageSize : : kb32 :
page = address > > 15 ;
address & = 0x7fff ;
break ;
}
if ( ! mapping_ [ page ] . target ) {
return nullptr ;
}
2024-03-07 03:00:34 +00:00
// TODO: eliminate switch here.
2024-03-07 03:14:56 +00:00
// Top of my head idea: is_read, is_user and is_os_mode make three bits, so
// keep a one-byte bitmap of permitted accesses rather than the raw protection
// level?
2024-03-07 03:00:34 +00:00
switch ( mapping_ [ page ] . protection_level ) {
case 0b00 : break ;
case 0b01 :
if ( ! is_read & & mode = = InstructionSet : : ARM : : Mode : : User ) {
return nullptr ;
}
break ;
default :
if ( mode = = InstructionSet : : ARM : : Mode : : User ) {
return nullptr ;
}
if ( ! is_read & & ! os_mode_ ) {
return nullptr ;
}
break ;
}
2024-03-07 02:51:19 +00:00
return reinterpret_cast < IntT * > ( mapping_ [ page ] . target + address ) ;
}
2024-03-06 20:31:07 +00:00
2024-03-07 02:51:19 +00:00
void update_mapping ( ) {
// For each physical page, project it into logical space.
switch ( page_size_ ) {
default :
case PageSize : : kb4 : update_mapping < PageSize : : kb4 > ( ) ; break ;
case PageSize : : kb8 : update_mapping < PageSize : : kb8 > ( ) ; break ;
case PageSize : : kb16 : update_mapping < PageSize : : kb16 > ( ) ; break ;
case PageSize : : kb32 : update_mapping < PageSize : : kb32 > ( ) ; break ;
}
2024-03-06 19:56:06 +00:00
}
2024-03-07 02:51:19 +00:00
template < PageSize size >
2024-03-06 19:56:06 +00:00
void update_mapping ( ) {
2024-03-07 02:51:19 +00:00
// Clear all logical mappings.
std : : fill ( mapping_ . begin ( ) , mapping_ . end ( ) , MappedPage { } ) ;
// For each physical page, project it into logical space
// and store it.
for ( const auto page : pages_ ) {
uint32_t physical , logical ;
switch ( size ) {
case PageSize : : kb4 :
// 4kb:
// A[6:0] -> PPN[6:0]
// A[11:10] -> LPN[12:11]; A[22:12] -> LPN[10:0] i.e. 8192 logical pages
2024-03-13 18:31:26 +00:00
physical = page & BitMask < 6 , 0 > : : value ;
2024-03-07 02:51:19 +00:00
physical < < = 12 ;
2024-03-13 18:31:26 +00:00
logical = ( page & BitMask < 11 , 10 > : : value ) < < 1 ;
logical | = ( page & BitMask < 22 , 12 > : : value ) > > 12 ;
2024-03-07 02:51:19 +00:00
break ;
case PageSize : : kb8 :
// 8kb:
// A[0] -> PPN[6]; A[6:1] -> PPN[5:0]
// A[11:10] -> LPN[11:10]; A[22:13] -> LPN[9:0] i.e. 4096 logical pages
2024-03-13 18:31:26 +00:00
physical = ( page & BitMask < 0 , 0 > : : value ) < < 6 ;
physical | = ( page & BitMask < 6 , 1 > : : value ) > > 1 ;
2024-03-07 02:51:19 +00:00
physical < < = 13 ;
2024-03-13 18:31:26 +00:00
logical = page & BitMask < 11 , 10 > : : value ;
logical | = ( page & BitMask < 22 , 13 > : : value ) > > 13 ;
2024-03-07 02:51:19 +00:00
break ;
case PageSize : : kb16 :
// 16kb:
// A[1:0] -> PPN[6:5]; A[6:2] -> PPN[4:0]
// A[11:10] -> LPN[10:9]; A[22:14] -> LPN[8:0] i.e. 2048 logical pages
2024-03-13 18:31:26 +00:00
physical = ( page & BitMask < 1 , 0 > : : value ) < < 5 ;
physical | = ( page & BitMask < 6 , 2 > : : value ) > > 2 ;
2024-03-07 02:51:19 +00:00
physical < < = 14 ;
2024-03-13 18:31:26 +00:00
logical = ( page & BitMask < 11 , 10 > : : value ) > > 1 ;
logical | = ( page & BitMask < 22 , 14 > : : value ) > > 14 ;
2024-03-07 02:51:19 +00:00
break ;
case PageSize : : kb32 :
// 32kb:
2024-03-08 00:40:34 +00:00
// A[1] -> PPN[6]; A[2] -> PPN[5]; A[0] -> PPN[4]; A[6:3] -> PPN[3:0]
2024-03-07 02:51:19 +00:00
// A[11:10] -> LPN[9:8]; A[22:15] -> LPN[7:0] i.e. 1024 logical pages
2024-03-13 18:31:26 +00:00
physical = ( page & BitMask < 1 , 1 > : : value ) < < 5 ;
physical | = ( page & BitMask < 2 , 2 > : : value ) < < 3 ;
physical | = ( page & BitMask < 0 , 0 > : : value ) < < 4 ;
physical | = ( page & BitMask < 6 , 3 > : : value ) > > 3 ;
2024-03-07 02:51:19 +00:00
physical < < = 15 ;
2024-03-13 18:31:26 +00:00
logical = ( page & BitMask < 11 , 10 > : : value ) > > 2 ;
logical | = ( page & BitMask < 22 , 15 > : : value ) > > 15 ;
2024-03-07 02:51:19 +00:00
break ;
}
2024-03-14 14:53:38 +00:00
// printf("%08x => physical %d -> logical %d\n", page, (physical >> 15), logical);
2024-03-13 01:23:22 +00:00
2024-03-07 02:51:19 +00:00
// TODO: consider clashes.
// TODO: what if there's less than 4mb present?
mapping_ [ logical ] . target = & ram_ [ physical ] ;
mapping_ [ logical ] . protection_level = ( page > > 8 ) & 3 ;
}
2024-03-06 19:56:06 +00:00
}
2024-03-04 17:08:46 +00:00
} ;
2024-03-04 17:06:43 +00:00
class ConcreteMachine :
public Machine ,
2024-03-05 02:09:24 +00:00
public MachineTypes : : MediaTarget ,
2024-03-04 17:06:43 +00:00
public MachineTypes : : TimedMachine ,
public MachineTypes : : ScanProducer
{
2024-03-08 03:16:58 +00:00
// TODO: pick a sensible clock rate; this is just code for '24 MIPS, please'.
static constexpr int ClockRate = 24'000'000 ;
2024-03-07 16:12:40 +00:00
// Timers tick at 2Mhz, so figure out the proper divider for that.
static constexpr int TimerTarget = ClockRate / 2'000'000 ;
int timer_divider_ = TimerTarget ;
2024-03-04 17:06:43 +00:00
public :
ConcreteMachine (
const Analyser : : Static : : Target & target ,
const ROMMachine : : ROMFetcher & rom_fetcher
2024-03-12 15:34:31 +00:00
) : executor_ ( * this ) {
2024-03-07 16:12:40 +00:00
set_clock_rate ( ClockRate ) ;
2024-03-05 02:09:24 +00:00
constexpr ROM : : Name risc_os = ROM : : Name : : AcornRISCOS319 ;
ROM : : Request request ( risc_os ) ;
auto roms = rom_fetcher ( request ) ;
if ( ! request . validate ( roms ) ) {
throw ROMMachine : : Error : : MissingROMs ;
}
executor_ . bus . set_rom ( roms . find ( risc_os ) - > second ) ;
insert_media ( target . media ) ;
2024-03-04 17:06:43 +00:00
}
2024-03-12 15:34:31 +00:00
void did_write_ioc ( ) {
test_interrupts ( ) ;
}
2024-03-04 17:06:43 +00:00
2024-03-12 15:34:31 +00:00
private :
2024-03-04 17:06:43 +00:00
// MARK: - ScanProducer.
void set_scan_target ( Outputs : : Display : : ScanTarget * scan_target ) override {
( void ) scan_target ;
}
Outputs : : Display : : ScanStatus get_scaled_scan_status ( ) const override {
return Outputs : : Display : : ScanStatus ( ) ;
}
2024-03-19 15:34:10 +00:00
std : : array < uint32_t , 10 > pc_history ;
std : : size_t pc_history_ptr = 0 ;
uint32_t instr_count = 0 ;
2024-03-04 17:06:43 +00:00
// MARK: - TimedMachine.
void run_for ( Cycles cycles ) override {
2024-03-07 15:23:46 +00:00
static uint32_t last_pc = 0 ;
2024-03-19 19:03:31 +00:00
static bool log = false ;
2024-03-07 15:23:46 +00:00
2024-03-05 02:09:24 +00:00
auto instructions = cycles . as < int > ( ) ;
2024-03-07 16:12:40 +00:00
while ( instructions ) {
auto run_length = std : : min ( timer_divider_ , instructions ) ;
instructions - = run_length ;
timer_divider_ - = run_length ;
while ( run_length - - ) {
uint32_t instruction ;
2024-03-19 15:34:10 +00:00
pc_history [ pc_history_ptr ] = executor_ . pc ( ) ;
pc_history_ptr = ( pc_history_ptr + 1 ) % pc_history . size ( ) ;
2024-03-07 16:12:40 +00:00
if ( ! executor_ . bus . read ( executor_ . pc ( ) , instruction , executor_ . registers ( ) . mode ( ) , false ) ) {
logger . info ( ) . append ( " Prefetch abort at %08x; last good was at %08x " , executor_ . pc ( ) , last_pc ) ;
executor_ . prefetch_abort ( ) ;
// TODO: does a double abort cause a reset?
executor_ . bus . read ( executor_ . pc ( ) , instruction , executor_ . registers ( ) . mode ( ) , false ) ;
} else {
last_pc = executor_ . pc ( ) ;
}
// TODO: pipeline prefetch?
2024-03-20 00:26:17 +00:00
if ( executor_ . pc ( ) = = 0x03810bd8 ) {
printf ( " At %08x; after last PC %08x and %zu ago was %08x \n " , executor_ . pc ( ) , pc_history [ ( pc_history_ptr - 2 + pc_history . size ( ) ) % pc_history . size ( ) ] , pc_history . size ( ) , pc_history [ pc_history_ptr ] ) ;
}
log = executor_ . pc ( ) = = 0x03810bd8 ;
2024-03-07 19:28:39 +00:00
2024-03-07 16:39:26 +00:00
if ( log ) {
2024-03-19 15:34:10 +00:00
InstructionSet : : ARM : : Disassembler < arm_model > disassembler ;
InstructionSet : : ARM : : dispatch < arm_model > ( instruction , disassembler ) ;
2024-03-08 19:13:34 +00:00
auto info = logger . info ( ) ;
2024-03-19 15:34:10 +00:00
info . append ( " [%d] %08x: %08x \t \t %s \t prior:[ " ,
instr_count ,
executor_ . pc ( ) ,
instruction ,
disassembler . last ( ) . to_string ( executor_ . pc ( ) ) . c_str ( ) ) ;
2024-03-09 03:54:42 +00:00
for ( uint32_t c = 0 ; c < 15 ; c + + ) {
2024-03-08 19:13:34 +00:00
info . append ( " r%d:%08x " , c , executor_ . registers ( ) [ c ] ) ;
}
info . append ( " ] " ) ;
2024-03-07 16:39:26 +00:00
}
2024-03-14 14:43:51 +00:00
// logger.info().append("%08x: %08x", executor_.pc(), instruction);
2024-03-11 01:45:56 +00:00
InstructionSet : : ARM : : execute ( instruction , executor_ ) ;
2024-03-19 15:34:10 +00:00
+ + instr_count ;
2024-03-07 19:28:39 +00:00
// if(
2024-03-11 19:06:17 +00:00
// executor_.pc() > 0x038021d0 &&
2024-03-12 02:19:14 +00:00
// last_r1 != executor_.registers()[1]
// ||
2024-03-08 19:13:34 +00:00
// (
// last_link != executor_.registers()[14] ||
// last_r0 != executor_.registers()[0] ||
// last_r10 != executor_.registers()[10] ||
// last_r1 != executor_.registers()[1]
// )
2024-03-07 19:28:39 +00:00
// ) {
// logger.info().append("%08x modified R14 to %08x; R0 to %08x; R10 to %08x; R1 to %08x",
// last_pc,
// executor_.registers()[14],
// executor_.registers()[0],
// executor_.registers()[10],
// executor_.registers()[1]
// );
2024-03-12 02:19:14 +00:00
// logger.info().append("%08x modified R1 to %08x",
// last_pc,
// executor_.registers()[1]
// );
2024-03-07 19:28:39 +00:00
// last_link = executor_.registers()[14];
// last_r0 = executor_.registers()[0];
// last_r10 = executor_.registers()[10];
// last_r1 = executor_.registers()[1];
// }
2024-03-06 20:05:24 +00:00
}
2024-03-05 02:09:24 +00:00
2024-03-19 15:34:10 +00:00
if ( log ) {
printf ( " " ) ;
}
2024-03-07 16:12:40 +00:00
if ( ! timer_divider_ ) {
timer_divider_ = TimerTarget ;
2024-03-11 16:14:00 +00:00
if ( executor_ . bus . tick_timers ( ) ) {
2024-03-12 15:34:31 +00:00
test_interrupts ( ) ;
2024-03-11 16:14:00 +00:00
}
2024-03-07 16:12:40 +00:00
}
2024-03-05 02:09:24 +00:00
}
}
2024-03-12 15:34:31 +00:00
void test_interrupts ( ) {
using Exception = InstructionSet : : ARM : : Registers : : Exception ;
const int requests = executor_ . bus . interrupt_mask ( ) ;
if ( ( requests & InterruptRequests : : FIQ ) & & executor_ . registers ( ) . interrupt < Exception : : FIQ > ( ) ) {
return ;
}
if ( requests & InterruptRequests : : IRQ ) {
executor_ . registers ( ) . interrupt < Exception : : IRQ > ( ) ;
}
}
2024-03-05 02:09:24 +00:00
// MARK: - MediaTarget
bool insert_media ( const Analyser : : Static : : Media & ) override {
// int c = 0;
// for(auto &disk : media.disks) {
// fdc_.set_disk(disk, c);
// c++;
// if(c == 4) break;
// }
// return true;
return false ;
2024-03-04 17:06:43 +00:00
}
2024-03-04 17:08:46 +00:00
// MARK: - ARM execution
2024-03-05 02:09:24 +00:00
static constexpr auto arm_model = InstructionSet : : ARM : : Model : : ARMv2 ;
2024-03-12 15:34:31 +00:00
InstructionSet : : ARM : : Executor < arm_model , Memory < ConcreteMachine > > executor_ ;
2024-03-04 17:06:43 +00:00
} ;
}
using namespace Archimedes ;
std : : unique_ptr < Machine > Machine : : Archimedes ( const Analyser : : Static : : Target * target , const ROMMachine : : ROMFetcher & rom_fetcher ) {
return std : : make_unique < ConcreteMachine > ( * target , rom_fetcher ) ;
}