-6840ptm: Fixed counter duration and fixed output behavior in single-shot and dual-8-bit modes. [Ryan Holtz] (#10111)

This commit is contained in:
MooglyGuy 2022-07-21 01:04:36 +02:00 committed by GitHub
parent ad63028466
commit 3eb3713e87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 253 additions and 316 deletions

View File

@ -94,9 +94,9 @@ void ptm6840_device::device_start()
m_out_cb.resolve_all_safe();
m_irq_cb.resolve_safe();
m_timer[0] = timer_alloc(FUNC(ptm6840_device::timeout), this);
m_timer[1] = timer_alloc(FUNC(ptm6840_device::timeout), this);
m_timer[2] = timer_alloc(FUNC(ptm6840_device::timeout), this);
m_timer[0] = timer_alloc(FUNC(ptm6840_device::state_changed), this);
m_timer[1] = timer_alloc(FUNC(ptm6840_device::state_changed), this);
m_timer[2] = timer_alloc(FUNC(ptm6840_device::state_changed), this);
for (auto & elem : m_timer)
{
@ -117,13 +117,12 @@ void ptm6840_device::device_start()
save_item(NAME(m_gate));
save_item(NAME(m_clk));
save_item(NAME(m_mode));
save_item(NAME(m_fired));
save_item(NAME(m_single_fired));
save_item(NAME(m_enabled));
save_item(NAME(m_external_clock));
save_item(NAME(m_counter));
save_item(NAME(m_disable_time));
save_item(NAME(m_latch));
save_item(NAME(m_hightime));
}
@ -141,20 +140,17 @@ void ptm6840_device::device_reset()
m_status_read_since_int = 0;
m_irq = 0;
m_t3_scaler = 0;
m_hightime[0] = false;
m_hightime[1] = false;
m_hightime[2] = false;
for (int i = 0; i < 3; i++)
{
m_counter[i] = 0xffff;
m_latch[i] = 0xffff;
m_counter[i] = 0xffff;
m_latch[i] = 0xffff;
m_disable_time[i] = attotime::never;
m_output[i] = false;
m_clk[i] = false;
m_fired[i] = 0;
m_enabled[i] = 0;
m_mode[i] = 0;
m_output[i] = false;
m_clk[i] = false;
m_single_fired[i] = false;
m_enabled[i] = false;
m_mode[i] = 0;
}
}
@ -166,106 +162,57 @@ void ptm6840_device::device_resolve_objects()
//-------------------------------------------------
// subtract_from_counter - Subtract from Counter
// deduct_from_counter - count back by one step
//-------------------------------------------------
void ptm6840_device::subtract_from_counter(int counter, int count)
void ptm6840_device::deduct_from_counter(int idx)
{
// Determine the clock frequency for this timer
double clk = m_control_reg[counter] & INTERNAL_CLK_EN ? static_cast<double>(clock()) : m_external_clock[counter];
// Dual-byte mode
if (m_control_reg[counter] & COUNT_MODE_8BIT)
if (m_control_reg[idx] & COUNT_MODE_8BIT)
{
int lsb = m_counter[counter] & 0xff;
int msb = m_counter[counter] >> 8;
// Dual-byte mode
uint16_t msb = m_counter[idx] >> 8;
uint16_t lsb = m_counter[idx] & 0xff;
// Count the clocks
lsb -= count;
lsb--;
// Loop while we're less than zero
while (lsb < 0)
bool timed_out = false;
if (lsb == 0xffff)
{
// Borrow from the MSB
lsb += (m_latch[counter] & 0xff) + 1;
lsb = (m_latch[idx] & 0xff) + 1;
msb--;
// If MSB goes less than zero, we've expired
if ((msb == 0 && !m_hightime[counter]) || (msb < 0 && m_hightime[counter]))
if (msb < 0)
{
timeout(counter);
msb = (m_latch[counter] >> 8) + 1;
// If MSB is less than zero, we've timed out, no need to manually reload
timed_out = true;
state_changed(idx);
}
else if (msb == 0)
{
// If MSB is at zero, our output state potentially needs to change, also no need to manually reload
msb = (m_latch[idx] >> 8) + 1;
state_changed(idx);
}
}
// Store the result
m_counter[counter] = (msb << 8) | lsb;
}
// Word mode
else
{
int word = m_counter[counter];
// Count the clocks
word -= count;
// if we're less than zero
if (word < 0)
// Store the result if we haven't timed out (which already reloads the counter from the latches)
if (!timed_out)
{
// We've expired
timeout(counter);
}
else
{
// Store the result
m_counter[counter] = word;
}
}
if (m_enabled[counter])
{
int clks = m_counter[counter];
if (m_control_reg[counter] & COUNT_MODE_8BIT)
{
/* In dual 8 bit mode, let the counter fire when MSB == 0 */
m_hightime[counter] = !(clks & 0xff00);
clks &= 0xff00;
}
attotime duration = attotime::from_hz(clk) * clks;
if (counter == 2)
{
duration *= m_t3_divisor;
}
duration += attotime::from_ticks(2, clock());
m_timer[counter]->adjust(duration, counter);
}
}
//-------------------------------------------------
// tick
//-------------------------------------------------
void ptm6840_device::tick(int counter, int count)
{
if (counter == 2)
{
m_t3_scaler += count;
if ( m_t3_scaler > m_t3_divisor - 1)
{
subtract_from_counter(counter, 1);
m_t3_scaler = 0;
m_counter[idx] = (msb << 8) + lsb;
}
}
else
{
subtract_from_counter(counter, count);
// Word mode
m_counter[idx]--;
const bool one_shot_mode = m_mode[idx] == 4 || m_mode[idx] == 6;
// If we've ticked once in one-shot-mode, or we've expired, our state needs to change
if ((one_shot_mode && !m_output[idx]) || m_counter[idx] == 0xffff)
{
state_changed(idx);
}
}
}
@ -309,77 +256,70 @@ void ptm6840_device::update_interrupts()
// compute_counter - Compute Counter
//-------------------------------------------------
uint16_t ptm6840_device::compute_counter(int counter) const
int ptm6840_device::compute_counter(int idx) const
{
uint32_t clk;
// If there's no timer, return the count
if (!m_enabled[counter])
// If the timer is disabled, return the raw counter value
if (!m_enabled[idx])
{
LOGMASKED(LOG_COUNTERS, "Timer #%d read counter: %04x\n", counter + 1, m_counter[counter]);
return m_counter[counter];
LOGMASKED(LOG_COUNTERS, "Timer #%d read counter: %04x\n", idx + 1, m_counter[idx]);
return m_counter[idx];
}
else if (m_control_reg[0] & RESET_TIMERS)
{
// If we're held in reset, return the latch value, as it's what is meaningful
return m_latch[counter];
// If we're held in reset, return either the latch value for 16-bit mode, or the computed count for dual-8-bit
if (m_control_reg[idx] & COUNT_MODE_8BIT)
{
const uint16_t latch_lsb = m_latch[idx] & 0xff;
const uint16_t latch_msb = m_latch[idx] >> 8;
return latch_msb * (latch_lsb + 1);
}
return m_latch[idx];
}
// determine the clock frequency for this timer
if (m_control_reg[counter] & INTERNAL_CLK_EN)
if (m_control_reg[idx] & INTERNAL_CLK_EN)
{
clk = clock();
}
else
{
clk = m_external_clock[counter];
clk = m_external_clock[idx];
}
if (counter == 2)
if (idx == 2)
{
clk /= m_t3_divisor;
}
LOGMASKED(LOG_COUNTERS, "Timer #%d %s clock freq %d\n", counter + 1, (m_control_reg[counter] & INTERNAL_CLK_EN) ? "internal" : "external", clk);
LOGMASKED(LOG_COUNTERS, "Timer #%d %s clock freq %d\n", idx + 1, (m_control_reg[idx] & INTERNAL_CLK_EN) ? "internal" : "external", clk);
// See how many are left
attotime remaining_time = m_timer[counter]->remaining();
attotime remaining_time = m_timer[idx]->remaining();
if (remaining_time.is_never())
{
if (m_disable_time[counter].is_never())
if (m_disable_time[idx].is_never())
{
return m_counter[counter];
return m_counter[idx];
}
remaining_time = m_disable_time[counter];
}
attotime e_time = attotime::from_ticks(2, clock());
int remaining = 0;
if (remaining_time >= e_time)
{
remaining_time -= e_time;
remaining = remaining_time.as_ticks(clk);
remaining_time = m_disable_time[idx];
}
int remaining = remaining_time.as_ticks(clk);
// Adjust the count for dual byte mode
if (m_control_reg[counter] & COUNT_MODE_8BIT)
{
int divisor = (m_counter[counter] & 0xff) + 1;
int msb = remaining / divisor;
int lsb = remaining % divisor;
remaining = (msb << 8) | lsb;
}
LOGMASKED(LOG_COUNTERS, "Timer #%d read counter: %04x\n", counter + 1, remaining);
LOGMASKED(LOG_COUNTERS, "Timer #%d read counter: %04x\n", idx + 1, remaining);
return remaining;
}
//-------------------------------------------------
// reload_count - Reload Counter
// reload_counter
//-------------------------------------------------
void ptm6840_device::reload_count(int idx)
void ptm6840_device::reload_counter(int idx)
{
const bool one_shot_mode = m_mode[idx] == 4 || m_mode[idx] == 6;
// Copy the latched value in
m_counter[idx] = m_latch[idx];
@ -397,22 +337,30 @@ void ptm6840_device::reload_count(int idx)
}
// Determine the number of clock periods before we expire
int count = m_counter[idx];
int count = m_counter[idx] + 1;
if (m_control_reg[idx] & COUNT_MODE_8BIT)
{
if (m_hightime[idx])
count = 0xff;
const uint16_t latch_lsb = m_latch[idx] & 0xff;
const uint16_t latch_msb = m_latch[idx] >> 8;
if (!m_output[idx])
{
count = (latch_lsb + 1) * latch_msb;
}
else
count = ((count >> 8) + 1) * ((count & 0xff) + 1);
{
count = latch_lsb + 1;
}
}
m_fired[idx] = 0;
const bool one_shot_mode = m_mode[idx] == 4 || m_mode[idx] == 6;
if (one_shot_mode)
else if (one_shot_mode)
{
m_output[idx] = false;
m_out_cb[idx](m_output[idx]);
if (!m_output[idx])
{
count = 1;
}
else
{
count = m_counter[idx];
}
}
// Set the timer
@ -420,11 +368,12 @@ void ptm6840_device::reload_count(int idx)
if (clk == 0.0)
{
m_enabled[idx] = 0;
m_enabled[idx] = false;
m_timer[idx]->adjust(attotime::never);
}
else
{
m_enabled[idx] = true;
attotime duration = attotime::from_hz(clk) * count;
if (idx == 2)
@ -432,12 +381,8 @@ void ptm6840_device::reload_count(int idx)
duration *= m_t3_divisor;
}
duration += attotime::from_ticks(2, clock());
LOGMASKED(LOG_COUNTERS, "Timer #%d init_timer: duration = %f\n", idx + 1, duration.as_double());
m_enabled[idx] = 1;
const bool one_shot_mode = m_mode[idx] == 4 || m_mode[idx] == 6;
const bool gated = (!one_shot_mode && m_gate[idx]) || (m_control_reg[0] & RESET_TIMERS);
if (gated)
@ -558,9 +503,8 @@ void ptm6840_device::write(offs_t offset, uint8_t data)
for (int i = 0; i < 3; i++)
{
m_timer[i]->adjust(attotime::never);
m_enabled[i] = 0;
m_hightime[i] = false;
reload_count(i);
m_enabled[i] = false;
reload_counter(i);
m_output[i] = false;
m_out_cb[i](m_output[i]);
}
@ -570,8 +514,8 @@ void ptm6840_device::write(offs_t offset, uint8_t data)
{
for (int i = 0; i < 3; i++)
{
m_hightime[i] = false;
reload_count(i);
m_single_fired[i] = false;
reload_counter(i);
if (!m_disable_time[i].is_never() && m_timer[i]->remaining().is_never() && ((m_control_reg[i] & INTERNAL_CLK_EN) || m_external_clock[i] != 0.0))
{
m_timer[i]->adjust(m_disable_time[i], i);
@ -587,56 +531,7 @@ void ptm6840_device::write(offs_t offset, uint8_t data)
// Changing the clock source? (e.g. Zwackery)
if (diffs & INTERNAL_CLK_EN)
{
m_hightime[idx] = false;
if (!(m_control_reg[0] & RESET_TIMERS))
{
double divisor = idx == 2 ? m_t3_divisor : 1.0;
double clk = (m_control_reg[idx] & INTERNAL_CLK_EN ? static_cast<double>(clock()) : m_external_clock[idx]) / divisor;
if (clk == 0.0)
{
// Temporarily restore the old control value to retrieve the current counter value
m_control_reg[idx] ^= diffs;
m_counter[idx] = compute_counter(idx);
m_control_reg[idx] = data;
m_enabled[idx] = 0;
m_timer[idx]->adjust(attotime::never);
}
else
{
attotime duration = attotime::from_hz(clk);
u16 updated_count = m_counter[idx];
if (m_control_reg[idx] & INTERNAL_CLK_EN && m_external_clock[idx] == 0)
{
duration *= updated_count;
}
else
{
// Temporarily restore the old control value to retrieve the current counter value
m_control_reg[idx] ^= diffs;
updated_count = compute_counter(idx);
duration *= updated_count;
m_control_reg[idx] = data;
}
duration += attotime::from_ticks(2, clock());
m_enabled[idx] = 1;
const bool one_shot_mode = m_mode[idx] == 4 || m_mode[idx] == 6;
const bool gated = !one_shot_mode && m_gate[idx];
if (gated)
{
m_timer[idx]->adjust(attotime::never);
m_disable_time[idx] = duration;
}
else
{
m_timer[idx]->adjust(duration, idx);
}
}
}
update_expiration_for_clock_source(idx, !(m_control_reg[idx] & INTERNAL_CLK_EN), m_external_clock[idx]);
}
break;
}
@ -664,8 +559,7 @@ void ptm6840_device::write(offs_t offset, uint8_t data)
// Reload the count if in an appropriate mode
if (!(m_control_reg[idx] & 0x10) || (m_control_reg[0] & RESET_TIMERS))
{
m_hightime[idx] = false;
reload_count(idx);
reload_counter(idx);
}
LOGMASKED(LOG_COUNTERS, "%s: Counter #%d latch = %04X\n", machine().describe_context(), idx + 1, m_latch[idx]);
@ -676,70 +570,69 @@ void ptm6840_device::write(offs_t offset, uint8_t data)
//-------------------------------------------------
// timeout - Called if timer is mature
// state_changed - called if timer output state
// changes (masked or not)
//-------------------------------------------------
TIMER_CALLBACK_MEMBER(ptm6840_device::timeout)
TIMER_CALLBACK_MEMBER(ptm6840_device::state_changed)
{
LOGMASKED(LOG_TIMEOUTS, "**ptm6840 t%d timeout**\n", param + 1);
// Set the interrupt flag
m_status_reg |= (1 << param);
m_status_read_since_int &= ~(1 << param);
update_interrupts();
if (m_control_reg[param] & COUNT_OUT_EN)
{
switch (m_mode[param])
{
case 0:
case 2:
if (m_control_reg[param] & COUNT_MODE_8BIT)
{
m_hightime[param] = !m_hightime[param];
m_output[param] = m_hightime[param];
m_out_cb[param](m_output[param]);
}
else
{
m_output[param] = !m_output[param];
m_out_cb[param](m_output[param]);
}
LOGMASKED(LOG_TIMEOUTS, "%6.6f: **ptm6840 t%d output %d **\n", machine().time().as_double(), param + 1, m_output[param]);
break;
case 4:
case 6:
if (!m_fired[param])
{
m_output[param] = true;
LOGMASKED(LOG_TIMEOUTS, "**ptm6840 t%d output %d **\n", param + 1, m_output[param]);
m_out_cb[param](m_output[param]);
// No changes in output until reinit
m_fired[param] = 1;
m_status_reg |= (1 << param);
m_status_read_since_int &= ~(1 << param);
update_interrupts();
}
break;
}
}
else
{
m_out_cb[param](0);
}
m_enabled[param] = 0;
LOGMASKED(LOG_TIMEOUTS, "**ptm6840 t%d state_changed**\n", param + 1);
// Set the interrupt flag if at the end of a full cycle
const bool one_shot_mode = m_mode[param] == 4 || m_mode[param] == 6;
if (!one_shot_mode)
const bool dual_8bit = m_control_reg[param] & COUNT_MODE_8BIT;
const bool end_of_cycle = (!dual_8bit && !one_shot_mode) || m_output[param];
if (end_of_cycle)
{
reload_count(param);
m_status_reg |= (1 << param);
m_status_read_since_int &= ~(1 << param);
update_interrupts();
}
const bool enable_output = m_control_reg[param] & COUNT_OUT_EN;
switch (m_mode[param])
{
case 0:
case 2:
m_output[param] = !m_output[param];
if (enable_output)
{
m_out_cb[param](m_output[param]);
}
else
{
m_out_cb[param](0);
}
LOGMASKED(LOG_TIMEOUTS, "%6.6f: **ptm6840 t%d output %d **\n", machine().time().as_double(), param + 1, m_output[param]);
break;
case 4:
case 6:
m_output[param] = !m_output[param];
LOGMASKED(LOG_TIMEOUTS, "**ptm6840 t%d output %d **\n", param + 1, m_output[param]);
if (!m_single_fired[param])
{
if (enable_output)
{
m_out_cb[param](m_output[param]);
}
if (!m_output[param])
{
// Don't allow output to change until reinitialization
m_single_fired[param] = true;
}
}
else
{
m_out_cb[param](0);
}
break;
}
m_enabled[param] = false;
reload_counter(param);
}
@ -754,8 +647,9 @@ void ptm6840_device::set_gate(int idx, int state)
{
if (!(m_control_reg[0] & RESET_TIMERS))
{
m_hightime[idx] = false;
reload_count(idx);
m_single_fired[idx] = false;
m_output[idx] = false;
reload_counter(idx);
}
if (!m_disable_time[idx].is_never() && ((m_control_reg[idx] & INTERNAL_CLK_EN) || m_external_clock[idx] != 0.0))
{
@ -799,11 +693,93 @@ void ptm6840_device::set_clock(int idx, int state)
// Don't allow ticking if timers are held in reset, internally-clocked, or gated
if (use_external_clk && timer_running && !gated)
{
tick(idx, 1);
if (idx == 2)
{
m_t3_scaler++;
if (m_t3_scaler >= m_t3_divisor)
{
deduct_from_counter(idx);
m_t3_scaler = 0;
}
}
else
{
deduct_from_counter(idx);
}
}
}
//-------------------------------------------------
// update_expiration_for_clock_source
//-------------------------------------------------
void ptm6840_device::update_expiration_for_clock_source(int idx, bool changed_to_external, double new_external_clock)
{
if (!(m_control_reg[0] & RESET_TIMERS))
{
double divisor = idx == 2 ? m_t3_divisor : 1.0;
double clk = (m_control_reg[idx] & INTERNAL_CLK_EN ? static_cast<double>(clock()) : new_external_clock) / divisor;
// First, figure out how much time was remaining on the counter
if (changed_to_external)
{
m_control_reg[idx] |= INTERNAL_CLK_EN;
}
int updated_counter = compute_counter(idx);
if (changed_to_external)
{
m_control_reg[idx] &= ~INTERNAL_CLK_EN;
}
if (clk == 0.0)
{
// If we're externally clocked with no fixed incoming clock
// Adjust for dual-byte mode if we're not in the last countdown
if ((m_control_reg[idx] & COUNT_MODE_8BIT) && !m_output[idx])
{
const uint16_t latch_lsb = m_latch[idx] & 0xff;
const uint16_t latch_msb = m_latch[idx] >> 8;
const uint8_t count_lsb = updated_counter % latch_msb;
const uint8_t count_msb = (updated_counter - count_lsb) / (latch_lsb + 1);
m_counter[idx] = (count_msb << 8) | count_lsb;
}
else
{
m_counter[idx] = updated_counter;
}
m_enabled[idx] = false;
m_timer[idx]->adjust(attotime::never);
return;
}
else
{
// If we're externally clocked with a valid incoming clock OR we're internally-clocked
attotime duration = attotime::from_hz(clk) * updated_counter;
m_enabled[idx] = true;
const bool one_shot_mode = m_mode[idx] == 4 || m_mode[idx] == 6;
const bool gated = !one_shot_mode && m_gate[idx];
if (gated)
{
m_timer[idx]->adjust(attotime::never);
m_disable_time[idx] = duration;
}
else
{
m_timer[idx]->adjust(duration, idx);
}
}
}
}
//-------------------------------------------------
// set_ext_clock - set external clock frequency
//-------------------------------------------------
@ -813,46 +789,9 @@ void ptm6840_device::set_ext_clock(int idx, double clk)
if (m_external_clock[idx] == clk)
return;
m_counter[idx] = compute_counter(idx);
if (!(m_control_reg[idx] & INTERNAL_CLK_EN) && clk == 0.0)
if (!(m_control_reg[idx] & INTERNAL_CLK_EN))
{
m_enabled[idx] = 0;
m_timer[idx]->adjust(attotime::never);
}
else
{
double new_clk = (m_control_reg[idx] & INTERNAL_CLK_EN) ? (double)clock() : clk;
// Determine the number of clock periods before we expire
int count = m_counter[idx];
if (m_control_reg[idx] & COUNT_MODE_8BIT)
{
count = ((count >> 8) + 1) * ((count & 0xff) + 1);
}
attotime duration = attotime::from_hz(new_clk) * count;
if (idx == 2)
{
duration *= m_t3_divisor;
}
duration += attotime::from_ticks(2, clock());
m_enabled[idx] = 1;
const bool one_shot_mode = m_mode[idx] == 4 || m_mode[idx] == 6;
const bool gated = (!one_shot_mode && m_gate[idx]) || (m_control_reg[0] & RESET_TIMERS);
if (gated)
{
m_disable_time[idx] = duration;
m_timer[idx]->adjust(attotime::never);
}
else
{
m_timer[idx]->adjust(duration, idx);
}
update_expiration_for_clock_source(idx, false, clk);
}
m_external_clock[idx] = clk;

View File

@ -33,11 +33,11 @@ public:
auto o3_callback() { return m_out_cb[2].bind(); }
auto irq_callback() { return m_irq_cb.bind(); }
int status(int clock) const { return m_enabled[clock]; } // get whether timer is enabled
int irq_state() const { return m_irq; } // get IRQ state
uint16_t count(int counter) const { return compute_counter(counter); } // get counter value
void set_ext_clock(int counter, double clock); // set clock frequency
int ext_clock(int counter) const { return m_external_clock[counter]; } // get clock frequency
int status(int idx) const { return m_enabled[idx]; } // get whether timer is enabled
int irq_state() const { return m_irq; } // get IRQ state
int count(int idx) const { return compute_counter(idx); } // get counter value
void set_ext_clock(int counter, double clock); // set clock frequency
int ext_clock(int idx) const { return m_external_clock[idx]; } // get clock frequency
void write(offs_t offset, uint8_t data);
uint8_t read(offs_t offset);
@ -61,12 +61,13 @@ protected:
virtual void device_resolve_objects() override;
private:
void subtract_from_counter(int counter, int count);
void tick(int counter, int count);
TIMER_CALLBACK_MEMBER(timeout);
void deduct_from_counter(int idx);
void tick(int counter);
TIMER_CALLBACK_MEMBER(state_changed);
uint16_t compute_counter(int counter) const;
void reload_count(int idx);
int compute_counter(int idx) const;
void reload_counter(int idx);
void update_expiration_for_clock_source(int idx, bool changed_to_external = false, double new_external_clock = 0.0);
enum
{
@ -112,7 +113,7 @@ private:
bool m_clk[3]; // Clock states
bool m_enabled[3];
uint8_t m_mode[3];
bool m_fired[3];
bool m_single_fired[3];
uint8_t m_t3_divisor;
uint8_t m_t3_scaler;
uint8_t m_irq;
@ -129,9 +130,6 @@ private:
attotime m_disable_time[3];
static const char *const opmode[];
// set in dual 8 bit mode to indicate Output high time cycle
bool m_hightime[3];
};