Encoding exerciseTo disassemble Branch immediates are distributed across nonadjacent fields and represent offsets in multiples of two bytes. In practical development, labels should normally be used; the assembler computes the offset and the linker resolves relocations when necessary. 6. Subroutines, Recursion, and Stack FramesA subroutine must preserve the ABI’s required machine state. A non-leaf routine normally saves Figure: Sixteen-byte RISC-V stack frame Figure: Example 16-byte stack frame for a recursive RV32 routine. 6.1 Unsigned recursive factorial# uint32_t factorial_u(uint32_t n)
# Exact mathematical result is representable only for 0 <= n <= 12.
# Larger inputs return the low 32 bits and may exhaust the stack.
.globl factorial_u
factorial_u:
sltiu t0, a0, 2 # t0 = 1 exactly when unsigned n < 2
bnez t0, .Lfact_base
addi sp, sp, -16
sw ra, 12(sp)
sw a0, 8(sp)
addi a0, a0, -1
call factorial_u
lw t1, 8(sp)
mul a0, a0, t1
lw ra, 12(sp)
addi sp, sp, 16
ret
.Lfact_base:
li a0, 1
retThe unsigned Recursion is often inappropriate in safety-critical embedded systems because stack consumption depends on input. An iterative implementation with a checked upper bound is usually more predictable. 7. Macros and Conditional AssemblyAn assembler macro performs source-level substitution. Unlike a subroutine, it has no call/return overhead, but every expansion increases code size. .macro PUSH reg
addi sp, sp, -16
sw \reg, 12(sp)
.endm
.macro POP reg
lw \reg, 12(sp)
addi sp, sp, 16
.endmThis pair preserves stack alignment when used consistently, although allocating 16 bytes for one word trades memory for ABI simplicity. Macros that introduce labels should use assembler-supported unique suffixes such as Macros should document modified registers, memory effects, and alignment assumptions. Excessive macro use can obscure control flow and complicate debugging because the debugger executes expanded instructions rather than the conceptual macro operation. 8. Interfacing Assembly with CC and assembly can interoperate only when both follow the same ABI. The function name, argument registers, return register, register-preservation rules, stack alignment, and data layout must agree. #include <stdint.h>
extern uint32_t sum_u32(const uint32_t *p, uint32_t n);
int main(void) {
uint32_t data[4] = {5, 7, 9, 11};
return (sum_u32(data, 4) == 32) ? 0 : 1;
}The pointer is passed in C types matter. A C 9. Executable Toolchain WorkflowThe following bare-metal workflow uses a GNU RISC-V cross-toolchain and QEMU. Tool names can vary by installation. A minimal startup file may call .section .data
.align 2
values: .word 5, 7, 9, 11
result: .word 0
.section .text
.globl _start
_start:
la a0, values
li a1, 4
call sum_u32
la t0, result
sw a0, 0(t0)
ebreak
1: j 1bA compact linker script named ENTRY(_start)
SECTIONS {
. = 0x80000000;
.text : { *(.text*) }
.rodata : { *(.rodata*) }
.data : { *(.data*) }
.bss : { *(.bss*) *(COMMON) }
}Build and inspect the program: riscv64-unknown-elf-gcc -march=rv32im -mabi=ilp32 -g -c start.S sum.S riscv64-unknown-elf-ld -m elf32lriscv -T link.ld -o app.elf start.o sum.o riscv64-unknown-elf-objdump -d -M no-aliases app.elf riscv64-unknown-elf-readelf -h -S -s app.elf
Start QEMU paused with a GDB server: qemu-system-riscv32 -machine virt -nographic -bios none \ -kernel app.elf -S -s In another terminal: riscv64-unknown-elf-gdb app.elf (gdb) target remote :1234 (gdb) break sum_u32 (gdb) continue (gdb) info registers a0 a1 sp ra (gdb) x/4uw $a0 (gdb) display/i $pc (gdb) stepi (gdb) nexti (gdb) continue (gdb) p/x result At entry to (gdb) watch result (gdb) continue If execution fails, inspect the program counter, disassemble the current address, verify stack alignment, and compare register values against the ABI. Stepping at the instruction level is often more informative than source-level stepping in handwritten assembly. 10. Applied Case: Safe Sensor AverageBackgroundAn illustrative embedded monitoring system samples a 16-bit analog-to-digital converter. A C task passes an array of unsigned samples to an RV32IM assembly routine that computes the truncated integer mean. ProblemThe routine must prevent division by zero, avoid accumulator overflow under its specified operating limit, and report invalid input explicitly. It must not write an output value after an error. AnalysisEach sample is in the range 0 to 65,535. The interface limits the sample count to 65,535. Therefore, the maximum sum is Smax = 65535 × 65535 = 4294836225 This is below the largest 32-bit unsigned value, 4,294,967,295. Thus, a 32-bit unsigned accumulator is sufficient under the stated bound. The integer average is \bar{x}int = \left\lfloor \frac{\sumi=0n-1 xi}{n} \right\rfloor where xi is an unsigned 16-bit sample and 1 ≤ n ≤ 65535. The result has the same ADC-count unit as each sample. Solution# int avg_u16(const uint16_t *p, uint32_t n, uint32_t *out)
# Returns 0 on success and -1 for n == 0 or n > 65535.
# On failure, *out is unchanged.
.globl avg_u16
avg_u16:
beq a1, zero, .Lavg_error # prevents division by zero
li t0, 65535
bltu t0, a1, .Lavg_error # unsigned 65535 < n
li t0, 0 # i
li t1, 0 # sum
.Lavg_loop:
bgeu t0, a1, .Lavg_divide
slli t2, t0, 1
add t3, a0, t2
lhu t4, 0(t3)
add t1, t1, t4
addi t0, t0, 1
j .Lavg_loop
.Lavg_divide:
divu t1, t1, a1
sw t1, 0(a2)
li a0, 0
ret
.Lavg_error:
li a0, -1
retThe branch to For samples Outcome and Learning PointsThe implementation converts interface assumptions into enforceable checks. Unsigned branches match the unsigned count type, accumulator capacity is proven rather than assumed, and the error contract prevents consumers from mistaking an invalid result for a valid zero reading. Production code should additionally validate pointers where the system’s memory-protection model permits meaningful validation. 11. Debugging and Optimization PrinciplesCommon defects include incorrect branch signedness, off-by-one loop limits, failure to restore Optimization should begin with measurement. Useful transformations include keeping frequently used values in registers, reducing repeated address calculations, unrolling short loops, and scheduling independent instructions to hide latency. However, unrolling enlarges code, and smaller code may be preferable in instruction-cache-constrained microcontrollers. A strength-reduced pointer loop can replace indexed address calculation, but pointer termination remains subject to valid-object and non-wrapping assumptions. Optimized code must preserve ABI behavior, volatile device-access ordering, error handling, and memory safety. Automated compiler explorers, instruction-set simulators, and AI-assisted code generation can accelerate experimentation, but generated assembly must be checked against the ISA manual, ABI, timing requirements, and test vectors. AI output should never be accepted as evidence of worst-case execution time or functional safety without independent verification. 12. Safety, Security, and GovernanceAssembly bypasses many language-level protections. An incorrect pointer can overwrite control data; an unbalanced stack can redirect returns; and data-dependent execution may leak information through timing or power consumption. Secure routines validate sizes, avoid secret-dependent branches where required, clear sensitive temporary data when justified, and follow platform protections such as execute-never memory and control-flow integrity. Safety-regulated projects require traceability between requirements, source code, tests, and tool versions. Toolchain updates can alter instruction scheduling or object formats, so reproducible builds and qualified verification procedures are important. Reverse engineering must also respect software licenses, intellectual-property law, privacy obligations, and authorization boundaries. Chapter SummaryAssembly language provides direct control over registers, memory, arithmetic, and control flow. Correct machine-level programming requires explicit attention to signedness, integer width, alignment, overflow, bounds, and the ABI. RV32IM array traversal uses load/store operations and unsigned branches when counters and lengths are unsigned. Stack frames preserve return addresses and callee-saved state, while macros trade call overhead for code expansion. Instruction encoding explains how symbolic operations become machine words, and disassembly connects executable bytes back to architecture. Assemblers, linkers, ELF inspection tools, simulators, and debuggers form an integrated development workflow. The sensor-average case demonstrated that boundary analysis and an early zero-length check can prevent both accumulator overflow and division by zero. Key TermsApplication Binary Interface (ABI): Rules governing calls, registers, data layout, and binary interoperability. Assembler: Tool that translates assembly source into object code. Branch: Instruction that conditionally changes control flow. Callee-saved register: Register a called routine must restore if it modifies it. Caller-saved register: Register the caller must preserve when its value is needed after a call. Calling convention: ABI rules for passing arguments, returning values, and preserving state. Disassembler: Tool that translates machine instructions into assembly notation. Endianness: Byte ordering used to represent multibyte values in memory. Instruction set architecture: Programmer-visible definition of instructions, registers, and machine behavior. Linker: Tool that combines object files and resolves symbols and relocations. Macro: Assembly-time source expansion that generates one or more instructions. Machine code: Binary instruction representation executed by a processor. Pseudoinstruction: Assembly notation expanded into one or more real instructions. Relocation: Link-time adjustment of an address-dependent object-code field. Simulator: Software model that executes or emulates a target ISA. Stack frame: Region of stack memory allocated to one active subroutine invocation. Symbol: Named address or value used by assembly and linking tools. Two’s complement: Standard representation of signed binary integers. Unsigned comparison: Comparison that interprets every operand bit as magnitude. Review QuestionsShort Answer Questions
Long Answer Questions
Analytical / Application Questions
Multiple Choice Questions with AnswersQuestion 1Which RV32 instruction correctly exits a loop when unsigned index A. B. C. D. Correct Answer: B. Question 2What is the RV32 machine word for A. B. C. D. Correct Answer: A. Question 3How is machine word A. B. C. D. Correct Answer: B. Question 4Why does the factorial routine use A. To multiply B. To detect signed multiplication overflow C. To test the unsigned base case without misclassifying high-bit-set values D. To align the stack pointer Correct Answer: C. To test the unsigned base case without misclassifying high-bit-set values Question 5What is the largest input for which factorial is exactly representable as an unsigned 32-bit value? A. 10 B. 11 C. 12 D. 13 Correct Answer: C. 12 Question 6In the RISC-V ABI, which register normally holds the first integer argument and return value? A. B. C. D. Correct Answer: A. Question 7For samples A. 800 B. 950 C. 1000 D. 4000 Correct Answer: C. 1000 Question 8How does A. It relies on the architectural result of B. It replaces a zero length with one C. It branches to an error return before executing D. It stores zero before division Correct Answer: C. It branches to an error return before executing Question 9Which tool combines object files and resolves external symbols? A. Assembler B. Linker C. Debugger D. Simulator Correct Answer: B. Linker Question 10Which statement about an assembler macro is correct? A. It always executes faster than every equivalent routine B. It is expanded during assembly and can increase code size C. It automatically preserves all registers D. It is resolved only by the runtime loader Correct Answer: B. It is expanded during assembly and can increase code size
Microprocessor Architecture
|
Chapter 4 of 15 |