GE-115 Emulator
An Emulator of the General Electrics GE-115 computer
alu_dec.c
Go to the documentation of this file.
1
49#include "alu_dec.h"
50#include <string.h>
51
52/* -------------------------------------------------------------------------
53 * Internal helpers
54 * ---------------------------------------------------------------------- */
55
57static int dec_sign_is_neg(uint8_t sign_nibble)
58{
59 return (sign_nibble == 0xB || sign_nibble == 0xD);
60}
61
76static uint8_t dec_get_digit(const uint8_t *mem, uint16_t packed_addr,
77 int packed_bytes, int digit_idx)
78{
79 /* digit 0 is in the high nibble of packed_addr (rightmost byte) */
80 int total_digits = 2 * packed_bytes - 1;
81 if (digit_idx < 0 || digit_idx >= total_digits)
82 return 0;
83
84 /* byte offset from rightmost byte (rightmost = offset 0) */
85 /* digit 0: byte 0 high nibble
86 digit 1: byte 1 high nibble
87 digit 2: byte 1 low nibble
88 digit 3: byte 2 high nibble
89 ...
90 digit 2k-1: byte k high nibble (k >= 1)
91 digit 2k : byte k low nibble (k >= 1)
92 */
93 int byte_off, hi;
94 if (digit_idx == 0) {
95 byte_off = 0;
96 hi = 1;
97 } else {
98 /* digit_idx >= 1 */
99 byte_off = (digit_idx + 1) / 2;
100 hi = ((digit_idx + 1) % 2 == 0) ? 0 : 1; /* odd idx → hi nibble */
101 }
102
103 uint8_t b = mem[(uint16_t)(packed_addr - byte_off)];
104 return hi ? (b >> 4) & 0xF : b & 0xF;
105}
106
108static void dec_set_digit(struct ge *ge, uint16_t packed_addr,
109 int packed_bytes, int digit_idx, uint8_t digit)
110{
111 int total_digits = 2 * packed_bytes - 1;
112 if (digit_idx < 0 || digit_idx >= total_digits)
113 return;
114
115 int byte_off, hi;
116 if (digit_idx == 0) {
117 byte_off = 0;
118 hi = 1;
119 } else {
120 byte_off = (digit_idx + 1) / 2;
121 hi = ((digit_idx + 1) % 2 == 0) ? 0 : 1;
122 }
123
124 uint16_t addr = (uint16_t)(packed_addr - byte_off);
125 uint8_t cur = ge->mem[addr];
126 if (hi)
127 ge_mem_store8(ge, addr, (uint8_t)((cur & 0x0F) | ((digit & 0xF) << 4)));
128 else
129 ge_mem_store8(ge, addr, (uint8_t)((cur & 0xF0) | (digit & 0xF)));
130}
131
133static uint8_t dec_get_sign(const uint8_t *mem, uint16_t packed_addr)
134{
135 return mem[packed_addr] & 0x0F;
136}
137
139static void dec_set_sign(struct ge *ge, uint16_t packed_addr, uint8_t sign)
140{
141 ge_mem_store8(ge, packed_addr,
142 (uint8_t)((ge->mem[packed_addr] & 0xF0) | (sign & 0x0F)));
143}
144
148static void dec_zero_digits(struct ge *ge, uint16_t packed_addr, int packed_bytes)
149{
150 /* rightmost byte: clear high nibble only (preserve sign in low nibble) */
151 ge_mem_store8(ge, packed_addr, (uint8_t)(ge->mem[packed_addr] & 0x0F));
152 for (int i = 1; i < packed_bytes; i++)
153 ge_mem_store8(ge, (uint16_t)(packed_addr - i), 0x00);
154}
155
160static uint8_t dec_result_cc(int is_zero, uint8_t result_sign)
161{
162 if (is_zero)
163 return ALU_CC_ZERO; /* 2 — also zero for negative zero */
164 return dec_sign_is_neg(result_sign) ? ALU_CC_NEG : ALU_CC_POS;
165}
166
172static int bcd_add_digits(uint8_t *result, const uint8_t *a, const uint8_t *b,
173 int n_digits)
174{
175 int carry = 0;
176 for (int i = 0; i < n_digits; i++) {
177 int sum = a[i] + b[i] + carry;
178 carry = sum / 10;
179 result[i] = (uint8_t)(sum % 10);
180 }
181 return carry;
182}
183
188static int bcd_sub_digits(uint8_t *result, const uint8_t *a, const uint8_t *b,
189 int n_digits)
190{
191 int borrow = 0;
192 for (int i = 0; i < n_digits; i++) {
193 int diff = (int)a[i] - (int)b[i] - borrow;
194 if (diff < 0) {
195 diff += 10;
196 borrow = 1;
197 } else {
198 borrow = 0;
199 }
200 result[i] = (uint8_t)diff;
201 }
202 return borrow;
203}
204
209static int bcd_cmp_digits(const uint8_t *a, const uint8_t *b, int n_digits)
210{
211 for (int i = n_digits - 1; i >= 0; i--) {
212 if (a[i] > b[i])
213 return 1;
214 if (a[i] < b[i])
215 return -1;
216 }
217 return 0;
218}
219
221static int bcd_is_zero(const uint8_t *d, int n)
222{
223 for (int i = 0; i < n; i++)
224 if (d[i])
225 return 0;
226 return 1;
227}
228
232static void dec_read_digits(const uint8_t *mem, uint16_t packed_addr,
233 int packed_bytes, uint8_t *digits, int n_digits)
234{
235 for (int i = 0; i < n_digits; i++)
236 digits[i] = dec_get_digit(mem, packed_addr, packed_bytes, i);
237}
238
243static void dec_write_digits(struct ge *ge, uint16_t packed_addr,
244 int packed_bytes, const uint8_t *digits, int n_digits)
245{
246 dec_zero_digits(ge, packed_addr, packed_bytes);
247 for (int i = 0; i < n_digits && i < (2 * packed_bytes - 1); i++)
248 dec_set_digit(ge, packed_addr, packed_bytes, i, digits[i]);
249}
250
251/* -------------------------------------------------------------------------
252 * §5.6.1.1 AP — Add Packed
253 * ---------------------------------------------------------------------- */
254
255void alu_ap(struct ge *ge, uint16_t a, uint8_t alen, uint16_t b, uint8_t blen)
256{
257 int ab = alen + 1; /* bytes in operand 1 */
258 int bb = blen + 1; /* bytes in operand 2 */
259
260 /* Overflow: L1 < L2 (manual §5.6.1.1). The deck (step 0x45) shows the
261 * result field is STILL written (truncated to L1) and cc=0 is reported,
262 * so flag it and fall through rather than returning early. */
263 int len_ovf = (alen < blen);
264
265 int an = 2 * ab - 1; /* digits in operand 1 */
266 int bn = 2 * bb - 1; /* digits in operand 2 */
267
268 uint8_t a_sign = dec_get_sign(ge->mem, a);
269 uint8_t b_sign = dec_get_sign(ge->mem, b);
270 int a_neg = dec_sign_is_neg(a_sign);
271 int b_neg = dec_sign_is_neg(b_sign);
272
273 uint8_t a_d[33] = {0}; /* max 16 bytes = 31 digits */
274 uint8_t b_d[33] = {0};
275 uint8_t r_d[33] = {0};
276
277 dec_read_digits(ge->mem, a, ab, a_d, an);
278 dec_read_digits(ge->mem, b, bb, b_d, bn);
279
280 /* Extend b with leading zeros to length an */
281 /* b_d is already zero-padded since array is zero-initialized */
282
283 uint8_t result_sign;
284 int overflow = 0;
285
286 if (a_neg == b_neg) {
287 /* Same sign: add magnitudes */
288 int carry = bcd_add_digits(r_d, a_d, b_d, an);
289 if (carry) {
290 overflow = 1;
291 /* Result is incomplete per manual — keep truncated digits */
292 }
293 result_sign = a_neg ? 0xD : 0xC;
294 } else {
295 /* Different signs: subtract smaller from larger */
296 int cmp = bcd_cmp_digits(a_d, b_d, an);
297 if (cmp >= 0) {
298 bcd_sub_digits(r_d, a_d, b_d, an);
299 result_sign = a_neg ? 0xD : 0xC;
300 } else {
301 bcd_sub_digits(r_d, b_d, a_d, an);
302 result_sign = b_neg ? 0xD : 0xC;
303 }
304 }
305
306 if (overflow || len_ovf) {
307 /* Overflow: write the truncated low-order digits and PRESERVE the
308 * destination's existing sign nibble (deck step 0x45: 0x45+0x0025 ->
309 * 0x65; step 0x46: 902+136 -> 1038 truncated to 038, sign 5 kept ->
310 * 0x0385). Report cc=0 (the MP-DP/AP NOTE overflow slot). */
311 (void)result_sign;
312 dec_write_digits(ge, a, ab, r_d, an);
314 return;
315 }
316
317 dec_write_digits(ge, a, ab, r_d, an);
318 dec_set_sign(ge, a, result_sign);
319 alu_set_cc(ge, dec_result_cc(bcd_is_zero(r_d, an), result_sign));
320}
321
322/* -------------------------------------------------------------------------
323 * §5.6.1.2 SP — Subtract Packed
324 * ---------------------------------------------------------------------- */
325
326void alu_sp(struct ge *ge, uint16_t a, uint8_t alen, uint16_t b, uint8_t blen)
327{
328 /*
329 * Invert the sign of operand 2 in a temporary copy and call ap.
330 * This is equivalent to the manual description ("proceeds similarly to
331 * AP except for sign processing").
332 * We avoid modifying ge->mem[b], so we toggle the sign in a scratch byte.
333 */
334 uint8_t orig_sign = dec_get_sign(ge->mem, b);
335 uint8_t flipped;
336
337 /* Flip sign for subtraction */
338 if (dec_sign_is_neg(orig_sign))
339 flipped = 0xC; /* was negative → treat as positive */
340 else
341 flipped = 0xD; /* was positive → treat as negative */
342
343 dec_set_sign(ge, b, flipped);
344 alu_ap(ge, a, alen, b, blen);
345 dec_set_sign(ge, b, orig_sign); /* restore operand 2 */
346}
347
348/* -------------------------------------------------------------------------
349 * §5.6.1.3 MP — Multiply Packed
350 * ---------------------------------------------------------------------- */
351
352/*
353 * Algorithm: standard BCD long-multiplication.
354 * Manual constraints (§5.6.1.3):
355 * - blen <= 8 (second operand ≤ 8 bytes = 15 digits + sign)
356 * - blen < alen (multiplicand length < multiplier length)
357 * - Multiplier (op1) must have at least blen+1 leading zero digits
358 * - Overflow → operation NOT performed, CC=0
359 */
360void alu_mp(struct ge *ge, uint16_t a, uint8_t alen, uint16_t b, uint8_t blen)
361{
362 int ab = alen + 1;
363 int bb = blen + 1;
364
365 /* Overflow conditions: the multiplier field must be at most 8 bytes
366 * (bb counts bytes; the deck's step-0x25 MP 10,9 has a 9-byte multiplier
367 * and must overflow), and must be shorter than the result/multiplicand
368 * field. (blen is the 0-indexed length code, so the byte test is on bb.) */
369 if (bb > 8 || blen >= alen)
370 goto overflow;
371
372 int an = 2 * ab - 1;
373 int bn = 2 * bb - 1;
374
375 uint8_t a_d[33] = {0};
376 uint8_t b_d[33] = {0};
377
378 dec_read_digits(ge->mem, a, ab, a_d, an);
379 dec_read_digits(ge->mem, b, bb, b_d, bn);
380
381 uint8_t a_sign = dec_get_sign(ge->mem, a);
382 uint8_t b_sign = dec_get_sign(ge->mem, b);
383
384 /*
385 * Check that the top (an - bn) digits of op1 are zero (the "multiplier
386 * leading zeros" rule: the result replaces the entire op1 field).
387 * Actually the manual says the multiplier must contain at least L2+1
388 * characters equal to zero to the left of the significant part.
389 * L2+1 = bb bytes = 2*bb-1 digits... but that would mean all digits
390 * in the top half must be zero. Interpret: top bn digits of op1 must
391 * be zero.
392 *
393 * UNCERTAINTY: the exact leading-zero count required is not unambiguously
394 * stated in the available OCR. Using: the top bb*2 digits of op1 (i.e.
395 * indices an-1 down to bn) must all be zero.
396 */
397 for (int i = bn; i < an; i++) {
398 if (a_d[i] != 0)
399 goto overflow;
400 }
401
402 /* Multiply: r[i+j] += a_d[i] * b_d[j] (with BCD correction) */
403 uint8_t r_d[33] = {0};
404 for (int i = 0; i < bn; i++) {
405 int carry = 0;
406 for (int j = 0; j < an; j++) {
407 int prod = r_d[i + j] + a_d[j] * b_d[i] + carry;
408 carry = prod / 10;
409 r_d[i + j] = (uint8_t)(prod % 10);
410 }
411 /* carry beyond an+bn digits → overflow */
412 if (i + an < 33)
413 r_d[i + an] += (uint8_t)carry;
414 }
415
416 /* Check result fits in an digits */
417 for (int i = an; i < 33; i++) {
418 if (r_d[i])
419 goto overflow;
420 }
421
422 /* Sign: algebraic product */
423 int a_neg = dec_sign_is_neg(a_sign);
424 int b_neg = dec_sign_is_neg(b_sign);
425 uint8_t result_sign = (a_neg != b_neg) ? 0xD : 0xC;
426
427 dec_write_digits(ge, a, ab, r_d, an);
428 dec_set_sign(ge, a, result_sign);
429 alu_set_cc(ge, dec_result_cc(bcd_is_zero(r_d, an), result_sign));
430 return;
431
432overflow:
433 /* On overflow MP clears the second-operand (V2 = b) field and reports
434 * cc=0 (the MP-DP NOTE table's overflow slot). funktionalcpu step 0x27
435 * checks the cleared b field (CMC 5,0x00E8,0x05A5) after an overflowing
436 * MP 6,5; steps 0x25/0x26 only check the cc. */
437 for (int k = 0; k < bb; k++)
438 ge_mem_store8(ge, (uint16_t)(b - k), 0x00);
440}
441
442/* -------------------------------------------------------------------------
443 * §5.6.1.4 DP — Divide Packed
444 * ---------------------------------------------------------------------- */
445
446/*
447 * Result layout: quotient in leftmost (alen-blen) bytes of op1,
448 * remainder in rightmost (blen+1) bytes.
449 * Manual constraints:
450 * a) alen > blen (L1 > L2)
451 * b) blen <= 7 (L2 <= 7)
452 * c) quotient fits in the (alen-blen) character slot
453 * d) divisor != 0
454 * On overflow: operation NOT performed.
455 *
456 * UNCERTAINTY: the exact digit-slot arithmetic for quotient/remainder
457 * placement is derived from the field-size rules in §5.6.1.4 and §7.2.
458 * Verified: "quotient placed leftmost (L1-L2 positions), remainder
459 * rightmost (L2+1 positions)". Implemented with big-integer BCD division.
460 */
461void alu_dp(struct ge *ge, uint16_t a, uint8_t alen, uint16_t b, uint8_t blen)
462{
463 int ab = alen + 1;
464 int bb = blen + 1;
465
466 /* Overflow checks a, b */
467 if (alen <= blen || blen > 7) {
469 return;
470 }
471
472 int an = 2 * ab - 1;
473 int bn = 2 * bb - 1;
474
475 uint8_t a_d[33] = {0};
476 uint8_t b_d[33] = {0};
477
478 dec_read_digits(ge->mem, a, ab, a_d, an);
479 dec_read_digits(ge->mem, b, bb, b_d, bn);
480
481 uint8_t a_sign = dec_get_sign(ge->mem, a);
482 uint8_t b_sign = dec_get_sign(ge->mem, b);
483
484 /* Overflow check d: divisor != 0 */
485 if (bcd_is_zero(b_d, bn)) {
487 return;
488 }
489
490 /*
491 * Perform BCD long division: dividend a_d[an-1..0] / divisor b_d[bn-1..0]
492 * Both arrays have [0]=rightmost (least significant) digit.
493 * We work most-significant-first, so reverse indices.
494 *
495 * Quotient slot: qn = 2*(alen-blen)-1 digits (leftmost bytes count = alen-blen)
496 * Remainder slot: rn = bn digits
497 */
498 int q_bytes = alen - blen; /* bytes for quotient field */
499 int qn = 2 * q_bytes - 1; /* max quotient digits */
500
501 /*
502 * Division by value. The operand fields are decimal; convert to integers,
503 * divide, and convert the quotient/remainder back to BCD (least-significant
504 * digit first) for placement. unsigned __int128 covers the full field
505 * width (up to ~31 digits) without overflow.
506 */
507 unsigned __int128 dividend = 0, divisor = 0;
508 for (int i = an - 1; i >= 0; i--) dividend = dividend * 10 + a_d[i];
509 for (int i = bn - 1; i >= 0; i--) divisor = divisor * 10 + b_d[i];
510 /* divisor != 0 already verified above */
511
512 unsigned __int128 quotient = dividend / divisor;
513 unsigned __int128 remainder = dividend % divisor;
514
515 /* Overflow check c: quotient must fit in qn digits */
516 unsigned __int128 qcap = 1;
517 for (int i = 0; i < qn; i++) qcap *= 10;
518 if (quotient >= qcap) {
520 return;
521 }
522
523 /* Convert to least-significant-digit-first BCD arrays */
524 uint8_t q_lsf[33] = {0};
525 uint8_t r_lsf[33] = {0};
526 for (int i = 0; i < qn; i++) { q_lsf[i] = (uint8_t)(quotient % 10); quotient /= 10; }
527 for (int i = 0; i < bn; i++) { r_lsf[i] = (uint8_t)(remainder % 10); remainder /= 10; }
528
529 /* Signs */
530 int a_neg = dec_sign_is_neg(a_sign);
531 int b_neg = dec_sign_is_neg(b_sign);
532 uint8_t q_sign = (a_neg != b_neg) ? 0xD : 0xC;
533 uint8_t r_sign = a_neg ? 0xD : 0xC; /* remainder sign = dividend sign */
534
535 /*
536 * Write quotient into leftmost q_bytes of op1:
537 * Quotient field address = a - bb (bb bytes from rightmost = offset to quotient's rightmost)
538 * Quotient rightmost byte address = (a - bb)
539 * (rightmost byte of full field is a; rightmost byte of quotient sub-field
540 * is a - bb since remainder occupies bb bytes at the right)
541 */
542 uint16_t q_addr = (uint16_t)(a - bb);
543 dec_zero_digits(ge, q_addr, q_bytes);
544 dec_write_digits(ge, q_addr, q_bytes, q_lsf, qn);
545 dec_set_sign(ge, q_addr, q_sign);
546
547 /* Write remainder into rightmost bb bytes of op1 */
548 uint16_t r_addr = a;
549 dec_zero_digits(ge, r_addr, bb);
550 dec_write_digits(ge, r_addr, bb, r_lsf, bn);
551 dec_set_sign(ge, r_addr, r_sign);
552
553 /* CC reflects quotient (most significant result) */
554 alu_set_cc(ge, dec_result_cc(bcd_is_zero(q_lsf, qn), q_sign));
555}
556
557/* -------------------------------------------------------------------------
558 * §5.6.1.6 CMP — Compare Packed
559 * ---------------------------------------------------------------------- */
560
561void alu_cmp(struct ge *ge, uint16_t a, uint8_t alen, uint16_t b, uint8_t blen)
562{
563 /* Overflow if L1 < L2 */
564 if (alen < blen) {
566 return;
567 }
568
569 int ab = alen + 1;
570 int bb = blen + 1;
571 int an = 2 * ab - 1;
572 int bn = 2 * bb - 1;
573
574 uint8_t a_d[33] = {0};
575 uint8_t b_d[33] = {0};
576
577 dec_read_digits(ge->mem, a, ab, a_d, an);
578 dec_read_digits(ge->mem, b, bb, b_d, bn);
579
580 uint8_t a_sign = dec_get_sign(ge->mem, a);
581 uint8_t b_sign = dec_get_sign(ge->mem, b);
582 int a_neg = dec_sign_is_neg(a_sign);
583 int b_neg = dec_sign_is_neg(b_sign);
584
585 /* Compare algebraically; positive zero == negative zero */
586 int a_zero = bcd_is_zero(a_d, an);
587 int b_zero = bcd_is_zero(b_d, bn);
588
589 if (a_zero && b_zero) {
591 return;
592 }
593
594 if (a_neg != b_neg) {
595 /* Different signs: negative < positive */
597 return;
598 }
599
600 /* Same sign: compare magnitudes */
601 int mag_cmp = bcd_cmp_digits(a_d, b_d, an);
602 if (mag_cmp == 0) {
604 } else if (a_neg) {
605 /* Both negative: larger magnitude = smaller value */
606 alu_set_cc(ge, mag_cmp > 0 ? ALU_CC_LOW : ALU_CC_HIGH);
607 } else {
608 alu_set_cc(ge, mag_cmp > 0 ? ALU_CC_HIGH : ALU_CC_LOW);
609 }
610}
611
612/* -------------------------------------------------------------------------
613 * §5.6.1.5 MVP — Move Packed
614 * ---------------------------------------------------------------------- */
615
616void alu_mvp(struct ge *ge, uint16_t a, uint8_t alen, uint16_t b, uint8_t blen)
617{
618 int ab = alen + 1;
619 int bb = blen + 1;
620 int an = 2 * ab - 1;
621 int bn = 2 * bb - 1;
622
623 uint8_t b_d[33] = {0};
624 dec_read_digits(ge->mem, b, bb, b_d, bn);
625 uint8_t b_sign = dec_get_sign(ge->mem, b);
626
627 /* Overflow if L1 < L2 — but operation IS performed with incomplete result */
628 int overflow = (alen < blen);
629
630 dec_zero_digits(ge, a, ab);
631
632 /* Copy as many digits as fit in op1; if bn > an the excess (left) are dropped */
633 int copy_n = (bn < an) ? bn : an;
634 for (int i = 0; i < copy_n; i++)
635 dec_set_digit(ge, a, ab, i, b_d[i]);
636
637 /* MVP moves the source sign nibble VERBATIM (it is a move, not an
638 * arithmetic op) — deck step 0x4D moves source sign 0xA and expects 0xA,
639 * not a normalized 0xC. */
640 uint8_t result_sign = b_sign;
641 dec_set_sign(ge, a, result_sign);
642
643 if (overflow) {
645 return;
646 }
647
648 uint8_t r_d[33] = {0};
649 dec_read_digits(ge->mem, a, ab, r_d, an);
650 alu_set_cc(ge, dec_result_cc(bcd_is_zero(r_d, an), result_sign));
651}
652
653/* -------------------------------------------------------------------------
654 * §5.5.3.4 PK — Pack (no sign processing)
655 * ---------------------------------------------------------------------- */
656
657/*
658 * Zoned operand: one digit per byte; digit = low nibble.
659 * Packed operand: two digits per byte; rightmost byte high nibble = last digit,
660 * low nibble = sign (NOT processed by PK).
661 * The manual says: "loads in packed form the 2L+2 less significant halves
662 * (low nibbles) of the characters of the second operand."
663 * "Operation proceeds from left to right."
664 *
665 * Source length: 2*dlen+2 bytes of zoned source (2L+2 digits; we use all of them).
666 * Destination: dlen+1 packed bytes.
667 * Addressing: dst = rightmost byte of destination packed field.
668 * src = rightmost byte of zoned source field (manual: "address of
669 * rightmost position").
670 *
671 * NOTE: the manual says L+1 for destination and 2L+2 source characters
672 * where L = dlen. So src occupies slen+1 zoned bytes; we pack the low nibbles
673 * of those into the destination, taking 2*(dlen+1) digits = dlen+1 packed bytes.
674 * If source is shorter (slen < 2*dlen+1) we left-pad with zeros.
675 * If source is longer (slen > 2*dlen+1) we use only the rightmost 2*(dlen+1) chars.
676 *
677 * Sign nibble: left as-is in destination (not altered by PK per §5.5.3.4).
678 *
679 * UNCERTAINTY: PK/UPK addressing — manual is slightly ambiguous on whether
680 * src address is leftmost or rightmost for zoned format. §5.6.2 PKS/UPKS
681 * confirms "address of rightmost position"; we use the same convention for
682 * PK/UPK.
683 */
684void alu_pk(struct ge *ge, uint16_t dst, uint8_t dlen, uint16_t src, uint8_t slen)
685{
686 int db = dlen + 1; /* destination packed bytes (= L+1) */
687 (void)slen; /* source is 2L+2 zoned chars, derived from dlen */
688
689 /*
690 * Re-derived from the PK microcode (flowchart "PK Dalla Fase Alfa", states
691 * 64|65 -> 60-63 -> 40-43, dwg timing charts). Both pointers INCREMENT
692 * (V2+1->V2 source, V1+1->V1 dest), so PK runs from the given (leftmost,
693 * most-significant) address UPWARD, not from a rightmost byte.
694 *
695 * The accumulate state (60-63) does MEM->RO, then routes the source digit
696 * (RO low nibble) to NI4 (high nibble) on one SA00 phase and NI3 (low
697 * nibble) on the alternate phase; the write state (40-43) does RO->MEM.
698 * So two consecutive zoned digits fill one packed byte: 1st (more
699 * significant) -> high nibble, 2nd -> low nibble. PK does NOT process a
700 * sign, so every byte holds two full digits (2L+2 digits total).
701 */
702 int total_digits = 2 * db; /* 2 digits/byte, no sign nibble */
703
704 for (int d = 0; d < total_digits; d++) {
705 uint8_t digit = ge->mem[(uint16_t)(src + d)] & 0x0F; /* read upward */
706 uint16_t daddr = (uint16_t)(dst + d / 2); /* write upward */
707 uint8_t cur = ge->mem[daddr];
708 if (d & 1)
709 cur = (uint8_t)((cur & 0xF0) | digit); /* 2nd digit -> low */
710 else
711 cur = (uint8_t)((cur & 0x0F) | (uint8_t)(digit << 4)); /* 1st digit -> high */
712 ge_mem_store8(ge, daddr, cur);
713 }
714}
715
716/* -------------------------------------------------------------------------
717 * §5.5.3.5 UPK — Unpack (no sign processing, preserve existing zones)
718 * ---------------------------------------------------------------------- */
719
720/*
721 * UPK is the inverse of PK and uses the same "process upward from the given
722 * (leftmost) address" convention (UPK microcode flowchart, states 64|65 ->
723 * 60-63, pointers increment). Each packed source byte holds two digits and is
724 * expanded into two zoned destination bytes: high nibble -> first (more
725 * significant) zoned byte, low nibble -> second. No sign is processed, so a
726 * source of slen+1 packed bytes yields 2*(slen+1) zoned digits. The
727 * destination's existing zone (high nibble) is preserved; only the low nibble
728 * (digit) is written. Validated against funktionalcpu steps 0x1D/0x1E.
729 */
730void alu_upk(struct ge *ge, uint16_t dst, uint8_t dlen, uint16_t src, uint8_t slen)
731{
732 (void)dlen; /* dest length is 2*(slen+1), derived from the source */
733 int sb = slen + 1; /* packed source bytes */
734 int total = 2 * sb; /* zoned destination bytes (2 digits per source byte) */
735
736 for (int d = 0; d < total; d++) {
737 uint8_t sbyte = ge->mem[(uint16_t)(src + d / 2)]; /* read upward */
738 uint8_t digit = (d & 1) ? (uint8_t)(sbyte & 0x0F) /* 2nd: low nibble */
739 : (uint8_t)((sbyte >> 4) & 0x0F);/* 1st: high nibble */
740 uint16_t daddr = (uint16_t)(dst + d); /* write upward */
741 /* Preserve high nibble (zone) of destination, put digit in low nibble */
742 ge_mem_store8(ge, daddr, (uint8_t)((ge->mem[daddr] & 0xF0) | digit));
743 }
744}
745
746/* -------------------------------------------------------------------------
747 * §5.6.2.1 PKS — Pack with Sign
748 * ---------------------------------------------------------------------- */
749
750/*
751 * Like PK but:
752 * 1. Source right nibble zone is examined: if 0xA → negative sign (1101 = D)
753 * any other → positive sign (1100 = C).
754 * 2. Generated sign is written into packed result's sign nibble.
755 * 3. CC is set.
756 *
757 * "Packing interprets the combination 1010 in the zone of the first character
758 * to the right of the first operand as a minus sign and any other combination
759 * as a plus sign." (§5.6.2.1)
760 * "First character to the right of the first operand" means the rightmost
761 * source zoned byte.
762 */
763void alu_pks(struct ge *ge, uint16_t dst, uint8_t dlen, uint16_t src, uint8_t slen)
764{
765 int db = dlen + 1;
766 int sb = slen + 1;
767 int total_digits = 2 * db - 1;
768
769 /* Determine sign from zone of rightmost source byte */
770 uint8_t src_zone = (ge->mem[src] >> 4) & 0x0F;
771 uint8_t result_sign = (src_zone == 0xA) ? 0xD : 0xC;
772
773 /* Pack digits (same as PK) */
774 for (int d = 0; d < total_digits; d++) {
775 uint8_t digit;
776 if (d < sb) {
777 digit = ge->mem[(uint16_t)(src - d)] & 0x0F;
778 } else {
779 digit = 0;
780 }
781 dec_set_digit(ge, dst, db, d, digit);
782 }
783
784 dec_set_sign(ge, dst, result_sign);
785
786 /* CC: check if all digits are zero */
787 uint8_t r_d[33] = {0};
788 dec_read_digits(ge->mem, dst, db, r_d, total_digits);
789 int is_zero = bcd_is_zero(r_d, total_digits);
790
791 if (is_zero)
792 alu_set_cc(ge, ALU_CC_ZERO); /* = 2 */
793 else
795}
796
797/* -------------------------------------------------------------------------
798 * §5.6.2.2 UPKS — Unpack with Sign
799 * ---------------------------------------------------------------------- */
800
801/*
802 * "The unpacking always generates the 0100 zone on all the digits."
803 * Zone = 0x4 on every result byte.
804 * CC set to reflect sign and value of packed source.
805 */
806void alu_upks(struct ge *ge, uint16_t dst, uint8_t dlen, uint16_t src, uint8_t slen)
807{
808 int sb = slen + 1;
809 int sn = 2 * sb - 1;
810 int db = dlen + 1;
811
812 for (int i = 0; i < db; i++) {
813 uint8_t digit;
814 if (i < sn) {
815 digit = dec_get_digit(ge->mem, src, sb, i) & 0x0F;
816 } else {
817 digit = 0;
818 }
819 uint16_t daddr = (uint16_t)(dst - i);
820 /* Zone is always 0x4 */
821 ge_mem_store8(ge, daddr, (uint8_t)(0x40 | digit));
822 }
823
824 /* CC reflects sign and value of packed source operand */
825 uint8_t src_sign = dec_get_sign(ge->mem, src);
826 uint8_t s_d[33] = {0};
827 dec_read_digits(ge->mem, src, sb, s_d, sn);
828 int is_zero = bcd_is_zero(s_d, sn);
829
830 if (is_zero)
832 else
834}
835
836/* -------------------------------------------------------------------------
837 * §5.5.3.6 EDT — Edit
838 * ---------------------------------------------------------------------- */
839
840/*
841 * Pattern control codes (verified from §5.5.3.6 bit patterns):
842 * SST = 0x20 (00100000) — digit substitute, continues zero suppression
843 * TSZ = 0x21 (00100001) — digit substitute, terminates zero suppression
844 * RSZ = 0x22 (00100010) — reset (start) zero suppression condition,
845 * replaces with fill char, pointer doesn't advance
846 *
847 * First byte of pattern = fill character.
848 * Source: unpacked decimal (one digit per byte, low nibble), addressed via
849 * src = RIGHTMOST byte; we advance LEFT (decrement address) to walk
850 * through digits.
851 *
852 * "Operation proceeds from left to right" through the pattern.
853 * We maintain a pointer into the source field, advancing it when a digit
854 * is consumed.
855 *
856 * CC (§5.5.3.7):
857 * FA04=1 FA05=0 (CC=2): operation ended in zero suppression condition
858 * FA04=1 FA05=1 (CC=3): operation ended in no-zero-suppression condition
859 * (FA04=0 cases listed as "not possible")
860 *
861 * UNCERTAINTY: The source field is described as "normally unpacked decimal"
862 * (§5.5.3.6). The source addressing — whether src points to the rightmost
863 * or leftmost byte — is inferred from context (digits are consumed left to
864 * right as pattern is scanned left to right, and the source pointer advances
865 * forward through memory). We treat src as the address of the FIRST (leftmost)
866 * digit of the source, incrementing as we consume digits.
867 * ALSO: "plen" is the pattern length in bytes (auxiliary character); the
868 * source field extent is implicitly defined by digit-select chars in pattern.
869 */
870void alu_edt(struct ge *ge, uint16_t pattern, uint8_t plen, uint16_t src)
871{
872 if (plen == 0)
873 return;
874
875 uint8_t fill = ge->mem[pattern]; /* first pattern byte = fill char */
876 int zero_suppress = 1; /* starts in zero suppression condition */
877 uint16_t src_ptr = src; /* current source byte pointer */
878
879 for (int i = 0; i < plen; i++) {
880 uint16_t pat_addr = (uint16_t)(pattern + i);
881 uint8_t pc = ge->mem[pat_addr];
882
883 if (pc == 0x20) {
884 /* SST: substitute digit */
885 uint8_t digit = ge->mem[src_ptr] & 0x0F;
886 if (zero_suppress) {
887 if (digit == 0) {
888 ge_mem_store8(ge, pat_addr, fill);
889 } else {
890 ge_mem_store8(ge, pat_addr, ge->mem[src_ptr]); /* keep digit byte */
891 zero_suppress = 0;
892 }
893 } else {
894 ge_mem_store8(ge, pat_addr, ge->mem[src_ptr]);
895 }
896 src_ptr++;
897 } else if (pc == 0x21) {
898 /* TSZ: digit substitute + terminate zero suppression */
899 ge_mem_store8(ge, pat_addr, ge->mem[src_ptr]);
900 zero_suppress = 0;
901 src_ptr++;
902 } else if (pc == 0x22) {
903 /* RSZ: reset zero suppression (restore fill), pointer stays */
904 ge_mem_store8(ge, pat_addr, fill);
905 zero_suppress = 1;
906 /* source pointer does NOT advance */
907 } else {
908 /* Insertion character */
909 if (zero_suppress) {
910 ge_mem_store8(ge, pat_addr, fill);
911 /* pointer does NOT advance */
912 } else {
913 /* leave insertion char in place */
914 /* pointer does NOT advance */
915 }
916 }
917 }
918
919 /* CC: 2 if still in zero suppression, 3 if zero suppression lifted */
920 alu_set_cc(ge, zero_suppress ? ALU_CC_ZERO : ALU_CC_POS);
921}
922
923/*
924 * Arithmetic unit, decimal mode.
925 *
926 * With CI45 low (arithmetic) and CI46 high, the UA works in BCD -- the
927 * function code cp06 ch.087 gives as UCO01, decimal add. Subtraction shares
928 * the SUBTRACT line with binary (UCO11): decimal subtract and binary subtract
929 * are indistinguishable in the function code, because in BCD a subtraction IS
930 * an addition of the complement. So this mirrors the binary path exactly --
931 * complement the addend, carry in URPE -- but complements to NINE and carries
932 * per digit rather than per byte.
933 *
934 * CI50 ("opera solo UA1") inhibits UZE71/UZE81, the inter-zone enables, so
935 * only the low unit participates: one digit instead of two. The high quartet
936 * is passed through from RO, which is the least-surprising reading of an
937 * inert upper zone -- the sheets do not say what it drives.
938 */
939uint8_t ge_ua_decimal(uint8_t bo, uint8_t ro, int subtract, int one_digit,
940 uint8_t *carry)
941{
942 unsigned c = *carry ? 1 : 0;
943 unsigned out = 0;
944 int digits = one_digit ? 1 : 2;
945
946 for (int i = 0; i < digits; i++) {
947 unsigned a = (ro >> (4 * i)) & 0xf;
948 unsigned b = (bo >> (4 * i)) & 0xf;
949 unsigned d = a + (subtract ? 9 - b : b) + c;
950
951 if (d > 9) { d -= 10; c = 1; } else { c = 0; }
952 out |= (d & 0xf) << (4 * i);
953 }
954
955 if (one_digit)
956 out |= ro & 0xf0;
957
958 *carry = (uint8_t)c;
959 return (uint8_t)out;
960}
void alu_set_cc(struct ge *ge, uint8_t cc)
Definition alu_cc.c:4
@ ALU_CC_EQUAL
Definition alu_cc.h:52
@ ALU_CC_POS
Definition alu_cc.h:53
@ ALU_CC_OVF
Definition alu_cc.h:54
@ ALU_CC_ZERO
Definition alu_cc.h:52
@ ALU_CC_NEG
Definition alu_cc.h:51
@ ALU_CC_LOW
Definition alu_cc.h:51
@ ALU_CC_HIGH
Definition alu_cc.h:53
uint8_t ge_ua_decimal(uint8_t bo, uint8_t ro, int subtract, int one_digit, uint8_t *carry)
Arithmetic unit in decimal mode – one byte (two BCD digits), or one digit when CI50 restricts the uni...
Definition alu_dec.c:939
void alu_mvp(struct ge *ge, uint16_t a, uint8_t alen, uint16_t b, uint8_t blen)
MVP 0xE8 Move Packed: op1 = op2 (sign preserved from op2); CC set.
Definition alu_dec.c:616
void alu_sp(struct ge *ge, uint16_t a, uint8_t alen, uint16_t b, uint8_t blen)
SP 0xEB Subtract Packed: op1 = op1 - op2; CC set.
Definition alu_dec.c:326
static uint8_t dec_result_cc(int is_zero, uint8_t result_sign)
Compute the CC value from a sign and whether the result is zero.
Definition alu_dec.c:160
static int bcd_cmp_digits(const uint8_t *a, const uint8_t *b, int n_digits)
Compare two unsigned BCD digit arrays (big-endian: [n-1]=most-significant).
Definition alu_dec.c:209
void alu_upks(struct ge *ge, uint16_t dst, uint8_t dlen, uint16_t src, uint8_t slen)
UPKS 0xEF Unpack with Sign: packed op2 → zoned op1; zone always 0x4.
Definition alu_dec.c:806
static void dec_write_digits(struct ge *ge, uint16_t packed_addr, int packed_bytes, const uint8_t *digits, int n_digits)
Write a digit array back into a packed field.
Definition alu_dec.c:243
void alu_upk(struct ge *ge, uint16_t dst, uint8_t dlen, uint16_t src, uint8_t slen)
UPK 0xD8 Unpack: packed op2 → zoned op1 (no sign processing; zone of each result byte is taken from t...
Definition alu_dec.c:730
static void dec_zero_digits(struct ge *ge, uint16_t packed_addr, int packed_bytes)
Clear all digits (not sign) in a packed field to zero.
Definition alu_dec.c:148
static void dec_set_digit(struct ge *ge, uint16_t packed_addr, int packed_bytes, int digit_idx, uint8_t digit)
Set a single digit in a packed field (same indexing as dec_get_digit).
Definition alu_dec.c:108
static int bcd_add_digits(uint8_t *result, const uint8_t *a, const uint8_t *b, int n_digits)
BCD add two digit arrays (right-to-left, big-endian [0]=rightmost).
Definition alu_dec.c:172
void alu_pks(struct ge *ge, uint16_t dst, uint8_t dlen, uint16_t src, uint8_t slen)
PKS 0xEE Pack with Sign: zoned op2 → packed op1; sign from zone of rightmost source byte (zone 0xA → ...
Definition alu_dec.c:763
static int bcd_is_zero(const uint8_t *d, int n)
Returns 1 if digit array is all zeros.
Definition alu_dec.c:221
void alu_mp(struct ge *ge, uint16_t a, uint8_t alen, uint16_t b, uint8_t blen)
MP 0xEC Multiply Packed: op1 = op1 * op2; CC set.
Definition alu_dec.c:360
static int bcd_sub_digits(uint8_t *result, const uint8_t *a, const uint8_t *b, int n_digits)
BCD subtract b from a (right-to-left).
Definition alu_dec.c:188
static void dec_set_sign(struct ge *ge, uint16_t packed_addr, uint8_t sign)
Set the sign nibble of a packed field.
Definition alu_dec.c:139
static uint8_t dec_get_digit(const uint8_t *mem, uint16_t packed_addr, int packed_bytes, int digit_idx)
Extract a single decimal digit from a packed field.
Definition alu_dec.c:76
static uint8_t dec_get_sign(const uint8_t *mem, uint16_t packed_addr)
Get the sign nibble of a packed field.
Definition alu_dec.c:133
void alu_cmp(struct ge *ge, uint16_t a, uint8_t alen, uint16_t b, uint8_t blen)
CMP 0xE9 Compare Packed (algebraic, no operand change); CC set.
Definition alu_dec.c:561
static int dec_sign_is_neg(uint8_t sign_nibble)
Returns 1 if the sign nibble represents a negative value.
Definition alu_dec.c:57
static void dec_read_digits(const uint8_t *mem, uint16_t packed_addr, int packed_bytes, uint8_t *digits, int n_digits)
Read a packed field into a digit array (right-to-left, [0]=rightmost digit).
Definition alu_dec.c:232
void alu_pk(struct ge *ge, uint16_t dst, uint8_t dlen, uint16_t src, uint8_t slen)
PK 0xDA Pack: zoned op2 → packed op1 (no sign processing).
Definition alu_dec.c:684
void alu_edt(struct ge *ge, uint16_t pattern, uint8_t plen, uint16_t src)
EDT 0xDE Edit packed source into pattern at op1.
Definition alu_dec.c:870
void alu_ap(struct ge *ge, uint16_t a, uint8_t alen, uint16_t b, uint8_t blen)
AP 0xEA Add Packed: op1 = op1 + op2; CC set.
Definition alu_dec.c:255
void alu_dp(struct ge *ge, uint16_t a, uint8_t alen, uint16_t b, uint8_t blen)
DP 0xED Divide Packed: op1[left L1-L2 chars] = quotient, op1[right L2+1 chars] = remainder; CC set.
Definition alu_dec.c:461
GE-130 packed/signed decimal ALU helpers.
void ge_mem_store8(struct ge *ge, uint16_t addr, uint8_t val)
Store a byte with generated odd parity + mark-written (for the hybrid ALU/SS write paths that write g...
Definition ge.c:317
The entire state of the emulated system, including registers, memory, peripherals and timings.
Definition ge.h:172
uint8_t mem[MEM_SIZE]
The memory of the emulated system.
Definition ge.h:695