Last active
December 2, 2025 16:46
-
-
Save cyyself/227e99c5a707308737396804188ed088 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| class DTSGen: | |
| def __init__(self, | |
| compatible: str = "xiangshan,nemu-board", | |
| model: str = "XiangShan", | |
| isa_extensions: list = ["i", "m", "a", "f", "d", "c"], | |
| mmu_type="riscv,sv39", | |
| timebase_freq: int = 10000000, # 10 MHz | |
| nr_harts: int = 1, | |
| clint_addr: int = 0x38000000, | |
| plic_addr: int = 0x3c000000, | |
| memories: list = [(0x80000000, 8*1024*1024*1024)], # [(start, size)], default 8 GB at 2 GB | |
| reserved_memories: list = [], # [(start, size)] | |
| bootargs: str = "console=hvc0 earlycon=sbi rdinit=/sbin/init", | |
| rng_seed: bytes = b"NEMU_BOARD", | |
| uartlite_addr: int = 0x40600000, | |
| nemu_sdhci_addr: int = None): | |
| self.isa_extensions = isa_extensions | |
| self.mmu_type = mmu_type | |
| self.timebase_freq = timebase_freq | |
| self.nr_harts = nr_harts | |
| self.clint_addr = clint_addr | |
| self.plic_addr = plic_addr | |
| self.memories = memories | |
| self.reserved_memories = reserved_memories | |
| self.compatible = compatible | |
| self.model = model | |
| self.bootargs = bootargs | |
| # Note: console=hvc0 is a hack for NEMU Uartlite to use SBI | |
| # console to get around the lack of TX FIFO empty interrupt | |
| # implementation. The proper param should be "console=ttyUL0" | |
| # for Uartlite IP on Xilinx FPGA. | |
| # Ensure we have set the following kernel configs: | |
| # CONFIG_NONPORTABLE=y | |
| # CONFIG_HVC_RISCV_SBI=y | |
| self.rng_seed = rng_seed | |
| self.soc_devices = [] # [dev_str0, ...] | |
| if uartlite_addr is not None and plic_addr is not None: | |
| self.soc_devices.append(self.__gen_uartlite(uartlite_addr, 3)) | |
| if nemu_sdhci_addr is not None: | |
| self.soc_devices.append(self.__gen_nemu_sdhci(nemu_sdhci_addr)) | |
| def __indent(self, text, level: int = 4): | |
| indent_str = ' ' * level | |
| return "\n".join(indent_str + line if line.strip() != "" else line for line in text.split("\n")) | |
| def __gen_addrsize(self, addrsize, cell_number): | |
| res = "" | |
| for i in range(cell_number): | |
| res += f"0x{(addrsize >> (32 * (cell_number - i - 1))) & 0xFFFFFFFF:x} " | |
| return res.strip() | |
| def __gen_memory(self, memory_start, memory_size): | |
| return f""" | |
| memory@{memory_start:x} {{ | |
| device_type = "memory"; | |
| reg = <{self.__gen_addrsize(memory_start, 2)} {self.__gen_addrsize(memory_size, 2)}>; | |
| }}; | |
| """.strip() | |
| def __gen_isa_string(self, isa, cacheline_size=64): | |
| # As riscv,isa has been deprecated, leave it as rv64imafdc is enough for compatibility | |
| # Cache parameters are used for Zicbom / Zicbop / Zicboz | |
| # support in Linux | |
| return f""" | |
| { f"riscv,cbom-block-size = <{cacheline_size}>;" if "zicbom" in isa else "" } | |
| { f"riscv,cbop-block-size = <{cacheline_size}>;" if "zicbop" in isa else "" } | |
| { f"riscv,cboz-block-size = <{cacheline_size}>;" if "zicboz" in isa else "" } | |
| riscv,isa = "rv64imafdc"; | |
| riscv,isa-base = "rv64i"; | |
| riscv,isa-extensions = {", ".join(f'"{ext}"' for ext in isa)}; | |
| """.strip() | |
| def __gen_cpu_node(self, hart_id, isa): | |
| # Reference: https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/riscv/cpus.yaml | |
| return f""" | |
| cpu{hart_id}: cpu@{hart_id} {{ | |
| compatible = "riscv"; | |
| device_type = "cpu"; | |
| mmu-type = "{self.mmu_type}"; | |
| reg = <{hart_id}>; | |
| {self.__indent(self.__gen_isa_string(isa))} | |
| cpu{hart_id}_intc: interrupt-controller {{ | |
| #interrupt-cells = <1>; | |
| compatible = "riscv,cpu-intc"; | |
| interrupt-controller; | |
| }}; | |
| }}; | |
| """.strip() | |
| def __gen_cpus(self): | |
| return f""" | |
| cpus {{ | |
| #address-cells = <1>; | |
| #size-cells = <0>; | |
| timebase-frequency = <{self.timebase_freq}>; | |
| {self.__indent('\n'.join(self.__gen_cpu_node(hart_id, self.isa_extensions) + "\n" for hart_id in range(self.nr_harts)))} | |
| }}; | |
| """.strip() | |
| def __gen_clint(self): | |
| # Reference: https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/timer/sifive%2Cclint.yaml | |
| if self.clint_addr == None: | |
| return "" | |
| # 3 is MSI, 7 is MTI | |
| cpu_intc_list = [f"<&cpu{hart_id}_intc 3>, <&cpu{hart_id}_intc 7>" for hart_id in range(self.nr_harts)] | |
| cpu_intc_head = " interrupts-extended = "; | |
| cpu_intc_str = cpu_intc_head | |
| for each_intc in zip(cpu_intc_list, list(range(self.nr_harts))): | |
| cpu_intc_str += each_intc[0] | |
| if each_intc[1] != self.nr_harts - 1: | |
| cpu_intc_str += ",\n" + " " * (len(cpu_intc_head)) | |
| return f""" | |
| clint: clint@{self.clint_addr:x} {{ | |
| compatible = "riscv,clint0"; | |
| reg = <{self.__gen_addrsize(self.clint_addr, 2)} {self.__gen_addrsize(0x10000, 2)}>; | |
| {cpu_intc_str}; | |
| }}; | |
| """.strip() | |
| def __gen_plic(self, max_priority: int = 7, ndev: int = 64): | |
| # Reference: https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/interrupt-controller/sifive%2Cplic-1.0.0.yaml | |
| if self.plic_addr is None: | |
| return "" | |
| # 9 is SEI, 11 is MEI | |
| cpu_intc_list = [f"<&cpu{hart_id}_intc 9>, <&cpu{hart_id}_intc 11>" for hart_id in range(self.nr_harts)] | |
| cpu_intc_head = " interrupts-extended = "; | |
| cpu_intc_str = cpu_intc_head | |
| for each_intc in zip(cpu_intc_list, list(range(self.nr_harts))): | |
| cpu_intc_str += each_intc[0] | |
| if each_intc[1] != self.nr_harts - 1: | |
| cpu_intc_str += ",\n" + " " * (len(cpu_intc_head)) | |
| return f""" | |
| plic: plic@{self.plic_addr:x} {{ | |
| compatible = "riscv,plic0"; | |
| reg = <{self.__gen_addrsize(self.plic_addr, 2)} {self.__gen_addrsize(0x400000, 2)}>; | |
| #interrupt-cells = <1>; | |
| interrupt-controller; | |
| {cpu_intc_str}; | |
| riscv,max-priority = <{max_priority}>; | |
| riscv,ndev = <{ndev}>; | |
| }}; | |
| """.strip() | |
| def __gen_reserved_memory(self): | |
| if len(self.reserved_memories) == 0: | |
| return "" | |
| res = f""" | |
| reserved-memory {{ | |
| #address-cells = <2>; | |
| #size-cells = <2>; | |
| ranges; | |
| """ | |
| for ((start, size), idx) in zip(self.reserved_memories, list(range(len(self.reserved_memories)))): | |
| res += self.__indent(f""" | |
| resv{idx}@{start:x} {{ | |
| reg = <{self.__gen_addrsize(start, 2)} {self.__gen_addrsize(size, 2)}>; | |
| no-map; | |
| }}; | |
| """.strip() + "\n") | |
| res += "};" | |
| return res | |
| def __gen_uartlite(self, uartlite_addr, plic_addr): | |
| # https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/serial/xlnx%2Copb-uartlite.yaml | |
| # Uartlite hardware does not handle clock division, so we do | |
| # not specify clocks here | |
| return f""" | |
| serial@{uartlite_addr:x} {{ | |
| compatible = "xlnx,xps-uartlite-1.00.a"; | |
| reg = <{self.__gen_addrsize(uartlite_addr, 2)} {self.__gen_addrsize(0x1000, 2)}>; | |
| interrupts-extended = <&plic {plic_addr}>; | |
| current-speed = <115200>; | |
| xlnx,data-bits = <8>; | |
| xlnx,use-parity = <0>; | |
| }}; | |
| """.strip() | |
| def __gen_nemu_sdhci(self, sd_addr): | |
| return f""" | |
| mmc@{sd_addr:x} {{ | |
| compatible = "nemu,sdhost"; | |
| reg = <{self.__gen_addrsize(sd_addr, 2)} {self.__gen_addrsize(0x1000, 2)}>; | |
| }}; | |
| """.strip() | |
| def __gen_soc(self): | |
| res = f""" | |
| soc {{ | |
| #address-cells = <2>; | |
| #size-cells = <2>; | |
| compatible = "simple-bus"; | |
| ranges; | |
| """ + "\n" | |
| for dev_str in self.soc_devices: | |
| res += self.__indent(dev_str, 4) + "\n\n" | |
| res += "};" | |
| return res.strip() | |
| def add_device(self, dev_str: str): | |
| self.soc_devices.append(dev_str) | |
| def gen_dts(self): | |
| return f""" | |
| /dts-v1/; | |
| / {{ | |
| #address-cells = <2>; | |
| #size-cells = <2>; | |
| compatible = "{self.compatible}"; | |
| model = "{self.model}"; | |
| chosen {{ | |
| {self.__indent(f'bootargs = "{self.bootargs}";', 8) if self.bootargs else ''} | |
| {self.__indent(f'rng-seed = /bits/ 8 <{" ".join(f"0x{b:02x}" for b in self.rng_seed)}>;', 8) if self.rng_seed else ''} | |
| }}; | |
| {self.__indent('\n'.join(self.__gen_memory(start, size) for (start, size) in self.memories))} | |
| {self.__indent(self.__gen_cpus())} | |
| {self.__indent(self.__gen_clint())} | |
| {self.__indent(self.__gen_plic())} | |
| {self.__indent(self.__gen_reserved_memory())} | |
| {self.__indent(self.__gen_soc())} | |
| }}; | |
| """.strip() | |
| if __name__ == "__main__": | |
| gen = DTSGen( | |
| isa_extensions=["i", "m", "a", "f", "d", "c", "zicbom", "zicbop", "zicboz"], | |
| nr_harts=1 | |
| ) | |
| print(gen.gen_dts()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment