From 6f9406bbe3a91ec2b291a4b087390b38807f2997 Mon Sep 17 00:00:00 2001 From: Sidney Cadot Date: Tue, 17 Dec 2024 23:24:35 +0100 Subject: [PATCH] This adds timer functionality to sim65. It provides access to a handful of 64-bit counters that count different things: - clock cycles - instructions - number of IRQ processed - number of NMIs processed - nanoseconds since 1-1-1970. This in not ready yet to be pushed as a merge request into the upstream CC65 repository. What's lacking: - documentation - tests And to be discussed: - do we agree on this implementation direction and interface in principe? - can I include inttypes.h for printing a 64-bit unsigned value? - will clock_gettime() work on a Windows build? --- cfg/sim6502.cfg | 2 +- cfg/sim65c02.cfg | 2 +- src/sim65/6502.c | 13 +++- src/sim65/error.c | 8 +- src/sim65/main.c | 6 +- src/sim65/memory.c | 20 ++++- src/sim65/peripherals.c | 159 ++++++++++++++++++++++++++++++++++++++++ src/sim65/peripherals.h | 98 +++++++++++++++++++++++++ 8 files changed, 293 insertions(+), 15 deletions(-) create mode 100644 src/sim65/peripherals.c create mode 100644 src/sim65/peripherals.h 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/src/sim65/6502.c b/src/sim65/6502.c index 48a1d560d..f48ba8fee 100644 --- a/src/sim65/6502.c +++ b/src/sim65/6502.c @@ -42,6 +42,7 @@ */ #include "memory.h" +#include "peripherals.h" #include "error.h" #include "6502.h" #include "paravirt.h" @@ -391,7 +392,7 @@ CPUType CPU; typedef void (*OPFunc) (void); /* The CPU registers */ -static CPURegs Regs; +CPURegs Regs; /* Cycles for the current insn */ static unsigned Cycles; @@ -4107,6 +4108,8 @@ unsigned ExecuteInsn (void) if (HaveNMIRequest) { HaveNMIRequest = 0; + PRegs.counter_nmi_events += 1; + PUSH (PCH); PUSH (PCL); PUSH (Regs.SR & ~BF); @@ -4121,6 +4124,8 @@ unsigned ExecuteInsn (void) } else if (HaveIRQRequest && GET_IF () == 0) { HaveIRQRequest = 0; + PRegs.counter_irq_events += 1; + PUSH (PCH); PUSH (PCL); PUSH (Regs.SR & ~BF); @@ -4139,8 +4144,14 @@ unsigned ExecuteInsn (void) /* Execute it */ Handlers[CPU][OPC] (); + + /* Increment the instruction counter by one.NMIs and IRQs are counted separately. */ + PRegs.counter_instructions += 1; } + /* Increment the 64-bit clock cycle counter with the cycle count for the instruction that we just executed */ + PRegs.counter_clock_cycles += Cycles; + /* Return the number of clock cycles needed by this insn */ return Cycles; } diff --git a/src/sim65/error.c b/src/sim65/error.c index fc24ca006..3618d660f 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", PRegs.counter_clock_cycles); } 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..c4b6bb220 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. */ + PeripheralWriteByte (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 PeripheralReadByte (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..edf401b02 --- /dev/null +++ b/src/sim65/peripherals.c @@ -0,0 +1,159 @@ +/*****************************************************************************/ +/* */ +/* 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 "peripherals.h" + + +/*****************************************************************************/ +/* Data */ +/*****************************************************************************/ + + + +/* The peripheral registers. */ +PeripheralRegs PRegs; + + + +/*****************************************************************************/ +/* Code */ +/*****************************************************************************/ + + + +static uint64_t get_uint64_wallclock_time(void) +{ + struct timespec ts; + int result = clock_gettime(CLOCK_REALTIME, &ts); + if (result != 0) + { + // On failure, time will be set to the max value. + return 0xffffffffffffffff; + } + + /* Return time since the 1-1-1970 epoch, in nanoseconds. + * Note that this time may be off by an integer number of seconds, as POSIX + * maintaines that all days are 86,400 seconds long, which is not true due to + * leap seconds. + */ + return ts.tv_sec * 1000000000 + ts.tv_nsec; +} + + + +void PeripheralWriteByte (uint8_t Addr, uint8_t Val) +/* Write a byte to a memory location in the peripheral address aperture. */ +{ + switch (Addr) { + case PERIPHERALS_ADDRESS_OFFSET_LATCH: { + /* A write to the "latch" register performs a simultaneous latch of all registers */ + + /* Latch the current wallclock time first. */ + PRegs.latched_wallclock_time = get_uint64_wallclock_time(); + + /* Now latch all the cycles maintained by the processor. */ + PRegs.latched_counter_clock_cycles = PRegs.latched_counter_clock_cycles; + PRegs.latched_counter_instructions = PRegs.latched_counter_instructions; + PRegs.latched_counter_irq_events = PRegs.latched_counter_irq_events; + PRegs.latched_counter_nmi_events = PRegs.latched_counter_nmi_events; + break; + } + case PERIPHERALS_ADDRESS_OFFSET_SELECT: { + /* Set the value of the visibility-selection register. */ + PRegs.visible_latch_register = Val; + break; + } + default: { + /* Any other write is ignored */ + } + } +} + + + +uint8_t PeripheralReadByte (uint8_t Addr) +/* Read a byte from a memory location in the peripheral address aperture. */ +{ + switch (Addr) { + case PERIPHERALS_ADDRESS_OFFSET_SELECT: { + return PRegs.visible_latch_register; + } + case PERIPHERALS_ADDRESS_OFFSET_REG64 + 0: + case PERIPHERALS_ADDRESS_OFFSET_REG64 + 1: + case PERIPHERALS_ADDRESS_OFFSET_REG64 + 2: + case PERIPHERALS_ADDRESS_OFFSET_REG64 + 3: + case PERIPHERALS_ADDRESS_OFFSET_REG64 + 4: + case PERIPHERALS_ADDRESS_OFFSET_REG64 + 5: + case PERIPHERALS_ADDRESS_OFFSET_REG64 + 6: + case PERIPHERALS_ADDRESS_OFFSET_REG64 + 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 byte_select = Addr - PERIPHERALS_ADDRESS_OFFSET_REG64; /* 0 .. 7 */ + uint64_t value; + switch (PRegs.visible_latch_register) { + case PERIPHERALS_REG64_SELECT_CLOCKCYCLE_COUNTER: value = PRegs.latched_counter_clock_cycles; break; + case PERIPHERALS_REG64_SELECT_INSTRUCTION_COUNTER: value = PRegs.latched_counter_instructions; break; + case PERIPHERALS_REG64_SELECT_IRQ_COUNTER: value = PRegs.latched_counter_irq_events; break; + case PERIPHERALS_REG64_SELECT_NMI_COUNTER: value = PRegs.latched_counter_nmi_events; break; + case PERIPHERALS_REG64_SELECT_WALLCLOCK_TIME: value = PRegs.latched_wallclock_time; break; + default: value = 0; /* Reading from a non-supported register will yield 0. */ + } + /* Return the desired byte of the latched counter. 0==LSB, 7==MSB. */ + return value >> (byte_select * 8); + } + default: { + /* Any other read yields a zero value. */ + return 0; + } + } +} + + + +void PeripheralsInit (void) +/* Initialize the peripheral registers */ +{ + PRegs.counter_clock_cycles = 0; + PRegs.counter_instructions = 0; + PRegs.counter_irq_events = 0; + PRegs.counter_nmi_events = 0; + + PRegs.latched_counter_clock_cycles = 0; + PRegs.latched_counter_instructions = 0; + PRegs.latched_counter_irq_events = 0; + PRegs.latched_counter_nmi_events = 0; + PRegs.latched_wallclock_time = 0; + + PRegs.visible_latch_register = 0; +} diff --git a/src/sim65/peripherals.h b/src/sim65/peripherals.h new file mode 100644 index 000000000..76da6e2f8 --- /dev/null +++ b/src/sim65/peripherals.h @@ -0,0 +1,98 @@ +/*****************************************************************************/ +/* */ +/* 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 + +#define PERIPHERALS_APERTURE_BASE_ADDRESS 0xffc0 +#define PERIPHERALS_APERTURE_LAST_ADDRESS 0xffc9 + +#define PERIPHERALS_ADDRESS_OFFSET_LATCH 0x00 +#define PERIPHERALS_ADDRESS_OFFSET_SELECT 0x01 +#define PERIPHERALS_ADDRESS_OFFSET_REG64 0x02 + +#define PERIPHERALS_LATCH (PERIPHERALS_APERTURE_BASE_ADDRESS + PERIPHERALS_ADDRESS_OFFSET_LATCH) +#define PERIPHERALS_SELECT (PERIPHERALS_APERTURE_BASE_ADDRESS + PERIPHERALS_ADDRESS_OFFSET_SELECT) +#define PERIPHERALS_REG64 (PERIPHERALS_APERTURE_BASE_ADDRESS + PERIPHERALS_ADDRESS_OFFSET_REG64) + +#define PERIPHERALS_REG64_SELECT_CLOCKCYCLE_COUNTER 0x00 +#define PERIPHERALS_REG64_SELECT_INSTRUCTION_COUNTER 0x01 +#define PERIPHERALS_REG64_SELECT_IRQ_COUNTER 0x02 +#define PERIPHERALS_REG64_SELECT_NMI_COUNTER 0x03 +#define PERIPHERALS_REG64_SELECT_WALLCLOCK_TIME 0x80 + +typedef struct { + /* the invisible counters that are continuously updated */ + uint64_t counter_clock_cycles; + uint64_t counter_instructions; + uint64_t counter_irq_events; + uint64_t counter_nmi_events; + /* latched counters upon a write to the 'latch' address. + * One of these will be visible (read only) through an each-byte aperture. */ + uint64_t latched_counter_clock_cycles; + uint64_t latched_counter_instructions; + uint64_t latched_counter_irq_events; + uint64_t latched_counter_nmi_events; + uint64_t latched_wallclock_time; + /* Select which of the five latched registers will be visible. + * This is a Read/Write byte-wide register. + * If a non-existent register is selected, the 8-byte aperture will read as zero. + */ + uint8_t visible_latch_register; +} PeripheralRegs; + +extern PeripheralRegs PRegs; + +/*****************************************************************************/ +/* Code */ +/*****************************************************************************/ + + + +void PeripheralWriteByte (uint8_t Addr, uint8_t Val); +/* Write a byte to a memory location in the peripheral address aperture. */ + + +uint8_t PeripheralReadByte (uint8_t Addr); +/* Read a byte from a memory location in the peripheral address aperture. */ + + +void PeripheralsInit (void); +/* Initialize the peripheral registers */ + + + +/* End of peripherals.h */ + +#endif