1 // license:BSD-3-Clause
2 // copyright-holders:F. Ulivi
3 /*********************************************************************
4 
5     1ma6.cpp
6 
7     HP-85 tape controller (1MA6-0001)
8 
9     *Status of tape tests of service ROM*
10 
11     Test Code   Description     Status
12     ==================================
13     I           Write protect   OK
14     P           Status test     OK
15     Q           Speed test      OK
16     R           Hole detection  Fails with "HOLE D" error (*1)
17     S           Write test      OK
18     T           Read test       OK
19     U           Record test     OK (*2)
20 
21     *1 Hole test fails because it depends on the diameter of the
22        holes being correct (this driver doesn't emulate it).
23     *2 Record test is buggy (byte @74ab in service ROM should be 0x54).
24        Test succeeds if this bug is corrected first.
25 
26 *********************************************************************/
27 
28 #include "emu.h"
29 #include "1ma6.h"
30 
31 // Debugging
32 #include "logmacro.h"
33 #define LOG_DBG_MASK (LOG_GENERAL << 1)
34 #define LOG_DBG(...) LOGMASKED(LOG_DBG_MASK, __VA_ARGS__)
35 #define LOG_RW_MASK (LOG_DBG_MASK << 1)
36 #define LOG_RW(...) LOGMASKED(LOG_RW_MASK, __VA_ARGS__)
37 #undef VERBOSE
38 //#define VERBOSE (LOG_GENERAL | LOG_DBG_MASK | LOG_RW_MASK)
39 #define VERBOSE 0
40 
41 // Device type definition
42 DEFINE_DEVICE_TYPE(HP_1MA6, hp_1ma6_device, "hp_1ma6", "HP 1MA6")
43 
44 // Bit manipulation
45 namespace {
BIT_MASK(unsigned n)46 	static constexpr unsigned BIT_MASK(unsigned n)
47 	{
48 		return 1U << n;
49 	}
50 
BIT_CLR(T & w,unsigned n)51 	template<typename T> void BIT_CLR(T& w , unsigned n)
52 	{
53 		w &= ~(T)BIT_MASK(n);
54 	}
55 
BIT_SET(T & w,unsigned n)56 	template<typename T> void BIT_SET(T& w , unsigned n)
57 	{
58 		w |= (T)BIT_MASK(n);
59 	}
60 
COPY_BIT(bool bit,T & w,unsigned n)61 	template<typename T> void COPY_BIT(bool bit , T& w , unsigned n)
62 	{
63 		if (bit) {
64 			BIT_SET(w , n);
65 		} else {
66 			BIT_CLR(w , n);
67 		}
68 	}
69 }
70 
71 // **** Constants ****
72 constexpr double FAST_SPEED = 60.0;             // Fast speed: 60 ips
73 constexpr double SLOW_SPEED = 10.0;             // Slow speed: 10 ips
74 constexpr double MOVING_THRESHOLD = 2.0;        // Tape is moving when speed > 2.0 ips
75 constexpr double ACCELERATION = 1200.0;         // Acceleration when speed set point is changed: 1200 ips^2
76 // One tachometer tick every 1/32 of inch
77 constexpr hti_format_t::tape_pos_t TACH_TICK_LEN = hti_format_t::ONE_INCH_POS / 32;
78 // Minimum gap size (totally made up)
79 constexpr hti_format_t::tape_pos_t MIN_GAP_SIZE = TACH_TICK_LEN;
80 
81 // Bits in control register
82 enum control_bits : unsigned
83 {
84 	CTL_TRACK_NO_BIT = 0,     // Track selection
85 	CTL_POWER_UP_BIT = 1,     // Tape controller power up
86 	CTL_MOTOR_ON_BIT = 2,     // Motor control
87 	CTL_DIR_FWD_BIT = 3,      // Tape direction = forward
88 	CTL_FAST_BIT = 4,         // Speed = fast
89 	CTL_WRITE_DATA_BIT = 5,   // Write data
90 	CTL_WRITE_SYNC_BIT = 6,   // Write SYNC
91 	CTL_WRITE_GAP_BIT = 7     // Write gap
92 };
93 
94 // Bits in status register
95 enum status_bits : unsigned
96 {
97 	STS_CASSETTE_IN_BIT = 0,  // Cassette in
98 	STS_STALL_BIT = 1,        // Tape stalled
99 	STS_ILIM_BIT = 2,         // Overcurrent
100 	STS_WRITE_EN_BIT = 3,     // Write enabled
101 	STS_HOLE_BIT = 4,         // Hole detected
102 	STS_GAP_BIT = 5,          // Gap detected
103 	STS_TACH_BIT = 6,         // Tachometer tick
104 	STS_READY_BIT = 7         // Ready
105 };
106 
107 // Timers
108 enum {
109 	TAPE_TMR_ID,
110 	HOLE_TMR_ID
111 };
112 
hp_1ma6_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)113 hp_1ma6_device::hp_1ma6_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
114 	: device_t(mconfig , HP_1MA6 , tag , owner , clock)
115 	, m_tape(*this , "drive")
116 {
117 	clear_state();
118 }
119 
reg_w(offs_t offset,uint8_t data)120 void hp_1ma6_device::reg_w(offs_t offset, uint8_t data)
121 {
122 	LOG("WR %u=%02x\n" , offset , data);
123 	switch(offset) {
124 	case 0:
125 		// Control register
126 		start_cmd_exec(data);
127 		break;
128 
129 	case 1:
130 		// Data register
131 		m_data_reg = data;
132 		break;
133 	}
134 }
135 
reg_r(offs_t offset)136 uint8_t hp_1ma6_device::reg_r(offs_t offset)
137 {
138 	uint8_t res = 0;
139 
140 	switch (offset) {
141 	case 0:
142 		// Status register
143 		m_tape->update_speed_pos();
144 		if (m_cmd_state == CMD_IDLE) {
145 			BIT_SET(m_status_reg , STS_READY_BIT);
146 		} else if (m_cmd_state == CMD_STOPPING ||
147 				   m_cmd_state == CMD_FAST_FWD_REV ||
148 				   m_cmd_state == CMD_WR_GAP) {
149 			BIT_CLR(m_status_reg , STS_READY_BIT);
150 		}
151 		if ((m_control_reg & (BIT_MASK(CTL_WRITE_SYNC_BIT) |
152 							  BIT_MASK(CTL_WRITE_GAP_BIT))) != 0 &&
153 			(m_tape->cart_out_r() || !m_tape->wpr_r())) {
154 			BIT_SET(m_status_reg , STS_WRITE_EN_BIT);
155 		} else {
156 			BIT_CLR(m_status_reg , STS_WRITE_EN_BIT);
157 		}
158 		// Gap detection
159 		if (m_cmd_state == CMD_IDLE ||
160 			m_tape->gap_reached(MIN_GAP_SIZE)) {
161 			BIT_SET(m_status_reg , STS_GAP_BIT);
162 		}
163 
164 		res = m_status_reg;
165 		// Clear latching bits
166 		BIT_CLR(m_status_reg , STS_HOLE_BIT);
167 		BIT_CLR(m_status_reg , STS_GAP_BIT);
168 		BIT_CLR(m_status_reg , STS_TACH_BIT);
169 		BIT_CLR(m_status_reg , STS_READY_BIT);
170 		if (!m_tape->cart_out_r()) {
171 			BIT_SET(m_status_reg , STS_CASSETTE_IN_BIT);
172 		}
173 		break;
174 
175 	case 1:
176 		// Data register
177 		res = m_data_reg;
178 		break;
179 	}
180 	LOG("RD %u=%02x\n" , offset , res);
181 	return res;
182 }
183 
WRITE_LINE_MEMBER(hp_1ma6_device::cart_out_w)184 WRITE_LINE_MEMBER(hp_1ma6_device::cart_out_w)
185 {
186 	LOG_DBG("cart_out_w %d\n" , state);
187 	if (state) {
188 		// STS_CASSETTE_IN_BIT is set by reading status register
189 		BIT_CLR(m_status_reg , STS_CASSETTE_IN_BIT);
190 	}
191 }
192 
WRITE_LINE_MEMBER(hp_1ma6_device::hole_w)193 WRITE_LINE_MEMBER(hp_1ma6_device::hole_w)
194 {
195 	if (state) {
196 		LOG_DBG("hole_w\n");
197 		BIT_SET(m_status_reg , STS_HOLE_BIT);
198 	}
199 }
200 
WRITE_LINE_MEMBER(hp_1ma6_device::tacho_tick_w)201 WRITE_LINE_MEMBER(hp_1ma6_device::tacho_tick_w)
202 {
203 	if (state) {
204 		LOG_DBG("tacho_tick_w\n");
205 		BIT_SET(m_status_reg , STS_TACH_BIT);
206 	}
207 }
208 
WRITE_LINE_MEMBER(hp_1ma6_device::motion_w)209 WRITE_LINE_MEMBER(hp_1ma6_device::motion_w)
210 {
211 	if (state) {
212 		LOG_DBG("motion_w @%.6f st=%d\n" , machine().time().as_double() , m_cmd_state);
213 		switch (m_cmd_state) {
214 		case CMD_STOPPING:
215 			if (!m_tape->is_moving()) {
216 				m_cmd_state = CMD_IDLE;
217 			}
218 			break;
219 
220 		case CMD_STARTING:
221 			{
222 				hp_dc100_tape_device::tape_op_t op = hp_dc100_tape_device::OP_IDLE;
223 
224 				switch (m_control_reg & (BIT_MASK(CTL_FAST_BIT) |
225 										 BIT_MASK(CTL_WRITE_DATA_BIT) |
226 										 BIT_MASK(CTL_WRITE_SYNC_BIT) |
227 										 BIT_MASK(CTL_WRITE_GAP_BIT))) {
228 				case 0:
229 					if (m_tape->is_above_threshold()) {
230 						// Start RD
231 						m_cmd_state = CMD_RD_WAIT_SYNC;
232 						op = hp_dc100_tape_device::OP_READ;
233 					}
234 					break;
235 
236 				case BIT_MASK(CTL_FAST_BIT):
237 				case BIT_MASK(CTL_WRITE_GAP_BIT):
238 				case BIT_MASK(CTL_WRITE_GAP_BIT) | BIT_MASK(CTL_WRITE_DATA_BIT):
239 					// Start simple movement
240 					m_cmd_state = CMD_FAST_FWD_REV;
241 					break;
242 
243 				case BIT_MASK(CTL_WRITE_DATA_BIT):
244 					if (m_tape->is_above_threshold()) {
245 						// Start re-writing
246 						m_cmd_state = CMD_WR_WAIT_SYNC;
247 						// Need to achieve sync first (hence RD op)
248 						op = hp_dc100_tape_device::OP_READ;
249 					}
250 					break;
251 
252 				case BIT_MASK(CTL_WRITE_SYNC_BIT):
253 					if (m_tape->is_above_threshold()) {
254 						// Start WR
255 						load_wr_word();
256 						m_cmd_state = CMD_WR_PREAMBLE;
257 						op = hp_dc100_tape_device::OP_WRITE;
258 					}
259 					break;
260 
261 				case BIT_MASK(CTL_WRITE_SYNC_BIT) | BIT_MASK(CTL_WRITE_GAP_BIT):
262 					if (m_tape->is_above_threshold()) {
263 						// Start erasing (gap writing)
264 						m_cmd_state = CMD_WR_GAP;
265 						op = hp_dc100_tape_device::OP_ERASE;
266 					}
267 					break;
268 
269 				default:
270 					break;
271 				}
272 				m_tape->set_op(op);
273 			}
274 			break;
275 
276 		default:
277 			break;
278 		}
279 	}
280 }
281 
WRITE_LINE_MEMBER(hp_1ma6_device::rd_bit_w)282 WRITE_LINE_MEMBER(hp_1ma6_device::rd_bit_w)
283 {
284 	LOG_RW("RD bit %d (st=%d,sr=%02x,i=%u)\n" , state , m_cmd_state , m_data_sr , m_bit_idx);
285 	switch (m_cmd_state) {
286 	case CMD_RD_WAIT_SYNC:
287 		if (state) {
288 			// Got sync (bit is 1)
289 			LOG_RW("RD synced\n");
290 			m_cmd_state = CMD_RD;
291 			m_bit_idx = 7;
292 			m_data_sr = 0;
293 		}
294 		break;
295 
296 	case CMD_RD:
297 		if (state) {
298 			BIT_SET(m_data_sr , m_bit_idx);
299 		}
300 		if (m_bit_idx) {
301 			m_bit_idx--;
302 		} else {
303 			LOG_RW("RD byte %02x\n" , m_data_sr);
304 			m_data_reg = m_data_sr;
305 			m_bit_idx = 7;
306 			m_data_sr = 0;
307 			BIT_SET(m_status_reg , STS_READY_BIT);
308 		}
309 		break;
310 
311 	case CMD_WR_WAIT_SYNC:
312 		m_cmd_state = CMD_WR_PREAMBLE;
313 		load_wr_word();
314 		m_tape->set_op(hp_dc100_tape_device::OP_WRITE);
315 		break;
316 
317 	default:
318 		break;
319 	}
320 }
321 
READ_LINE_MEMBER(hp_1ma6_device::wr_bit_r)322 READ_LINE_MEMBER(hp_1ma6_device::wr_bit_r)
323 {
324 	bool bit = m_cmd_state == CMD_WR_PREAMBLE ? false : BIT(m_data_sr , m_bit_idx);
325 	if (m_bit_idx) {
326 		m_bit_idx--;
327 	} else {
328 		if (m_cmd_state == CMD_WR) {
329 			load_wr_word();
330 		}
331 		m_cmd_state = CMD_WR;
332 		m_bit_idx = 7;
333 	}
334 	LOG_RW("WR bit %d (sr=%02x,i=%u)\n" , bit , m_data_sr , m_bit_idx);
335 	return bit;
336 }
337 
device_add_mconfig(machine_config & config)338 void hp_1ma6_device::device_add_mconfig(machine_config &config)
339 {
340 	HP_DC100_TAPE(config , m_tape , 0);
341 	m_tape->set_acceleration(ACCELERATION);
342 	m_tape->set_set_points(SLOW_SPEED , FAST_SPEED);
343 	m_tape->set_tick_size(TACH_TICK_LEN);
344 	m_tape->set_bits_per_word(16);
345 	m_tape->set_go_threshold(MOVING_THRESHOLD);
346 	m_tape->cart_out().set(FUNC(hp_1ma6_device::cart_out_w));
347 	m_tape->hole().set(FUNC(hp_1ma6_device::hole_w));
348 	m_tape->tacho_tick().set(FUNC(hp_1ma6_device::tacho_tick_w));
349 	m_tape->motion_event().set(FUNC(hp_1ma6_device::motion_w));
350 	m_tape->rd_bit().set(FUNC(hp_1ma6_device::rd_bit_w));
351 	m_tape->wr_bit().set(FUNC(hp_1ma6_device::wr_bit_r));
352 }
353 
device_start()354 void hp_1ma6_device::device_start()
355 {
356 	save_item(NAME(m_data_reg));
357 	save_item(NAME(m_status_reg));
358 	save_item(NAME(m_control_reg));
359 	save_item(NAME(m_bit_idx));
360 	save_item(NAME(m_data_sr));
361 }
362 
device_reset()363 void hp_1ma6_device::device_reset()
364 {
365 	clear_state();
366 }
367 
clear_state()368 void hp_1ma6_device::clear_state()
369 {
370 	m_data_reg = 0;
371 	m_status_reg = 0;
372 	m_control_reg = 0;
373 	m_bit_idx = 0;
374 	m_data_sr = 0;
375 	m_cmd_state = CMD_IDLE;
376 }
377 
load_wr_word()378 void hp_1ma6_device::load_wr_word()
379 {
380 	m_bit_idx = 7;
381 	m_data_sr = m_data_reg;
382 	LOG_RW("WR byte %02x\n" , m_data_sr);
383 	BIT_SET(m_status_reg , STS_READY_BIT);
384 }
385 
start_cmd_exec(uint8_t new_ctl_reg)386 void hp_1ma6_device::start_cmd_exec(uint8_t new_ctl_reg)
387 {
388 	m_control_reg = new_ctl_reg;
389 
390 	if (!BIT(new_ctl_reg , CTL_POWER_UP_BIT) ||
391 		!BIT(new_ctl_reg , CTL_MOTOR_ON_BIT)) {
392 		m_cmd_state = CMD_STOPPING;
393 	} else {
394 		m_cmd_state = CMD_STARTING;
395 	}
396 
397 	m_tape->set_op(hp_dc100_tape_device::OP_IDLE);
398 	m_tape->set_track_no(BIT(m_control_reg , CTL_TRACK_NO_BIT));
399 
400 	hp_dc100_tape_device::tape_speed_t new_speed = hp_dc100_tape_device::SP_STOP;
401 	if (BIT(new_ctl_reg , CTL_POWER_UP_BIT) &&
402 		BIT(new_ctl_reg , CTL_MOTOR_ON_BIT)) {
403 		new_speed = BIT(new_ctl_reg , CTL_FAST_BIT) ? hp_dc100_tape_device::SP_FAST :
404 			hp_dc100_tape_device::SP_SLOW;
405 	}
406 
407 	m_tape->set_speed_setpoint(new_speed , BIT(new_ctl_reg , CTL_DIR_FWD_BIT));
408 	motion_w(1);
409 }
410