Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Assembly (x86-64, AArch64, RISC-V)  Module 2 – Bits, bytes and numbers

Bits, bytes, words and hexadecimal

Read hexadecimal four bits at a time, convert by hand, and name the 8- to 64-bit sizes and register views of x86-64, AArch64 and RISC-V, with recorded runs.

  • Beginner
  • 20 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Convert between binary, hexadecimal and decimal by hand, and check the result with a programmer calculator
  • Name the sizes that x86-64, AArch64 and RISC-V give to 16, 32 and 64 bits
  • Identify how many bytes a load moves from its mnemonic, its size keyword or its encoding
  • Predict what a write to a narrow register name leaves in the whole 64-bit register

Before you start

What you’ll learn · Before you start

What you will learn

  • Convert between binary, hexadecimal and decimal by hand, and check the result with a programmer calculator
  • Name the sizes that x86-64, AArch64 and RISC-V give to 16, 32 and 64 bits
  • Identify how many bytes a load moves from its mnemonic, its size keyword or its encoding
  • Predict what a write to a narrow register name leaves in the whole 64-bit register

Before you start

On this page

A disassembler prints ldr w0, [x1] beside the eight characters b9400020, and a run of the same load on this page prints x0 = 0x0000000055667788: every number is hexadecimal, and the load read 4 bytes because w0 names 32 bits. A bit: A single binary digit, 0 or 1, the smallest unit of data. Eight bits make a byte, and a 64-bit register holds 64 of them. is one binary digit, a byte: Eight bits, the smallest unit of memory with its own address on x86-64, AArch64 and RISC-V. A byte holds 256 patterns, written 0x00 to 0xFF. is 8 bits, one hexadecimal: Base 16, written with the digits 0 to 9 and A to F. Each digit stands for four bits, so 0x2C is the byte 0010 1100, which is 44; the prefix 0x marks a hexadecimal number in C, Python and most assemblers. digit stands for exactly 4 bits, and the size called a word: A size whose meaning depends on the manual: 16 bits in Intel's x86-64 manual, which keeps the register width of the 8086, but 32 bits on AArch64 and RISC-V. Give the size in bits or bytes next to the name. is 16 bits on x86-64 but 32 bits on AArch64 and RISC-V.

Below, you convert between binary, hexadecimal and decimal by hand and check every step with short Python programs you can change and run, set the size names of the three ISAs side by side with the instructions that choose them, and watch a recorded AArch64 run show what a write to a narrow register name does to the whole 64-bit register. What assembly language is showed bytes such as 0x2a; this lesson makes them quick to read.

One hexadecimal digit stands for four bits

Take the number 300. Written in binary it is 1 0010 1100: nine digits, each 0 or 1, worth 256, 128, 64 and so on down to 1 from the left. Nine digits are already hard to read, and a 64-bit register holds 64 of them. Four bits can form exactly 16 patterns, from 0000 to 1111, and hexadecimal has exactly 16 digits, 0 to 9 and then A to F for 10 to 15. So each hexadecimal digit stands for one group of four bits, a nibble: A group of four bits, half a byte. One hexadecimal digit stands for exactly one nibble, so a byte is written with two hexadecimal digits., and turning a binary number: A number written in base 2, with only the digits 0 and 1, each worth twice the digit to its right: 1 0010 1100 is 256 + 32 + 8 + 4 = 300. into hexadecimal takes no arithmetic beyond each group: cut the bits into fours from the right, then replace every group with its digit. Step through 300 group by group:

Interactive · step by step Reading 300 in binary as three hexadecimal digits Step through the bits of 300 one group of four at a time, starting at the right, and watch each group become one digit.
Reading 300 in binary as three hexadecimal digits, step 7 of 7. Showing 300 (1 0010 1100), 0x12C, 0001, 1, 0010, 2, 1100, C, 0010 1100, 0x2C = 44. In focus: 0010 1100, 0x2C = 44.3001 0010 11000x12Cin hexadecimal0001bits 11 to 81third digit0010bits 7 to 42second digit1100bits 3 to 0C8 + 4 = 120010 1100the low byte0x2C = 44what 8 bits keep

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. 300 as nine bits. 300 is 256 + 32 + 8 + 4, so its binary form has a 1 in the places worth those four numbers and a 0 everywhere else: 1 0010 1100, nine bits.
  2. Groups of four from the right. Cut the bits into groups of four, starting at the right end: 1100, then 0010, then the single 1 that is left, padded with zeros to 0001. Zeros on the left never change a number.
  3. The lowest group is C. 1100 has ones in the places worth 8 and 4, so it is 12, written as the single digit C. A group of four bits is worth 0 to 15, exactly the range of one hexadecimal digit.
  4. The next group is 2. 0010 is 2. Its group sits four places higher, so each of its bits is worth 16 times as much: that is why it becomes the second hexadecimal digit.
  5. The top group is 1. 0001 is 1, the third digit. Nine bits needed only three hexadecimal digits, and no arithmetic beyond adding 8, 4, 2 and 1 inside each group.
  6. Read the digits top down. Reading the digits from the highest group to the lowest gives 0x12C. Check it the other way: 1 × 256 + 2 × 16 + 12 = 300.
  7. Two digits are one byte. The two lowest digits, 2C, are the lowest 8 bits, 0010 1100: the low byte. It is 44, the value an 8-bit register name keeps from 300.

The sixteen patterns are worth knowing on sight. The right half of the table is the left half with the bit worth 8 added:

Bits Hex Bits Hex
0000 0 1000 8
0001 1 1001 9
0010 2 1010 A
0011 3 1011 B
0100 4 1100 C
0101 5 1101 D
0110 6 1110 E
0111 7 1111 F

Because a digit is always four bits, the length of a hexadecimal number gives its width: two digits fill a byte, four fill 16 bits, eight fill 32 and sixteen fill 64. That is why disassemblers print 64-bit addresses with 16 digits, and why the recorded x0 = 0x0000000055667788 keeps its eight leading zeros: they say that the register is 64 bits wide and that its top 32 bits are 0. In C, Python and the GNU and LLVM assemblers the prefix 0x marks a hexadecimal number, and Python writes a binary one with 0b.

The next program prints a few numbers in decimal, then in binary with every nibble above its digit, then in hexadecimal. Press Edit, put your own whole numbers in VALUES and run it again:

Decimal, binary nibbles and hexadecimal digits Python · py/hex_digits.py
"""One hexadecimal digit is four bits: each value in decimal, in hexadecimal and in binary cut into nibbles."""

