1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-09-26 11:55:15 +00:00

Switch to macro blocks of execution; flail around audio.

This commit is contained in:
Thomas Harte 2024-03-20 11:42:37 -04:00
parent 3a2d9c6082
commit 08673ff021

View File

@ -194,9 +194,56 @@ private:
static constexpr uint8_t MACK = 0b0011'0010; // Last data byte acknowledge.
static constexpr uint8_t SMAK = 0b0011'0011; // Last data byte acknowledge.
static constexpr uint8_t PRST = 0b0010'0001; // Does nothing.
};
struct Audio {
void set_next_end(uint32_t value) {
next_.end = value;
}
void set_next_start(uint32_t value) {
next_.start = value;
next_buffer_valid_ = true;
}
bool interrupt() const {
return !next_buffer_valid_;
}
void swap() {
current_.start = next_.start;
std::swap(current_.end, next_.end);
next_buffer_valid_ = false;
halted_ = false;
}
bool tick() {
if(halted_) {
return false;
}
current_.start += 16;
if(current_.start == current_.end) {
if(next_buffer_valid_) {
swap();
return true;
} else {
halted_ = true;
return false;
}
}
return false;
}
private:
bool next_buffer_valid_ = false;
bool halted_ = true; // This is a bit of a guess.
struct Buffer {
uint32_t start = 0, end = 0;
};
Buffer current_, next_;
};
struct Video {
void write(uint32_t value) {
const auto target = (value >> 24) & 0xfc;
@ -327,7 +374,7 @@ namespace InterruptRequests {
static constexpr int FIQ = 0x02;
};
struct Interrupts {
struct InputOutput {
int interrupt_mask() const {
return
((irq_a_.request() | irq_b_.request()) ? InterruptRequests::IRQ : 0) |
@ -577,7 +624,7 @@ struct Interrupts {
return true;
}
Interrupts() : keyboard_(serial_) {
InputOutput() : keyboard_(serial_) {
irq_a_.status = IRQA::SetAlways | IRQA::PowerOnReset;
irq_b_.status = 0x00;
fiq_.status = 0x80; // 'set always'.
@ -585,6 +632,27 @@ struct Interrupts {
i2c_.add_peripheral(&cmos_, 0xa0);
}
Audio &audio() {
return audio_;
}
bool tick_audio() {
if(audio_.tick()) {
if(audio_.interrupt()) {
irq_b_.apply(IRQB::SoundBufferPointerUsed);
} else {
irq_b_.clear(IRQB::SoundBufferPointerUsed);
}
return true;
}
return false;
}
void swap_audio() {
audio_.swap();
irq_b_.clear(IRQB::SoundBufferPointerUsed);
}
private:
// IRQA, IRQB and FIQ states.
struct Interrupt {
@ -620,6 +688,9 @@ private:
// The I2C bus.
I2C::Bus i2c_;
CMOSRAM cmos_;
// Audio.
Audio audio_;
};
/// Primarily models the MEMC.
@ -654,11 +725,47 @@ struct Memory {
}
switch(write_zones_[(address >> 21) & 31]) {
case Zone::DMAAndMEMC:
if((address & 0b1110'0000'0000'0000'0000) == 0b1110'0000'0000'0000'0000) {
// "The parameters are encoded into the processor address lines".
case Zone::DMAAndMEMC: {
const auto buffer_address = [](uint32_t source) -> uint32_t {
return (source & 0x1fffc0) << 2;
};
// The MEMC itself isn't on the data bus; all values below should be taken from `address`.
switch((address >> 17) & 0b111) {
case 0b000:
logger.error().append("TODO: DMA/MEMC Vinit = %04x", address & 0x1fffc0);
return true;
case 0b001:
logger.error().append("TODO: DMA/MEMC Vstart = %04x", address & 0x1fffc0);
return true;
case 0b010:
logger.error().append("TODO: DMA/MEMC Vend = %04x", address & 0x1fffc0);
return true;
case 0b011:
logger.error().append("TODO: DMA/MEMC Cinit = %04x", address & 0x1fffc0);
return true;
case 0b100:
logger.error().append("TODO: DMA/MEMC Sstart = %04x", address & 0x1fffc0);
ioc_.audio().set_next_start(buffer_address(address));
return true;
case 0b101:
logger.error().append("TODO: DMA/MEMC SendN = %04x", address & 0x1fffc0);
ioc_.audio().set_next_end(buffer_address(address));
return true;
case 0b110:
logger.error().append("TODO: DMA/MEMC Sptr");
ioc_.swap_audio();
return true;
case 0b111:
os_mode_ = address & (1 << 12);
sound_dma_enable_ = address & (1 << 11);
audio_dma_enable_ = address & (1 << 11);
video_dma_enable_ = address & (1 << 10);
switch((address >> 8) & 3) {
default:
@ -673,14 +780,11 @@ struct Memory {
low_rom_access_time_ = ROMAccessTime((address >> 4) & 3);
page_size_ = PageSize((address >> 2) & 3);
logger.info().append("MEMC Control: %08x -> OS:%d sound:%d video:%d refresh:%d high:%d low:%d size:%d", address, os_mode_, sound_dma_enable_, video_dma_enable_, dynamic_ram_refresh_, high_rom_access_time_, low_rom_access_time_, page_size_);
logger.info().append("MEMC Control: %08x -> OS:%d audio:%d video:%d refresh:%d high:%d low:%d size:%d", address, os_mode_, audio_dma_enable_, video_dma_enable_, dynamic_ram_refresh_, high_rom_access_time_, low_rom_access_time_, page_size_);
map_dirty_ = true;
return true;
} else {
logger.error().append("TODO: DMA/MEMC %08x to %08x", source, address);
}
break;
} break;
case Zone::LogicallyMappedRAM: {
const auto item = logical_ram<IntT, false>(address, mode);
@ -784,11 +888,17 @@ struct Memory {
return ioc_.tick_timers();
}
bool tick_audio() {
// TODO: does disabling audio DMA pause output, or leave it ticking and merely
// stop allowing it to use the bus?
return ioc_.tick_audio();
}
private:
bool has_moved_rom_ = false;
std::array<uint8_t, 4*1024*1024> ram_{};
std::array<uint8_t, 2*1024*1024> rom_;
Interrupts ioc_;
InputOutput ioc_;
Video vidc_;
IOCWriteDelegateT &ioc_write_delegate_;
@ -810,7 +920,7 @@ struct Memory {
// Control register values.
bool os_mode_ = false;
bool sound_dma_enable_ = false;
bool audio_dma_enable_ = false;
bool video_dma_enable_ = false; // "Unaffected" by reset, so here picked arbitrarily.
enum class DynamicRAMRefresh {
@ -1009,12 +1119,60 @@ class ConcreteMachine:
public MachineTypes::TimedMachine,
public MachineTypes::ScanProducer
{
private:
// TODO: pick a sensible clock rate; this is just code for '24 MIPS, please'.
static constexpr int ClockRate = 24'000'000;
// Timers tick at 2Mhz, so figure out the proper divider for that.
static constexpr int TimerTarget = ClockRate / 2'000'000;
int timer_divider_ = TimerTarget;
// Runs for 24 cycles, distributing calls to the various ticking subsystems
// 'correctly' (i.e. correctly for the approximation in use).
//
// The implementation of this is coupled to the ClockRate above, hence its
// appearance here.
void macro_tick() {
macro_counter_ -= 24;
// This is a 24-cycle window, so at 24Mhz macro_tick() is called at 1Mhz.
// Hence, required ticks are:
//
// * CPU: 24;
// * video: 12; [TODO]
// * timers: 2;
// * audio: 1. [TODO]
tick_cpu();
tick_cpu();
tick_cpu();
tick_cpu();
tick_cpu();
tick_cpu();
tick_cpu();
tick_cpu();
tick_cpu();
tick_cpu();
tick_cpu();
tick_cpu();
tick_timers();
tick_cpu();
tick_cpu();
tick_cpu();
tick_cpu();
tick_cpu();
tick_cpu();
tick_cpu();
tick_cpu();
tick_cpu();
tick_cpu();
tick_cpu();
tick_cpu();
tick_timers();
tick_audio();
}
int macro_counter_ = 0;
public:
ConcreteMachine(
@ -1053,17 +1211,17 @@ class ConcreteMachine:
// MARK: - TimedMachine.
void run_for(Cycles cycles) override {
macro_counter_ += cycles.as<int>();
while(macro_counter_ > 0) {
macro_tick();
}
}
void tick_cpu() {
static uint32_t last_pc = 0;
static bool log = false;
auto instructions = cycles.as<int>();
while(instructions) {
auto run_length = std::min(timer_divider_, instructions);
instructions -= run_length;
timer_divider_ -= run_length;
while(run_length--) {
uint32_t instruction;
pc_history[pc_history_ptr] = executor_.pc();
pc_history_ptr = (pc_history_ptr + 1) % pc_history.size();
@ -1132,20 +1290,6 @@ class ConcreteMachine:
// }
}
if(log) {
printf("");
}
if(!timer_divider_) {
timer_divider_ = TimerTarget;
if(executor_.bus.tick_timers()) {
test_interrupts();
}
}
}
}
void test_interrupts() {
using Exception = InstructionSet::ARM::Registers::Exception;
@ -1158,6 +1302,18 @@ class ConcreteMachine:
}
}
void tick_timers() {
if(executor_.bus.tick_timers()) {
test_interrupts();
}
}
void tick_audio() {
if(executor_.bus.tick_audio()) {
test_interrupts();
}
}
// MARK: - MediaTarget
bool insert_media(const Analyser::Static::Media &) override {
// int c = 0;