diff --git a/cfg/sim6502.cfg b/cfg/sim6502.cfg index 39c33581c..d393a4aee 100644 --- a/cfg/sim6502.cfg +++ b/cfg/sim6502.cfg @@ -5,7 +5,7 @@ SYMBOLS { MEMORY { ZP: file = "", start = $0000, size = $0100; HEADER: file = %O, start = $0000, size = $000C; - MAIN: file = %O, define = yes, start = $0200, size = $FDF0 - __STACKSIZE__; + MAIN: file = %O, define = yes, start = $0200, size = $FDC0 - __STACKSIZE__; } SEGMENTS { ZEROPAGE: load = ZP, type = zp; diff --git a/cfg/sim65c02.cfg b/cfg/sim65c02.cfg index 39c33581c..d393a4aee 100644 --- a/cfg/sim65c02.cfg +++ b/cfg/sim65c02.cfg @@ -5,7 +5,7 @@ SYMBOLS { MEMORY { ZP: file = "", start = $0000, size = $0100; HEADER: file = %O, start = $0000, size = $000C; - MAIN: file = %O, define = yes, start = $0200, size = $FDF0 - __STACKSIZE__; + MAIN: file = %O, define = yes, start = $0200, size = $FDC0 - __STACKSIZE__; } SEGMENTS { ZEROPAGE: load = ZP, type = zp; diff --git a/doc/sim65.sgml b/doc/sim65.sgml index 962f07254..9f2914254 100644 --- a/doc/sim65.sgml +++ b/doc/sim65.sgml @@ -228,6 +228,105 @@ but if customization is needed +Counter peripheral + +

The sim65 simulator supports a memory-mapped counter peripheral that manages +a number of 64-bit counters that are continuously updated as the simulator is +running. For each counter, it also provides a 64 bit "latching" register. + +

The functionality of the counter peripheral is accessible through 3 registers: + + +PERIPHERALS_COUNTER_LATCH ($FFC0, write-only) +PERIPHERALS_COUNTER_SELECT ($FFC1, read/write) +PERIPHERALS_COUNTER_VALUE ($FFC2..$FFC9, read-only) + + +

These three registers are used as follows. + +

When a program explicitly requests a "counter latch" operation by writing any value +to the PERIPHERALS_COUNTER_LATCH address ($FFC0), all live registers are simultaneously +copied to the latch registers. They will keep their newly latched values until another latch +operation is requested. + +

The PERIPHERALS_COUNTER_SELECT address ($FFC1) register holds an 8-bit value that +specifies which 64-bit latch register is currently readable through the PERIPHERALS_COUNTER_VALUE +address range. Six values are currently defined: + + +$00: latched clock cycle counter selected. +$01: latched CPU instruction counter selected. +$02: latched IRQ interrupt counter selected. +$03: latched NMI interrupt counter selected. +$80: latched wallclock time (nanoseconds) selected. +$81: latched wallclock time (split: seconds, nanoseconds) selected. + + +

Values $00 to $03 provide access to the latched (frozen) value of their respective live +counters at the time of the last write to PERIPHERALS_COUNTER_LATCH. + +

When PERIPHERALS_COUNTER_SELECT equals $80, the PERIPHERALS_COUNTER_VALUE +will be a 64-bit value corresponding to the number of nanoseconds elapsed since the Unix epoch +(Midnight, Jan 1st, 1970 UTC), at the time of the last latch operation. + +

When PERIPHERALS_COUNTER_SELECT equals $81, the high 32 bits of PERIPHERALS_COUNTER_VALUE +will be a 32-bit value corresponding to the number of seconds elapsed since the Unix epoch (Midnight, Jan 1st, +1970 UTC), at the time of the last latch operation. The low 32 bits of +PERIPHERALS_COUNTER_VALUE will hold the nanoseconds since the start of that second. + +

The two different wallclock-time latch registers will always refer to precisely the same time instant. +For some applications, the single 64-bit value measured in nanoseconds will be more convenient, while +for other applications, the split 32/32 bits representation with separate second and nanosecond +values will be more convenient. + +

Note that the time elapsed since the Unix epoch is an approximation, as the implementation depends on the +way POSIX defines time-since-the-epoch. Unfortunately, POSIX incorrectly assumes that all days are precisely +86400 seconds long, which is not true in case of leap seconds. The way this inconsistency is resolved is +system dependent. + +

On reset, PERIPHERALS_COUNTER_SELECT is initialized to zero. If the PERIPHERALS_COUNTER_SELECT +register holds a value other than one of the six values described above, all PERIPHERALS_COUNTER_VALUE +bytes will read as zero. + +

The PERIPHERALS_COUNTER_VALUE addresses ($FFC2..$FFC9) are used to read to currently +selected 64-bit latch register value. Address $FFC2 holds the least significant byte (LSB), +while address $FFC9 holds the most significant byte (MSB). + +

On reset, all latch registers are reset to zero. Reading any of the PERIPHERALS_COUNTER_VALUE +bytes before the first write to PERIPHERALS_COUNTER_LATCH will yield zero. + +Example: + + +#include +#include + +volatile uint8_t * CounterLatch = (uint8_t *)0xffc0; +volatile uint8_t * CounterSelect = (uint8_t *)0xffc1; +volatile uint32_t * CounterValue = (uint32_t *)0xffc2; + +static void print_current_counters(void) +{ + *CounterLatch = 0; /* latch values */ + + *CounterSelect = 0x00; + printf("clock cycles ............... : %08lx %08lx\n", CounterValue[1], CounterValue[0]); + *CounterSelect = 0x01; + printf("instructions ............... : %08lx %08lx\n", CounterValue[1], CounterValue[0]); + *CounterSelect = 0x80; + printf("wallclock time ............. : %08lx %08lx\n", CounterValue[1], CounterValue[0]); + *CounterSelect = 0x81; + printf("wallclock time, split ...... : %08lx %08lx\n", CounterValue[1], CounterValue[0]); + printf("\n"); +} + +int main(void) +{ + print_current_counters(); + print_current_counters(); + return 0; +} + Copyright

diff --git a/src/sim65.vcxproj b/src/sim65.vcxproj index 7bc489398..07a9f7fb5 100644 --- a/src/sim65.vcxproj +++ b/src/sim65.vcxproj @@ -86,6 +86,7 @@ + @@ -93,8 +94,9 @@ + - \ No newline at end of file + diff --git a/src/sim65/6502.c b/src/sim65/6502.c index 079a2343a..be5afc036 100644 --- a/src/sim65/6502.c +++ b/src/sim65/6502.c @@ -46,6 +46,7 @@ #include #include "memory.h" +#include "peripherals.h" #include "error.h" #include "6502.h" #include "paravirt.h" @@ -4730,6 +4731,8 @@ unsigned ExecuteInsn (void) if (HaveNMIRequest) { HaveNMIRequest = false; + Peripherals.Counter.NmiEvents += 1; + PUSH (PCH); PUSH (PCL); PUSH (Regs.SR & ~BF); @@ -4744,6 +4747,8 @@ unsigned ExecuteInsn (void) } else if (HaveIRQRequest && GET_IF () == 0) { HaveIRQRequest = false; + Peripherals.Counter.IrqEvents += 1; + PUSH (PCH); PUSH (PCL); PUSH (Regs.SR & ~BF); @@ -4762,8 +4767,14 @@ unsigned ExecuteInsn (void) /* Execute it */ Handlers[CPU][OPC] (); + + /* Increment the instruction counter by one.NMIs and IRQs are counted separately. */ + Peripherals.Counter.CpuInstructions += 1; } + /* Increment the 64-bit clock cycle counter with the cycle count for the instruction that we just executed. */ + Peripherals.Counter.ClockCycles += Cycles; + /* Return the number of clock cycles needed by this instruction */ return Cycles; } diff --git a/src/sim65/error.c b/src/sim65/error.c index fc24ca006..af8e88413 100644 --- a/src/sim65/error.c +++ b/src/sim65/error.c @@ -36,9 +36,10 @@ #include #include #include +#include #include "error.h" - +#include "peripherals.h" /*****************************************************************************/ @@ -50,9 +51,6 @@ /* flag to print cycles at program termination */ int PrintCycles = 0; -/* cycles are counted by main.c */ -extern unsigned long long TotalCycles; - /*****************************************************************************/ @@ -120,7 +118,7 @@ void SimExit (int Code) /* Exit the simulation with an exit code */ { if (PrintCycles) { - fprintf (stdout, "%llu cycles\n", TotalCycles); + fprintf (stdout, "%" PRIu64 " cycles\n", Peripherals.Counter.ClockCycles); } exit (Code); } diff --git a/src/sim65/main.c b/src/sim65/main.c index 76c912c6b..8b41fcc0f 100644 --- a/src/sim65/main.c +++ b/src/sim65/main.c @@ -47,6 +47,7 @@ #include "6502.h" #include "error.h" #include "memory.h" +#include "peripherals.h" #include "paravirt.h" @@ -60,9 +61,6 @@ /* Name of program file */ const char* ProgramFile; -/* count of total cycles executed */ -unsigned long long TotalCycles = 0; - /* exit simulator after MaxCycles Cccles */ unsigned long long MaxCycles = 0; @@ -309,6 +307,7 @@ int main (int argc, char* argv[]) } MemInit (); + PeripheralsInit (); SPAddr = ReadProgramFile (); ParaVirtInit (I, SPAddr); @@ -318,7 +317,6 @@ int main (int argc, char* argv[]) RemainCycles = MaxCycles; while (1) { Cycles = ExecuteInsn (); - TotalCycles += Cycles; if (MaxCycles) { if (Cycles > RemainCycles) { ErrorCode (SIM65_ERROR_TIMEOUT, "Maximum number of cycles reached."); diff --git a/src/sim65/memory.c b/src/sim65/memory.c index b93693b91..c80bf0f93 100644 --- a/src/sim65/memory.c +++ b/src/sim65/memory.c @@ -36,7 +36,7 @@ #include #include "memory.h" - +#include "peripherals.h" /*****************************************************************************/ @@ -59,7 +59,14 @@ uint8_t Mem[0x10000]; void MemWriteByte (uint16_t Addr, uint8_t Val) /* Write a byte to a memory location */ { - Mem[Addr] = Val; + if ((PERIPHERALS_APERTURE_BASE_ADDRESS <= Addr) && (Addr <= PERIPHERALS_APERTURE_LAST_ADDRESS)) + { + /* Defer the the memory-mapped peripherals handler for this write. */ + PeripheralsWriteByte (Addr - PERIPHERALS_APERTURE_BASE_ADDRESS, Val); + } else { + /* Write to the Mem array. */ + Mem[Addr] = Val; + } } @@ -76,7 +83,14 @@ void MemWriteWord (uint16_t Addr, uint16_t Val) uint8_t MemReadByte (uint16_t Addr) /* Read a byte from a memory location */ { - return Mem[Addr]; + if ((PERIPHERALS_APERTURE_BASE_ADDRESS <= Addr) && (Addr <= PERIPHERALS_APERTURE_LAST_ADDRESS)) + { + /* Defer the the memory-mapped peripherals handler for this read. */ + return PeripheralsReadByte (Addr - PERIPHERALS_APERTURE_BASE_ADDRESS); + } else { + /* Read from the Mem array. */ + return Mem[Addr]; + } } diff --git a/src/sim65/peripherals.c b/src/sim65/peripherals.c new file mode 100644 index 000000000..4fa512ed8 --- /dev/null +++ b/src/sim65/peripherals.c @@ -0,0 +1,209 @@ +/*****************************************************************************/ +/* */ +/* peripherals.c */ +/* */ +/* Memory-mapped peripheral subsystem for the 6502 simulator */ +/* */ +/* */ +/* */ +/* (C) 2024-2025, Sidney Cadot */ +/* */ +/* */ +/* This software is provided 'as-is', without any expressed or implied */ +/* warranty. In no event will the authors be held liable for any damages */ +/* arising from the use of this software. */ +/* */ +/* Permission is granted to anyone to use this software for any purpose, */ +/* including commercial applications, and to alter it and redistribute it */ +/* freely, subject to the following restrictions: */ +/* */ +/* 1. The origin of this software must not be misrepresented; you must not */ +/* claim that you wrote the original software. If you use this software */ +/* in a product, an acknowledgment in the product documentation would be */ +/* appreciated but is not required. */ +/* 2. Altered source versions must be plainly marked as such, and must not */ +/* be misrepresented as being the original software. */ +/* 3. This notice may not be removed or altered from any source */ +/* distribution. */ +/* */ +/*****************************************************************************/ + +#include +#include +#if defined(__MINGW64__) +/* For gettimeofday() */ +#include +#endif + + +#include "peripherals.h" + + +/*****************************************************************************/ +/* Data */ +/*****************************************************************************/ + + + +/* The system-wide state of the peripherals */ +Sim65Peripherals Peripherals; + + + +/*****************************************************************************/ +/* Code */ +/*****************************************************************************/ + +static bool GetWallclockTime (struct timespec * ts) +/* Get the wallclock time with nanosecond resolution. */ +{ + /* Note: the 'struct timespec' type is available on all compilers we want to support. */ + + bool time_valid; + +#if defined(__MINGW64__) + /* When using the MinGW64 compiler, neither timespec_get() nor clock_gettime() + * are available; using either of them makes the Linux workflow build fail. + * The gettimeofday() function does work, so use that; its microsecond resolution + * is fine for most applications. + */ + struct timeval tv; + time_valid = (gettimeofday(&tv, NULL) == 0); + if (time_valid) { + ts->tv_sec = tv.tv_sec; + ts->tv_nsec = tv.tv_usec * 1000; + } +#elif defined(_MSC_VER) + /* Using the Microsoft C++ compiler. + * clock_gettime() is not available; use timespec_get() instead. + */ + time_valid = timespec_get(ts, TIME_UTC) == TIME_UTC; +#else + /* On all other compilers, assume that clock_gettime() is available. + * This is true on Linux and MacOS, at least. + */ + time_valid = clock_gettime(CLOCK_REALTIME, ts) == 0; +#endif + + return time_valid; +} + + + +void PeripheralsWriteByte (uint8_t Addr, uint8_t Val) +/* Write a byte to a memory location in the peripherals address aperture. */ +{ + switch (Addr) { + + /* Handle writes to the Counter peripheral. */ + + case PERIPHERALS_COUNTER_ADDRESS_OFFSET_LATCH: { + + /* A write to the "latch" register performs a simultaneous latch of all registers. */ + + /* Latch the current wallclock time before doing anything else. */ + + struct timespec ts; + bool time_valid = GetWallclockTime (&ts); + + if (time_valid) { + /* Wallclock time: number of nanoseconds since 1-1-1970. */ + Peripherals.Counter.LatchedWallclockTime = 1000000000 * (uint64_t)ts.tv_sec + ts.tv_nsec; + /* Wallclock time, split: high word is number of seconds since 1-1-1970, + * low word is number of nanoseconds since the start of that second. */ + Peripherals.Counter.LatchedWallclockTimeSplit = (uint64_t)ts.tv_sec << 32 | ts.tv_nsec; + } else { + /* Unable to get time. Report max uint64 value for both fields. */ + Peripherals.Counter.LatchedWallclockTime = -1; + Peripherals.Counter.LatchedWallclockTimeSplit = -1; + } + + /* Latch the counters that reflect the state of the processor. */ + Peripherals.Counter.LatchedClockCycles = Peripherals.Counter.ClockCycles; + Peripherals.Counter.LatchedCpuInstructions = Peripherals.Counter.CpuInstructions; + Peripherals.Counter.LatchedIrqEvents = Peripherals.Counter.IrqEvents; + Peripherals.Counter.LatchedNmiEvents = Peripherals.Counter.NmiEvents; + break; + } + case PERIPHERALS_COUNTER_ADDRESS_OFFSET_SELECT: { + /* Set the value of the visibility-selection register. */ + Peripherals.Counter.LatchedValueSelected = Val; + break; + } + + /* Handle writes to unused and read-only peripheral addresses. */ + + default: { + /* No action. */ + } + } +} + + + +uint8_t PeripheralsReadByte (uint8_t Addr) +/* Read a byte from a memory location in the peripherals address aperture. */ +{ + switch (Addr) { + + /* Handle reads from the Counter peripheral. */ + + case PERIPHERALS_COUNTER_ADDRESS_OFFSET_SELECT: { + return Peripherals.Counter.LatchedValueSelected; + } + case PERIPHERALS_COUNTER_ADDRESS_OFFSET_VALUE + 0: + case PERIPHERALS_COUNTER_ADDRESS_OFFSET_VALUE + 1: + case PERIPHERALS_COUNTER_ADDRESS_OFFSET_VALUE + 2: + case PERIPHERALS_COUNTER_ADDRESS_OFFSET_VALUE + 3: + case PERIPHERALS_COUNTER_ADDRESS_OFFSET_VALUE + 4: + case PERIPHERALS_COUNTER_ADDRESS_OFFSET_VALUE + 5: + case PERIPHERALS_COUNTER_ADDRESS_OFFSET_VALUE + 6: + case PERIPHERALS_COUNTER_ADDRESS_OFFSET_VALUE + 7: { + /* Read from any of the eight counter bytes. + * The first byte is the 64 bit value's LSB, the seventh byte is its MSB. + */ + unsigned SelectedByteIndex = Addr - PERIPHERALS_COUNTER_ADDRESS_OFFSET_VALUE; /* 0 .. 7 */ + uint64_t Value; + switch (Peripherals.Counter.LatchedValueSelected) { + case PERIPHERALS_COUNTER_SELECT_CLOCKCYCLE_COUNTER: Value = Peripherals.Counter.LatchedClockCycles; break; + case PERIPHERALS_COUNTER_SELECT_INSTRUCTION_COUNTER: Value = Peripherals.Counter.LatchedCpuInstructions; break; + case PERIPHERALS_COUNTER_SELECT_IRQ_COUNTER: Value = Peripherals.Counter.LatchedIrqEvents; break; + case PERIPHERALS_COUNTER_SELECT_NMI_COUNTER: Value = Peripherals.Counter.LatchedNmiEvents; break; + case PERIPHERALS_COUNTER_SELECT_WALLCLOCK_TIME: Value = Peripherals.Counter.LatchedWallclockTime; break; + case PERIPHERALS_COUNTER_SELECT_WALLCLOCK_TIME_SPLIT: Value = Peripherals.Counter.LatchedWallclockTimeSplit; break; + default: Value = 0; /* Reading from a non-existent latch register will yield 0. */ + } + /* Return the desired byte of the latched counter; 0==LSB, 7==MSB. */ + return (uint8_t)(Value >> (SelectedByteIndex * 8)); + } + + /* Handle reads from unused peripheral and write-only addresses. */ + + default: { + /* Return zero value. */ + return 0; + } + } +} + + + +void PeripheralsInit (void) +/* Initialize the peripherals. */ +{ + /* Initialize the Counter peripheral */ + + Peripherals.Counter.ClockCycles = 0; + Peripherals.Counter.CpuInstructions = 0; + Peripherals.Counter.IrqEvents = 0; + Peripherals.Counter.NmiEvents = 0; + + Peripherals.Counter.LatchedClockCycles = 0; + Peripherals.Counter.LatchedCpuInstructions = 0; + Peripherals.Counter.LatchedIrqEvents = 0; + Peripherals.Counter.LatchedNmiEvents = 0; + Peripherals.Counter.LatchedWallclockTime = 0; + Peripherals.Counter.LatchedWallclockTimeSplit = 0; + + Peripherals.Counter.LatchedValueSelected = 0; +} diff --git a/src/sim65/peripherals.h b/src/sim65/peripherals.h new file mode 100644 index 000000000..16ea83782 --- /dev/null +++ b/src/sim65/peripherals.h @@ -0,0 +1,120 @@ +/*****************************************************************************/ +/* */ +/* peripherals.h */ +/* */ +/* Memory-mapped peripheral subsystem for the 6502 simulator */ +/* */ +/* */ +/* */ +/* (C) 2024-2025, Sidney Cadot */ +/* */ +/* */ +/* This software is provided 'as-is', without any expressed or implied */ +/* warranty. In no event will the authors be held liable for any damages */ +/* arising from the use of this software. */ +/* */ +/* Permission is granted to anyone to use this software for any purpose, */ +/* including commercial applications, and to alter it and redistribute it */ +/* freely, subject to the following restrictions: */ +/* */ +/* 1. The origin of this software must not be misrepresented; you must not */ +/* claim that you wrote the original software. If you use this software */ +/* in a product, an acknowledgment in the product documentation would be */ +/* appreciated but is not required. */ +/* 2. Altered source versions must be plainly marked as such, and must not */ +/* be misrepresented as being the original software. */ +/* 3. This notice may not be removed or altered from any source */ +/* distribution. */ +/* */ +/*****************************************************************************/ + + + +#ifndef PERIPHERALS_H +#define PERIPHERALS_H + +#include + +/* The memory range where the memory-mapped peripherals can be accessed. */ + +#define PERIPHERALS_APERTURE_BASE_ADDRESS 0xffc0 +#define PERIPHERALS_APERTURE_LAST_ADDRESS 0xffc9 + +/* Declarations for the COUNTER peripheral (currently the only peripheral). */ + +#define PERIPHERALS_COUNTER_ADDRESS_OFFSET_LATCH 0x00 +#define PERIPHERALS_COUNTER_ADDRESS_OFFSET_SELECT 0x01 +#define PERIPHERALS_COUNTER_ADDRESS_OFFSET_VALUE 0x02 + +#define PERIPHERALS_COUNTER_LATCH (PERIPHERALS_APERTURE_BASE_ADDRESS + PERIPHERALS_ADDRESS_OFFSET_COUNTER_LATCH) +#define PERIPHERALS_COUNTER_SELECT (PERIPHERALS_APERTURE_BASE_ADDRESS + PERIPHERALS_ADDRESS_OFFSET_COUNTER_SELECT) +#define PERIPHERALS_COUNTER_VALUE (PERIPHERALS_APERTURE_BASE_ADDRESS + PERIPHERALS_ADDRESS_OFFSET_COUNTER) + +#define PERIPHERALS_COUNTER_SELECT_CLOCKCYCLE_COUNTER 0x00 +#define PERIPHERALS_COUNTER_SELECT_INSTRUCTION_COUNTER 0x01 +#define PERIPHERALS_COUNTER_SELECT_IRQ_COUNTER 0x02 +#define PERIPHERALS_COUNTER_SELECT_NMI_COUNTER 0x03 +#define PERIPHERALS_COUNTER_SELECT_WALLCLOCK_TIME 0x80 +#define PERIPHERALS_COUNTER_SELECT_WALLCLOCK_TIME_SPLIT 0x81 + +typedef struct { + /* The invisible counters that keep processor state. */ + uint64_t ClockCycles; + uint64_t CpuInstructions; + uint64_t IrqEvents; + uint64_t NmiEvents; + /* The 'latched_...' fields below hold values that are sampled upon a write + * to the PERIPHERALS_COUNTER_LATCH address. + * One of these will be visible (read only) through an eight-byte aperture. + * The purpose of these latched registers is to read 64-bit values one byte + * at a time, without having to worry that their content will change along + * the way. + */ + uint64_t LatchedClockCycles; + uint64_t LatchedCpuInstructions; + uint64_t LatchedIrqEvents; + uint64_t LatchedNmiEvents; + uint64_t LatchedWallclockTime; + uint64_t LatchedWallclockTimeSplit; + /* Select which of the six latched registers will be visible. + * This is a single byte, read/write register, accessible via address + * PERIPHERALS_COUNTER_SELECT. If a non-existent latch register is selected, + * the PERIPHERALS_COUNTER_VALUE will be zero. + */ + uint8_t LatchedValueSelected; +} CounterPeripheral; + + + +/* Declare the 'Sim65Peripherals' type and its single instance 'Peripherals'. */ + +typedef struct { + /* State of the peripherals available in sim65. + * Currently, there is only one peripheral: the Counter. */ + CounterPeripheral Counter; +} Sim65Peripherals; + +extern Sim65Peripherals Peripherals; + +/*****************************************************************************/ +/* Code */ +/*****************************************************************************/ + + + +void PeripheralsWriteByte (uint8_t Addr, uint8_t Val); +/* Write a byte to a memory location in the peripheral address aperture. */ + + +uint8_t PeripheralsReadByte (uint8_t Addr); +/* Read a byte from a memory location in the peripheral address aperture. */ + + +void PeripheralsInit (void); +/* Initialize the peripherals. */ + + + +/* End of peripherals.h */ + +#endif