tmp68301: Add timers and fix interrupts (thanks grdians for the test cases)

This commit is contained in:
Olivier Galibert 2023-04-04 18:58:09 +02:00
parent ca6e220dbb
commit 407af69762
4 changed files with 168 additions and 36 deletions

View File

@ -28,6 +28,7 @@ m68000_device::m68000_device(const machine_config &mconfig, device_type type, co
m_cpu_space_config("cpu_space", ENDIANNESS_BIG, 16, 24, 0, address_map_constructor(FUNC(m68000_device::default_autovectors_map), this))
{
m_mmu = nullptr;
m_disable_interrupt_callback = false;
}
void m68000_device::abort_access(u32 reason)
@ -444,12 +445,14 @@ void m68000_device::start_interrupt_vector_lookup()
// flag for berr -> spurious
int level = m_next_state >> 24;
if(m_interrupt_mixer)
standard_irq_callback(level == 7 && m_nmi_uses_generic ? INPUT_LINE_NMI : level, m_pc);
else {
for(int i=0; i<3; i++)
if(level & (1<<i))
standard_irq_callback(i, m_pc);
if(!m_disable_interrupt_callback) {
if(m_interrupt_mixer)
standard_irq_callback(level == 7 && m_nmi_uses_generic ? INPUT_LINE_NMI : level, m_pc);
else {
for(int i=0; i<3; i++)
if(level & (1<<i))
standard_irq_callback(i, m_pc);
}
}
// Clear the nmi flag
@ -468,6 +471,7 @@ void m68000_device::end_interrupt_vector_lookup()
m68000_mcu_device::m68000_mcu_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock) :
m68000_device(mconfig, type, tag, owner, clock)
{
m_disable_interrupt_callback = true;
}
void m68000_mcu_device::execute_run()
@ -564,5 +568,4 @@ void m68000_mcu_device::set_current_interrupt_level(u32 level)
m_nmi_pending = true;
update_interrupt();
}

View File

@ -166,6 +166,7 @@ protected:
u32 m_int_level;
u32 m_int_next_state;
bool m_nmi_uses_generic;
bool m_disable_interrupt_callback;
u64 m_last_vpa_time;
// Current instruction state and substate

View File

