毕设(1):地址空间的划分

地址空间

由于计算机软件系统是一个裸机系统,因此没有虚拟内存系统,都是直接访存,也就是说 tlb 其实是相当于不工作的。

只要在start.S 中直接修改 csr 标记 CSR.CRMD.DA = 1 (直接地址模式)、CSR.CRMD.PG = 0 (关闭分页)。

地址段划分如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//==========================================================================
// 主存储器
//==========================================================================
#define RAM_BASE 0x00000000 // DDR3/SRAM 起始
#define RAM_END 0x1CFFFFFF // 464MB (代码+数据+堆)
// Stack
#define STACK_TOP 0x1EFFFFFF
#define STACK_BOTT 0x1D000000 // 32MB
// Framebuffer 放在 RAM 末尾
#define FRAMEBUF_BASE 0x1F000000
#define FRAMEBUF_SIZE 0x01000000 // 16MB (双缓冲 1080P)
#define FRAMEBUF_END 0x1FFFFFFF

//==========================================================================
// Boot ROM (BRAM 实现)
//==========================================================================
#define BOOTROM_BASE 0x20000000 // Boot ROM 起始
#define BOOTROM_SIZE 0x00001000 // 4KB BRAM
#define BOOTROM_END 0x20000FFF // 边界

//==========================================================================
// 外设 - CONFREG (0x1FD0_xxxx)
//==========================================================================
#define CONFREG_BASE 0x1FD00000

// 控制寄存器
#define CR0_ADDR 0x1FD00000 // CR0
#define CR0_ADDR 0x1FD00004 // CR1
#define CR0_ADDR 0x1FD00008 // CR2
#define CR0_ADDR 0x1FD0000c // CR3
#define CR0_ADDR 0x1FD00010 // CR4
#define CR0_ADDR 0x1FD00014 // CR5
#define CR0_ADDR 0x1FD00018 // CR6
#define CR7_ADDR 0x1FD0001C // CR7

// Timer
#define TIMER_ADDR 0x1FD0E000

// LED & Button
#define LED_ADDR 0x1FD0F000
#define BTN_STEP_ADDR 0x1FD0F028
#define FREQ_ADDR 0x1FD0F030

// GPIO
#define GPIO_DATA_ADDR 0x1FD0F040
#define GPIO_DIR_ADDR 0x1FD0F044
#define GPIO_IN_ADDR 0x1FD0F048

// PS2 Keyboard
#define PS2_DATA_ADDR 0x1FD0F050
#define PS2_STATUS_ADDR 0x1FD0F054
#define PS2_CTRL_ADDR 0x1FD0F058

//==========================================================================
// 外设 - APB (0x1FE0_xxxx)
//==========================================================================
#define APB_BASE 0x1FE00000
#define UART_BASE 0x1FE00000 // UART 16550

关于 axi 仲裁器的设计,所有的非 APB、CONFREG 的地址都会落到 SRAM 通道。因此主存储器、BOOTROM 都能收到icache、dcache 的请求。我只需要在 SRAM 设备设置一个选择器,当bootloader 执行的时候,将数据直接从 brom 模块给出,就可以了。

由于我可能需要开辟一些比较大的数组或者使用递归什么的,栈区设置了 32MB,这对于裸机系统来说已经相当大了。

链接脚本的设计

内存布局好后,就可以主要针对 ddr 的范围来划分内存区域了。另外按照 loongarch 标准,csr_eentry 的地址需要 64 字节对齐:

1
2
3
4
5
6
7
8
always @(posedge clk) begin
if (rst) begin
csr_eentry[5:0] <= 6'b0;
end
else if (eentry_wen) begin
csr_eentry[31:6] <= csr_wdata[31:6];
end
end

因此,中断处理程序也需要链接到 64 字节对齐的地方。但是这样写并不会生效:

1
2
3
4
5
6
7
8
9
.text.entry : {
...
} > RAM /* 结束于 0x30 */

. = ALIGN(64); /* 期望:. = 0x40 */

.exception : { /* 实际:还是从 0x30 开始 */
...
} > RAM

以为当多个输出段都指定 > RAM 时,链接器会尝试将它们紧密排列,以减少内存空洞。. = ALIGN(64) 确实会更新位置计数器到 0x40,但链接器在分配段地址时,可能会忽略段之间的间隙,直接把下一个段放在上一个段结束的位置。而且经过测试,确实对不齐。

