mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-25 18:30:21 +00:00
Merge pull request #178 from TomHarte/CPC664
Introduces the beginning seeds 8272 emulation, and therefore floppy emulation for the CPC
This commit is contained in:
commit
4427e9a254
@ -263,7 +263,7 @@ void WD1770::process_write_completed() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = mask; return; case __LINE__:
|
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = mask; return; case __LINE__:
|
||||||
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = Event::Timer; delay_time_ = ms * 8000; if(delay_time_) return; case __LINE__:
|
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = Event::Timer; delay_time_ = ms * 8000; case __LINE__: if(delay_time_) return;
|
||||||
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; interesting_event_mask_ = Event::Token; distance_into_section_ = 0; return; case __LINE__: if(latest_token_.type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = Event::Token; return; }
|
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; interesting_event_mask_ = Event::Token; distance_into_section_ = 0; return; case __LINE__: if(latest_token_.type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = Event::Token; return; }
|
||||||
#define BEGIN_SECTION() switch(resume_point_) { default:
|
#define BEGIN_SECTION() switch(resume_point_) { default:
|
||||||
#define END_SECTION() 0; }
|
#define END_SECTION() 0; }
|
||||||
|
462
Components/8272/i8272.cpp
Normal file
462
Components/8272/i8272.cpp
Normal file
@ -0,0 +1,462 @@
|
|||||||
|
//
|
||||||
|
// i8272.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 05/08/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "i8272.hpp"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
using namespace Intel;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
const uint8_t StatusRQM = 0x80; // Set: ready to send or receive from processor.
|
||||||
|
const uint8_t StatusDIO = 0x40; // Set: data is expected to be taken from the 8272 by the processor.
|
||||||
|
const uint8_t StatusNDM = 0x20; // Set: the execution phase of a data transfer command is ongoing and DMA mode is disabled.
|
||||||
|
const uint8_t StatusCB = 0x10; // Set: the FDC is busy.
|
||||||
|
//const uint8_t StatusD3B = 0x08; // Set: drive 3 is seeking.
|
||||||
|
//const uint8_t StatusD2B = 0x04; // Set: drive 2 is seeking.
|
||||||
|
//const uint8_t StatusD1B = 0x02; // Set: drive 1 is seeking.
|
||||||
|
//const uint8_t StatusD0B = 0x01; // Set: drive 0 is seeking.
|
||||||
|
}
|
||||||
|
|
||||||
|
i8272::i8272(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) :
|
||||||
|
Storage::Disk::MFMController(clock_rate, clock_rate_multiplier, revolutions_per_minute),
|
||||||
|
main_status_(StatusRQM),
|
||||||
|
interesting_event_mask_((int)Event8272::CommandByte),
|
||||||
|
resume_point_(0),
|
||||||
|
delay_time_(0),
|
||||||
|
status_{0, 0, 0} {
|
||||||
|
posit_event((int)Event8272::CommandByte);
|
||||||
|
}
|
||||||
|
|
||||||
|
void i8272::run_for(Cycles cycles) {
|
||||||
|
Storage::Disk::MFMController::run_for(cycles);
|
||||||
|
|
||||||
|
// check for an expired timer
|
||||||
|
if(delay_time_ > 0) {
|
||||||
|
if(cycles.as_int() >= delay_time_) {
|
||||||
|
delay_time_ = 0;
|
||||||
|
posit_event((int)Event8272::Timer);
|
||||||
|
} else {
|
||||||
|
delay_time_ -= cycles.as_int();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update seek status of any drives presently seeking
|
||||||
|
if(main_status_ & 0xf) {
|
||||||
|
for(int c = 0; c < 4; c++) {
|
||||||
|
if(drives_[c].phase == Drive::Seeking) {
|
||||||
|
drives_[c].step_rate_counter += cycles.as_int();
|
||||||
|
int steps = drives_[c].step_rate_counter / (8000 * step_rate_time_);
|
||||||
|
drives_[c].step_rate_counter %= (8000 * step_rate_time_);
|
||||||
|
while(steps--) {
|
||||||
|
// Perform a step.
|
||||||
|
int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
|
||||||
|
drives_[c].drive->step(direction);
|
||||||
|
drives_[c].head_position += direction;
|
||||||
|
|
||||||
|
// Check for completion.
|
||||||
|
if(seek_is_satisfied(c)) {
|
||||||
|
drives_[c].phase = Drive::CompletedSeeking;
|
||||||
|
if(drives_[c].target_head_position == -1) drives_[c].head_position = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void i8272::set_register(int address, uint8_t value) {
|
||||||
|
// don't consider attempted sets to the status register
|
||||||
|
if(!address) return;
|
||||||
|
|
||||||
|
// if not ready for commands, do nothing
|
||||||
|
if(!(main_status_ & StatusRQM)) return;
|
||||||
|
|
||||||
|
// accumulate latest byte in the command byte sequence
|
||||||
|
command_.push_back(value);
|
||||||
|
posit_event((int)Event8272::CommandByte);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t i8272::get_register(int address) {
|
||||||
|
if(address) {
|
||||||
|
// printf("8272 get data\n");
|
||||||
|
|
||||||
|
if(result_stack_.empty()) return 0xff;
|
||||||
|
uint8_t result = result_stack_.back();
|
||||||
|
result_stack_.pop_back();
|
||||||
|
if(result_stack_.empty()) posit_event((int)Event8272::ResultEmpty);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
// printf("Main status: %02x\n", main_status_);
|
||||||
|
return main_status_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||||
|
if(drive < 4 && drive >= 0) {
|
||||||
|
drives_[drive].drive->set_disk(disk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define BEGIN_SECTION() switch(resume_point_) { default:
|
||||||
|
#define END_SECTION() }
|
||||||
|
|
||||||
|
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = (int)mask; return; case __LINE__:
|
||||||
|
|
||||||
|
#define PASTE(x, y) x##y
|
||||||
|
#define CONCAT(x, y) PASTE(x, y)
|
||||||
|
|
||||||
|
#define FIND_HEADER() \
|
||||||
|
CONCAT(find_header, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \
|
||||||
|
if(event_type == (int)Event::IndexHole) index_hole_limit_--; \
|
||||||
|
else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__); \
|
||||||
|
\
|
||||||
|
if(index_hole_limit_) goto CONCAT(find_header, __LINE__); \
|
||||||
|
CONCAT(header_found, __LINE__): 0;\
|
||||||
|
|
||||||
|
#define FIND_DATA() \
|
||||||
|
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \
|
||||||
|
if(event_type == (int)Event::Token && get_latest_token().type != Token::Data) goto CONCAT(find_data, __LINE__);
|
||||||
|
|
||||||
|
#define READ_HEADER() \
|
||||||
|
distance_into_section_ = 0; \
|
||||||
|
set_data_mode(Reading); \
|
||||||
|
CONCAT(read_header, __LINE__): WAIT_FOR_EVENT(Event::Token); \
|
||||||
|
header_[distance_into_section_] = get_latest_token().byte_value; \
|
||||||
|
distance_into_section_++; \
|
||||||
|
if(distance_into_section_ < 6) goto CONCAT(read_header, __LINE__); \
|
||||||
|
set_data_mode(Scanning);
|
||||||
|
|
||||||
|
#define SET_DRIVE_HEAD_MFM() \
|
||||||
|
if(!dma_mode_) main_status_ |= StatusNDM; \
|
||||||
|
set_drive(drives_[command_[1]&3].drive); \
|
||||||
|
set_is_double_density(command_[0] & 0x40); \
|
||||||
|
invalidate_track();
|
||||||
|
|
||||||
|
void i8272::posit_event(int event_type) {
|
||||||
|
if(!(interesting_event_mask_ & event_type)) return;
|
||||||
|
interesting_event_mask_ &= ~event_type;
|
||||||
|
|
||||||
|
BEGIN_SECTION();
|
||||||
|
|
||||||
|
// Resets busy and non-DMA execution, clears the command buffer, sets the data mode to scanning and flows
|
||||||
|
// into wait_for_complete_command_sequence.
|
||||||
|
wait_for_command:
|
||||||
|
set_data_mode(Storage::Disk::MFMController::DataMode::Scanning);
|
||||||
|
main_status_ &= ~(StatusCB | StatusNDM);
|
||||||
|
command_.clear();
|
||||||
|
|
||||||
|
// Sets the data request bit, and waits for a byte. Then sets the busy bit. Continues accepting bytes
|
||||||
|
// until it has a quantity that make up an entire command, then resets the data request bit and
|
||||||
|
// branches to that command.
|
||||||
|
wait_for_complete_command_sequence:
|
||||||
|
main_status_ |= StatusRQM;
|
||||||
|
WAIT_FOR_EVENT(Event8272::CommandByte)
|
||||||
|
main_status_ |= StatusCB;
|
||||||
|
|
||||||
|
switch(command_[0] & 0x1f) {
|
||||||
|
case 0x06: // read data
|
||||||
|
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||||
|
main_status_ &= ~StatusRQM;
|
||||||
|
goto read_data;
|
||||||
|
|
||||||
|
case 0x0b: // read deleted data
|
||||||
|
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||||
|
main_status_ &= ~StatusRQM;
|
||||||
|
goto read_deleted_data;
|
||||||
|
|
||||||
|
case 0x05: // write data
|
||||||
|
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||||
|
main_status_ &= ~StatusRQM;
|
||||||
|
goto write_data;
|
||||||
|
|
||||||
|
case 0x09: // write deleted data
|
||||||
|
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||||
|
main_status_ &= ~StatusRQM;
|
||||||
|
goto write_deleted_data;
|
||||||
|
|
||||||
|
case 0x02: // read track
|
||||||
|
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||||
|
main_status_ &= ~StatusRQM;
|
||||||
|
goto read_track;
|
||||||
|
|
||||||
|
case 0x0a: // read ID
|
||||||
|
if(command_.size() < 2) goto wait_for_complete_command_sequence;
|
||||||
|
main_status_ &= ~StatusRQM;
|
||||||
|
goto read_id;
|
||||||
|
|
||||||
|
case 0x0d: // format track
|
||||||
|
if(command_.size() < 6) goto wait_for_complete_command_sequence;
|
||||||
|
main_status_ &= ~StatusRQM;
|
||||||
|
goto format_track;
|
||||||
|
|
||||||
|
case 0x11: // scan low
|
||||||
|
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||||
|
main_status_ &= ~StatusRQM;
|
||||||
|
goto scan_low;
|
||||||
|
|
||||||
|
case 0x19: // scan low or equal
|
||||||
|
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||||
|
main_status_ &= ~StatusRQM;
|
||||||
|
goto scan_low_or_equal;
|
||||||
|
|
||||||
|
case 0x1d: // scan high or equal
|
||||||
|
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||||
|
main_status_ &= ~StatusRQM;
|
||||||
|
goto scan_high_or_equal;
|
||||||
|
|
||||||
|
case 0x07: // recalibrate
|
||||||
|
if(command_.size() < 2) goto wait_for_complete_command_sequence;
|
||||||
|
main_status_ &= ~StatusRQM;
|
||||||
|
goto recalibrate;
|
||||||
|
|
||||||
|
case 0x08: // sense interrupt status
|
||||||
|
main_status_ &= ~StatusRQM;
|
||||||
|
goto sense_interrupt_status;
|
||||||
|
|
||||||
|
case 0x03: // specify
|
||||||
|
if(command_.size() < 3) goto wait_for_complete_command_sequence;
|
||||||
|
main_status_ &= ~StatusRQM;
|
||||||
|
goto specify;
|
||||||
|
|
||||||
|
case 0x04: // sense drive status
|
||||||
|
if(command_.size() < 2) goto wait_for_complete_command_sequence;
|
||||||
|
main_status_ &= ~StatusRQM;
|
||||||
|
goto sense_drive_status;
|
||||||
|
|
||||||
|
case 0x0f: // seek
|
||||||
|
if(command_.size() < 3) goto wait_for_complete_command_sequence;
|
||||||
|
main_status_ &= ~StatusRQM;
|
||||||
|
goto seek;
|
||||||
|
|
||||||
|
default: // invalid
|
||||||
|
main_status_ &= ~StatusRQM;
|
||||||
|
goto invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Performs the read data command.
|
||||||
|
read_data:
|
||||||
|
printf("Read data, sector %02x %02x %02x %02x\n", command_[2], command_[3], command_[4], command_[5]);
|
||||||
|
|
||||||
|
// Establishes the drive and head being addressed, and whether in double density mode; populates the internal
|
||||||
|
// cylinder, head, sector and size registers from the command stream.
|
||||||
|
SET_DRIVE_HEAD_MFM();
|
||||||
|
cylinder_ = command_[2];
|
||||||
|
head_ = command_[3];
|
||||||
|
sector_ = command_[4];
|
||||||
|
size_ = command_[5];
|
||||||
|
|
||||||
|
// Sets a maximum index hole limit of 2 then performs a find header/read header loop, continuing either until
|
||||||
|
// the index hole limit is breached or a sector is found with a cylinder, head, sector and size equal to the
|
||||||
|
// values in the internal registers.
|
||||||
|
index_hole_limit_ = 2;
|
||||||
|
find_next_sector:
|
||||||
|
FIND_HEADER();
|
||||||
|
if(!index_hole_limit_) goto read_data_not_found;
|
||||||
|
READ_HEADER();
|
||||||
|
if(header_[0] != cylinder_ || header_[1] != head_ || header_[2] != sector_ || header_[3] != size_) goto find_next_sector;
|
||||||
|
|
||||||
|
// Finds the next data block and sets data mode to reading.
|
||||||
|
FIND_DATA();
|
||||||
|
distance_into_section_ = 0;
|
||||||
|
set_data_mode(Reading);
|
||||||
|
|
||||||
|
// Waits for the next token, then supplies it to the CPU by: (i) setting data request and direction; and (ii) resetting
|
||||||
|
// data request once the byte has been taken. Continues until all bytes have been read.
|
||||||
|
//
|
||||||
|
// TODO: signal if the CPU is too slow and missed a byte; at the minute it'll just silently miss. Also allow for other
|
||||||
|
// ways that sector size might have been specified.
|
||||||
|
get_byte:
|
||||||
|
WAIT_FOR_EVENT(Event::Token);
|
||||||
|
result_stack_.push_back(get_latest_token().byte_value);
|
||||||
|
distance_into_section_++;
|
||||||
|
main_status_ |= StatusRQM | StatusDIO;
|
||||||
|
WAIT_FOR_EVENT(Event8272::ResultEmpty);
|
||||||
|
main_status_ &= ~StatusRQM;
|
||||||
|
if(distance_into_section_ < (128 << size_)) goto get_byte;
|
||||||
|
|
||||||
|
// read CRC, without transferring it
|
||||||
|
WAIT_FOR_EVENT(Event::Token);
|
||||||
|
WAIT_FOR_EVENT(Event::Token);
|
||||||
|
|
||||||
|
// For a final result phase, post the standard ST0, ST1, ST2, C, H, R, N
|
||||||
|
goto post_st012chrn;
|
||||||
|
|
||||||
|
// Execution reaches here if two index holes were discovered before a matching sector — i.e. the data wasn't found.
|
||||||
|
// In that case set appropriate error flags and post the results.
|
||||||
|
read_data_not_found:
|
||||||
|
printf("Not found\n");
|
||||||
|
|
||||||
|
status_[1] |= 0x4;
|
||||||
|
status_[0] = 0x40; // (status_[0] & ~0xc0) |
|
||||||
|
goto post_st012chrn;
|
||||||
|
|
||||||
|
read_deleted_data:
|
||||||
|
printf("Read deleted data unimplemented!!\n");
|
||||||
|
goto wait_for_command;
|
||||||
|
|
||||||
|
write_data:
|
||||||
|
printf("Write data unimplemented!!\n");
|
||||||
|
goto wait_for_command;
|
||||||
|
|
||||||
|
write_deleted_data:
|
||||||
|
printf("Write deleted data unimplemented!!\n");
|
||||||
|
goto wait_for_command;
|
||||||
|
|
||||||
|
read_track:
|
||||||
|
printf("Read track unimplemented!!\n");
|
||||||
|
goto wait_for_command;
|
||||||
|
|
||||||
|
// Performs the read ID command.
|
||||||
|
read_id:
|
||||||
|
// Establishes the drive and head being addressed, and whether in double density mode.
|
||||||
|
printf("Read ID\n");
|
||||||
|
SET_DRIVE_HEAD_MFM();
|
||||||
|
|
||||||
|
// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes.
|
||||||
|
// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found.
|
||||||
|
index_hole_limit_ = 2;
|
||||||
|
read_id_find_next_sector:
|
||||||
|
FIND_HEADER();
|
||||||
|
if(!index_hole_limit_) goto read_data_not_found;
|
||||||
|
READ_HEADER();
|
||||||
|
|
||||||
|
// Sets internal registers from the discovered header and posts the standard ST0, ST1, ST2, C, H, R, N.
|
||||||
|
cylinder_ = header_[0];
|
||||||
|
head_ = header_[1];
|
||||||
|
sector_ = header_[2];
|
||||||
|
size_ = header_[3];
|
||||||
|
|
||||||
|
goto post_st012chrn;
|
||||||
|
|
||||||
|
format_track:
|
||||||
|
printf("Fromat track unimplemented!!\n");
|
||||||
|
goto wait_for_command;
|
||||||
|
|
||||||
|
scan_low:
|
||||||
|
printf("Scan low unimplemented!!\n");
|
||||||
|
goto wait_for_command;
|
||||||
|
|
||||||
|
scan_low_or_equal:
|
||||||
|
printf("Scan low or equal unimplemented!!\n");
|
||||||
|
goto wait_for_command;
|
||||||
|
|
||||||
|
scan_high_or_equal:
|
||||||
|
printf("Scan high or equal unimplemented!!\n");
|
||||||
|
goto wait_for_command;
|
||||||
|
|
||||||
|
// Performs both recalibrate and seek commands. These commands occur asynchronously, so the actual work
|
||||||
|
// occurs in ::run_for; this merely establishes that seeking should be ongoing.
|
||||||
|
recalibrate:
|
||||||
|
seek:
|
||||||
|
printf((command_.size() > 2) ? "Seek\n" : "Recalibrate\n");
|
||||||
|
|
||||||
|
// Declines to act if a seek is already ongoing; otherwise resets all status registers, sets the drive
|
||||||
|
// into seeking mode, sets the drive's main status seeking bit, and sets the target head position: for
|
||||||
|
// a recalibrate the target is -1 and ::run_for knows that -1 means the terminal condition is the drive
|
||||||
|
// returning that its at track zero, and that it should reset the drive's current position once reached.
|
||||||
|
if(drives_[command_[1]&3].phase != Drive::Seeking) {
|
||||||
|
status_[0] = status_[1] = status_[2] = 0;
|
||||||
|
int drive = command_[1]&3;
|
||||||
|
drives_[drive].phase = Drive::Seeking;
|
||||||
|
drives_[drive].steps_taken = 0;
|
||||||
|
drives_[drive].target_head_position = (command_.size() > 2) ? command_[2] : -1;
|
||||||
|
drives_[drive].step_rate_counter = 0;
|
||||||
|
|
||||||
|
// Check whether any steps are even needed.
|
||||||
|
if(seek_is_satisfied(drive)) {
|
||||||
|
drives_[drive].phase = Drive::CompletedSeeking;
|
||||||
|
} else {
|
||||||
|
main_status_ |= 1 << (command_[1]&3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
goto wait_for_command;
|
||||||
|
|
||||||
|
// Performs sense interrupt status.
|
||||||
|
sense_interrupt_status:
|
||||||
|
printf("Sense interrupt status\n");
|
||||||
|
{
|
||||||
|
// Find the first drive that is in the CompletedSeeking state.
|
||||||
|
int found_drive = -1;
|
||||||
|
for(int c = 0; c < 4; c++) {
|
||||||
|
if(drives_[c].phase == Drive::CompletedSeeking) {
|
||||||
|
found_drive = c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a drive was found, return its results. Otherwise return a single 0x80.
|
||||||
|
if(found_drive != -1) {
|
||||||
|
drives_[found_drive].phase = Drive::NotSeeking;
|
||||||
|
status_[0] = (uint8_t)found_drive | 0x20;
|
||||||
|
main_status_ &= ~(1 << found_drive);
|
||||||
|
|
||||||
|
result_stack_.push_back(drives_[found_drive].head_position);
|
||||||
|
result_stack_.push_back(status_[0]);
|
||||||
|
} else {
|
||||||
|
result_stack_.push_back(0x80);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
goto post_result;
|
||||||
|
|
||||||
|
// Performs specify.
|
||||||
|
specify:
|
||||||
|
// Just store the values, and terminate the command.
|
||||||
|
step_rate_time_ = command_[1] &0xf0; // i.e. 16 to 240m
|
||||||
|
head_unload_time_ = command_[1] & 0x0f; // i.e. 1 to 16ms
|
||||||
|
head_load_time_ = command_[2] & ~1; // i.e. 2 to 254 ms in increments of 2ms
|
||||||
|
dma_mode_ = !(command_[2] & 1);
|
||||||
|
goto wait_for_command;
|
||||||
|
|
||||||
|
sense_drive_status:
|
||||||
|
printf("Sense drive status unimplemented!!\n");
|
||||||
|
goto wait_for_command;
|
||||||
|
|
||||||
|
// Performs any invalid command.
|
||||||
|
invalid:
|
||||||
|
// A no-op, but posts ST0.
|
||||||
|
result_stack_.push_back(status_[0]);
|
||||||
|
goto post_result;
|
||||||
|
|
||||||
|
// Posts ST0, ST1, ST2, C, H, R and N as a result phase.
|
||||||
|
post_st012chrn:
|
||||||
|
result_stack_.push_back(size_);
|
||||||
|
result_stack_.push_back(sector_);
|
||||||
|
result_stack_.push_back(head_);
|
||||||
|
result_stack_.push_back(cylinder_);
|
||||||
|
|
||||||
|
result_stack_.push_back(status_[2]);
|
||||||
|
result_stack_.push_back(status_[1]);
|
||||||
|
result_stack_.push_back(status_[0]);
|
||||||
|
|
||||||
|
goto post_result;
|
||||||
|
|
||||||
|
// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack — the
|
||||||
|
// last thing in it will be returned first.
|
||||||
|
post_result:
|
||||||
|
// Set ready to send data to the processor, no longer in non-DMA execution phase.
|
||||||
|
main_status_ |= StatusRQM | StatusDIO;
|
||||||
|
main_status_ &= ~StatusNDM;
|
||||||
|
|
||||||
|
// The actual stuff of unwinding result_stack_ is handled by ::get_register; wait
|
||||||
|
// until the processor has read all result bytes.
|
||||||
|
WAIT_FOR_EVENT(Event8272::ResultEmpty);
|
||||||
|
|
||||||
|
// Reset data direction and end the command.
|
||||||
|
main_status_ &= ~StatusDIO;
|
||||||
|
goto wait_for_command;
|
||||||
|
|
||||||
|
END_SECTION()
|
||||||
|
}
|
||||||
|
|
||||||
|
bool i8272::seek_is_satisfied(int drive) {
|
||||||
|
return (drives_[drive].target_head_position == drives_[drive].head_position) ||
|
||||||
|
(drives_[drive].target_head_position == -1 && drives_[drive].drive->get_is_track_zero());
|
||||||
|
}
|
83
Components/8272/i8272.hpp
Normal file
83
Components/8272/i8272.hpp
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
//
|
||||||
|
// i8272.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 05/08/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef i8272_hpp
|
||||||
|
#define i8272_hpp
|
||||||
|
|
||||||
|
#include "../../Storage/Disk/MFMDiskController.hpp"
|
||||||
|
#include "../../Storage/Disk/Drive.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Intel {
|
||||||
|
|
||||||
|
class i8272: public Storage::Disk::MFMController {
|
||||||
|
public:
|
||||||
|
i8272(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute);
|
||||||
|
|
||||||
|
void run_for(Cycles);
|
||||||
|
|
||||||
|
void set_register(int address, uint8_t value);
|
||||||
|
uint8_t get_register(int address);
|
||||||
|
|
||||||
|
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void posit_event(int type);
|
||||||
|
uint8_t main_status_;
|
||||||
|
uint8_t status_[3];
|
||||||
|
|
||||||
|
std::vector<uint8_t> command_;
|
||||||
|
std::vector<uint8_t> result_stack_;
|
||||||
|
|
||||||
|
enum class Event8272: int {
|
||||||
|
CommandByte = (1 << 3),
|
||||||
|
Timer = (1 << 4),
|
||||||
|
ResultEmpty = (1 << 5),
|
||||||
|
};
|
||||||
|
|
||||||
|
int interesting_event_mask_;
|
||||||
|
int resume_point_;
|
||||||
|
int delay_time_;
|
||||||
|
|
||||||
|
int step_rate_time_;
|
||||||
|
int head_unload_time_;
|
||||||
|
int head_load_time_;
|
||||||
|
bool dma_mode_;
|
||||||
|
|
||||||
|
struct Drive {
|
||||||
|
uint8_t head_position;
|
||||||
|
|
||||||
|
enum Phase {
|
||||||
|
NotSeeking,
|
||||||
|
Seeking,
|
||||||
|
CompletedSeeking
|
||||||
|
} phase;
|
||||||
|
int step_rate_counter;
|
||||||
|
int steps_taken;
|
||||||
|
int target_head_position; // either an actual number, or -1 to indicate to step until track zero
|
||||||
|
|
||||||
|
std::shared_ptr<Storage::Disk::Drive> drive;
|
||||||
|
|
||||||
|
Drive() : head_position(0), phase(NotSeeking), drive(new Storage::Disk::Drive) {};
|
||||||
|
} drives_[4];
|
||||||
|
|
||||||
|
uint8_t header_[6];
|
||||||
|
int distance_into_section_;
|
||||||
|
int index_hole_limit_;
|
||||||
|
|
||||||
|
uint8_t cylinder_, head_, sector_, size_;
|
||||||
|
|
||||||
|
bool seek_is_satisfied(int drive);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* i8272_hpp */
|
@ -10,9 +10,10 @@
|
|||||||
|
|
||||||
#include "../../Processors/Z80/Z80.hpp"
|
#include "../../Processors/Z80/Z80.hpp"
|
||||||
|
|
||||||
#include "../../Components/8255/i8255.hpp"
|
|
||||||
#include "../../Components/AY38910/AY38910.hpp"
|
|
||||||
#include "../../Components/6845/CRTC6845.hpp"
|
#include "../../Components/6845/CRTC6845.hpp"
|
||||||
|
#include "../../Components/8255/i8255.hpp"
|
||||||
|
#include "../../Components/8272/i8272.hpp"
|
||||||
|
#include "../../Components/AY38910/AY38910.hpp"
|
||||||
|
|
||||||
#include "../../Storage/Tape/Tape.hpp"
|
#include "../../Storage/Tape/Tape.hpp"
|
||||||
|
|
||||||
@ -427,6 +428,19 @@ struct KeyboardState {
|
|||||||
uint8_t rows[10];
|
uint8_t rows[10];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Wraps the 8272 so as to provide proper clocking and RPM counts, and just directly
|
||||||
|
exposes motor control, applying the same value to all drives.
|
||||||
|
*/
|
||||||
|
class FDC: public Intel::i8272 {
|
||||||
|
public:
|
||||||
|
FDC() : i8272(Cycles(8000000), 16, 300) {}
|
||||||
|
|
||||||
|
void set_motor_on(bool on) {
|
||||||
|
Intel::i8272::set_motor_on(on);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Provides the mechanism of receipt for input and output of the 8255's various ports.
|
Provides the mechanism of receipt for input and output of the 8255's various ports.
|
||||||
*/
|
*/
|
||||||
@ -545,6 +559,9 @@ class ConcreteMachine:
|
|||||||
// Pump the AY.
|
// Pump the AY.
|
||||||
ay_.run_for(cycle.length);
|
ay_.run_for(cycle.length);
|
||||||
|
|
||||||
|
// Clock the FDC, if connected, using a lazy scale by two
|
||||||
|
if(has_fdc_) fdc_.run_for(Cycles(cycle.length.as_int()));
|
||||||
|
|
||||||
// Stop now if no action is strictly required.
|
// Stop now if no action is strictly required.
|
||||||
if(!cycle.is_terminal()) return HalfCycles(0);
|
if(!cycle.is_terminal()) return HalfCycles(0);
|
||||||
|
|
||||||
@ -567,8 +584,10 @@ class ConcreteMachine:
|
|||||||
case 1: crtc_bus_handler_.set_colour(*cycle.value & 0x1f); break;
|
case 1: crtc_bus_handler_.set_colour(*cycle.value & 0x1f); break;
|
||||||
case 2:
|
case 2:
|
||||||
// Perform ROM paging.
|
// Perform ROM paging.
|
||||||
read_pointers_[0] = (*cycle.value & 4) ? &ram_[0] : os_.data();
|
read_pointers_[0] = (*cycle.value & 4) ? &ram_[0] : roms_[rom_model_].data();
|
||||||
read_pointers_[3] = (*cycle.value & 8) ? &ram_[49152] : basic_.data();
|
|
||||||
|
upper_rom_is_paged_ = !(*cycle.value & 8);
|
||||||
|
read_pointers_[3] = upper_rom_is_paged_ ? roms_[upper_rom_].data() : &ram_[49152];
|
||||||
|
|
||||||
// Reset the interrupt timer if requested.
|
// Reset the interrupt timer if requested.
|
||||||
if(*cycle.value & 0x10) interrupt_timer_.reset_count();
|
if(*cycle.value & 0x10) interrupt_timer_.reset_count();
|
||||||
@ -580,6 +599,12 @@ class ConcreteMachine:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for an upper ROM selection
|
||||||
|
if(has_fdc_ && !(address&0x2000)) {
|
||||||
|
upper_rom_ = (*cycle.value == 7) ? ROMType::AMSDOS : rom_model_ + 1;
|
||||||
|
if(upper_rom_is_paged_) read_pointers_[3] = roms_[upper_rom_].data();
|
||||||
|
}
|
||||||
|
|
||||||
// Check for a CRTC access
|
// Check for a CRTC access
|
||||||
if(!(address & 0x4000)) {
|
if(!(address & 0x4000)) {
|
||||||
switch((address >> 8) & 3) {
|
switch((address >> 8) & 3) {
|
||||||
@ -593,6 +618,16 @@ class ConcreteMachine:
|
|||||||
if(!(address & 0x800)) {
|
if(!(address & 0x800)) {
|
||||||
i8255_.set_register((address >> 8) & 3, *cycle.value);
|
i8255_.set_register((address >> 8) & 3, *cycle.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for an FDC access
|
||||||
|
if(has_fdc_ && (address & 0x580) == 0x100) {
|
||||||
|
fdc_.set_register(address & 1, *cycle.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a disk motor access
|
||||||
|
if(has_fdc_ && !(address & 0x580)) {
|
||||||
|
fdc_.set_motor_on(!!(*cycle.value));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case CPU::Z80::PartialMachineCycle::Input:
|
case CPU::Z80::PartialMachineCycle::Input:
|
||||||
// Default to nothing answering
|
// Default to nothing answering
|
||||||
@ -611,6 +646,11 @@ class ConcreteMachine:
|
|||||||
if(!(address & 0x800)) {
|
if(!(address & 0x800)) {
|
||||||
*cycle.value = i8255_.get_register((address >> 8) & 3);
|
*cycle.value = i8255_.get_register((address >> 8) & 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for an FDC access
|
||||||
|
if(has_fdc_ && (address & 0x580) == 0x100) {
|
||||||
|
*cycle.value = fdc_.get_register(address & 1);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CPU::Z80::PartialMachineCycle::Interrupt:
|
case CPU::Z80::PartialMachineCycle::Interrupt:
|
||||||
@ -666,11 +706,28 @@ class ConcreteMachine:
|
|||||||
|
|
||||||
/// The ConfigurationTarget entry point; should configure this meachine as described by @c target.
|
/// The ConfigurationTarget entry point; should configure this meachine as described by @c target.
|
||||||
void configure_as_target(const StaticAnalyser::Target &target) {
|
void configure_as_target(const StaticAnalyser::Target &target) {
|
||||||
// Establish reset memory map as per machine model (or, for now, as a hard-wired 464)
|
switch(target.amstradcpc.model) {
|
||||||
read_pointers_[0] = os_.data();
|
case StaticAnalyser::AmstradCPCModel::CPC464:
|
||||||
|
rom_model_ = ROMType::OS464;
|
||||||
|
has_fdc_ = false;
|
||||||
|
break;
|
||||||
|
case StaticAnalyser::AmstradCPCModel::CPC664:
|
||||||
|
rom_model_ = ROMType::OS664;
|
||||||
|
has_fdc_ = true;
|
||||||
|
break;
|
||||||
|
case StaticAnalyser::AmstradCPCModel::CPC6128:
|
||||||
|
rom_model_ = ROMType::OS6128;
|
||||||
|
has_fdc_ = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establish default memory map
|
||||||
|
upper_rom_is_paged_ = true;
|
||||||
|
upper_rom_ = rom_model_ + 1;
|
||||||
|
read_pointers_[0] = roms_[rom_model_].data();
|
||||||
read_pointers_[1] = &ram_[16384];
|
read_pointers_[1] = &ram_[16384];
|
||||||
read_pointers_[2] = &ram_[32768];
|
read_pointers_[2] = &ram_[32768];
|
||||||
read_pointers_[3] = basic_.data();
|
read_pointers_[3] = roms_[upper_rom_].data();
|
||||||
|
|
||||||
write_pointers_[0] = &ram_[0];
|
write_pointers_[0] = &ram_[0];
|
||||||
write_pointers_[1] = &ram_[16384];
|
write_pointers_[1] = &ram_[16384];
|
||||||
@ -681,16 +738,19 @@ class ConcreteMachine:
|
|||||||
if(!target.tapes.empty()) {
|
if(!target.tapes.empty()) {
|
||||||
tape_player_.set_tape(target.tapes.front());
|
tape_player_.set_tape(target.tapes.front());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Insert up to four disks.
|
||||||
|
int c = 0;
|
||||||
|
for(auto &disk : target.disks) {
|
||||||
|
fdc_.set_disk(disk, c);
|
||||||
|
c++;
|
||||||
|
if(c == 4) break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// See header; provides the system ROMs.
|
// See header; provides the system ROMs.
|
||||||
void set_rom(ROMType type, std::vector<uint8_t> data) {
|
void set_rom(ROMType type, std::vector<uint8_t> data) {
|
||||||
// Keep only the two ROMs that are currently of interest.
|
roms_[(int)type] = data;
|
||||||
switch(type) {
|
|
||||||
case ROMType::OS464: os_ = data; break;
|
|
||||||
case ROMType::BASIC464: basic_ = data; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// See header; sets a key as either pressed or released.
|
// See header; sets a key as either pressed or released.
|
||||||
@ -715,6 +775,8 @@ class ConcreteMachine:
|
|||||||
i8255PortHandler i8255_port_handler_;
|
i8255PortHandler i8255_port_handler_;
|
||||||
Intel::i8255::i8255<i8255PortHandler> i8255_;
|
Intel::i8255::i8255<i8255PortHandler> i8255_;
|
||||||
|
|
||||||
|
FDC fdc_;
|
||||||
|
|
||||||
InterruptTimer interrupt_timer_;
|
InterruptTimer interrupt_timer_;
|
||||||
Storage::Tape::BinaryTapePlayer tape_player_;
|
Storage::Tape::BinaryTapePlayer tape_player_;
|
||||||
|
|
||||||
@ -722,8 +784,12 @@ class ConcreteMachine:
|
|||||||
HalfCycles crtc_counter_;
|
HalfCycles crtc_counter_;
|
||||||
HalfCycles half_cycles_since_ay_update_;
|
HalfCycles half_cycles_since_ay_update_;
|
||||||
|
|
||||||
uint8_t ram_[65536];
|
uint8_t ram_[128 * 1024];
|
||||||
std::vector<uint8_t> os_, basic_;
|
std::vector<uint8_t> roms_[7];
|
||||||
|
int rom_model_;
|
||||||
|
bool has_fdc_;
|
||||||
|
bool upper_rom_is_paged_;
|
||||||
|
int upper_rom_;
|
||||||
|
|
||||||
uint8_t *read_pointers_[4];
|
uint8_t *read_pointers_[4];
|
||||||
uint8_t *write_pointers_[4];
|
uint8_t *write_pointers_[4];
|
||||||
|
@ -18,9 +18,10 @@
|
|||||||
|
|
||||||
namespace AmstradCPC {
|
namespace AmstradCPC {
|
||||||
|
|
||||||
enum ROMType: uint8_t {
|
enum ROMType: int {
|
||||||
OS464, OS664, OS6128,
|
OS464 = 0, BASIC464,
|
||||||
BASIC464, BASIC664, BASIC6128,
|
OS664, BASIC664,
|
||||||
|
OS6128, BASIC6128,
|
||||||
AMSDOS
|
AMSDOS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -395,6 +395,8 @@
|
|||||||
4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */; };
|
4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */; };
|
||||||
4BB73EC21B587A5100552FC2 /* Clock_SignalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */; };
|
4BB73EC21B587A5100552FC2 /* Clock_SignalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */; };
|
||||||
4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */; };
|
4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */; };
|
||||||
|
4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC951C1F368D83008F4C34 /* i8272.cpp */; };
|
||||||
|
4BBC95221F36B16C008F4C34 /* MFMDiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC95201F36B16C008F4C34 /* MFMDiskController.cpp */; };
|
||||||
4BBF49AF1ED2880200AB3669 /* FUSETests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF49AE1ED2880200AB3669 /* FUSETests.swift */; };
|
4BBF49AF1ED2880200AB3669 /* FUSETests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF49AE1ED2880200AB3669 /* FUSETests.swift */; };
|
||||||
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */; };
|
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */; };
|
||||||
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */; };
|
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */; };
|
||||||
@ -954,6 +956,10 @@
|
|||||||
4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntermediateShader.cpp; sourceTree = "<group>"; };
|
4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntermediateShader.cpp; sourceTree = "<group>"; };
|
||||||
4BBB14301CD2CECE00BDB55C /* IntermediateShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IntermediateShader.hpp; sourceTree = "<group>"; };
|
4BBB14301CD2CECE00BDB55C /* IntermediateShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IntermediateShader.hpp; sourceTree = "<group>"; };
|
||||||
4BBC34241D2208B100FFC9DF /* CSFastLoading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSFastLoading.h; sourceTree = "<group>"; };
|
4BBC34241D2208B100FFC9DF /* CSFastLoading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSFastLoading.h; sourceTree = "<group>"; };
|
||||||
|
4BBC951C1F368D83008F4C34 /* i8272.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = i8272.cpp; path = 8272/i8272.cpp; sourceTree = "<group>"; };
|
||||||
|
4BBC951D1F368D83008F4C34 /* i8272.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = i8272.hpp; path = 8272/i8272.hpp; sourceTree = "<group>"; };
|
||||||
|
4BBC95201F36B16C008F4C34 /* MFMDiskController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MFMDiskController.cpp; sourceTree = "<group>"; };
|
||||||
|
4BBC95211F36B16C008F4C34 /* MFMDiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MFMDiskController.hpp; sourceTree = "<group>"; };
|
||||||
4BBF49AE1ED2880200AB3669 /* FUSETests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FUSETests.swift; sourceTree = "<group>"; };
|
4BBF49AE1ED2880200AB3669 /* FUSETests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FUSETests.swift; sourceTree = "<group>"; };
|
||||||
4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextureBuilder.cpp; sourceTree = "<group>"; };
|
4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextureBuilder.cpp; sourceTree = "<group>"; };
|
||||||
4BBF99091C8FBA6F0075DAFB /* TextureBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TextureBuilder.hpp; sourceTree = "<group>"; };
|
4BBF99091C8FBA6F0075DAFB /* TextureBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TextureBuilder.hpp; sourceTree = "<group>"; };
|
||||||
@ -1525,6 +1531,7 @@
|
|||||||
4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */,
|
4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */,
|
||||||
4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */,
|
4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */,
|
||||||
4B30512B1D989E2200B4FED8 /* Drive.cpp */,
|
4B30512B1D989E2200B4FED8 /* Drive.cpp */,
|
||||||
|
4BBC95201F36B16C008F4C34 /* MFMDiskController.cpp */,
|
||||||
4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */,
|
4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */,
|
||||||
4B121F961E060CF000BFDA12 /* PCMSegment.cpp */,
|
4B121F961E060CF000BFDA12 /* PCMSegment.cpp */,
|
||||||
4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */,
|
4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */,
|
||||||
@ -1532,6 +1539,7 @@
|
|||||||
4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */,
|
4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */,
|
||||||
4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */,
|
4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */,
|
||||||
4B30512C1D989E2200B4FED8 /* Drive.hpp */,
|
4B30512C1D989E2200B4FED8 /* Drive.hpp */,
|
||||||
|
4BBC95211F36B16C008F4C34 /* MFMDiskController.hpp */,
|
||||||
4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */,
|
4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */,
|
||||||
4B121F971E060CF000BFDA12 /* PCMSegment.hpp */,
|
4B121F971E060CF000BFDA12 /* PCMSegment.hpp */,
|
||||||
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */,
|
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */,
|
||||||
@ -1982,6 +1990,15 @@
|
|||||||
path = ../../Processors;
|
path = ../../Processors;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
4BBC951F1F368D87008F4C34 /* 8272 */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4BBC951C1F368D83008F4C34 /* i8272.cpp */,
|
||||||
|
4BBC951D1F368D83008F4C34 /* i8272.hpp */,
|
||||||
|
);
|
||||||
|
name = 8272;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
4BBF49B41ED2881600AB3669 /* FUSE */ = {
|
4BBF49B41ED2881600AB3669 /* FUSE */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -2048,6 +2065,7 @@
|
|||||||
4B4A762D1DB1A35C007AAE2E /* AY38910 */,
|
4B4A762D1DB1A35C007AAE2E /* AY38910 */,
|
||||||
4BE845221F2FF7F400A5EA22 /* 6845 */,
|
4BE845221F2FF7F400A5EA22 /* 6845 */,
|
||||||
4BD9137C1F3115AC009BCF85 /* 8255 */,
|
4BD9137C1F3115AC009BCF85 /* 8255 */,
|
||||||
|
4BBC951F1F368D87008F4C34 /* 8272 */,
|
||||||
);
|
);
|
||||||
name = Components;
|
name = Components;
|
||||||
path = ../../Components;
|
path = ../../Components;
|
||||||
@ -2732,12 +2750,14 @@
|
|||||||
4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */,
|
4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */,
|
||||||
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
|
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
|
||||||
4B3940E71DA83C8300427841 /* AsyncTaskQueue.cpp in Sources */,
|
4B3940E71DA83C8300427841 /* AsyncTaskQueue.cpp in Sources */,
|
||||||
|
4BBC95221F36B16C008F4C34 /* MFMDiskController.cpp in Sources */,
|
||||||
4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */,
|
4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */,
|
||||||
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */,
|
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */,
|
||||||
4B8378DF1F33675F005CA9E4 /* CharacterMapper.cpp in Sources */,
|
4B8378DF1F33675F005CA9E4 /* CharacterMapper.cpp in Sources */,
|
||||||
4B8FE2291DA1EDDF0090D3CE /* ElectronOptionsPanel.swift in Sources */,
|
4B8FE2291DA1EDDF0090D3CE /* ElectronOptionsPanel.swift in Sources */,
|
||||||
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */,
|
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */,
|
||||||
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */,
|
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */,
|
||||||
|
4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */,
|
||||||
4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */,
|
4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */,
|
||||||
4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */,
|
4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */,
|
||||||
4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */,
|
4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */,
|
||||||
|
@ -19,5 +19,8 @@ void StaticAnalyser::AmstradCPC::AddTargets(
|
|||||||
target.disks = disks;
|
target.disks = disks;
|
||||||
target.tapes = tapes;
|
target.tapes = tapes;
|
||||||
target.cartridges = cartridges;
|
target.cartridges = cartridges;
|
||||||
|
|
||||||
|
target.amstradcpc.model = target.disks.empty() ? AmstradCPCModel::CPC464 : AmstradCPCModel::CPC664;
|
||||||
|
|
||||||
destination.push_back(target);
|
destination.push_back(target);
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,12 @@ enum class ZX8081MemoryModel {
|
|||||||
SixtyFourKB
|
SixtyFourKB
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class AmstradCPCModel {
|
||||||
|
CPC464,
|
||||||
|
CPC664,
|
||||||
|
CPC6128
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
A list of disks, tapes and cartridges plus information about the machine to which to attach them and its configuration,
|
A list of disks, tapes and cartridges plus information about the machine to which to attach them and its configuration,
|
||||||
and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
|
and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
|
||||||
@ -61,6 +67,8 @@ struct Target {
|
|||||||
} machine;
|
} machine;
|
||||||
float probability;
|
float probability;
|
||||||
|
|
||||||
|
// TODO: this is too C-like a solution; make Target a base class and
|
||||||
|
// turn the following into information held by more specific subclasses.
|
||||||
union {
|
union {
|
||||||
struct {
|
struct {
|
||||||
bool has_adfs;
|
bool has_adfs;
|
||||||
@ -87,6 +95,10 @@ struct Target {
|
|||||||
ZX8081MemoryModel memory_model;
|
ZX8081MemoryModel memory_model;
|
||||||
bool isZX81;
|
bool isZX81;
|
||||||
} zx8081;
|
} zx8081;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
AmstradCPCModel model;
|
||||||
|
} amstradcpc;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string loadingCommand;
|
std::string loadingCommand;
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
|
|
||||||
using namespace Storage::Disk;
|
using namespace Storage::Disk;
|
||||||
|
|
||||||
Controller::Controller(int clock_rate, int clock_rate_multiplier, int revolutions_per_minute) :
|
Controller::Controller(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) :
|
||||||
clock_rate_(clock_rate * clock_rate_multiplier),
|
clock_rate_(clock_rate.as_int() * clock_rate_multiplier),
|
||||||
clock_rate_multiplier_(clock_rate_multiplier),
|
clock_rate_multiplier_(clock_rate_multiplier),
|
||||||
rotational_multiplier_(60, revolutions_per_minute),
|
rotational_multiplier_(60, revolutions_per_minute),
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ Controller::Controller(int clock_rate, int clock_rate_multiplier, int revolution
|
|||||||
|
|
||||||
is_reading_(true),
|
is_reading_(true),
|
||||||
|
|
||||||
TimedEventLoop((unsigned int)(clock_rate * clock_rate_multiplier)) {
|
TimedEventLoop((unsigned int)(clock_rate.as_int() * clock_rate_multiplier)) {
|
||||||
// seed this class with a PLL, any PLL, so that it's safe to assume non-nullptr later
|
// seed this class with a PLL, any PLL, so that it's safe to assume non-nullptr later
|
||||||
Time one(1);
|
Time one(1);
|
||||||
set_expected_bit_length(one);
|
set_expected_bit_length(one);
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#include "PCMSegment.hpp"
|
#include "PCMSegment.hpp"
|
||||||
#include "PCMPatchedTrack.hpp"
|
#include "PCMPatchedTrack.hpp"
|
||||||
#include "../TimedEventLoop.hpp"
|
#include "../TimedEventLoop.hpp"
|
||||||
|
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||||
|
|
||||||
namespace Storage {
|
namespace Storage {
|
||||||
namespace Disk {
|
namespace Disk {
|
||||||
@ -33,7 +34,7 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
|
|||||||
Constructs a @c DiskDrive that will be run at @c clock_rate and runs its PLL at @c clock_rate*clock_rate_multiplier,
|
Constructs a @c DiskDrive that will be run at @c clock_rate and runs its PLL at @c clock_rate*clock_rate_multiplier,
|
||||||
spinning inserted disks at @c revolutions_per_minute.
|
spinning inserted disks at @c revolutions_per_minute.
|
||||||
*/
|
*/
|
||||||
Controller(int clock_rate, int clock_rate_multiplier, int revolutions_per_minute);
|
Controller(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Communicates to the PLL the expected length of a bit as a fraction of a second.
|
Communicates to the PLL the expected length of a bit as a fraction of a second.
|
||||||
|
@ -16,6 +16,7 @@ CPCDSK::CPCDSK(const char *file_name) :
|
|||||||
Storage::FileHolder(file_name), is_extended_(false) {
|
Storage::FileHolder(file_name), is_extended_(false) {
|
||||||
if(!check_signature("MV - CPC", 8)) {
|
if(!check_signature("MV - CPC", 8)) {
|
||||||
is_extended_ = true;
|
is_extended_ = true;
|
||||||
|
fseek(file_, 0, SEEK_SET);
|
||||||
if(!check_signature("EXTENDED", 8))
|
if(!check_signature("EXTENDED", 8))
|
||||||
throw ErrorNotCPCDSK;
|
throw ErrorNotCPCDSK;
|
||||||
}
|
}
|
||||||
@ -26,6 +27,8 @@ CPCDSK::CPCDSK(const char *file_name) :
|
|||||||
head_count_ = (unsigned int)fgetc(file_);
|
head_count_ = (unsigned int)fgetc(file_);
|
||||||
|
|
||||||
if(is_extended_) {
|
if(is_extended_) {
|
||||||
|
// Skip two unused bytes and grab the track size table.
|
||||||
|
fseek(file_, 2, SEEK_CUR);
|
||||||
for(unsigned int c = 0; c < head_position_count_ * head_count_; c++) {
|
for(unsigned int c = 0; c < head_position_count_ * head_count_; c++) {
|
||||||
track_sizes_.push_back((size_t)(fgetc(file_) << 8));
|
track_sizes_.push_back((size_t)(fgetc(file_) << 8));
|
||||||
}
|
}
|
||||||
@ -59,6 +62,7 @@ std::shared_ptr<Track> CPCDSK::get_uncached_track_at_position(unsigned int head,
|
|||||||
unsigned int t = 0;
|
unsigned int t = 0;
|
||||||
while(t < chronological_track && t < track_sizes_.size()) {
|
while(t < chronological_track && t < track_sizes_.size()) {
|
||||||
file_offset += track_sizes_[t];
|
file_offset += track_sizes_[t];
|
||||||
|
t++;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Tracks are a fixed size in the original DSK file format.
|
// Tracks are a fixed size in the original DSK file format.
|
||||||
@ -79,48 +83,71 @@ std::shared_ptr<Track> CPCDSK::get_uncached_track_at_position(unsigned int head,
|
|||||||
uint8_t track;
|
uint8_t track;
|
||||||
uint8_t side;
|
uint8_t side;
|
||||||
uint8_t sector;
|
uint8_t sector;
|
||||||
size_t length;
|
uint8_t length;
|
||||||
uint8_t status1;
|
uint8_t status1;
|
||||||
uint8_t status2;
|
uint8_t status2;
|
||||||
|
size_t actual_length;
|
||||||
};
|
};
|
||||||
std::vector<SectorInfo> sector_infos;
|
std::vector<SectorInfo> sector_infos;
|
||||||
while(number_of_sectors--) {
|
while(number_of_sectors--) {
|
||||||
SectorInfo new_sector;
|
SectorInfo sector_info;
|
||||||
|
|
||||||
new_sector.track = (uint8_t)fgetc(file_);
|
sector_info.track = (uint8_t)fgetc(file_);
|
||||||
new_sector.side = (uint8_t)fgetc(file_);
|
sector_info.side = (uint8_t)fgetc(file_);
|
||||||
new_sector.sector = (uint8_t)fgetc(file_);
|
sector_info.sector = (uint8_t)fgetc(file_);
|
||||||
new_sector.length = (size_t)(128 << fgetc(file_));
|
sector_info.length = (uint8_t)fgetc(file_);
|
||||||
if(new_sector.length == 0x2000) new_sector.length = 0x1800;
|
sector_info.status1 = (uint8_t)fgetc(file_);
|
||||||
new_sector.status1 = (uint8_t)fgetc(file_);
|
sector_info.status2 = (uint8_t)fgetc(file_);
|
||||||
new_sector.status2 = (uint8_t)fgetc(file_);
|
sector_info.actual_length = fgetc16le();
|
||||||
fseek(file_, 2, SEEK_CUR);
|
|
||||||
|
|
||||||
sector_infos.push_back(new_sector);
|
sector_infos.push_back(sector_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the sectors.
|
// Get the sectors.
|
||||||
fseek(file_, file_offset + 0x100, SEEK_SET);
|
fseek(file_, file_offset + 0x100, SEEK_SET);
|
||||||
if(is_extended_) {
|
std::vector<Storage::Encodings::MFM::Sector> sectors;
|
||||||
// TODO: everything about extended disk images
|
for(auto §or_info : sector_infos) {
|
||||||
} else {
|
Storage::Encodings::MFM::Sector new_sector;
|
||||||
std::vector<Storage::Encodings::MFM::Sector> sectors;
|
new_sector.track = sector_info.track;
|
||||||
for(auto §or_info : sector_infos) {
|
new_sector.side = sector_info.side;
|
||||||
Storage::Encodings::MFM::Sector new_sector;
|
new_sector.sector = sector_info.sector;
|
||||||
new_sector.track = sector_info.track;
|
|
||||||
new_sector.side = sector_info.side;
|
|
||||||
new_sector.sector = sector_info.sector;
|
|
||||||
new_sector.data.resize(sector_info.length);
|
|
||||||
fread(new_sector.data.data(), sizeof(uint8_t), sector_info.length, file_);
|
|
||||||
|
|
||||||
// TODO: obey the status bytes, somehow (?)
|
size_t data_size;
|
||||||
|
if(is_extended_) {
|
||||||
|
data_size = sector_info.actual_length;
|
||||||
|
} else {
|
||||||
|
data_size = (size_t)(128 << sector_info.length);
|
||||||
|
if(data_size == 0x2000) data_size = 0x1800;
|
||||||
|
}
|
||||||
|
new_sector.data.resize(data_size);
|
||||||
|
fread(new_sector.data.data(), sizeof(uint8_t), data_size, file_);
|
||||||
|
|
||||||
sectors.push_back(std::move(new_sector));
|
// TODO: obey the status bytes, somehow (?)
|
||||||
|
if(sector_info.status1 || sector_info.status2) {
|
||||||
|
if(sector_info.status1 & 0x08) {
|
||||||
|
// The CRC failed in the ID field.
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sector_info.status2 & 0x20) {
|
||||||
|
// The CRC failed in the data field.
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sector_info.status2 & 0x40) {
|
||||||
|
// This sector is marked as deleted.
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sector_info.status2 & 0x01) {
|
||||||
|
// Data field wasn't found.
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Unhandled: status errors\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: supply gay 3 length and filler byte
|
sectors.push_back(std::move(new_sector));
|
||||||
if(sectors.size()) return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: supply gay 3 length and filler byte
|
||||||
|
if(sectors.size()) return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors);
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
155
Storage/Disk/MFMDiskController.cpp
Normal file
155
Storage/Disk/MFMDiskController.cpp
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
//
|
||||||
|
// MFMDiskController.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 05/08/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "MFMDiskController.hpp"
|
||||||
|
|
||||||
|
#include "../../Storage/Disk/Encodings/MFM.hpp"
|
||||||
|
|
||||||
|
using namespace Storage::Disk;
|
||||||
|
|
||||||
|
MFMController::MFMController(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) :
|
||||||
|
Storage::Disk::Controller(clock_rate, clock_rate_multiplier, revolutions_per_minute),
|
||||||
|
crc_generator_(0x1021, 0xffff),
|
||||||
|
data_mode_(DataMode::Scanning),
|
||||||
|
is_awaiting_marker_value_(false) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void MFMController::process_index_hole() {
|
||||||
|
posit_event((int)Event::IndexHole);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MFMController::process_write_completed() {
|
||||||
|
posit_event((int)Event::DataWritten);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MFMController::set_is_double_density(bool is_double_density) {
|
||||||
|
is_double_density_ = is_double_density;
|
||||||
|
Storage::Time bit_length;
|
||||||
|
bit_length.length = 1;
|
||||||
|
bit_length.clock_rate = is_double_density ? 500000 : 250000;
|
||||||
|
set_expected_bit_length(bit_length);
|
||||||
|
|
||||||
|
if(!is_double_density) is_awaiting_marker_value_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MFMController::get_is_double_density() {
|
||||||
|
return is_double_density_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MFMController::set_data_mode(DataMode mode) {
|
||||||
|
data_mode_ = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
MFMController::Token MFMController::get_latest_token() {
|
||||||
|
return latest_token_;
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberTheory::CRC16 &MFMController::get_crc_generator() {
|
||||||
|
return crc_generator_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MFMController::process_input_bit(int value, unsigned int cycles_since_index_hole) {
|
||||||
|
if(data_mode_ == DataMode::Writing) return;
|
||||||
|
|
||||||
|
shift_register_ = (shift_register_ << 1) | value;
|
||||||
|
bits_since_token_++;
|
||||||
|
|
||||||
|
if(data_mode_ == DataMode::Scanning) {
|
||||||
|
Token::Type token_type = Token::Byte;
|
||||||
|
if(!is_double_density_) {
|
||||||
|
switch(shift_register_ & 0xffff) {
|
||||||
|
case Storage::Encodings::MFM::FMIndexAddressMark:
|
||||||
|
token_type = Token::Index;
|
||||||
|
crc_generator_.reset();
|
||||||
|
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::IndexAddressByte);
|
||||||
|
break;
|
||||||
|
case Storage::Encodings::MFM::FMIDAddressMark:
|
||||||
|
token_type = Token::ID;
|
||||||
|
crc_generator_.reset();
|
||||||
|
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::IDAddressByte);
|
||||||
|
break;
|
||||||
|
case Storage::Encodings::MFM::FMDataAddressMark:
|
||||||
|
token_type = Token::Data;
|
||||||
|
crc_generator_.reset();
|
||||||
|
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::DataAddressByte);
|
||||||
|
break;
|
||||||
|
case Storage::Encodings::MFM::FMDeletedDataAddressMark:
|
||||||
|
token_type = Token::DeletedData;
|
||||||
|
crc_generator_.reset();
|
||||||
|
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::DeletedDataAddressByte);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch(shift_register_ & 0xffff) {
|
||||||
|
case Storage::Encodings::MFM::MFMIndexSync:
|
||||||
|
bits_since_token_ = 0;
|
||||||
|
is_awaiting_marker_value_ = true;
|
||||||
|
|
||||||
|
token_type = Token::Sync;
|
||||||
|
latest_token_.byte_value = Storage::Encodings::MFM::MFMIndexSyncByteValue;
|
||||||
|
break;
|
||||||
|
case Storage::Encodings::MFM::MFMSync:
|
||||||
|
bits_since_token_ = 0;
|
||||||
|
is_awaiting_marker_value_ = true;
|
||||||
|
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
|
||||||
|
|
||||||
|
token_type = Token::Sync;
|
||||||
|
latest_token_.byte_value = Storage::Encodings::MFM::MFMSyncByteValue;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(token_type != Token::Byte) {
|
||||||
|
latest_token_.type = token_type;
|
||||||
|
bits_since_token_ = 0;
|
||||||
|
posit_event((int)Event::Token);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(bits_since_token_ == 16) {
|
||||||
|
latest_token_.type = Token::Byte;
|
||||||
|
latest_token_.byte_value = (uint8_t)(
|
||||||
|
((shift_register_ & 0x0001) >> 0) |
|
||||||
|
((shift_register_ & 0x0004) >> 1) |
|
||||||
|
((shift_register_ & 0x0010) >> 2) |
|
||||||
|
((shift_register_ & 0x0040) >> 3) |
|
||||||
|
((shift_register_ & 0x0100) >> 4) |
|
||||||
|
((shift_register_ & 0x0400) >> 5) |
|
||||||
|
((shift_register_ & 0x1000) >> 6) |
|
||||||
|
((shift_register_ & 0x4000) >> 7));
|
||||||
|
bits_since_token_ = 0;
|
||||||
|
|
||||||
|
if(is_awaiting_marker_value_ && is_double_density_) {
|
||||||
|
is_awaiting_marker_value_ = false;
|
||||||
|
switch(latest_token_.byte_value) {
|
||||||
|
case Storage::Encodings::MFM::IndexAddressByte:
|
||||||
|
latest_token_.type = Token::Index;
|
||||||
|
break;
|
||||||
|
case Storage::Encodings::MFM::IDAddressByte:
|
||||||
|
latest_token_.type = Token::ID;
|
||||||
|
break;
|
||||||
|
case Storage::Encodings::MFM::DataAddressByte:
|
||||||
|
latest_token_.type = Token::Data;
|
||||||
|
break;
|
||||||
|
case Storage::Encodings::MFM::DeletedDataAddressByte:
|
||||||
|
latest_token_.type = Token::DeletedData;
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
crc_generator_.add(latest_token_.byte_value);
|
||||||
|
posit_event((int)Event::Token);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
118
Storage/Disk/MFMDiskController.hpp
Normal file
118
Storage/Disk/MFMDiskController.hpp
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
//
|
||||||
|
// MFMDiskController.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 05/08/2017.
|
||||||
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef MFMDiskController_hpp
|
||||||
|
#define MFMDiskController_hpp
|
||||||
|
|
||||||
|
#include "DiskController.hpp"
|
||||||
|
#include "../../NumberTheory/CRC.hpp"
|
||||||
|
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||||
|
|
||||||
|
namespace Storage {
|
||||||
|
namespace Disk {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Extends Controller with a built-in shift register and FM/MFM decoding logic,
|
||||||
|
being able to post event messages to subclasses.
|
||||||
|
*/
|
||||||
|
class MFMController: public Controller {
|
||||||
|
public:
|
||||||
|
MFMController(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Indicates whether the controller should try to decode double-density MFM content, or single-density FM content.
|
||||||
|
void set_is_double_density(bool);
|
||||||
|
|
||||||
|
/// @returns @c true if currently decoding MFM content; @c false otherwise.
|
||||||
|
bool get_is_double_density();
|
||||||
|
|
||||||
|
enum DataMode {
|
||||||
|
/// When the controller is scanning it will obey all synchronisation marks found, even if in the middle of data.
|
||||||
|
Scanning,
|
||||||
|
/// When the controller is reading it will ignore synchronisation marks and simply return a new token every sixteen PLL clocks.
|
||||||
|
Reading,
|
||||||
|
/// When the controller is writing, it will replace the underlying data with that which has been enqueued, posting Event::DataWritten when the queue is empty.
|
||||||
|
Writing
|
||||||
|
};
|
||||||
|
/// Sets the current data mode.
|
||||||
|
void set_data_mode(DataMode);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Describes a token found in the incoming PLL bit stream. Tokens can be one of:
|
||||||
|
|
||||||
|
Index: the bit pattern usually encoded at the start of a track to denote the position of the index hole;
|
||||||
|
ID: the pattern that begins an ID section, i.e. a sector header, announcing sector number, track number, etc.
|
||||||
|
Data: the pattern that begins a data section, i.e. sector contents.
|
||||||
|
DeletedData: the pattern that begins a deleted data section, i.e. deleted sector contents.
|
||||||
|
|
||||||
|
Sync: MFM only; the same synchronisation mark is used in MFM to denote the bottom three of the four types
|
||||||
|
of token listed above; this class combines notification of that mark and the distinct index sync mark.
|
||||||
|
Both are followed by a byte to indicate type. When scanning an MFM stream, subclasses will receive an
|
||||||
|
announcement of sync followed by an announcement of one of the above four types of token.
|
||||||
|
|
||||||
|
Byte: reports reading of an ordinary byte, with expected timing bits.
|
||||||
|
|
||||||
|
When the data mode is set to 'reading', only Byte tokens are returned; detection of the other kinds of token
|
||||||
|
is suppressed. Controllers will likely want to switch data mode when receiving ID and sector contents, as
|
||||||
|
spurious sync signals can otherwise be found in ordinary data, causing framing errors.
|
||||||
|
*/
|
||||||
|
struct Token {
|
||||||
|
enum Type {
|
||||||
|
Index, ID, Data, DeletedData, Sync, Byte
|
||||||
|
} type;
|
||||||
|
uint8_t byte_value;
|
||||||
|
};
|
||||||
|
/// @returns The most-recently read token from the surface of the disk.
|
||||||
|
Token get_latest_token();
|
||||||
|
|
||||||
|
/// @returns The controller's CRC generator. This is automatically fed during reading.
|
||||||
|
NumberTheory::CRC16 &get_crc_generator();
|
||||||
|
|
||||||
|
// Events
|
||||||
|
enum class Event: int {
|
||||||
|
Token = (1 << 0), // Indicates recognition of a new token in the flux stream. Use get_latest_token() for more details.
|
||||||
|
IndexHole = (1 << 1), // Indicates the passing of a physical index hole.
|
||||||
|
DataWritten = (1 << 2), // Indicates that all queued bits have been written
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Subclasses should implement this. It is called every time a new @c Event is discovered in the incoming data stream.
|
||||||
|
Therefore it is called to announce when:
|
||||||
|
|
||||||
|
(i) a new token is discovered in the incoming stream: an index, ID, data or deleted data, a sync mark or a new byte of data.
|
||||||
|
(ii) the index hole passes; or
|
||||||
|
(iii) the queue of data to be written has been exhausted.
|
||||||
|
*/
|
||||||
|
virtual void posit_event(int type) = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Storage::Disk::Controller
|
||||||
|
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole);
|
||||||
|
virtual void process_index_hole();
|
||||||
|
virtual void process_write_completed();
|
||||||
|
|
||||||
|
// PLL input state
|
||||||
|
int bits_since_token_;
|
||||||
|
int shift_register_;
|
||||||
|
bool is_awaiting_marker_value_;
|
||||||
|
|
||||||
|
// input configuration
|
||||||
|
bool is_double_density_;
|
||||||
|
DataMode data_mode_;
|
||||||
|
|
||||||
|
// output
|
||||||
|
Token latest_token_;
|
||||||
|
|
||||||
|
// CRC generator
|
||||||
|
NumberTheory::CRC16 crc_generator_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* MFMDiskController_hpp */
|
Loading…
Reference in New Issue
Block a user