@ -40,12 +40,22 @@ void tmp68301_device::internal_update(uint64_t current_time)
if(m_serial_rx_next_event[i] && current_time >= m_serial_rx_next_event[i])
serial_rx_update(i);
if(m_serial_tx_next_event[i] && m_serial_tx_next_event[i] < event_time)
if(m_serial_tx_next_event[i] && (!event_time || m_serial_tx_next_event[i] < event_time))
event_time = m_serial_tx_next_event[i];
if(m_serial_rx_next_event[i] && m_serial_rx_next_event[i] < event_time)
if(m_serial_rx_next_event[i] && (!event_time || m_serial_rx_next_event[i] < event_time))
event_time = m_serial_rx_next_event[i];
}
// Timers
for(int i=0; i != 3; i++) {
// Calling update changes the next event time
if(m_timer_next_event[i] && current_time >= m_timer_next_event[i])
timer_update(i);
if(m_timer_next_event[i] && (!event_time || m_timer_next_event[i] < event_time))
event_time = m_timer_next_event[i];
}
recompute_bcount(event_time);
}
@ -104,6 +114,8 @@ void tmp68301_device::device_start()
save_item(NAME(m_tmcr1));
save_item(NAME(m_tmcr2));
save_item(NAME(m_tctr));
save_item(NAME(m_timer_next_event));
save_item(NAME(m_timer_last_sync));
}
void tmp68301_device::device_reset()
@ -159,11 +171,14 @@ void tmp68301_device::device_reset()
m_serial_rx_line[i] = 0;
}
m_tcr[0] = 0x0052;
m_tcr[1] = m_tcr[2] = 0x0012;
m_tmcr1[0] = m_tmcr1[1] = m_tmcr1[2] = 0x0000;
m_tmcr2[0] = m_tmcr2[1] = m_tmcr2[2] = 0x0000;
m_tctr[0] = m_tctr[1] = m_tctr[2] = 0x0000;
for(int i=0; i != 3; i++) {
m_tcr[i] = i ? 0x0012 : 0x0052;
m_tmcr1[i] = 0x0000;
m_tmcr2[i] = 0x0000;
m_tctr[i] = 0x0000;
m_timer_next_event[i] = 0;
m_timer_last_sync[i] = 0;
}
}
void tmp68301_device::internal_map(address_map &map)
@ -579,6 +594,8 @@ u8 tmp68301_device::interrupt_callback()
{
auto [level, vector, slot] = interrupt_get_current();
logerror("interrupt callback ipr=%03x imr=%03x (%x, %02x, %d)\n", m_ipr, m_imr, level, vector, slot);
if(slot < 3)
standard_irq_callback(slot, m_pc);
if(vector != 0x1f) {
m_isr |= 1 << slot;
if(slot >= 3 || !(m_icr[slot] & 0x08))
@ -1114,7 +1131,7 @@ const char *const tmp68301_device::timer_source_names[3][4] = {
{ "internal", "external", "ch0", "ch1" }
};
const int tmp68301_device::timer_divider[16] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 256, 256, 256, 256, 256, 256, 256 };
const int tmp68301_device::timer_divider[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 8, 8, 8, 8, 8 };
u16 tmp68301_device::tcr0_r() { return m_tcr[0]; }
u16 tmp68301_device::tcr1_r() { return m_tcr[1]; }
@ -1124,9 +1141,9 @@ u16 tmp68301_device::tmcr11_r() { return m_tmcr1[1]; }
u16 tmp68301_device::tmcr12_r() { return m_tmcr2[1]; }
u16 tmp68301_device::tmcr21_r() { return m_tmcr1[2]; }
u16 tmp68301_device::tmcr22_r() { return m_tmcr2[2]; }
u16 tmp68301_device::tctr0_r() { return m_tctr[0]; }
u16 tmp68301_device::tctr1_r() { return m_tctr[1]; }
u16 tmp68301_device::tctr2_r() { return m_tctr[2]; }
u16 tmp68301_device::tctr0_r() { timer_sync(0); return m_tctr[0]; }
u16 tmp68301_device::tctr1_r() { timer_sync(1); return m_tctr[1]; }
u16 tmp68301_device::tctr2_r() { timer_sync(2); return m_tctr[2]; }
void tmp68301_device::tcr0_w(offs_t, u16 data, u16 mem_mask) { tcr_w(0, data, mem_mask); }
void tmp68301_device::tcr1_w(offs_t, u16 data, u16 mem_mask) { tcr_w(1, data, mem_mask); }
@ -1146,6 +1163,8 @@ void tmp68301_device::tctr2_w(offs_t, u16 data, u16 mem_mask) { tctr_w(2, data,
void tmp68301_device::tcr_w(int ch, u16 data, u16 mem_mask)
{
timer_sync(ch);
static const char *const count_mode[4] = { "normal", "?", "start", "wait" };
static const char *const max_mode[4] = { "?", "1", "2", "1&2" };
u16 old = m_tmcr1[ch];
@ -1159,48 +1178,140 @@ void tmp68301_device::tcr_w(int ch, u16 data, u16 mem_mask)
if(ch == 0)
logerror("timer %d clk=%s div=%d mode=%s repeat=%s intr=%s %s%s\n",
ch,
timer_source_names[ch][(m_tcr[ch] >> 14) & 3],
timer_divider[(m_tcr[ch] >> 10) & 15],
count_mode[(m_tcr[ch] >> 8) & 3],
m_tcr[ch] & 0x0080 ? "on" : "off",
m_tcr[ch] & 0x0004 ? "on" : "off",
m_tcr[ch] & 0x0002 ? "stopped" : "running",
m_tcr[ch] & 0x0001 ? "" : " clear");
timer_source_names[ch][(m_tcr[ch] >> TCR_CK) & 3],
1 << timer_divider[(m_tcr[ch] >> TCR_P) & 15],
count_mode[(m_tcr[ch] >> TCR_T) & 3],
m_tcr[ch] & TCR_N1 ? "on" : "off",
m_tcr[ch] & TCR_INT ? "on" : "off",
m_tcr[ch] & TCR_CS ? "stopped" : "running",
m_tcr[ch] & TCR_TS ? "" : " clear");
else
logerror("timer %d clk=%s div=%d mode=%s repeat=%s output=%s max=%s intr=%s %s%s\n",
ch,
timer_source_names[ch][(m_tcr[ch] >> 14) & 3],
timer_divider[(m_tcr[ch] >> 10) & 15],
count_mode[(m_tcr[ch] >> 8) & 3],
m_tcr[ch] & 0x0080 ? "on" : "off",
m_tcr[ch] & 0x0040 ? "invert" : "pulse",
max_mode[(m_tcr[ch] >> 4) & 3],
m_tcr[ch] & 0x0004 ? "on" : "off",
m_tcr[ch] & 0x0002 ? "stopped" : "running",
m_tcr[ch] & 0x0001 ? "" : " clear");
timer_source_names[ch][(m_tcr[ch] >> TCR_CK) & 3],
1 << timer_divider[(m_tcr[ch] >> TCR_P) & 15],
count_mode[(m_tcr[ch] >> TCR_T) & 3],
m_tcr[ch] & TCR_N1 ? "on" : "off",
m_tcr[ch] & TCR_RP ? "invert" : "pulse",
max_mode[(m_tcr[ch] >> TCR_MR) & 3],
m_tcr[ch] & TCR_INT ? "on" : "off",
m_tcr[ch] & TCR_CS ? "stopped" : "running",
m_tcr[ch] & TCR_TS ? "" : " clear");
timer_predict(ch);
}
void tmp68301_device::tmcr1_w(int ch, u16 data, u16 mem_mask)
{
timer_sync(ch);
u16 old = m_tmcr1[ch];
COMBINE_DATA(&m_tmcr1[ch]);
if(m_tmcr1[ch] == old)
return;
logerror("timer %d max 1 %04x\n", ch, m_tmcr1[ch]);
timer_predict(ch);
}
void tmp68301_device::tmcr2_w(int ch, u16 data, u16 mem_mask)
{
timer_sync(ch);
u16 old = m_tmcr2[ch];
COMBINE_DATA(&m_tmcr2[ch]);
if(m_tmcr2[ch] == old)
return;
logerror("timer %d max 2 %04x\n", ch, m_tmcr2[ch]);
timer_predict(ch);
}
void tmp68301_device::tctr_w(int ch, u16 data, u16 mem_mask)
{
timer_sync(ch);
logerror("timer %d counter reset\n", ch);
if(ch)
m_tctr[ch] = 0;
timer_predict(ch);
}
void tmp68301_device::timer_update(int ch)
{
timer_sync(ch);
timer_predict(ch);
}
void tmp68301_device::timer_sync(int ch)
{
if(!(m_tcr[ch] & TCR_TS)) {
m_tctr[ch] = 0;
return;
}
if(m_tcr[ch] & TCR_CS)
return;
u32 div = timer_divider[(m_tcr[ch] >> TCR_P) & 15];
u64 ctime = total_cycles();
// Don't fold the shifts, the computation would be incorrect
u32 ntctr = m_tctr[ch] + ((ctime >> div) - (m_timer_last_sync[ch] >> div));
u32 maxmode = (m_tcr[ch] >> TCR_MR) & 3;
if(maxmode == 1 || maxmode == 2) {
u32 max = (maxmode == 1) ? m_tmcr1[ch] : m_tmcr2[ch];
if(max == 0)
max = 0x10000;
if(m_tctr[ch] >= max) {
if(ntctr >= 0x10000)
ntctr -= 0x10000;
else
max = 0x10000;
}
if(ntctr >= max) {
if(m_tcr[ch] & TCR_INT)
interrupt_internal_trigger(4 + ch);
ntctr = ntctr % max;
}
}
m_tctr[ch] = ntctr;
m_timer_last_sync[ch] = ctime;
}
void tmp68301_device::timer_predict(int ch)
{
if(!(m_tcr[ch] & TCR_TS))
m_tctr[ch] = 0;
if((m_tcr[ch] & (TCR_INT|TCR_CS|TCR_TS)) != (TCR_INT|TCR_TS)) {
m_timer_next_event[ch] = 0;
return;
}
u32 maxmode = (m_tcr[ch] >> TCR_MR) & 3;
if(maxmode == 0) {
logerror("timer %d no max selected ?\n", ch);
return;
}
if(maxmode == 3) {
logerror("timer %d alternating max mode unsupported\n", ch);
return;
}
if((m_tcr[ch] & TCR_N1) == 0) {
// Need to add a flag to say "counter done" to make it work, reset on mode change.
logerror("timer %d single-shot mode unsupported\n");
return;
}
if(((m_tcr[ch] >> TCR_CK) & 3) != 0) {
logerror("timer %d source %s unsupported\n", ch, timer_source_names[ch][(m_tcr[ch] >> TCR_CK) & 3]);
return;
}
s32 delta = ((maxmode == 1) ? m_tmcr1[ch] : m_tmcr2[ch]) - m_tctr[ch];
if(delta <= 0)
delta += 0x10000;
u32 div = timer_divider[(m_tcr[ch] >> TCR_P) & 15];
u64 ctime = total_cycles();
m_timer_next_event[ch] = (((ctime >> div) + delta) << div);
recompute_bcount(ctime);
}

View File

@ -102,7 +102,7 @@ protected:
void ieir_w(u8 data);
u8 interrupt_callback();
void interrupt_update();
void interrupt_internal_trigger(int id);
void interrupt_internal_trigger(int vector);
std::tuple<u32, u8, u32> interrupt_get_current() const;
// Parallel interface
@ -244,12 +244,29 @@ protected:
void scr_w(u8 data);
// 16-bit timer
enum {
TCR_CK = 14,
TCR_P = 10,
TCR_T = 8,
TCR_N1 = 0x0080,
TCR_RP = 0x0040,
TCR_MR = 4,
TCR_INT = 0x0004,
TCR_CS = 0x0002,
TCR_TS = 0x0001
};
u64 m_timer_next_event[3], m_timer_last_sync[3];
u16 m_tcr[3], m_tmcr1[3], m_tmcr2[3], m_tctr[3];
static const int timer_source_id[3][2];
static const char *const timer_source_names[3][4];
static const int timer_divider[16];
void timer_update(int ch);
void timer_sync(int ch);
void timer_predict(int ch);
void tcr_w(int ch, u16 data, u16 mem_mask);
void tmcr1_w(int ch, u16 data, u16 mem_mask);
void tmcr2_w(int ch, u16 data, u16 mem_mask);