1 // license:BSD-3-Clause
2 // copyright-holders:Patrick Mackinlay
3 
4 /*
5  * Brooktree Bt431 Monolithic CMOS 64x64 Pixel Cursor Generator.
6  *
7  * Sources:
8  *   - http://bitsavers.org/components/brooktree/_dataBooks/1993_Brooktree_Graphics_and_Imaging_Product_Databook.pdf
9  *
10  * TODO:
11  *   - test, profile and optimize
12  */
13 
14 #include "emu.h"
15 #include "bt431.h"
16 
17 #define LOG_GENERAL   (1U << 0)
18 
19 //#define VERBOSE       (LOG_GENERAL)
20 #include "logmacro.h"
21 
22 DEFINE_DEVICE_TYPE(BT431, bt431_device, "bt431", "Bt431 64x64 Pixel Cursor Generator")
23 
bt431_device(machine_config const & mconfig,char const * tag,device_t * owner,u32 clock)24 bt431_device::bt431_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock)
25 	: device_t(mconfig, BT431, tag, owner, clock)
26 {
27 }
28 
device_start()29 void bt431_device::device_start()
30 {
31 	save_item(NAME(m_address));
32 	save_item(NAME(m_command));
33 
34 	save_item(NAME(m_cursor_x));
35 	save_item(NAME(m_cursor_y));
36 	save_item(NAME(m_window_x));
37 	save_item(NAME(m_window_y));
38 	save_item(NAME(m_window_w));
39 	save_item(NAME(m_window_h));
40 
41 	save_item(NAME(m_ram));
42 }
43 
device_reset()44 void bt431_device::device_reset()
45 {
46 	m_address = 0;
47 	m_command = 0;
48 
49 	update();
50 }
51 
map(address_map & map)52 void bt431_device::map(address_map &map)
53 {
54 	map(0x00, 0x00).rw(FUNC(bt431_device::addr_r<0>), FUNC(bt431_device::addr_w<0>));
55 	map(0x01, 0x01).rw(FUNC(bt431_device::addr_r<8>), FUNC(bt431_device::addr_w<8>));
56 	map(0x02, 0x02).rw(FUNC(bt431_device::ram_r), FUNC(bt431_device::ram_w));
57 	map(0x03, 0x03).rw(FUNC(bt431_device::reg_r), FUNC(bt431_device::reg_w));
58 }
59 
ram_r()60 u8 bt431_device::ram_r()
61 {
62 	u8 const data = m_ram[m_address & ADDRESS_MASK];
63 
64 	// increment address register
65 	if (!machine().side_effects_disabled())
66 		m_address = (m_address + 1) & ADDRESS_MASK;
67 
68 	return data;
69 }
70 
ram_w(u8 data)71 void bt431_device::ram_w(u8 data)
72 {
73 	m_ram[m_address & ADDRESS_MASK] = data;
74 
75 	// increment address register
76 	if (!machine().side_effects_disabled())
77 		m_address = (m_address + 1) & ADDRESS_MASK;
78 }
79 
reg_r()80 u8 bt431_device::reg_r()
81 {
82 	u8 data = 0;
83 
84 	switch (m_address & 0xf)
85 	{
86 	case REG_COMMAND: data = m_command; break;
87 
88 	case REG_CURSOR_X_LO: data = m_cursor_x & 0xff; break;
89 	case REG_CURSOR_X_HI: data = (m_cursor_x >> 8); break;
90 	case REG_CURSOR_Y_LO: data = m_cursor_y & 0xff; break;
91 	case REG_CURSOR_Y_HI: data = (m_cursor_y >> 8); break;
92 
93 	case REG_WINDOW_X_LO: data = m_window_x & 0xff; break;
94 	case REG_WINDOW_X_HI: data = (m_window_x >> 8); break;
95 	case REG_WINDOW_Y_LO: data = m_window_y & 0xff; break;
96 	case REG_WINDOW_Y_HI: data = (m_window_y >> 8); break;
97 
98 	case REG_WINDOW_W_LO: data = m_window_w & 0xff; break;
99 	case REG_WINDOW_W_HI: data = (m_window_w >> 8); break;
100 	case REG_WINDOW_H_LO: data = m_window_h & 0xff; break;
101 	case REG_WINDOW_H_HI: data = (m_window_h >> 8); break;
102 
103 	default:
104 		LOG("read from unknown address 0x%04x (%s)\n",
105 			m_address, machine().describe_context());
106 		break;
107 	}
108 
109 	// increment address register
110 	if (!machine().side_effects_disabled())
111 		m_address = (m_address + 1) & ADDRESS_MASK;
112 
113 	return data;
114 }
115 
reg_w(u8 data)116 void bt431_device::reg_w(u8 data)
117 {
118 	switch (m_address & 0xf)
119 	{
120 	case REG_COMMAND:
121 		m_command = data & CR_WM;
122 		LOG("64x64 cursor %s, cross hair cursor %s, cursor format %s, cross hair thickness %d\n",
123 			(data & CR_D6) ? "enable" : "disable",
124 			(data & CR_D5) ? "enable" : "disable",
125 			(data & CR_D4) ? "OR" : "XOR",
126 			((data & CR_D1D0) << 1) + 1);
127 		break;
128 
129 	case REG_CURSOR_X_LO:
130 		m_cursor_x = (m_cursor_x & 0x0f00) | data;
131 		LOG("cursor x low register: 0x%02x (%s)\n", data, machine().describe_context());
132 		break;
133 	case REG_CURSOR_X_HI:
134 		m_cursor_x = (u16(data & 0xf) << 8) | (m_cursor_x & 0xff);
135 		LOG("cursor x high register: 0x%02x (%s)\n", data, machine().describe_context());
136 		break;
137 	case REG_CURSOR_Y_LO:
138 		m_cursor_y = (m_cursor_y & 0x0f00) | data;
139 		LOG("cursor y low register: 0x%02x (%s)\n", data, machine().describe_context());
140 		break;
141 	case REG_CURSOR_Y_HI:
142 		m_cursor_y = (u16(data & 0xf) << 8) | (m_cursor_y & 0xff);
143 		LOG("cursor y high register: 0x%02x (%s)\n", data, machine().describe_context());
144 		break;
145 
146 	case REG_WINDOW_X_LO:
147 		m_window_x = (m_window_x & 0x0f00) | data;
148 		LOG("window x low register: 0x%02x\n", data);
149 		break;
150 	case REG_WINDOW_X_HI:
151 		m_window_x = (u16(data & 0xf) << 8) | (m_window_x & 0xff);
152 		LOG("window x high register: 0x%02x\n", data);
153 		break;
154 	case REG_WINDOW_Y_LO:
155 		m_window_y = (m_window_y & 0x0f00) | data;
156 		LOG("window y low register: 0x%02x\n", data);
157 		break;
158 	case REG_WINDOW_Y_HI:
159 		m_window_y = (u16(data & 0xf) << 8) | (m_window_y & 0xff);
160 		LOG("window y high register: 0x%02x\n", data);
161 		break;
162 
163 	case REG_WINDOW_W_LO:
164 		m_window_w = (m_window_w & 0x0f00) | data;
165 		LOG("window width low register: 0x%02x\n", data);
166 		break;
167 	case REG_WINDOW_W_HI:
168 		m_window_w = (u16(data & 0xf) << 8) | (m_window_w & 0xff);
169 		LOG("window width high register: 0x%02x\n", data);
170 		break;
171 	case REG_WINDOW_H_LO:
172 		m_window_h = (m_window_h & 0x0f00) | data;
173 		LOG("window height low register: 0x%02x\n", data);
174 		break;
175 	case REG_WINDOW_H_HI:
176 		m_window_h = (u16(data & 0xf) << 8) | (m_window_h & 0xff);
177 		LOG("window height high register: 0x%02x\n", data);
178 		break;
179 
180 	default:
181 		LOG("write to unknown address 0x%04x data 0x%02x (%s)\n",
182 			m_address, data, machine().describe_context());
183 		break;
184 	}
185 
186 	// increment address register
187 	m_address = (m_address + 1) & ADDRESS_MASK;
188 
189 	update();
190 }
191 
update()192 void bt431_device::update()
193 {
194 	/*
195 	 * The cursor (x) value to be written is calculated as follows:
196 	 *
197 	 *   Cx = desired display screen (x) position + D + H - P
198 	 *
199 	 * where
200 	 *
201 	 *   P = 37 if 1:1 output multiplexing, 52 if 4:1 output multiplexing,
202 	 *       57 if 5:1 output multiplexing
203 	 *   D = skew (in pixels) between the output cursor data and external pixel
204 	 *       data
205 	 *   H = number of pixels between the first rising edge of CLOCK
206 	 *       following the falling edge of HSYNC* to active video
207 	 *
208 	 * The P value is one-half cursor RAM width + (internal pipeline delay in
209 	 * clock cycles * one, four or five, depending on multiplex selection).
210 	 *
211 	 * The cursor (y) value to be written is calculated as follows:
212 	 *
213 	 *   Cy = desired display screen (y) position + V - 32
214 	 *
215 	 * where
216 	 *
217 	 *   V = number of scan lines from the first falling edge of HSYNC* that is
218 	 *       two or more clock cycles after the falling edge of VSYNC* to
219 	 *       active video.
220 	 *
221 	 * Values from $0FC0 (-64) to $0FBF (+4031) may be loaded into the
222 	 * cursor (y) register. The negative values ($0FC0 to $0FFF) are used
223 	 * in situations where V < 32, and the cursor must be moved off the
224 	 * top of the screen.
225 	 */
226 	const int cursor_x = m_cursor_x + (
227 		(m_command & CR_D3D2) == CR_D3D2_11 ? 37 :
228 		(m_command & CR_D3D2) == CR_D3D2_41 ? 52 :
229 		(m_command & CR_D3D2) == CR_D3D2_51 ? 57 : 0);
230 	const int cursor_y = (m_cursor_y < 0xfc0 ? m_cursor_y : m_cursor_y - 0x1000) + 32;
231 
232 	// update bitmap cursor drawing rectangle
233 	m_bm_window.set(cursor_x - 31, cursor_x + 32, cursor_y - 31, cursor_y + 32);
234 
235 	// update cross hair cursor drawing rectangles
236 	const int thickness = m_command & CR_D1D0;
237 	if (m_window_x == 0 && m_window_y == 0 && m_window_w == 0x0fff && m_window_h == 0x0fff)
238 	{
239 		// full screen cross hair cursor
240 		m_ch_v.set(cursor_x - thickness, cursor_x + thickness, m_window_y, m_window_y + m_window_h - 1);
241 		m_ch_h.set(m_window_x, m_window_x + m_window_w - 1, cursor_y - thickness, cursor_y + thickness);
242 	}
243 	else
244 	{
245 		// windowed cross hair cursor
246 		const int window_x = m_window_x + (
247 			(m_command & CR_D3D2) == CR_D3D2_11 ? 5 :
248 			(m_command & CR_D3D2) == CR_D3D2_41 ? 20 :
249 			(m_command & CR_D3D2) == CR_D3D2_51 ? 25 : 0);
250 		const int window_y = m_window_y;
251 
252 		const int window_w = m_window_w + (
253 			(m_command & CR_D3D2) == CR_D3D2_11 ? 2 :
254 			(m_command & CR_D3D2) == CR_D3D2_41 ? 8 :
255 			(m_command & CR_D3D2) == CR_D3D2_51 ? 10 : 0);
256 		const int window_h = m_window_h + 2;
257 
258 		m_ch_v.set(cursor_x - thickness, cursor_x + thickness, window_y + 1, window_y + window_h - 2);
259 		m_ch_h.set(window_x + 1, window_x + window_w - 2, cursor_y - thickness, cursor_y + thickness);
260 	}
261 }
262 
cur_r(unsigned x,unsigned y) const263 bool bt431_device::cur_r(unsigned x, unsigned y) const
264 {
265 	bool data = false;
266 
267 	// cross hair cursor
268 	if ((m_command & CR_D5) && (m_ch_h.contains(x, y) || m_ch_v.contains(x, y)))
269 		data = true;
270 
271 	// bitmap cursor
272 	if ((m_command & CR_D6) && m_bm_window.contains(x, y))
273 	{
274 		bool const bit = BIT(m_ram[(y - m_bm_window.top()) * 8 + (x - m_bm_window.left()) / 8], 7 - (x - m_bm_window.left()) % 8);
275 
276 		if (m_command & CR_D4)
277 			data |= bit;
278 		else
279 			data ^= bit;
280 	}
281 
282 	return data;
283 }
284