mirror of
https://github.com/cc65/cc65.git
synced 2025-08-08 06:25:17 +00:00
Merge branch 'cc65:master' into struct-assignment-bug2566
This commit is contained in:
@@ -5,7 +5,7 @@ SYMBOLS {
|
|||||||
MEMORY {
|
MEMORY {
|
||||||
ZP: file = "", start = $0000, size = $0100;
|
ZP: file = "", start = $0000, size = $0100;
|
||||||
HEADER: file = %O, start = $0000, size = $000C;
|
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 {
|
SEGMENTS {
|
||||||
ZEROPAGE: load = ZP, type = zp;
|
ZEROPAGE: load = ZP, type = zp;
|
||||||
|
@@ -5,7 +5,7 @@ SYMBOLS {
|
|||||||
MEMORY {
|
MEMORY {
|
||||||
ZP: file = "", start = $0000, size = $0100;
|
ZP: file = "", start = $0000, size = $0100;
|
||||||
HEADER: file = %O, start = $0000, size = $000C;
|
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 {
|
SEGMENTS {
|
||||||
ZEROPAGE: load = ZP, type = zp;
|
ZEROPAGE: load = ZP, type = zp;
|
||||||
|
@@ -228,6 +228,105 @@ but if customization is needed <tt/sim6502.cfg/ or <tt/sim65c02.cfg/ might be us
|
|||||||
|
|
||||||
</itemize>
|
</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>
|
<sect>Copyright<p>
|
||||||
|
|
||||||
|
@@ -86,6 +86,7 @@
|
|||||||
<ClInclude Include="sim65\error.h" />
|
<ClInclude Include="sim65\error.h" />
|
||||||
<ClInclude Include="sim65\memory.h" />
|
<ClInclude Include="sim65\memory.h" />
|
||||||
<ClInclude Include="sim65\paravirt.h" />
|
<ClInclude Include="sim65\paravirt.h" />
|
||||||
|
<ClInclude Include="sim65\peripherals.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="sim65\6502.c" />
|
<ClCompile Include="sim65\6502.c" />
|
||||||
@@ -93,8 +94,9 @@
|
|||||||
<ClCompile Include="sim65\main.c" />
|
<ClCompile Include="sim65\main.c" />
|
||||||
<ClCompile Include="sim65\memory.c" />
|
<ClCompile Include="sim65\memory.c" />
|
||||||
<ClCompile Include="sim65\paravirt.c" />
|
<ClCompile Include="sim65\paravirt.c" />
|
||||||
|
<ClCompile Include="sim65\peripherals.c" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
<ImportGroup Label="ExtensionTargets">
|
<ImportGroup Label="ExtensionTargets">
|
||||||
</ImportGroup>
|
</ImportGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -46,6 +46,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
|
#include "peripherals.h"
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "6502.h"
|
#include "6502.h"
|
||||||
#include "paravirt.h"
|
#include "paravirt.h"
|
||||||
@@ -4730,6 +4731,8 @@ unsigned ExecuteInsn (void)
|
|||||||
if (HaveNMIRequest) {
|
if (HaveNMIRequest) {
|
||||||
|
|
||||||
HaveNMIRequest = false;
|
HaveNMIRequest = false;
|
||||||
|
Peripherals.Counter.NmiEvents += 1;
|
||||||
|
|
||||||
PUSH (PCH);
|
PUSH (PCH);
|
||||||
PUSH (PCL);
|
PUSH (PCL);
|
||||||
PUSH (Regs.SR & ~BF);
|
PUSH (Regs.SR & ~BF);
|
||||||
@@ -4744,6 +4747,8 @@ unsigned ExecuteInsn (void)
|
|||||||
} else if (HaveIRQRequest && GET_IF () == 0) {
|
} else if (HaveIRQRequest && GET_IF () == 0) {
|
||||||
|
|
||||||
HaveIRQRequest = false;
|
HaveIRQRequest = false;
|
||||||
|
Peripherals.Counter.IrqEvents += 1;
|
||||||
|
|
||||||
PUSH (PCH);
|
PUSH (PCH);
|
||||||
PUSH (PCL);
|
PUSH (PCL);
|
||||||
PUSH (Regs.SR & ~BF);
|
PUSH (Regs.SR & ~BF);
|
||||||
@@ -4762,8 +4767,14 @@ unsigned ExecuteInsn (void)
|
|||||||
|
|
||||||
/* Execute it */
|
/* Execute it */
|
||||||
Handlers[CPU][OPC] ();
|
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 the number of clock cycles needed by this instruction */
|
||||||
return Cycles;
|
return Cycles;
|
||||||
}
|
}
|
||||||
|
@@ -36,9 +36,10 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
|
#include "peripherals.h"
|
||||||
|
|
||||||
|
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
@@ -50,9 +51,6 @@
|
|||||||
/* flag to print cycles at program termination */
|
/* flag to print cycles at program termination */
|
||||||
int PrintCycles = 0;
|
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 */
|
/* Exit the simulation with an exit code */
|
||||||
{
|
{
|
||||||
if (PrintCycles) {
|
if (PrintCycles) {
|
||||||
fprintf (stdout, "%llu cycles\n", TotalCycles);
|
fprintf (stdout, "%" PRIu64 " cycles\n", Peripherals.Counter.ClockCycles);
|
||||||
}
|
}
|
||||||
exit (Code);
|
exit (Code);
|
||||||
}
|
}
|
||||||
|
@@ -47,6 +47,7 @@
|
|||||||
#include "6502.h"
|
#include "6502.h"
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
|
#include "peripherals.h"
|
||||||
#include "paravirt.h"
|
#include "paravirt.h"
|
||||||
|
|
||||||
|
|
||||||
@@ -60,9 +61,6 @@
|
|||||||
/* Name of program file */
|
/* Name of program file */
|
||||||
const char* ProgramFile;
|
const char* ProgramFile;
|
||||||
|
|
||||||
/* count of total cycles executed */
|
|
||||||
unsigned long long TotalCycles = 0;
|
|
||||||
|
|
||||||
/* exit simulator after MaxCycles Cccles */
|
/* exit simulator after MaxCycles Cccles */
|
||||||
unsigned long long MaxCycles = 0;
|
unsigned long long MaxCycles = 0;
|
||||||
|
|
||||||
@@ -309,6 +307,7 @@ int main (int argc, char* argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
MemInit ();
|
MemInit ();
|
||||||
|
PeripheralsInit ();
|
||||||
|
|
||||||
SPAddr = ReadProgramFile ();
|
SPAddr = ReadProgramFile ();
|
||||||
ParaVirtInit (I, SPAddr);
|
ParaVirtInit (I, SPAddr);
|
||||||
@@ -318,7 +317,6 @@ int main (int argc, char* argv[])
|
|||||||
RemainCycles = MaxCycles;
|
RemainCycles = MaxCycles;
|
||||||
while (1) {
|
while (1) {
|
||||||
Cycles = ExecuteInsn ();
|
Cycles = ExecuteInsn ();
|
||||||
TotalCycles += Cycles;
|
|
||||||
if (MaxCycles) {
|
if (MaxCycles) {
|
||||||
if (Cycles > RemainCycles) {
|
if (Cycles > RemainCycles) {
|
||||||
ErrorCode (SIM65_ERROR_TIMEOUT, "Maximum number of cycles reached.");
|
ErrorCode (SIM65_ERROR_TIMEOUT, "Maximum number of cycles reached.");
|
||||||
|
@@ -36,7 +36,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
|
#include "peripherals.h"
|
||||||
|
|
||||||
|
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
@@ -59,7 +59,14 @@ uint8_t Mem[0x10000];
|
|||||||
void MemWriteByte (uint16_t Addr, uint8_t Val)
|
void MemWriteByte (uint16_t Addr, uint8_t Val)
|
||||||
/* Write a byte to a memory location */
|
/* 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)
|
uint8_t MemReadByte (uint16_t Addr)
|
||||||
/* Read a byte from a memory location */
|
/* 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
209
src/sim65/peripherals.c
Normal 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
120
src/sim65/peripherals.h
Normal 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
|
Reference in New Issue
Block a user