diff options
Diffstat (limited to 'src/devices/machine')
-rw-r--r-- | src/devices/machine/gen_fifo.cpp | 201 | ||||
-rw-r--r-- | src/devices/machine/gen_fifo.h | 222 |
2 files changed, 423 insertions, 0 deletions
diff --git a/src/devices/machine/gen_fifo.cpp b/src/devices/machine/gen_fifo.cpp new file mode 100644 index 00000000000..639adacd571 --- /dev/null +++ b/src/devices/machine/gen_fifo.cpp @@ -0,0 +1,201 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#include "emu.h" +#include "gen_fifo.h" + +template<typename T> generic_fifo_device_base<T>::generic_fifo_device_base(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) : + device_t(mconfig, type, tag, owner, clock), + m_empty_cb(*this), + m_full_cb(*this), + m_on_fifo_empty_pre_sync([](){}), + m_on_fifo_empty_post_sync([](){}), + m_on_fifo_unempty([](){}), + m_on_fifo_full_post_sync([](){}), + m_on_fifo_unfull([](){}), + m_on_push([](){}), + m_on_pop([](){}), + m_sync_empty(nullptr), + m_sync_full(nullptr), + m_size(0), + m_empty_triggered(false), + m_full_triggered(false) +{ +} + +template<typename T> void generic_fifo_device_base<T>::clear() +{ + bool was_empty = is_empty(); + bool was_full = m_size && is_full(); + m_values.clear(); + m_extra_values.clear(); + + if(m_sync_empty) { + m_sync_empty->adjust(attotime::never); + m_sync_full->adjust(attotime::never); + } + + if(was_full) + m_full_cb(false); + + if(m_full_triggered) { + m_full_triggered = false; + m_on_fifo_unfull(); + } + + if(!was_empty) + m_empty_cb(true); +} + +template<typename T> void generic_fifo_device_base<T>::device_start() +{ + m_empty_cb.resolve_safe(); + m_full_cb.resolve_safe(); + + m_sync_empty = timer_alloc(0); + m_sync_full = timer_alloc(1); + + // This is not saving the fifo, let's hope it's empty... + save_item(NAME(m_size)); + save_item(NAME(m_empty_triggered)); + save_item(NAME(m_full_triggered)); +} + +template<typename T> void generic_fifo_device_base<T>::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + switch(id) { + case T_FULL: { + // Add the extra values in if there's space + bool was_empty = is_empty(); + bool was_full = is_full(); + + while(!m_extra_values.empty() && !is_full()) { + T t(std::move(m_extra_values.front())); + m_extra_values.erase(m_extra_values.begin()); + m_values.emplace_back(std::move(t)); + } + + // Adjust devcb lines as needed + if(was_empty && !is_empty()) + set_empty_cb(false); + if(!was_full && is_full()) + set_full_cb(true); + + // Are there still values that don't fit? + if(!m_extra_values.empty()) { + // If yes, stop the source if not done yet + if(!m_full_triggered) { + m_full_triggered = true; + m_on_fifo_full_post_sync(); + } + } + break; + } + + case T_EMPTY: { + // Sync was called for a fifo empty, is it still empty? + if(is_empty()) { + // If yes, stop the destination if not done yet + if(!m_empty_triggered) { + m_empty_triggered = true; + m_on_fifo_empty_post_sync(); + } + } + break; + } + } +} + +template<typename T> T generic_fifo_device_base<T>::pop() +{ + // Are we empty? + if(is_empty()) { + // First, trigger the sync + m_sync_empty->adjust(attotime::zero); + // Then fire the callback + m_on_fifo_empty_pre_sync(); + + return T(); + + } else { + // Prepare the value + bool was_full = is_full(); + T t(std::move(m_values.front())); + m_values.erase(m_values.begin()); + + // If we have extra values and the source is stopped, + // push one in, and possibly restart the source. + if(!m_extra_values.empty()) { + T t1(std::move(m_extra_values.front())); + m_extra_values.erase(m_extra_values.begin()); + m_values.emplace_back(std::move(t1)); + + if(m_extra_values.empty()) { + // We don't have any extra values left, we can unblock + // the source. + if(m_full_triggered) { + m_full_triggered = false; + m_on_fifo_unfull(); + } + } + } + + // Update the devcb lines + if(is_empty()) + m_empty_cb(true); + if(was_full && !is_full()) + m_full_cb(false); + + // We did a pop + m_on_pop(); + return std::move(t); + } +} + +template<typename T> void generic_fifo_device_base<T>::push(T &&t) +{ + // Are we already overflowed? + if(!m_extra_values.empty()) + // If yes, just add to the overflow queue + m_extra_values.emplace_back(std::move(t)); + + else if(is_full()) { + // Otherwise, are we full? + + // If yes start the overflow queue + m_extra_values.emplace_back(std::move(t)); + + // And trigger the sync + m_sync_full->adjust(attotime::zero); + + } else { + // Add the value to the fifo + bool was_empty = is_empty(); + m_values.emplace_back(std::move(t)); + + // Update the devcb lines + if(was_empty) + m_empty_cb(false); + if(is_full()) + m_full_cb(true); + + // Unblock the destination if needed + if(m_empty_triggered) { + m_empty_triggered = false; + m_on_fifo_unempty(); + } + } + + // We did a push + m_on_push(); +} + +template class generic_fifo_device_base<u32>; + +generic_fifo_u32_device::generic_fifo_u32_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : + generic_fifo_device_base<u32>(mconfig, GENERIC_FIFO_U32, tag, owner, clock) +{ +} + +DEFINE_DEVICE_TYPE(GENERIC_FIFO_U32, generic_fifo_u32_device, "generic_fifo_u32_device", "Generic fifo, u32 values") + diff --git a/src/devices/machine/gen_fifo.h b/src/devices/machine/gen_fifo.h new file mode 100644 index 00000000000..0ba80fd5fc7 --- /dev/null +++ b/src/devices/machine/gen_fifo.h @@ -0,0 +1,222 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#ifndef MAME_MACHINE_GEN_FIFO_H +#define MAME_MACHINE_GEN_FIFO_H + + +/* Generic fifo device with flow control + * + * + * To use it: + * - Create the device, put push/pop in the appropriate memory maps of + * the source (push) and destination (pop) devices + * - Connect the empty/full callbacks in the MCFG if needed + * - Call setup at init with the size and the callbacks + * - Enjoy + * + * The design is such that destination devices must be able to be + * halted and to retrigger a fifo read when asked to, while the source + * devices only need to be haltable. There is some leeway on the + * source devices, e.g. no values are ever lost, so one can rudely + * overflow the fifo when the source is not a simple executable + * device. + * + * The callbacks: + * - on_fifo_empty_pre_sync: + * Called on a pop with an empty fifo. Must ask the destination + * to try again (e.g. ->stall() or equivalent). The pop itself + * will then return zero. Triggers a machine-wide sync, because + * the source device may be behind and could push data in the + * remaining of its timeslice. + * + * - on_fifo_empty_post_sync: + * Called after the sync consecutive to a pop with an empty fifo. + * It means that even with the source device synced the fifo is + * still empty. The destination device should be halted. + * + * - on_fifo_unempty: + * Called when the fifo is filled again after a call to + * on_fifo_empty_post_sync. The destination device should be + * restarted, the pop will succeed this time. + * + * - on_fifo_full_post_sync: + * When the source pushes in a full fifo, the extra value is + * stored and a machine sync is triggered to give a chance to the + * destination device to pop some of the fifo data in its + * remaining timeslice. If after the sync the fifo is still full, + * that callback is triggered. The source device should be halted. + * + * - on_fifo_unfull: + * Called when the fifo again has free space after a call to + * on_fifo_full_post_sync. The source device should be restarted. + * + * - on_push: + * Called when a new value was just pushed. That callback is + * usually not needed, but it can be useful when the destination + * is not an executable device but something hardcoded + * (rasterizer, etc). + * + * - on_pop: + * Called when a new value was just pushed. That callback is + * usually not needed, but it can be useful when the source is not + * an executable device but something hardcoded. + * + * Note: setup can be called multiple times, each call overrides the + * previous and clears the fifo. + * + * Note: the fifo element type T must be copyable if one wants to use + * peek(). It must trivially copyable and of size 1, 2, 4 or 8 bytes + * to use the memory-map accessors. Otherwise only movability is + * required. + */ + + +#define MCFG_GENERIC_FIFO_EMPTY_CALLBACK(_devcb) \ + devcb = &downcast<screen_device &>(*device).set_empty_cb(DEVCB_##_devcb); + +#define MCFG_GENERIC_FIFO_FULL_CALLBACK(_devcb) \ + devcb = &downcast<screen_device &>(*device).set_full_cb(DEVCB_##_devcb); + + +template<typename T> class generic_fifo_device_base : public device_t { +public: + /* The general setup. Call be called multiple times, clears the fifo. */ + void setup(size_t size, + std::function<void ()> on_fifo_empty_pre_sync, + std::function<void ()> on_fifo_empty_post_sync, + std::function<void ()> on_fifo_unempty, + std::function<void ()> on_fifo_full_post_sync, + std::function<void ()> on_fifo_unfull, + std::function<void ()> on_push = [](){}, + std::function<void ()> on_pop = [](){}) { + m_on_fifo_empty_pre_sync = on_fifo_empty_pre_sync; + m_on_fifo_empty_post_sync = on_fifo_empty_post_sync; + m_on_fifo_unempty = on_fifo_unempty; + m_on_fifo_full_post_sync = on_fifo_full_post_sync; + m_on_fifo_unfull = on_fifo_unfull; + m_on_push = on_push; + m_on_pop = on_pop; + + clear(); + m_size = size; + } + + /* Generic push/pop */ + T pop(); + void push(T &&value); + + /* Indicates whether the fifo is empty or full. Note that a pop + on a full fifo does not ensure it will become non-full, there + may be extra values stored. Also, an empty fifo can fill up + from extra values later after a sync. */ + + bool is_empty() const { return m_values.empty(); } + bool is_full() const { return m_values.size() >= m_size; } + + /* Empty the fifo. */ + void clear(); + + /* Callbacks signalling empty (true)/nonempty (false) and full (true)/nonfull (false) */ + template<class Object> devcb_base &set_empty_cb(Object &&object) { return m_empty_cb.set_callback(std::forward<Object>(object)); } + template<class Object> devcb_base &set_full_cb(Object &&object) { return m_full_cb.set_callback(std::forward<Object>(object)); } + + /* Get the fifo current size - Note that real hardware usually + can't do that. May be bigger that the fifo size if some extra + values are stored. */ + size_t size() const { return m_values.size() + m_extra_values.size(); } + + /* Peek at a value in the fifo at an offset, 0 being the next + value to be popped, 1 the one following, etc - Note that real + hardware usually can't do that. Returns 0 or equivalent when + the offset is over the current fifo size. */ + template <typename U=T> std::enable_if_t<(std::is_copy_constructible<T>::value && std::is_same<T, U>::value), T> peek(offs_t offset) const { + if(offset < m_values.size()) + return m_values[offset]; + offset -= m_values.size(); + if(offset < m_extra_values.size()) + return m_extra_values[offset]; + return T(); + } + +protected: + generic_fifo_device_base(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock); + virtual ~generic_fifo_device_base() = default; + + /* These used to build memory accessors that can be put in a + memory map. They do the appropriate bit-identical type + conversions if you say make a fifo of floats and want to access + in with a 32-bits handler, which deals with 32-bits unsigned + integers. Don't be afraid by the apparently costly memcpy, the + compiler sees through it and removes it, and that's the only + standard-sanctioned way to do that. + + They need to be upcalled in the deriving class from real + accessors though. + */ + + template <typename U> std::enable_if_t<(std::is_trivially_copyable<T>::value && sizeof(T) == sizeof(U)), void> write_gen(U data) { + T t; + memcpy(&t, &data, sizeof(data)); + push(std::move(t)); + } + + template <typename U> std::enable_if_t<(std::is_trivially_copyable<T>::value && sizeof(T) == sizeof(U)), U> read_gen() { + T t(pop()); + U data; + memcpy(&data, &t, sizeof(data)); + return data; + } + + +private: + // Timer IDs for sync on empty and full + enum timer_ids { T_EMPTY, T_FULL }; + + // Configured callbacks + + devcb_write_line m_empty_cb; + devcb_write_line m_full_cb; + + std::function<void ()> m_on_fifo_empty_pre_sync; + std::function<void ()> m_on_fifo_empty_post_sync; + std::function<void ()> m_on_fifo_unempty; + std::function<void ()> m_on_fifo_full_post_sync; + std::function<void ()> m_on_fifo_unfull; + std::function<void ()> m_on_push; + std::function<void ()> m_on_pop; + + // The values are stored into two vectors for simplicity. + // m_values may become a rotating buffer when everything else + // works. m_extra_values should probably stay a vector, but could + // become a list. + + std::vector<T> m_values; + std::vector<T> m_extra_values; + + // The synchronization timers + emu_timer *m_sync_empty, *m_sync_full; + + // Configured size of the fifo + size_t m_size; + + // Notes whether the halting callbacks were triggered + bool m_empty_triggered, m_full_triggered; + + virtual void device_start() override; + virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override; +}; + +class generic_fifo_u32_device : public generic_fifo_device_base<u32> +{ +public: + generic_fifo_u32_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + virtual ~generic_fifo_u32_device() = default; + + DECLARE_READ32_MEMBER(read) { return read_gen<u32>(); } + DECLARE_WRITE32_MEMBER(write) { write_gen(data); } +}; + +DECLARE_DEVICE_TYPE(GENERIC_FIFO_U32, generic_fifo_u32_device) + +#endif |