summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/machine/k573fpga.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mame/machine/k573fpga.cpp')
-rw-r--r--src/mame/machine/k573fpga.cpp297
1 files changed, 200 insertions, 97 deletions
diff --git a/src/mame/machine/k573fpga.cpp b/src/mame/machine/k573fpga.cpp
index ad8134d9002..f11b80eebd6 100644
--- a/src/mame/machine/k573fpga.cpp
+++ b/src/mame/machine/k573fpga.cpp
@@ -15,14 +15,15 @@ k573fpga_device::k573fpga_device(const machine_config &mconfig, const char *tag,
device_t(mconfig, KONAMI_573_DIGITAL_FPGA, tag, owner, clock),
ram(*this, finder_base::DUMMY_TAG),
mas3507d(*this, "mpeg"),
- use_ddrsbm_fpga(false)
+ is_ddrsbm_fpga(false)
{
}
void k573fpga_device::device_add_mconfig(machine_config &config)
{
MAS3507D(config, mas3507d);
- mas3507d->sample_cb().set(*this, FUNC(k573fpga_device::get_decrypted));
+ mas3507d->mpeg_frame_sync_cb().set(*this, FUNC(k573fpga_device::mpeg_frame_sync));
+ mas3507d->demand_cb().set(*this, FUNC(k573fpga_device::mas3507d_demand));
}
void k573fpga_device::device_start()
@@ -30,74 +31,104 @@ void k573fpga_device::device_start()
save_item(NAME(crypto_key1));
save_item(NAME(crypto_key2));
save_item(NAME(crypto_key3));
+ save_item(NAME(crypto_key1_start));
+ save_item(NAME(crypto_key2_start));
+ save_item(NAME(crypto_key3_start));
save_item(NAME(mp3_start_addr));
- save_item(NAME(mp3_cur_addr));
save_item(NAME(mp3_end_addr));
- save_item(NAME(use_ddrsbm_fpga));
- save_item(NAME(is_stream_active));
- save_item(NAME(is_timer_active));
- save_item(NAME(counter_previous));
+ save_item(NAME(mp3_cur_start_addr));
+ save_item(NAME(mp3_cur_end_addr));
+ save_item(NAME(mp3_cur_addr));
+ save_item(NAME(mp3_data));
+ save_item(NAME(mp3_remaining_bytes));
+ save_item(NAME(is_ddrsbm_fpga));
+ save_item(NAME(mpeg_status));
+ save_item(NAME(fpga_status));
+ save_item(NAME(mp3_frame_counter));
+ save_item(NAME(counter_value));
save_item(NAME(counter_current));
- save_item(NAME(last_playback_status));
+ save_item(NAME(counter_base));
+ save_item(NAME(is_mpeg_frame_synced));
+
+ m_stream_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(k573fpga_device::update_stream), this));
+ m_stream_timer->adjust(attotime::never);
}
void k573fpga_device::device_reset()
{
+ fpga_status = 0;
mp3_start_addr = 0;
- mp3_cur_addr = 0;
mp3_end_addr = 0;
+ mp3_cur_start_addr = 0;
+ mp3_cur_end_addr = 0;
+ mp3_cur_addr = 0;
+ mp3_data = 0;
+ mp3_remaining_bytes = 0;
- crypto_key1 = 0;
- crypto_key2 = 0;
- crypto_key3 = 0;
+ crypto_key1_start = crypto_key1 = 0;
+ crypto_key2_start = crypto_key2 = 0;
+ crypto_key3_start = crypto_key3 = 0;
- is_stream_active = false;
- is_timer_active = false;
+ counter_current = counter_base = machine().time();
- counter_current = counter_previous = counter_offset = 0;
+ mpeg_status = 0;
+ mp3_frame_counter = 0;
+ counter_value = 0;
+ is_mpeg_frame_synced = false;
mas3507d->reset_playback();
- last_playback_status = get_mpeg_ctrl();
}
-void k573fpga_device::reset_counter() {
- counter_current = counter_previous = counter_offset = 0;
- status_update();
-}
-
-void k573fpga_device::status_update() {
- auto cur_playback_status = get_mpeg_ctrl();
- is_timer_active = is_streaming() || ((cur_playback_status == last_playback_status && last_playback_status > PLAYBACK_STATE_IDLE) || cur_playback_status > last_playback_status);
- last_playback_status = cur_playback_status;
-
- if(!is_timer_active)
- counter_current = counter_previous = counter_offset = 0;
+void k573fpga_device::reset_counter()
+{
+ is_mpeg_frame_synced = false;
+ counter_current = counter_base = machine().time();
+ counter_value = 0;
}
-uint32_t k573fpga_device::get_counter() {
- status_update();
-
- counter_previous = counter_current;
-
- if(is_timer_active) {
- mas3507d->update_stream();
- counter_current = mas3507d->get_samples() - counter_offset;
+void k573fpga_device::update_counter()
+{
+ if (is_ddrsbm_fpga) {
+ // The counter for Solo Bass Mix is used differently than other games.
+ // DDR Solo Bass Mix will sync the internal playback timer to the first second of the MP3 using the MP3 frame counter.
+ // After that the playback timer is incremented using the difference between the last counter value and the current counter value.
+ // This counter register itself is always running even when no audio is playing.
+ // TODO: What happens when mp3_counter_low_w is written to on Solo Bass Mix?
+ counter_value = (machine().time() - counter_base).as_double();
+ }
+ else if (is_mpeg_frame_synced) {
+ // Timer only seems to start when the first MPEG frame sync is encountered, so wait for that trigger
+ counter_base = counter_current;
+ counter_current = machine().time();
+ counter_value += (counter_current - counter_base).as_double();
}
+}
- return counter_current;
+uint32_t k573fpga_device::get_counter()
+{
+ update_counter();
+ return counter_value * 44100;
}
-uint32_t k573fpga_device::get_counter_diff() {
+uint32_t k573fpga_device::get_counter_diff()
+{
// Delta playback time since last counter update.
// I couldn't find any active usages of this register but it exists in some code paths.
// The functionality was tested using custom code running on real hardware.
// When this is called, it will return the difference between the current counter value
// and the last read counter value, and then reset the counter back to the previously read counter's value.
- auto diff = counter_current - counter_previous;
- counter_current -= diff;
- counter_previous = counter_current;
- get_counter();
- return diff;
+ auto prev = counter_value;
+ update_counter();
+ auto diff = counter_value - prev;
+ counter_value = prev;
+ return diff * 44100;
+}
+
+uint16_t k573fpga_device::get_mp3_frame_count()
+{
+ // All games can read this but only DDR Solo Bass Mix actively uses it.
+ // Returns the same value as using a default read to get the frame counter from the MAS3507D over i2c.
+ return mp3_frame_counter & 0xffff;
}
uint16_t k573fpga_device::mas_i2c_r()
@@ -113,72 +144,91 @@ void k573fpga_device::mas_i2c_w(uint16_t data)
mas3507d->i2c_sda_w(data & 0x1000);
}
-uint16_t k573fpga_device::get_mpeg_ctrl()
+void k573fpga_device::set_crypto_key1(uint16_t v)
{
- switch(mas3507d->get_status()) {
- case mas3507d_device::PLAYBACK_STATE_IDLE:
- return PLAYBACK_STATE_IDLE;
-
- case mas3507d_device::PLAYBACK_STATE_BUFFER_FULL:
- return PLAYBACK_STATE_BUFFER_FULL;
+ crypto_key1_start = crypto_key1 = v;
+ update_mp3_decode_state();
+}
+void k573fpga_device::set_crypto_key2(uint16_t v)
+{
+ crypto_key2_start = crypto_key2 = v;
+ update_mp3_decode_state();
+}
+void k573fpga_device::set_crypto_key3(uint8_t v)
+{
+ crypto_key3_start = crypto_key3 = v;
+ update_mp3_decode_state();
+}
- case mas3507d_device::PLAYBACK_STATE_DEMAND_BUFFER:
- return PLAYBACK_STATE_DEMAND_BUFFER;
- }
+void k573fpga_device::set_mp3_start_addr(uint32_t v)
+{
+ mp3_start_addr = v;
+ update_mp3_decode_state();
+}
- return PLAYBACK_STATE_IDLE;
+void k573fpga_device::set_mp3_end_addr(uint32_t v)
+{
+ mp3_end_addr = v;
+ update_mp3_decode_state();
}
-bool k573fpga_device::is_mp3_playing()
+uint16_t k573fpga_device::get_mpeg_ctrl()
{
- return get_mpeg_ctrl() > PLAYBACK_STATE_IDLE;
+ return mpeg_status;
}
uint16_t k573fpga_device::get_fpga_ctrl()
{
// 0x0000 Not Streaming
// 0x1000 Streaming
- return is_streaming() << 12;
+ int is_streaming = BIT(fpga_status, FPGA_STREAMING_ENABLE)
+ && mp3_cur_addr >= mp3_cur_start_addr
+ && mp3_cur_addr < mp3_cur_end_addr;
+ return is_streaming << 12;
}
-bool k573fpga_device::is_streaming()
-{
- return is_stream_active && mp3_cur_addr < mp3_end_addr;
-}
-
-void k573fpga_device::set_mpeg_ctrl(uint16_t data)
+void k573fpga_device::set_fpga_ctrl(uint16_t data)
{
LOG("FPGA MPEG control %c%c%c | %04x\n",
- data & 0x8000 ? '#' : '.',
- data & 0x4000 ? '#' : '.', // "Active" flag. The FPGA will never start streaming data without this bit set
- data & 0x2000 ? '#' : '.',
+ BIT(data, FPGA_FRAME_COUNTER_ENABLE) ? '#' : '.',
+ BIT(data, FPGA_STREAMING_ENABLE) ? '#' : '.',
+ BIT(data, FPGA_MP3_ENABLE) ? '#' : '.',
data);
- mas3507d->reset_playback();
+ if (!BIT(data, FPGA_FRAME_COUNTER_ENABLE) && BIT(fpga_status, FPGA_FRAME_COUNTER_ENABLE)) {
+ mp3_frame_counter = 0;
+ }
- if(data == 0xa000) {
- is_stream_active = false;
- counter_current = counter_previous = 0;
- status_update();
- } else if(data == 0xe000) {
- is_stream_active = true;
- mp3_cur_addr = mp3_start_addr;
-
- reset_counter();
-
- if(!mas3507d->is_started) {
- mas3507d->start_playback();
- mas3507d->update_stream();
-
- // Audio should be buffered by this point.
- // The assumption is that the number of samples actually played can be
- // calculated by subtracting the base sample count when the song was started
- // from the current sample count when the counter register is read.
- // Otherwise, the sample count will always be ahead by the number of samples
- // that were in the buffered frames.
- counter_offset = mas3507d->get_samples();
- }
+ if ((BIT(data, FPGA_MP3_ENABLE) != BIT(fpga_status, FPGA_MP3_ENABLE))
+ || (BIT(data, FPGA_STREAMING_ENABLE) != BIT(fpga_status, FPGA_STREAMING_ENABLE))) {
+ mas3507d->reset_playback();
+ }
+
+ fpga_status = data;
+}
+
+
+void k573fpga_device::update_mp3_decode_state()
+{
+ // HACK: The exact timing of when the internal state in the FPGA updates is still unknown
+ // so update the state any time one of the core settings (decryption keys or data start/stop addr)
+ // for a stream changes.
+ mp3_cur_addr = mp3_start_addr;
+ mp3_cur_start_addr = mp3_start_addr;
+ mp3_cur_end_addr = mp3_end_addr;
+ is_mpeg_frame_synced = false;
+ mp3_remaining_bytes = 0;
+ crypto_key1 = crypto_key1_start;
+ crypto_key2 = crypto_key2_start;
+ crypto_key3 = crypto_key3_start;
+ mp3_frame_counter = 0;
+ reset_counter();
+
+ if (is_ddrsbm_fpga) {
+ crypto_key3 = crypto_key3_start = 0;
}
+
+ mas3507d->reset_playback();
}
uint16_t k573fpga_device::decrypt_default(uint16_t v)
@@ -234,6 +284,11 @@ uint16_t k573fpga_device::decrypt_default(uint16_t v)
uint16_t k573fpga_device::decrypt_ddrsbm(uint16_t data)
{
+ // TODO: Work out the proper decryption algorithm.
+ // Similar to the other games, ddrsbm is capable of sending a pre-mutated key that is used to simulate seeking by starting MP3 playback from a non-zero offset.
+ // The MP3 seeking functionality doesn't appear to be used so the game doesn't break from lack of support from what I can tell.
+ // The proper key mutation found in game code is: crypto_key1 = rol(crypto_key1, offset & 0x0f)
+
uint8_t key[16] = {0};
uint16_t key_state = bitswap<16>(
crypto_key1,
@@ -273,18 +328,66 @@ uint16_t k573fpga_device::decrypt_ddrsbm(uint16_t data)
return output_word;
}
-uint16_t k573fpga_device::get_decrypted()
+TIMER_CALLBACK_MEMBER(k573fpga_device::update_stream)
+{
+ if (!BIT(mpeg_status, PLAYBACK_STATE_DEMAND)) {
+ return;
+ }
+
+ if (!BIT(fpga_status, FPGA_MP3_ENABLE)
+ || !BIT(fpga_status, FPGA_STREAMING_ENABLE)
+ || mp3_cur_addr < mp3_cur_start_addr
+ || mp3_cur_addr >= mp3_cur_end_addr) {
+ return;
+ }
+
+ if (mp3_remaining_bytes <= 0) {
+ uint16_t src = ram[mp3_cur_addr >> 1];
+ mp3_data = is_ddrsbm_fpga ? decrypt_ddrsbm(src) : decrypt_default(src);
+ mp3_data = ((mp3_data >> 8) & 0xff) | ((mp3_data & 0xff) << 8);
+ mp3_cur_addr += 2;
+ mp3_remaining_bytes = 2;
+ }
+
+ mas3507d->sid_w(mp3_data & 0xff);
+ mp3_data >>= 8;
+ mp3_remaining_bytes--;
+}
+
+WRITE_LINE_MEMBER(k573fpga_device::mpeg_frame_sync)
{
- if(!is_streaming()) {
- is_stream_active = false;
- return 0;
+ if (state) {
+ mpeg_status &= ~(1 << PLAYBACK_STATE_IDLE);
+ mpeg_status |= 1 << PLAYBACK_STATE_PLAYING;
+
+ if (!is_mpeg_frame_synced) {
+ reset_counter();
+ is_mpeg_frame_synced = true;
+ }
+
+ if (BIT(fpga_status, FPGA_FRAME_COUNTER_ENABLE)) {
+ mp3_frame_counter++;
+ }
}
+ else {
+ mpeg_status &= ~(1 << PLAYBACK_STATE_PLAYING);
+ mpeg_status |= 1 << PLAYBACK_STATE_IDLE;
+ }
+}
- uint16_t src = ram[mp3_cur_addr >> 1];
- uint16_t result = use_ddrsbm_fpga ? decrypt_ddrsbm(src) : decrypt_default(src);
- mp3_cur_addr += 2;
+WRITE_LINE_MEMBER(k573fpga_device::mas3507d_demand)
+{
+ if (state && !BIT(mpeg_status, PLAYBACK_STATE_DEMAND)) {
+ mpeg_status |= 1 << PLAYBACK_STATE_DEMAND;
+ }
+ else if (!state && BIT(mpeg_status, PLAYBACK_STATE_DEMAND)) {
+ mpeg_status &= ~(1 << PLAYBACK_STATE_DEMAND);
+ m_stream_timer->adjust(attotime::never);
+ }
- return result;
+ if (state && BIT(mpeg_status, PLAYBACK_STATE_DEMAND)) {
+ m_stream_timer->adjust(attotime::zero);
+ }
}
DEFINE_DEVICE_TYPE(KONAMI_573_DIGITAL_FPGA, k573fpga_device, "k573fpga", "Konami 573 Digital I/O FPGA")