VALUES = [44, 255, 300, 4096]  # put your own whole numbers here and run it again

for n in VALUES:
    digits = f"{n:X}"  # hexadecimal digits, no prefix
    bits = f"{n:0{4 * len(digits)}b}"  # binary, padded to four bits for every hex digit
    nibbles = [bits[i : i + 4] for i in range(0, len(bits), 4)]
    print(f"{n:>5}  binary {' '.join(nibbles)}")
    print(f"{'':>5}  hex    {' '.join(f'{d:>4}' for d in digits)}   0x{digits}")

# Python reads all three spellings of 44, and the "_" format option groups binary and hex in fours.
print(0x2C, 0b0010_1100, int("2C", 16), int("101100", 2))
print(f"{300:_b}", f"{0x1122334455667788:_x}")

Output

   44  binary 0010 1100
       hex       2    C   0x2C
  255  binary 1111 1111
       hex       F    F   0xFF
  300  binary 0001 0010 1100
       hex       1    2    C   0x12C
 4096  binary 0001 0000 0000 0000
       hex       1    0    0    0   0x1000
44 44 44 44
1_0010_1100 1122_3344_5566_7788

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 hex_digits.py

300 comes out as 0001 0010 1100 above 1 2 C, and 4096 as 0001 0000 0000 0000 above 0x1000: a 1 followed by twelve zero bits is a 1 followed by three zero digits. The last two lines show that Python reads 0x2C, 0b0010_1100 and int("2C", 16) as the same 44, and that a format with the _ option groups binary and hexadecimal digits in fours, as Python’s format specification promises.

Key idea

Hexadecimal is binary written four bits at a time: one digit per nibble, two digits per byte and sixteen digits for a 64-bit register, so going between the two is a lookup, not a calculation.

Checkpoint

Check: One hex digit for every four bits

  1. Question 1 of 2 Which hexadecimal number is the bit pattern 1011 0110?

    Choose one answer.

    A hint

    Read each group of four bits on its own, then write the two digits in the same order.

    Show the answer: Question 1 of 2

    Answer: 0xB6

    Cut the bits into fours from the right: 0110 is 6 and 1011 is 11, the digit B. Written high digit first, the byte is 0xB6, which is 11 × 16 + 6 = 182 in decimal.

  2. Question 2 of 2 How many hexadecimal digits does it take to show every bit of a 64-bit register, leading zeros included?

    Type a number.

    A hint

    Each hexadecimal digit stands for the same number of bits.

    Show the answer: Question 2 of 2

    Answer: 16 digits

    One hexadecimal digit is four bits, so 64 bits need 64 ÷ 4 = 16 digits. That is why the recorded run prints x0 as 0x0000000055667788: the leading zeros show the register is 64 bits wide.

Converting by hand: divide by 16, then add place values

Hexadecimal is a positional system like decimal, with 16 in place of 10. The digits of 0x12C are worth 1 × 256, 2 × 16 and 12 × 1, and 256 + 32 + 12 = 300. That gives both directions. From hexadecimal to decimal, multiply each digit by its power of 16 (1, 16, 256, 4,096 and so on from the right) and add. From decimal to hexadecimal, divide by 16 again and again: each remainder, 0 to 15, is one digit, the first remainder is the lowest digit, and the division stops when the quotient reaches 0. The program does both for 300 the way you would on paper; change n and run it again:

300 to hexadecimal by division, and back by place values Python · py/by_hand.py
"""Decimal to hexadecimal by dividing by 16, then back again by place values, as you would on paper."""

DIGITS = "0123456789ABCDEF"
n = 300  # change it and run again

remainders = []
q = n
while q > 0:
    q, r = divmod(q, 16)
    remainders.append(r)
    print(f"{q * 16 + r:>5} = {q:>4} × 16 + {r:>2}   digit {DIGITS[r]}")
text = "".join(DIGITS[r] for r in reversed(remainders)) or "0"
print("remainders read from the bottom up: 0x" + text)
slip = "".join(DIGITS[r] for r in remainders) or "0"
print("read from the top down instead: 0x" + slip, "=", int(slip, 16))

total = 0
for place, d in enumerate(reversed(text)):
    total += DIGITS.index(d) * 16**place
    print(f"{d} × 16^{place} = {DIGITS.index(d) * 16**place}")
print("total", total, "| hex() says", hex(n), "| int() says", int(text, 16))
print("the last two hex digits are the low byte:", hex(n & 0xFF), "=", n & 0xFF)

Output

  300 =   18 × 16 + 12   digit C
   18 =    1 × 16 +  2   digit 2
    1 =    0 × 16 +  1   digit 1
remainders read from the bottom up: 0x12C
read from the top down instead: 0xC21 = 3105
C × 16^0 = 12
2 × 16^1 = 32
1 × 16^2 = 256
total 300 | hex() says 0x12c | int() says 300
the last two hex digits are the low byte: 0x2c = 44

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 by_hand.py

The three divisions leave the remainders 12, 2 and 1. Read from the last one to the first, they are 1, 2 and C: 0x12C. The place values add back to 300, and Python’s own hex() and int() agree. The last line is the shortcut that readers of disassembly use most: the two lowest hexadecimal digits are the low byte, here 0x2c, which is 44.

Reading the remainders in the order they came

The first remainder is the lowest digit. Writing the digits down as they appear gives 0xC21 for 300, which the program shows is 3105, more than ten times too big. Read the remainders from the last to the first, then check the result by adding the place values back.

From decimal to binary, go through hexadecimal: 300 is 0x12C, and writing each digit as its four bits gives 0001 0010 1100 without dividing by 2 nine times. From binary to decimal, take the same road in reverse.

To check a conversion without a program, MySmartCoPilot’s programmer calculator shows a value in hexadecimal, decimal, octal and binary at once, at a width you choose from 8 to 128 bits, with a grid of its bits; at 8 bits it wraps 200 + 100 to 44 and says that the sum overflowed. The number base converter converts between other bases as well, fractions included.

GATE CS 2027

In the GATE 2027 syllabus for Computer Science and Information Technology, from IIT Madras, Section 2 (Digital Logic) names “number representation and arithmetic (fixed and floating point)”, and Section 3 (Computer Organization and Architecture) begins with “instruction set and addressing modes”. This module works through the first of them lesson by lesson, and the sizes and load forms below belong to the second.

Checkpoint

