Program Loader

之前写了一个很简单的 Program Loader,
现在就来真正的实作它,让它能够把编译好的程序放到指定位置吧。
主要参考对象是我们的老朋友:RISC-V-TLM

大部分的 SOC 都会有一些开机的流程,
例如先把程序烧进去指定装置的指定位置上,
然後就是多个阶段的 Boot Loader,
第一阶段被 Hardware 的机制 Load 起来,
第二阶段被第一阶段 Loader Load 起来,
每一阶段都做一点准备 (设定 Externam Device Register, 启动 MMU 等),
最後把主程序 Load 进记忆体开始执行,
大概会有个 2~3 阶段。

目前这个模拟器规划比较简单,
因为模拟环境目前没有需要提前设定的设计,
也没有 Binary Code 大小的限制,
一开机就可以直接跳进主程序执行,
所以只要有办法把主程序放到指定位置,
再让 Cpu 从那边开始执行就可以了!

实际程序

github 页面 Tag: ITDay27

开场先处理 Load File 失败的问题,
让 Simulator 在读到非法指令的时後就结束。

//executor.cpp
		default:
			cpu->raise_exception(CPU_INTERFACE::ILLEGAL_INSTRUCTION_EXCEPTION_CAUSE);
			break;

为简化开发流程,
loadBinaeyFromHex 目前只支援特定档名与档案格式,
之後扩充的时候再支援指定路径的部分。

//memory.h
...
	void loadBinaeyFromHex(std::string filePath = "./binary.hex");
...

Loader 大致上跟 RISC-V-TLM 的做法一样,
这边将 hexFile.is_open()line[0] != ':' 改成判断完先处理,
而不是一层一层包起来,稍微提升可读性。

尝试用 std::map + Switch Statement 简化每次比较字串的流程,
不需要每次都写 else if(std::stoul(line.substr(7, 2)) == "02")
但看起来可读性没有比较好,反而是执行效率有机会变好一点。

修掉了奇怪的 * 16
RISC-V-TLM 在 "03" 的 case 有一段程序码:
code_segment = stol(line.substr(9, 4), nullptr, 16) * 16; /* ? */
最後那段 * 16; /* ? */ 看起来是不小心把某个 Memory 的位置放错了,
导致需要用特别行为的处理,
但在这边没有这个问题,改回正常运算流程。

//memory.cpp
...
void MEMORY::loadBinaeyFromHex(std::string filePath)
{
	std::ifstream hexFile(filePath);
	std::string line = "";

	if(!hexFile.is_open()) {
		std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
		std::cout << "Binary File Open Failed!!" << std::endl;
		std::cout << "Cause: " << std::strerror(errno) << std::endl;
		std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
		return;
	}

	uint32_t extended_address = 0;
	uint32_t memory_offset = 0;
	uint32_t program_counter = 0;
	dataMemory.assign(0x100000, 0);
	while(std::getline(hexFile, line)) {
		if(line[0] != ':') {
			continue; //skip this line
		}

		switch (std::stoul(line.substr(7, 2))) {
			case 0: { //Data
				auto byteCount = std::stol(line.substr(1, 2), nullptr, 16);
				auto address = extended_address + std::stoul(line.substr(3, 4), nullptr, 16);
				for(int i=0; i < byteCount; i++) {
					auto value = std::stoul(line.substr(9 + (i*2), 2), nullptr, 16);
					dataMemory[address + i] = value;
					std::cout << "00" << " address: 0x" << address + i << " value: 0x" <<  std::hex << value << std::endl;
				}
			}
			break;
			case 2: { //Extended segment address
				extended_address = std::stoul(line.substr(9, 4), nullptr, 16);
			}
			break;
			case 3: { //Start segment address
				uint32_t code_segment = stoul(line.substr(9, 4), nullptr, 16);
				program_counter = code_segment + stoul(line.substr(13, 4), nullptr, 16);
				std::cout << "03 " << "program counter should be: 0x" << std::hex << program_counter << std::endl;
			}
			break;
			case 4: { //Start srgmant address
				memory_offset = stoul(line.substr(9, 4), nullptr, 16) << 16;
				extended_address = 0;
			}
			break;
			case 5: { //Get start program counter
				program_counter = stol(line.substr(9, 8), nullptr, 16);
				std::cout << "05 " << "program counter should be: 0x" << std::hex << program_counter << std::endl;
			}
			break;
			default:
				break;
		}

	}
}
...

执行结果

这次测试直接把 Program Counter 写死到指定位置,
预计把 Program Loader 从 Memory 独立出去之後,再让 Cpu 可以从正确位置开始执行。

程序码来源则是之前先做好的 Cross Compiler 编译结果,
之後会再开一篇说明,
有兴趣的可以先照 RISC-V-TLM 的流程建立编译环境。
另外注意如果是虚拟环境(VMware等等),要记得把容量和记忆体调高,
不然会载不下 gcc + 编译 gcc 的过程会因为记忆体不足碰到奇怪的问题。

现在才发现 RISC-V-TLM 有点奇怪,
我们从从第一道指令执行结果就不一样了!
导致我的第二道指令存取到不合法的位置。

看来要找一下 RISC-V-TLM 一开始到底做了些什麽,
不然没道理 ADDI 一开始 x2 - 32 会变成 0x3ffffdf
x2 到底存了什麽神奇数字?又是从哪边来的呢?
我们明天好好研究一下吧!

//RISCV-SIM by hsufit
current_pc: 0x1054 target_pc: 0x1058 ADDI 2 0 2 rs1Value: 0xffffffe0 rs2Value: 0x0 rdValue: 0xffffffe0 immValue: 0xffffffe0
error at address: 0xfffffffc!!
current_pc: 0x1058 target_pc: 0x105c SW 2 8 28 rs1Value: 0xffffffe0 rs2Value: 0x0 rdValue: 0x0 immValue: 0x1c
//RISC-V-TLM by mariusmm
time 0 s: PC: 0x10054. time 0 s: ADDI: x2 + -32 -> x2(0x3ffffdf)
time 10 ns: PC: 0x10058. time 10 ns: SW: x8(0x0) -> x2 + 0x1c (@0x3fffffb)

<<:  DAY 28:Command Pattern,将动作已指令一个一个完成

>>:  总结与未来展望

Day-13 ConstraintLayout

ConstraintLayout(约束布局) ConstraintLayout为Android St...

[Day 22]第二主餐-aws,始动

好的,经过了前两篇的中场休息後 今天我们要来把我们的code架到服务器上面 也就代表我们要进入第二主...

关於code signing [程序码签章] 这档事 ...

所谓的程序码签章,就是一个指一个数位的签章,在编译好的软件上签章。软件一旦被重新编译、修改,上面的签...

远距工作停看听:挑战篇

前言 昨天分享了远距工作的好处,今天紧接着来看它带来的挑战,以及我们有什麽方式可以去改善它。 远距工...

[Day 05] - 用Spring Boot 建立Service

一般而言,网站程序大多会是这样的架构: https://developer.mozilla.org/...