Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions DESIGN
Original file line number Diff line number Diff line change
Expand Up @@ -508,8 +508,10 @@ CONTENTS

The CPU checks if the IRQ line is high at the ench of each clock cycle. So if
IRQ is high at the end of T1, an interrupt was requested.
During T2, the cpu is already in the process of accepting and processing
the interrupt. This cycle is called the "buffer cycle"
After the CPU has received an interrupt request, it will set its internal IQ
flag and wait for the execution of the current instruction to finish. After
finishing, the IQ will be cleared and the Interrupt acknowledge sequence will
be started.

Interrupt acknowledge:

Expand All @@ -524,7 +526,7 @@ CONTENTS
GIO ─────────────────────────────🮤 DATA IN 🮥─
🮡──────────🮠

In the next cycle after the buffer cycle, the cpu will set the IRA line high
In the first cycle of the IRA sequence, the cpu will set the IRA line high
for two clock pulses. During the first pulse, nothing else happens.
During the second pulse the CPU reads a byte of whatever data currently is on
the I/O Bus.
Expand Down
41 changes: 35 additions & 6 deletions emu/mfdemu/impl/cpu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ void Cpu::iclck() {
this->gioWrite();
break;
case CpuState::INST_FETCH:
if(m_regFL.iq) {
this->newState(CpuState::HARD_INTERRUPT);
this->execHardInterrupt();
this->m_regFL.iq = false;
break;
}
this->fetchInst();
break;
case CpuState::INST_EXEC:
Expand All @@ -86,8 +92,9 @@ void Cpu::iclck() {
break;
}

// Let the current instruction finished before acknowledging the interrupt
if(irq && m_regFL.ie) {
newState(CpuState::HARD_INTERRUPT);
m_regFL.iq = true;
}
}

Expand Down Expand Up @@ -531,6 +538,7 @@ void Cpu::execReset() {
.nf = false,
.ie = false,
.rt = false,
.iq = false,
};