Check: Converting between hexadecimal and decimal

  1. Question 1 of 2 What is 0x2A in decimal?

    Type a number.

    A hint

    The left digit counts sixteens, and A stands for 10.

    Show the answer: Question 1 of 2

    Answer: 42

    0x2A has the digit 2 in the sixteens place and A, which is 10, in the ones place: 2 × 16 + 10 = 32 + 10 = 42.

  2. Question 2 of 2 Which hexadecimal number is 1000 in decimal?

    Choose one answer.

    A hint

    Divide by 16 until the quotient is 0, and keep every remainder.

    Show the answer: Question 2 of 2

    Answer: 0x3E8

    Repeated division by 16 gives the remainders 8, 14 and 3. Read from the last to the first they are 3, E and 8: 0x3E8. Check it with place values: 3 × 256 + 14 × 16 + 8 = 768 + 224 + 8 = 1000.

Thirty-two bits are a doubleword on x86-64 and a word on AArch64 and RISC-V

The same 4-byte load is mov eax, dword ptr [rsi] on x86-64, ldr w0, [x1] on AArch64 and lw a0, 0(a1) on RISC-V, and the three manuals give that size two different names. A byte is 8 bits on all three. Intel’s manual defines a word as 2 bytes, a doubleword: Twice a word in the manual you are reading: 32 bits on x86-64, where assemblers write it dword, and 64 bits on AArch64 and RISC-V. as 4, a quadword as 8 and a double quadword as 16. It traces the family back to the 8086 of 1978, whose registers were 16 bits wide, and it still calls 16 bits a word. AArch64 and RISC-V count from 32 bits instead: Arm’s procedure call standard, AAPCS64, lists a half-word as 2 bytes, a word as 4, a double-word as 8 and a quad-word as 16, and the RISC-V specification defines a word of memory as 32 bits, with the halfword, the doubleword and the quadword at 16, 64 and 128 bits. Intel’s words are therefore half the size of the words in the other two manuals, and each size from 16 bits up has two names:

Size Intel x86-64 AArch64 and RISC-V
8 bits, 1 byte, 2 hex digits byte byte
16 bits, 2 bytes, 4 hex digits word halfword
32 bits, 4 bytes, 8 hex digits doubleword word
64 bits, 8 bytes, 16 hex digits quadword doubleword
128 bits, 16 bytes, 32 hex digits double quadword quadword

The program builds the same table from the number of bits and adds the largest unsigned value of each size, a run of F digits as long as the size. Run it, or add a row for 128 bits to SIZES:

Each size in bits, bytes and hex digits, with its names Python · py/size_names.py
"""Integer sizes: bits, bytes, hex digits, the largest unsigned value, and the name each ISA's manual gives the size."""

SIZES = [  # (bits, name in Intel's x86-64 manual, name in the AArch64 and RISC-V documents)
    (8, "byte", "byte"),
    (16, "word", "halfword"),
    (32, "doubleword", "word"),
    (64, "quadword", "doubleword"),
]

print(f"{'bits':>4} {'bytes':>5} {'digits':>6}  {'largest unsigned':<19} {'x86-64':<11} AArch64 and RISC-V")
for bits, x86, arm_riscv in SIZES:
    largest = 2**bits - 1
    print(f"{bits:>4} {bits // 8:>5} {bits // 4:>6}  {f'0x{largest:X}':<19} {x86:<11} {arm_riscv}")

Output

bits bytes digits  largest unsigned    x86-64      AArch64 and RISC-V
   8     1      2  0xFF                byte        byte
  16     2      4  0xFFFF              word        halfword
  32     4      8  0xFFFFFFFF          doubleword  word
  64     8     16  0xFFFFFFFFFFFFFFFF  quadword    doubleword

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 size_names.py

The largest 32-bit value is 0xFFFFFFFF, eight F digits, and the largest 64-bit value has sixteen. The x86 names are also the assembler’s size keywords, word, dword and qword, while AArch64 and RISC-V put the size into the mnemonic or the register name, as the next section shows.

Reading an Arm word with the x86 size

When an AArch64 or RISC-V manual says an instruction loads a word, it means 4 bytes. Read with the x86 meaning, 2 bytes, every offset into an array of words comes out halved: the third word of an array at x1 starts at x1 + 8, not at x1 + 4. Turn a size name into bytes for the ISA whose manual you are reading before you use it.

Checkpoint

Check: The size names of each ISA

  1. Question 1 of 2 Intel's x86-64 manual calls a value a quadword. How many bytes is it?

    Type a number.

    A hint

    In Intel's naming a word is 2 bytes, and a quadword is four words.

    Show the answer: Question 1 of 2

    Answer: 8 bytes

    Intel's manual defines a word as 2 bytes, a doubleword as 4 and a quadword as 8 bytes, 64 bits. The assembler spells that size qword.

  2. Question 2 of 2 An AArch64 manual says that an instruction stores a word at the address in x2. How many bytes of memory change?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: 4

    Arm's procedure call standard lists a word as 4 bytes, a half-word as 2 and a double-word as 8. So a word stored at x2 changes the 4 bytes from x2 to x2 + 3.

The instruction names how many bytes it moves

Each ISA spells the size of a memory access in the instruction itself. AArch64 uses the mnemonic and the register: ldrb and ldrh read 1 and 2 bytes into a w register, and ldr reads 4 bytes into a w register and 8 into an x register. RISC-V uses the mnemonic alone: lb, lh, lw and ld read a byte, a halfword, a word and a doubleword, and lbu, lhu and lwu fill the bits above the value with zeros where the plain forms copy its top bit. x86-64 takes the size from a register operand, and when the only operand with a size is in memory, a keyword gives it: byte ptr, word ptr, dword ptr or qword ptr in GNU Intel syntax, or a suffix on the mnemonic in AT&T syntax: The x86 notation that GNU as and objdump use by default: the source operand comes first, registers take a % sign and immediates a $ sign, memory is written disp(base, index, scale), and a suffix on the mnemonic (b, w, l or q) gives the operand size. More in “Assembler syntax: NASM, GNU as, Intel and AT&T”. Stores follow the same pattern:

Bytes x86-64, Intel syntax AArch64 RISC-V
1 byte ptr ldrb, strb lb, lbu, sb
2 word ptr ldrh, strh lh, lhu, sh
4 dword ptr ldr, str with w lw, lwu, sw
8 qword ptr ldr, str with x ld, sd

The session below assembles the loads of the table for each ISA with LLVM and prints them back with its disassembler. It only encodes and decodes them: no x86-64 or RISC-V code runs in it. The three source files come first:

The three source files the session assembles

