The reset vector is an ISA fact
A CPU needs a first address. On MIPS32 that address is the
,
0xBFC00000, and the architecture fixes it. Every MIPS32 core, from a
router chip to your generated one, fetches its first word from there.
Your description states it as a fact of the ISA: RESET_PC in field_match.py. The fetch stage resets its PC register to it. RISC-V
does the opposite: the vector is the implementation’s choice. Your RV32IM
description picks 0, and a machine may pick another value.
What the linker script places
Putting something useful there is the linker script’s job. The compile tool generates it from the same memory layout that sizes the hardware. Here is the part that matters, with the numbers filled in for your machine:
MEMORY {
IMEM (rx) : ORIGIN = 0xbfc00000, LENGTH = 8192
DMEM (rw) : ORIGIN = 0x10000000, LENGTH = 16368
}
__mmio_putchar = 0x10003ff0;
__mmio_putint = 0x10003ff4;
__mmio_exit = 0x10003ff8;
__stack_top = 0x10003ff0;
SECTIONS {
.text : { KEEP(*(.text.start)) *(.text .text.*) } > IMEM /* crt0 first: the reset vector */
.rodata : { *(.rodata .rodata.*) } > DMEM /* strings: a load must reach them */
.data : { *(.data .data.*) _gp = . + 0x7ff0; } > DMEM
.bss : { *(.bss .bss.*) } > DMEM /* zeros: the image is zero-filled */
}
Two decisions are written here. First, .text.start, the section crt0 declares, is placed first in the instruction memory. So _start lands exactly on the reset vector. Second, everything a load can reach goes to the
data memory, .rodata included. The string "hello from carolyne" is read-only. But lb reads the data memory, so that is where it must be.
Where things live: the test machine's memory map (16 KB of data)
Higher addresses are at the top. The stack starts just under the doors and grows downward.
What to notice
This is the map from §4.1, and now you know who drew it. The MEMORY block
is the two hardware memories. The four symbols are the doors and the stack top. crt0 and the C header both take their addresses from here. The data
length stops at 0x10003FF0, so no variable can ever be placed on a door.
The four symbols are how crt0 avoids writing any number of its own. The C header carolyne_io.h is generated from the same layout. So the assembly, the C and the hardware cannot disagree about an address.
crt0, line by line
Here is the beside the words the linker made of it. Click a source line to see which dump lines it became.
crt0_mips.S beside its 42 words in hello_o0.dump (bfc00000–bfc000a4)
carolyne_ref/crt0_mips.S; carolyne_ref/dumps/hello_o0.dump
1.set noreorder2.set noat3_start:4.irp n, 1, 2, ... 315 move $\n, $zero6.endr7lui $gp, %hi(_gp)8addiu $gp, $gp, %lo(_gp)9lui $sp, %hi(__stack_top)10addiu $sp, $sp, %lo(__stack_top)11jal main12nop13lui $t0, %hi(__mmio_exit)14addiu $t0, $t0, %lo(__mmio_exit)15sw $v0, 0($t0)16_park: b _park17nopbfc0000000000825 move at,zerobfc0000400001025 move v0,zerobfc0000800001825 move v1,zerobfc0000c00002025 move a0,zerobfc0001000002825 move a1,zero… … 23 more moves: a2 … k1bfc000700000e825 move sp,zerobfc000740000f025 move s8,zerobfc000780000f825 move ra,zerobfc0007c3c1c1001 lui gp,0x1001bfc00080279c8010 addiu gp,gp,-32752bfc000843c1d1000 lui sp,0x1000bfc0008827bd3ff0 addiu sp,sp,16368bfc0008c0ff00063 jal bfc0018c <main>bfc0009000000000 nopbfc000943c081000 lui t0,0x1000bfc0009825083ff8 addiu t0,t0,16376bfc0009cad020000 sw v0,0(t0)bfc000a01000ffff b bfc000a0 <_park>bfc000a400000000 nopClick any highlighted line to see it light up in the other listing, and read what it does.
What to notice
Three source lines became 31 words. Every %hi and %lo became a number. The _gp pair shows the +1 rule of §3.2 in a real program. jal main
became jal bfc0018c: the linker knew where main ended up. The two nop
lines are in the source by hand, because noreorder means the assembler
adds none.
Why does crt0 write 0 into thirty-one registers? Because a register powers up
holding nothing you can rely on. In simulation it holds X, “unknown”, and
any instruction reading X produces X. Thirty-one move words cost nothing
next to a program, and after them every register holds a known value.
Watch it run
The runner below starts three registers with garbage, so you can see crt0
erase it. main is a stand-in that returns 7.
From the reset vector to the exit door: crt0 around a main that returns 7
What runs before main, what runs after it, and how does the return value leave the machine?
program (address · word · instruction)
| PC → | 0xbfc00000 | 0x00000825 | _start:move $at,$zero |
| 0xbfc00004 | 0x00001025 | move $v0,$zero | |
| 0xbfc00008 | 0x00001825 | move $v1,$zero | |
| 0xbfc0000c | 0x00002025 | move $a0,$zero | |
| 0xbfc00010 | 0x00002825 | move $a1,$zero | |
| 0xbfc00014 | 0x00003025 | move $a2,$zero | |
| 0xbfc00018 | 0x00003825 | move $a3,$zero | |
| 0xbfc0001c | 0x00004025 | move $t0,$zero | |
| 0xbfc00020 | 0x00004825 | move $t1,$zero | |
| 0xbfc00024 | 0x00005025 | move $t2,$zero | |
| 0xbfc00028 | 0x00005825 | move $t3,$zero | |
| 0xbfc0002c | 0x00006025 | move $t4,$zero | |
| 0xbfc00030 | 0x00006825 | move $t5,$zero | |
| 0xbfc00034 | 0x00007025 | move $t6,$zero | |
| 0xbfc00038 | 0x00007825 | move $t7,$zero | |
| 0xbfc0003c | 0x00008025 | move $s0,$zero | |
| 0xbfc00040 | 0x00008825 | move $s1,$zero | |
| 0xbfc00044 | 0x00009025 | move $s2,$zero | |
| 0xbfc00048 | 0x00009825 | move $s3,$zero | |
| 0xbfc0004c | 0x0000a025 | move $s4,$zero | |
| 0xbfc00050 | 0x0000a825 | move $s5,$zero | |
| 0xbfc00054 | 0x0000b025 | move $s6,$zero | |
| 0xbfc00058 | 0x0000b825 | move $s7,$zero | |
| 0xbfc0005c | 0x0000c025 | move $t8,$zero | |
| 0xbfc00060 | 0x0000c825 | move $t9,$zero | |
| 0xbfc00064 | 0x0000d025 | move $k0,$zero | |
| 0xbfc00068 | 0x0000d825 | move $k1,$zero | |
| 0xbfc0006c | 0x0000e025 | move $gp,$zero | |
| 0xbfc00070 | 0x0000e825 | move $sp,$zero | |
| 0xbfc00074 | 0x0000f025 | move $fp,$zero | |
| 0xbfc00078 | 0x0000f825 | move $ra,$zero | |
| 0xbfc0007c | 0x3c1c1001 | lui $gp,0x1001 | |
| 0xbfc00080 | 0x279c8010 | addiu $gp,$gp,-32752 | |
| 0xbfc00084 | 0x3c1d1000 | lui $sp,0x1000 | |
| 0xbfc00088 | 0x27bd3ff0 | addiu $sp,$sp,16368 | |
| 0xbfc0008c | 0x0ff00063 | jal 0xbfc0018c | |
| 0xbfc00090 | 0x00000000 | nop | |
| 0xbfc00094 | 0x3c081000 | lui $t0,0x1000 | |
| 0xbfc00098 | 0x25083ff8 | addiu $t0,$t0,16376 | |
| 0xbfc0009c | 0xad020000 | sw $v0,0($t0) | |
| 0xbfc000a0 | 0x1000ffff | _park:b 0xbfc000a0 | |
| 0xbfc000a4 | 0x00000000 | nop | |
| 0xbfc0018c | 0x24020007 | main:li $v0,7 | |
| 0xbfc00190 | 0x03e00008 | jr $ra | |
| 0xbfc00194 | 0x00000000 | nop |
registers — read · written this tick
memory 0x10003fe0 … 0x10003ffb — touched this tick
console (what the doors printed)
▮tick 0 of 43
Nothing has run yet. The PC (program counter) points at the first instruction.
Three registers start with garbage (t0, sp, ra) and crt0 erases it with the others. gp and sp become linker-script addresses in two steps each. jal links to the word after its slot. main puts 7 in v0. The last store writes 7 to the exit door at 0x10003FF8, and the run ends there: _park never runs.
What to notice
$t0, $sp and $ra start with garbage and turn to 0 as their move runs. $gp and $sp each take two instructions to reach their address.
jal writes $ra with 0xBFC00094, the word after the slot. The last store writes 7 into the exit door, and the run stops there. The console is empty, the exit code is 7, and _park never runs.
Before reading on, decide:
crt0's jal main is at 0xBFC0008C. After main's jr $ra, at which address does crt0 continue?
Go deeper: what the ELF holds, and how it becomes two images
The linked ELF is a list of sections, plus an entry point. Each section has its load address and its bytes. The compile tool reads it and writes two images.
A section whose address falls in IMEM goes to the instruction image. A section in DMEM goes to the data image. .bss has no bytes in the file, and needs none. The image starts as zeros, so crt0 needs no clearing loop.
The tool also refuses an ELF whose entry point is not reset_pc. The script
puts _start there today, and the check is what states that promise. A few sections are discarded on purpose: .MIPS.abiflags, .reginfo, .pdr and the like. They describe the file to other tools. A core that only fetches words has no use for them.
Can you now explain it?
The architecture fixes the first address, the linker script puts crt0 there, crt0 sets the registers and the stack from linker symbols, calls main, and turns main's return value into a store to the exit door.
If you remember only three things
- 0xBFC00000 is the ISA's number. Every MIPS core starts there. The script places .text.start first so crt0 is what it finds.
- crt0 names symbols, never addresses. _gp, __stack_top and __mmio_exit come from the generated script. The linker fills each lui/addiu pair.
- Exit is a store. The core has no halt. crt0 stores $v0 to a watched address and the harness stops the run. _park keeps the PC busy in case it does not.
Now try to explain this one yourself: suppose the linker script placed .text.start after the C functions. What would the core fetch at reset, and what would go wrong?
Go deeper: a model answer, once you have tried
The first word at 0xBFC00000 would be whatever function the linker put first. For hello.c that is print_char’s addiu sp,sp,-8. The core would
run it with every register undefined. $sp would be garbage, so the frame
store sw s8,4(sp) would write somewhere random. The function would end with
jr ra to an undefined address. Nothing would ever reach main. The compile tool catches this before the core sees it. The ELF’s entry point _start would no longer equal reset_pc, and the build stops with that message.
Check your understanding
0/5 answered1.Where does a MIPS32 core fetch its first instruction after reset, and who decided that address?
2.crt0 writes `lui $sp,%hi(__stack_top)` and never a number. Where does the number come from?
3._gp is 0x10008010, yet crt0 becomes `lui gp,0x1001` then `addiu gp,gp,-32752`. Why 0x1001 and a negative number?
4.The compile tool splits the ELF into two images. Where does the string "hello from carolyne" go, and why?
5.On the real core, what happens right after crt0's `sw $v0,0($t0)` to the exit door?
Next: §9.3 — hello.c end to end: -O0, -O2 and the cycle count.