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