S (loads_a64.s) · asm/loads_a64.s

// Four loads that differ only in how many bytes they read from the address in x1 (AArch64, GNU/LLVM syntax).
        ldrb    w0, [x1]        // 1 byte, zero-extended into w0
        ldrh    w0, [x1]        // 2 bytes (a halfword)
        ldr     w0, [x1]        // 4 bytes (a word)
        ldr     x0, [x1]        // 8 bytes (a doubleword)

S (loads_rv64.s) · asm/loads_rv64.s

# Seven RV64I loads from the address in a1; only the access size and the kind of extension differ.
        lb      a0, 0(a1)       # 1 byte, sign-extended to 64 bits
        lh      a0, 0(a1)       # 2 bytes (a halfword), sign-extended
        lw      a0, 0(a1)       # 4 bytes (a word), sign-extended
        ld      a0, 0(a1)       # 8 bytes (a doubleword)
        lbu     a0, 0(a1)       # 1 byte, zero-extended
        lhu     a0, 0(a1)       # 2 bytes, zero-extended
        lwu     a0, 0(a1)       # 4 bytes, zero-extended

S (loads_x64.s) · asm/loads_x64.s

# Four x86-64 loads from the address in rsi, in GNU Intel syntax; the ptr keyword names the size.
        .intel_syntax noprefix
        movzx   eax, byte ptr [rsi]     # 1 byte, zero-extended
        movzx   eax, word ptr [rsi]     # 2 bytes (an x86 word), zero-extended
        mov     eax, dword ptr [rsi]    # 4 bytes (a doubleword)
        mov     rax, qword ptr [rsi]    # 8 bytes (a quadword)
Loads of every size, assembled for AArch64, RISC-V and x86-64 Python · asm/encodings_session.py
"""The same loads on three ISAs, assembled by LLVM and printed back by its disassembler.

Nothing here runs the x86-64 or RISC-V code: llvm-mc only encodes the instructions and llvm-objdump only reads the
bytes, so the session works on any machine with LLVM.
"""

import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).parent / "sessions"))
import session  # noqa: E402

session.run("""
# AArch64: every instruction is one 32-bit word
llvm-mc -triple=aarch64 -filetype=obj loads_a64.s -o loads_a64.o
llvm-objdump -d loads_a64.o | grep -E '^ +[0-9a-f]+:'
# RISC-V: also one 32-bit word each
llvm-mc -triple=riscv64 -filetype=obj loads_rv64.s -o loads_rv64.o
llvm-objdump -d loads_rv64.o | grep -E '^ +[0-9a-f]+:'
# x86-64: a string of bytes whose length depends on the instruction
llvm-mc -triple=x86_64 -filetype=obj loads_x64.s -o loads_x64.o
llvm-objdump -d -M intel loads_x64.o | grep -E '^ +[0-9a-f]+:'
""")

Output

# AArch64: every instruction is one 32-bit word
$ llvm-mc -triple=aarch64 -filetype=obj loads_a64.s -o loads_a64.o
$ llvm-objdump -d loads_a64.o | grep -E '^ +[0-9a-f]+:'
       0: 39400020     	ldrb	w0, [x1]
       4: 79400020     	ldrh	w0, [x1]
       8: b9400020     	ldr	w0, [x1]
       c: f9400020     	ldr	x0, [x1]
# RISC-V: also one 32-bit word each
$ llvm-mc -triple=riscv64 -filetype=obj loads_rv64.s -o loads_rv64.o
$ llvm-objdump -d loads_rv64.o | grep -E '^ +[0-9a-f]+:'
       0: 00058503     	lb	a0, 0x0(a1)
       4: 00059503     	lh	a0, 0x0(a1)
       8: 0005a503     	lw	a0, 0x0(a1)
       c: 0005b503     	ld	a0, 0x0(a1)
      10: 0005c503     	lbu	a0, 0x0(a1)
      14: 0005d503     	lhu	a0, 0x0(a1)
      18: 0005e503     	lwu	a0, 0x0(a1)
# x86-64: a string of bytes whose length depends on the instruction
$ llvm-mc -triple=x86_64 -filetype=obj loads_x64.s -o loads_x64.o
$ llvm-objdump -d -M intel loads_x64.o | grep -E '^ +[0-9a-f]+:'
       0: 0f b6 06                     	movzx	eax, byte ptr [rsi]
       3: 0f b7 06                     	movzx	eax, word ptr [rsi]
       6: 8b 06                        	mov	eax, dword ptr [rsi]
       8: 48 8b 06                     	mov	rax, qword ptr [rsi]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 encodings_session.py

The AArch64 and RISC-V loads are all 32-bit words, eight hexadecimal digits each, while the x86-64 loads are 3, 3, 2 and 3 bytes long, so an x86-64 listing has no fixed grid. The size is still in its bytes: Intel’s opcode tables list 0F B6 for movzx from a byte and 0F B7 from a word, and the extra 48 in front of 8b 06 is the REX.W prefix that the table puts before the 64-bit form of mov. In the fixed-width ISAs the size sits in a few bits of the instruction word, and finding them takes the nibble reading of the first section:

Interactive · step by step Finding the load size inside the instruction word Step through one AArch64 load and one RISC-V load, digit by digit, until each word says how many bytes it reads.
Finding the load size inside the instruction word, step 7 of 7. Showing 0xb9400020, 0x0005a503, b = 1011, a = 1010, size bits 10, funct3 010, 4 bytes, 4 bytes. In focus: 4 bytes.0xb9400020ldr w0, [x1]0x0005a503lw a0, 0(a1)b = 1011first hex digita = 1010bits 15 to 12size bits 10bits 31 and 30funct3 010bits 14 to 124 bytesan AArch64 word4 bytesa RISC-V word

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. Two loads as eight digits each. The recorded session printed both loads as 32-bit words: b9400020 for ldr w0, [x1] on AArch64 and 0005a503 for lw a0, 0(a1) on RISC-V. Four bits per digit, so each word is eight digits.
  2. AArch64: the first digit. Bits 31 to 28 are the first digit, b, which is 1011. The four AArch64 loads of the session start with 3, 7, b and f, that is 0011, 0111, 1011 and 1111: only the top two bits differ.
  3. Those two bits are the size. LLVM's AArch64 instruction definitions call bits 31 and 30 of these loads the size field: 00 for ldrb, 01 for ldrh, 10 for ldr into a w register and 11 for ldr into an x register. Here they are 10.
  4. 10 means four bytes. Each step of the size field doubles the access: 1, 2, 4 and 8 bytes. 10 is the third step, 4 bytes, an AArch64 word, and the bytes land in w0.
  5. RISC-V: the fourth digit. On RISC-V, count digits from the right: 3, 0, 5, then a. The fourth digit holds bits 15 to 12, and a is 1010.
  6. The low three bits are funct3. Bit 15 is the lowest bit of the address register a1. Bits 14 to 12 are the funct3 field that picks the load, here 010. The RISC-V listing gives lb 000, lh 001, lw 010 and ld 011.
  7. 010 means a word. 010 is lw, a 4-byte word, sign-extended to 64 bits on RV64. With the top funct3 bit set the loads are lbu 100, lhu 101 and lwu 110, which is why the session shows c, d and e in that digit.

