summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/machine')
-rw-r--r--src/devices/machine/gen_fifo.cpp201
-rw-r--r--src/devices/machine/gen_fifo.h222
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