解决办法就是将 .text.entry 填充到 0x40 就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/*
* linker.ld - LoongArch32 裸机链接脚本
*
* 内存布局 (512MB DDR):
* 0x00000000 - 0x1CFFFFFF : RAM (464MB) - 代码+数据+堆
* 0x1D000000 - 0x1EFFFFFF : Stack (32MB)
* 0x1F000000 - 0x1FFFFFFF : Framebuffer (16MB)
* 0x20000000 - 0x20000FFF : Boot ROM (4KB, BRAM)
*
* 地址空间分配:
* .text.entry: 0x00000000 启动代码 (.text.start)
* .exception : 0x00000040 异常/中断入口 (64字节对齐)
* .text : 紧随其后 其余代码
* .rodata : 紧随 .text 只读数据
* .data : 紧随 .rodata 已初始化数据
* .bss : 紧随 .data 未初始化数据
* heap : _end 之后 堆 (向上增长至 0x1CFFFFFF)
* stack : 0x1D000000 - 0x1EFFFFFF (向下增长, 32MB)
*/

OUTPUT_ARCH(loongarch)
ENTRY(_start)

/* 内存定义 */
MEMORY
{
RAM (rwx) : ORIGIN = 0x00000000, LENGTH = 464M
}

/* 栈大小 */
_stack_size = 32M;

SECTIONS
{
/* ======== 启动代码 (必须在 0x00) ======== */
. = 0x00000000;
.text.entry : {
_text_start = .;
KEEP(*(.text.start)) /* 启动代码在最前面 */
. = 0x40; /* 填充到 0x40,为 .exception 预留空间 */
} > RAM

/* ======== 异常/中断入口 (紧随 .text.entry,地址 0x40) ======== */
.exception ALIGN(64) : {
__exception_start = .;
KEEP(*(.exception))
*(.exception.*)
__exception_end = .;
} > RAM

/* ======== 代码段 ======== */
.text : {
*(.text)
*(.text.*)
_text_end = .;
} > RAM

/* ======== 只读数据段 ======== */
.rodata ALIGN(16) : {
_rodata_start = .;
*(.rodata)
*(.rodata.*)
*(.srodata)
*(.srodata.*)
_rodata_end = .;
} > RAM

/* ======== 已初始化数据段 ======== */
.data ALIGN(16) : {
_data_start = .;
*(.data)
*(.data.*)
*(.sdata)
*(.sdata.*)
_data_end = .;
} > RAM

/* ======== 未初始化数据段 ======== */
.bss ALIGN(16) : {
_bss_start = .;
*(.bss)
*(.bss.*)
*(.sbss)
*(.sbss.*)
*(COMMON)
_bss_end = .;
} > RAM

/* ======== 程序结束标记 ======== */
. = ALIGN(16);
_end = .;

/* ======== 堆区域 ======== */
_heap_start = .;
_heap_end = 0x1D000000; /* 堆结束于栈底 */
_heap_size = _heap_end - _heap_start;

/* ======== 栈区域 ======== */
/* 栈: 0x1D000000 - 0x1EFFFFFF (32MB, 向下增长) */
_stack_bottom = 0x1D000000;
_stack_top = 0x1F000000;

/* ======== Framebuffer 区域 ======== */
_framebuf_base = 0x1F000000;
_framebuf_end = 0x20000000;

/* 丢弃不需要的段 */
/DISCARD/ : {
*(.comment)
*(.note.*)
*(.eh_frame)
*(.eh_frame_hdr)
}
}

/* 导出符号供程序使用 */
PROVIDE(__bss_start = _bss_start);
PROVIDE(__bss_end = _bss_end);
PROVIDE(__stack_top = _stack_top);
PROVIDE(__heap_start = _heap_start);
PROVIDE(__heap_end = _heap_end);
PROVIDE(__framebuf_base = _framebuf_base);
PROVIDE(__framebuf_end = _framebuf_end);

毕设(1):地址空间的划分
http://blog.luliang.online/2026/01/09/毕设1:地址空间的划分/
作者
Luyoung
发布于
2026年1月9日
许可协议