The four AArch64 loads start with 3, 7, b and f because only bits 31 and 30 change between them. The RISC-V loads differ in their fourth digit from the right, 8, 9, a and b, and then c, d and e for the unsigned forms. A disassembler does exactly this, field by field, for every word it prints.

Checkpoint

Check: How an instruction picks its access size

  1. Question 1 of 2 Which of these instructions read exactly 2 bytes from memory?

    Choose every answer that is right.

    Show the answer: Question 1 of 2

    Answer:

    • lh a0, 0(a1)
    • movzx eax, word ptr [rsi]
    • ldrh w0, [x1]

    A 2-byte access is a halfword on AArch64 and RISC-V (ldrh, lh) and a word on x86-64 (word ptr). lw and ldr into a w register both read 4 bytes, the word of AArch64 and RISC-V.

  2. Question 2 of 2 A disassembler prints the AArch64 word 0xf9400441 for a load from [x2, #8]. Using the first hex digit, how many bytes does it read?

    Choose one answer.

    A hint

    Write the first digit as four bits and look at the top two.

    Show the answer: Question 2 of 2

    Answer: 8 bytes

    The first hex digit holds bits 31 to 28. For these loads bits 31 and 30 are the size: 00, 01, 10 and 11 for 1, 2, 4 and 8 bytes. f is 1111, so the size is 11 and the load reads 8 bytes: it is ldr x1, [x2, #8].

A narrow register name is a window on one 64-bit register

A 64-bit register often has more than one name, each for a different number of its low bits. On x86-64 the first register is rax for all 64 bits, eax for the low 32, ax for the low 16 and al for the low 8, and Intel’s register table gives every general-purpose register the same four levels, down to r8d, r8w and r8b for r8. AArch64 has two names, x0 and w0, which Arm’s procedure call standard describes as one register used in a 64-bit or in a 32-bit context. RISC-V has one name per register, and its width, called XLEN, is 32 bits on RV32 and 64 on RV64.

Reading a narrow name always gives the low bits. Writing one is where the ISAs differ, and the next program shows it on real AArch64 hardware. Each function in views_a64.s takes the address of the 8 bytes 0x1122334455667788, or that value itself, and views.c prints what the function left in x0 or in memory:

views_a64.s and its C caller

S · asm/views_a64.s

// Seven small AArch64 functions for an Apple silicon Mac. The C caller in views.c passes an address or a value in x0
// (and a byte in w1), then prints in hexadecimal what each function left in x0 or in memory.
        .text
        .globl  _load_byte
        .globl  _load_half
        .globl  _load_word
        .globl  _load_dword
        .globl  _keep_w
        .globl  _add_w
        .globl  _store_byte
        .p2align 2
_load_byte:                     // x0 holds the address of 8 bytes
        ldrb    w0, [x0]        // read 1 byte
        ret
_load_half:
        ldrh    w0, [x0]        // read 2 bytes
        ret
_load_word:
        ldr     w0, [x0]        // read 4 bytes
        ret
_load_dword:
        ldr     x0, [x0]        // read 8 bytes
        ret
_keep_w:                        // x0 holds a 64-bit value
        mov     w0, w0          // write the 32-bit view w0 with its own low 32 bits
        ret
_add_w:
        add     w0, w0, #1      // a 32-bit add: the result goes to w0
        ret
_store_byte:                    // x0 holds an address, w1 a byte
        strb    w1, [x0]        // write 1 byte; the other 7 bytes stay as they were
        ret

C · asm/views.c

// Calls the AArch64 functions of views_a64.s and prints, in hexadecimal, what each one left in x0 or in memory.
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>

uint64_t load_byte(const uint64_t *p);
uint64_t load_half(const uint64_t *p);
uint64_t load_word(const uint64_t *p);
uint64_t load_dword(const uint64_t *p);
uint64_t keep_w(uint64_t x);
uint64_t add_w(uint64_t x);
void store_byte(uint64_t *p, uint8_t b);

int main(void) {
    uint64_t memory = 0x1122334455667788;  // 8 bytes in memory

    printf("memory          0x%016" PRIx64 "\n", memory);
    printf("ldrb w0, [x0]   x0 = 0x%016" PRIx64 "\n", load_byte(&memory));
    printf("ldrh w0, [x0]   x0 = 0x%016" PRIx64 "\n", load_half(&memory));
    printf("ldr  w0, [x0]   x0 = 0x%016" PRIx64 "\n", load_word(&memory));
    printf("ldr  x0, [x0]   x0 = 0x%016" PRIx64 "\n", load_dword(&memory));
    printf("mov  w0, w0     x0 = 0x%016" PRIx64 "\n", keep_w(memory));
    printf("add  w0, w0, 1  x0 = 0x%016" PRIx64 "\n", add_w(memory));
    store_byte(&memory, 0xFF);
    printf("strb w1, [x0]   memory = 0x%016" PRIx64 "\n", memory);
    return 0;
}
Narrow loads, 32-bit writes and a byte store on Apple silicon Python · asm/views_session.py
"""Build views.c with views_a64.s on an Apple silicon Mac and run it: every line it prints comes from AArch64 code."""

import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).parent / "sessions"))
import session  # noqa: E402

session.run("""
uname -m
clang -o views views.c views_a64.s
./views; echo "exit=$?"
""")

Output

$ uname -m
arm64
$ clang -o views views.c views_a64.s
$ ./views; echo "exit=$?"
memory          0x1122334455667788
ldrb w0, [x0]   x0 = 0x0000000000000088
ldrh w0, [x0]   x0 = 0x0000000000007788
ldr  w0, [x0]   x0 = 0x0000000055667788
ldr  x0, [x0]   x0 = 0x1122334455667788
mov  w0, w0     x0 = 0x0000000055667788
add  w0, w0, 1  x0 = 0x0000000055667789
strb w1, [x0]   memory = 0x11223344556677ff
exit=0

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 views_session.py

