--- tags: computer-arch --- # Quiz2 of Computer Architecture (2025 Fall) > Solutions ## Problem `A` You are tasked with designing a RISC-V assembly program that solves the classic Tower of Hanoi puzzle using an iterative method. The key insight lies in Gray codes, which trace a Hamiltonian path on an $n$-dimensional cube by interpreting each bit as a coordinate. The reflected Gray code is the most common variant, constructed as follows: * 1-bit Gray code: `0`, `1`. * 2-bit Gray code: Mirror the 1-bit sequence and prefix with `0` and `1`, producing `00, 01, 11, 10`. * n-bit Gray code: Recursively mirror the $(n-1)$-bit sequence, prefix with `0` and `1`, then concatenate. A key property of Gray codes is that consecutive entries differ by exactly one bit flip. Determining the flipped bit * Even number of `1`s: flip the last bit. * Odd number of `1`s: flip the bit immediately to the left of the rightmost `1`. Gray code generation and the Tower of Hanoi share the same recursive nature: 1. Recursive structure * Tower of Hanoi: solve for $n-1$ disks, move the largest disk, then solve again for $n-1$ disks. * Gray code: mirror the $(n-1)$-bit sequence, prefix with `0` and `1`, then concatenate. 2. Pattern formation Both processes generate a self-similar sequence, often described by the recursive “ABACABADABACABA” pattern. 3. Iterative move generation via Gray code * Disk labeling: assign letters to disks, with the smallest as `A`, the next as `B`, and so on. * Move determination: each bit flip in the Gray code indicates moving the corresponding disk. * Special handling for disk `A`: always move the smallest disk in a fixed cyclic direction (e.g., Peg 1 → Peg 2 → Peg 3 → Peg 1 → …). This scheme ensures that exactly one disk moves per step, and that no larger disk is ever placed on a smaller one, preserving the rules of the Tower of Hanoi. Your task is to complete the iterative RV32I implementation so that it executes correctly in the Ripes simulator. ```c .text .globl main main: addi x2, x2, -32 sw x8, 0(x2) sw x9, 4(x2) sw x18, 8(x2) sw x19, 12(x2) sw x20, 16(x2) li x5, 0x15 sw x5, 20(x2) sw x5, 24(x2) sw x5, 28(x2) # Fix disk positions (BLANK 1-3: neutralize x5 effect) # BLANK 1: Fix position at x2+20 sw x0, 20(x2) # BLANK 2: Fix position at x2+24 sw x0, 24(x2) # BLANK 3: Fix position at x2+28 sw x0, 28(x2) addi x8, x0, 1 game_loop: # BLANK 4: Check loop termination (2^3 moves) addi x5, x0, 8 beq x8, x5, finish_game # Gray code formula: gray(n) = n XOR (n >> k) # BLANK 5: What is k for Gray code? srli x5, x8, 1 # BLANK 6: Complete Gray(n) calculation xor x6, x8, x5 # BLANK 7-8: Calculate previous value and its shift addi x7, x8, -1 srli x28, x7, 1 # BLANK 9: Generate Gray(n-1) xor x7, x7, x28 # BLANK 10: Which bits changed? xor x5, x6, x7 # Initialize disk number addi x9, x0, 0 # BLANK 11: Mask for testing LSB andi x6, x5, 1 # BLANK 12: Branch if disk 0 moves bne x6, x0, disk_found # BLANK 13: Set disk 1 addi x9, x0, 1 # BLANK 14: Test second bit with proper mask andi x6, x5, 2 bne x6, x0, disk_found # BLANK 15: Last disk number addi x9, x0, 2 disk_found: # BLANK 16: Check impossible pattern (multiple bits) andi x30, x5, 5 addi x31, x0, 5 beq x30, x31, pattern_match jal x0, continue_move pattern_match: continue_move: # BLANK 17: Word-align disk index (multiply by what?) slli x5, x9, 2 # BLANK 18: Base offset for disk array addi x5, x5, 20 add x5, x2, x5 lw x18, 0(x5) bne x9, x0, handle_large # BLANK 19: Small disk moves by how many positions? addi x19, x18, 2 # BLANK 20: Number of pegs addi x6, x0, 3 blt x19, x6, display_move sub x19, x19, x6 jal x0, display_move handle_large: # BLANK 21: Load reference disk position lw x6, 20(x2) # BLANK 22: Sum of all peg indices (0+1+2) addi x19, x0, 3 sub x19, x19, x18 sub x19, x19, x6 display_move: la x20, obdata add x5, x20, x18 # BLANK 23: Load byte from obfuscated data lbu x11, 0(x5) # BLANK 24: Decode constant (0x6F) li x6, 0x6F xor x11, x11, x6 # BLANK 25: Final offset adjustment addi x11, x11, -0x12 add x7, x20, x19 lbu x12, 0(x7) xor x12, x12, x6 addi x12, x12, -0x12 la x10, str1 addi x17, x0, 4 ecall addi x10, x9, 1 addi x17, x0, 1 ecall la x10, str2 addi x17, x0, 4 ecall addi x10, x11, 0 addi x17, x0, 11 ecall la x10, str3 addi x17, x0, 4 ecall addi x10, x12, 0 addi x17, x0, 11 ecall addi x10, x0, 10 addi x17, x0, 11 ecall # BLANK 26: Calculate storage offset slli x5, x9, 2 addi x5, x5, 20 add x5, x2, x5 # BLANK 27: Update disk position sw x19, 0(x5) # BLANK 28-29: Increment counter and loop addi x8, x8, 1 jal x0, game_loop finish_game: lw x8, 0(x2) lw x9, 4(x2) lw x18, 8(x2) lw x19, 12(x2) lw x20, 16(x2) addi x2, x2, 32 addi x17, x0, 10 ecall .data obdata: .byte 0x3c, 0x3b, 0x3a str1: .asciz "Move Disk " str2: .asciz " from " str3: .asciz " to " ``` A01: `sw x0, 20(x2)` A02: `sw x0, 24(x2)` A03: `sw x0, 28(x2)` A04: `8` A05: `x8` A06: `x5` A07: `1` A08: `xor` A09: `x8` A10: `x5` A11: `xor` A12: `-1` A13: `x7` A14: `x7` A15: `x28` A16: `x6` A17: `x7` A18: `1` A19: `bne` A20: `x0` A21: `addi` A22: `2` A23: `2` A24: `5` A25: `2` A26: `20` A27: `2` A28: `3` A29: `20(x2)` A30: `3` A31: `lbu` A32: `0(x5)` A33: `0x6F` A34: `-0x12` A35: `2` A36: `sw` A37: `0(x5)` A38: `addi` A39: `1` A40: `jal` A41: `game_loop` A42: (意思相近就給分) The program needs to display three peg names: - Peg A: ASCII code 65 (0x41) - Peg B: ASCII code 66 (0x42) - Peg C: ASCII code 67 (0x43) Encoding Process ``` Step 1: Choose XOR key = 0x6F Step 2: Add offset = 0x12 Step 3: Apply encoding 'A' encoding: Original: 0x41 (65) Add offset: 0x41 + 0x12 = 0x53 XOR: 0x53 XOR 0x6F = 0x3C 'B' encoding: Original: 0x42 (66) Add offset: 0x42 + 0x12 = 0x54 XOR: 0x54 XOR 0x6F = 0x3B 'C' encoding: Original: 0x43 (67) Add offset: 0x43 + 0x12 = 0x55 XOR: 0x55 XOR 0x6F = 0x3A ``` Therefore obdata stores: `.byte 0x3c, 0x3b, 0x3a` The decoding steps in the program: ```asm # 1. Load encoded data la x20, obdata # x20 points to encoded data add x5, x20, x18 # x18 is position index (0/1/2) lbu x11, 0(x5) # Load encoded byte # 2. XOR decoding li x6, 0x6F # Load XOR key xor x11, x11, x6 # Apply XOR # 3. Subtract offset addi x11, x11, -0x12 # Restore ASCII value ``` --- ## Problem `B` Consider a 16-bit fixed-point format with 1 sign bit, 5 integer bits, and 10 fraction bits. The fraction field encodes ($2^{-1},2^{-2},\dots,2^{-10}$). For example, `0b0 10010 1010000000` encodes (18+0.625=18.625). Also consider a 16-bit floating-point format that follows IEEE-754 half precision conventions (including subnormals, NaNs, etc.). It has 1 sign bit, a 5-bit exponent with **bias (=15)**, and a 10-bit fraction. Part 1 * Write (-18.625) in hexadecimal using the 16-bit **fixed-point** format. **B01** * Write (-18.625) in hexadecimal using the 16-bit **floating-point** format. **B02** * How many numbers in the range (`[8,16)`) (including 8, excluding 16) are representable by the fixed-point format? **B03** --- * B01: `0xCA00` Reasoning: sign=1, integer=18=`10010`, fraction=0.625=`.101` → `1010000000` (10 bits). Bit pattern: `1 10010 1010000000` → nibbles: `1100 1010 1000 0000`? → `0xCA80`. ==FIXME== * B02: `0xCCA8` Reasoning: (18.625 = 1.0010101_2 \times 2^{4}). sign=1, exponent=(4+15=19=0b10011), fraction (drop the leading 1, pad to 10 bits)=`0010101000`. Bit pattern: `1 10011 0010101000` → hex `0xCCA8`. * B03: $2^{13}$ or 8192 Step size is (2^{-10}). The width of ([8,16)) is ($8=2^{3}$). The count is $$ \frac{16-8}{2^{-10}} = 8 \times 2^{10} = 2^{3} \times 2^{10} = 2^{13}. $$ Complete the following RV32I program. Using only integer instructions, it should pack the two encodings into 16-bit values and store them in memory at the labels `fx_out` and `fp_out`. The expected output in the Ripes simulator is: ``` FX1: 0xca80 FP1: 0xc92a FX2: 0x4aa0 FP2: 0x4ca8 --- Bit fields for FX1 --- Sign = 1, Int = 268435522, Frac = 640 ``` ```c # Fixed-Point and Floating-Point Encoding - Answer Key # Fixed-point: 1 sign, 5 integer, 10 fraction bits # Half-precision: 1 sign, 5 exponent (bias=15), 10 mantissa bits # # IMPORTANT: Using only x-register names, no ABI names # Only allowed pseudo-instructions: li, la .text .globl _start _start: # Part 1: Encode -10.25 in fixed-point # -10.25 = -(10 + 0.25) # Binary: 1 01010 0100000000 = 0xA900 lui x1, 0xA9000 srli x1, x1, 16 # Part 2: Encode -10.328125 in half-precision # -10.328125 = -1.291015625 × 2^3 # Binary: 1 10010 0100101010 = 0xC92A lui x4, 0xC92A0 srli x4, x4, 16 # Part 3: Encode 18.625 in both formats # Fixed-point: 0 10010 1010000000 = 0x4A80 lui x6, 0x4A800 srli x6, x6, 16 # Half-precision: 0 10011 0010101000 = 0x4CA8 lui x7, 0x4CA80 srli x7, x7, 16 # Part 4: Store results for verification la x15, results sh x1, 0(x15) sh x4, 2(x15) sh x6, 4(x15) sh x7, 6(x15) # Part 5: Bit field extraction # Extract sign bit from x1 (fixed-point -10.25) srli x9, x1, 15 andi x9, x9, 1 # Extract integer part (bits [14:10]) srli x10, x1, 10 andi x10, x10, 0x1F # Extract fraction (bits [9:0]) andi x11, x1, 0x3FF # Part 6: Set return values for verification # Place key results in specific registers for checking # x1 = 0xA900, x4 = 0xC92A, x6 = 0x4A80, x7 = 0x4CA8 # x9 = 1 (sign), x10 = 10 (integer), x11 = 256 (fraction) # Set exit code to indicate success li x10, 0 # Exit code 0 = success # Exit properly li x17, 10 # exit syscall ecall .data .align 4 results: .half 0, 0, 0, 0 # Four 16-bit results # Validation checklist: # After running in Ripes, check: # 1. Register x1 = 0x0000A900 (fixed-point -10.25) # 2. Register x4 = 0x0000C92A (half-precision -10.328125) # 3. Register x6 = 0x00004A80 (fixed-point 18.625) # 4. Register x7 = 0x00004CA8 (half-precision 18.625) # 5. Register x9 = 0x00000001 (sign bit) # 6. Register x10 = 0x00000000 (exit code, was 10 for integer) # 7. Register x11 = 0x00000100 (fraction = 256) # 8. Memory at 'results': A900, C92A, 4A80, 4CA8 ``` B04: `0xA9000` or equivalent B05: `16` B06: `0xC92A0` or equivalent B07: `srli` B08: `0x4A800` or equivalent B09: `x6` B10: `16` B11: `0x4CA80` or equivalent B12: `x1` B13: `2` B14: `4` B15: `x7` B16: `srli` B17: `15` B18: `10` B19: `0x1F` B20: `0x3FF` For the fixed-point format used above, compute the absolute error and relative error compared to the true value −18.625. Briefly explain why such an error arises in fixed-point representation. B21: `0` B22: `0` B023: 意思相近就給分 Verify what -18.625 should encode to: - Integer part: 18 = 10010 (5 bits) ✓ - Fractional part: 0.625 = 5/8 Converting 0.625 to the 10-bit fraction field: - 0.625 × 1024 = 640 - 640 in binary = 1010000000 (10 bits) Full encoding: - Sign: 1 (negative) - Integer: 10010 - Fraction: 1010000000 - Combined: 1 10010 1010000000 = 0xCA80 Verification of represented value: - Integer: 18 - Fraction: 640/1024 = 0.625 - Total: -(18 + 0.625) = -18.625 True value: -18.625Represented value: -18.625 Absolute Error: |Represented value - True value| = |-18.625 - (-18.625)| = 0 Relative Error: |Represented value - True value| / |True value| = 0 / 18.625 = 0 Although -18.625 has no representation error, fixed-point formats generally introduce errors due to: 1. Quantization Error: Values must be rounded to the nearest multiple of 2^(-10). For example, -18.627 would need to be rounded to either $-18.626953125$ (642/1024) or $-18.62793125$ (643/1024). 2. Finite Precision: Only values that are exact multiples of 1/1024 in the fractional part can be represented precisely. Most decimal values (like 0.1, 0.2, 0.3) cannot be represented exactly. 3. Rounding Effects: When the true value falls between two representable values, rounding to the nearest introduces error. Example with error: If we wanted to represent -18.7: - True value: -18.7 - Fraction needed: 0.7 × 1024 = 716.8 - Must round to 717/1024 ≈ 0.70019531 - Represented value: -18.70019531 - Absolute error: 0.00019531 - Relative error: 0.00019531/18.7 ≈ 0.001%