1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-07 08:28:57 +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:
Thomas Harte 2017-07-27 21:19:29 -04:00 committed by GitHub
commit b9f4f7a530
13 changed files with 62 additions and 48 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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]) {

View File

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

View File

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

View File

@ -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) {}