ldrb, ldrh and ldr w0 returned 0x88, 0x7788 and 0x55667788, each with zeros above it in x0. They read the lowest-addressed bytes, which on a little-endian machine hold the low end of the value, an order that the endianness lesson explains. mov w0, w0 and add w0, w0, #1 wrote only the 32-bit name, yet x0 came back as 0x0000000055667788 and 0x0000000055667789: on AArch64 a write to a w register clears bits 63 to 32, a rule that Arm’s A64 guide states for every write to a w register and that LLVM’s AArch64 code generator relies on. The store went the other way: strb changed one byte of memory and left the other seven alone, 0x11223344556677ff.

x86-64 clears the top half on a 32-bit write too, but not on narrower ones: Intel’s manual says that a 32-bit result is zero-extended to 64 bits, while an 8-bit or 16-bit result leaves the upper 56 or 48 bits unchanged. RV64 does a third thing. Its W instructions, such as addiw, addw and subw, ignore the upper 32 bits of their inputs and write a 32-bit result sign extension: Widening a value by copying its top bit into every new bit above it, so a two's complement number keeps its signed value: the byte 0xc8 (−56) becomes 0xffffffc8 in 32 bits. movsx, ldrsb and lb sign-extend. More in “Two's complement and sign extension” to 64 bits, with bit 31 copied into every bit above it. The programs above ran on AArch64, so the next program applies the two manuals’ rules to a 64-bit value in Python:

The x86-64 and RV64 write rules applied to one value Python · py/register_views.py
"""What a write to a narrow view leaves in the whole 64-bit register, under the x86-64 and RV64 rules."""

ALL = (1 << 64) - 1


def low(value, bits):
    """The low `bits` bits: the narrow view of the register."""
    return value & ((1 << bits) - 1)


def x86_write(old, value, bits):
    """x86-64: a 32-bit write clears bits 63..32; an 8- or 16-bit write keeps every bit above the view."""
    if bits == 32:
        return low(value, 32)
    return (old & ~((1 << bits) - 1) & ALL) | low(value, bits)


def rv64_w_result(value):
    """RV64 W instructions: the low 32 bits of the result, with bit 31 copied into bits 63..32."""
    r = low(value, 32)
    return r | (ALL ^ 0xFFFF_FFFF) if r & 0x8000_0000 else r


rax = 0x1122_3344_5566_7788  # change it and run again
print(f"rax before         0x{rax:016x}")
for name, bits in [("mov al, 0x01", 8), ("mov ax, 0x01", 16), ("mov eax, 0x01", 32)]:
    print(f"{name:<18} 0x{x86_write(rax, 0x01, bits):016x}")

a0 = 0x1122_3344_89AB_CDEF  # bit 31 of the low half is 1 here
print(f"a0 before          0x{a0:016x}")
print(f"addiw a0, a0, 0    0x{rv64_w_result(a0):016x}")
print(f"addiw a0, a0, 1    0x{rv64_w_result(a0 + 1):016x}")

Output

rax before         0x1122334455667788
mov al, 0x01       0x1122334455667701
mov ax, 0x01       0x1122334455660001
mov eax, 0x01      0x0000000000000001
a0 before          0x1122334489abcdef
addiw a0, a0, 0    0xffffffff89abcdef
addiw a0, a0, 1    0xffffffff89abcdf0

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 register_views.py

mov al changed the last two digits and mov ax the last four, while mov eax left 0x0000000000000001: the 11223344 above it is gone. addiw a0, a0, 0 turned 0x1122334489abcdef into 0xffffffff89abcdef, because the top bit of the digit 8, which is bit 31, is 1. Step through the same writes on the three register files:

Interactive · step by step What a narrow write leaves in the whole register Step through four writes on x86-64, one on AArch64 and one on RV64; each box shows the register as its 16 hex digits, so you can see which ones change.
What a narrow write leaves in the whole register, step 8 of 8. Showing rax (0000000000000001), eax (wrote 0x00000001), ax (bits 15 to 0), al (bits 7 to 0), x0 (0000000055667788), w0 (mov w0, w0), a0 on RV64 (1122334489abcdef), addiw result (ffffffff89abcdef). In focus: addiw result, a0 on RV64.rax0000000000000001eaxwrote 0x00000001axbits 15 to 0albits 7 to 0x00000000055667788w0mov w0, w0a0 on RV641122334489abcdefaddiw resultffffffff89abcdef

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. One register, four names. On x86-64, rax is 64 bits, and eax, ax and al name its low 32, 16 and 8 bits. They are views of one register, not four registers. The model starts with rax = 0x1122334455667788.
  2. Write al: 8 bits change. mov al, 0x01 writes the low byte. Intel's manual says an 8-bit result leaves the upper 56 bits of the register as they were, so only the last two hex digits change: 0x1122334455667701.
  3. Write ax: 16 bits change. mov ax, 0x01 writes 16 bits and keeps the upper 48, so the last four digits change: 0x1122334455660001.
  4. Write eax: the top half clears. mov eax, 0x01 is different. In 64-bit mode a 32-bit result is zero-extended into the whole register, so rax becomes 0x0000000000000001 and the 11223344 above it is gone.
  5. AArch64: x0 and w0. AArch64 names each register twice: x0 for all 64 bits and w0 for the low 32. There is no 8-bit or 16-bit name; ldrb and ldrh load into w0 instead, as the recorded run showed.
  6. Write w0: the top half clears. The recorded mov w0, w0 wrote w0 with its own value, and x0 came back as 0x0000000055667788. Like eax on x86-64, a 32-bit write clears bits 63 to 32.
  7. RV64: one name, 64 bits. RISC-V gives each register one name, and on RV64 each is 64 bits wide. A W instruction works on the low 32 bits only. The model starts a0 at 0x1122334489abcdef, whose bit 31 is 1.
  8. addiw copies bit 31 up. addiw a0, a0, 0 keeps the low 32 bits, 89abcdef, and copies bit 31 into bits 63 to 32: 0xffffffff89abcdef. The 32-bit result is sign-extended, where x86-64 and AArch64 fill with zeros.

Copying bit 31 keeps a negative 32-bit number negative in 64 bits, and two’s complement shows why.

Key idea

A narrow name reads the low bits of one 64-bit register, so check the whole register after a narrow write: 32-bit writes clear the top half on x86-64 and AArch64, 8-bit and 16-bit x86 writes keep it, and RV64 W instructions fill it with copies of bit 31.

