mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-11 08:30:55 +00:00
Merge pull request #163 from TomHarte/WaitSampling
Adjusts the timing of the Z80's wait line sampling to be on a half clock and better regularises 'action' partial bus cycles
This commit is contained in:
commit
b9f4f7a530
@ -99,8 +99,12 @@ HalfCycles Machine::perform_machine_cycle(const CPU::Z80::PartialMachineCycle &c
|
||||
} break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Interrupt:
|
||||
// resetting event is M1 and IOREQ both simultaneously having leading edges;
|
||||
// that happens 2 cycles before the end of INTACK. So the timer was reset and
|
||||
// now has advanced twice.
|
||||
horizontal_counter_ = HalfCycles(2);
|
||||
|
||||
*cycle.value = 0xff;
|
||||
horizontal_counter_ = 0;
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Refresh:
|
||||
@ -109,7 +113,7 @@ HalfCycles Machine::perform_machine_cycle(const CPU::Z80::PartialMachineCycle &c
|
||||
// final two cycles of an opcode fetch. Therefore communicate a transient signalling
|
||||
// of the IRQ line if necessary.
|
||||
if(!(address & 0x40)) {
|
||||
set_interrupt_line(true, -2);
|
||||
set_interrupt_line(true, Cycles(-2));
|
||||
set_interrupt_line(false);
|
||||
}
|
||||
if(has_latched_video_byte_) {
|
||||
@ -126,8 +130,7 @@ HalfCycles Machine::perform_machine_cycle(const CPU::Z80::PartialMachineCycle &c
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcodeStart:
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcodeWait:
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
// Check for use of the fast tape hack.
|
||||
if(use_fast_tape_hack_ && address == tape_trap_address_ && tape_player_.has_tape()) {
|
||||
uint64_t prior_offset = tape_player_.get_tape()->get_offset();
|
||||
|
@ -34,6 +34,7 @@ class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler {
|
||||
0xbd, 0x00, 0x00, // [4] LDA $0000, x (no wrap)
|
||||
0xbd, 0x02, 0x00, // [5] LDA $0002, x (wrap)
|
||||
0xb9, 0x00, 0x00, // [4] LDA $0000, y (no wrap)
|
||||
|
||||
0xb9, 0x10, 0x00, // [5] LDA $0010, y (wrap)
|
||||
0xa1, 0x44, // [6] LDA ($44, x)
|
||||
0xb1, 0x00, // [5] LDA ($00), y (no wrap)
|
||||
@ -222,7 +223,7 @@ class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler {
|
||||
|
||||
func testMachine(_ testMachine: CSTestMachine, didTrapAtAddress address: UInt16) {
|
||||
if self.endTime == 0 {
|
||||
self.endTime = machine.timestamp - 1
|
||||
self.endTime = (machine.timestamp / 2) - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ static CPU::MOS6502::Register registerForRegister(CSTestMachine6502Register reg)
|
||||
}
|
||||
|
||||
- (uint32_t)timestamp {
|
||||
return _processor->get_timestamp();
|
||||
return _processor->get_timestamp().as_int();
|
||||
}
|
||||
|
||||
- (void)setIrqLine:(BOOL)irqLine {
|
||||
|
@ -61,7 +61,7 @@ typedef NS_ENUM(NSInteger, CSTestMachineZ80Register) {
|
||||
@property(nonatomic, readonly, nonnull) NSArray<CSTestMachineZ80BusOperationCapture *> *busOperationCaptures;
|
||||
|
||||
@property(nonatomic, readonly) BOOL isHalted;
|
||||
@property(nonatomic, readonly) int completedCycles;
|
||||
@property(nonatomic, readonly) int completedHalfCycles;
|
||||
|
||||
@property(nonatomic) BOOL nmiLine;
|
||||
@property(nonatomic) BOOL irqLine;
|
||||
|
@ -14,7 +14,7 @@
|
||||
- (void)testMachineDidPerformBusOperation:(CPU::Z80::PartialMachineCycle::Operation)operation
|
||||
address:(uint16_t)address
|
||||
value:(uint8_t)value
|
||||
timeStamp:(int)time_stamp;
|
||||
timeStamp:(HalfCycles)time_stamp;
|
||||
@end
|
||||
|
||||
#pragma mark - C++ delegate handlers
|
||||
@ -23,7 +23,7 @@ class BusOperationHandler: public CPU::Z80::AllRAMProcessor::MemoryAccessDelegat
|
||||
public:
|
||||
BusOperationHandler(CSTestMachineZ80 *targetMachine) : target_(targetMachine) {}
|
||||
|
||||
void z80_all_ram_processor_did_perform_bus_operation(CPU::Z80::AllRAMProcessor &processor, CPU::Z80::PartialMachineCycle::Operation operation, uint16_t address, uint8_t value, int time_stamp) {
|
||||
void z80_all_ram_processor_did_perform_bus_operation(CPU::Z80::AllRAMProcessor &processor, CPU::Z80::PartialMachineCycle::Operation operation, uint16_t address, uint8_t value, HalfCycles time_stamp) {
|
||||
[target_ testMachineDidPerformBusOperation:operation address:address value:value timeStamp:time_stamp];
|
||||
}
|
||||
|
||||
@ -154,8 +154,8 @@ static CPU::Z80::Register registerForRegister(CSTestMachineZ80Register reg) {
|
||||
return _processor->get_halt_line() ? YES : NO;
|
||||
}
|
||||
|
||||
- (int)completedCycles {
|
||||
return _processor->get_timestamp();
|
||||
- (int)completedHalfCycles {
|
||||
return _processor->get_timestamp().as_int();
|
||||
}
|
||||
|
||||
- (void)setNmiLine:(BOOL)nmiLine {
|
||||
@ -184,7 +184,7 @@ static CPU::Z80::Register registerForRegister(CSTestMachineZ80Register reg) {
|
||||
_processor->set_memory_access_delegate(captureBusActivity ? _busOperationHandler : nullptr);
|
||||
}
|
||||
|
||||
- (void)testMachineDidPerformBusOperation:(CPU::Z80::PartialMachineCycle::Operation)operation address:(uint16_t)address value:(uint8_t)value timeStamp:(int)timeStamp {
|
||||
- (void)testMachineDidPerformBusOperation:(CPU::Z80::PartialMachineCycle::Operation)operation address:(uint16_t)address value:(uint8_t)value timeStamp:(HalfCycles)timeStamp {
|
||||
if(self.captureBusActivity) {
|
||||
CSTestMachineZ80BusOperationCapture *capture = [[CSTestMachineZ80BusOperationCapture alloc] init];
|
||||
switch(operation) {
|
||||
@ -216,7 +216,7 @@ static CPU::Z80::Register registerForRegister(CSTestMachineZ80Register reg) {
|
||||
}
|
||||
capture.address = address;
|
||||
capture.value = value;
|
||||
capture.timeStamp = timeStamp;
|
||||
capture.timeStamp = timeStamp.as_int();
|
||||
|
||||
[_busOperationCaptures addObject:capture];
|
||||
}
|
||||
|
@ -195,8 +195,8 @@ class FUSETests: XCTestCase {
|
||||
machine.runForNumber(ofCycles: Int32(targetState.tStates))
|
||||
|
||||
// Verify that exactly the right number of cycles was hit; this is a primitive cycle length tester.
|
||||
let cyclesRun = machine.completedCycles
|
||||
XCTAssert(cyclesRun == Int32(targetState.tStates), "Instruction length off; was \(machine.completedCycles) but should be \(targetState.tStates): \(name)")
|
||||
let halfCyclesRun = machine.completedHalfCycles
|
||||
XCTAssert(halfCyclesRun == Int32(targetState.tStates) * 2, "Instruction length off; was \(machine.completedHalfCycles) but should be \(targetState.tStates * 2): \(name)")
|
||||
|
||||
let finalState = RegisterState(machine: machine)
|
||||
|
||||
|
@ -61,7 +61,9 @@ class Z80MachineCycleTests: XCTestCase {
|
||||
// array access
|
||||
break
|
||||
} else {
|
||||
if length != busCycles[index].length || cycle.operation != busCycles[index].operation {
|
||||
XCTAssert(length & Int32(1) == 0, "While performing \(machine.busOperationCaptures) Z80 ended on a half cycle")
|
||||
|
||||
if length != busCycles[index].length*2 || cycle.operation != busCycles[index].operation {
|
||||
matches = false
|
||||
break;
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ class ConcreteAllRAMProcessor: public AllRAMProcessor, public Processor<Concrete
|
||||
}
|
||||
|
||||
inline Cycles perform_bus_operation(BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
timestamp_++;
|
||||
timestamp_ += Cycles(1);
|
||||
|
||||
if(operation == BusOperation::ReadOpcode) {
|
||||
check_address_for_trap(address);
|
||||
|
@ -25,7 +25,7 @@ void AllRAMProcessor::get_data_at_address(uint16_t startAddress, size_t length,
|
||||
memcpy(data, &memory_[startAddress], endAddress - startAddress);
|
||||
}
|
||||
|
||||
uint32_t AllRAMProcessor::get_timestamp() {
|
||||
HalfCycles AllRAMProcessor::get_timestamp() {
|
||||
return timestamp_;
|
||||
}
|
||||
|
||||
|
@ -13,12 +13,14 @@
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace CPU {
|
||||
|
||||
class AllRAMProcessor {
|
||||
public:
|
||||
AllRAMProcessor(size_t memory_size);
|
||||
virtual uint32_t get_timestamp();
|
||||
HalfCycles get_timestamp();
|
||||
void set_data_at_address(uint16_t startAddress, size_t length, const uint8_t *data);
|
||||
void get_data_at_address(uint16_t startAddress, size_t length, uint8_t *data);
|
||||
|
||||
@ -31,7 +33,7 @@ class AllRAMProcessor {
|
||||
|
||||
protected:
|
||||
std::vector<uint8_t> memory_;
|
||||
uint32_t timestamp_;
|
||||
HalfCycles timestamp_;
|
||||
|
||||
inline void check_address_for_trap(uint16_t address) {
|
||||
if(traps_[address]) {
|
||||
|
@ -67,8 +67,7 @@ enum Flag: uint8_t {
|
||||
*/
|
||||
struct PartialMachineCycle {
|
||||
enum Operation {
|
||||
ReadOpcodeStart = 0,
|
||||
ReadOpcodeWait,
|
||||
ReadOpcode = 0,
|
||||
Read,
|
||||
Write,
|
||||
Input,
|
||||
@ -79,16 +78,19 @@ struct PartialMachineCycle {
|
||||
Internal,
|
||||
BusAcknowledge,
|
||||
|
||||
ReadOpcodeWait,
|
||||
ReadWait,
|
||||
WriteWait,
|
||||
InputWait,
|
||||
OutputWait,
|
||||
InterruptWait,
|
||||
|
||||
ReadOpcodeStart,
|
||||
ReadStart,
|
||||
WriteStart,
|
||||
InputStart,
|
||||
OutputStart,
|
||||
InterruptStart,
|
||||
} operation;
|
||||
HalfCycles length;
|
||||
uint16_t *address;
|
||||
@ -102,33 +104,37 @@ struct PartialMachineCycle {
|
||||
return operation <= Operation::BusAcknowledge;
|
||||
}
|
||||
inline bool is_wait() const {
|
||||
return operation >= Operation::ReadWait && operation <= Operation::InterruptWait;
|
||||
return operation >= Operation::ReadOpcodeWait && operation <= Operation::InterruptWait;
|
||||
}
|
||||
};
|
||||
|
||||
// Elemental bus operations
|
||||
#define ReadOpcodeStart() {PartialMachineCycle::ReadOpcodeStart, HalfCycles(4), &pc_.full, &operation_, false}
|
||||
#define ReadOpcodeStart() {PartialMachineCycle::ReadOpcodeStart, HalfCycles(3), &pc_.full, &operation_, false}
|
||||
#define ReadOpcodeWait(f) {PartialMachineCycle::ReadOpcodeWait, HalfCycles(2), &pc_.full, &operation_, f}
|
||||
#define ReadOpcodeEnd() {PartialMachineCycle::ReadOpcode, HalfCycles(1), &pc_.full, &operation_, false}
|
||||
|
||||
#define Refresh(len) {PartialMachineCycle::Refresh, HalfCycles(len), &refresh_addr_.full, nullptr, false}
|
||||
|
||||
#define ReadStart(addr, val) {PartialMachineCycle::ReadStart, HalfCycles(4), &addr.full, &val, false}
|
||||
#define ReadStart(addr, val) {PartialMachineCycle::ReadStart, HalfCycles(3), &addr.full, &val, false}
|
||||
#define ReadWait(l, addr, val, f) {PartialMachineCycle::ReadWait, HalfCycles(l), &addr.full, &val, f}
|
||||
#define ReadEnd(addr, val) {PartialMachineCycle::Read, HalfCycles(2), &addr.full, &val, false}
|
||||
#define ReadEnd(addr, val) {PartialMachineCycle::Read, HalfCycles(3), &addr.full, &val, false}
|
||||
|
||||
#define WriteStart(addr, val) {PartialMachineCycle::WriteStart,HalfCycles(4), &addr.full, &val, false}
|
||||
#define WriteStart(addr, val) {PartialMachineCycle::WriteStart,HalfCycles(3), &addr.full, &val, false}
|
||||
#define WriteWait(l, addr, val, f) {PartialMachineCycle::WriteWait, HalfCycles(l), &addr.full, &val, f}
|
||||
#define WriteEnd(addr, val) {PartialMachineCycle::Write, HalfCycles(2), &addr.full, &val, false}
|
||||
#define WriteEnd(addr, val) {PartialMachineCycle::Write, HalfCycles(3), &addr.full, &val, false}
|
||||
|
||||
#define InputStart(addr, val) {PartialMachineCycle::InputStart, HalfCycles(4), &addr.full, &val, false}
|
||||
#define InputStart(addr, val) {PartialMachineCycle::InputStart, HalfCycles(3), &addr.full, &val, false}
|
||||
#define InputWait(addr, val, f) {PartialMachineCycle::InputWait, HalfCycles(2), &addr.full, &val, f}
|
||||
#define InputEnd(addr, val) {PartialMachineCycle::Input, HalfCycles(2), &addr.full, &val, false}
|
||||
#define InputEnd(addr, val) {PartialMachineCycle::Input, HalfCycles(3), &addr.full, &val, false}
|
||||
|
||||
#define OutputStart(addr, val) {PartialMachineCycle::OutputStart, HalfCycles(4), &addr.full, &val, false}
|
||||
#define OutputStart(addr, val) {PartialMachineCycle::OutputStart, HalfCycles(3), &addr.full, &val, false}
|
||||
#define OutputWait(addr, val, f) {PartialMachineCycle::OutputWait, HalfCycles(2), &addr.full, &val, f}
|
||||
#define OutputEnd(addr, val) {PartialMachineCycle::Output, HalfCycles(2), &addr.full, &val, false}
|
||||
#define OutputEnd(addr, val) {PartialMachineCycle::Output, HalfCycles(3), &addr.full, &val, false}
|
||||
|
||||
#define IntAck(length, val) {PartialMachineCycle::Interrupt, HalfCycles(length), nullptr, &val, false}
|
||||
#define IntAckStart(length, val) {PartialMachineCycle::InterruptStart, HalfCycles(length), nullptr, &val, false}
|
||||
#define IntWait(val) {PartialMachineCycle::InterruptWait, HalfCycles(2), nullptr, &val, true}
|
||||
#define IntAckEnd(val) {PartialMachineCycle::Interrupt, HalfCycles(3), nullptr, &val, false}
|
||||
|
||||
|
||||
// A wrapper to express a bus operation as a micro-op
|
||||
#define BusOp(op) {MicroOp::BusOperation, nullptr, nullptr, op}
|
||||
@ -741,12 +747,14 @@ template <class T> class Processor {
|
||||
const MicroOp normal_fetch_decode_execute[] = {
|
||||
BusOp(ReadOpcodeStart()),
|
||||
BusOp(ReadOpcodeWait(true)),
|
||||
BusOp(ReadOpcodeEnd()),
|
||||
{ MicroOp::DecodeOperation }
|
||||
};
|
||||
const MicroOp short_fetch_decode_execute[] = {
|
||||
BusOp(ReadOpcodeStart()),
|
||||
BusOp(ReadOpcodeWait(false)),
|
||||
BusOp(ReadOpcodeWait(true)),
|
||||
BusOp(ReadOpcodeEnd()),
|
||||
{ MicroOp::DecodeOperation }
|
||||
};
|
||||
copy_program((length == 4) ? normal_fetch_decode_execute : short_fetch_decode_execute, target.fetch_decode_execute);
|
||||
@ -813,6 +821,7 @@ template <class T> class Processor {
|
||||
{ MicroOp::BeginNMI },
|
||||
BusOp(ReadOpcodeStart()),
|
||||
BusOp(ReadOpcodeWait(true)),
|
||||
BusOp(ReadOpcodeEnd()),
|
||||
BusOp(Refresh(6)),
|
||||
Push(pc_),
|
||||
{ MicroOp::JumpTo66, nullptr, nullptr},
|
||||
@ -820,14 +829,16 @@ template <class T> class Processor {
|
||||
};
|
||||
MicroOp irq_mode0_program[] = {
|
||||
{ MicroOp::BeginIRQMode0 },
|
||||
BusOp(IntAck(8, operation_)),
|
||||
BusOp(IntAckStart(5, operation_)),
|
||||
BusOp(IntWait(operation_)),
|
||||
BusOp(IntAckEnd(operation_)),
|
||||
{ MicroOp::DecodeOperationNoRChange }
|
||||
};
|
||||
MicroOp irq_mode1_program[] = {
|
||||
{ MicroOp::BeginIRQ },
|
||||
BusOp(IntAck(10, operation_)),
|
||||
BusOp(IntAckStart(7, operation_)),
|
||||
BusOp(IntWait(operation_)),
|
||||
BusOp(IntAckEnd(operation_)),
|
||||
BusOp(Refresh(4)),
|
||||
Push(pc_),
|
||||
{ MicroOp::Move16, &temp16_.full, &pc_.full },
|
||||
@ -835,8 +846,9 @@ template <class T> class Processor {
|
||||
};
|
||||
MicroOp irq_mode2_program[] = {
|
||||
{ MicroOp::BeginIRQ },
|
||||
BusOp(IntAck(10, temp16_.bytes.low)),
|
||||
BusOp(IntAckStart(7, temp16_.bytes.low)),
|
||||
BusOp(IntWait(temp16_.bytes.low)),
|
||||
BusOp(IntAckEnd(temp16_.bytes.low)),
|
||||
BusOp(Refresh(4)),
|
||||
Push(pc_),
|
||||
{ MicroOp::Move8, &ir_.bytes.high, &temp16_.bytes.high },
|
||||
@ -1882,7 +1894,7 @@ template <class T> class Processor {
|
||||
how many cycles before now the line changed state. The value may not be longer than the
|
||||
current machine cycle. If called at any other time, this must be zero.
|
||||
*/
|
||||
void set_interrupt_line(bool value, int offset = 0) {
|
||||
void set_interrupt_line(bool value, HalfCycles offset = 0) {
|
||||
if(irq_line_ == value) return;
|
||||
|
||||
// IRQ requests are level triggered and masked.
|
||||
@ -1896,7 +1908,7 @@ template <class T> class Processor {
|
||||
// If this change happened at least one cycle ago then: (i) we're promised that this is a machine
|
||||
// cycle per the contract on supplying an offset; and (ii) that means it happened before the lines
|
||||
// were sampled. So adjust the most recent sample.
|
||||
if(offset < 0) {
|
||||
if(offset <= HalfCycles(-2)) {
|
||||
last_request_status_ = (last_request_status_ & ~Interrupt::IRQ) | (request_status_ & Interrupt::IRQ);
|
||||
}
|
||||
}
|
||||
|
@ -17,14 +17,14 @@ class ConcreteAllRAMProcessor: public AllRAMProcessor, public Processor<Concrete
|
||||
ConcreteAllRAMProcessor() : AllRAMProcessor() {}
|
||||
|
||||
inline Cycles perform_machine_cycle(const PartialMachineCycle &cycle) {
|
||||
timestamp_ += cycle.length.as_int();
|
||||
timestamp_ += cycle.length;
|
||||
if(!cycle.is_terminal()) {
|
||||
return Cycles(0);
|
||||
}
|
||||
|
||||
uint16_t address = cycle.address ? *cycle.address : 0x0000;
|
||||
switch(cycle.operation) {
|
||||
case PartialMachineCycle::ReadOpcodeStart:
|
||||
case PartialMachineCycle::ReadOpcode:
|
||||
check_address_for_trap(address);
|
||||
case PartialMachineCycle::Read:
|
||||
*cycle.value = memory_[address];
|
||||
@ -57,7 +57,7 @@ class ConcreteAllRAMProcessor: public AllRAMProcessor, public Processor<Concrete
|
||||
}
|
||||
|
||||
if(delegate_ != nullptr) {
|
||||
delegate_->z80_all_ram_processor_did_perform_bus_operation(*this, cycle.operation, address, cycle.value ? *cycle.value : 0x00, timestamp_ >> 1);
|
||||
delegate_->z80_all_ram_processor_did_perform_bus_operation(*this, cycle.operation, address, cycle.value ? *cycle.value : 0x00, timestamp_);
|
||||
}
|
||||
|
||||
return Cycles(0);
|
||||
@ -94,10 +94,6 @@ class ConcreteAllRAMProcessor: public AllRAMProcessor, public Processor<Concrete
|
||||
void set_wait_line(bool value) {
|
||||
CPU::Z80::Processor<ConcreteAllRAMProcessor>::set_wait_line(value);
|
||||
}
|
||||
|
||||
uint32_t get_timestamp() {
|
||||
return timestamp_ >> 1;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ class AllRAMProcessor:
|
||||
static AllRAMProcessor *Processor();
|
||||
|
||||
struct MemoryAccessDelegate {
|
||||
virtual void z80_all_ram_processor_did_perform_bus_operation(CPU::Z80::AllRAMProcessor &processor, CPU::Z80::PartialMachineCycle::Operation operation, uint16_t address, uint8_t value, int time_stamp) = 0;
|
||||
virtual void z80_all_ram_processor_did_perform_bus_operation(CPU::Z80::AllRAMProcessor &processor, CPU::Z80::PartialMachineCycle::Operation operation, uint16_t address, uint8_t value, HalfCycles time_stamp) = 0;
|
||||
};
|
||||
inline void set_memory_access_delegate(MemoryAccessDelegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
@ -38,8 +38,6 @@ class AllRAMProcessor:
|
||||
virtual void set_non_maskable_interrupt_line(bool value) = 0;
|
||||
virtual void set_wait_line(bool value) = 0;
|
||||
|
||||
virtual uint32_t get_timestamp() = 0;
|
||||
|
||||
protected:
|
||||
MemoryAccessDelegate *delegate_;
|
||||
AllRAMProcessor() : ::CPU::AllRAMProcessor(65536), delegate_(nullptr) {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user