mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-25 03:32:01 +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_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 BEGIN_SECTION() switch(resume_point_) { default:
|
||||
#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 "../../Components/8255/i8255.hpp"
|
||||
#include "../../Components/AY38910/AY38910.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"
|
||||
|
||||
@ -427,6 +428,19 @@ struct KeyboardState {
|
||||
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.
|
||||
*/
|
||||
@ -545,6 +559,9 @@ class ConcreteMachine:
|
||||
// Pump the AY.
|
||||
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.
|
||||
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 2:
|
||||
// Perform ROM paging.
|
||||
read_pointers_[0] = (*cycle.value & 4) ? &ram_[0] : os_.data();
|
||||
read_pointers_[3] = (*cycle.value & 8) ? &ram_[49152] : basic_.data();
|
||||
read_pointers_[0] = (*cycle.value & 4) ? &ram_[0] : roms_[rom_model_].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.
|
||||
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
|
||||
if(!(address & 0x4000)) {
|
||||
switch((address >> 8) & 3) {
|
||||
@ -593,6 +618,16 @@ class ConcreteMachine:
|
||||
if(!(address & 0x800)) {
|
||||
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;
|
||||
case CPU::Z80::PartialMachineCycle::Input:
|
||||
// Default to nothing answering
|
||||
@ -611,6 +646,11 @@ class ConcreteMachine:
|
||||
if(!(address & 0x800)) {
|
||||
*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;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Interrupt:
|
||||
@ -666,11 +706,28 @@ class ConcreteMachine:
|
||||
|
||||
/// The ConfigurationTarget entry point; should configure this meachine as described by @c 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)
|
||||
read_pointers_[0] = os_.data();
|
||||
switch(target.amstradcpc.model) {
|
||||
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_[2] = &ram_[32768];
|
||||
read_pointers_[3] = basic_.data();
|
||||
read_pointers_[3] = roms_[upper_rom_].data();
|
||||
|
||||
write_pointers_[0] = &ram_[0];
|
||||
write_pointers_[1] = &ram_[16384];
|
||||
@ -681,16 +738,19 @@ class ConcreteMachine:
|
||||
if(!target.tapes.empty()) {
|
||||
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.
|
||||
void set_rom(ROMType type, std::vector<uint8_t> data) {
|
||||
// Keep only the two ROMs that are currently of interest.
|
||||
switch(type) {
|
||||
case ROMType::OS464: os_ = data; break;
|
||||
case ROMType::BASIC464: basic_ = data; break;
|
||||
default: break;
|
||||
}
|
||||
roms_[(int)type] = data;
|
||||
}
|
||||
|
||||
// See header; sets a key as either pressed or released.
|
||||
@ -715,6 +775,8 @@ class ConcreteMachine:
|
||||
i8255PortHandler i8255_port_handler_;
|
||||
Intel::i8255::i8255<i8255PortHandler> i8255_;
|
||||
|
||||
FDC fdc_;
|
||||
|
||||
InterruptTimer interrupt_timer_;
|
||||
Storage::Tape::BinaryTapePlayer tape_player_;
|
||||
|
||||
@ -722,8 +784,12 @@ class ConcreteMachine:
|
||||
HalfCycles crtc_counter_;
|
||||
HalfCycles half_cycles_since_ay_update_;
|
||||
|
||||
uint8_t ram_[65536];
|
||||
std::vector<uint8_t> os_, basic_;
|
||||
uint8_t ram_[128 * 1024];
|
||||
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 *write_pointers_[4];
|
||||
|
@ -18,9 +18,10 @@
|
||||
|
||||
namespace AmstradCPC {
|
||||
|
||||
enum ROMType: uint8_t {
|
||||
OS464, OS664, OS6128,
|
||||
BASIC464, BASIC664, BASIC6128,
|
||||
enum ROMType: int {
|
||||
OS464 = 0, BASIC464,
|
||||
OS664, BASIC664,
|
||||
OS6128, BASIC6128,
|
||||
AMSDOS
|
||||
};
|
||||
|
||||
|
@ -395,6 +395,8 @@
|
||||
4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EB61B587A5100552FC2 /* AllSuiteATests.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 */; };
|
||||
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 */; };
|
||||
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99081C8FBA6F0075DAFB /* TextureBuilder.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -1525,6 +1531,7 @@
|
||||
4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */,
|
||||
4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */,
|
||||
4B30512B1D989E2200B4FED8 /* Drive.cpp */,
|
||||
4BBC95201F36B16C008F4C34 /* MFMDiskController.cpp */,
|
||||
4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */,
|
||||
4B121F961E060CF000BFDA12 /* PCMSegment.cpp */,
|
||||
4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */,
|
||||
@ -1532,6 +1539,7 @@
|
||||
4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */,
|
||||
4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */,
|
||||
4B30512C1D989E2200B4FED8 /* Drive.hpp */,
|
||||
4BBC95211F36B16C008F4C34 /* MFMDiskController.hpp */,
|
||||
4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */,
|
||||
4B121F971E060CF000BFDA12 /* PCMSegment.hpp */,
|
||||
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */,
|
||||
@ -1982,6 +1990,15 @@
|
||||
path = ../../Processors;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BBC951F1F368D87008F4C34 /* 8272 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BBC951C1F368D83008F4C34 /* i8272.cpp */,
|
||||
4BBC951D1F368D83008F4C34 /* i8272.hpp */,
|
||||
);
|
||||
name = 8272;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BBF49B41ED2881600AB3669 /* FUSE */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -2048,6 +2065,7 @@
|
||||
4B4A762D1DB1A35C007AAE2E /* AY38910 */,
|
||||
4BE845221F2FF7F400A5EA22 /* 6845 */,
|
||||
4BD9137C1F3115AC009BCF85 /* 8255 */,
|
||||
4BBC951F1F368D87008F4C34 /* 8272 */,
|
||||
);
|
||||
name = Components;
|
||||
path = ../../Components;
|
||||
@ -2732,12 +2750,14 @@
|
||||
4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */,
|
||||
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
|
||||
4B3940E71DA83C8300427841 /* AsyncTaskQueue.cpp in Sources */,
|
||||
4BBC95221F36B16C008F4C34 /* MFMDiskController.cpp in Sources */,
|
||||
4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */,
|
||||
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */,
|
||||
4B8378DF1F33675F005CA9E4 /* CharacterMapper.cpp in Sources */,
|
||||
4B8FE2291DA1EDDF0090D3CE /* ElectronOptionsPanel.swift in Sources */,
|
||||
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */,
|
||||
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */,
|
||||
4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */,
|
||||
4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */,
|
||||
4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */,
|
||||
4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */,
|
||||
|
@ -19,5 +19,8 @@ void StaticAnalyser::AmstradCPC::AddTargets(
|
||||
target.disks = disks;
|
||||
target.tapes = tapes;
|
||||
target.cartridges = cartridges;
|
||||
|
||||
target.amstradcpc.model = target.disks.empty() ? AmstradCPCModel::CPC464 : AmstradCPCModel::CPC664;
|
||||
|
||||
destination.push_back(target);
|
||||
}
|
||||
|
@ -46,6 +46,12 @@ enum class ZX8081MemoryModel {
|
||||
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,
|
||||
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;
|
||||
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 {
|
||||
struct {
|
||||
bool has_adfs;
|
||||
@ -87,6 +95,10 @@ struct Target {
|
||||
ZX8081MemoryModel memory_model;
|
||||
bool isZX81;
|
||||
} zx8081;
|
||||
|
||||
struct {
|
||||
AmstradCPCModel model;
|
||||
} amstradcpc;
|
||||
};
|
||||
|
||||
std::string loadingCommand;
|
||||
|
@ -11,8 +11,8 @@
|
||||
|
||||
using namespace Storage::Disk;
|
||||
|
||||
Controller::Controller(int clock_rate, int clock_rate_multiplier, int revolutions_per_minute) :
|
||||
clock_rate_(clock_rate * clock_rate_multiplier),
|
||||
Controller::Controller(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) :
|
||||
clock_rate_(clock_rate.as_int() * clock_rate_multiplier),
|
||||
clock_rate_multiplier_(clock_rate_multiplier),
|
||||
rotational_multiplier_(60, revolutions_per_minute),
|
||||
|
||||
@ -21,7 +21,7 @@ Controller::Controller(int clock_rate, int clock_rate_multiplier, int revolution
|
||||
|
||||
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
|
||||
Time one(1);
|
||||
set_expected_bit_length(one);
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "PCMSegment.hpp"
|
||||
#include "PCMPatchedTrack.hpp"
|
||||
#include "../TimedEventLoop.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace Storage {
|
||||
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,
|
||||
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.
|
||||
|
@ -16,6 +16,7 @@ CPCDSK::CPCDSK(const char *file_name) :
|
||||
Storage::FileHolder(file_name), is_extended_(false) {
|
||||
if(!check_signature("MV - CPC", 8)) {
|
||||
is_extended_ = true;
|
||||
fseek(file_, 0, SEEK_SET);
|
||||
if(!check_signature("EXTENDED", 8))
|
||||
throw ErrorNotCPCDSK;
|
||||
}
|
||||
@ -26,6 +27,8 @@ CPCDSK::CPCDSK(const char *file_name) :
|
||||
head_count_ = (unsigned int)fgetc(file_);
|
||||
|
||||
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++) {
|
||||
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;
|
||||
while(t < chronological_track && t < track_sizes_.size()) {
|
||||
file_offset += track_sizes_[t];
|
||||
t++;
|
||||
}
|
||||
} else {
|
||||
// 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 side;
|
||||
uint8_t sector;
|
||||
size_t length;
|
||||
uint8_t length;
|
||||
uint8_t status1;
|
||||
uint8_t status2;
|
||||
size_t actual_length;
|
||||
};
|
||||
std::vector<SectorInfo> sector_infos;
|
||||
while(number_of_sectors--) {
|
||||
SectorInfo new_sector;
|
||||
SectorInfo sector_info;
|
||||
|
||||
new_sector.track = (uint8_t)fgetc(file_);
|
||||
new_sector.side = (uint8_t)fgetc(file_);
|
||||
new_sector.sector = (uint8_t)fgetc(file_);
|
||||
new_sector.length = (size_t)(128 << fgetc(file_));
|
||||
if(new_sector.length == 0x2000) new_sector.length = 0x1800;
|
||||
new_sector.status1 = (uint8_t)fgetc(file_);
|
||||
new_sector.status2 = (uint8_t)fgetc(file_);
|
||||
fseek(file_, 2, SEEK_CUR);
|
||||
sector_info.track = (uint8_t)fgetc(file_);
|
||||
sector_info.side = (uint8_t)fgetc(file_);
|
||||
sector_info.sector = (uint8_t)fgetc(file_);
|
||||
sector_info.length = (uint8_t)fgetc(file_);
|
||||
sector_info.status1 = (uint8_t)fgetc(file_);
|
||||
sector_info.status2 = (uint8_t)fgetc(file_);
|
||||
sector_info.actual_length = fgetc16le();
|
||||
|
||||
sector_infos.push_back(new_sector);
|
||||
sector_infos.push_back(sector_info);
|
||||
}
|
||||
|
||||
// Get the sectors.
|
||||
fseek(file_, file_offset + 0x100, SEEK_SET);
|
||||
if(is_extended_) {
|
||||
// TODO: everything about extended disk images
|
||||
} else {
|
||||
std::vector<Storage::Encodings::MFM::Sector> sectors;
|
||||
for(auto §or_info : sector_infos) {
|
||||
Storage::Encodings::MFM::Sector new_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_);
|
||||
std::vector<Storage::Encodings::MFM::Sector> sectors;
|
||||
for(auto §or_info : sector_infos) {
|
||||
Storage::Encodings::MFM::Sector new_sector;
|
||||
new_sector.track = sector_info.track;
|
||||
new_sector.side = sector_info.side;
|
||||
new_sector.sector = sector_info.sector;
|
||||
|
||||
// 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
|
||||
if(sectors.size()) return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors);
|
||||
sectors.push_back(std::move(new_sector));
|
||||
}
|
||||
|
||||
// TODO: supply gay 3 length and filler byte
|
||||
if(sectors.size()) return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors);
|
||||
|
||||
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