Source code for luna_soc.generate.c
#
# This file is part of LUNA.
#
# Copyright (c) 2023-2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
"""Generate a C library for SoC designs."""
import datetime
from amaranth import unsigned
from amaranth_soc.memory import MemoryMap, ResourceInfo
from amaranth_soc import csr
from ..gateware.cpu.ic import InterruptMap
from . import introspect
[docs]
class LinkerScript():
def __init__(self, memory_map: MemoryMap, reset_addr: int = 0x00000000):
self.memory_map = memory_map
self.reset_addr = reset_addr
[docs]
def generate(self, file=None, macro_name="SOC_RESOURCES", platform_name="Generic Platform"):
""" Generates a ldscript that holds our primary RAM and ROM regions.
Parameters:
file -- Optional. If provided, this will be treated as the file= argument to the print()
function. This can be used to generate file content instead of printing to the terminal.
"""
def emit(content):
""" Utility function that emits a string to the targeted file. """
print(content, file=file)
# TODO this should be determined by introspection
memories = ["ram", "rom", "blockram", "spiflash",
"bootrom", "scratchpad", "mainram"]
# Insert our automatically generated header.
emit("/**")
emit(" * Linker memory regions.")
emit(" *")
emit(" * Automatically generated by LUNA; edits will be discarded on rebuild.")
emit(" * (Most header files phrase this 'Do not edit.'; be warned accordingly.)")
emit(" *")
emit(f" * Generated: {datetime.datetime.now()}.")
emit(" */")
emit("")
# memory regions
emit("MEMORY")
emit("{")
window: MemoryMap
name: MemoryMap.Name
for window, name, (start, end, ratio) in self.memory_map.windows():
name = name[0]
if name not in memories:
continue
if self.reset_addr >= start and self.reset_addr < end:
start = self.reset_addr
emit(f" {name} : ORIGIN = 0x{start:08x}, LENGTH = 0x{end-start:08x}")
emit("}")
emit("")
[docs]
class Header():
def __init__(self, memory_map: MemoryMap, interrupts: InterruptMap):
self.memory_map = memory_map
self.interrupts = interrupts
[docs]
def generate(self, file=None, macro_name="SOC_RESOURCES", platform_name="Generic Platform"):
""" Generate a C header file that simplifies access to the platform's resources.
Parameters:
macro_name -- Optional. The name of the guard macro for the C header, as a string without spaces.
file -- Optional. If provided, this will be treated as the file= argument to the print()
function. This can be used to generate file content instead of printing to the terminal.
"""
def emit(content):
""" Utility function that emits a string to the targeted file. """
print(content, file=file)
# Create a mapping that maps our register sizes to C types.
types_for_size = {
8: 'uint64_t',
4: 'uint32_t',
2: 'uint16_t',
1: 'uint8_t'
}
# Emit a warning header.
emit("/*")
emit(" * Automatically generated by LUNA; edits will be discarded on rebuild.")
emit(" * (Most header files phrase this 'Do not edit.'; be warned accordingly.)")
emit(" *")
emit(f" * Generated: {datetime.datetime.now()}.")
emit(" */")
emit("\n")
emit(f"#ifndef __{macro_name}_H__")
emit(f"#define __{macro_name}_H__")
emit("")
emit("#include <stdint.h>\n")
emit("#include <stdbool.h>")
emit("")
emit("//")
emit("// Environment Information")
emit("//")
emit("")
emit(f"#define PLATFORM_NAME \"{platform_name}\"")
emit("")
# Emit our constant data for all Minerva CPUs.
# TODO Support vexriscv
self._emit_minerva_basics(emit)
emit("//")
emit("// Peripherals")
emit("//")
window: MemoryMap
window_name: MemoryMap.Name
for window, window_name, (start, end, ratio) in self.memory_map.windows():
base_addr = start
resource_info: ResourceInfo
for resource_info in window.all_resources():
# normalize path and generate name
path = resource_info.path
path = tuple([MemoryMap.Name(n) for n in path[0]] if len(path) == 1 else path)
name = "_".join([str(s[0].lower()) for s in path])
# get resource address & size
start = resource_info.start
addr = base_addr + start
size = resource_info.end - start
# always generate a macro for the resource's address and size
emit(f"#define {name.upper()}_ADDRESS (0x{addr:08x}U)")
emit(f"#define {name.upper()}_SIZE ({size})")
# if the resource is not a register we're done
register: csr.Register = resource_info.resource
if not issubclass(register.__class__, csr.Register):
continue
# generate convenience macros for reading and writing the register
c_type = types_for_size[size]
# Generate a read stub, if useful...
if register._access.readable():
emit(f"static inline {c_type} {name}_read(void) {{")
emit(f" volatile {c_type} *reg = ({c_type} *){name.upper()}_ADDRESS;")
emit(f" return *reg;")
emit(f"}}")
# ... and a write stub.
if register._access.writable():
emit(f"static inline void {name}_write({c_type} value) {{")
emit(f" volatile {c_type} *reg = ({c_type} *){name.upper()}_ADDRESS;")
emit(f" *reg = value;")
emit(f"}}")
emit("")
emit("//")
emit("// Interrupts")
emit("//")
for irq, (name, peripheral) in self.interrupts.items():
# Function that determines if a given unit has an IRQ pending.
emit(f"static inline bool {name}_interrupt_pending(void) {{")
emit(f" return pending_irqs() & (1 << {irq});")
emit(f"}}")
# IRQ masking
emit(f"static inline void {name}_interrupt_enable(void) {{")
emit(f" irq_setmask(irq_getmask() | (1 << {irq}));")
emit(f"}}")
emit(f"static inline void {name}_interrupt_disable(void) {{")
emit(f" irq_setmask(irq_getmask() & ~(1 << {irq}));")
emit(f"}}")
emit("#endif")
emit("")
def _emit_minerva_basics(self, emit):
""" Emits the standard Minerva RISC-V CSR functionality.
Parameters
----------
emit: callable(str)
The function used to print the code lines to the output stream.
"""
emit("#ifndef read_csr")
emit("#define read_csr(reg) ({ unsigned long __tmp; \\")
emit(" asm volatile (\"csrr %0, \" #reg : \"=r\"(__tmp)); \\")
emit(" __tmp; })")
emit("#endif")
emit("")
emit("#ifndef write_csr")
emit("#define write_csr(reg, val) ({ \\")
emit(" asm volatile (\"csrw \" #reg \", %0\" :: \"rK\"(val)); })")
emit("#endif")
emit("")
emit("#ifndef set_csr")
emit("#define set_csr(reg, bit) ({ unsigned long __tmp; \\")
emit(" asm volatile (\"csrrs %0, \" #reg \", %1\" : \"=r\"(__tmp) : \"rK\"(bit)); \\")
emit(" __tmp; })")
emit("#endif")
emit("")
emit("#ifndef clear_csr")
emit("#define clear_csr(reg, bit) ({ unsigned long __tmp; \\")
emit(" asm volatile (\"csrrc %0, \" #reg \", %1\" : \"=r\"(__tmp) : \"rK\"(bit)); \\")
emit(" __tmp; })")
emit("#endif")
emit("")
emit("#ifndef MSTATUS_MIE")
emit("#define MSTATUS_MIE 0x00000008")
emit("#endif")
emit("")
emit("//")
emit("// Minerva headers")
emit("//")
emit("")
emit("static inline uint32_t irq_getie(void)")
emit("{")
emit(" return (read_csr(mstatus) & MSTATUS_MIE) != 0;")
emit("}")
emit("")
emit("static inline void irq_setie(uint32_t ie)")
emit("{")
emit(" if (ie) {")
emit(" set_csr(mstatus, MSTATUS_MIE);")
emit(" } else {")
emit(" clear_csr(mstatus, MSTATUS_MIE);")
emit(" }")
emit("}")
emit("")
emit("static inline uint32_t irq_getmask(void)")
emit("{")
emit(" return read_csr(0x330);")
emit("}")
emit("")
emit("static inline void irq_setmask(uint32_t value)")
emit("{")
emit(" write_csr(0x330, value);")
emit("}")
emit("")
emit("static inline uint32_t pending_irqs(void)")
emit("{")
emit(" return read_csr(0x360);")
emit("}")
emit("")