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