1
0
mirror of https://github.com/cc65/cc65.git synced 2025-02-06 12:31:12 +00:00

Merge branch 'cc65:master' into struct-assignment-bug2566

This commit is contained in:
Sidney Cadot 2024-12-31 23:02:08 +01:00 committed by GitHub
commit 380a946faa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 466 additions and 15 deletions

View File

@ -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;

View File

@ -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;

View File

@ -228,6 +228,105 @@ but if customization is needed <tt/sim6502.cfg/ or <tt/sim65c02.cfg/ might be us
</itemize>
<sect>Counter peripheral
<p>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.
<p>The functionality of the counter peripheral is accessible through 3 registers:
<itemize>
<item><tt>PERIPHERALS_COUNTER_LATCH</tt> ($FFC0, write-only)
<item><tt>PERIPHERALS_COUNTER_SELECT</tt> ($FFC1, read/write)
<item><tt>PERIPHERALS_COUNTER_VALUE</tt> ($FFC2..$FFC9, read-only)
</itemize>
<p>These three registers are used as follows.
<p>When a program explicitly requests a "counter latch" operation by writing any value
to the <tt>PERIPHERALS_COUNTER_LATCH</tt> 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.
<p>The <tt>PERIPHERALS_COUNTER_SELECT</tt> address ($FFC1) register holds an 8-bit value that
specifies which 64-bit latch register is currently readable through the <tt>PERIPHERALS_COUNTER_VALUE</tt>
address range. Six values are currently defined:
<itemize>
<item>$00: latched clock cycle counter selected.
<item>$01: latched CPU instruction counter selected.
<item>$02: latched IRQ interrupt counter selected.
<item>$03: latched NMI interrupt counter selected.
<item>$80: latched wallclock time (nanoseconds) selected.
<item>$81: latched wallclock time (split: seconds, nanoseconds) selected.
</itemize>
<p>Values $00 to $03 provide access to the latched (frozen) value of their respective live
counters at the time of the last write to <tt>PERIPHERALS_COUNTER_LATCH</tt>.
<p>When <tt>PERIPHERALS_COUNTER_SELECT</tt> equals $80, the <tt>PERIPHERALS_COUNTER_VALUE</tt>
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.
<p>When <tt>PERIPHERALS_COUNTER_SELECT</tt> equals $81, the high 32 bits of <tt>PERIPHERALS_COUNTER_VALUE</tt>
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
<tt>PERIPHERALS_COUNTER_VALUE</tt> will hold the nanoseconds since the start of that second.
<p>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.
<p>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.
<p>On reset, <tt>PERIPHERALS_COUNTER_SELECT</tt> is initialized to zero. If the <tt>PERIPHERALS_COUNTER_SELECT</tt>
register holds a value other than one of the six values described above, all <tt>PERIPHERALS_COUNTER_VALUE</tt>
bytes will read as zero.
<p>The <tt>PERIPHERALS_COUNTER_VALUE</tt> 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).
<p>On reset, all latch registers are reset to zero. Reading any of the <tt>PERIPHERALS_COUNTER_VALUE</tt>
bytes before the first write to <tt>PERIPHERALS_COUNTER_LATCH</tt> will yield zero.
Example:
<tscreen><verb>
#include <stdio.h>
#include <stdint.h>
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;
}
</verb></tscreen>
<sect>Copyright<p>

View File

@ -86,6 +86,7 @@
<ClInclude Include="sim65\error.h" />
<ClInclude Include="sim65\memory.h" />
<ClInclude Include="sim65\paravirt.h" />
<ClInclude Include="sim65\peripherals.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="sim65\6502.c" />
@ -93,8 +94,9 @@
<ClCompile Include="sim65\main.c" />
<ClCompile Include="sim65\memory.c" />
<ClCompile Include="sim65\paravirt.c" />
<ClCompile Include="sim65\peripherals.c" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -46,6 +46,7 @@
#include <stdint.h>
#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;
}

View File

@ -36,9 +36,10 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <inttypes.h>
#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);
}

View File

@ -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.");

View File

@ -36,7 +36,7 @@
#include <string.h>
#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];
}
}

209
src/sim65/peripherals.c Normal file
View File

@ -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 <stdbool.h>
#include <time.h>
#if defined(__MINGW64__)
/* For gettimeofday() */
#include <sys/time.h>
#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;
}

120
src/sim65/peripherals.h Normal file
View File

@ -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 <stdint.h>
/* 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