logDebug() << "\nreset, IP = " << std::hex << m_regIP << std::dec << "\n";
Expand Down Expand Up @@ -571,7 +579,7 @@ void Cpu::execInterrupt() {
logDebug() << "saving IP\n";
m_regSP -= 2;
m_addressBusAddress = m_regSP;
m_addressBusOutput = m_regIP;
m_addressBusOutput = NEXT_IP_VALUE;
m_stateStep = 1;
newState(CpuState::ABUS_WRITE);
break;
Expand Down Expand Up @@ -1063,11 +1071,32 @@ void Cpu::execInstINC() {
m_stateStep = EXEC_INST_STEP_INC_IP;
}

/** @todo: implement */
void Cpu::execInstINT() {}
void Cpu::execInstINT() {
if(!m_regFL.ie) {
finishState();
return;
}
m_regIID = m_operand1.value;
newState(CpuState::INTERRUPT);
}

/** @todo: implement */
void Cpu::execInstIRET() {}
void Cpu::execInstIRET() {
switch(m_stateStep) {
case 0:
m_regIID = 0;
m_addressBusAddress = m_regSP;
m_stateStep = 1;
newState(CpuState::ABUS_READ);
break;
case 1:
m_regSP += 2;
m_regIP = m_addressBusInput;
finishState();
break;
default:
shared::panic("invalid state: execInstIRET reached an invalid state step");
}
}

void Cpu::execInstJMP() {
constexpr u8 MOVE_TO_STASH = 16;
Expand Down
4 changes: 3 additions & 1 deletion emu/mfdemu/impl/cpu.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ struct CpuFlags {
bool nf;
bool ie;
bool rt;
/** @brief interrupt queued; not program-accessible. */
bool iq;
};

struct AddressingMode {
Expand All @@ -56,7 +58,7 @@ struct Operand {
class Cpu {
public:
enum class CpuState : u8 {
ABUS_READ,
ABUS_READ = 0,
ABUS_READ_INDIRECT,
ABUS_WRITE,
ABUS_WRITE_INDIRECT,
Expand Down
3 changes: 2 additions & 1 deletion test/mfdemu/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
add_executable(emu-test main.cpp
arithmetic.cpp
gio.cpp
interrupt.cpp
)
target_link_libraries(emu-test PRIVATE emu shared)

add_test(NAME emu-test COMMAND emu-test --ni)
add_test(NAME emu-test COMMAND emu-test --ni)
124 changes: 124 additions & 0 deletions test/mfdemu/interrupt.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#define DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
#include <doctest/doctest.h>

#include "test_cpu.hpp"

#include <mfdemu/impl/bus/aio_device.hpp>

using namespace ::mfdemu::impl;

namespace test::mfdemu {
static void test(CpuTest &cpu, u16 iid, u16 iv, u16 ret) {
// run until the cpu is about to enter the interrupt vector
while(cpu.m_regFL.ie) {
cpu.iclck();
}

CHECK_EQ(cpu.m_regIID, iid);
REQUIRE_EQ(cpu.m_regIP, iv);

// Test IRET
for(u8 i = 0; i < 16; i++) {
cpu.iclck();
}

CHECK_EQ(cpu.m_regIID, 0x0000);
REQUIRE_EQ(cpu.m_regIP, ret);
}

static void setMemory(std::vector<u8> &mem) {
mem[0x0000] = 0x10;
mem[0x0001] = 0x00;
mem[0x0002] = 0x00;
mem[0x0003] = 0x00;
mem[0x1234] = 0x0f;
mem[0x1235] = 0x00;
mem[0xFFFC] = 0x12;
mem[0xFFFD] = 0x34;
mem[0xFFFE] = 0x00;
mem[0xFFFF] = 0x00;
}

TEST_SUITE("Interrupt") {
TEST_CASE("hardware interrupt") {
std::vector<u8> main_memory(64UL * 1024);
setMemory(main_memory);
auto main_memory_dev = std::make_shared<AioDevice>(false, 64 * 1024);
main_memory_dev->setData(std::move(main_memory));
REQUIRE(main_memory_dev != nullptr);

CpuTest cpu;
cpu.connectAddressDevice(main_memory_dev);
cpu.reset = true;
cpu.iclck();
cpu.reset = false;

/* run the cpu for some time */
for(u8 i = 0; i < 16; i++) {
cpu.iclck();
}

cpu.m_regFL.ie = true;

/* IRQ */
cpu.irq = true;
cpu.iclck(); // initial cpu reaction time
cpu.irq = false;

/* wait for CPU to switch state & IRA T1.
* Might take a moment since its finishing off the current instruction.
* This will also include T1 of the IRA.
*/
while(cpu.m_regFL.iq) {
cpu.iclck();
}

REQUIRE(cpu.ira()); // IRA T1
cpu.iclck(); // IRA T2
REQUIRE(cpu.ira());
cpu.iclck(); // IRA done, internal interrupt processing

// TODO: Provide an IO device to properly test IRA sequence
const u16 fake_iid = 0x0012;
cpu.m_regIID = fake_iid;
test(cpu, fake_iid, 0x1234, 0x004);
}
TEST_CASE("software interrupt") {
std::vector<u8> main_memory(64UL * 1024);
setMemory(main_memory);
main_memory[0x0000] = 0x0e;
main_memory[0x0003] = 0x12;
auto main_memory_dev = std::make_shared<AioDevice>(false, 64 * 1024);
main_memory_dev->setData(std::move(main_memory));
REQUIRE(main_memory_dev != nullptr);

CpuTest cpu;
cpu.connectAddressDevice(main_memory_dev);

cpu.reset = true;
cpu.iclck();
cpu.reset = false;

/* last thing reset does is clear all flags. */
cpu.m_regFL.ie = true;
while(cpu.m_regFL.ie) {
cpu.iclck();
}

cpu.m_regFL.ie = true;

/* run until interrupts disabled. means we are processing one. */
while(cpu.m_regFL.ie) {
cpu.iclck();
}

for(u8 i = 0; i < 3; i++) {
cpu.iclck();
}

// TODO: Provide an IO device to properly test IRA sequence
const u16 fake_iid = 0x0012;
test(cpu, fake_iid, 0x1234, 0x004);
}
}
} // namespace test::mfdemu
3 changes: 3 additions & 0 deletions test/mfdemu/test_cpu.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ class CpuTest : public Cpu {
u16 &m_regSP = Cpu::m_regSP;
u16 &m_regIP = Cpu::m_regIP;
u16 &m_regAR = Cpu::m_regAR;
u16 &m_regIID = Cpu::m_regIID;
CpuFlags &m_regFL = Cpu::m_regFL;

u16 &m_ioBusInput = Cpu::m_ioBusInput;
u16 &m_ioBusOutput = Cpu::m_ioBusOutput;
u16 &m_ioBusAddress = Cpu::m_ioBusAddress;

std::stack<CpuState, std::vector<CpuState>> &m_state = Cpu::m_state;
};
} // namespace test::mfdemu