Checkpoint

Check: Narrow register names and the full register

  1. Question 1 of 2 rax holds 0xFFFFFFFFFFFFFFFF. After mov eax, 1 runs, what does rax hold?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 0x0000000000000001

    Intel's manual says a 32-bit operand gives a 32-bit result, zero-extended to 64 bits in the destination. So mov eax, 1 leaves rax = 0x0000000000000001, while mov ax, 1 and mov al, 1 would keep the bits above them.

  2. Question 2 of 2 On RV64, a0 holds 0x00000000F0000001. What does addiw a0, a0, 0 leave in a0?

    Choose one answer.

    A hint

    Look at bit 31 of the low 32 bits. Where does a W instruction copy it?

    Show the answer: Question 2 of 2

    Answer: 0xFFFFFFFFF0000001

    RV64's W instructions ignore the upper 32 bits of their inputs and write a 32-bit result sign-extended to 64 bits. F is 1111, so bit 31 is 1 and the result is 0xFFFFFFFFF0000001.

Practise

Fifteen questions cover the five ideas of the lesson, from a single nibble to a whole register after a write. The session asks first about the idea you know least, explains every answer as soon as you check it, and brings a missed question back at the end:

Practice that adapts to you

Practise this lesson

15 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 15 Which group of four bits does the hexadecimal digit D stand for?

    Choose one answer.

    Show the answer: Question 1 of 15

    Answer: 1101

    The digits A to F stand for 10 to 15, so D is 13. In four bits, 13 = 8 + 4 + 1 is 1101.

  2. Question 2 of 15 What is the byte 0xA5 in binary?

    Choose one answer.

    Show the answer: Question 2 of 15

    Answer: 1010 0101

    Hexadecimal to binary is a lookup: A is 1010 and 5 is 0101, so 0xA5 is 1010 0101, which is 165 in decimal.

  3. Question 3 of 15 A 64-bit register holds the address 0x0000000100003f50. Which value does an 8-bit view of that register hold?

    Choose one answer.

    Show the answer: Question 3 of 15

    Answer: 0x50

    Each hex digit is four bits, so the low 8 bits are the last two digits: 0x50. A 16-bit view would hold 0x3f50 and a 32-bit view 0x00003f50.

  4. Question 4 of 15 What is 0x100 in decimal?

    Type a number.

    A hint

    The third digit from the right counts the second power of 16.

    Show the answer: Question 4 of 15

    Answer: 256

    0x100 is 1 × 16² + 0 × 16 + 0 = 256, just as 100 in decimal is 1 × 10².

  5. Question 5 of 15 Which hexadecimal number equals 4096 in decimal?

    Choose one answer.

    Show the answer: Question 5 of 15

    Answer: 0x1000

    4096 = 16³, and a power of 16 is a 1 followed by zeros in base 16: 0x1000. In binary that is a 1 followed by twelve zeros, 0001 0000 0000 0000.

  6. Question 6 of 15 A program ends with status 1000, and the kernel keeps only the low byte for the parent. Which number does the shell print for $?

    Type a number.

    A hint

    Write 1000 in hexadecimal first, then keep the last two digits.

    Show the answer: Question 6 of 15

    Answer: 232

    1000 is 0x3E8. Its low byte is the last two digits, 0xE8, which is 14 × 16 + 8 = 232. The same rule turns an exit status of 300, 0x12C, into 44, 0x2C.

  7. Question 7 of 15 In Intel's x86-64 manual, how many bits is a word?

    Choose one answer.

    Show the answer: Question 7 of 15

    Answer: 16

    Intel's manual names the sizes byte (8 bits), word (16), doubleword (32), quadword (64) and double quadword (128).

  8. Question 8 of 15 A RISC-V manual page says an instruction stores a doubleword. Which RV64 store is it, and how many bytes does it write?

    Choose one answer.

    Show the answer: Question 8 of 15

    Answer: sd, 8 bytes

    RISC-V names 32 bits a word and 64 bits a doubleword. The RV64I stores are sb, sh, sw and sd for 1, 2, 4 and 8 bytes, so a doubleword store is sd, 8 bytes.

  9. Question 9 of 15 A note translated from Intel's manual says a field is a quadword. What does the RISC-V specification call the same size, and which RV64 load reads it whole?

    Choose one answer.

    Show the answer: Question 9 of 15

    Answer: A doubleword, read by ld

    Convert the name to a size first: an Intel quadword is 8 bytes. In RISC-V terms 8 bytes is a doubleword, and the RV64 load for it is ld. The word quadword means 16 bytes in RISC-V.

  10. Question 10 of 15 How many bytes does ldr w3, [x5] read from memory on AArch64?

    Choose one answer.

    Show the answer: Question 10 of 15

    Answer: 4

    On AArch64 the destination register names the size of an ldr: w for 4 bytes, x for 8. ldrb and ldrh name 1 and 2 bytes in the mnemonic.

  11. Question 11 of 15 The byte at the address in a1 is 0x80. Which RV64 load leaves 0x0000000000000080 in a0?

    Choose one answer.

    Show the answer: Question 11 of 15

    Answer: lbu a0, 0(a1)

    lb and lbu both read one byte; lb copies its top bit into every bit above it, lbu writes zeros there. For 0x80, whose top bit is 1, only lbu leaves 0x0000000000000080.

  12. Question 12 of 15 In the recorded session the four x86-64 loads take 3, 3, 2 and 3 bytes, while every AArch64 and RISC-V load takes 4. What does that mean for reading an x86-64 hex dump?

    Choose one answer.

    Show the answer: Question 12 of 15

    Answer: Each instruction's length depends on its bytes, so you decode one to know where the next starts

    AArch64 and RISC-V instructions here sit on a 4-byte grid, so every eighth hex digit starts a new one. x86-64 instructions have different lengths, so a reader, like a disassembler, decodes each instruction to find the next.

  13. Question 13 of 15 rax holds 0x1122334455667788. After mov al, 0x01 runs, what does rax hold?

    Choose one answer.

    Show the answer: Question 13 of 15

    Answer: 0x1122334455667701

    An 8-bit x86-64 write replaces bits 7 to 0 and leaves the upper 56 bits as they were. The lesson's Python model prints 0x1122334455667701 for exactly this write.

  14. Question 14 of 15 On AArch64, x5 holds 0xFFFFFFFF00000010, and the program runs add w5, w5, #1. What does x5 hold afterwards?

    Choose one answer.

    Show the answer: Question 14 of 15

    Answer: 0x0000000000000011

    0x10 + 1 = 0x11 in the low 32 bits, and writing w5 clears bits 63 to 32 of x5. In the recorded run, add w0, w0, #1 on 0x1122334455667788 left 0x0000000055667789 the same way.

  15. Question 15 of 15 On RV64, a0 holds 0x1122334489ABCDEF, and you want the low 32 bits as a positive 64-bit value, 0x0000000089ABCDEF. Why does addiw a0, a0, 0 not give it?

    Choose one answer.

    Show the answer: Question 15 of 15

    Answer: It copies bit 31, which is 1 here, into bits 63 to 32, giving 0xFFFFFFFF89ABCDEF

    RV64 keeps 32-bit values sign-extended: addiw a0, a0, 0 turns 0x1122334489ABCDEF into 0xFFFFFFFF89ABCDEF, as the lesson's model prints. A value whose bit 31 is 1 cannot come out positive from a W instruction.

