# adder-mod.m4 # Self-modifying adder code # Use { and } instead of ` and ' to quote changequote({, }) # === Control structure definitions =========================================== # These definiions help clean up repetitive portions of the code # Usage: for(var, init_value, final_value, expansion) # Sets 'var' equal to 'init_value' and repeatedly increments or decrements # 'var'. One copy of 'expansion' is emitted during each iteration. Iteration # terminates when 'var' equals 'final_value', and 'expansion' is not iterated # for this final case. define(for, {pushdef({$1}, {$2})_for({$1}, {$2}, {$3}, {$4}, ifelse(eval({$2} < {$3}), 1, 1, -1))popdef({$1})}) # Internal helper macro define(_for, {ifelse($1, {$3}, , {$4{}define({$1}, eval($1 + $5))_for({$1}, {$2}, {$3}, {$4}, {$5})})}) # === Pseudoinstruction definitions =========================================== # These definitions make it a little easier to determine the intent of the code # Usage: jr(nreg) # Branches to the address indicated by 'nreg'. define(jr, add($pc, {$1}, $zero)) # Usage: ji(imm16) # Branches to the address indicated by 'imm16'. define(ji, addi($pc, $zero, {$1})) # Usage: jri(imm16) # Branches to the address indicated by 'nreg' + 'imm16'. define(jri, addi($pc, {$1}, {$2})) # Usage: clear(nreg) # Sets 'nreg' equal to 0x00000000. define(clear, add({$1}, $zero, $zero)) # === Miscellaneous definitions =============================================== # More helpful stuff... # Usage: array(name, width) # Reserves 'width' words for an array named 'name'. define(array, pushdef({i})$1:{for}({i}, 1, $2, 0xdeadbeef;) 0xdeadbeef{}popdef({i})) # === Initial values ========================================================== # Set boot-time initial values for memory and registers # These two values help to flush out bugs in the code; 0xdeadbeef shows up in a # hurry in the vm register and memory dumps .nreg: 0xdeadbeef; # Initial value for normal registers .breg: 0xf; # Initial value for BCD registers # Now set some registers to have *useful* values .zero: 0; # $zero = 0, duh .xw: 0; # $xw = 0 (sets BCD x = 0) .yw: 0; # $yw = 0 (sets BCD y = 0) .cf: 0; # $cf = 0 (no prior add to carry from) .c0: jri($r1, @asym_add); # $c0 = jump into asymmetric addition .c1: disp($zw, $zero, @bcd_z); # $c1 = display result and reboot .pc: @entry; # $pc = entry point # === Entry point ============================================================= # Get and process user input # ----------------------------------------------------------------------------- entry: # Call on the virtual machine to get user input read($r0, $r1, @ascii_in); # Get command beqi($r0, 2, @do_add); # If command is ADD ARRAYS, branch # === Command: IN ARRAY k numeric=string ====================================== # Convert array k from ASCII to BCD # ----------------------------------------------------------------------------- do_in: # Determine how many ASCII words to convert cshri($r2, $r1, 3); # $r2 = num of word pairs, rounded up subi($r2, $r2, 16); # $r2 = num of ASCII word pairs to skip adds($r2, $r2, 1); # $r2 = number of instructions to skip # Determine which BCD array to load beqi($r0, 1, @use_y); # If using BCD array y, branch # Prepare to initialize BCD array x cshri($xw, $r1, 3); # $xw = number of BCD words in x addi($r3, $zero, @bcd_x); # $r3 = address of BCD x jri($r2, @to_bcd); # Start converting # ----------------------------------------------------------------------------- use_y: # Prepare to initialize BCD array y cshri($yw, $r1, 3); # $yw = number of BCD words in y addi($r3, $zero, @bcd_y); # $r3 = address of BCD y jri($r2, @to_bcd); # Start converting # ----------------------------------------------------------------------------- to_bcd: # Starting with the most-significant ASCII word, convert to BCD for(i, 0, 16, { la($uh, $zero, @ascii_in + i * 2); # Load more-significant four digits la($ul, $zero, @ascii_in + i * 2 + 1); # Load least-significant four digits sb($u, $r3, 15 - i); # Store as packed BCD }) ji(@entry); # Go get more input # === Command: ADD ARRAYS ===================================================== # Add the two arrays and display the result # ----------------------------------------------------------------------------- do_add: # Set $r0 to the number of symmetric words that need adding min($r0, $xw, $yw); # $r0 = number of symmetric words # Set $r1 to the number of symmetric instructions to execute (equal to the # number of asymmetric instructions to skip) add($r1, $r0, $zero); # $r1 = number of symmetric words adds($r1, $r0, 1); # $r1 = symmetric words * 3 adds($r1, $r0, 3); # $r1 = symmetic words * 11 # Determine what kind of symmetry is present bgt($xw, $yw, @case_2); # Branch to @case_2 if $xw > $yw bgt($yw, $xw, @case_3); # Branch to @case_3 if $yw > $xw # ----------------------------------------------------------------------------- # Case 1: BCD x and y have the same number of words case_1: # Modify symmetric addition code to call disp when finished add($zw, $xw, $zero); # $zw = resultant number of words sw($c1, $r1, @sym_add); # Skip unnecessary symmetric adds ji(@sym_add); # Let's do it # ----------------------------------------------------------------------------- # Case 2: BCD x has more words than BCD y case_2: # Arrange for asymmetric addition code to call disp when finished add($r2, $xw, $zero); # $r2 = 1 * total number of words adds($r2, $xw, 1); # $r2 = 3 * total number of words adds($r2, $xw, 3); # $r2 = 11 * total number of words # Arrange for copy code to call disp when finished add($r3, $xw, $xw); # $r3 = 2 * total number of words # Configure asymmetric/copy code to use BCD x addi($r4, $zero, @bcd_x); # $r4 = address of larger BCD array add($zw, $xw, $zero); # $zw = resultant number of words # Next step... ji(@fix_add); # Now apply the above modifications # ----------------------------------------------------------------------------- # Case 3: BCD y has more words than BCD x case_3: # Arrange for asymmetric addition code to call disp when finished add($r2, $yw, $zero); # $r2 = 1 * total number of words adds($r2, $yw, 1); # $r2 = 3 * total number of words adds($r2, $yw, 3); # $r2 = 11 * total number of words # Arrange for copy code to call disp when finished add($r3, $yw, $yw); # $r3 = 2 * total number of words # Configure asymmetric/copy code to use BCD x addi($r4, $zero, @bcd_y); # $r4 = address of larger BCD array add($zw, $yw, $zero); # $zw = resultant number of words # ----------------------------------------------------------------------------- fix_add: # Tweak! sw($c0, $r1, @sym_add); # Skip unnecessary symmetric adds sw($c1, $r2, @asym_add); # Skip unnecessary asymmetric adds sw($c1, $r3, @copy_add); # Skip unnecessary asymmetric copies # ----------------------------------------------------------------------------- # Symmetric addition of all digits in BCD x and BCD y sym_add: # Repeat for all digits in number; some of these iterations will be skipped for(i, 0, 16, { lb($u, $zero, @bcd_x + i); # Grab a word from BCD x lb($v, $zero, @bcd_y + i); # Grab a word from BCD y for(j, 0, 8, {format({badd($u%d, $u%d, $v%d);}, j, j, j)}) # Add BCD digits sb($u, $zero, @bcd_z + i); # Store result in BCD z }) # Must be done adding if this point if ever reached disp($zw, $zero, @bcd_z); # Display result # ----------------------------------------------------------------------------- # Asymmetric addition (number with base $r4 has more digits than the other) asym_add: # Repeat for all digits in number; some of these iterations will be skipped for(i, 0, 16, { beqi($cf, 0, @copy_add + i * 2); # Skip propagation when carry runs out lb($u, $r4, i); # Grab a word from the larger BCD array for(j, 0, 8, {format({baddi($u%d, $u%d, 0);}, j, j)}) # Propagate any carry sb($u, $zero, @bcd_z + i); # Store the result in BCD z }) # Must be done adding if this point if ever reached disp($zw, $zero, @bcd_z); # Display result # ----------------------------------------------------------------------------- # Asymmetric addition with no carry (simply copy digits from $r4 to BCD z) copy_add: # Repeat for all digits in number; some of these iterations will be skipped for(i, 0, 16, { lb($u, $r4, i); # Grab a word from the larger BCD array sb($u, $zero, @bcd_z + i); # Store the result in BCD z }) # Must be done adding if this point if ever reached disp($zw, $zero, @bcd_z); # Display result # === Data ==================================================================== # Array values # Name and reserve blocks of memory array(bcd_x, 16); # Input BCD array x array(bcd_y, 16); # Input BCD array y array(bcd_z, 16); # Output BCD array z array(ascii_in, 32); # Input ASCII array # ----------------------------------------------------------------------------- # Nothing to see here... move along now... eof: # vim:syntax=sh # EOF