blob: 11a9ebc7bb0022853cc22d41a19ae35f075678fc [file] [log] [blame]
# Copyright (c) 2021 The Regents of the University of California
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met: redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer;
# redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution;
# neither the name of the copyright holders nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import os
from typing import Optional
from ...utils.override import overrides
from .simple_board import SimpleBoard
from .abstract_board import AbstractBoard
from ..processors.abstract_processor import AbstractProcessor
from ..memory.abstract_memory_system import AbstractMemorySystem
from ..cachehierarchies.abstract_cache_hierarchy import AbstractCacheHierarchy
from ...isas import ISA
from ...runtime import get_runtime_isa
import m5
from m5.objects import (
Bridge,
PMAChecker,
RiscvLinux,
RiscvRTC,
AddrRange,
IOXBar,
Clint,
Plic,
Terminal,
LupioBLK,
LupioIPI,
LupioPIC,
LupioRNG,
LupioRTC,
LupioTMR,
LupioTTY,
LupV,
AddrRange,
CowDiskImage,
RawDiskImage,
Frequency,
Port,
)
from m5.util.fdthelper import (
Fdt,
FdtNode,
FdtProperty,
FdtPropertyStrings,
FdtPropertyWords,
FdtState,
)
class LupvBoard(SimpleBoard):
"""
A board capable of full system simulation for RISC-V.
This board uses a set of LupIO education-friendly devices.
This board assumes that you will be booting Linux.
**Limitations**
* Only works with classic caches
"""
def __init__(
self,
clk_freq: str,
processor: AbstractProcessor,
memory: AbstractMemorySystem,
cache_hierarchy: AbstractCacheHierarchy,
) -> None:
super().__init__(clk_freq, processor, memory, cache_hierarchy)
if get_runtime_isa() != ISA.RISCV:
raise EnvironmentError(
"RiscvBoard will only work with the RISC-V ISA. Please"
" recompile gem5 with ISA=RISCV."
)
if cache_hierarchy.is_ruby():
raise EnvironmentError("RiscvBoard is not compatible with Ruby")
self.workload = RiscvLinux()
# Initialize all the devices that we want to use on this board
# Interrupt IDS for PIC Device
self._excep_code = { 'INT_SOFT_SUPER': 1, 'INT_TIMER_SUPER': 5,
'INT_TIMER_MACHINE': 7, 'INT_EXT_SUPER': 9,
'INT_EXT_MACHINE': 10 }
self._int_ids = { 'TTY': 1, 'BLK': 2, 'RNG': 3}
# CLINT
self.clint = Clint(pio_addr=0x2000000)
# PLIC
self.pic = Plic(pio_addr=0xc000000)
# LUPIO IPI
self.lupio_ipi = LupioIPI(
pio_addr=0x20001000,
int_type=self._excep_code['INT_SOFT_SUPER'],
num_threads = self.processor.get_num_cores()
)
# LUPIO PIC
self.lupio_pic = LupioPIC(
pio_addr=0x20002000,
int_type = self._excep_code['INT_EXT_SUPER'],
num_threads = self.processor.get_num_cores()
)
#LupV Platform
self.lupv = LupV(
pic = self.lupio_pic,
uart_int_id = self._int_ids['TTY']
)
# LUPIO BLK
self.lupio_blk = LupioBLK(
pio_addr=0x20000000,
platform = self.lupv,
int_id = self._int_ids['BLK']
)
# LUPIO RNG
self.lupio_rng = LupioRNG(
pio_addr=0x20005000,
platform = self.lupv,
int_id = self._int_ids['RNG']
)
# LUPIO RTC
self.lupio_rtc = LupioRTC(pio_addr=0x20004000)
# LUPIO TMR
self.lupio_tmr = LupioTMR(
pio_addr=0x20006000,
int_type = self._excep_code['INT_TIMER_SUPER'],
num_threads = self.processor.get_num_cores()
)
# LUPIO TTY
self.lupio_tty = LupioTTY(
pio_addr=0x20007000,
platform = self.lupv,
int_id = self._int_ids['TTY']
)
self.terminal = Terminal()
pic_srcs = [
self._int_ids['TTY'],
self._int_ids['BLK'],
self._int_ids['RNG']
]
# Set the number of sources to the PIC as 0 because we've removed the
# connections from all the external devices to the PIC, and moved them
# to the LupioPIC. The PIC and CLINT only remain on the board at this
# point for our bbl to use upon startup, and will
# remain unused during the simulation
self.pic.n_src = 0
self.pic.n_contexts = 0
self.lupio_pic.n_src = max(pic_srcs) + 1
self.lupio_pic.num_threads = self.processor.get_num_cores()
self.lupio_tmr.num_threads = self.processor.get_num_cores()
self.clint.num_threads = self.processor.get_num_cores()
# Add the RTC
# TODO: Why 100MHz? Does something else need to change when this does?
self.rtc = RiscvRTC(frequency=Frequency("100MHz"))
self.clint.int_pin = self.rtc.int_pin
# Incoherent I/O bus
self.iobus = IOXBar()
self._on_chip_devices = [
self.clint,
self.pic,
self.lupio_ipi,
self.lupio_pic,
self.lupio_tmr
]
self._off_chip_devices = [
self.lupio_blk,
self.lupio_tty,
self.lupio_rng,
self.lupio_rtc
]
def _setup_io_devices(self) -> None:
"""Connect the I/O devices to the I/O bus"""
for device in self._off_chip_devices:
device.pio = self.iobus.mem_side_ports
self.lupio_blk.dma = self.iobus.cpu_side_ports
for device in self._on_chip_devices:
device.pio = self.get_cache_hierarchy().get_mem_side_port()
self.bridge = Bridge(delay="10ns")
self.bridge.mem_side_port = self.iobus.cpu_side_ports
self.bridge.cpu_side_port = (
self.get_cache_hierarchy().get_mem_side_port()
)
self.bridge.ranges = [
AddrRange(dev.pio_addr, size=dev.pio_size)
for dev in self._off_chip_devices
]
def _setup_pma(self) -> None:
"""Set the PMA devices on each core"""
uncacheable_range = [
AddrRange(dev.pio_addr, size=dev.pio_size)
for dev in self._on_chip_devices + self._off_chip_devices
]
# TODO: Not sure if this should be done per-core like in the example
for cpu in self.get_processor().get_cores():
cpu.get_mmu().pma_checker = PMAChecker(
uncacheable=uncacheable_range
)
@overrides(AbstractBoard)
def has_io_bus(self) -> bool:
return True
@overrides(AbstractBoard)
def get_io_bus(self) -> IOXBar:
return self.iobus
def has_coherent_io(self) -> bool:
return True
def get_mem_side_coherent_io_port(self) -> Port:
return self.iobus.mem_side_ports
@overrides(AbstractBoard)
def setup_memory_ranges(self):
memory = self.get_memory()
mem_size = memory.get_size()
self.mem_ranges = [AddrRange(start=0x80000000, size=mem_size)]
memory.set_memory_range(self.mem_ranges)
def set_workload(
self, bootloader: str, disk_image: str, command: Optional[str] = None
):
"""Setup the full system files
See https://github.com/darchr/lupio-gem5/blob/lupio/README.md
for running the full system, and downloading the right files to do so.
The command passes in a boot loader and disk image, as well as the
script to start the simulaiton.
After the workload is set up, this function will generate the device
tree file and output it to the output directory.
**Limitations**
* Only supports a Linux kernel
* Must use the provided bootloader and disk image as denoted in the
README above.
"""
self.workload.object_file = bootloader
# Set the disk image for the block device to use
image = CowDiskImage(
child=RawDiskImage(read_only=True),
read_only=False
)
image.child.image_file = disk_image
self.lupio_blk.image = image
# Linux boot command flags
kernel_cmd = [
"earlycon console=ttyLIO0",
"root=/dev/lda1",
"ro"
]
self.workload.command_line = " ".join(kernel_cmd)
# Note: This must be called after set_workload because it looks for an
# attribute named "disk" and connects
self._setup_io_devices()
self._setup_pma()
# Default DTB address if bbl is built with --with-dts option
self.workload.dtb_addr = 0x87E00000
# We need to wait to generate the device tree until after the disk is
# set up. Now that the disk and workload are set, we can generate the
# device tree file.
self.generate_device_tree(m5.options.outdir)
self.workload.dtb_filename = os.path.join(
m5.options.outdir, "device.dtb"
)
def generate_device_tree(self, outdir: str) -> None:
"""Creates the dtb and dts files.
Creates two files in the outdir: 'device.dtb' and 'device.dts'
:param outdir: Directory to output the files
"""
state = FdtState(addr_cells=2, size_cells=2, cpu_cells=1)
root = FdtNode("/")
root.append(state.addrCellsProperty())
root.append(state.sizeCellsProperty())
root.appendCompatible(["riscv-virtio"])
for mem_range in self.mem_ranges:
node = FdtNode("memory@%x" % int(mem_range.start))
node.append(FdtPropertyStrings("device_type", ["memory"]))
node.append(
FdtPropertyWords(
"reg",
state.addrCells(mem_range.start)
+ state.sizeCells(mem_range.size()),
)
)
root.append(node)
# See Documentation/devicetree/bindings/riscv/cpus.txt for details.
cpus_node = FdtNode("cpus")
cpus_state = FdtState(addr_cells=1, size_cells=0)
cpus_node.append(cpus_state.addrCellsProperty())
cpus_node.append(cpus_state.sizeCellsProperty())
# Used by the CLINT driver to set the timer frequency. Value taken from
# RISC-V kernel docs (Note: freedom-u540 is actually 1MHz)
cpus_node.append(FdtPropertyWords("timebase-frequency", [100000000]))
for i, core in enumerate(self.get_processor().get_cores()):
node = FdtNode(f"cpu@{i}")
node.append(FdtPropertyStrings("device_type", "cpu"))
node.append(FdtPropertyWords("reg", state.CPUAddrCells(i)))
node.append(FdtPropertyStrings("mmu-type", "riscv,sv48"))
node.append(FdtPropertyStrings("status", "okay"))
node.append(FdtPropertyStrings("riscv,isa", "rv64imafdc"))
# TODO: Should probably get this from the core.
freq = self.clk_domain.clock[0].frequency
node.appendCompatible(["riscv"])
int_phandle = state.phandle(f"cpu@{i}.int_state")
int_node = FdtNode("interrupt-controller")
int_state = FdtState(interrupt_cells=1)
int_phandle = state.phandle(f"cpu@{i}.int_state")
int_node.append(int_state.interruptCellsProperty())
int_node.append(FdtProperty("interrupt-controller"))
int_node.appendCompatible("riscv,cpu-intc")
int_node.append(FdtPropertyWords("phandle", [int_phandle]))
node.append(int_node)
cpus_node.append(node)
root.append(cpus_node)
soc_node = FdtNode("soc")
soc_state = FdtState(addr_cells=2, size_cells=2)
soc_node.append(soc_state.addrCellsProperty())
soc_node.append(soc_state.sizeCellsProperty())
soc_node.append(FdtProperty("ranges"))
soc_node.appendCompatible(["simple-bus"])
# CLINT node
clint = self.clint
clint_node = clint.generateBasicPioDeviceNode(
soc_state, "clint", clint.pio_addr, clint.pio_size
)
int_extended = list()
for i, core in enumerate(self.get_processor().get_cores()):
phandle = state.phandle(f"cpu@{i}.int_state")
int_extended.append(phandle)
int_extended.append(0x3)
int_extended.append(phandle)
int_extended.append(0x7)
clint_node.append(
FdtPropertyWords("interrupts-extended", int_extended)
)
clint_node.appendCompatible(["riscv,clint0"])
soc_node.append(clint_node)
# Clock
clk_node = FdtNode("lupv-clk")
clk_phandle = state.phandle(clk_node)
clk_node.append(FdtPropertyWords("phandle", [clk_phandle]))
clk_node.append(FdtPropertyWords("clock-frequency", [100000000]))
clk_node.append(FdtPropertyWords("#clock-cells", [0]))
clk_node.appendCompatible(["fixed-clock"])
root.append(clk_node)
# LupioTMR
lupio_tmr = self.lupio_tmr
lupio_tmr_node = lupio_tmr.generateBasicPioDeviceNode(soc_state,
"lupio-tmr", lupio_tmr.pio_addr,
lupio_tmr.pio_size)
int_state = FdtState(addr_cells=0, interrupt_cells=1)
lupio_tmr_node.append(FdtPropertyWords("clocks", [clk_phandle]))
int_extended = list()
for i, core in enumerate(self.get_processor().get_cores()):
phandle = state.phandle(f"cpu@{i}.int_state")
int_extended.append(phandle)
int_extended.append(self._excep_code['INT_TIMER_SUPER'])
lupio_tmr_node.append(
FdtPropertyWords("interrupts-extended", int_extended))
lupio_tmr_node.appendCompatible(["lupio,tmr"])
soc_node.append(lupio_tmr_node)
# PLIC node
plic = self.pic
plic_node = plic.generateBasicPioDeviceNode(
soc_state, "plic", plic.pio_addr, plic.pio_size
)
int_state = FdtState(addr_cells=0, interrupt_cells=1)
plic_node.append(int_state.addrCellsProperty())
plic_node.append(int_state.interruptCellsProperty())
phandle = int_state.phandle(plic)
plic_node.append(FdtPropertyWords("phandle", [phandle]))
plic_node.append(FdtPropertyWords("riscv,ndev", 0))
int_extended = list()
for i, core in enumerate(self.get_processor().get_cores()):
phandle = state.phandle(f"cpu@{i}.int_state")
int_extended.append(phandle)
int_extended.append(0xB)
int_extended.append(phandle)
int_extended.append(0x9)
plic_node.append(FdtPropertyWords("interrupts-extended", int_extended))
plic_node.append(FdtProperty("interrupt-controller"))
plic_node.appendCompatible(["riscv,plic0"])
soc_node.append(plic_node)
# LupioIPI Device
lupio_ipi = self.lupio_ipi
lupio_ipi_node = lupio_ipi.generateBasicPioDeviceNode(soc_state,
"lupio-ipi", lupio_ipi.pio_addr,
lupio_ipi.pio_size)
int_extended = list()
for i, core in enumerate(self.get_processor().get_cores()):
phandle = state.phandle(f"cpu@{i}.int_state")
int_extended.append(phandle)
int_extended.append(self._excep_code['INT_SOFT_SUPER'])
lupio_ipi_node.append(
FdtPropertyWords("interrupts-extended", int_extended))
lupio_ipi_node.append(FdtProperty("interrupt-controller"))
lupio_ipi_node.appendCompatible(["lupio,ipi"])
soc_node.append(lupio_ipi_node)
# LupioPIC Device
lupio_pic = self.lupio_pic
lupio_pic_node = lupio_pic.generateBasicPioDeviceNode(soc_state,
"lupio-pic", lupio_pic.pio_addr,
lupio_pic.pio_size)
int_state = FdtState(interrupt_cells=1)
lupio_pic_node.append(int_state.interruptCellsProperty())
phandle = state.phandle(lupio_pic)
lupio_pic_node.append(FdtPropertyWords("phandle", [phandle]))
int_extended = list()
for i, core in enumerate(self.get_processor().get_cores()):
phandle = state.phandle(f"cpu@{i}.int_state")
int_extended.append(phandle)
int_extended.append(self._excep_code['INT_EXT_SUPER'])
lupio_pic_node.append(
FdtPropertyWords("interrupts-extended", int_extended))
lupio_pic_node.append(FdtProperty("interrupt-controller"))
lupio_pic_node.appendCompatible(["lupio,pic"])
soc_node.append(lupio_pic_node)
# LupioBLK Device
lupio_blk = self.lupio_blk
lupio_blk_node = lupio_blk.generateBasicPioDeviceNode(soc_state,
"lupio-blk", lupio_blk.pio_addr,
lupio_blk.pio_size)
lupio_blk_node.appendCompatible(["lupio,blk"])
lupio_blk_node.append(
FdtPropertyWords("interrupts",
[self.lupio_blk.int_id]))
lupio_blk_node.append(
FdtPropertyWords("interrupt-parent",
state.phandle(self.lupio_pic)))
soc_node.append(lupio_blk_node)
# LupioRNG Device
lupio_rng = self.lupio_rng
lupio_rng_node = lupio_rng.generateBasicPioDeviceNode(soc_state,
"lupio-rng", lupio_rng.pio_addr,lupio_rng.pio_size)
lupio_rng_node.appendCompatible(["lupio,rng"])
lupio_rng_node.append(
FdtPropertyWords("interrupts",
[self.lupio_rng.int_id]))
lupio_rng_node.append(
FdtPropertyWords("interrupt-parent",
state.phandle(self.lupio_pic)))
soc_node.append(lupio_rng_node)
# LupioRTC Device
lupio_rtc = self.lupio_rtc
lupio_rtc_node = lupio_rtc.generateBasicPioDeviceNode(
soc_state, "lupio-rtc", lupio_rtc.pio_addr, lupio_rtc.pio_size
)
lupio_rtc_node.appendCompatible(["lupio,rtc"])
soc_node.append(lupio_rtc_node)
# LupioTTY Device
lupio_tty = self.lupio_tty
lupio_tty_node = lupio_tty.generateBasicPioDeviceNode(soc_state,
"lupio-tty", lupio_tty.pio_addr, lupio_tty.pio_size)
lupio_tty_node.appendCompatible(["lupio,tty"])
lupio_tty_node.append(
FdtPropertyWords("interrupts",
[self.lupio_tty.int_id]))
lupio_tty_node.append(
FdtPropertyWords("interrupt-parent",
state.phandle(self.lupio_pic)))
soc_node.append(lupio_tty_node)
root.append(soc_node)
fdt = Fdt()
fdt.add_rootnode(root)
fdt.writeDtsFile(os.path.join(outdir, "device.dts"))
fdt.writeDtbFile(os.path.join(outdir, "device.dtb"))