Interview questions

A warm-up for freshers: is a word 16 bits or 32?

Both, depending on whose manual you read, because each family named its sizes from its own starting point. Intel’s x86-64 manual still calls 16 bits a word, the register width of the 8086 where its line began, so 32 bits is a doubleword and 64 bits a quadword, and assemblers spell those operand sizes word, dword and qword. AArch64 and RISC-V were defined with 32-bit words: Arm’s procedure call standard lists a word as 4 bytes and a double-word as 8, and the RISC-V specification defines a word of memory as 32 bits, a halfword as 16 and a doubleword as 64. A byte is 8 bits on all three. In an answer, give the size in bits or bytes next to the name and say whose naming you are using, because the same name in two manuals can differ by a factor of two.

Two harder questions in the module’s bank carry these sizes into real code: a 32-bit array index that changes from unsigned to signed, and a header field renamed while a tool moves from Windows to AArch64 Linux. Each row leads to its first step and its follow-ups:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    A function returns table[i], with i a 32-bit unsigned int. After a refactor makes i an int, the x86-64 code changes from mov eax, esi to movsxd rax, esi and the AArch64 load from uxtw #2 to sxtw #2. Why does a 32-bit index need widening, and for which values do the two versions read different elements?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A tool that reads a binary file header was written on Windows, whose documentation lists the fields as WORD and DWORD. Ported to Linux on AArch64, it declares each DWORD as unsigned long and each WORD as an Arm word, and the header's fields come out with wrong values. Why, and how should the fields be declared?

    Model answer, Pro. First step and follow-ups

Flashcards · 6 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. How many bits does one hexadecimal digit stand for, and how many digits fill a 64-bit register?

    One hex digit is exactly four bits, a nibble. Two digits make a byte and sixteen digits fill a 64-bit register, so turning binary into hex is a matter of cutting the bits into groups of four from the right.

  2. How do you turn 300 into hexadecimal by hand, and how do you check the answer?

    Divide by 16 repeatedly and read the remainders from last to first: 300 is 0x12C. Check by adding each digit times its power of 16: 1 x 256 + 2 x 16 + 12 = 300.

  3. Is a word 16 bits or 32 bits?

    It depends on the manual. Intel's x86-64 manual keeps the 8086 meaning, so a word is 16 bits, a doubleword 32 and a quadword 64. AArch64 and RISC-V call 32 bits a word and 64 bits a doubleword. Say the size in bits next to the name.

  4. How does each ISA say how many bytes a load moves?

    x86-64 writes byte, word, dword or qword ptr on the memory operand. AArch64 uses ldrb and ldrh, or ldr into a w or an x register. RISC-V has separate loads: lb, lh, lw and ld.

  5. What happens to the upper bits of a 64-bit register when you write only a narrow part of it?

    A 32-bit write (eax on x86-64, w0 on AArch64) clears the upper 32 bits. An 8-bit or 16-bit x86 write (al, ax) leaves them as they were. An RV64 W instruction fills them with copies of bit 31.

  6. How many patterns does a byte hold, and how are they written in hexadecimal?

    256 patterns, from 0x00 to 0xFF. A byte is 8 bits and the smallest unit of memory with its own address on x86-64, AArch64 and RISC-V.

Key takeaways

  • One hexadecimal digit is exactly four bits, a nibble, so two digits make a byte and sixteen fill a 64-bit register; binary becomes hexadecimal by cutting the bits into fours from the right.
  • Decimal becomes hexadecimal by dividing by 16 and reading the remainders from the last to the first, and hexadecimal becomes decimal by adding each digit times its power of 16: 0x12C is 256 + 32 + 12 = 300.
  • Intel’s x86-64 manual calls 16 bits a word, 32 bits a doubleword and 64 bits a quadword, while AArch64 and RISC-V call 32 bits a word and 64 bits a doubleword; a byte is 8 bits on all three.
  • An instruction states its access size: byte, word, dword or qword ptr on x86-64, ldrb, ldrh and ldr into a w or an x register on AArch64, and lb, lh, lw and ld on RISC-V, and the fixed-width encodings carry that size in a few bits a reader can find digit by digit.
  • A narrow register name views the low bits of one 64-bit register: writing eax or w0 clears the upper 32 bits, writing al or ax keeps them, and an RV64 W instruction fills them with copies of bit 31.

Terms in this lesson

binary number
A number written in base 2, with only the digits 0 and 1, each worth twice the digit to its right: 1 0010 1100 is 256 + 32 + 8 + 4 = 300.
bit
A single binary digit, 0 or 1, the smallest unit of data. Eight bits make a byte, and a 64-bit register holds 64 of them.
byte
Eight bits, the smallest unit of memory with its own address on x86-64, AArch64 and RISC-V. A byte holds 256 patterns, written 0x00 to 0xFF.
doubleword
Twice a word in the manual you are reading: 32 bits on x86-64, where assemblers write it dword, and 64 bits on AArch64 and RISC-V.
hexadecimal
Base 16, written with the digits 0 to 9 and A to F. Each digit stands for four bits, so 0x2C is the byte 0010 1100, which is 44; the prefix 0x marks a hexadecimal number in C, Python and most assemblers.
nibble
A group of four bits, half a byte. One hexadecimal digit stands for exactly one nibble, so a byte is written with two hexadecimal digits.
word
A size whose meaning depends on the manual: 16 bits in Intel's x86-64 manual, which keeps the register width of the 8086, but 32 bits on AArch64 and RISC-V. Give the size in bits or bytes next to the name.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.