summaryrefslogtreecommitdiffstats
path: root/src/devices/machine/gen_fifo.h
blob: 4db8a75d8c10700072c13b43ea0166f7b16b42ac (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
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 popped.  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) \
	downcast<screen_device &>(*device).set_empty_cb(DEVCB_##_devcb);

#define MCFG_GENERIC_FIFO_FULL_CALLBACK(_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