mirror of
https://github.com/cc65/cc65.git
synced 2025-03-03 09:32:33 +00:00
Merge pull request #2593 from sidneycadot/sim65-add-peripheral-support-and-docs-and-samples
sim65: add C/assembly support, docs, and samples for the new peripheral functionality.
This commit is contained in:
commit
9fd090ae8b
75
asminc/sim65.inc
Normal file
75
asminc/sim65.inc
Normal file
@ -0,0 +1,75 @@
|
||||
|
||||
; *******************************************************************************
|
||||
; ** **
|
||||
; ** sim65.inc : assembler definitions for the sim6502 and sim65c02 targets. **
|
||||
; ** **
|
||||
; ** Sidney Cadot, January 2025 **
|
||||
; ** **
|
||||
; *******************************************************************************
|
||||
|
||||
; The '_peripherals' symbol is defined in the linker configuration
|
||||
; file to correspond to the first address in the periperal memory
|
||||
; aparture.
|
||||
;
|
||||
; We use it here as a base address for all peripheral addresses.
|
||||
|
||||
.import _peripherals
|
||||
|
||||
; **************************************************************
|
||||
; ** **
|
||||
; ** Define assembler symbols for the "counter" peripheral. **
|
||||
; ** **
|
||||
; **************************************************************
|
||||
|
||||
peripheral_counter_base := _peripherals + 0
|
||||
|
||||
peripheral_counter_latch := peripheral_counter_base + 0
|
||||
peripheral_counter_select := peripheral_counter_base + 1
|
||||
peripheral_counter_value := peripheral_counter_base + 2
|
||||
|
||||
; Values for the peripheral_counter_select register.
|
||||
|
||||
COUNTER_SELECT_CLOCKCYCLE_COUNTER = $00
|
||||
COUNTER_SELECT_INSTRUCTION_COUNTER = $01
|
||||
COUNTER_SELECT_IRQ_COUNTER = $02
|
||||
COUNTER_SELECT_NMI_COUNTER = $03
|
||||
COUNTER_SELECT_WALLCLOCK_TIME = $80
|
||||
COUNTER_SELECT_WALLCLOCK_TIME_SPLIT = $81
|
||||
|
||||
; ********************************************************************
|
||||
; ** **
|
||||
; ** Define assembler symbols for the "sim65 control" peripheral. **
|
||||
; ** **
|
||||
; ********************************************************************
|
||||
|
||||
peripheral_sim65_base := _peripherals + 10
|
||||
|
||||
peripheral_sim65_cpu_mode := peripheral_sim65_base + 0
|
||||
peripheral_sim65_trace_mode := peripheral_sim65_base + 1
|
||||
|
||||
; Values for the peripheral_sim65_cpu_mode register.
|
||||
|
||||
SIM65_CPU_MODE_6502 = $00
|
||||
SIM65_CPU_MODE_65C02 = $01
|
||||
SIM65_CPU_MODE_6502X = $02
|
||||
|
||||
; Bitfield values for the peripheral_sim65_trace_mode field.
|
||||
|
||||
SIM65_TRACE_MODE_FIELD_INSTR_COUNTER = $40
|
||||
SIM65_TRACE_MODE_FIELD_CLOCK_COUNTER = $20
|
||||
SIM65_TRACE_MODE_FIELD_PC = $10
|
||||
SIM65_TRACE_MODE_FIELD_INSTR_BYTES = $08
|
||||
SIM65_TRACE_MODE_FIELD_INSTR_ASSEMBLY = $04
|
||||
SIM65_TRACE_MODE_FIELD_CPU_REGISTERS = $02
|
||||
SIM65_TRACE_MODE_FIELD_CC65_SP = $01
|
||||
|
||||
; Values for the peripheral_sim65_trace_mode field that fully disable / enable tracing.
|
||||
|
||||
SIM65_TRACE_MODE_DISABLE = $00
|
||||
SIM65_TRACE_MODE_ENABLE_FULL = $7F
|
||||
|
||||
; ************************
|
||||
; ** **
|
||||
; ** End of sim65.inc **
|
||||
; ** **
|
||||
; ************************
|
@ -1,12 +1,15 @@
|
||||
SYMBOLS {
|
||||
__EXEHDR__: type = import;
|
||||
__STACKSIZE__: type = weak, value = $0800; # 2k stack
|
||||
_peripherals: type = export, value = $FFC0;
|
||||
}
|
||||
|
||||
MEMORY {
|
||||
ZP: file = "", start = $0000, size = $0100;
|
||||
HEADER: file = %O, start = $0000, size = $000C;
|
||||
MAIN: file = %O, define = yes, start = $0200, size = $FDC0 - __STACKSIZE__;
|
||||
MAIN: file = %O, define = yes, start = $0200, size = $FFC0 - $0200 - __STACKSIZE__;
|
||||
}
|
||||
|
||||
SEGMENTS {
|
||||
ZEROPAGE: load = ZP, type = zp;
|
||||
EXEHDR: load = HEADER, type = ro;
|
||||
@ -18,6 +21,7 @@ SEGMENTS {
|
||||
DATA: load = MAIN, type = rw;
|
||||
BSS: load = MAIN, type = bss, define = yes;
|
||||
}
|
||||
|
||||
FEATURES {
|
||||
CONDES: type = constructor,
|
||||
label = __CONSTRUCTOR_TABLE__,
|
||||
|
@ -1,12 +1,15 @@
|
||||
SYMBOLS {
|
||||
__EXEHDR__: type = import;
|
||||
__STACKSIZE__: type = weak, value = $0800; # 2k stack
|
||||
_peripherals: type = export, value = $FFC0;
|
||||
}
|
||||
|
||||
MEMORY {
|
||||
ZP: file = "", start = $0000, size = $0100;
|
||||
HEADER: file = %O, start = $0000, size = $000C;
|
||||
MAIN: file = %O, define = yes, start = $0200, size = $FDC0 - __STACKSIZE__;
|
||||
MAIN: file = %O, define = yes, start = $0200, size = $FFC0 - $0200 - __STACKSIZE__;
|
||||
}
|
||||
|
||||
SEGMENTS {
|
||||
ZEROPAGE: load = ZP, type = zp;
|
||||
EXEHDR: load = HEADER, type = ro;
|
||||
@ -18,6 +21,7 @@ SEGMENTS {
|
||||
DATA: load = MAIN, type = rw;
|
||||
BSS: load = MAIN, type = bss, define = yes;
|
||||
}
|
||||
|
||||
FEATURES {
|
||||
CONDES: type = constructor,
|
||||
label = __CONSTRUCTOR_TABLE__,
|
||||
|
135
doc/sim65.sgml
135
doc/sim65.sgml
@ -40,6 +40,8 @@ The simulator is called as follows:
|
||||
Long options:
|
||||
--help Help (this text)
|
||||
--cycles Print amount of executed CPU cycles
|
||||
--cpu <type> Override CPU type (6502, 65C02, 6502X)
|
||||
--trace Enable CPU trace
|
||||
--verbose Increase verbosity
|
||||
--version Print the simulator version number
|
||||
</verb></tscreen>
|
||||
@ -70,6 +72,17 @@ Here is a description of all the command line options:
|
||||
count.
|
||||
|
||||
|
||||
<tag><tt>--cpu <type></tt></tag>
|
||||
|
||||
Specify the CPU type to use while executing the program. This CPU type
|
||||
is normally determined from the program file header, but it can be useful
|
||||
to override it.
|
||||
|
||||
<tag><tt>--trace</tt></tag>
|
||||
|
||||
Print a single line of information for each instruction or interrupt that
|
||||
is executed by the CPU to stdout.
|
||||
|
||||
<tag><tt>-v, --verbose</tt></tag>
|
||||
|
||||
Increase the simulator verbosity.
|
||||
@ -298,25 +311,23 @@ bytes before the first write to <tt>PERIPHERALS_COUNTER_LATCH</tt> will yield ze
|
||||
Example:
|
||||
|
||||
<tscreen><verb>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
/* This example uses the peripheral support in sim65.h */
|
||||
|
||||
volatile uint8_t * CounterLatch = (uint8_t *)0xffc0;
|
||||
volatile uint8_t * CounterSelect = (uint8_t *)0xffc1;
|
||||
volatile uint32_t * CounterValue = (uint32_t *)0xffc2;
|
||||
#include <stdio.h>
|
||||
#include <sim65.h>
|
||||
|
||||
static void print_current_counters(void)
|
||||
{
|
||||
*CounterLatch = 0; /* latch values */
|
||||
peripherals.counter.latch = 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]);
|
||||
peripherals.counter.select = COUNTER_SELECT_CLOCKCYCLE_COUNTER;
|
||||
printf("clock cycles ............... : %08lx %08lx\n", peripherals.counter.value32[1], peripherals.counter.value32[0]);
|
||||
peripherals.counter.select = COUNTER_SELECT_INSTRUCTION_COUNTER;
|
||||
printf("instructions ............... : %08lx %08lx\n", peripherals.counter.value32[1], peripherals.counter.value32[0]);
|
||||
peripherals.counter.select = COUNTER_SELECT_WALLCLOCK_TIME;
|
||||
printf("wallclock time ............. : %08lx %08lx\n", peripherals.counter.value32[1], peripherals.counter.value32[0]);
|
||||
peripherals.counter.select = COUNTER_SELECT_WALLCLOCK_TIME_SPLIT;
|
||||
printf("wallclock time, split ...... : %08lx %08lx\n", peripherals.counter.value32[1], peripherals.counter.value32[0]);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
@ -328,6 +339,102 @@ int main(void)
|
||||
}
|
||||
</verb></tscreen>
|
||||
|
||||
<sect>SIM65 control peripheral
|
||||
|
||||
<p>The sim65 simulator supports a memory-mapped peripheral that allows control
|
||||
of the simulator behavior itself.
|
||||
|
||||
<p>The sim65 control peripheral interface consists of 2 registers:
|
||||
|
||||
<itemize>
|
||||
<item><tt>PERIPHERALS_SIMCONTROL_CPUMODE</tt> ($FFCA, read/write)
|
||||
<item><tt>PERIPHERALS_SIMCONTROL_TRACEMODE</tt> ($FFCB, read/write)
|
||||
</itemize>
|
||||
|
||||
<p>Address <tt>PERIPHERALS_SIMCONTROL_CPUMODE</tt> allows access to the currently active CPU mode.
|
||||
|
||||
<p>Possible values are CPU_6502 (0), CPU_65C02 (1), and CPU_6502X (2). For specialized applications,
|
||||
it may be useful to switch CPU models at runtime; this is supported by writing 0, 1, or 2 to this address.
|
||||
Writing any other value will be ignored.
|
||||
|
||||
<p>Address <tt>PERIPHERALS_SIMCONTROL_TRACEMODE</tt> allows inspection and control of the currently active
|
||||
CPU tracing mode.
|
||||
|
||||
<p>A value of 0 means tracing is disabled; a value of $7F fully enables tracing. The 7
|
||||
lower bits of the value actually provide control over which fields are printed; see below
|
||||
for an explanation of the seven fields.
|
||||
|
||||
<p>Having the ability to enable/disable tracing on the fly can be a useful debugging aid. For example,
|
||||
it can be used to enable tracing for short fragments of code. Consider the following example:
|
||||
|
||||
<tscreen><verb>
|
||||
/* This example uses the TRACE_ON and TRACE_OFF macros defined in sim65.h */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sim65.h>
|
||||
|
||||
unsigned x;
|
||||
|
||||
int main(void)
|
||||
{
|
||||
TRACE_ON();
|
||||
|
||||
x = 0x1234; /* We want to see what happens here. */
|
||||
|
||||
TRACE_OFF();
|
||||
|
||||
return 0;
|
||||
}
|
||||
</verb></tscreen>
|
||||
|
||||
<p>This small test program, when compiled with optimizations enabled (-O), produces the output trace below:
|
||||
|
||||
<tscreen><verb>
|
||||
70 232 022E A2 12 ldx #$12 A=7F X=00 Y=04 S=FD Flags=nvdizC SP=FFBC
|
||||
71 234 0230 A9 34 lda #$34 A=7F X=12 Y=04 S=FD Flags=nvdizC SP=FFBC
|
||||
72 236 0232 8D C8 02 sta $02C8 A=34 X=12 Y=04 S=FD Flags=nvdizC SP=FFBC
|
||||
73 240 0235 8E C9 02 stx $02C9 A=34 X=12 Y=04 S=FD Flags=nvdizC SP=FFBC
|
||||
74 244 0238 A9 00 lda #$00 A=34 X=12 Y=04 S=FD Flags=nvdizC SP=FFBC
|
||||
75 246 023A 8D CB FF sta $FFCB A=00 X=12 Y=04 S=FD Flags=nvdiZC SP=FFBC
|
||||
</verb></tscreen>
|
||||
|
||||
<p>The example output shows the full trace format, consisting of the following seven fields:
|
||||
|
||||
<itemize>
|
||||
<item>The first field is an instruction counter. We see here that the assignment '<tt>x = 0x1234;</tt>'
|
||||
starts at the 70th CPU instruction since the start of the simulator, and takes four 6502 instructions.
|
||||
The two instructions that follow correspond to the execution of the <tt>TRACE_OFF</tt>' macro
|
||||
that disables tracing.
|
||||
<item>The second field shows the clock cycles since the start of the program. Here we see that the
|
||||
first four instructions take 12 clock cycles in total (262 - 250 = 12).
|
||||
<item>The third field shows the program counter as a four-digit, i.e., the PC register. Its 16-bit
|
||||
value is displayed as a 4-digit hecadecimal number.
|
||||
<item>The fourth field shows one to three hexadecimal byte values that make up the instruction.
|
||||
<item>The fifth field shows the instruction in human-readable assembly language.
|
||||
<item>The sixth field shows the CPU registers before execution of the instruction. The A, X, Y, and
|
||||
S registers are each shown as a single byte value. The six status bits of the CPU are shown in
|
||||
the order NVDIZC (Negative, Overflow, Decimal, Interrupt, Zero, Carry). They are displayed as
|
||||
a capital letter if the flag is set, or a small letter if the flag is unset.
|
||||
<item>The seventh and last field shows the software stack pointer SP as used by CC65 programs that
|
||||
conform to the CC65 conventions.
|
||||
</itemize>
|
||||
|
||||
<p>Writing a specific value to <tt>PERIPHERALS_SIMCONTROL_TRACEMODE</tt> will control which of these
|
||||
seven fields are displayed. The following values are defined to denote the seven fields:
|
||||
|
||||
<itemize>
|
||||
<item>TRACE_FIELD_INSTR_COUNTER = 0x40
|
||||
<item>TRACE_FIELD_CLOCK_COUNTER = 0x20
|
||||
<item>TRACE_FIELD_PC = 0x10
|
||||
<item>TRACE_FIELD_INSTR_BYTES = 0x08
|
||||
<item>TRACE_FIELD_INSTR_ASSEMBLY = 0x04
|
||||
<item>TRACE_FIELD_CPU_REGISTERS = 0x02
|
||||
<item>TRACE_FIELD_CC65_SP = 0x01
|
||||
</itemize>
|
||||
|
||||
<p>For example, writing the value $16 to <tt>PERIPHERALS_SIMCONTROL_TRACEMODE</tt> will only display
|
||||
the program counter, instruction assembly, and CPU registers fields.
|
||||
|
||||
<sect>Copyright<p>
|
||||
|
||||
sim65 (and all cc65 binutils) are (C) Copyright 1998-2000 Ullrich von
|
||||
|
136
include/sim65.h
Normal file
136
include/sim65.h
Normal file
@ -0,0 +1,136 @@
|
||||
/*****************************************************************************/
|
||||
/* */
|
||||
/* sim65.h */
|
||||
/* */
|
||||
/* Definitions for the sim6502 and sim65c02 targets */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* (C) 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 _SIM65_H
|
||||
#define _SIM65_H
|
||||
|
||||
/* Check that we include this file while compiling to a compatible target. */
|
||||
#if !defined(__SIM6502__) && !defined(__SIM65C02__)
|
||||
# error This module may only be used when compiling for the sim6502 or sim65c02 targets!
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* The sim65 targets (sim6502, sim65c02) have a peripheral memory aperture located at
|
||||
* address range 0xFFC0 .. 0xFFDF. Currently, the following peripherals are located
|
||||
* inside that memory apeture:
|
||||
*
|
||||
* $FFC0 .. $FFC9 "counter" peripheral
|
||||
* $FFCA .. $FFCB "sim65 control" peripheral
|
||||
* $FFCC .. $FFDF (currently unused)
|
||||
*
|
||||
* The "peripherals" structure below corresponds to the register layout of the currently
|
||||
* defined peripherals in this memory range. Combined with the fact that the sim6502 and
|
||||
* sim65c02 linker configuration files define the "peripherals" symbol to be fixed at
|
||||
* address $FFC0, this provides easy-to-use and efficient access to the peripheral registers.
|
||||
*
|
||||
* After including "sim65.h", it is possible for a C program to do things like:
|
||||
*
|
||||
* {
|
||||
* peripherals.counter.latch = 0;
|
||||
* peripherals.sim65.cpu_mode = SIM65_CPU_MODE_6502X;
|
||||
* peripherals.sim65.trace_mode = SIM65_TRACE_MODE_ENABLE_FULL;
|
||||
* }
|
||||
*
|
||||
* Note that "peripherals" variable is declared volatile. This instructs a C compiler to
|
||||
* forego optimizations on memory accesses to the variable. However, CC65 currently ignores
|
||||
* the volatile attribute. Fortunately, it is not smart with respect to optimizing
|
||||
* memory accesses, so accessing the "peripherals" fields works fine in practice.
|
||||
*/
|
||||
|
||||
extern volatile struct {
|
||||
struct {
|
||||
uint8_t latch;
|
||||
uint8_t select;
|
||||
union {
|
||||
uint8_t value [8]; /* Access value as eight separate bytes. */
|
||||
uint16_t value16 [4]; /* Access value as four 16-bit words. */
|
||||
uint32_t value32 [2]; /* Access value as two 32-bit long words. */
|
||||
};
|
||||
} counter;
|
||||
struct {
|
||||
uint8_t cpu_mode;
|
||||
uint8_t trace_mode;
|
||||
} sim65;
|
||||
} peripherals;
|
||||
|
||||
/* Values for the peripherals.counter.select field. */
|
||||
#define COUNTER_SELECT_CLOCKCYCLE_COUNTER 0x00
|
||||
#define COUNTER_SELECT_INSTRUCTION_COUNTER 0x01
|
||||
#define COUNTER_SELECT_IRQ_COUNTER 0x02
|
||||
#define COUNTER_SELECT_NMI_COUNTER 0x03
|
||||
#define COUNTER_SELECT_WALLCLOCK_TIME 0x80
|
||||
#define COUNTER_SELECT_WALLCLOCK_TIME_SPLIT 0x81
|
||||
|
||||
/* Values for the peripherals.sim65.cpu_mode field. */
|
||||
#define SIM65_CPU_MODE_6502 0x00
|
||||
#define SIM65_CPU_MODE_65C02 0x01
|
||||
#define SIM65_CPU_MODE_6502X 0x02
|
||||
|
||||
/* Bitfield values for the peripherals.sim65.trace_mode field. */
|
||||
#define SIM65_TRACE_MODE_FIELD_INSTR_COUNTER 0x40
|
||||
#define SIM65_TRACE_MODE_FIELD_CLOCK_COUNTER 0x20
|
||||
#define SIM65_TRACE_MODE_FIELD_PC 0x10
|
||||
#define SIM65_TRACE_MODE_FIELD_INSTR_BYTES 0x08
|
||||
#define SIM65_TRACE_MODE_FIELD_INSTR_ASSEMBLY 0x04
|
||||
#define SIM65_TRACE_MODE_FIELD_CPU_REGISTERS 0x02
|
||||
#define SIM65_TRACE_MODE_FIELD_CC65_SP 0x01
|
||||
|
||||
/* Values for the peripherals.sim65.trace_mode field that fully disable / enable tracing. */
|
||||
#define SIM65_TRACE_MODE_DISABLE 0x00
|
||||
#define SIM65_TRACE_MODE_ENABLE_FULL 0x7F
|
||||
|
||||
/* Convenience macros to enable / disable tracing at runtime. */
|
||||
#define TRACE_ON() do peripherals.sim65.trace_mode = SIM65_TRACE_MODE_ENABLE_FULL; while(0)
|
||||
#define TRACE_OFF() do peripherals.sim65.trace_mode = SIM65_TRACE_MODE_DISABLE; while(0)
|
||||
|
||||
/* Convenience macro to query the CPU mode at runtime. */
|
||||
#define GET_CPU_MODE() peripherals.sim65.cpu_mode
|
||||
|
||||
/* Convenience macro to set the CPU mode at runtime.
|
||||
*
|
||||
* Use SIM65_CPU_MODE_6502, SIM65_CPU_MODE_65C02, or SIM65_CPU_MODE_6502 as argument.
|
||||
*
|
||||
* Important Note:
|
||||
*
|
||||
* When running in a program compiled for the "sim6502" target, it is safe to switch to
|
||||
* 65C02 or 6502X mode, since the runtime library will only use plain 6502 opcodes, and
|
||||
* those work the same in 65C02 and 6502X mode.
|
||||
*
|
||||
* However, when running in a program compiled for the "sim65c02" target, it is NOT safe
|
||||
* to switch to 6502 or 6502X mode, since many routines in the runtime library use
|
||||
* 65C02-specific opcodes, and these will not work as expected when the CPU is switched
|
||||
* to 6502 or 6502X mode. When such an instruction is encountered, the program will
|
||||
* exhibit undefined behavior.
|
||||
*/
|
||||
#define SET_CPU_MODE(mode) do peripherals.sim65.cpu_mode = mode; while(0)
|
||||
|
||||
/* End of sim65.h */
|
||||
#endif
|
104
samples/sim65/cpumode_example.c
Normal file
104
samples/sim65/cpumode_example.c
Normal file
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Sim65 cpu-mode switching example.
|
||||
*
|
||||
* Description
|
||||
* -----------
|
||||
*
|
||||
* We can inspect and manipulate the CPU model that sim65 emulates at runtime.
|
||||
*
|
||||
* Sim65 always runs in one of three modes:
|
||||
*
|
||||
* - 6502 mode: the 151 documented opcodes are supported; if the processor encounters
|
||||
* one of the 105 undocumented opcodes, the simulator ends with an
|
||||
* 'illegal opcode' message.
|
||||
* - 65C02 mode: the 105 undocumented opcodes now have well-defined behavior. Some
|
||||
* do useful things, while all others are now defined as NOPs.
|
||||
* - 6502X mode: the 105 undocumented opcodes don't have documented behavior, but
|
||||
* they /do/ have behavior on a real 6502. This behavior has been
|
||||
* figured out, and is deterministic (with minor exceptions).
|
||||
* In this mode, sim65 mimics the behavior of a real 6502 when
|
||||
* it encounters an undocumented opcode, rather than terminating.
|
||||
*
|
||||
* In the example below, we first switch to 6502X mode and execute a small
|
||||
* assembly code fragment, then repeat this in 65C02 mode.
|
||||
*
|
||||
* The code fragment is designed to distinguish between a 6502 and a 65C02
|
||||
* processor based on the behavior of the ADC function in decimal mode.
|
||||
*
|
||||
* Important Note:
|
||||
*
|
||||
* When running in a program compiled for the "sim6502" target, it is safe to switch to
|
||||
* 65C02 or 6502X mode, since the runtime library will only use plain 6502 opcodes, and
|
||||
* those work the same in 65C02 and 6502X mode.
|
||||
*
|
||||
* However, when running in a program compiled for the "sim65c02" target, it is NOT safe
|
||||
* to switch to 6502 or 6502X mode, since many routines in the runtime library use
|
||||
* 65C02-specific opcodes, and these will not work as expected when the CPU is switched
|
||||
* to 6502 or 6502X mode. When such an instruction is encountered, the program will
|
||||
* exhibit undefined behavior.
|
||||
*
|
||||
* For this reason, this program will only work when compiled for the "sim6502" target.
|
||||
*
|
||||
* Running the example
|
||||
* -------------------
|
||||
*
|
||||
* cl65 -t sim6502 -O cpumode_example.c -o cpumode_example.prg
|
||||
* sim65 cpumode_example.prg
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <sim65.h>
|
||||
|
||||
static bool __fastcall__ is_65c02(void)
|
||||
{
|
||||
/* This assembly routine loads 0 into AX on a 6502 (also on a 6502 on which decimal
|
||||
* mode is not implemented), and 1 on a 65C02.
|
||||
*
|
||||
* Note: this implementation triggers a "control reaches end of non-void function"
|
||||
* warning that can be safely ignored. While no return statement is present, the
|
||||
* return value is correctly loaded into AX by the assembly code.
|
||||
*/
|
||||
__asm__("sed");
|
||||
__asm__("ldx #0");
|
||||
__asm__("txa");
|
||||
__asm__("sbc #155");
|
||||
__asm__("asl");
|
||||
__asm__("rol");
|
||||
__asm__("and #1");
|
||||
__asm__("cld");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("CPU mode at startup ....... : %u\n", GET_CPU_MODE());
|
||||
printf("Is 65C02? ................. : %s\n", is_65c02() ? "YES" : "NO");
|
||||
|
||||
printf("\n");
|
||||
|
||||
printf("Switching to 6502 mode ....\n");
|
||||
SET_CPU_MODE(SIM65_CPU_MODE_6502);
|
||||
printf("Current CPU mode .......... : %u\n", GET_CPU_MODE());
|
||||
printf("Is 65C02? ................. : %s\n", is_65c02() ? "YES" : "NO");
|
||||
|
||||
printf("\n");
|
||||
|
||||
printf("Switching to 65C02 mode ...\n");
|
||||
SET_CPU_MODE(SIM65_CPU_MODE_65C02);
|
||||
printf("Current CPU mode .......... : %u\n", GET_CPU_MODE());
|
||||
printf("Is 65C02? ................. : %s\n", is_65c02() ? "YES" : "NO");
|
||||
|
||||
printf("\n");
|
||||
|
||||
printf("Switching to 6502X mode ...\n");
|
||||
SET_CPU_MODE(SIM65_CPU_MODE_6502X);
|
||||
printf("Current CPU mode .......... : %u\n", GET_CPU_MODE());
|
||||
printf("Is 65C02? ................. : %s\n", is_65c02() ? "YES" : "NO");
|
||||
|
||||
printf("\n");
|
||||
|
||||
printf("Bye!\n");
|
||||
|
||||
return 0;
|
||||
}
|
117
samples/sim65/timer_example.c
Normal file
117
samples/sim65/timer_example.c
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Sim65 timer example.
|
||||
*
|
||||
* Description
|
||||
* -----------
|
||||
*
|
||||
* This example tests the clock cycle counter feature of sim65.
|
||||
*
|
||||
* The function 'timestamp' obtains the lower 32-bits of the clock cycle counter.
|
||||
*
|
||||
* The function 'calc_sum_terms' calculates the sum of a range of integers
|
||||
* starting at zero. It simply iterates over all terms, which means that its
|
||||
* runtime is a linear function of its input value.
|
||||
*
|
||||
* In the main function, we first derive an 'offset' value by getting two timestamp
|
||||
* values, with nothing happening in between. Ideally this should yield a 0 clock
|
||||
* cycle duration, but due to the overhead of calling the 'timestamp' function,
|
||||
* and the 'timestamp' function itself, the difference between these timestamp
|
||||
* will be non-zero. We store this value in the 'overhead' variable, and subtract
|
||||
* this value in later measurements.
|
||||
*
|
||||
* Next, we measure the duration of calling the function 'calc_sum_terms' with two
|
||||
* input values, 0, and 1. The duration includes storing the result in the 'result'
|
||||
* variable.
|
||||
*
|
||||
* Extrapolating from these two measurements, and assuming that the runtime of
|
||||
* calling 'calc_sum_terms' and storing its result scales linearly with its argument,
|
||||
* we can predict the duration of a call to 'calc_sum_terms' with a much larger
|
||||
* argument (max_terms = 10000).
|
||||
*
|
||||
* Finally, we actually measure the duration with max_terms = 10000. If the
|
||||
* duration measured is equal to the predicted value, we exit successfully. If not,
|
||||
* we exit with failure.
|
||||
*
|
||||
* Running the example
|
||||
* -------------------
|
||||
*
|
||||
* cl65 -t sim6502 -O timer_example.c -o timer_example.prg
|
||||
* sim65 timer_example.prg
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sim65.h>
|
||||
|
||||
static uint32_t timestamp(void)
|
||||
{
|
||||
peripherals.counter.select = COUNTER_SELECT_CLOCKCYCLE_COUNTER;
|
||||
peripherals.counter.latch = 0;
|
||||
return peripherals.counter.value32[0];
|
||||
}
|
||||
|
||||
static unsigned long calc_sum_terms(unsigned max_term)
|
||||
/* A function with a runtime that scales linearly with its argument. */
|
||||
{
|
||||
unsigned k;
|
||||
unsigned long sum = 0;
|
||||
for (k = 0; k <= max_term; ++k)
|
||||
{
|
||||
sum += k;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
unsigned max_term;
|
||||
unsigned long result;
|
||||
uint32_t t1, t2, overhead;
|
||||
int32_t d0, d1, duration;
|
||||
int32_t predicted_duration;
|
||||
|
||||
/* Calibration measurement of zero clock cycles, to determine the overhead. */
|
||||
|
||||
overhead = 0;
|
||||
t1 = timestamp();
|
||||
t2 = timestamp() - overhead;
|
||||
overhead = (t2 - t1);
|
||||
|
||||
/* Calculate call duration (including assignment of result) for argument value 0. */
|
||||
|
||||
max_term = 0;
|
||||
t1 = timestamp();
|
||||
result = calc_sum_terms(max_term);
|
||||
t2 = timestamp();
|
||||
d0 = (t2 - t1) - overhead;
|
||||
printf("max_term = %u -> result = %lu; duration = %ld\n", max_term, result, d0);
|
||||
|
||||
/* Calculate call duration (including assignment of result) for argument value 1. */
|
||||
|
||||
max_term = 1;
|
||||
t1 = timestamp();
|
||||
result = calc_sum_terms(max_term);
|
||||
t2 = timestamp();
|
||||
d1 = (t2 - t1) - overhead;
|
||||
printf("max_term = %u -> result = %lu; duration = %ld\n", max_term, result, d1);
|
||||
|
||||
/* Predict runtime for a much bigger argument value, 10000. */
|
||||
|
||||
max_term = 10000;
|
||||
predicted_duration = d0 + max_term * (d1 - d0);
|
||||
|
||||
printf("predicted duration for max_term = %u: %ld\n", max_term, predicted_duration);
|
||||
|
||||
/* Do the actual measurement for max_term = 10000.
|
||||
* Note: equality between the prediction and the measurement is only achieved if we compile with -O.
|
||||
*/
|
||||
|
||||
t1 = timestamp();
|
||||
result = calc_sum_terms(max_term);
|
||||
t2 = timestamp();
|
||||
duration = (t2 - t1) - overhead;
|
||||
printf("max_term = %u -> result = %lu; duration = %ld\n", max_term, result, duration);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
40
samples/sim65/trace_example.c
Normal file
40
samples/sim65/trace_example.c
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Sim65 trace functionailty example.
|
||||
*
|
||||
* Description
|
||||
* -----------
|
||||
*
|
||||
* The easiest way to use tracing in sim65 is to pass the '--trace' option
|
||||
* to sim65 while starting a program.
|
||||
*
|
||||
* However, it is also possiblke to enable and disable the trace functionality
|
||||
* at runtime, from within the C code itself. This can be useful to produce
|
||||
* runtime traces of small code fragments for debugging purposes.
|
||||
*
|
||||
* In this example, We use the TRACE_ON and TRACE_OFF macros provided in sim65.h
|
||||
* to trace what the CPU is doing during a single statement: the assignment of
|
||||
* a constant to a global variable.
|
||||
*
|
||||
* Running the example
|
||||
* -------------------
|
||||
*
|
||||
* cl65 -t sim6502 -O trace_example.c -o trace_example.prg
|
||||
* sim65 trace_example.prg
|
||||
*
|
||||
* Compiling and running the program like this will produce a trace of six 6502 instructions.
|
||||
* The first four instructions correspond to the 'x = 0x1234' assignment statement.
|
||||
* The last two instructions (ending in a store to address $FFCB) disable the trace facility.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <sim65.h>
|
||||
|
||||
unsigned x;
|
||||
|
||||
int main(void)
|
||||
{
|
||||
TRACE_ON();
|
||||
x = 0x1234;
|
||||
TRACE_OFF();
|
||||
return 0;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user