diff --git a/src/main/java/li/cil/circuity/Main.java b/src/main/java/li/cil/circuity/Main.java new file mode 100644 index 00000000..8cb2588e --- /dev/null +++ b/src/main/java/li/cil/circuity/Main.java @@ -0,0 +1,81 @@ +package li.cil.circuity; + +import li.cil.circuity.api.vm.device.memory.PhysicalMemory; +import li.cil.circuity.vm.device.memory.ByteBufferMemory; +import li.cil.circuity.vm.riscv.R5Board; + +import java.io.BufferedInputStream; +import java.io.FileInputStream; + +public final class Main { + public static void main(final String[] args) throws Exception { + final PhysicalMemory memory = new ByteBufferMemory(64 * 1014 * 1024); + final R5Board board = new R5Board(); + board.addDevice(0x80000000, memory); + + final String firmware = "C:\\Users\\fnuecke\\Documents\\Repositories\\Circuity-1.15\\buildroot\\fw_jump.bin"; + loadProgramFile(memory, 0, firmware); + + final String kernel = "C:\\Users\\fnuecke\\Documents\\Repositories\\Circuity-1.15\\buildroot\\Image"; + loadProgramFile(memory, 0x400000, kernel); + + final UARTReader reader = new UARTReader(board); + final Thread thread = new Thread(reader); + thread.start(); + +// System.out.println("Waiting for VisualVM..."); +// Thread.sleep(10 * 1000); +// System.out.println("Starting!"); + + final long start = System.currentTimeMillis(); + + final int n = 100000; + for (int i = 0; i < n; i++) { + board.step(100000); + } + + final long duration = System.currentTimeMillis() - start; + + System.out.printf("Took: %.2fs\n", duration / 1000.0); + + reader.stop(); + thread.join(); + } + + private static void loadProgramFile(final PhysicalMemory memory, int address, final String path) throws Exception { + try (final FileInputStream is = new FileInputStream(path)) { + final BufferedInputStream bis = new BufferedInputStream(is); + for (int value = bis.read(); value != -1; value = bis.read()) { + memory.store8(address++, (byte) value); + } + } + } + + private static final class UARTReader implements Runnable { + private final R5Board board; + private boolean keepRunning = true; + + private UARTReader(final R5Board board) { + this.board = board; + } + + public void stop() { + keepRunning = false; + } + + @Override + public void run() { + try { + while (keepRunning) { + final int i = board.readValue(); + if (i >= 0) { + System.out.print((char) i); + } + Thread.yield(); + } + } catch (final Throwable t) { + t.printStackTrace(); + } + } + } +} diff --git a/src/main/java/li/cil/circuity/api/vm/Interrupt.java b/src/main/java/li/cil/circuity/api/vm/Interrupt.java new file mode 100644 index 00000000..c9cfd62d --- /dev/null +++ b/src/main/java/li/cil/circuity/api/vm/Interrupt.java @@ -0,0 +1,27 @@ +package li.cil.circuity.api.vm; + +import li.cil.circuity.api.vm.device.InterruptController; + +public final class Interrupt { + public int id; + public InterruptController controller; + + public Interrupt() { + } + + public Interrupt(final int id) { + this.id = id; + } + + public void raiseInterrupt() { + if (controller != null) { + controller.raiseInterrupts(1 << id); + } + } + + public void lowerInterrupt() { + if (controller != null) { + controller.lowerInterrupts(1 << id); + } + } +} diff --git a/src/main/java/li/cil/circuity/api/vm/MemoryMap.java b/src/main/java/li/cil/circuity/api/vm/MemoryMap.java new file mode 100644 index 00000000..ac4df29f --- /dev/null +++ b/src/main/java/li/cil/circuity/api/vm/MemoryMap.java @@ -0,0 +1,45 @@ +package li.cil.circuity.api.vm; + +import li.cil.circuity.api.vm.device.memory.MemoryAccessException; +import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice; + +import javax.annotation.Nullable; +import java.util.OptionalInt; + +/** + * Represents a physical memory mapping of devices. + */ +public interface MemoryMap { + /** + * Tries to find a vacant memory range of the specified size in the specified ranged. + * + * @param start the minimum starting address for the memory range to find (inclusive). + * @param end the maximum starting address for the memory range to find (inclusive). + * @param size the size of the memory range to find. + * @return the address of a free memory range, if one was found. + */ + OptionalInt findFreeRange(final int start, final int end, final int size); + + boolean addDevice(final int address, final MemoryMappedDevice device); + + void removeDevice(final MemoryMappedDevice device); + + int getDeviceAddress(final MemoryMappedDevice device); + + @Nullable + MemoryRange getMemoryRange(final int address); + + void setDirty(final MemoryRange range, final int offset); + + byte load8(final int address) throws MemoryAccessException; + + void store8(final int address, final byte value) throws MemoryAccessException; + + short load16(final int address) throws MemoryAccessException; + + void store16(final int address, final short value) throws MemoryAccessException; + + int load32(final int address) throws MemoryAccessException; + + void store32(final int address, final int value) throws MemoryAccessException; +} diff --git a/src/main/java/li/cil/circuity/api/vm/MemoryRange.java b/src/main/java/li/cil/circuity/api/vm/MemoryRange.java new file mode 100644 index 00000000..baf23ffa --- /dev/null +++ b/src/main/java/li/cil/circuity/api/vm/MemoryRange.java @@ -0,0 +1,52 @@ +package li.cil.circuity.api.vm; + +import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice; + +import java.util.Objects; + +public final class MemoryRange { + public final MemoryMappedDevice device; + public final int start, end; // both are inclusive + + public MemoryRange(final MemoryMappedDevice device, final int start, final int end) { + if (Integer.compareUnsigned(start, end) > 0) { + throw new IllegalArgumentException(); + } + + this.device = device; + this.start = start; + this.end = end; + } + + public MemoryRange(final MemoryMappedDevice device, final int address) { + this(device, address, address + device.getLength() - 1); + } + + public boolean contains(final int address) { + return Integer.compareUnsigned(address, start) >= 0 && Integer.compareUnsigned(address, end) <= 0; + } + + public boolean intersects(final MemoryRange other) { + return Integer.compareUnsigned(start, other.end) <= 0 && Integer.compareUnsigned(end, other.start) >= 0; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final MemoryRange that = (MemoryRange) o; + return start == that.start && + end == that.end && + device.equals(that.device); + } + + @Override + public int hashCode() { + return Objects.hash(device, start, end); + } + + @Override + public String toString() { + return String.format("%s@[%x-%x]", device, start, end); + } +} diff --git a/src/main/java/li/cil/circuity/api/vm/device/Device.java b/src/main/java/li/cil/circuity/api/vm/device/Device.java new file mode 100644 index 00000000..1f34799a --- /dev/null +++ b/src/main/java/li/cil/circuity/api/vm/device/Device.java @@ -0,0 +1,4 @@ +package li.cil.circuity.api.vm.device; + +public interface Device { +} diff --git a/src/main/java/li/cil/circuity/api/vm/device/InterruptController.java b/src/main/java/li/cil/circuity/api/vm/device/InterruptController.java new file mode 100644 index 00000000..d8700ff0 --- /dev/null +++ b/src/main/java/li/cil/circuity/api/vm/device/InterruptController.java @@ -0,0 +1,26 @@ +package li.cil.circuity.api.vm.device; + +/** + * Interrupt controllers expose an API to set and unset some interrupts from the outside. + */ +public interface InterruptController extends Device { + /** + * Mark the interrupts represented by the set bits in the specified mask as active. + *

+ * In the mask each registered interrupt is represented by a single bit, at the location of its id. So if interrupts + * 1 and 4 are to be made active, the mask would be 0b00000101. + * + * @param mask the mask of interrupts to set active. + */ + void raiseInterrupts(final int mask); + + /** + * Mark the interrupts represented by the set bits in the specified mask as inactive. + *

+ * In the mask each registered interrupt is represented by a single bit, at the location of its id. So if interrupts + * 1 and 4 are to be made inactive, the mask would be 0b00000101. + * + * @param mask the mask of interrupts to set inactive. + */ + void lowerInterrupts(final int mask); +} diff --git a/src/main/java/li/cil/circuity/api/vm/device/InterruptSource.java b/src/main/java/li/cil/circuity/api/vm/device/InterruptSource.java new file mode 100644 index 00000000..282c394d --- /dev/null +++ b/src/main/java/li/cil/circuity/api/vm/device/InterruptSource.java @@ -0,0 +1,7 @@ +package li.cil.circuity.api.vm.device; + +import li.cil.circuity.api.vm.Interrupt; + +public interface InterruptSource extends Device { + Iterable getInterrupts(); +} diff --git a/src/main/java/li/cil/circuity/api/vm/device/Interrupter.java b/src/main/java/li/cil/circuity/api/vm/device/Interrupter.java new file mode 100644 index 00000000..e22ecb2d --- /dev/null +++ b/src/main/java/li/cil/circuity/api/vm/device/Interrupter.java @@ -0,0 +1,44 @@ +package li.cil.circuity.api.vm.device; + +/** + * An interrupter is an interrupt controller that will raise some interrupt based on interrupts + * it received. + *

+ * Implementations track a list of registered interrupts to allow devices being connected to the + * controller without these devices having to know of each other. Additionally, this list + * of registered interrupt ids shall be persisted to allow devices to reclaim their interrupt + * ids after a load. Implementations may decide to only retain ids for reclaiming for one + * load iteration, i.e. any ids that have not been reclaimed after a load when the next save + * occurs may be freed for regular registration again. + */ +public interface Interrupter extends InterruptController { + /** + * Register a new interrupt. + *

+ * Use this to reserve a new interrupt with an arbitrary id. + * + * @return the id of the registered interrupt; -1 if no more interrupts can be registered. + */ + int registerInterrupt(); + + /** + * Register an interrupt with a specific id. + *

+ * Use this to reclaim an interrupt ID after state has been restored for example, i.e. a savegame has been loaded. + * + * @param id the id to reclaim. + * @return true if this id had not yet been claimed; false otherwise. + */ + boolean registerInterrupt(final int id); + + /** + * Release an interrupt id. + *

+ * This can be used to return the lease on an interrupt id to the controller so it can be handed out in future + * {@link #registerInterrupt()} calls. Typically this should be called by devices that have been disconnected + * from the controller for any reason. + * + * @param id the id to return to the pool of available ids. + */ + void releaseInterrupt(final int id); +} diff --git a/src/main/java/li/cil/circuity/api/vm/device/Resettable.java b/src/main/java/li/cil/circuity/api/vm/device/Resettable.java new file mode 100644 index 00000000..01c70c47 --- /dev/null +++ b/src/main/java/li/cil/circuity/api/vm/device/Resettable.java @@ -0,0 +1,5 @@ +package li.cil.circuity.api.vm.device; + +public interface Resettable { + void reset(); +} diff --git a/src/main/java/li/cil/circuity/api/vm/device/Steppable.java b/src/main/java/li/cil/circuity/api/vm/device/Steppable.java new file mode 100644 index 00000000..e8dfdd8d --- /dev/null +++ b/src/main/java/li/cil/circuity/api/vm/device/Steppable.java @@ -0,0 +1,5 @@ +package li.cil.circuity.api.vm.device; + +public interface Steppable extends Device { + void step(final int cycles); +} diff --git a/src/main/java/li/cil/circuity/api/vm/device/memory/MemoryAccessException.java b/src/main/java/li/cil/circuity/api/vm/device/memory/MemoryAccessException.java new file mode 100644 index 00000000..a81b4a89 --- /dev/null +++ b/src/main/java/li/cil/circuity/api/vm/device/memory/MemoryAccessException.java @@ -0,0 +1,13 @@ +package li.cil.circuity.api.vm.device.memory; + +public class MemoryAccessException extends Exception { + private final int address; + + public MemoryAccessException(final int address) { + this.address = address; + } + + public int getAddress() { + return address; + } +} diff --git a/src/main/java/li/cil/circuity/api/vm/device/memory/MemoryMappedDevice.java b/src/main/java/li/cil/circuity/api/vm/device/memory/MemoryMappedDevice.java new file mode 100644 index 00000000..d84916d9 --- /dev/null +++ b/src/main/java/li/cil/circuity/api/vm/device/memory/MemoryMappedDevice.java @@ -0,0 +1,43 @@ +package li.cil.circuity.api.vm.device.memory; + +import li.cil.circuity.api.vm.MemoryMap; +import li.cil.circuity.api.vm.device.Device; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * {@link MemoryMappedDevice}s can be registered with a {@link MemoryMap} + * so that they can be accessed via a memory range using the same mechanisms used for accessing RAM. + */ +public interface MemoryMappedDevice extends Device { + Logger LOGGER = LogManager.getLogger(); + + int getLength(); + + default byte load8(final int offset) throws MemoryAccessException { + LOGGER.debug("Unsupported device read access in [{}] at [{}].", getClass().getSimpleName(), offset); + return 0; + } + + default void store8(final int offset, final byte value) throws MemoryAccessException { + LOGGER.debug("Unsupported device write access in [{}] at [{}].", getClass().getSimpleName(), offset); + } + + default short load16(final int offset) throws MemoryAccessException { + LOGGER.debug("Unsupported device read access in [{}] at [{}].", getClass().getSimpleName(), offset); + return 0; + } + + default void store16(final int offset, final short value) throws MemoryAccessException { + LOGGER.debug("Unsupported device write access in [{}] at [{}].", getClass().getSimpleName(), offset); + } + + default int load32(final int offset) throws MemoryAccessException { + LOGGER.debug("Unsupported device read access in [{}] at [{}].", getClass().getSimpleName(), offset); + return 0; + } + + default void store32(final int offset, final int value) throws MemoryAccessException { + LOGGER.debug("Unsupported device write access in [{}] at [{}].", getClass().getSimpleName(), offset); + } +} diff --git a/src/main/java/li/cil/circuity/api/vm/device/memory/MemoryStream.java b/src/main/java/li/cil/circuity/api/vm/device/memory/MemoryStream.java new file mode 100644 index 00000000..eaaff865 --- /dev/null +++ b/src/main/java/li/cil/circuity/api/vm/device/memory/MemoryStream.java @@ -0,0 +1,11 @@ +package li.cil.circuity.api.vm.device.memory; + +public interface MemoryStream { + boolean canLoad16(); + + short load16() throws MemoryAccessException; + + boolean canLoad32(); + + int load32() throws MemoryAccessException; +} diff --git a/src/main/java/li/cil/circuity/api/vm/device/memory/PhysicalMemory.java b/src/main/java/li/cil/circuity/api/vm/device/memory/PhysicalMemory.java new file mode 100644 index 00000000..b543f4bb --- /dev/null +++ b/src/main/java/li/cil/circuity/api/vm/device/memory/PhysicalMemory.java @@ -0,0 +1,12 @@ +package li.cil.circuity.api.vm.device.memory; + +import li.cil.circuity.api.vm.MemoryMap; + +/** + * Instances marked with this interface can be treated as random-access memory. + *

+ * {@link MemoryMap}s may use this to decide whether a memory + * region can be stored in a translation lookaside buffer. + */ +public interface PhysicalMemory extends MemoryMappedDevice { +} diff --git a/src/main/java/li/cil/circuity/api/vm/device/rtc/RealTimeCounter.java b/src/main/java/li/cil/circuity/api/vm/device/rtc/RealTimeCounter.java new file mode 100644 index 00000000..1ddb369e --- /dev/null +++ b/src/main/java/li/cil/circuity/api/vm/device/rtc/RealTimeCounter.java @@ -0,0 +1,9 @@ +package li.cil.circuity.api.vm.device.rtc; + +import li.cil.circuity.api.vm.device.Device; + +public interface RealTimeCounter extends Device { + long getTime(); + + int getFrequency(); +} diff --git a/src/main/java/li/cil/circuity/api/vm/device/rtc/SystemTimeRealTimeCounter.java b/src/main/java/li/cil/circuity/api/vm/device/rtc/SystemTimeRealTimeCounter.java new file mode 100644 index 00000000..1a53c870 --- /dev/null +++ b/src/main/java/li/cil/circuity/api/vm/device/rtc/SystemTimeRealTimeCounter.java @@ -0,0 +1,27 @@ +package li.cil.circuity.api.vm.device.rtc; + +public final class SystemTimeRealTimeCounter implements RealTimeCounter { + private static final SystemTimeRealTimeCounter INSTANCE = new SystemTimeRealTimeCounter(); + + private static final int NANOSECONDS_PER_SECOND = 1_000_000_000; + private static final int FREQUENCY = 1000; // 10_000_000 + + public static RealTimeCounter create() { + return INSTANCE; + } + + @Override + public long getTime() { + return System.currentTimeMillis(); +// final long milliseconds = System.currentTimeMillis(); +// final long seconds = milliseconds / 1000; +// final long nanoseconds = System.nanoTime() % NANOSECONDS_PER_SECOND; +// +// return seconds * FREQUENCY + (nanoseconds / (NANOSECONDS_PER_SECOND / FREQUENCY)); + } + + @Override + public int getFrequency() { + return FREQUENCY; + } +} diff --git a/src/main/java/li/cil/circuity/api/vm/devicetree/DeviceNames.java b/src/main/java/li/cil/circuity/api/vm/devicetree/DeviceNames.java new file mode 100644 index 00000000..dbd13ee5 --- /dev/null +++ b/src/main/java/li/cil/circuity/api/vm/devicetree/DeviceNames.java @@ -0,0 +1,107 @@ +package li.cil.circuity.api.vm.devicetree; + +import li.cil.circuity.vm.devicetree.DeviceTreeRegistry; + +/** + * List of commonly used @{@link DeviceTreeRegistry} node names. + *

Sourced Devicetree Specification 0.3, available at https://github.com/devicetree-org/devicetree-specification/releases

+ */ +@SuppressWarnings({"unused", "SpellCheckingInspection"}) +public final class DeviceNames { + public static final String ADC = "adc"; + public static final String ACCELEROMETER = "accelerometer"; + public static final String ATM = "atm"; + public static final String AUDIO_CODEC = "audio-codec"; + public static final String AUDIO_CONTROLLER = "audio-controller"; + public static final String BACKLIGHT = "backlight"; + public static final String BLUETOOTH = "bluetooth"; + public static final String BUS = "bus"; + public static final String CACHE_CONTROLLER = "cache-controller"; + public static final String CAMERA = "camera"; + public static final String CAN = "can"; + public static final String CHARGER = "charger"; + public static final String CLOCK = "clock"; + public static final String CLOCK_CONTROLLER = "clock-controller"; + public static final String COMPACT_FLASH = "compact-flash"; + public static final String CPU = "cpu"; + public static final String CPUS = "cpus"; + public static final String CRYPTO = "crypto"; + public static final String DISK = "disk"; + public static final String DISPLAY = "display"; + public static final String DMA_CONTROLLER = "dma-controller"; + public static final String DSI = "dsi"; + public static final String DSP = "dsp"; + public static final String EEPROM = "eeprom"; + public static final String EFUSE = "efuse"; + public static final String ENDPOINT = "endpoint"; + public static final String ETHERNET = "ethernet"; + public static final String ETHERNET_PHY = "ethernet-phy"; + public static final String FDC = "fdc"; + public static final String FLASH = "flash"; + public static final String GNSS = "gnss"; + public static final String GPIO = "gpio"; + public static final String GPU = "gpu"; + public static final String GYROMETER = "gyrometer"; + public static final String HDMI = "hdmi"; + public static final String HWLOCK = "hwlock"; + public static final String I2C = "i2c"; + public static final String I2C_MUX = "i2c-mux"; + public static final String IDE = "ide"; + public static final String INTERRUPT_CONTROLLER = "interrupt-controller"; + public static final String IOMMU = "iommu"; + public static final String ISA = "isa"; + public static final String KEYBOARD = "keyboard"; + public static final String KEY = "key"; + public static final String KEYS = "keys"; + public static final String LCD_CONTROLLER = "lcd-controller"; + public static final String LED = "led"; + public static final String LEDS = "leds"; + public static final String LED_CONTROLLER = "led-controller"; + public static final String LIGHT_SENSOR = "light-sensor"; + public static final String MAGNETOMETER = "magnetometer"; + public static final String MAILBOX = "mailbox"; + public static final String MDIO = "mdio"; + public static final String MEMORY = "memory"; + public static final String MEMORY_CONTROLLER = "memory-controller"; + public static final String MMC = "mmc"; + public static final String MMC_SLOT = "mmc-slot"; + public static final String MOUSE = "mouse"; + public static final String NAND_CONTROLLER = "nand-controller"; + public static final String NVRAM = "nvram"; + public static final String OSCILLATOR = "oscillator"; + public static final String PARALLEL = "parallel"; + public static final String PC_CARD = "pc-card"; + public static final String PCI = "pci"; + public static final String PCIE = "pcie"; + public static final String PHY = "phy"; + public static final String PINCTRL = "pinctrl"; + public static final String PMIC = "pmic"; + public static final String PMU = "pmu"; + public static final String PORT = "port"; + public static final String PORTS = "ports"; + public static final String POWER_MONITOR = "power-monitor"; + public static final String PWM = "pwm"; + public static final String REGULATOR = "regulator"; + public static final String RESET_CONTROLLER = "reset-controller"; + public static final String RNG = "rng"; + public static final String RTC = "rtc"; + public static final String SATA = "sata"; + public static final String SCSI = "scsi"; + public static final String SERIAL = "serial"; + public static final String SOUND = "sound"; + public static final String SPI = "spi"; + public static final String SRAM_CONTROLLER = "sram-controller"; + public static final String SSI_CONTROLLER = "ssi-controller"; + public static final String SYSCON = "syscon"; + public static final String TEMPERATURE_SENSOR = "temperature-sensor"; + public static final String TIMER = "timer"; + public static final String TOUCHSCREEN = "touchscreen"; + public static final String TPM = "tpm"; + public static final String USB = "usb"; + public static final String USB_HUB = "usb-hub"; + public static final String USB_PHY = "usb-phy"; + public static final String VIDEO_CODEC = "video-codec"; + public static final String VME = "vme"; + public static final String WATCHDOG = "watchdog"; + public static final String WIFI = "wifi"; +} diff --git a/src/main/java/li/cil/circuity/api/vm/devicetree/DeviceTree.java b/src/main/java/li/cil/circuity/api/vm/devicetree/DeviceTree.java new file mode 100644 index 00000000..735a9dfd --- /dev/null +++ b/src/main/java/li/cil/circuity/api/vm/devicetree/DeviceTree.java @@ -0,0 +1,42 @@ +package li.cil.circuity.api.vm.devicetree; + +import li.cil.circuity.api.vm.device.Device; +import li.cil.circuity.vm.devicetree.FlattenedDeviceTree; + +import javax.annotation.Nullable; +import java.util.function.Consumer; + +public interface DeviceTree { + FlattenedDeviceTree flatten(); + + int createPHandle(final Device device); + + int getPHandle(final Device device); + + DeviceTree find(final String path); + + DeviceTree addProp(final String name, final Object... values); + + DeviceTree getChild(final String name, @Nullable final String address); + + default DeviceTree getChild(final String name, final int address) { + return getChild(name, String.format("%x", address)); + } + + default DeviceTree getChild(final String name) { + return getChild(name, null); + } + + default DeviceTree putChild(final String name, @Nullable final String address, final Consumer builder) { + builder.accept(getChild(name, address)); + return this; + } + + default DeviceTree putChild(final String name, final int address, final Consumer builder) { + return putChild(name, String.format("%x", address), builder); + } + + default DeviceTree putChild(final String name, final Consumer builder) { + return putChild(name, null, builder); + } +} diff --git a/src/main/java/li/cil/circuity/api/vm/devicetree/DeviceTreePropertyNames.java b/src/main/java/li/cil/circuity/api/vm/devicetree/DeviceTreePropertyNames.java new file mode 100644 index 00000000..8639fa0a --- /dev/null +++ b/src/main/java/li/cil/circuity/api/vm/devicetree/DeviceTreePropertyNames.java @@ -0,0 +1,31 @@ +package li.cil.circuity.api.vm.devicetree; + +import li.cil.circuity.vm.devicetree.DeviceTreeRegistry; + +/** + *

List of well-known device tree node properties.

+ *

Sourced Devicetree Specification 0.3, available at https://github.com/devicetree-org/devicetree-specification/releases

+ * + * @see DeviceTreeRegistry + */ +@SuppressWarnings({"unused", "SpellCheckingInspection"}) +public final class DeviceTreePropertyNames { + public static final String COMPATIBLE = "compatible"; + public static final String MODEL = "model"; + public static final String PHANDLE = "phandle"; + public static final String STATUS = "status"; + public static final String NUM_ADDRESS_CELLS = "#address-cells"; + public static final String NUM_SIZE_CELLS = "#size-cells"; + public static final String REG = "reg"; + public static final String VIRTUAL_REG = "virtual-reg"; + public static final String RANGES = "ranges"; + public static final String DMA_RANGES = "dma-ranges"; + public static final String DEVICE_TYPE = "device_type"; + public static final String INTERRUPTS = "interrupts"; + public static final String INTERRUPT_PARENT = "interrupt-parent"; + public static final String INTERRUPTS_EXTENDED = "interrupts-extended"; + public static final String NUM_INTERRUPT_CELLS = "#interrupt-cells"; + public static final String INTERRUPT_CONTROLLER = "interrupt-controller"; + public static final String INTERRUPT_MAP = "interrupt-map"; + public static final String INTERRUPT_MAP_MASK = "interrupt-map-mask"; +} diff --git a/src/main/java/li/cil/circuity/api/vm/devicetree/DeviceTreeProvider.java b/src/main/java/li/cil/circuity/api/vm/devicetree/DeviceTreeProvider.java new file mode 100644 index 00000000..1c741c28 --- /dev/null +++ b/src/main/java/li/cil/circuity/api/vm/devicetree/DeviceTreeProvider.java @@ -0,0 +1,18 @@ +package li.cil.circuity.api.vm.devicetree; + +import li.cil.circuity.api.vm.MemoryMap; +import li.cil.circuity.api.vm.device.Device; + +import java.util.Optional; + +public interface DeviceTreeProvider { + default Optional getName(final Device device) { + return Optional.empty(); + } + + default Optional createNode(final DeviceTree root, final MemoryMap memoryMap, final Device device, final String deviceName) { + return Optional.empty(); + } + + void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device); +} diff --git a/src/main/java/li/cil/circuity/vm/SimpleMemoryMap.java b/src/main/java/li/cil/circuity/vm/SimpleMemoryMap.java new file mode 100644 index 00000000..8b9121f0 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/SimpleMemoryMap.java @@ -0,0 +1,150 @@ +package li.cil.circuity.vm; + +import li.cil.circuity.api.vm.MemoryMap; +import li.cil.circuity.api.vm.MemoryRange; +import li.cil.circuity.api.vm.device.memory.MemoryAccessException; +import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; +import java.util.OptionalInt; + +public final class SimpleMemoryMap implements MemoryMap { + private final Map devices = new HashMap<>(); + + // For device IO we often get sequential access to the same range/device, so we remember the last one as a cache. + private MemoryRange cache; + + @Override + public OptionalInt findFreeRange(final int start, final int end, final int size) { + if (size == 0) { + return OptionalInt.empty(); + } + + if (Integer.compareUnsigned(end, start) < 0) { + return OptionalInt.empty(); + } + + if (Integer.compareUnsigned(end - start, size - 1) < 0) { + return OptionalInt.empty(); + } + + if (Integer.toUnsignedLong(start) + Integer.toUnsignedLong(size) >= 0xFFFFFFFFL) { + return OptionalInt.empty(); + } + + final MemoryRange candidateRange = new MemoryRange(null, start, start + size); + for (final MemoryRange existingRange : devices.values()) { + if (existingRange.intersects(candidateRange)) { + if (existingRange.end != 0xFFFFFFFF) { // Avoid overflow. + return findFreeRange(existingRange.end + 1, end, size); + } else { + return OptionalInt.empty(); + } + } + } + + return OptionalInt.of(start); + } + + @Override + public boolean addDevice(final int address, final MemoryMappedDevice device) { + final MemoryRange deviceRange = new MemoryRange(device, address); + if (devices.values().stream().anyMatch(range -> range.intersects(deviceRange))) { + return false; + } + devices.put(device, deviceRange); + return true; + } + + @Override + public void removeDevice(final MemoryMappedDevice device) { + devices.remove(device); + cache = null; + } + + @Override + public int getDeviceAddress(final MemoryMappedDevice device) { + final MemoryRange range = devices.get(device); + if (range != null) { + return range.start; + } else { + return -1; + } + } + + @Nullable + @Override + public MemoryRange getMemoryRange(final int address) { + // TODO some proper index structure to speed this up? + if (cache != null && cache.contains(address)) { + return cache; + } + + for (final MemoryRange range : devices.values()) { + if (range.contains(address)) { + cache = range; + return range; + } + } + + return null; + } + + @Override + public void setDirty(final MemoryRange range, final int offset) { + // todo implement tracking dirty bits; really need this if we want to add a frame buffer. + } + + @Override + public byte load8(final int address) throws MemoryAccessException { + final MemoryRange range = getMemoryRange(address); + if (range != null) { + return range.device.load8(address - range.start); + } + return 0; + } + + @Override + public void store8(final int address, final byte value) throws MemoryAccessException { + final MemoryRange range = getMemoryRange(address); + if (range != null) { + range.device.store8(address - range.start, value); + } + } + + @Override + public short load16(final int address) throws MemoryAccessException { + final MemoryRange range = getMemoryRange(address); + if (range != null) { + return range.device.load16(address - range.start); + } + return 0; + } + + @Override + public void store16(final int address, final short value) throws MemoryAccessException { + final MemoryRange range = getMemoryRange(address); + if (range != null) { + range.device.store16(address - range.start, value); + } + } + + @Override + public int load32(final int address) throws MemoryAccessException { + final MemoryRange range = getMemoryRange(address); + if (range != null) { + return range.device.load32(address - range.start); + } + return 0; + } + + @Override + public void store32(final int address, final int value) throws MemoryAccessException { + final MemoryRange range = getMemoryRange(address); + if (range != null) { + range.device.store32(address - range.start, value); + } + } +} diff --git a/src/main/java/li/cil/circuity/vm/components/InterruptSourceRegistry.java b/src/main/java/li/cil/circuity/vm/components/InterruptSourceRegistry.java new file mode 100644 index 00000000..a1051fac --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/components/InterruptSourceRegistry.java @@ -0,0 +1,14 @@ +package li.cil.circuity.vm.components; + +public final class InterruptSourceRegistry { + public int registerInterrupt() { + return -1; + } + + public boolean registerInterrupt(final int id) { + return false; + } + + public void releaseInterrupt(final int id) { + } +} diff --git a/src/main/java/li/cil/circuity/vm/device/UART16550A.java b/src/main/java/li/cil/circuity/vm/device/UART16550A.java new file mode 100644 index 00000000..c081a089 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/device/UART16550A.java @@ -0,0 +1,463 @@ +package li.cil.circuity.vm.device; + +import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue; +import it.unimi.dsi.fastutil.bytes.BytePriorityQueue; +import li.cil.circuity.api.vm.Interrupt; +import li.cil.circuity.api.vm.device.InterruptSource; +import li.cil.circuity.api.vm.device.Resettable; +import li.cil.circuity.api.vm.device.Steppable; +import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice; + +import java.util.Collections; + +// TODO Break implementation +@SuppressWarnings("PointlessBitwiseExpression") +public final class UART16550A implements Resettable, Steppable, MemoryMappedDevice, InterruptSource { + private static final int UART_RBR_OFFSET = 0; // Receive buffer register (Read-only) + private static final int UART_THR_OFFSET = 0; // Transmitter holding register (Write-only) + private static final int UART_IER_OFFSET = 1; // Interrupt enable register (Read-write) + private static final int UART_FCR_OFFSET = 2; // FIFO control register (Write-only) + private static final int UART_IIR_OFFSET = 2; // Interrupt identification register (Read-write) + private static final int UART_LCR_OFFSET = 3; // Line control register (Read-write) + private static final int UART_MCR_OFFSET = 4; // Modem control register (Read-write) + private static final int UART_LSR_OFFSET = 5; // Line status register (Read-only) + private static final int UART_MSR_OFFSET = 6; // Modem status register (Read-only) + private static final int UART_SCR_OFFSET = 7; // Scratch register (Read-only) + + private static final int UART_DLL_OFFSET = 0; // Divisor latch register (least significant byte) (Read-write) + private static final int UART_DLM_OFFSET = 1; // Divisor latch register (most significant byte) (Read-write) + + private static final int UART_IER_RDI = 0b0001; // Received data available + private static final int UART_IER_THRI = 0b0010; // Transmitter holding register empty + private static final int UART_IER_RLSI = 0b0100; // Receiver line status register change + private static final int UART_IER_MSI = 0b1000; // Modem status register change + + private static final int UART_IIR_NO_INTERRUPT = 0b00000001; // Any interrupt pending bit + private static final int UART_IIR_ID_MASK = 0b00001110; // Last interrupt type mask + private static final int UART_IIR_MSI = 0x0000; // Modem status change + private static final int UART_IIR_THRI = 0b0010; // Transmitter holding register empty + private static final int UART_IIR_RDI = 0b0100; // Received data available + private static final int UART_IIR_RLSI = 0b0110; // Line status change + private static final int UART_IIR_CTI = 0b1100; // Character timeout + private static final int UART_IIR_NO_FIFO = 0b00000000; // No FIFO + private static final int UART_IIR_UNUSABLE_FIFO = 0b10000000; // Unusable FIFO + private static final int UART_IIR_FIFO_ENABLED = 0b11000000; // FIFO enabled + + private static final int UART_LCR_WORD_LENGTH_8 = 0b00000011; // Data word length = 8 + private static final int UART_LCR_STOP_BITS = 0b00000100; // 0 = 1 stop bit, 1 = 1.5 stop bits (5 bits word) / 2 stop bits (7, 7 or 8 bits word) + private static final int UART_LCR_ODD_PARITY = 0b00001000; // Odd parity + private static final int UART_LCR_EVEN_PARITY = 0b00011000; // Even parity + private static final int UART_LCR_HIGH_PARITY = 0b00101000; // High parity (stick) + private static final int UART_LCR_LOW_PARITY = 0b00111000; // Low parity (stick) + private static final int UART_LCR_BREAK_SIGNAL = 0b01000000; // Break signal enabled + private static final int UART_LCR_DLAB = 0b10000000; // Divisor latch access bit + private static final int UART_LCR_MUTABLE_BITS_MASK = UART_LCR_DLAB; // We only support a few settings... + + private static final int UART_MCR_DTR = 1 << 0; // Data terminal ready + private static final int UART_MCR_RTS = 1 << 1; // Request to send + private static final int UART_MCR_AO1 = 1 << 2; // Auxiliary output 1 + private static final int UART_MCR_AO2 = 1 << 3; // Auxiliary output 2 + private static final int UART_MCR_LBM = 1 << 4; // Loopback mode + + private static final int UART_LSR_DR = 1 << 0; // Receiver data ready + private static final int UART_LSR_OE = 1 << 1; // Overrun error indicator + private static final int UART_LSR_PE = 1 << 2; // Parity error indicator + private static final int UART_LSR_FE = 1 << 3; // Frame error indicator + private static final int UART_LSR_BI = 1 << 4; // Break interrupt indicator + private static final int UART_LSR_THRE = 1 << 5; // Transmit-hold-register empty + private static final int UART_LSR_TEMT = 1 << 6; // Transmitter empty + private static final int UART_LSR_FIFOE = 1 << 7; // Fifo error + private static final int UART_LSR_IRQ_MASK = UART_LSR_BI | UART_LSR_FE | UART_LSR_PE | UART_LSR_OE; + + private static final int UART_MSR_DCTS = 1 << 0; // Change in Clear to send + private static final int UART_MSR_DDSR = 1 << 1; // Change in Data set ready + private static final int UART_MSR_TERI = 1 << 2; // Trailing edge Ring indicator + private static final int UART_MSR_DDCD = 1 << 3; // Change in Carrier detect + private static final int UART_MSR_CTS = 1 << 4; // Clear to send + private static final int UART_MSR_DSR = 1 << 5; // Data set ready + private static final int UART_MSR_RI = 1 << 6; // Ring indicator + private static final int UART_MSR_DCD = 1 << 7; // Carrier detect + private static final int UART_MSR_DIRTY = UART_MSR_DCTS | UART_MSR_DDSR | UART_MSR_TERI | UART_MSR_DDCD; + + private static final int UART_FCR_FE = 1 << 0; // Enable/disable FIFOs + private static final int UART_FCR_RFR = 1 << 1; // Clear receive FIFO + private static final int UART_FCR_XFR = 1 << 2; // Clear transmit FIFO + private static final int UART_FCR_DMS = 1 << 3; // Select DMA mode + private static final int UART_FCR_ITL_MASK = 0b11000000; // Receive FIFO interrupt trigger level mask + private static final int UART_FCR_ITL1 = 0b00000000; // Receive FIFO interrupt trigger at 1 byte + private static final int UART_FCR_ITL2 = 0b01000000; // Receive FIFO interrupt trigger at 4 bytes + private static final int UART_FCR_ITL3 = 0b10000000; // Receive FIFO interrupt trigger at 8 bytes + private static final int UART_FCR_ITL4 = 0b11000000; // Receive FIFO interrupt trigger at 14 bytes + + private static final int UART_DL_2304 = 0x0900; // 50 Baud + private static final int UART_DL_384 = 0x0180; // 300 Baud + private static final int UART_DL_96 = 0x0060; // 1200 Baud + private static final int UART_DL_48 = 0x0030; // 2400 Baud + private static final int UART_DL_24 = 0x0018; // 4800 Baud + private static final int UART_DL_12 = 0x000C; // 9600 Baud + private static final int UART_DL_6 = 0x0006; // 19200 Baud + private static final int UART_DL_3 = 0x0003; // 38400 Baud + private static final int UART_DL_2 = 0x0002; // 57600 Baud + private static final int UART_DL_1 = 0x0001; // 115200 Baud + + private static final int FIFO_QUEUE_CAPACITY = 16; + + private byte rbr; + private byte thr; + private byte ier; + private byte iir; + private byte fcr; + private byte lcr; + private byte mcr; + private byte lsr; + private byte msr; + private byte scr; + private short dl; + + private int triggerLevel; + private final BytePriorityQueue receiveFifo = new ByteArrayFIFOQueue(FIFO_QUEUE_CAPACITY); + private final BytePriorityQueue transmitFifo = new ByteArrayFIFOQueue(FIFO_QUEUE_CAPACITY); + + private boolean interruptUpdatePending; + private boolean transmitInterruptPending; + private boolean timeoutInterruptPending; + private final Interrupt interrupt = new Interrupt(); + + private final Object lock = new Object(); + + public UART16550A() { + reset(); + } + + public Interrupt getInterrupt() { + return interrupt; + } + + public void putByte(final byte value) { + synchronized (lock) { + if ((fcr & UART_FCR_FE) != 0) { + if (receiveFifo.size() < FIFO_QUEUE_CAPACITY) { + receiveFifo.enqueue(value); + } else { + lsr |= UART_LSR_OE; + } + lsr |= UART_LSR_DR; + } else { + if ((lsr & UART_LSR_DR) != 0) { + lsr |= UART_LSR_OE; + } + rbr = value; + lsr |= UART_LSR_DR; + } + + interruptUpdatePending = true; + } + } + + public int getByte() { + synchronized (lock) { + if ((lsr & UART_LSR_TEMT) != 0) { + return -1; + } + + final byte value; + if ((fcr & UART_FCR_FE) != 0) { + value = transmitFifo.dequeueByte(); + if (transmitFifo.isEmpty()) { + lsr |= UART_LSR_THRE | UART_LSR_TEMT; + } + } else { + value = thr; + lsr |= UART_LSR_THRE | UART_LSR_TEMT; + } + + if ((lsr & UART_LSR_THRE) != 0 && !transmitInterruptPending) { + transmitInterruptPending = true; + interruptUpdatePending = true; + } + + return value; + } + } + + @Override + public void reset() { + rbr = 0; + thr = 0; + ier = 0; + iir = (byte) UART_IIR_NO_INTERRUPT; + fcr = 0; + lcr = 0; + mcr = (byte) UART_MCR_AO2; + lsr = (byte) (UART_LSR_THRE | UART_LSR_TEMT); + msr = (byte) (UART_MSR_CTS | UART_MSR_DCD | UART_MSR_DSR); + scr = 0; + dl = UART_DL_12; + + triggerLevel = 1; + + receiveFifo.clear(); + transmitFifo.clear(); + + interruptUpdatePending = false; + transmitInterruptPending = false; + timeoutInterruptPending = false; + interrupt.lowerInterrupt(); + } + + @Override + public void step(final int cycles) { + if (interruptUpdatePending) { + updateInterrupts(); + } + + // TODO set timeout interrupt after 4 char transmit times + } + + @Override + public int getLength() { + return 0x100; + } + + @Override + public byte load8(final int offset) { + switch (offset) { + // case UART_DLL_OFFSET: + case UART_RBR_OFFSET: { + if ((lcr & UART_LCR_DLAB) != 0) { // UART_DLL + return (byte) dl; + } else { // UART_RBR + final byte result; + synchronized (lock) { + if ((fcr & UART_FCR_FE) != 0) { // FIFO enabled + result = receiveFifo.isEmpty() ? 0 : receiveFifo.dequeueByte(); + if (receiveFifo.isEmpty()) { + lsr &= ~(UART_LSR_DR | UART_LSR_BI); + } + timeoutInterruptPending = false; + } else { // No FIFO + result = rbr; + lsr &= ~(UART_LSR_DR | UART_LSR_BI); + } + updateInterrupts(); + } + + if ((mcr & UART_MCR_LBM) == 0) { + // TODO Fire event that input was accepted? + } + + return result; + } + } + + // case UART_DLM_OFFSET: + case UART_IER_OFFSET: { + if ((lcr & UART_LCR_DLAB) != 0) { // UART_DLM + return (byte) (dl >>> 8); + } else { // UART_IER + return ier; + } + } + + case UART_IIR_OFFSET: { + synchronized (lock) { + final byte result = iir; + if ((iir & UART_IIR_ID_MASK) == UART_IIR_THRI) { + transmitInterruptPending = false; + updateInterrupts(); + } + return result; + } + } + + case UART_LCR_OFFSET: { + return lcr; + } + + case UART_MCR_OFFSET: { + return mcr; + } + + case UART_LSR_OFFSET: { + synchronized (lock) { + final byte result = lsr; + if ((lsr & (UART_LSR_BI | UART_LSR_OE)) != 0) { + lsr &= ~(UART_LSR_BI | UART_LSR_OE); + updateInterrupts(); + } + return result; + } + } + + case UART_MSR_OFFSET: { + if ((mcr & UART_MCR_LBM) != 0) { + // Loopback mode -> output = input + return (byte) ((mcr & 0b1100) << 4 | // OUT [3:2] -> [7:6] + (mcr & 0b0010) << 3 | // RTS [1] -> [4] + (mcr & 0b0001) << 5); // DTR [0] -> [5] + } else { + synchronized (lock) { + final byte result = msr; + if ((msr & UART_MSR_DIRTY) != 0) { + msr &= ~UART_MSR_DIRTY; + updateInterrupts(); + } + return result; + } + } + } + + case UART_SCR_OFFSET: { + return scr; + } + } + + return 0; + } + + @Override + public void store8(final int offset, final byte value) { + switch (offset) { + // case UART_DLL_OFFSET: + case UART_THR_OFFSET: { + if ((lcr & UART_LCR_DLAB) != 0) { // UART_DLL + dl = (short) ((dl & 0xFF00) | (value & 0x00FF)); + } else { // UART_RBR + synchronized (lock) { + thr = value; + if ((fcr & UART_FCR_FE) != 0) { + if (transmitFifo.size() >= FIFO_QUEUE_CAPACITY) { + transmitFifo.dequeueByte(); + } + transmitFifo.enqueue(thr); + } + + transmitInterruptPending = false; + lsr &= ~(UART_LSR_THRE | UART_LSR_TEMT); + + updateInterrupts(); + } + } + break; + } + + // case UART_DLM_OFFSET: + case UART_IER_OFFSET: { + if ((lcr & UART_LCR_DLAB) != 0) { // UART_DLM + dl = (short) ((value << 8) | (dl & 0x00FF)); + } else { // UART_IER + synchronized (lock) { + final int changes = ier ^ value; + ier = (byte) (value & 0b1111); + + if ((changes & UART_IER_THRI) != 0) { + transmitInterruptPending = (ier & UART_IER_THRI) != 0 && (lsr & UART_LSR_THRE) != 0; + } + + if (changes != 0) { + updateInterrupts(); + } + } + } + break; + } + + case UART_FCR_OFFSET: { + synchronized (lock) { + final int changes = fcr ^ value; + final boolean forceClear = (changes & UART_FCR_FE) != 0; + + if (forceClear || ((int) value & UART_FCR_RFR) != 0) { + synchronized (receiveFifo) { + lsr &= ~(UART_LSR_DR | UART_LSR_BI); + timeoutInterruptPending = false; + receiveFifo.clear(); + } + } + if (forceClear || ((int) value & UART_FCR_XFR) != 0) { + synchronized (transmitFifo) { + lsr |= UART_LSR_THRE; + transmitInterruptPending = true; + transmitFifo.clear(); + } + } + + fcr = (byte) ((int) value & 0b11001001); + + if (((int) value & UART_FCR_FE) != 0) { + iir |= UART_IIR_FIFO_ENABLED; + switch ((int) value & UART_FCR_ITL_MASK) { + case UART_FCR_ITL1: { + triggerLevel = 1; + break; + } + case UART_FCR_ITL2: { + triggerLevel = 4; + break; + } + case UART_FCR_ITL3: { + triggerLevel = 8; + break; + } + case UART_FCR_ITL4: { + triggerLevel = 14; + break; + } + } + } else { + iir &= ~UART_IIR_FIFO_ENABLED; + } + + updateInterrupts(); + } + break; + } + + case UART_LCR_OFFSET: { + lcr = value; + break; + } + + case UART_MCR_OFFSET: { + mcr = (byte) (value & 0b11111); + break; + } + + case UART_SCR_OFFSET: { + scr = value; + break; + } + } + } + + @Override + public Iterable getInterrupts() { + return Collections.singleton(interrupt); + } + + private void updateInterrupts() { + final int niir; + if ((ier & UART_IER_RLSI) != 0 && (lsr & UART_LSR_IRQ_MASK) != 0) { + niir = UART_IIR_RLSI; + } else if ((ier & UART_IER_RDI) != 0 && timeoutInterruptPending) { + niir = UART_IIR_CTI; + } else if ((ier & UART_IER_RDI) != 0 && (lsr & UART_LSR_DR) != 0 && + ((fcr & UART_FCR_FE) == 0 || receiveFifo.size() > triggerLevel)) { + niir = UART_IIR_RDI; + } else if ((ier & UART_IER_THRI) != 0 && transmitInterruptPending) { + niir = UART_IIR_THRI; + } else if ((ier & UART_IER_MSI) != 0 && (msr & UART_MSR_DIRTY) != 0) { + niir = UART_IIR_MSI; + } else { + niir = UART_IIR_NO_INTERRUPT; + } + + iir = (byte) (niir | (iir & ~(UART_IIR_ID_MASK | UART_IIR_NO_INTERRUPT))); + + if ((iir & UART_IIR_NO_INTERRUPT) != 0) { + interrupt.lowerInterrupt(); + } else { + interrupt.raiseInterrupt(); + } + } +} diff --git a/src/main/java/li/cil/circuity/vm/device/memory/ByteBufferMemory.java b/src/main/java/li/cil/circuity/vm/device/memory/ByteBufferMemory.java new file mode 100644 index 00000000..299f4846 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/device/memory/ByteBufferMemory.java @@ -0,0 +1,79 @@ +package li.cil.circuity.vm.device.memory; + +import li.cil.circuity.api.vm.device.memory.MemoryAccessException; +import li.cil.circuity.api.vm.device.memory.PhysicalMemory; +import li.cil.circuity.vm.device.memory.exception.LoadFaultException; +import li.cil.circuity.vm.device.memory.exception.StoreFaultException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class ByteBufferMemory implements PhysicalMemory { + private final ByteBuffer data; + + public ByteBufferMemory(final int size) { + if ((size & 0b11) != 0) + throw new IllegalArgumentException("size must be a multiple of four"); + this.data = ByteBuffer.allocate(size); + data.order(ByteOrder.LITTLE_ENDIAN); + } + + @Override + public int getLength() { + return data.capacity(); + } + + @Override + public byte load8(final int offset) throws MemoryAccessException { + try { + return data.get(offset); + } catch (final IndexOutOfBoundsException e) { + throw new LoadFaultException(offset); + } + } + + @Override + public void store8(final int offset, final byte value) throws MemoryAccessException { + try { + data.put(offset, value); + } catch (final IndexOutOfBoundsException e) { + throw new StoreFaultException(offset); + } + } + + @Override + public short load16(final int offset) throws MemoryAccessException { + try { + return data.getShort(offset); + } catch (final IndexOutOfBoundsException e) { + throw new LoadFaultException(offset); + } + } + + @Override + public void store16(final int offset, final short value) throws MemoryAccessException { + try { + data.putShort(offset, value); + } catch (final IndexOutOfBoundsException e) { + throw new StoreFaultException(offset); + } + } + + @Override + public int load32(final int offset) throws MemoryAccessException { + try { + return data.getInt(offset); + } catch (final IndexOutOfBoundsException e) { + throw new LoadFaultException(offset); + } + } + + @Override + public void store32(final int offset, final int value) throws MemoryAccessException { + try { + data.putInt(offset, value); + } catch (final IndexOutOfBoundsException e) { + throw new StoreFaultException(offset); + } + } +} diff --git a/src/main/java/li/cil/circuity/vm/device/memory/exception/FetchFaultException.java b/src/main/java/li/cil/circuity/vm/device/memory/exception/FetchFaultException.java new file mode 100644 index 00000000..5405fd8d --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/device/memory/exception/FetchFaultException.java @@ -0,0 +1,9 @@ +package li.cil.circuity.vm.device.memory.exception; + +import li.cil.circuity.api.vm.device.memory.MemoryAccessException; + +public final class FetchFaultException extends MemoryAccessException { + public FetchFaultException(final int address) { + super(address); + } +} diff --git a/src/main/java/li/cil/circuity/vm/device/memory/exception/FetchPageFaultException.java b/src/main/java/li/cil/circuity/vm/device/memory/exception/FetchPageFaultException.java new file mode 100644 index 00000000..214b8c01 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/device/memory/exception/FetchPageFaultException.java @@ -0,0 +1,9 @@ +package li.cil.circuity.vm.device.memory.exception; + +import li.cil.circuity.api.vm.device.memory.MemoryAccessException; + +public final class FetchPageFaultException extends MemoryAccessException { + public FetchPageFaultException(final int address) { + super(address); + } +} diff --git a/src/main/java/li/cil/circuity/vm/device/memory/exception/LoadFaultException.java b/src/main/java/li/cil/circuity/vm/device/memory/exception/LoadFaultException.java new file mode 100644 index 00000000..1a94a59c --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/device/memory/exception/LoadFaultException.java @@ -0,0 +1,9 @@ +package li.cil.circuity.vm.device.memory.exception; + +import li.cil.circuity.api.vm.device.memory.MemoryAccessException; + +public final class LoadFaultException extends MemoryAccessException { + public LoadFaultException(final int address) { + super(address); + } +} diff --git a/src/main/java/li/cil/circuity/vm/device/memory/exception/LoadPageFaultException.java b/src/main/java/li/cil/circuity/vm/device/memory/exception/LoadPageFaultException.java new file mode 100644 index 00000000..813e413f --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/device/memory/exception/LoadPageFaultException.java @@ -0,0 +1,16 @@ +package li.cil.circuity.vm.device.memory.exception; + +import li.cil.circuity.api.vm.device.memory.MemoryAccessException; + +public final class LoadPageFaultException extends MemoryAccessException { + private final int address; + + public LoadPageFaultException(final int address) { + super(address); + this.address = address; + } + + public int getAddress() { + return address; + } +} diff --git a/src/main/java/li/cil/circuity/vm/device/memory/exception/MisalignedFetchException.java b/src/main/java/li/cil/circuity/vm/device/memory/exception/MisalignedFetchException.java new file mode 100644 index 00000000..d550901b --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/device/memory/exception/MisalignedFetchException.java @@ -0,0 +1,16 @@ +package li.cil.circuity.vm.device.memory.exception; + +import li.cil.circuity.api.vm.device.memory.MemoryAccessException; + +public final class MisalignedFetchException extends MemoryAccessException { + private final int address; + + public MisalignedFetchException(final int address) { + super(address); + this.address = address; + } + + public int getAddress() { + return address; + } +} diff --git a/src/main/java/li/cil/circuity/vm/device/memory/exception/MisalignedLoadException.java b/src/main/java/li/cil/circuity/vm/device/memory/exception/MisalignedLoadException.java new file mode 100644 index 00000000..9567c2fb --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/device/memory/exception/MisalignedLoadException.java @@ -0,0 +1,16 @@ +package li.cil.circuity.vm.device.memory.exception; + +import li.cil.circuity.api.vm.device.memory.MemoryAccessException; + +public final class MisalignedLoadException extends MemoryAccessException { + private final int address; + + public MisalignedLoadException(final int address) { + super(address); + this.address = address; + } + + public int getAddress() { + return address; + } +} diff --git a/src/main/java/li/cil/circuity/vm/device/memory/exception/MisalignedStoreException.java b/src/main/java/li/cil/circuity/vm/device/memory/exception/MisalignedStoreException.java new file mode 100644 index 00000000..daf7b8e1 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/device/memory/exception/MisalignedStoreException.java @@ -0,0 +1,16 @@ +package li.cil.circuity.vm.device.memory.exception; + +import li.cil.circuity.api.vm.device.memory.MemoryAccessException; + +public final class MisalignedStoreException extends MemoryAccessException { + private final int address; + + public MisalignedStoreException(final int address) { + super(address); + this.address = address; + } + + public int getAddress() { + return address; + } +} diff --git a/src/main/java/li/cil/circuity/vm/device/memory/exception/StoreFaultException.java b/src/main/java/li/cil/circuity/vm/device/memory/exception/StoreFaultException.java new file mode 100644 index 00000000..81eee434 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/device/memory/exception/StoreFaultException.java @@ -0,0 +1,9 @@ +package li.cil.circuity.vm.device.memory.exception; + +import li.cil.circuity.api.vm.device.memory.MemoryAccessException; + +public final class StoreFaultException extends MemoryAccessException { + public StoreFaultException(final int address) { + super(address); + } +} diff --git a/src/main/java/li/cil/circuity/vm/device/memory/exception/StorePageFaultException.java b/src/main/java/li/cil/circuity/vm/device/memory/exception/StorePageFaultException.java new file mode 100644 index 00000000..246ce407 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/device/memory/exception/StorePageFaultException.java @@ -0,0 +1,9 @@ +package li.cil.circuity.vm.device.memory.exception; + +import li.cil.circuity.api.vm.device.memory.MemoryAccessException; + +public final class StorePageFaultException extends MemoryAccessException { + public StorePageFaultException(final int address) { + super(address); + } +} diff --git a/src/main/java/li/cil/circuity/vm/devicetree/DeviceTreeImpl.java b/src/main/java/li/cil/circuity/vm/devicetree/DeviceTreeImpl.java new file mode 100644 index 00000000..5c6157fb --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/devicetree/DeviceTreeImpl.java @@ -0,0 +1,193 @@ +package li.cil.circuity.vm.devicetree; + +import it.unimi.dsi.fastutil.ints.IntArraySet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.Object2IntArrayMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import li.cil.circuity.api.vm.MemoryMap; +import li.cil.circuity.api.vm.device.Device; +import li.cil.circuity.api.vm.devicetree.DeviceTree; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +final class DeviceTreeImpl implements DeviceTree { + private final DeviceTreeImpl root; + private final Object2IntMap phandles; + private final IntSet createdPhandles; + private final MemoryMap mmu; + + public final String name; // node-name + public final String address; // unit-address + private final List properties = new ArrayList<>(); + public final List children = new ArrayList<>(); + + public DeviceTreeImpl(@Nullable final DeviceTreeImpl parent, final MemoryMap mmu, final String name, final String address) { + this.root = parent != null ? parent.root : this; + this.phandles = parent != null ? parent.phandles : new Object2IntArrayMap<>(); + this.createdPhandles = parent != null ? parent.createdPhandles : new IntArraySet(); + this.mmu = mmu; + this.name = name == null ? "" : validateName(name); + this.address = address; + } + + public DeviceTreeImpl(final MemoryMap mmu) { + this(null, mmu, null, null); + } + + @Override + public FlattenedDeviceTree flatten() { + final FlattenedDeviceTree fdt = new FlattenedDeviceTree(); + flatten(fdt); + return fdt; + } + + @Override + public int createPHandle(final Device device) { + final int phandle = getPHandle(device); + createdPhandles.add(phandle); + return phandle; + } + + @Override + public int getPHandle(final Device device) { + return phandles.computeIntIfAbsent(device, d -> phandles.size() + 1); + } + + @Override + public DeviceTree find(final String path) { + if (path.isEmpty()) { + return this; + } + + final int splitIndex = path.indexOf('/'); + if (splitIndex == 0) { + return root.find(path.substring(1)); + } else { + if (splitIndex > 0) { + final String childName = path.substring(0, splitIndex); + final String pathTail = path.substring(splitIndex + 1); + return getChild(childName).find(pathTail); + } else { + return getChild(path); + } + } + } + + @Override + public DeviceTree addProp(final String name, final Object... values) { + properties.add(new DeviceTreeProperty(name, values)); + return this; + } + + @Override + public DeviceTree putChild(final String name, final String address, final Consumer builder) { + builder.accept(getChild(name, address)); + return this; + } + + @Override + public DeviceTree getChild(final String name, @Nullable final String address) { + final String fullName = fullName(name, address); + for (final DeviceTreeImpl child : children) { + if (fullName.equals(child.fullName())) { + return child; + } + } + + final DeviceTreeImpl child = new DeviceTreeImpl(this, mmu, name, address); + children.add(child); + return child; + } + + private void flatten(final FlattenedDeviceTree fdt) { + if (address == null) { + fdt.beginNode(name); + } else { + fdt.beginNode(name + "@" + address); + } + + for (final DeviceTreeProperty property : properties) { + property.flatten(fdt); + } + + for (final DeviceTree child : children) { + if (child instanceof DeviceTreeImpl) { + final DeviceTreeImpl childBuilder = (DeviceTreeImpl) child; + childBuilder.flatten(fdt); + } + } + + fdt.endNode(); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + toString(sb, 0); + return sb.toString(); + } + + private void toString(final StringBuilder sb, final int indent) { + appendIndent(sb, indent).append(name); + if (address != null) { + sb.append('@').append(address); + } + sb.append(" {\n"); + + for (final DeviceTreeProperty property : properties) { + appendIndent(sb, indent + 1).append(property.toString()).append('\n'); + } + + for (final DeviceTree child : children) { + if (child instanceof DeviceTreeImpl) { + final DeviceTreeImpl childBuilder = (DeviceTreeImpl) child; + childBuilder.toString(sb, indent + 1); + } else { + appendIndent(sb, indent + 1).append(child.toString()).append('\n'); + } + } + + appendIndent(sb, indent).append("}\n"); + } + + private static StringBuilder appendIndent(final StringBuilder sb, final int indent) { + for (int i = 0; i < indent * 4; i++) { + sb.append(' '); + } + return sb; + } + + private String fullName() { + return fullName(name, address); + } + + private static String fullName(final String name, @Nullable final String address) { + return address != null ? String.format("%s@%s", name, address) : name; + } + + private static String validateName(final String value) { + if (value.length() < 1) + throw new IllegalArgumentException("name too short (<1)"); + if (value.length() > 31) + throw new IllegalArgumentException("name too long (>31)"); + for (int i = 0; i < value.length(); i++) { + final char ch = value.charAt(i); + if (!isValidCharacterForNodeName(ch)) { + throw new IllegalArgumentException("invalid character [" + ch + "] in name [" + value + "]"); + } + } + return value; + } + + private static boolean isValidCharacterForNodeName(final int ch) { + return (ch >= '0' && ch <= '9') || + (ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z') || + ch == ',' || ch == '.' || + ch == '_' || ch == '+' || + ch == '-'; + } +} diff --git a/src/main/java/li/cil/circuity/vm/devicetree/DeviceTreeProperty.java b/src/main/java/li/cil/circuity/vm/devicetree/DeviceTreeProperty.java new file mode 100644 index 00000000..f1c17a64 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/devicetree/DeviceTreeProperty.java @@ -0,0 +1,96 @@ +package li.cil.circuity.vm.devicetree; + +public final class DeviceTreeProperty { + private final String name; + private final Object[] values; + + public DeviceTreeProperty(final String name, final Object... values) { + this.name = validateName(name); + this.values = values; + for (final Object value : values) { + if (!(value instanceof String || + value instanceof Integer || + value instanceof Long)) { + throw new IllegalArgumentException(); + } + } + } + + public void flatten(final FlattenedDeviceTree fdt) { + fdt.property(name, values); + } + + @Override + public String toString() { + if (values == null || values.length == 0) { + return name + ";"; + } else { + final StringBuilder sb = new StringBuilder(); + sb.append(name).append(" = "); + boolean isFirst = true; + boolean previousWasNumber = false; + for (final Object current : values) { + final boolean currentIsNumber = current instanceof Number; + if (previousWasNumber && !currentIsNumber) { + sb.append('>'); + } + if (!isFirst) + if (!previousWasNumber || !currentIsNumber) { + sb.append(", "); + } else { + sb.append(" "); + } + if (currentIsNumber && !previousWasNumber) { + sb.append('<'); + } + + isFirst = false; + + if (current instanceof String) { + sb.append('"').append((String) current).append('"'); + } else if (current instanceof Integer) { + sb.append("0x").append(Integer.toUnsignedString((int) current, 16)); + } else if (current instanceof Long) { + sb.append("0x").append(Integer.toUnsignedString((int) (((long) current) >>> 32), 16)); + sb.append("0x").append(Integer.toUnsignedString((int) ((long) current), 16)); + } else { + throw new AssertionError(); + } + + previousWasNumber = currentIsNumber; + } + + if (previousWasNumber) { + sb.append('>'); + } + + sb.append(";"); + + return sb.toString(); + } + } + + private static String validateName(final String value) { + if (value.length() < 1) + throw new IllegalArgumentException("name too short (<1)"); + if (value.length() > 31) + throw new IllegalArgumentException("name too long (>31)"); + for (int i = 0; i < value.length(); i++) { + final char ch = value.charAt(i); + if (!isValidCharacterForPropertyName(ch)) { + throw new IllegalArgumentException("invalid character [" + ch + "] in name [" + value + "]"); + } + } + return value; + } + + private static boolean isValidCharacterForPropertyName(final int ch) { + return (ch >= '0' && ch <= '9') || + (ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z') || + ch == ',' || ch == '.' || + ch == '_' || ch == '+' || + ch == '-' || ch == '?' || + ch == '#'; + } +} diff --git a/src/main/java/li/cil/circuity/vm/devicetree/DeviceTreeRegistry.java b/src/main/java/li/cil/circuity/vm/devicetree/DeviceTreeRegistry.java new file mode 100644 index 00000000..63025dd6 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/devicetree/DeviceTreeRegistry.java @@ -0,0 +1,144 @@ +package li.cil.circuity.vm.devicetree; + +import li.cil.circuity.api.vm.MemoryMap; +import li.cil.circuity.api.vm.device.Device; +import li.cil.circuity.api.vm.device.InterruptSource; +import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice; +import li.cil.circuity.api.vm.device.memory.PhysicalMemory; +import li.cil.circuity.api.vm.devicetree.DeviceTree; +import li.cil.circuity.api.vm.devicetree.DeviceTreeProvider; +import li.cil.circuity.vm.device.UART16550A; +import li.cil.circuity.vm.devicetree.provider.*; +import li.cil.circuity.vm.riscv.device.R5CoreLocalInterrupter; +import li.cil.circuity.vm.riscv.device.R5HostTargetInterface; +import li.cil.circuity.vm.riscv.device.R5PlatformLevelInterruptController; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.Consumer; + +public final class DeviceTreeRegistry { + private static final Logger LOGGER = LogManager.getLogger(); + + private static final Map, DeviceTreeProvider> providers = new HashMap<>(); + private static final Map, DeviceTreeProvider> providerCache = new HashMap<>(); + + static { + addProvider(MemoryMappedDevice.class, MemoryMappedDeviceProvider.INSTANCE); + addProvider(InterruptSource.class, InterruptSourceProvider.INSTANCE); + addProvider(PhysicalMemory.class, PhysicalMemoryProvider.INSTANCE); + addProvider(R5HostTargetInterface.class, HostTargetInterfaceProvider.INSTANCE); + addProvider(R5PlatformLevelInterruptController.class, PlatformLevelInterruptControllerProvider.INSTANCE); + addProvider(R5CoreLocalInterrupter.class, CoreLocalInterrupterProvider.INSTANCE); + addProvider(UART16550A.class, UART16550AProvider.INSTANCE); + // VirtIOProvider.INSTANCE + } + + public static void addProvider(final Class clazz, final DeviceTreeProvider provider) { + providers.put(clazz, provider); + providerCache.clear(); + } + + private static void visitBaseTypes(@Nullable final Class clazz, final Consumer> visitor) { + if (clazz == null) { + return; + } + + visitor.accept(clazz); + visitBaseTypes(clazz.getSuperclass(), visitor); + + final Class[] interfaces = clazz.getInterfaces(); + for (final Class iface : interfaces) { + visitor.accept(iface); + visitBaseTypes(iface, visitor); + } + } + + @Nullable + public static DeviceTreeProvider getProvider(final Device device) { + final Class deviceClass = device.getClass(); + if (providerCache.containsKey(deviceClass)) { + return providerCache.get(deviceClass); + } + + final List relevant = new ArrayList<>(); + final Set> seen = new HashSet<>(); + visitBaseTypes(deviceClass, c -> { + if (seen.add(c) && providers.containsKey(c)) { + relevant.add(providers.get(c)); + } + }); + + if (relevant.size() == 0) { + return null; + } + + if (relevant.size() == 1) { + return relevant.get(0); + } + + // Flip things around so when iterating in visit() we go from least to most specific provider. + Collections.reverse(relevant); + + return new DeviceTreeProvider() { + @Override + public Optional getName(final Device device) { + for (int i = relevant.size() - 1; i >= 0; i--) { + final Optional name = relevant.get(i).getName(device); + if (name.isPresent()) { + return name; + } + } + + return Optional.empty(); + } + + @Override + public Optional createNode(final DeviceTree root, final MemoryMap memoryMap, final Device device, final String deviceName) { + for (int i = relevant.size() - 1; i >= 0; i--) { + final Optional node = relevant.get(i).createNode(root, memoryMap, device, deviceName); + if (node.isPresent()) { + return node; + } + } + + return Optional.empty(); + } + + @Override + public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) { + for (final DeviceTreeProvider provider : relevant) { + provider.visit(node, memoryMap, device); + } + } + }; + } + + public static void visit(final DeviceTree root, final MemoryMap memoryMap, final Device device) { + final DeviceTreeProvider provider = getProvider(device); + if (provider == null) { + LOGGER.warn("No provider for device [{}].", device); + return; + } + + final Optional name = provider.getName(device); + if (!name.isPresent()) { + LOGGER.warn("Failed obtaining name for device [{}].", device); + return; + } + + final Optional node = provider.createNode(root, memoryMap, device, name.get()); + if (!node.isPresent()) { + LOGGER.warn("Failed obtaining node for device [{}].", device); + return; + } + + provider.visit(node.get(), memoryMap, device); + } + + public static DeviceTree create(final MemoryMap mmu) { + return new DeviceTreeImpl(mmu); + } +} diff --git a/src/main/java/li/cil/circuity/vm/devicetree/FlattenedDeviceTree.java b/src/main/java/li/cil/circuity/vm/devicetree/FlattenedDeviceTree.java new file mode 100644 index 00000000..c30d4348 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/devicetree/FlattenedDeviceTree.java @@ -0,0 +1,224 @@ +package li.cil.circuity.vm.devicetree; + +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public final class FlattenedDeviceTree { + private static final int FDT_MAGIC = 0xd00dfeed; + private static final int FDT_VERSION = 0x11; + private static final int FDT_LAST_COMP_VERSION = 0x10; + + private static final int FDT_BEGIN_NODE = 0x00000001; + private static final int FDT_END_NODE = 0x00000002; + private static final int FDT_PROP = 0x00000003; + private static final int FDT_NOP = 0x00000004; + private static final int FDT_END = 0x00000009; + + private static final int FDT_HEADER_SIZE = 10 * 4; // 10 * sizeof(int) + + private List names = new ArrayList<>(); + private IntList nameOffsets = new IntArrayList(); + + private final ByteArrayOutputStream structureBlock = new ByteArrayOutputStream(); + private final DataOutputStream structureBlockWriter = new DataOutputStream(structureBlock); + private int openStructureNodes = 0; + + public void beginNode(final String name) { + try { + structureBlockWriter.writeInt(FDT_BEGIN_NODE); + structureBlockWriter.writeBytes(name); + structureBlockWriter.writeByte(0); + pad(structureBlockWriter, 4); + openStructureNodes++; + } catch (final IOException e) { + throw new AssertionError(e); + } + } + + public void endNode() { + try { + structureBlockWriter.writeInt(FDT_END_NODE); + openStructureNodes--; + } catch (final IOException e) { + throw new AssertionError(e); + } + } + + public void property(final String name, final Object... values) { + try { + int valuesByteLength = 0; + for (final Object value : values) { + if (value instanceof String) { + valuesByteLength += ((String) value).length() + 1; + } else if (value instanceof Integer) { + valuesByteLength += 4; + } else if (value instanceof Long) { + valuesByteLength += 8; + } else { + throw new IllegalArgumentException(); + } + } + + structureBlockWriter.writeInt(FDT_PROP); + structureBlockWriter.writeInt(valuesByteLength); + structureBlockWriter.writeInt(getPropertyNameOffset(name)); + + for (final Object value : values) { + if (value instanceof String) { + structureBlockWriter.writeBytes((String) value); + structureBlockWriter.writeByte(0); + } else if (value instanceof Integer) { + structureBlockWriter.writeInt((int) value); + } else if (value instanceof Long) { + structureBlockWriter.writeInt((int) (((long) value) >>> 32)); + structureBlockWriter.writeInt((int) ((long) value)); + } else { + throw new AssertionError(); + } + } + + pad(structureBlockWriter, 4); + } catch (final IOException e) { + throw new AssertionError(e); + } + } + + public byte[] toDTB() { + if (openStructureNodes != 0) { + throw new IllegalStateException("Unbalanced nodes."); + } + + try { + final ByteArrayOutputStream fdt = new ByteArrayOutputStream(); + final DataOutputStream fdtWriter = new DataOutputStream(fdt); + + // We write blocks in this order: + // - Memory reservation block + // - Structure block + // - Strings block + + // We need to compute the offsets and sizes of these blocks in advance for the header. + int pos = FDT_HEADER_SIZE; + + final int headerPadding = paddingTo(FDT_HEADER_SIZE, 8); + pos += headerPadding; + + final int memoryReservationBlockOffset = pos; + final int memoryReservationBlockLength = 8 + 8; // single 0L, 0L reservation struct. + pos += memoryReservationBlockLength; + + final int structureBlockOffset = pos; + final int structureBlockLength = structureBlockWriter.size() + 4; // + 4 for FDT_END + pos += structureBlockLength; + + final int stringsBlockOffset = pos; + final int stringsBlockLength = stringsBlockLength(); + pos += stringsBlockLength; + + final fdt_header header = new fdt_header(); + header.boot_cpuid_phys = 0; + header.totalsize = pos; + header.size_dt_strings = stringsBlockLength; + header.size_dt_struct = structureBlockLength; + header.off_dt_struct = structureBlockOffset; + header.off_dt_strings = stringsBlockOffset; + header.off_mem_rsvmap = memoryReservationBlockOffset; + header.write(fdtWriter); + + pad(fdtWriter, 8); + + fdtWriter.writeLong(0); // fdt_reserve_entry.address + fdtWriter.writeLong(0); // fdt_reserve_entry.size + + fdtWriter.write(structureBlock.toByteArray()); + fdtWriter.writeInt(FDT_END); + + for (final String name : names) { + fdtWriter.writeBytes(name); + fdtWriter.writeByte(0); + } + + return fdt.toByteArray(); + } catch (final IOException e) { + throw new AssertionError(e); + } + } + + private void pad(final DataOutputStream output, final int alignment) throws IOException { + final int padding = paddingTo(output.size(), alignment); + for (int i = 0; i < padding; i++) { + output.writeByte(0); + } + } + + private int paddingTo(final int size, final int alignment) { + final int mask = alignment - 1; + if ((size & mask) == 0) { + return 0; + } else { + return ((size & ~mask) + alignment) - size; + } + } + + private int getPropertyNameOffset(final String name) { + final int index = names.indexOf(name); + if (index >= 0) { + return nameOffsets.getInt(index); + } else if (names.size() > 0) { + final int nameOffset = stringsBlockLength(); + + names.add(name); + nameOffsets.add(nameOffset); + + return nameOffset; + } else { + names.add(name); + nameOffsets.add(0); + return 0; + } + } + + private int stringsBlockLength() { + if (nameOffsets.size() == 0) { + return 0; + } + + final int lastNameIndex = names.size() - 1; + final int lastNameLength = names.get(lastNameIndex).length(); + final int lastNameOffset = nameOffsets.getInt(lastNameIndex); + return lastNameOffset + lastNameLength + 1; // +1 for \0 separator + } + + private static final class fdt_header { + public final int magic = FDT_MAGIC; + public int totalsize; + public int off_dt_struct; + public int off_dt_strings; + public int off_mem_rsvmap; + public final int version = FDT_VERSION; + public final int last_comp_version = FDT_LAST_COMP_VERSION; + public int boot_cpuid_phys; + public int size_dt_strings; + public int size_dt_struct; + + public void write(final DataOutput output) throws IOException { + output.writeInt(magic); + output.writeInt(totalsize); + output.writeInt(off_dt_struct); + output.writeInt(off_dt_strings); + output.writeInt(off_mem_rsvmap); + output.writeInt(version); + output.writeInt(last_comp_version); + output.writeInt(boot_cpuid_phys); + output.writeInt(size_dt_strings); + output.writeInt(size_dt_struct); + } + } +} diff --git a/src/main/java/li/cil/circuity/vm/devicetree/provider/CoreLocalInterrupterProvider.java b/src/main/java/li/cil/circuity/vm/devicetree/provider/CoreLocalInterrupterProvider.java new file mode 100644 index 00000000..3b1b2261 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/devicetree/provider/CoreLocalInterrupterProvider.java @@ -0,0 +1,29 @@ +package li.cil.circuity.vm.devicetree.provider; + +import li.cil.circuity.api.vm.MemoryMap; +import li.cil.circuity.api.vm.device.Device; +import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice; +import li.cil.circuity.api.vm.devicetree.DeviceTree; +import li.cil.circuity.api.vm.devicetree.DeviceTreePropertyNames; +import li.cil.circuity.api.vm.devicetree.DeviceTreeProvider; + +import java.util.Optional; + +public final class CoreLocalInterrupterProvider implements DeviceTreeProvider { + public static final DeviceTreeProvider INSTANCE = new CoreLocalInterrupterProvider(); + + @Override + public Optional getName(final Device device) { + return Optional.of("clint"); + } + + @Override + public Optional createNode(final DeviceTree root, final MemoryMap memoryMap, final Device device, final String deviceName) { + return Optional.of(root.find("/soc").getChild(deviceName, memoryMap.getDeviceAddress((MemoryMappedDevice) device))); + } + + @Override + public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) { + node.addProp(DeviceTreePropertyNames.COMPATIBLE, "riscv,clint0"); + } +} diff --git a/src/main/java/li/cil/circuity/vm/devicetree/provider/HostTargetInterfaceProvider.java b/src/main/java/li/cil/circuity/vm/devicetree/provider/HostTargetInterfaceProvider.java new file mode 100644 index 00000000..1019e6d7 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/devicetree/provider/HostTargetInterfaceProvider.java @@ -0,0 +1,23 @@ +package li.cil.circuity.vm.devicetree.provider; + +import li.cil.circuity.api.vm.MemoryMap; +import li.cil.circuity.api.vm.device.Device; +import li.cil.circuity.api.vm.devicetree.DeviceTree; +import li.cil.circuity.api.vm.devicetree.DeviceTreePropertyNames; +import li.cil.circuity.api.vm.devicetree.DeviceTreeProvider; + +import java.util.Optional; + +public class HostTargetInterfaceProvider implements DeviceTreeProvider { + public static final DeviceTreeProvider INSTANCE = new HostTargetInterfaceProvider(); + + @Override + public Optional getName(final Device device) { + return Optional.of("htif"); + } + + @Override + public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) { + node.addProp(DeviceTreePropertyNames.COMPATIBLE, "ucb,htif0"); + } +} diff --git a/src/main/java/li/cil/circuity/vm/devicetree/provider/InterruptSourceProvider.java b/src/main/java/li/cil/circuity/vm/devicetree/provider/InterruptSourceProvider.java new file mode 100644 index 00000000..a2141d83 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/devicetree/provider/InterruptSourceProvider.java @@ -0,0 +1,29 @@ +package li.cil.circuity.vm.devicetree.provider; + +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import li.cil.circuity.api.vm.Interrupt; +import li.cil.circuity.api.vm.MemoryMap; +import li.cil.circuity.api.vm.device.Device; +import li.cil.circuity.api.vm.device.InterruptController; +import li.cil.circuity.api.vm.device.InterruptSource; +import li.cil.circuity.api.vm.devicetree.DeviceTree; +import li.cil.circuity.api.vm.devicetree.DeviceTreeProvider; + +public final class InterruptSourceProvider implements DeviceTreeProvider { + public static final InterruptSourceProvider INSTANCE = new InterruptSourceProvider(); + + @Override + public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) { + final InterruptSource interruptSource = (InterruptSource) device; + final IntList interrupts = new IntArrayList(); + for (final Interrupt interrupt : interruptSource.getInterrupts()) { + final InterruptController controller = interrupt.controller; + if (controller != null) { + interrupts.add(node.getPHandle(controller)); + interrupts.add(interrupt.id); + } + } + node.addProp("interrupts-extended", interrupts.toArray()); + } +} diff --git a/src/main/java/li/cil/circuity/vm/devicetree/provider/MemoryMappedDeviceProvider.java b/src/main/java/li/cil/circuity/vm/devicetree/provider/MemoryMappedDeviceProvider.java new file mode 100644 index 00000000..54e3a1f2 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/devicetree/provider/MemoryMappedDeviceProvider.java @@ -0,0 +1,28 @@ +package li.cil.circuity.vm.devicetree.provider; + +import li.cil.circuity.api.vm.MemoryMap; +import li.cil.circuity.api.vm.device.Device; +import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice; +import li.cil.circuity.api.vm.devicetree.DeviceTree; +import li.cil.circuity.api.vm.devicetree.DeviceTreePropertyNames; +import li.cil.circuity.api.vm.devicetree.DeviceTreeProvider; + +import java.util.Optional; + +public class MemoryMappedDeviceProvider implements DeviceTreeProvider { + public static final DeviceTreeProvider INSTANCE = new MemoryMappedDeviceProvider(); + + @Override + public Optional createNode(final DeviceTree root, final MemoryMap memoryMap, final Device device, final String deviceName) { + return Optional.of(root.getChild(deviceName, memoryMap.getDeviceAddress((MemoryMappedDevice) device))); + } + + @Override + public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) { + final MemoryMappedDevice mappedDevice = (MemoryMappedDevice) device; + // TODO in the future when we may want to change bus widths check parent for cell and size cell num. + node.addProp(DeviceTreePropertyNames.REG, + ((long) memoryMap.getDeviceAddress(mappedDevice)) & 0xFFFFFFFFL, + ((long) mappedDevice.getLength()) & 0xFFFFFFFFL); + } +} diff --git a/src/main/java/li/cil/circuity/vm/devicetree/provider/PhysicalMemoryProvider.java b/src/main/java/li/cil/circuity/vm/devicetree/provider/PhysicalMemoryProvider.java new file mode 100644 index 00000000..0605f31e --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/devicetree/provider/PhysicalMemoryProvider.java @@ -0,0 +1,24 @@ +package li.cil.circuity.vm.devicetree.provider; + +import li.cil.circuity.api.vm.MemoryMap; +import li.cil.circuity.api.vm.device.Device; +import li.cil.circuity.api.vm.devicetree.DeviceNames; +import li.cil.circuity.api.vm.devicetree.DeviceTree; +import li.cil.circuity.api.vm.devicetree.DeviceTreePropertyNames; +import li.cil.circuity.api.vm.devicetree.DeviceTreeProvider; + +import java.util.Optional; + +public class PhysicalMemoryProvider implements DeviceTreeProvider { + public static final DeviceTreeProvider INSTANCE = new PhysicalMemoryProvider(); + + @Override + public Optional getName(final Device device) { + return Optional.of(DeviceNames.MEMORY); + } + + @Override + public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) { + node.addProp(DeviceTreePropertyNames.DEVICE_TYPE, "memory"); + } +} diff --git a/src/main/java/li/cil/circuity/vm/devicetree/provider/PlatformLevelInterruptControllerProvider.java b/src/main/java/li/cil/circuity/vm/devicetree/provider/PlatformLevelInterruptControllerProvider.java new file mode 100644 index 00000000..5ae86699 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/devicetree/provider/PlatformLevelInterruptControllerProvider.java @@ -0,0 +1,38 @@ +package li.cil.circuity.vm.devicetree.provider; + +import li.cil.circuity.api.vm.MemoryMap; +import li.cil.circuity.api.vm.device.Device; +import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice; +import li.cil.circuity.api.vm.devicetree.DeviceNames; +import li.cil.circuity.api.vm.devicetree.DeviceTree; +import li.cil.circuity.api.vm.devicetree.DeviceTreePropertyNames; +import li.cil.circuity.api.vm.devicetree.DeviceTreeProvider; +import li.cil.circuity.vm.riscv.device.R5PlatformLevelInterruptController; + +import java.util.Optional; + +public final class PlatformLevelInterruptControllerProvider implements DeviceTreeProvider { + public static final DeviceTreeProvider INSTANCE = new PlatformLevelInterruptControllerProvider(); + + @Override + public Optional getName(final Device device) { + return Optional.of("plic"); + } + + @Override + public Optional createNode(final DeviceTree root, final MemoryMap memoryMap, final Device device, final String deviceName) { + return Optional.of(root.find("/soc").getChild(deviceName, memoryMap.getDeviceAddress((MemoryMappedDevice) device))); + } + + @Override + public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) { + final R5PlatformLevelInterruptController plic = (R5PlatformLevelInterruptController) device; + node + .addProp("#address-cells", 0) + .addProp("#interrupt-cells", 1) + .addProp(DeviceNames.INTERRUPT_CONTROLLER) + .addProp(DeviceTreePropertyNames.COMPATIBLE, "riscv,plic0") + .addProp("riscv,ndev", 31) + .addProp(DeviceTreePropertyNames.PHANDLE, node.createPHandle(plic)); + } +} diff --git a/src/main/java/li/cil/circuity/vm/devicetree/provider/UART16550AProvider.java b/src/main/java/li/cil/circuity/vm/devicetree/provider/UART16550AProvider.java new file mode 100644 index 00000000..f694cd25 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/devicetree/provider/UART16550AProvider.java @@ -0,0 +1,25 @@ +package li.cil.circuity.vm.devicetree.provider; + +import li.cil.circuity.api.vm.MemoryMap; +import li.cil.circuity.api.vm.device.Device; +import li.cil.circuity.api.vm.devicetree.DeviceTree; +import li.cil.circuity.api.vm.devicetree.DeviceTreePropertyNames; +import li.cil.circuity.api.vm.devicetree.DeviceTreeProvider; + +import java.util.Optional; + +public final class UART16550AProvider implements DeviceTreeProvider { + public static final DeviceTreeProvider INSTANCE = new UART16550AProvider(); + + @Override + public Optional getName(final Device device) { + return Optional.of("uart"); + } + + @Override + public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) { + node + .addProp(DeviceTreePropertyNames.COMPATIBLE, "ns16550a") + .addProp("clock-frequency", 3686400); + } +} diff --git a/src/main/java/li/cil/circuity/vm/devicetree/provider/VirtIOProvider.java b/src/main/java/li/cil/circuity/vm/devicetree/provider/VirtIOProvider.java new file mode 100644 index 00000000..f9f3c7e5 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/devicetree/provider/VirtIOProvider.java @@ -0,0 +1,16 @@ +package li.cil.circuity.vm.devicetree.provider; + +import li.cil.circuity.api.vm.MemoryMap; +import li.cil.circuity.api.vm.device.Device; +import li.cil.circuity.api.vm.devicetree.DeviceTree; +import li.cil.circuity.api.vm.devicetree.DeviceTreePropertyNames; +import li.cil.circuity.api.vm.devicetree.DeviceTreeProvider; + +public final class VirtIOProvider implements DeviceTreeProvider { + public static final DeviceTreeProvider INSTANCE = new VirtIOProvider(); + + @Override + public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) { + node.addProp(DeviceTreePropertyNames.COMPATIBLE, "virtio,mmio"); + } +} diff --git a/src/main/java/li/cil/circuity/vm/riscv/R5.java b/src/main/java/li/cil/circuity/vm/riscv/R5.java new file mode 100644 index 00000000..3cb89eb9 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/riscv/R5.java @@ -0,0 +1,176 @@ +package li.cil.circuity.vm.riscv; + +@SuppressWarnings({"unused", "RedundantSuppression", "PointlessBitwiseExpression"}) +public final class R5 { + // Privilege levels. + public static final int PRIVILEGE_U = 0; // User + public static final int PRIVILEGE_S = 1; // Supervisor + public static final int PRIVILEGE_H = 2; // Hypervisor + public static final int PRIVILEGE_M = 3; // Machine + + // Software interrupts. + public static final int USIP_SHIFT = 0; // User + public static final int SSIP_SHIFT = 1; // Supervisor + public static final int HSIP_SHIFT = 2; // Hypervisor + public static final int MSIP_SHIFT = 3; // Machine + + // Timer interrupts. + public static final int UTIP_SHIFT = 4; // User + public static final int STIP_SHIFT = 5; // Supervisor + public static final int HTIP_SHIFT = 6; // Hypervisor + public static final int MTIP_SHIFT = 7; // Machine + + // External interrupts. + public static final int UEIP_SHIFT = 8; // User + public static final int SEIP_SHIFT = 9; // Supervisor + public static final int HEIP_SHIFT = 10; // Hypervisor + public static final int MEIP_SHIFT = 11; // Machine + + // Interrupt masks for mip/mideleg CSRs. + public static final int USIP_MASK = 0b1 << USIP_SHIFT; + public static final int SSIP_MASK = 0b1 << SSIP_SHIFT; + public static final int HSIP_MASK = 0b1 << HSIP_SHIFT; + public static final int MSIP_MASK = 0b1 << MSIP_SHIFT; + public static final int UTIP_MASK = 0b1 << UTIP_SHIFT; + public static final int STIP_MASK = 0b1 << STIP_SHIFT; + public static final int HTIP_MASK = 0b1 << HTIP_SHIFT; + public static final int MTIP_MASK = 0b1 << MTIP_SHIFT; + public static final int UEIP_MASK = 0b1 << UEIP_SHIFT; + public static final int SEIP_MASK = 0b1 << SEIP_SHIFT; + public static final int HEIP_MASK = 0b1 << HEIP_SHIFT; + public static final int MEIP_MASK = 0b1 << MEIP_SHIFT; + + // Machine status (mstatus[h]) CSR masks and offsets. + public static final int STATUS_UIE_SHIFT = 0; // U-mode interrupt-enable bit + public static final int STATUS_SIE_SHIFT = 1; // S-mode interrupt-enable bit + public static final int STATUS_MIE_SHIFT = 3; // M-mode interrupt-enable bit + public static final int STATUS_UPIE_SHIFT = 4; // Prior U-mode interrupt-enabled bit. + public static final int STATUS_SPIE_SHIFT = 5; // Prior S-mode interrupt-enabled bit. + public static final int STATUS_UBE_SHIFT = 6; // U-mode fetch/store endianness (0 = little, 1 = big). + public static final int STATUS_MPIE_SHIFT = 7; // Prior M-mode interrupt-enabled bit. + public static final int STATUS_SPP_SHIFT = 8; // Prior S-mode privilege mode. + public static final int STATUS_MPP_SHIFT = 11; // Prior M-mode privilege mode. + public static final int STATUS_FS_SHIFT = 13; + public static final int STATUS_XS_SHIFT = 15; + public static final int STATUS_MPRV_SHIFT = 17; // Modify PRiVilege. + public static final int STATUS_SUM_SHIFT = 18; // Permit Supervisor User Memory access. + public static final int STATUS_MXR_SHIFT = 19; // Make eXecutable Readable. + public static final int STATUS_TVM_SHIFT = 20; // Trap Virtual Memory + public static final int STATUS_TW_SHIFT = 21; // Timeout Wait + public static final int STATUS_TSR_SHIFT = 22; // Trap SRET + public static final int STATUS_SD_SHIFT = 31; // State Dirty + public static final int STATUS_SBE_SHIFT = 34; // S-mode fetch/store endianness (0 = little, 1 = big). + public static final int STATUS_MBE_SHIFT = 35; // M-mode fetch/store endianness (0 = little, 1 = big). + + public static final int STATUS_UIE_MASK = 1 << STATUS_UIE_SHIFT; + public static final int STATUS_SIE_MASK = 1 << STATUS_SIE_SHIFT; + public static final int STATUS_MIE_MASK = 1 << STATUS_MIE_SHIFT; + public static final int STATUS_UPIE_MASK = 1 << STATUS_UPIE_SHIFT; + public static final int STATUS_SPIE_MASK = 1 << STATUS_SPIE_SHIFT; + public static final int STATUS_UBE_MASK = 1 << STATUS_UBE_SHIFT; + public static final int STATUS_MPIE_MASK = 1 << STATUS_MPIE_SHIFT; + public static final int STATUS_SPP_MASK = 1 << STATUS_SPP_SHIFT; + public static final int STATUS_MPP_MASK = 0b11 << STATUS_MPP_SHIFT; + public static final int STATUS_FS_MASK = 0b11 << STATUS_FS_SHIFT; + public static final int STATUS_XS_MASK = 0b11 << STATUS_XS_SHIFT; + public static final int STATUS_MPRV_MASK = 1 << STATUS_MPRV_SHIFT; + public static final int STATUS_SUM_MASK = 1 << STATUS_SUM_SHIFT; + public static final int STATUS_MXR_MASK = 1 << STATUS_MXR_SHIFT; + public static final int STATUS_TVM_MASK = 1 << STATUS_TVM_SHIFT; + public static final int STATUS_TW_MASK = 1 << STATUS_TW_SHIFT; + public static final int STATUS_TSR_MASK = 1 << STATUS_TSR_SHIFT; + public static final int STATUS_SD_MASK = 1 << STATUS_SD_SHIFT; + public static final long STATUS_SBE_MASK = 1L << STATUS_SBE_SHIFT; + public static final long STATUS_MBE_MASK = 1L << STATUS_MBE_SHIFT; + + // Exception codes used mep/medeleg CSRs. + public static final int EXCEPTION_MISALIGNED_FETCH = 0; + public static final int EXCEPTION_FAULT_FETCH = 1; + public static final int EXCEPTION_ILLEGAL_INSTRUCTION = 2; + public static final int EXCEPTION_BREAKPOINT = 3; + public static final int EXCEPTION_MISALIGNED_LOAD = 4; + public static final int EXCEPTION_FAULT_LOAD = 5; + public static final int EXCEPTION_MISALIGNED_STORE = 6; + public static final int EXCEPTION_FAULT_STORE = 7; + public static final int EXCEPTION_USER_ECALL = 8; + public static final int EXCEPTION_SUPERVISOR_ECALL = 9; + public static final int EXCEPTION_HYPERVISOR_ECALL = 10; + public static final int EXCEPTION_MACHINE_ECALL = 11; + public static final int EXCEPTION_FETCH_PAGE_FAULT = 12; + public static final int EXCEPTION_LOAD_PAGE_FAULT = 13; + public static final int EXCEPTION_STORE_PAGE_FAULT = 15; + + // Highest bit means it's an interrupt/asynchronous exception, otherwise a regular exception. + public static final int INTERRUPT = 1 << 31; + + // Supported counters in [m|s]counteren CSRs. + public static final int MCOUNTERN_CY = 1 << 0; + public static final int MCOUNTERN_TM = 1 << 1; + public static final int MCOUNTERN_IR = 1 << 2; + public static final int MCOUNTERN_HPM3 = 1 << 3; // Contiguous HPM counters up to HPM31 after this. + + // SATP CSR masks. + public static final int SATP_PPN_MASK = 0b0000_0000_0011_1111_1111_1111_1111_1111; + public static final int SATP_ASID_MASK = 0b0111_1111_1100_0000_0000_0000_0000_0000; + public static final int SATP_MODE_MASK = 0b1000_0000_0000_0000_0000_0000_0000_0000; + + // Page sizes are 4KiB (V2p73). + public static final int PAGE_ADDRESS_SHIFT = 12; // 1<<12 == 4096; SATP << 12 == root PTE address + public static final int PAGE_ADDRESS_MASK = (1 << PAGE_ADDRESS_SHIFT) - 1; + + // Page table entry masks. See V2p73. + public static final int PTE_DATA_BITS = 10; // Number of PTE data bits. + public static final int PTE_V_MASK = 0b1 << 0; // Valid flag. + public static final int PTE_R_MASK = 0b1 << 1; // Allow read access. + public static final int PTE_W_MASK = 0b1 << 2; // Allow write access. + public static final int PTE_X_MASK = 0b1 << 3; // Allow code execution (instruction fetch). + public static final int PTE_U_MASK = 0b1 << 4; // Allow access to user mode only. + public static final int PTE_G_MASK = 0b1 << 5; // Global mapping. + public static final int PTE_A_MASK = 0b1 << 6; // Accessed flag (read, written or fetched). + public static final int PTE_D_MASK = 0b1 << 7; // Dirty flag (written). + public static final int PTE_RSW_MASK = 0b11 << 8; // Reserved for supervisor software. + + // Config for SV32 configuration. + public static final int SV32_LEVELS = 2; + public static final int SV32_PTE_SIZE_LOG2 = 2; // => * size == << log2(size) + public static final int SV32_XPN_SIZE = 10; // page number size per level in bits + public static final int SV32_XPN_MASK = (1 << SV32_XPN_SIZE) - 1; + + /** + * Computes flags for the machine ISA CSR given a list of extension letters. + * + * @param extensions the list of extensions to build a mask from. + * @return the mask representing the list of extensions. + */ + public static int isa(final char... extensions) { + int result = 0; + for (final char ch : extensions) { + final char extension = Character.toUpperCase(ch); + if (extension < 'A' || extension > 'Z') { + throw new IllegalArgumentException("Not a valid extension letter: " + extension); + } + + result |= 1 << (extension - 'A'); + } + return result; + } + + /** + * Gets the value for the MXL field in the misa CSR for a given XLEN. + * + * @param xlen the XLEN to get the MXL value for. Must be 32, 64 or 128. + * @return the MXL for the specified XLEN. + */ + public static int mxl(final int xlen) { + switch (xlen) { + case 32: + return 0b01; + case 64: + return 0b10; + case 128: + return 0b11; + default: + throw new IllegalArgumentException(); + } + } +} diff --git a/src/main/java/li/cil/circuity/vm/riscv/R5Assembler.java b/src/main/java/li/cil/circuity/vm/riscv/R5Assembler.java new file mode 100644 index 00000000..e164c0d8 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/riscv/R5Assembler.java @@ -0,0 +1,617 @@ +package li.cil.circuity.vm.riscv; + +import li.cil.circuity.api.vm.device.memory.PhysicalMemory; +import li.cil.circuity.api.vm.device.memory.MemoryAccessException; + +import java.util.*; +import java.util.function.Function; + +public final class R5Assembler { + private static final Map INSTRUCTION_SPEC_MAP = new HashMap<>(); + private static final Map TOKEN_ALIASES = new HashMap<>(); + private static final List FIELD_ARGUMENT_ORDER = new ArrayList<>(); + + static { + new InstructionSpec("LUI", "immU;rd;opcode") + .bind("opcode", 0b0110111) + .postprocess("imm", i -> i >> 12) + .register(); + new InstructionSpec("AUIPC", "immU;rd;opcode") + .bind("opcode", 0b0010111) + .register(); + + new InstructionSpec("JAL", "immJ;rd;opcode") + .bind("opcode", 0b1101111) + .postprocess("imm", i -> i >> 1) + .register(); + new InstructionSpec("JALR", "immI;rs1;funct3;rd;opcode") + .bind("opcode", 0b1100111) + .bind("funct3", 0b000) + .register(); + + new InstructionSpec("BEQ", "immB;rs2;rs1;funct3;opcode") + .bind("opcode", 0b1100011) + .bind("funct3", 0b000) + .register(); + new InstructionSpec("BNE", "immB;rs2;rs1;funct3;opcode") + .bind("opcode", 0b1100011) + .bind("funct3", 0b001) + .register(); + new InstructionSpec("BLT", "immB;rs2;rs1;funct3;opcode") + .bind("opcode", 0b1100011) + .bind("funct3", 0b100) + .register(); + new InstructionSpec("BGE", "immB;rs2;rs1;funct3;opcode") + .bind("opcode", 0b1100011) + .bind("funct3", 0b101) + .register(); + new InstructionSpec("BLTU", "immB;rs2;rs1;funct3;opcode") + .bind("opcode", 0b1100011) + .bind("funct3", 0b110) + .register(); + new InstructionSpec("BGEU", "immB;rs2;rs1;funct3;opcode") + .bind("opcode", 0b1100011) + .bind("funct3", 0b111) + .register(); + + new InstructionSpec("LB", "immI;rs1;funct3;rd;opcode") + .bind("opcode", 0b0000011) + .bind("funct3", 0b000) + .register(); + new InstructionSpec("LH", "immI;rs1;funct3;rd;opcode") + .bind("opcode", 0b0000011) + .bind("funct3", 0b001) + .register(); + new InstructionSpec("LW", "immI;rs1;funct3;rd;opcode") + .bind("opcode", 0b0000011) + .bind("funct3", 0b010) + .register(); + new InstructionSpec("LBU", "immI;rs1;funct3;rd;opcode") + .bind("opcode", 0b0000011) + .bind("funct3", 0b100) + .register(); + new InstructionSpec("LHU", "immI;rs1;funct3;rd;opcode") + .bind("opcode", 0b0000011) + .bind("funct3", 0b101) + .register(); + + new InstructionSpec("SB", "immS;rs2;rs1;funct3;opcode") + .bind("opcode", 0b0100011) + .bind("funct3", 0b000) + .register(); + new InstructionSpec("SH", "immS;rs2;rs1;funct3;opcode") + .bind("opcode", 0b0100011) + .bind("funct3", 0b001) + .register(); + new InstructionSpec("SW", "immS;rs2;rs1;funct3;opcode") + .bind("opcode", 0b0100011) + .bind("funct3", 0b010) + .register(); + + new InstructionSpec("ADDI", "immI;rs1;funct3;rd;opcode") + .bind("opcode", 0b0010011) + .bind("funct3", 0b000) + .register(); + new InstructionSpec("SLTI", "immI;rs1;funct3;rd;opcode") + .bind("opcode", 0b0010011) + .bind("funct3", 0b010) + .register(); + new InstructionSpec("SLTIU", "immI;rs1;funct3;rd;opcode") + .bind("opcode", 0b0010011) + .bind("funct3", 0b011) + .register(); + new InstructionSpec("XORI", "immI;rs1;funct3;rd;opcode") + .bind("opcode", 0b0010011) + .bind("funct3", 0b100) + .register(); + new InstructionSpec("ORI", "immI;rs1;funct3;rd;opcode") + .bind("opcode", 0b0010011) + .bind("funct3", 0b110) + .register(); + new InstructionSpec("ANDI", "immI;rs1;funct3;rd;opcode") + .bind("opcode", 0b0010011) + .bind("funct3", 0b111) + .register(); + + new InstructionSpec("SLLI", "funct7;shamt;rs1;funct3;rd;opcode") + .bind("opcode", 0b0010011) + .bind("funct3", 0b001) + .bind("funct7", 0b0000000) + .register(); + new InstructionSpec("SRLI", "funct7;shamt;rs1;funct3;rd;opcode") + .bind("opcode", 0b0010011) + .bind("funct3", 0b101) + .bind("funct7", 0b0000000) + .register(); + new InstructionSpec("SRAI", "funct7;shamt;rs1;funct3;rd;opcode") + .bind("opcode", 0b0010011) + .bind("funct3", 0b101) + .bind("funct7", 0b0100000) + .register(); + + new InstructionSpec("ADD", "funct7;rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0110011) + .bind("funct3", 0b000) + .bind("funct7", 0b0000000) + .register(); + new InstructionSpec("SUB", "funct7;rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0110011) + .bind("funct3", 0b000) + .bind("funct7", 0b0100000) + .register(); + new InstructionSpec("SLL", "funct7;rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0110011) + .bind("funct3", 0b001) + .bind("funct7", 0b0000000) + .register(); + new InstructionSpec("SLT", "funct7;rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0110011) + .bind("funct3", 0b010) + .bind("funct7", 0b0000000) + .register(); + new InstructionSpec("SLTU", "funct7;rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0110011) + .bind("funct3", 0b011) + .bind("funct7", 0b0000000) + .register(); + new InstructionSpec("XOR", "funct7;rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0110011) + .bind("funct3", 0b100) + .bind("funct7", 0b0000000) + .register(); + new InstructionSpec("SRL", "funct7;rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0110011) + .bind("funct3", 0b101) + .bind("funct7", 0b0000000) + .register(); + new InstructionSpec("SRA", "funct7;rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0110011) + .bind("funct3", 0b101) + .bind("funct7", 0b0100000) + .register(); + new InstructionSpec("OR", "funct7;rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0110011) + .bind("funct3", 0b110) + .bind("funct7", 0b0000000) + .register(); + new InstructionSpec("AND", "funct7;rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0110011) + .bind("funct3", 0b111) + .bind("funct7", 0b0000000) + .register(); + + new InstructionSpec("FENCE", "fm[31:27];pred[26:24];succ[23:20];rs1;funct3;rd;opcode") + .bind("opcode", 0b0001111) + .bind("funct3", 0b000) + .bind("fm", 0b00000) + .bind("pred", 0b000) + .bind("succ", 0b0000) + .register(); + new InstructionSpec("FENCE.I", "immI;rs1;funct3;rd;opcode") + .bind("opcode", 0b0001111) + .bind("funct3", 0b001) + .register(); + + new InstructionSpec("ECALL", "funct12;zero[19:7];opcode") + .bind("opcode", 0b1110011) + .bind("zero", 0) + .bind("funct12", 0b000000000000) + .register(); + new InstructionSpec("EBREAK", "funct12;zero[19:7];opcode") + .bind("opcode", 0b1110011) + .bind("zero", 0) + .bind("funct12", 0b000000000001) + .register(); + + new InstructionSpec("CSRRW", "csr;rs1;funct3;rd;opcode") + .bind("opcode", 0b1110011) + .bind("funct3", 0b001) + .register(); + new InstructionSpec("CSRRS", "csr;rs1;funct3;rd;opcode") + .bind("opcode", 0b1110011) + .bind("funct3", 0b010) + .register(); + new InstructionSpec("CSRRC", "csr;rs1;funct3;rd;opcode") + .bind("opcode", 0b1110011) + .bind("funct3", 0b011) + .register(); + new InstructionSpec("CSRRWI", "csr;uimm;funct3;rd;opcode") + .bind("opcode", 0b1110011) + .bind("funct3", 0b101) + .register(); + new InstructionSpec("CSRRSI", "csr;uimm;funct3;rd;opcode") + .bind("opcode", 0b1110011) + .bind("funct3", 0b110) + .register(); + new InstructionSpec("CSRRCI", "csr;uimm;funct3;rd;opcode") + .bind("opcode", 0b1110011) + .bind("funct3", 0b111) + .register(); + + new InstructionSpec("MUL", "funct7;rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0110011) + .bind("funct3", 0b000) + .bind("funct7", 0b0000001) + .register(); + new InstructionSpec("MULH", "funct12;rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0110011) + .bind("funct3", 0b001) + .bind("funct12", 0b0000001) + .register(); + new InstructionSpec("MULHSU", "funct7;rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0110011) + .bind("funct3", 0b010) + .bind("funct7", 0b0000001) + .register(); + new InstructionSpec("MULHU", "funct7;rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0110011) + .bind("funct3", 0b011) + .bind("funct7", 0b0000001) + .register(); + new InstructionSpec("DIV", "funct7;rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0110011) + .bind("funct3", 0b100) + .bind("funct7", 0b0000001) + .register(); + new InstructionSpec("DIVU", "funct7;rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0110011) + .bind("funct3", 0b101) + .bind("funct7", 0b0000001) + .register(); + new InstructionSpec("REM", "funct7;rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0110011) + .bind("funct3", 0b110) + .bind("funct7", 0b0000001) + .register(); + new InstructionSpec("REMU", "funct7;rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0110011) + .bind("funct3", 0b111) + .bind("funct7", 0b0000001) + .register(); + + new InstructionSpec("LW.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0101111) + .bind("funct3", 0b010) + .bind("rs2", 0b00000) + .bind("funct5", 0b00010) + .register(); + new InstructionSpec("SC.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0101111) + .bind("funct3", 0b010) + .bind("funct5", 0b00011) + .register(); + new InstructionSpec("AMOSWAP.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0101111) + .bind("funct3", 0b010) + .bind("funct5", 0b00001) + .register(); + new InstructionSpec("AMOADD.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0101111) + .bind("funct3", 0b010) + .bind("funct5", 0b00000) + .register(); + new InstructionSpec("AMOXOR.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0101111) + .bind("funct3", 0b010) + .bind("funct5", 0b00100) + .register(); + new InstructionSpec("AMOAND.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0101111) + .bind("funct3", 0b010) + .bind("funct5", 0b01100) + .register(); + new InstructionSpec("AMOOR.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0101111) + .bind("funct3", 0b010) + .bind("funct5", 0b01000) + .register(); + new InstructionSpec("AMOMIN.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0101111) + .bind("funct3", 0b010) + .bind("funct5", 0b10000) + .register(); + new InstructionSpec("AMOMAX.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0101111) + .bind("funct3", 0b010) + .bind("funct5", 0b10100) + .register(); + new InstructionSpec("AMOMINU.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0101111) + .bind("funct3", 0b010) + .bind("funct5", 0b11000) + .register(); + new InstructionSpec("AMOMAXU.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode") + .bind("opcode", 0b0101111) + .bind("funct3", 0b010) + .bind("funct5", 0b11100) + .register(); + + TOKEN_ALIASES.put("zero", "x0"); + TOKEN_ALIASES.put("ra", "x1"); + TOKEN_ALIASES.put("sp", "x2"); + TOKEN_ALIASES.put("gp", "x3"); + TOKEN_ALIASES.put("tp", "x4"); + TOKEN_ALIASES.put("t0", "x5"); + TOKEN_ALIASES.put("t1", "x6"); + TOKEN_ALIASES.put("t2", "x7"); + TOKEN_ALIASES.put("s0", "x8"); + TOKEN_ALIASES.put("fp", "x8"); + TOKEN_ALIASES.put("s1", "x9"); + TOKEN_ALIASES.put("a0", "x10"); + TOKEN_ALIASES.put("a1", "x11"); + TOKEN_ALIASES.put("a2", "x12"); + TOKEN_ALIASES.put("a3", "x13"); + TOKEN_ALIASES.put("a4", "x14"); + TOKEN_ALIASES.put("a5", "x15"); + TOKEN_ALIASES.put("a6", "x16"); + TOKEN_ALIASES.put("a7", "x17"); + TOKEN_ALIASES.put("s2", "x18"); + TOKEN_ALIASES.put("s3", "x19"); + TOKEN_ALIASES.put("s4", "x20"); + TOKEN_ALIASES.put("s5", "x21"); + TOKEN_ALIASES.put("s6", "x22"); + TOKEN_ALIASES.put("s7", "x23"); + TOKEN_ALIASES.put("s8", "x24"); + TOKEN_ALIASES.put("s9", "x25"); + TOKEN_ALIASES.put("s10", "x26"); + TOKEN_ALIASES.put("s11", "x27"); + TOKEN_ALIASES.put("t3", "x28"); + TOKEN_ALIASES.put("t4", "x29"); + TOKEN_ALIASES.put("t5", "x30"); + TOKEN_ALIASES.put("t6", "x31"); + + FIELD_ARGUMENT_ORDER.add("rd"); + FIELD_ARGUMENT_ORDER.add("rs1"); + FIELD_ARGUMENT_ORDER.add("rs2"); + FIELD_ARGUMENT_ORDER.add("imm"); + } + + public static void assemble(final String code, final PhysicalMemory memory, final int offset) throws MemoryAccessException { + assemble(code.split("\n"), memory, offset); + } + + public static void assemble(final String[] code, final PhysicalMemory memory, final int offset) throws MemoryAccessException { + int writeIndex = offset; + for (final String line : code) { + String processedLine; + final int commentStart = line.indexOf('#'); + if (commentStart >= 0) { + processedLine = line.substring(0, commentStart).trim(); + } else { + processedLine = line.trim(); + } + + if (processedLine.isEmpty()) { + continue; + } + + final String[] instAndArgs = line.trim().split(" ", 2); + final String instName = instAndArgs[0]; + final InstructionSpec inst = INSTRUCTION_SPEC_MAP.get(instName.toLowerCase()); + if (inst == null) { + throw new IllegalArgumentException("illegal instruction name '" + instName + "'"); + } + + final int instAssembled; + if (instAndArgs.length > 1) { + final String[] args = instAndArgs[1].split(","); + for (int i = 0; i < args.length; i++) { + args[i] = args[i].trim(); + } + instAssembled = inst.assemble(resolveArgs(inst.fields.keySet(), args)); + } else { + instAssembled = inst.assemble(Collections.emptyMap()); + } + + memory.store32(writeIndex, instAssembled); + writeIndex += 4; + } + } + + private static Map resolveArgs(final Set fieldNames, final String[] args) { + final Map result = new HashMap<>(); + int argIndex = 0; + for (final String fieldName : FIELD_ARGUMENT_ORDER) { + if (!fieldNames.contains(fieldName)) { + continue; + } + + String arg = args[argIndex]; + arg = TOKEN_ALIASES.getOrDefault(arg, arg); + + final int argValue; + if (arg.charAt(0) == 'x') { + argValue = Integer.decode(arg.substring(1)); + } else { + argValue = Integer.decode(arg); + } + + result.put(fieldName, argValue); + + argIndex++; + if (argIndex >= args.length) { + break; + } + } + + if (argIndex < args.length) { + throw new IllegalArgumentException(); + } + + return result; + } + + public static final class InstructionSpec { + private static final char FIELD_SPEC_SEPARATOR = ';'; + private static final char BIT_SPEC_START = '['; + private static final char BIT_SPEC_END = ']'; + private static final char BIT_SPEC_SEPARATOR = ','; + public static final char BIT_SPEC_RANGE_SEPARATOR = ':'; + + private static final Map DEFAULT_BIT_RANGES = new HashMap<>(); + + static { + DEFAULT_BIT_RANGES.put("opcode", "opcode[6:0]"); + DEFAULT_BIT_RANGES.put("rd", "rd[11:7]"); + DEFAULT_BIT_RANGES.put("funct3", "funct3[14:12]"); + DEFAULT_BIT_RANGES.put("rs1", "rs1[19:15]"); + DEFAULT_BIT_RANGES.put("uimm", "rs1[19:15]"); + DEFAULT_BIT_RANGES.put("rs2", "rs2[24:20]"); + DEFAULT_BIT_RANGES.put("shamt", "rs2[24:20]"); + DEFAULT_BIT_RANGES.put("funct7", "funct7[31:25]"); + DEFAULT_BIT_RANGES.put("immI", "imm[31:20]"); + DEFAULT_BIT_RANGES.put("csr", "imm[31:20]"); + DEFAULT_BIT_RANGES.put("immS", "imm[31:25,11:7]"); + DEFAULT_BIT_RANGES.put("immB", "imm[31,7,30:25,11:6]"); + DEFAULT_BIT_RANGES.put("immU", "imm[31:12]"); + DEFAULT_BIT_RANGES.put("immJ", "imm[31,19:12,20,30:21]"); + DEFAULT_BIT_RANGES.put("funct5", "funct5[31:27]"); + DEFAULT_BIT_RANGES.put("funct12", "funct12[31:20]"); + } + + private final String name; + private final Map> fields; + private final Map values; + private Map> postprocessors; + + public InstructionSpec(final String name, final String spec) { + // Spec grammar: + // SPEC := FIELD_SPEC_LIST + // FIELD_SPEC_LIST := FIELD_SPEC [ ";" FIELD_SPEC_LIST ] + // FIELD_SPEC := FIELD_NAME "[" BIT_SPEC_LIST "]" + // BIT_SPEC_LIST := BIT_SPEC [ "," BIT_SPEC_LIST ] + // BIT_SPEC := 0-9 | # Single bit indexing into opcode bits; equivalent to N:N + // 0-9 ":" 0-9 # Bit range indexing into opcode bits + + this.name = name; + fields = new HashMap<>(); + values = new HashMap<>(); + postprocessors = new HashMap<>(); + + parseFieldSpecList(spec, fields); + } + + public InstructionSpec bind(final String field, final int value) { + values.put(field, value); + return this; + } + + public InstructionSpec postprocess(final String field, final Function callback) { + postprocessors.put(field, callback); + return this; + } + + public void register() { + INSTRUCTION_SPEC_MAP.put(name.toLowerCase(), this); + } + + public int assemble(final Map values) { + final Function getValue = s -> { + final int value = values.computeIfAbsent(s, key -> this.values.computeIfAbsent(key, s1 -> { + throw new IllegalArgumentException("missing value '" + s1 + "'"); + })); + final Function postprocessor = postprocessors.get(s); + if (postprocessor != null) { + return postprocessor.apply(value); + } else { + return value; + } + }; + + int result = 0; + for (final String fieldName : fields.keySet()) { + final List fieldMapping = fields.get(fieldName); + final int fieldValue = getValue.apply(fieldName); + result |= map(fieldValue, fieldMapping); + } + return result; + } + + private int map(final int value, final List mapping) { + int mask = 0b1; + int result = 0; + for (final BitRange range : mapping) { + final int high = range.high; + final int low = range.low; + for (int i = low; i <= high; i++, mask <<= 1) { + final int bitValue = (value & mask) != 0 ? 1 : 0; + result |= bitValue << i; + } + } + return result; + } + + private static void parseFieldSpecList(final String fieldSpecList, final Map> fields) { + int fieldSpecStart = 0; + int nextFieldSpecSplit = fieldSpecList.indexOf(FIELD_SPEC_SEPARATOR); + do { + if (nextFieldSpecSplit < 0) + nextFieldSpecSplit = fieldSpecList.length(); // No more split, use remainder. + + String fieldSpec = fieldSpecList.substring(fieldSpecStart, nextFieldSpecSplit); + fieldSpec = DEFAULT_BIT_RANGES.getOrDefault(fieldSpec, fieldSpec); + if (fieldSpec.length() == 0) { + continue; + } + + final int bitSpecListStart = fieldSpec.indexOf(BIT_SPEC_START); + if (bitSpecListStart < 0 || fieldSpec.charAt(fieldSpec.length() - 1) != BIT_SPEC_END) { + throw new IllegalArgumentException("no bit spec list for field '" + fieldSpec + "'"); + } + + final String fieldName = fieldSpec.substring(0, bitSpecListStart); + + final String bitSpecList = fieldSpec.substring(bitSpecListStart + 1, fieldSpec.length() - 1); + int bitSpecStart = 0; + if (bitSpecList.length() == 0) { + throw new IllegalArgumentException("empty bit spec list for field '" + fieldName + "'"); + } + + final List bitRanges = new ArrayList<>(); + int nextBitSpecSplit = bitSpecList.indexOf(BIT_SPEC_SEPARATOR); + do { + if (nextBitSpecSplit < 0) + nextBitSpecSplit = bitSpecList.length(); // No more split, use remainder. + + final String bitSpec = bitSpecList.substring(bitSpecStart, nextBitSpecSplit); + if (bitSpec.length() == 0) { + continue; + } + + final int separatorIndex = bitSpec.indexOf(BIT_SPEC_RANGE_SEPARATOR); + if (separatorIndex < 0) { + final int bitIndex = Integer.decode(bitSpec); + bitRanges.add(new BitRange(bitIndex, bitIndex)); + } else { + final int bitIndexHigh = Integer.decode(bitSpec.substring(0, separatorIndex)); + final int bitIndexLow = Integer.decode(bitSpec.substring(separatorIndex + 1)); + bitRanges.add(new BitRange(bitIndexHigh, bitIndexLow)); + } + + bitSpecStart = nextBitSpecSplit + 1; + nextBitSpecSplit = bitSpecList.indexOf(BIT_SPEC_SEPARATOR, bitSpecStart); + } while (bitSpecStart < bitSpecList.length()); + + if (bitRanges.size() == 0) { + throw new IllegalArgumentException("empty bit spec list for field '" + fieldName + "'"); + } + + Collections.reverse(bitRanges); + + fields.put(fieldName, bitRanges); + + fieldSpecStart = nextFieldSpecSplit + 1; + nextFieldSpecSplit = fieldSpecList.indexOf(FIELD_SPEC_SEPARATOR, fieldSpecStart); + } while (fieldSpecStart < fieldSpecList.length()); + } + + private static final class BitRange { + public final int high, low; + + public BitRange(final int high, final int low) { + this.high = Math.max(high, low); + this.low = Math.min(high, low); + } + } + } +} diff --git a/src/main/java/li/cil/circuity/vm/riscv/R5Board.java b/src/main/java/li/cil/circuity/vm/riscv/R5Board.java new file mode 100644 index 00000000..11a4a240 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/riscv/R5Board.java @@ -0,0 +1,238 @@ +package li.cil.circuity.vm.riscv; + +import li.cil.circuity.api.vm.MemoryMap; +import li.cil.circuity.api.vm.device.Steppable; +import li.cil.circuity.api.vm.device.memory.MemoryAccessException; +import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice; +import li.cil.circuity.api.vm.device.memory.PhysicalMemory; +import li.cil.circuity.api.vm.device.rtc.RealTimeCounter; +import li.cil.circuity.api.vm.device.rtc.SystemTimeRealTimeCounter; +import li.cil.circuity.api.vm.devicetree.DeviceNames; +import li.cil.circuity.api.vm.devicetree.DeviceTree; +import li.cil.circuity.api.vm.devicetree.DeviceTreePropertyNames; +import li.cil.circuity.vm.SimpleMemoryMap; +import li.cil.circuity.vm.device.UART16550A; +import li.cil.circuity.vm.device.memory.ByteBufferMemory; +import li.cil.circuity.vm.devicetree.DeviceTreeRegistry; +import li.cil.circuity.vm.devicetree.FlattenedDeviceTree; +import li.cil.circuity.vm.riscv.device.R5CoreLocalInterrupter; +import li.cil.circuity.vm.riscv.device.R5HostTargetInterface; +import li.cil.circuity.vm.riscv.device.R5PlatformLevelInterruptController; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.ArrayList; +import java.util.List; +import java.util.OptionalInt; + +public final class R5Board { + private static final Logger LOGGER = LogManager.getLogger(); + + private static final int PHYSICAL_MEMORY_FIRST = 0x80000000; + private static final int PHYSICAL_MEMORY_LAST = 0xFFFFFFFF; + private static final int DEVICE_MEMORY_FIRST = 0x40010000; + private static final int DEVICE_MEMORY_LAST = 0x400FFFFF; + private static final int CLINT_ADDRESS = 0x02000000; + private static final int PLIC_ADDRESS = 0x0C000000; + private static final int HTIF_ADDRESS = 0x40008000; + private static final int UART_ADDRESS = 0x10000000; + + private static final int BIOS_ADDRESS = 0x1000; + private static final int LOW_MEMORY_SIZE = 0x2000; // Just needs to fit "jump to firmware". + + private static final int FIRMWARE_ADDRESS = PHYSICAL_MEMORY_FIRST; + private static final int FDT_ADDRESS = FIRMWARE_ADDRESS + 0x02200000; + + private final RealTimeCounter rtc; + private final MemoryMap memoryMap; + private final R5CPU cpu; + private final UART16550A uart; + private final List devices = new ArrayList<>(); + private final List steppableDevices = new ArrayList<>(); + + public R5Board() { + rtc = SystemTimeRealTimeCounter.create(); + memoryMap = new SimpleMemoryMap(); + cpu = new R5CPU(rtc, memoryMap); + uart = new UART16550A(); + + final PhysicalMemory flash = new ByteBufferMemory(LOW_MEMORY_SIZE); + final R5HostTargetInterface htif = new R5HostTargetInterface(); + final R5CoreLocalInterrupter clint = new R5CoreLocalInterrupter(rtc); + final R5PlatformLevelInterruptController plic = new R5PlatformLevelInterruptController(); + + steppableDevices.add(cpu); + + // Wire up interrupts. + clint.getMachineSoftwareInterrupt().controller = cpu; + clint.getMachineTimerInterrupt().controller = cpu; + plic.getMachineExternalInterrupt().controller = cpu; + plic.getSupervisorExternalInterrupt().controller = cpu; + uart.getInterrupt().id = 0xA; + uart.getInterrupt().controller = plic; + + // Map devices to memory. + addDevice(CLINT_ADDRESS, clint); + addDevice(PLIC_ADDRESS, plic); + addDevice(HTIF_ADDRESS, htif); + addDevice(UART_ADDRESS, uart); + memoryMap.addDevice(BIOS_ADDRESS, flash); + } + + public void step(final int cycles) { + for (final Steppable device : steppableDevices) { + device.step(cycles); + } + } + + public R5CPU getCpu() { + return cpu; + } + + public boolean addDevice(final int address, final MemoryMappedDevice device) { + if (device.getLength() == 0) { + return false; + } + + if (devices.contains(device)) { + // This prevents adding the same device at different addresses. However, that + // could be circumvented by using a wrapper device, so we save ourselves the + // additional bookkeeping needed for this here. + return false; + } + + if (!memoryMap.addDevice(address, device)) { + return false; + } + + devices.add(device); + + if (device instanceof Steppable) { + steppableDevices.add((Steppable) device); + } + + reset(); + return true; + } + + public boolean addDevice(final MemoryMappedDevice device) { + if (device.getLength() == 0) { + return false; + } + + final int startMin, startMax; + if (device instanceof PhysicalMemory) { + startMin = PHYSICAL_MEMORY_FIRST; + startMax = PHYSICAL_MEMORY_LAST - device.getLength() + 1; + } else { + startMin = DEVICE_MEMORY_FIRST; + startMax = DEVICE_MEMORY_LAST - device.getLength() + 1; + } + + final OptionalInt address = memoryMap.findFreeRange(startMin, startMax, device.getLength()); + return address.isPresent() && addDevice(address.getAsInt(), device); + } + + public void removeDevice(final MemoryMappedDevice device) { + memoryMap.removeDevice(device); + devices.remove(device); + + if (device instanceof Steppable) { + steppableDevices.remove(device); + } + + reset(); + } + + public void reset() { + cpu.reset(); + + try { + final FlattenedDeviceTree fdt = buildDeviceTree().flatten(); + final byte[] dtb = fdt.toDTB(); + for (int i = 0; i < dtb.length; i++) { + memoryMap.store8(FDT_ADDRESS + i, dtb[i]); + } + } catch (final MemoryAccessException e) { + throw new AssertionError(); + } + + try { + final int lui = 0b0110111; + final int jalr = 0b1100111; + + final int rd_x5 = 5 << 7; + final int rd_x11 = 11 << 7; + final int rs1_x5 = 5 << 15; + + int pc = 0x1000; // R5CPU starts executing at 0x1000. + + // lui a1, FDT_ADDRESS -> store FDT address in a1 for firmware + memoryMap.store32(pc, lui | rd_x11 + FDT_ADDRESS); + pc += 4; + + // lui t0, PHYSICAL_MEMORY_FIRST -> load address of firmware + memoryMap.store32(pc, lui | rd_x5 + PHYSICAL_MEMORY_FIRST); + pc += 4; + + // jalr zero, t0, 0 -> jump to firmware + memoryMap.store32(pc, jalr | rs1_x5); + } catch (final MemoryAccessException e) { + LOGGER.error(e); + } + } + + public int readValue() { + return uart.getByte(); + } + + private DeviceTree buildDeviceTree() { + final DeviceTree root = DeviceTreeRegistry.create(memoryMap); + root + .addProp(DeviceTreePropertyNames.NUM_ADDRESS_CELLS, 2) + .addProp(DeviceTreePropertyNames.NUM_SIZE_CELLS, 2) + .addProp(DeviceTreePropertyNames.COMPATIBLE, "riscv-virtio") + .addProp(DeviceTreePropertyNames.MODEL, "riscv-virtio,qemu"); +// .addProp(DeviceTreePropertyNames.COMPATIBLE, "ucbbar,riscvemu-bar_dev") +// .addProp(DeviceTreePropertyNames.MODEL, "ucbbar,riscvemu-bar"); + + root.putChild(DeviceNames.CPUS, cpus -> cpus + .addProp(DeviceTreePropertyNames.NUM_ADDRESS_CELLS, 1) + .addProp(DeviceTreePropertyNames.NUM_SIZE_CELLS, 0) + .addProp("timebase-frequency", rtc.getFrequency()) + + .putChild(DeviceNames.CPU, 0, cpuNode -> cpuNode + .addProp(DeviceTreePropertyNames.DEVICE_TYPE, DeviceNames.CPU) + .addProp(DeviceTreePropertyNames.REG, 0) + .addProp(DeviceTreePropertyNames.STATUS, "okay") + .addProp(DeviceTreePropertyNames.COMPATIBLE, "riscv") + .addProp("riscv,isa", "rv32imacsu") + + .addProp("mmu-type", "riscv,sv32") + .addProp("clock-frequency", 1_000_000) + + .putChild(DeviceNames.INTERRUPT_CONTROLLER, ic -> ic + .addProp("#interrupt-cells", 1) + .addProp(DeviceTreePropertyNames.INTERRUPT_CONTROLLER) + .addProp(DeviceTreePropertyNames.COMPATIBLE, "riscv,cpu-intc") + .addProp(DeviceTreePropertyNames.PHANDLE, ic.createPHandle(cpu))))); + + root.putChild("soc", soc -> soc + .addProp(DeviceTreePropertyNames.NUM_ADDRESS_CELLS, 2) + .addProp(DeviceTreePropertyNames.NUM_SIZE_CELLS, 2) + .addProp(DeviceTreePropertyNames.COMPATIBLE, "simple-bus") + .addProp(DeviceTreePropertyNames.RANGES)); + + for (final MemoryMappedDevice device : devices) { + DeviceTreeRegistry.visit(root, memoryMap, device); + } + + root.putChild("chosen", chosen -> chosen + .addProp("bootargs", "console=ttyS0") + .addProp("stdout-path", String.format("uart@%x", UART_ADDRESS))); +// .addProp("riscv,kernel-start", 0x400000) +// .addProp("riscv,kernel-end", 0x400000 + 14676260 /*8032596*/)); + + return root; + } +} diff --git a/src/main/java/li/cil/circuity/vm/riscv/R5CPU.java b/src/main/java/li/cil/circuity/vm/riscv/R5CPU.java new file mode 100644 index 00000000..ed317752 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/riscv/R5CPU.java @@ -0,0 +1,2311 @@ +package li.cil.circuity.vm.riscv; + +import io.netty.util.collection.IntObjectHashMap; +import io.netty.util.collection.IntObjectMap; +import li.cil.circuity.api.vm.MemoryMap; +import li.cil.circuity.api.vm.MemoryRange; +import li.cil.circuity.api.vm.device.InterruptController; +import li.cil.circuity.api.vm.device.Steppable; +import li.cil.circuity.api.vm.device.memory.MemoryAccessException; +import li.cil.circuity.api.vm.device.memory.PhysicalMemory; +import li.cil.circuity.api.vm.device.rtc.RealTimeCounter; +import li.cil.circuity.vm.device.memory.exception.*; +import li.cil.circuity.vm.riscv.exception.R5BreakpointException; +import li.cil.circuity.vm.riscv.exception.R5ECallException; +import li.cil.circuity.vm.riscv.exception.R5Exception; +import li.cil.circuity.vm.riscv.exception.R5IllegalInstructionException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.function.Consumer; + +/** + * RISC-V + *

+ * Based on ISA specifications found at https://riscv.org/technical/specifications/ + *

    + *
  • Volume 1, Unprivileged Spec v.20191213
  • + *
  • Volume 2, Privileged Spec v.20190608
  • + *
+ *

+ * Implemented extensions: + *

    + *
  • RV32I Base Integer Instruction Set, Version 2.1
  • + *
  • "Zifencei" Instruction-Fetch Fence, Version 2.0
  • + *
  • "M" Standard Extension for Integer Multiplication and Division, Version 2.0
  • + *
  • "A" Standard Extension for Atomic Instructions, Version 2.1
  • + *
  • "Zicsr", Control and Status Register (CSR) Instructions, Version 2.0
  • + *
  • TODO "F" Standard Extension for Single-Precision Floating-Point, Version 2.2
  • + *
  • TODO "D"
  • + *
  • "C" Standard Extension for Compressed Instructions, Version 2.0
  • + *
+ */ +public class R5CPU implements Steppable, InterruptController { + private static final Logger LOGGER = LogManager.getLogger(); + + public static final int PC_INIT = 0x1000; // Initial position of program counter. + + private static final int XLEN = 32; // Integer register width. + + // Base ISA descriptor CSR (misa) (V2p16). + private static final int MISA = (R5.mxl(XLEN) << (XLEN - 2)) | R5.isa('I', 'M', 'A', 'S', 'U', 'C'); // 'F', 'D' + + // UBE, SBE, MBE hardcoded to zero for little endianness. + // SD is computed. + // TVM, TW, TSR are unsupported and hardwired to zero. + private static final int MSTATUS_MASK = (R5.STATUS_UIE_MASK | R5.STATUS_SIE_MASK | R5.STATUS_MIE_MASK | + R5.STATUS_UPIE_MASK | R5.STATUS_SPIE_MASK | R5.STATUS_MPIE_MASK | + R5.STATUS_SPP_MASK | R5.STATUS_MPP_MASK | + R5.STATUS_FS_MASK | + R5.STATUS_MPRV_MASK | R5.STATUS_SUM_MASK | R5.STATUS_MXR_MASK); + + // No time and no high perf counters. + private static final int COUNTEREN_MASK = R5.MCOUNTERN_CY | R5.MCOUNTERN_IR; + + // Supervisor status (sstatus) CSR mask over mstatus. + private static final int SSTATUS_MASK = (R5.STATUS_UIE_MASK | R5.STATUS_SIE_MASK | + R5.STATUS_UPIE_MASK | R5.STATUS_SPIE_MASK | + R5.STATUS_SPP_MASK | + R5.STATUS_FS_MASK | R5.STATUS_XS_MASK | + R5.STATUS_SUM_MASK | R5.STATUS_MXR_MASK); + + // Translation look-aside buffer config. + private static final int TLB_SIZE = 256; // Must be a power of two for fast modulo via `& (TLB_SIZE - 1)`. + + /////////////////////////////////////////////////////////////////// + // RV32I + private int pc; // Program counter. + private final int[] x = new int[32]; // Integer registers. + + /////////////////////////////////////////////////////////////////// + // RV32F +// private final float[] f = new float[32]; // Float registers. +// private byte fflags; // fcsr[4:0] := NV . DZ . OF . UF . NX +// private byte frm; // fcsr[7:5] + + /////////////////////////////////////////////////////////////////// + // RV32A + private int reservation_set = -1; // Reservation set for RV32A's LR/SC. + + /////////////////////////////////////////////////////////////////// + // User-level CSRs + private long mcycle; + + // Machine-level CSRs + private int mstatus; // Machine Status Register; mstatush is always zero for us, SD is computed + private int mtvec; // Machine Trap-Vector Base-Address Register; 0b11=Mode: 0=direct, 1=vectored + private int medeleg, mideleg; // Machine Trap Delegation Registers + private int mip, mie; // Machine Interrupt Registers + private int mcounteren; // Machine Counter-Enable Register + private int mscratch; // Machine Scratch Register + private int mepc; // Machine Exception Program Counter + private int mcause; // Machine Cause Register + private int mtval; // Machine Trap Value Register + private byte fs; // part of mstatus, store separate for convenience + + // Supervisor-level CSRs + private int stvec; // Supervisor Trap Vector Base Address Register; 0b11=Mode: 0=direct, 1=vectored + private int scounteren; // Supervisor Counter-Enable Register + private int sscratch; // Supervisor Scratch Register + private int sepc; // Supervisor Exception Program Counter + private int scause; // Supervisor Cause Register + private int stval; // Supervisor Trap Value Register + private int satp; // Supervisor Address Translation and Protection Register + + /////////////////////////////////////////////////////////////////// + // Misc. state + private int priv; // Current privilege level. + private boolean waitingForInterrupt; + + /////////////////////////////////////////////////////////////////// + // Memory access + + // Translation look-aside buffers. + private final TLBEntry[] tlb_fetch = new TLBEntry[TLB_SIZE]; + private final TLBEntry[] tlb_read = new TLBEntry[TLB_SIZE]; + private final TLBEntry[] tlb_write = new TLBEntry[TLB_SIZE]; + + // Access to physical memory for load/store operations. + private final MemoryMap physicalMemory; + + // Real time counter -- at least in RISC-V Linux 5.1 the mtime CSR is needed in add_device_randomness + // where it doesn't use the SBI. Not implementing it would cause an illegal instruction exception + // halting the system. + private final RealTimeCounter rtc; + + public R5CPU(final RealTimeCounter rtc, final MemoryMap physicalMemory) { + this.rtc = rtc; + this.physicalMemory = physicalMemory; + + for (int i = 0; i < TLB_SIZE; i++) { + tlb_fetch[i] = new TLBEntry(); + } + for (int i = 0; i < TLB_SIZE; i++) { + tlb_read[i] = new TLBEntry(); + } + for (int i = 0; i < TLB_SIZE; i++) { + tlb_write[i] = new TLBEntry(); + } + + reset(); + } + + public void reset() { + pc = PC_INIT; + + // Volume 2, 3.3 Reset + priv = R5.PRIVILEGE_M; + mstatus = mstatus & ~R5.STATUS_MIE_MASK; + mstatus = mstatus & ~R5.STATUS_MPRV_MASK; + mcause = 0; + + flushTLB(); + } + + public void raiseInterrupts(final int mask) { + mip |= mask; + if (waitingForInterrupt && (mip & mie) != 0) { + waitingForInterrupt = false; + } + } + + public void lowerInterrupts(final int mask) { + mip &= ~mask; + } + + public void step(final int cycles) { + final long cycleLimit = mcycle + cycles; + // Note: practically the same as as cycleLimit > cycles, but we make sure the delta is + // sufficiently small to be a positive integer. + while (!waitingForInterrupt && cycleLimit > mcycle) { + if ((mip & mie) != 0 && raiseInterrupt()) { + return; + } + + try { + step(); + } catch (final LoadPageFaultException e) { + raiseException(R5.EXCEPTION_LOAD_PAGE_FAULT, e.getAddress()); + } catch (final StorePageFaultException e) { + raiseException(R5.EXCEPTION_STORE_PAGE_FAULT, e.getAddress()); + } catch (final FetchPageFaultException e) { + raiseException(R5.EXCEPTION_FETCH_PAGE_FAULT, e.getAddress()); + } catch (final LoadFaultException e) { + raiseException(R5.EXCEPTION_FAULT_LOAD, e.getAddress()); + } catch (final StoreFaultException e) { + raiseException(R5.EXCEPTION_FAULT_STORE, e.getAddress()); + } catch (final FetchFaultException e) { + raiseException(R5.EXCEPTION_FAULT_FETCH, e.getAddress()); + } catch (final MisalignedLoadException e) { + raiseException(R5.EXCEPTION_MISALIGNED_LOAD, e.getAddress()); + } catch (final MisalignedStoreException e) { + raiseException(R5.EXCEPTION_MISALIGNED_STORE, e.getAddress()); + } catch (final MisalignedFetchException e) { + raiseException(R5.EXCEPTION_MISALIGNED_FETCH, e.getAddress()); + } catch (final MemoryAccessException e) { + throw new AssertionError(); + } catch (final R5Exception e) { + raiseException(e.getExceptionCause(), e.getExceptionValue()); + } + + assert x[0] == 0; + } + } + + @SuppressWarnings("DuplicateBranchesInSwitch") + private void step() throws R5Exception, MemoryAccessException { + final int inst = fetch(pc); + + mcycle++; + + if ((inst & 0b11) == 0b11) { + // Instruction decoding, see Volume I: RISC-V Unprivileged ISA V20191214-draft page 16ff. + + final int opcode = getField(inst, 0, 6, 0); + final int rd = getField(inst, 7, 11, 0); + final int rs1 = getField(inst, 15, 19, 0); + final int rs2 = getField(inst, 20, 24, 0); + + // Opcode values, see Volume I: RISC-V Unprivileged ISA V20191214-draft page 130ff. They appear in the order + // they are introduced in the spec. Immediate value decoding follows the layouts described in 2.3. + + switch (opcode) { + /////////////////////////////////////////////////////////////////// + // 2.4 Integer Computational Instructions + /////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////// + // Integer Register-Immediate Instructions + case 0b0010011: { // OP-IMM (register-immediate operation) + final int funct3 = getField(inst, 12, 14, 0); + final int imm = inst >> 20; // inst[31:20], sign extended + final int result; + switch (funct3) { + case 0b000: { // ADDI + result = x[rs1] + imm; + break; + } + case 0b010: { // SLTI + result = x[rs1] < imm ? 1 : 0; + break; + } + case 0b011: { // SLTIU + result = Integer.compareUnsigned(x[rs1], imm) < 0 ? 1 : 0; + break; + } + case 0b100: { // XORI + result = x[rs1] ^ imm; + break; + } + case 0b110: { // ORI + result = x[rs1] | imm; + break; + } + case 0b111: { // ANDI + result = x[rs1] & imm; + break; + } + case 0b001: { // SLLI + if ((inst & 0b1111111_00000_00000_000_00000_0000000) != 0) + throw new R5IllegalInstructionException(inst); + + result = x[rs1] << (imm & 0b11111); + break; + } + case 0b101: { // SRLI/SRAI + final int funct7 = getField(imm, 5, 11, 0); // imm[11:5] + switch (funct7) { + case 0b0000000: { // SRLI + result = x[rs1] >>> (imm & 0b11111); + break; + } + case 0b0100000: { // SRAI + result = x[rs1] >> (imm & 0b11111); + break; + } + + default: { + throw new R5IllegalInstructionException(inst); + } + } + break; + } + + default: { + throw new R5IllegalInstructionException(inst); + } + } + + if (rd != 0) { + x[rd] = result; + } + + pc += 4; + break; + } + + case 0b0110111: { // LUI + final int imm = inst & 0b11111111111111111111_00000_0000000; // inst[31:12] + if (rd != 0) { + x[rd] = imm; + } + + pc += 4; + break; + } + + case 0b0010111: { // AUIPC + final int imm = inst & 0b11111111111111111111_00000_0000000; // inst[31:12] + if (rd != 0) { + x[rd] = pc + imm; + } + + pc += 4; + break; + } + + case 0b0110011: { // OP (register-register operation aka R-type) + final int a = x[rs1]; + final int b = x[rs2]; + final int funct7 = getField(inst, 25, 31, 0); + switch (funct7) { + case 0b000001: { + /////////////////////////////////////////////////////////////////// + // Chapter 7 "M" Standard Extension for Integer Multiplication and Division, Version 2.0 + /////////////////////////////////////////////////////////////////// + + final int funct3 = getField(inst, 12, 14, 0); + final int result; + switch (funct3) { + /////////////////////////////////////////////////////////////////// + // 7.1 Multiplication Operations + + case 0b000: { // MUL + result = a * b; + break; + } + case 0b001: { // MULH + result = (int) (((long) a * (long) b) >> 32); + break; + } + case 0b010: { // MULHSU + result = (int) (((long) a * Integer.toUnsignedLong(b)) >> 32); + break; + } + case 0b011: { // MULHU + result = (int) ((Integer.toUnsignedLong(a) * Integer.toUnsignedLong(b)) >>> 32); + break; + } + + /////////////////////////////////////////////////////////////////// + // 7.2 Division Operations, special cases from table 7.1 on p45. + + case 0b100: { // DIV + if (b == 0) { + result = -1; + } else if (a == Integer.MIN_VALUE && b == -1) { + result = a; + } else { + result = a / b; + } + break; + } + case 0b101: { // DIVU + if (b == 0) { + result = Integer.MAX_VALUE; + } else { + result = Integer.divideUnsigned(a, b); + } + break; + } + case 0b110: { // REM + if (b == 0) { + result = a; + } else if (a == Integer.MIN_VALUE && b == -1) { + result = 0; + } else { + result = a % b; + } + break; + } + case 0b111: { // REMU + if (b == 0) { + result = a; + } else { + result = Integer.remainderUnsigned(a, b); + } + break; + } + + default: { + throw new R5IllegalInstructionException(inst); + } + } + + if (rd != 0) { + x[rd] = result; + } + + break; + } + case 0b0000000: + case 0b0100000: { // Integer Register-Register Operations + final int funct3 = getField(inst, 12, 14, 0); + final int result; + switch (funct3 | funct7) { + case 0b000: { // ADD + result = a + b; + break; + } + //noinspection PointlessBitwiseExpression + case 0b000 | 0b0100000: { // SUB + result = a - b; + break; + } + case 0b001: { // SLL + result = a << b; + break; + } + case 0b010: { // SLT + result = a < b ? 1 : 0; + break; + } + case 0b011: { // SLTU + result = Integer.compareUnsigned(a, b) < 0 ? 1 : 0; + break; + } + case 0b100: { // XOR + result = a ^ b; + break; + } + case 0b101: { // SRL + result = a >>> b; + break; + } + case 0b101 | 0b0100000: { // SRA + result = a >> b; + break; + } + case 0b110: { // OR + result = a | b; + break; + } + case 0b111: { // AND + result = a & b; + break; + } + + default: { + throw new R5IllegalInstructionException(inst); + } + } + + if (rd != 0) { + x[rd] = result; + } + + break; + } + + default: { + throw new R5IllegalInstructionException(inst); + } + } + + pc += 4; + break; + } + + /////////////////////////////////////////////////////////////////// + // 2.5 Control Transfer Instructions + /////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////// + // Unconditional Jumps + case 0b1101111: { // JAL + final int imm = extendSign(getField(inst, 31, 31, 20) | + getField(inst, 21, 30, 1) | + getField(inst, 20, 20, 11) | + getField(inst, 12, 19, 12), 21); + if (rd != 0) { + x[rd] = pc + 4; + } + + pc += imm; + break; + } + + case 0b110_0111: { // JALR + final int imm = inst >> 20; // inst[31:20], sign extended + final int address = (x[rs1] + imm) & ~0b1; // Compute first in case rs1 == rd. + // Note: we just mask here, but technically we should raise an exception for misaligned jumps. + if (rd != 0) { + x[rd] = pc + 4; + } + + pc = address; + break; + } + + /////////////////////////////////////////////////////////////////// + // Conditional Branches + case 0b1100011: { // BRANCH + final int funct3 = getField(inst, 12, 14, 0); + final boolean condition; + switch (funct3 >>> 1) { + case 0b00: { // BEQ / BNE + condition = x[rs1] == x[rs2]; + break; + } + case 0b10: { // BLT / BGE + condition = x[rs1] < x[rs2]; + break; + } + case 0b11: { // BLTU / BGEU + condition = Integer.compareUnsigned(x[rs1], x[rs2]) < 0; + break; + } + default: { + throw new R5IllegalInstructionException(inst); + } + } + + final boolean invertCondition = (funct3 & 0b1) != 0; + if (condition ^ invertCondition) { + final int imm = extendSign(getField(inst, 31, 31, 12) | + getField(inst, 25, 30, 5) | + getField(inst, 8, 11, 1) | + getField(inst, 7, 7, 11), 13); + + pc += imm; + } else { + pc += 4; + } + + break; + } + + /////////////////////////////////////////////////////////////////// + // 2.6 Load and Store Instructions + /////////////////////////////////////////////////////////////////// + + case 0b0000011: { // LOAD + final int funct3 = getField(inst, 12, 14, 0); + final int imm = inst >> 20; // inst[31:20], sign extended + final int address = x[rs1] + imm; + final int result; + switch (funct3) { + case 0b000: { // LB + result = load8(address); + break; + } + case 0b001: { // LH + result = load16(address); + break; + } + case 0b010: { // LW + result = load32(address); + break; + } + case 0b100: { // LBU + result = load8(address) & 0xFF; + break; + } + case 0b101: { // LHU + result = load16(address) & 0xFFFF; + break; + } + + default: { + throw new R5IllegalInstructionException(inst); + } + } + + if (rd != 0) { + x[rd] = result; + } + + pc += 4; + break; + } + + case 0b0100011: { // STORE + final int funct3 = getField(inst, 12, 14, 0); + final int imm = extendSign(getField(inst, 25, 31, 5) | + getField(inst, 7, 11, 0), 12); + final int address = x[rs1] + imm; + final int value = x[rs2]; + switch (funct3) { + case 0b000: { // SB + store8(address, (byte) value); + break; + } + case 0b001: { // SH + store16(address, (short) value); + break; + } + case 0b010: { // SW + store32(address, value); + break; + } + default: { + throw new R5IllegalInstructionException(inst); + } + } + + pc += 4; + break; + } + + /////////////////////////////////////////////////////////////////// + // 2.7 Memory Ordering Instructions + /////////////////////////////////////////////////////////////////// + + case 0b0001111: { // MISC-MEM + final int funct3 = getField(inst, 12, 14, 0); + switch (funct3) { + case 0b000: { // FENCE +// if ((inst & 0b1111_00000000_11111_111_11111_0000000) != 0) // Not supporting any flags. +// throw new IllegalInstructionException(inst); + break; + } + + /////////////////////////////////////////////////////////////////// + // Chapter 3: "Zifencei" Instruction-Fetch Fence, Version 2.0 + /////////////////////////////////////////////////////////////////// + + case 0b001: { // FENCE.I + if (inst != 0b000000000000_00000_001_00000_0001111) + throw new R5IllegalInstructionException(inst); + break; + } + default: { + throw new R5IllegalInstructionException(inst); + } + } + + pc += 4; + break; + } + + /////////////////////////////////////////////////////////////////// + // 2.8 Environment Call and Breakpoints + /////////////////////////////////////////////////////////////////// + + case 0b1110011: { // SYSTEM + final int funct3 = getField(inst, 12, 14, 0); + if (funct3 == 0b100) { + throw new R5IllegalInstructionException(inst); + } + + switch (funct3 & 0b11) { + case 0b00: { // PRIV + final int funct12 = inst >>> 20; // inst[31:20], not sign-extended + switch (funct12) { + case 0b0000000_00000: { // ECALL + if ((inst & 0b000000000000_11111_111_11111_0000000) != 0) { + throw new R5IllegalInstructionException(inst); + } + + throw new R5ECallException(priv); + } + case 0b0000000_00001: { // EBREAK + if ((inst & 0b000000000000_11111_111_11111_0000000) != 0) { + throw new R5IllegalInstructionException(inst); + } + + throw new R5BreakpointException(); + } + // 0b0000000_00010: URET + case 0b0001000_00010: { // SRET + if ((inst & 0b000000000000_11111_111_11111_0000000) != 0) { + throw new R5IllegalInstructionException(inst); + } + if (priv < R5.PRIVILEGE_S) { + throw new R5IllegalInstructionException(inst); + } + + sret(); + return; + } + case 0b0011000_00010: { // MRET + if ((inst & 0b000000000000_11111_111_11111_0000000) != 0) { + throw new R5IllegalInstructionException(inst); + } + if (priv < R5.PRIVILEGE_M) { + throw new R5IllegalInstructionException(inst); + } + + mret(); + return; + } + + case 0b0001000_00101: { // WFI + if ((inst & 0b000000000000_11111_111_11111_0000000) != 0) { + throw new R5IllegalInstructionException(inst); + } + if (priv == R5.PRIVILEGE_U) { + throw new R5IllegalInstructionException(inst); + } + + if ((mip & mie) == 0) { + waitingForInterrupt = true; + pc += 4; + return; + } + break; + } + + default: { + final int funct7 = funct12 >>> 5; + if (funct7 == 0b0001001) { // SFENCE.VMA + if ((inst & 0b0000000_00000_00000_111_11111_0000000) != 0) { + throw new R5IllegalInstructionException(inst); + } + if (priv == R5.PRIVILEGE_U) { + throw new R5IllegalInstructionException(inst); + } + + if (rs1 == 0) { + flushTLB(); + } else { + flushTLB(x[rs1]); + } + } else { + throw new R5IllegalInstructionException(inst); + } + break; + } + } + } + + /////////////////////////////////////////////////////////////////// + // Chapter 9 "Zicsr", Control and Status Register (CSR) Instructions, Version 2.0 + /////////////////////////////////////////////////////////////////// + + case 0b01: // CSRRW[I] + case 0b10: // CSRRS[I] + case 0b11: { // CSRRC[I] + final int csr = inst >>> 20; // inst[31:20], not sign-extended + final int a = (funct3 & 0b100) == 0 ? x[rs1] : rs1; // 0b1XX are immediate versions. + final int funct3lb = funct3 & 0b11; + switch (funct3lb) { + case 0b01: { // CSRRW[I] + checkCSR(inst, csr, true); + if (rd != 0) { // Explicit check, spec says no read side-effects when rd = 0. + final int b = readCSR(inst, csr); + writeCSR(inst, csr, a); + x[rd] = b; // Write to register last, avoid lingering side-effect when write errors. + } else { + writeCSR(inst, csr, a); + } + break; + } + case 0b10: // CSRRS[I] + case 0b11: { // CSRRC[I] + final int b; + if (rs1 != 0) { + checkCSR(inst, csr, true); + b = readCSR(inst, csr); + final int masked = funct3lb == 0b10 ? (a | b) : (~a & b); + writeCSR(inst, csr, masked); + } else { + checkCSR(inst, csr, false); + b = readCSR(inst, csr); + } + + if (rd != 0) { + x[rd] = b; + } + break; + } + } + break; + } + } + + pc += 4; + break; + } + + /////////////////////////////////////////////////////////////////// + // Chapter 8 "A" Standard Extension for Atomic Instructions, Version 2.1 + /////////////////////////////////////////////////////////////////// + + case 0b0101111: { // AMO + final int funct3 = getField(inst, 12, 14, 0); + switch (funct3) { // width + case 0b010: { // 32 + final int funct5 = inst >>> 27; // inst[31:27], not sign-extended + final int address = x[rs1]; + final int result; + switch (funct5) { + /////////////////////////////////////////////////////////////////// + // 8.2 Load-Reserved/Store-Conditional Instructions + case 0b00010: { // LR.W + if (rs2 != 0) { + throw new R5IllegalInstructionException(inst); + } + result = load32(address); + reservation_set = address; + break; + } + case 0b00011: { // SC.W + if (address == reservation_set) { + store32(address, x[rs2]); + result = 0; + } else { + result = 1; + } + reservation_set = -1; // Always invalidate as per spec. + break; + } + + /////////////////////////////////////////////////////////////////// + // 8.4 Atomic Memory Operations + case 0b00001: // AMOSWAP.W + case 0b00000: // AMOADD.W + case 0b00100: // AMOXOR.W + case 0b01100: // AMOAND.W + case 0b01000: // AMOOR.W + case 0b10000: // AMOMIN.W + case 0b10100: // AMOMAX.W + case 0b11000: // AMOMINU.W + case 0b11100: { // AMOMAXU.W + // Grab operands, load left-hand from memory, right-hand from register. + final int a = load32(address); + final int b = x[rs2]; + + // Perform atomic operation. + final int c; + switch (funct5) { + case 0b00001: { // AMOSWAP.W + c = b; + break; + } + case 0b00000: { // AMOADD.W + c = a + b; + break; + } + case 0b00100: { // AMOXOR.W + c = a ^ b; + break; + } + case 0b01100: { // AMOAND.W + c = a & b; + break; + } + case 0b01000: { // AMOOR.W + c = a | b; + break; + } + case 0b10000: { // AMOMIN.W + c = Math.min(a, b); + break; + } + case 0b10100: { // AMOMAX.W + c = Math.max(a, b); + break; + } + case 0b11000: { // AMOMINU.W + c = (int) Math.min(Integer.toUnsignedLong(a), Integer.toUnsignedLong(b)); + break; + } + case 0b11100: { // AMOMAXU.W + c = (int) Math.max(Integer.toUnsignedLong(a), Integer.toUnsignedLong(b)); + break; + } + + default: { + throw new R5IllegalInstructionException(inst); + } + } + + // Store value read from memory in register, write result back to memory. + result = a; + store32(address, c); + break; + } + + default: { + throw new R5IllegalInstructionException(inst); + } + } + + if (rd != 0) { + x[rd] = result; + } + + break; + } + case 0b011: { // 64 + throw new R5IllegalInstructionException(inst); + } + + default: { + throw new R5IllegalInstructionException(inst); + } + } + + pc += 4; + break; + } + + default: { + throw new R5IllegalInstructionException(inst); + } + } + } else { + // Compressed instruction decoding, V1p97ff, p112f. + + if (inst == 0) { // Defined illegal instruction. + throw new R5IllegalInstructionException(inst); + } + + final int op = inst & 0b11; + switch (op) { + case 0b00: { // Quadrant 0 + final int funct3 = getField(inst, 13, 15, 0); + final int rd = getField(inst, 2, 4, 0) + 8; // V1p100 + switch (funct3) { + case 0b000: { // C.ADDI4SPN + final int imm = getField(inst, 11, 12, 4) | + getField(inst, 7, 10, 6) | + getField(inst, 6, 6, 2) | + getField(inst, 5, 5, 3); + if (imm == 0) { + throw new R5IllegalInstructionException(inst); + } + + x[rd] = x[2] + imm; + break; + } + // 0b001: C.FLD + case 0b010: { // C.LW + final int offset = getField(inst, 10, 12, 3) | + getField(inst, 6, 6, 2) | + getField(inst, 5, 5, 6); + final int rs1 = getField(inst, 7, 9, 0) + 8; // V1p100 + x[rd] = load32(x[rs1] + offset); + break; + } + // 0b011: C.FLW + // 0b101: C.FSD + case 0b110: { // C.SW + final int offset = getField(inst, 10, 12, 3) | + getField(inst, 6, 6, 2) | + getField(inst, 5, 5, 6); + final int rs1 = getField(inst, 7, 9, 0) + 8; // V1p100 + store32(x[rs1] + offset, x[rd /* = rs2 */]); + break; + } + // 0b111: C.FSW + + default: { + throw new R5IllegalInstructionException(inst); + } + } + + pc += 2; + break; + } + + case 0b01: { // Quadrant 1 + final int funct3 = getField(inst, 13, 15, 0); + switch (funct3) { + case 0b000: { // C.NOP / C.ADDI + final int rd = getField(inst, 7, 11, 0); + if (rd != 0) { // C.ADDI + final int imm = extendSign(getField(inst, 12, 12, 5) | + getField(inst, 2, 6, 0), 6); + x[rd] += imm; + } // else: imm != 0 ? HINT : C.NOP + + pc += 2; + break; + } + case 0b001: { // C.JAL + final int offset = extendSign(getField(inst, 12, 12, 11) | + getField(inst, 11, 11, 4) | + getField(inst, 9, 10, 8) | + getField(inst, 8, 8, 10) | + getField(inst, 7, 7, 6) | + getField(inst, 6, 6, 7) | + getField(inst, 3, 5, 1) | + getField(inst, 2, 2, 5), 12); + x[1] = pc + 2; + pc += offset; + break; + } + case 0b010: { // C.LI + final int rd = getField(inst, 7, 11, 0); + if (rd != 0) { + final int imm = extendSign(getField(inst, 12, 12, 5) | + getField(inst, 2, 6, 0), 6); + x[rd] = imm; + } // else: HINT + + pc += 2; + break; + } + case 0b011: { // C.ADDI16SP / C.LUI + final int rd = getField(inst, 7, 11, 0); + if (rd == 2) { // C.ADDI16SP + final int imm = extendSign(getField(inst, 12, 12, 9) | + getField(inst, 6, 6, 4) | + getField(inst, 5, 5, 6) | + getField(inst, 3, 4, 7) | + getField(inst, 2, 2, 5), 10); + if (imm == 0) { // Reserved. + throw new R5IllegalInstructionException(inst); + } + x[2] += imm; + } else if (rd != 0) { // C.LUI + final int imm = extendSign(getField(inst, 12, 12, 17) | + getField(inst, 2, 6, 12), 18); + if (imm == 0) { // Reserved. + throw new R5IllegalInstructionException(inst); + } + x[rd] = imm; + } // else: HINT + + pc += 2; + break; + } + case 0b100: { // C.SRLI / C.SRAI / C.ANDI / C.SUB / C.XOR / C.OR / C.AND + final int funct2 = getField(inst, 10, 11, 0); + final int rd = getField(inst, 7, 9, 0) + 8; + switch (funct2) { + case 0b00: // C.SRLI + case 0b01: { // C.SRAI + final int imm = getField(inst, 12, 12, 5) | + getField(inst, 2, 6, 0); + // imm[5] = 0 reserved for custom extensions; same as = 1 for us. + if ((funct2 & 0b1) == 0) { + x[rd] = x[rd] >>> imm; + } else { + x[rd] = x[rd] >> imm; + } + break; + } + case 0b10: { // C.ANDI + final int imm = extendSign(getField(inst, 12, 12, 5) | + getField(inst, 2, 6, 0), 6); + x[rd] &= imm; + break; + } + case 0b11: { // C.SUB / C.XOR / C.OR / C.AND + final int funct3b = getField(inst, 5, 6, 0) | + getField(inst, 12, 12, 2); + final int rs2 = getField(inst, 2, 4, 0) + 8; + switch (funct3b) { + case 0b000: { // C.SUB + x[rd] = x[rd] - x[rs2]; + break; + } + case 0b001: { // C.XOR + x[rd] = x[rd] ^ x[rs2]; + break; + } + case 0b010: { // C.OR + x[rd] = x[rd] | x[rs2]; + break; + } + case 0b011: { // C.AND + x[rd] = x[rd] & x[rs2]; + break; + } + // 0b100: C.SUBW + // 0b101: C.ADDW + + default: { + throw new R5IllegalInstructionException(inst); + } + } + break; + } + } + + pc += 2; + break; + } + case 0b101: { // C.J + final int offset = extendSign(getField(inst, 12, 12, 11) | + getField(inst, 11, 11, 4) | + getField(inst, 9, 10, 8) | + getField(inst, 8, 8, 10) | + getField(inst, 7, 7, 6) | + getField(inst, 6, 6, 7) | + getField(inst, 3, 5, 1) | + getField(inst, 2, 2, 5), 12); + pc += offset; + break; + } + case 0b110: // C.BEQZ + case 0b111: { // C.BNEZ + final int rs1 = getField(inst, 7, 9, 0) + 8; + final boolean condition = x[rs1] == 0; + if (condition ^ ((funct3 & 0b1) != 0)) { + final int offset = extendSign(getField(inst, 12, 12, 8) | + getField(inst, 10, 11, 3) | + getField(inst, 5, 6, 6) | + getField(inst, 3, 4, 1) | + getField(inst, 2, 2, 5), 9); + pc += offset; + } else { + pc += 2; + } + break; + } + + default: { + throw new R5IllegalInstructionException(inst); + } + } + + break; + } + + case 0b10: { // Quadrant 2 + final int funct3 = getField(inst, 13, 15, 0); + final int rd = getField(inst, 7, 11, 0); + switch (funct3) { + case 0b000: { // C.SLLI + final int imm = getField(inst, 12, 12, 5) | + getField(inst, 2, 6, 0); + // imm[5] = 0 reserved for custom extensions; same as = 1 for us. + if (rd != 0) { + x[rd] = x[rd] << imm; + } // else: HINT + + pc += 2; + break; + } + // 0b001: C.FLDSP + case 0b010: { // C.LWSP + final int offset = getField(inst, 12, 12, 5) | + getField(inst, 4, 6, 2) | + getField(inst, 2, 3, 6); + if (rd != 0) { + x[rd] = load32(x[2] + offset); + } + + pc += 2; + break; + } + // 0b011: C.FLWSP + case 0b100: { // C.JR / C.MV / C.EBREAK / C.JALR / C.ADD + final int rs2 = getField(inst, 2, 6, 0); + if ((inst & (1 << 12)) == 0) { // C.JR / C.MV + if (rs2 == 0) { // C.JR + if (rd == 0) { + throw new R5IllegalInstructionException(inst); + } + + pc = x[rd /* = rs1 */] & ~1; + } else { // C.MV + if (rd != 0) { + x[rd] = x[rs2]; + } // else: HINT + + pc += 2; + } + } else { // C.EBREAK / C.JALR / C.ADD + if (rs2 == 0) { // C.EBREAK / C.JALR + if (rd == 0) { // C.EBREAK + throw new R5BreakpointException(); + } else { // C.JALR + final int address = x[rd /* = rs1 */] & ~1; // Technically should raise exception on misaligned jump. + final int value = pc + 2; // In case rd == 1, avoid overwriting before using. + x[1] = value; + pc = address; + } + } else { // C.ADD + if (rd != 0) { + x[rd] += x[rs2]; + } // else: HINT + + pc += 2; + } + } + + break; + } + // 0b101: C.FSDSP + case 0b110: { // C.SWSP + final int offset = getField(inst, 9, 12, 2) | + getField(inst, 7, 8, 6); + final int rs2 = getField(inst, 2, 6, 0); + final int address = x[2] + offset; + final int value = x[rs2]; + store32(address, value); + + pc += 2; + break; + } + // 0b111: C.FSWSP + + default: { + throw new R5IllegalInstructionException(inst); + } + } + + break; + } + + default: { + throw new R5IllegalInstructionException(inst); + } + } + } + } + + private void checkCSR(final int inst, final int csr, final boolean throwIfReadonly) throws R5IllegalInstructionException { + if (throwIfReadonly && ((csr >= 0xC00 && csr <= 0xC1F) || (csr >= 0xC80 && csr <= 0xC9F))) + throw new R5IllegalInstructionException(inst); + + // Topmost bits, i.e. csr[11:8], encode access rights for CSR by convention. Of these, the top-most two bits, + // csr[11:10], encode read-only state, where 0b11: read-only, 0b00..0b10: read-write. + if (throwIfReadonly && ((csr & 0b1100_0000_0000) == 0b1100_0000_0000)) + throw new R5IllegalInstructionException(inst); + // The two following bits, csr[9:8], encode the lowest privilege level that can access the CSR. + if (priv < ((csr >>> 8) & 0b11)) + throw new R5IllegalInstructionException(inst); + } + + @SuppressWarnings("DuplicateBranchesInSwitch") + private int readCSR(final int inst, final int csr) throws R5IllegalInstructionException { + switch (csr) { + // Floating-Point Control and Status Registers +// case 0x001: { // fflags, Floating-Point Accrued Exceptions. +// return fflags; +// } +// case 0x002: { // frm, Floating-Point Dynamic Rounding Mode. +// return frm; +// } +// case 0x003: { // fcsr, Floating-Point Control and Status Register (frm + fflags). +// return (frm << 5) | fflags; +// } + + // User Trap Setup + // 0x000: ustatus, User status register. + // 0x004: uie, User interrupt-enabled register. + // 0x005: utvec, User trap handler base address. + + // User Trap Handling + // 0x040: uscratch, Scratch register for user trap handlers. + // 0x041: uepc, User exception program counter. + // 0x042: ucause, User trap cause. + // 0x043: utval, User bad address or instruction. + // 0x044: uip, User interrupt pending. + + // Supervisor Trap Setup + case 0x100: { // sstatus, Supervisor status register. + return getStatus(SSTATUS_MASK); + } + // 0x102: sedeleg, Supervisor exception delegation register. + // 0x103: sideleg, Supervisor interrupt delegation register. + case 0x104: { // sie, Supervisor interrupt-enable register. + return mie & mideleg; // Effectively read-only because we don't implement N. + } + case 0x105: { // stvec, Supervisor trap handler base address. + return stvec; + } + case 0x106: { // scounteren, Supervisor counter enable. + return scounteren; + } + + // Supervisor Trap Handling + case 0x140: { // sscratch Scratch register for supervisor trap handlers. + return sscratch; + } + case 0x141: { // sepc Supervisor exception program counter. + return sepc; + } + case 0x142: { // scause Supervisor trap cause. + return scause; + } + case 0x143: { // stval Supervisor bad address or instruction. + return stval; + } + case 0x144: { // sip Supervisor interrupt pending. + return mip & mideleg; // Effectively read-only because we don't implement N. + } + + // Supervisor Protection and Translation + case 0x180: { // satp Supervisor address translation and protection. + if (priv == R5.PRIVILEGE_S && (mstatus & R5.STATUS_TVM_MASK) != 0) { + throw new R5IllegalInstructionException(inst); + } + return satp; + } + + // Virtual Supervisor Registers + // 0x200: vsstatus, Virtual supervisor status register. + // 0x204: vsie, Virtual supervisor interrupt-enable register. + // 0x205: vstvec, Virtual supervisor trap handler base address. + // 0x240: vsscratch, Virtual supervisor scratch register. + // 0x241: vsepc, Virtual supervisor exception program counter. + // 0x242: vscause, Virtual supervisor trap cause. + // 0x243: vstval, Virtual supervisor bad address or instruction. + // 0x244: vsip, Virtual supervisor interrupt pending. + // 0x280: vsatp, Virtual supervisor address translation and protection + + // Machine Trap Setup + case 0x300: { // mstatus Machine status register. + return getStatus(MSTATUS_MASK); + } + case 0x301: { // misa ISA and extensions + return MISA; + } + case 0x302: { // medeleg Machine exception delegation register. + return medeleg; + } + case 0x303: { // mideleg Machine interrupt delegation register. + return mideleg; + } + case 0x304: { // mie Machine interrupt-enable register. + return mie; + } + case 0x305: { // mtvec Machine trap-handler base address. + return mtvec; + } + case 0x306: { // mcounteren Machine counter enable. + return mcounteren; + } + case 0x310: {// mstatush, Additional machine status register, RV32 only. + return 0; // Hardcoded to zero. MBE = 0, SBE = 0 -> always little-endian. + } + + // Machine Trap Handling + case 0x340: { // mscratch Scratch register for machine trap handlers. + return mscratch; + } + case 0x341: { // mepc Machine exception program counter. + return mepc; + } + case 0x342: { // mcause Machine trap cause. + return mcause; + } + case 0x343: { // mtval Machine bad address or instruction. + return mtval; + } + case 0x344: { // mip Machine interrupt pending. + return mip; + } + // 0x34A: mtinst, Machine trap instruction (transformed). + // 0x34B: mtval2, Machine bad guest physical address. + + // Machine Memory Protection + // 0x3A0: pmpcfg0. Physical memory protection configuration. + // 0x3A1: pmpcfg1. Physical memory protection configuration, RV32 only. + // 0x3A2: pmpcfg2. Physical memory protection configuration. + // 0x3A3...0x3AE: pmpcfg3...pmpcfg14, Physical memory protection configuration, RV32 only. + // 0x3AF: pmpcfg15, Physical memory protection configuration, RV32 only. + // 0x3B0: pmpaddr0, Physical memory protection address register. + // 0x3B1...0x3EF: pmpaddr1...pmpaddr63, Physical memory protection address register. + + // Hypervisor Trap Setup + // 0x600: hstatus, Hypervisor status register. + // 0x602: hedeleg, Hypervisor exception delegation register. + // 0x603: hideleg, Hypervisor interrupt delegation register. + // 0x604: hie, Hypervisor interrupt-enable register. + // 0x606: hcounteren, Hypervisor counter enable. + // 0x607: hgeie, Hypervisor guest external interrupt-enable register. + + // Hypervisor Trap Handling + // 0x643: htval, Hypervisor bad guest physical address. + // 0x644: hip, Hypervisor interrupt pending. + // 0x645: hvip, Hypervisor virtual interrupt pending. + // 0x64A: htinst, Hypervisor trap instruction (transformed). + // 0xE12: hgeip, Hypervisor guest external interrupt pending. + + // Hypervisor Protection and Translation + // 0x680: hgatp, Hypervisor guest address translation and protection. + + // Hypervisor Counter/Timer Virtualization Registers + // 0x605: htimedelta, Delta for VS/VU-mode timer. + // 0x615: htimedeltah, Upper 32 bits of htimedelta, RV32 only. + + //Machine Counter/Timers + case 0xB00: // mcycle, Machine cycle counter. + case 0xB02: { // minstret, Machine instructions-retired counter. + return (int) mcycle; + } + //0xB03: mhpmcounter3, Machine performance-monitoring counter. + //0xB04...0xB1F: mhpmcounter4...mhpmcounter31, Machine performance-monitoring counter. + case 0xB80: // mcycleh, Upper 32 bits of mcycle, RV32 only. + case 0xB82: { // minstreth, Upper 32 bits of minstret, RV32 only. + return (int) (mcycle >> 32); + } + //0xB83: mhpmcounter3h, Upper 32 bits of mhpmcounter3, RV32 only. + //0xB84...0xB9F: mhpmcounter4h...mhpmcounter31h, Upper 32 bits of mhpmcounter4, RV32 only. + + // Counters and Timers + case 0xC00: // cycle + case 0xC02: { // instret + // See Volume 2 p36: mcounteren/scounteren define availability to next lowest privilege level. + if (priv < R5.PRIVILEGE_M) { + final int counteren; + if (priv < R5.PRIVILEGE_S) { + counteren = scounteren; + } else { + counteren = mcounteren; + } + + // counteren[2:0] is IR, TM, CY. As such the bit index matches the masked csr value. + if ((counteren & (1 << (csr & 0b11))) == 0) { + throw new R5IllegalInstructionException(inst); + } + } + return (int) mcycle; + } + case 0xC01: { // time + return (int) rtc.getTime(); + } + // 0xC03 ... 0xC1F: hpmcounter3 ... hpmcounter31 + case 0xC80: // cycleh + case 0xC82: { // instreth + // See Volume 2 p36: mcounteren/scounteren define availability to next lowest privilege level. + if (priv < R5.PRIVILEGE_M) { + final int counteren; + if (priv < R5.PRIVILEGE_S) { + counteren = scounteren; + } else { + counteren = mcounteren; + } + + // counteren[2:0] is IR, TM, CY. As such the bit index matches the masked csr value. + if ((counteren & (1 << (csr & 0b11))) == 0) { + throw new R5IllegalInstructionException(inst); + } + } + return (int) (mcycle >> 32); + } + case 0xC81: { // timeh + return (int) (rtc.getTime() >>> 32); + } + // 0xC83 ... 0xC9F: hpmcounter3h ... hpmcounter31h + + // Machine Information Registers + case 0xF11: { // mvendorid, Vendor ID. + return 0; // Not implemented. + } + case 0xF12: { // marchid, Architecture ID. + return 0; // Not implemented. + } + case 0xF13: { // mimpid, Implementation ID. + return 0; // Not implemented. + } + case 0xF14: { // mhartid, Hardware thread ID. + return 0; // Single, primary hart. + } + + default: { + throw new R5IllegalInstructionException(inst); + } + } + } + + private void writeCSR(final int inst, final int csr, final int value) throws R5IllegalInstructionException { + switch (csr) { + // Floating-Point Control and Status Registers +// case 0x001: { // fflags, Floating-Point Accrued Exceptions. +// fflags = (byte) (value & 0b11111); +// } +// case 0x002: { // frm, Floating-Point Dynamic Rounding Mode. +// frm = (byte) (value & 0b111); +// if (frm >= 5) frm = 0; // TODO Not to spec; should store and raise invalid instruction on FP ops. +// } +// case 0x003: { // fcsr, Floating-Point Control and Status Register (frm + fflags). +// frm = (byte) ((value >>> 5) & 0b111); +// if (frm >= 5) frm = 0; // TODO Not to spec; should store and raise invalid instruction on FP ops. +// fflags = (byte) (value & 0b11111); +// break; +// } + + // User Trap Setup + // 0x000: ustatus, User status register. + // 0x004: uie, User interrupt-enabled register. + // 0x005: utvec, User trap handler base address. + + // User Trap Handling + // 0x040: uscratch, Scratch register for user trap handlers. + // 0x041: uepc, User exception program counter. + // 0x042: ucause, User trap cause. + // 0x043: utval, User bad address or instruction. + // 0x044: uip, User interrupt pending. + + // Supervisor Trap Setup + case 0x100: { // sstatus, Supervisor status register. + setStatus((mstatus & ~SSTATUS_MASK) | (value & SSTATUS_MASK)); + break; + } + // 0x102: sedeleg, Supervisor exception delegation register. + // 0x103: sideleg, Supervisor interrupt delegation register. + case 0x104: { // sie, Supervisor interrupt-enable register. + final int mask = mideleg; // Can only set stuff that's delegated to S mode. + mie = (mie & ~mask) | (value & mask); + break; + } + case 0x105: { // stvec, Supervisor trap handler base address. + stvec = value & ~0b10; // Don't allow reserved modes. + break; + } + case 0x106: { // scounteren, Supervisor counter enable. + scounteren = value & COUNTEREN_MASK; + break; + } + + // Supervisor Trap Handling + case 0x140: { // sscratch Scratch register for supervisor trap handlers. + sscratch = value; + break; + } + case 0x141: { // sepc Supervisor exception program counter. + sepc = value & ~0b1; + break; + } + case 0x142: { // scause Supervisor trap cause. + scause = value; + break; + } + case 0x143: { // stval Supervisor bad address or instruction. + stval = value; + break; + } + case 0x144: { // sip Supervisor interrupt pending. + final int mask = mideleg; // Can only set stuff that's delegated to S mode. + mip = (mip & ~mask) | (value & mask); + break; + } + + // Supervisor Protection and Translation + case 0x180: { // satp Supervisor address translation and protection. + final int validatedValue = value & ~R5.SATP_ASID_MASK; // Say no to ASID (not implemented). + final int change = satp ^ validatedValue; + if ((change & (R5.SATP_MODE_MASK | R5.SATP_PPN_MASK)) != 0) { + if (priv == R5.PRIVILEGE_S && (mstatus & R5.STATUS_TVM_MASK) != 0) { + throw new R5IllegalInstructionException(inst); + } + + satp = validatedValue; + flushTLB(); + } + break; + } + + // Virtual Supervisor Registers + // 0x200: vsstatus, Virtual supervisor status register. + // 0x204: vsie, Virtual supervisor interrupt-enable register. + // 0x205: vstvec, Virtual supervisor trap handler base address. + // 0x240: vsscratch, Virtual supervisor scratch register. + // 0x241: vsepc, Virtual supervisor exception program counter. + // 0x242: vscause, Virtual supervisor trap cause. + // 0x243: vstval, Virtual supervisor bad address or instruction. + // 0x244: vsip, Virtual supervisor interrupt pending. + // 0x280: vsatp, Virtual supervisor address translation and protection + + // Machine Trap Setup + case 0x300: { // mstatus Machine status register. + setStatus(value); + break; + } + case 0x301: { // misa ISA and extensions + break; // We do not support changing feature sets dynamically. + } + case 0x302: { // medeleg Machine exception delegation register. + // From Volume 2 p31: For exceptions that cannot occur in less privileged modes, the corresponding + // medeleg bits should be hardwired to zero. In particular, medeleg[11] is hardwired to zero. + medeleg = value & ~(1 << R5.EXCEPTION_MACHINE_ECALL); + break; + } + case 0x303: { // mideleg Machine interrupt delegation register. + final int mask = R5.SSIP_MASK | R5.STIP_MASK | R5.SEIP_MASK; + mideleg = (mideleg & ~mask) | (value & mask); + break; + } + case 0x304: { // mie Machine interrupt-enable register. + final int mask = R5.MEIP_MASK | R5.MTIP_MASK | R5.MSIP_MASK | R5.SEIP_MASK | R5.STIP_MASK | R5.SSIP_MASK; + mie = (mie & ~mask) | (value & mask); + break; + } + case 0x305: { // mtvec Machine trap-handler base address. + mtvec = value & ~0b10; // Don't allow reserved modes. + } + case 0x306: { // mcounteren Machine counter enable. + mcounteren = value & COUNTEREN_MASK; + break; + } + // 0x310: mstatush, Additional machine status register, RV32 only. + + // Machine Trap Handling + case 0x340: { // mscratch Scratch register for machine trap handlers. + mscratch = value; + break; + } + case 0x341: { // mepc Machine exception program counter. + mepc = value & ~0b1; // p38: Lowest bit must always be zero. + break; + } + case 0x342: { // mcause Machine trap cause. + mcause = value; + break; + } + case 0x343: { // mtval Machine bad address or instruction. + mtval = value; + break; + } + case 0x344: { // mip Machine interrupt pending. + // p32: MEIP, MTIP, MSIP are readonly in mip. + final int mask = R5.SEIP_MASK | R5.STIP_MASK | R5.SSIP_MASK; + mip = (mip & ~mask) | (value & mask); + break; + } + // 0x34A: mtinst, Machine trap instruction (transformed). + // 0x34B: mtval2, Machine bad guest physical address. + + // Machine Memory Protection + // 0x3A0: pmpcfg0. Physical memory protection configuration. + // 0x3A1: pmpcfg1. Physical memory protection configuration, RV32 only. + // 0x3A2: pmpcfg2. Physical memory protection configuration. + // 0x3A3...0x3AE: pmpcfg3...pmpcfg14, Physical memory protection configuration, RV32 only. + // 0x3AF: pmpcfg15, Physical memory protection configuration, RV32 only. + // 0x3B0: pmpaddr0, Physical memory protection address register. + // 0x3B1...0x3EF: pmpaddr1...pmpaddr63, Physical memory protection address register. + + // Hypervisor Trap Setup + // 0x600: hstatus, Hypervisor status register. + // 0x602: hedeleg, Hypervisor exception delegation register. + // 0x603: hideleg, Hypervisor interrupt delegation register. + // 0x604: hie, Hypervisor interrupt-enable register. + // 0x606: hcounteren, Hypervisor counter enable. + // 0x607: hgeie, Hypervisor guest external interrupt-enable register. + + // Hypervisor Trap Handling + // 0x643: htval, Hypervisor bad guest physical address. + // 0x644: hip, Hypervisor interrupt pending. + // 0x645: hvip, Hypervisor virtual interrupt pending. + // 0x64A: htinst, Hypervisor trap instruction (transformed). + + // Hypervisor Protection and Translation + // 0x680: hgatp, Hypervisor guest address translation and protection. + + // Hypervisor Counter/Timer Virtualization Registers + // 0x605: htimedelta, Delta for VS/VU-mode timer. + // 0x615: htimedeltah, Upper 32 bits of htimedelta, RV32 only. + + default: { + throw new R5IllegalInstructionException(inst); + } + } + } + + private int getStatus(final int mask) { + final int value = (mstatus | (fs << R5.STATUS_FS_SHIFT)) & mask; + if (((value & R5.STATUS_FS_MASK) == R5.STATUS_FS_MASK) || ((value & R5.STATUS_XS_MASK) == R5.STATUS_XS_MASK)) { + return value | R5.STATUS_SD_MASK; + } + return value; + } + + private void setStatus(final int value) { + final int change = mstatus ^ value; + final boolean mmuConfigChanged = + (change & (R5.STATUS_MPRV_MASK | R5.STATUS_SUM_MASK | R5.STATUS_MXR_MASK)) != 0 || + ((mstatus & R5.STATUS_MPRV_MASK) != 0 && (change & R5.STATUS_MPP_MASK) != 0); + if (mmuConfigChanged) { + flushTLB(); + } + + fs = (byte) ((value & R5.STATUS_FS_MASK) >>> R5.STATUS_FS_SHIFT); + + final int mask = MSTATUS_MASK & ~R5.STATUS_FS_MASK; + mstatus = (mstatus & ~mask) | (value & mask); + } + + private void setPrivilege(final int level) { + if (priv == level) { + return; + } + + flushTLB(); + + priv = level; + } + + private void raiseException(final int cause, final int value) { + // Exceptions take cycle. + mcycle++; + + // Check whether to run supervisor level trap instead of machine level one. + // We don't implement the N extension (user level interrupts) so if we're + // currently in S or U privilege level we'll run the S level trap handler + // either way -- assuming that the current interrupt/exception is allowed + // to be delegated by M level. + final boolean runInSupervisorMode; + final int exception = cause & ~R5.INTERRUPT; + if (priv <= R5.PRIVILEGE_S) { + if (cause != exception) { // Interrupt. + runInSupervisorMode = ((mideleg >>> exception) & 0b1) != 0; + } else { + runInSupervisorMode = ((medeleg >>> exception) & 0b1) != 0; + } + } else { + runInSupervisorMode = false; + } + + // Was interrupt for current priv level enabled? There are cases we can + // get here even for interrupts! Specifically when an M level interrupt + // is raised while in S mode. This will get here even if M level interrupt + // enabled bit is zero, as per spec (Volume 2 p21). + final int oldIE = (mstatus >>> priv) & 0b1; + + final int vec; + if (runInSupervisorMode) { + scause = cause; + sepc = pc; + stval = value; + mstatus = (mstatus & ~R5.STATUS_SPIE_MASK) | + (oldIE << R5.STATUS_SPIE_SHIFT); + mstatus = (mstatus & ~R5.STATUS_SPP_MASK) | + (priv << R5.STATUS_SPP_SHIFT); + mstatus &= ~R5.STATUS_SIE_MASK; + setPrivilege(R5.PRIVILEGE_S); + vec = stvec; + } else { + mcause = cause; + mepc = pc; + mtval = value; + mstatus = (mstatus & ~R5.STATUS_MPIE_MASK) | + (oldIE << R5.STATUS_MPIE_SHIFT); + mstatus = (mstatus & ~R5.STATUS_MPP_MASK) | + (priv << R5.STATUS_MPP_SHIFT); + mstatus &= ~R5.STATUS_MIE_MASK; + setPrivilege(R5.PRIVILEGE_M); + vec = mtvec; + } + + final int mode = vec & 0b11; + switch (mode) { + case 0b01: { // Vectored + if ((cause & R5.INTERRUPT) != 0) { + pc = vec + 4 * exception; + } else { + pc = vec & ~0b1; + } + break; + } + case 0b00: // Direct + default: { + pc = vec; + break; + } + } + } + + private void mret() { + final int mpp = (mstatus & R5.STATUS_MPP_MASK) >>> R5.STATUS_MPP_SHIFT; // Previous privilege level. + final int mpie = (mstatus & R5.STATUS_MPIE_MASK) >>> R5.STATUS_MPIE_SHIFT; // Preview interrupt-enable state. + mstatus = (mstatus & ~(1 << mpp)) | + (mpie << mpp); + mstatus |= R5.STATUS_MPIE_MASK; + mstatus &= ~R5.STATUS_MPP_MASK; + setPrivilege(mpp); + pc = mepc; + } + + private void sret() { + final int spp = (mstatus & R5.STATUS_SPP_MASK) >>> R5.STATUS_SPP_SHIFT; // Previous privilege level. + final int spie = (mstatus & R5.STATUS_SPIE_MASK) >>> R5.STATUS_SPIE_SHIFT; // Preview interrupt-enable state. + mstatus = (mstatus & ~(1 << spp)) | + (spie << spp); + mstatus |= R5.STATUS_SPIE_MASK; + mstatus &= ~R5.STATUS_SPP_MASK; + setPrivilege(spp); + pc = sepc; + } + + private void raiseException(final int cause) { + raiseException(cause, 0); + } + + private boolean raiseInterrupt() { + int pending = mip & mie; + assert pending != 0; + + int enabled = 0; + switch (priv) { + case R5.PRIVILEGE_M: { + // Check global MIE flag. + if ((mstatus & R5.STATUS_MIE_MASK) != 0) + enabled = ~mideleg; + break; + } + case R5.PRIVILEGE_S: { + // V2p21: interrupts handled by a higher privilege level, i.e. that are not delegated + // to a lower privilege level, will always fire -- even if their global flag is false! + enabled = ~mideleg; + + // Check global SIE flag. + if ((mstatus & R5.STATUS_SIE_MASK) != 0) + enabled |= mideleg; + break; + } + // We don't have the "N" extension for user-level interrupts, so all our interrupts will + // always be of M or S level, hence always enabled while in U mode (V2p21). + case R5.PRIVILEGE_U: + default: { + enabled = 0b1111_1111_1111_1111_1111_1111_1111_1111; + break; + } + } + + pending = pending & enabled; + if (pending == 0) { + return false; + } + + // p33: Interrupt order is handled in decreasing order of privilege mode, and inside a single + // privilege mode in order E,S,T. + // TODO custom interrupts have highest prio and are processed low to high + if ((pending & R5.MEIP_MASK) != 0) { + raiseException(R5.MEIP_SHIFT | R5.INTERRUPT); + } else if ((pending & R5.MSIP_MASK) != 0) { + raiseException(R5.MSIP_SHIFT | R5.INTERRUPT); + } else if ((pending & R5.MTIP_MASK) != 0) { + raiseException(R5.MTIP_SHIFT | R5.INTERRUPT); + } else if ((pending & R5.SEIP_MASK) != 0) { + raiseException(R5.SEIP_SHIFT | R5.INTERRUPT); + } else if ((pending & R5.SSIP_MASK) != 0) { + raiseException(R5.SSIP_SHIFT | R5.INTERRUPT); + } else if ((pending & R5.STIP_MASK) != 0) { + raiseException(R5.STIP_SHIFT | R5.INTERRUPT); + } else { + return false; // We don't support custom interrupts for now. + } + + return true; + } + + private void flushTLB() { + for (int i = 0; i < TLB_SIZE; i++) { + tlb_fetch[i].virtualAddress = -1; + } + for (int i = 0; i < TLB_SIZE; i++) { + tlb_read[i].virtualAddress = -1; + } + for (int i = 0; i < TLB_SIZE; i++) { + tlb_write[i].virtualAddress = -1; + } + } + + private void flushTLB(final int address) { + flushTLB(); + } + + private byte load8(final int address) throws MemoryAccessException { + return (byte) load(address, 8, 0); + } + + private void store8(final int address, final byte value) throws MemoryAccessException { + store(address, value, 8, 0); + } + + private short load16(final int address) throws MemoryAccessException { + return (short) load(address, 16, 1); + } + + private void store16(final int address, final short value) throws MemoryAccessException { + store(address, value, 16, 1); + } + + private int load32(final int address) throws MemoryAccessException { + return load(address, 32, 2); + } + + private void store32(final int address, final int value) throws MemoryAccessException { + store(address, value, 32, 2); + } + + private int fetch(final int address) throws MemoryAccessException { + assert (address & 1) == 0 : "misaligned fetch"; + final int tlbIndex = (address >>> R5.PAGE_ADDRESS_SHIFT) & (TLB_SIZE - 1); + final int virtualAddress = address & ~R5.PAGE_ADDRESS_MASK; + final TLBEntry entry = tlb_fetch[tlbIndex]; + if (entry.virtualAddress == virtualAddress) { + return entry.memory.load32(address + entry.toLocal); + } else { + return fetchSlow(address); + } + } + + private int load(final int address, final int size, final int sizeLog2) throws MemoryAccessException { + final int tlbIndex = (address >>> R5.PAGE_ADDRESS_SHIFT) & (TLB_SIZE - 1); + final int alignment = size / 8; // Enforce aligned memory access. + final int alignmentMask = alignment - 1; + final int virtualAddress = address & ~(R5.PAGE_ADDRESS_MASK & ~alignmentMask); + final TLBEntry entry = tlb_read[tlbIndex]; + if (entry.virtualAddress == virtualAddress) { + switch (sizeLog2) { + case 0: + return entry.memory.load8(address + entry.toLocal); + case 1: + return entry.memory.load16(address + entry.toLocal); + case 2: + return entry.memory.load32(address + entry.toLocal); + default: + throw new IllegalArgumentException(); + } + } else { + return loadSlow(address, sizeLog2); + } + } + + private void store(final int address, final int value, final int size, final int sizeLog2) throws MemoryAccessException { + final int tlbIndex = (address >>> R5.PAGE_ADDRESS_SHIFT) & (TLB_SIZE - 1); + final int alignment = size / 8; // Enforce aligned memory access. + final int alignmentMask = alignment - 1; + final int virtualAddress = address & ~(R5.PAGE_ADDRESS_MASK & ~alignmentMask); + final TLBEntry entry = tlb_write[tlbIndex]; + if (entry.virtualAddress == virtualAddress) { + switch (sizeLog2) { + case 0: + entry.memory.store8(address + entry.toLocal, (byte) value); + break; + case 1: + entry.memory.store16(address + entry.toLocal, (short) value); + break; + case 2: + entry.memory.store32(address + entry.toLocal, value); + break; + default: + throw new IllegalArgumentException(); + } + } else { + storeSlow(address, value, sizeLog2); + } + } + + private int fetchSlow(final int address) throws MemoryAccessException { + final int physicalAddress = getPhysicalAddress(address, AccessType.FETCH); + final MemoryRange range = physicalMemory.getMemoryRange(physicalAddress); + if (range == null || !(range.device instanceof PhysicalMemory)) { + throw new FetchFaultException(address); + } + + final int offset = physicalAddress - range.start; + final int tlbIndex = (address >>> R5.PAGE_ADDRESS_SHIFT) & (TLB_SIZE - 1); + final int virtualAddress = address & ~R5.PAGE_ADDRESS_MASK; + + final TLBEntry entry = tlb_fetch[tlbIndex]; + entry.memory = (PhysicalMemory) range.device; + entry.virtualAddress = virtualAddress; + entry.toLocal = offset - address; + + return entry.memory.load32(offset); + } + + private int loadSlow(final int address, final int sizeLog2) throws MemoryAccessException { + final int widthInBytes = 1 << sizeLog2; + final int alignment = address & (widthInBytes - 1); + if (alignment != 0) { + // Unaligned access, manually perform aligned loads. + // TODO: SBI might actually handle this for us, need to test + // throw new MisalignedLoadException(address); + switch (sizeLog2) { + case 1: { + final byte v0 = load8(address); + final byte v1 = load8(address + 1); + return v0 | (v1 << 8); + } + case 2: { + final int v0 = load32(address - alignment); + final int v1 = load32(address - alignment + 4); + return (v0 >>> (alignment * 8)) | (v1 << (32 - alignment * 8)); + } + default: + throw new IllegalArgumentException(); + } + } else { + final int physicalAddress = getPhysicalAddress(address, AccessType.READ); + final MemoryRange range = physicalMemory.getMemoryRange(physicalAddress); + if (range == null) { + LOGGER.debug("Trying to load from invalid physical address [{}].", address); + return 0; + } else if (range.device instanceof PhysicalMemory) { + final int offset = physicalAddress - range.start; + final int tlbIndex = (address >>> R5.PAGE_ADDRESS_SHIFT) & (TLB_SIZE - 1); + final int virtualAddress = address & ~R5.PAGE_ADDRESS_MASK; + + final TLBEntry entry = tlb_read[tlbIndex]; + entry.memory = (PhysicalMemory) range.device; + entry.virtualAddress = virtualAddress; + entry.toLocal = offset - address; + + switch (sizeLog2) { + case 0: + return entry.memory.load8(offset); + case 1: + return entry.memory.load16(offset); + case 2: + return entry.memory.load32(offset); + default: + throw new IllegalArgumentException(); + } + } else { + final int offset = physicalAddress - range.start; + switch (sizeLog2) { + case 0: + return range.device.load8(offset); + case 1: + return range.device.load16(offset); + case 2: + return range.device.load32(offset); + default: + throw new IllegalArgumentException(); + } + } + } + } + + private void storeSlow(final int address, final int value, final int sizeLog2) throws MemoryAccessException { + final int size = 1 << sizeLog2; + final int alignment = address & (size - 1); + if (alignment != 0) { + // Unaligned access, manually perform aligned stores. + // TODO: should avoid modifying memory in case of exception + // TODO: SBI might actually handle this for us, need to test + // throw new MisalignedStoreException(address); + for (int i = 0; i < size; i++) { + store8(address + i, (byte) (value >>> (8 * i))); + } + } else { + final int physicalAddress = getPhysicalAddress(address, AccessType.WRITE); + final MemoryRange range = physicalMemory.getMemoryRange(physicalAddress); + if (range == null) { + LOGGER.debug("Trying to store to invalid physical address [{}].", address); + } else if (range.device instanceof PhysicalMemory) { + final int offset = physicalAddress - range.start; + final int tlbIndex = (address >>> R5.PAGE_ADDRESS_SHIFT) & (TLB_SIZE - 1); + final int virtualAddress = address & ~R5.PAGE_ADDRESS_MASK; + + final TLBEntry entry = tlb_write[tlbIndex]; + entry.memory = (PhysicalMemory) range.device; + entry.virtualAddress = virtualAddress; + entry.toLocal = offset - address; + + switch (sizeLog2) { + case 0: + entry.memory.store8(offset, (byte) value); + break; + case 1: + entry.memory.store16(offset, (short) value); + break; + case 2: + entry.memory.store32(offset, value); + break; + default: + throw new IllegalArgumentException(); + } + + physicalMemory.setDirty(range, offset); + } else { + final int offset = physicalAddress - range.start; + switch (sizeLog2) { + case 0: + range.device.store8(offset, (byte) value); + break; + case 1: + range.device.store16(offset, (short) value); + break; + case 2: + range.device.store32(offset, value); + break; + default: + throw new IllegalArgumentException(); + } + } + } + } + + private int getPhysicalAddress(final int virtualAddress, final AccessType accessType) throws MemoryAccessException { + final int privilege; + if ((mstatus & R5.STATUS_MPRV_MASK) != 0 && accessType != AccessType.FETCH) { + privilege = (mstatus & R5.STATUS_MPP_MASK) >>> R5.STATUS_MPP_SHIFT; + } else { + privilege = this.priv; + } + + if (privilege == R5.PRIVILEGE_M) { + return virtualAddress; + } + + if ((satp & R5.SATP_MODE_MASK) == 0) { + return virtualAddress; + } + + // Virtual address structure: VPN1[31:22], VPN0[21:12], page offset[11:0] + // Physical address structure: PPN1[33:22], PPN0[21:12], page offset[11:0] + // Page table entry structure: PPN1[31:20], PPN0[19:10], RSW[9:8], D, A, G, U, X, W, R, V + + // Virtual address translation, V2p75f. + int pteAddress = (satp & R5.SATP_PPN_MASK) << R5.PAGE_ADDRESS_SHIFT; // 1. + for (int i = R5.SV32_LEVELS - 1; i >= 0; i--) { // 2. + final int vpnShift = R5.PAGE_ADDRESS_SHIFT + R5.SV32_XPN_SIZE * i; + final int vpn = (virtualAddress >>> vpnShift) & R5.SV32_XPN_MASK; + pteAddress += vpn << R5.SV32_PTE_SIZE_LOG2; // equivalent to vpn * PTE size + int pte = physicalMemory.load32(pteAddress); // 3. + + if ((pte & R5.PTE_V_MASK) == 0 || ((pte & R5.PTE_R_MASK) == 0 && (pte & R5.PTE_W_MASK) != 0)) { // 4. + throw getPageFaultException(accessType, virtualAddress); + } + + int xwr = pte & (R5.PTE_X_MASK | R5.PTE_W_MASK | R5.PTE_R_MASK); + if (xwr == 0) { // 5. + final int ppn = pte >>> R5.PTE_DATA_BITS; + pteAddress = ppn << R5.PAGE_ADDRESS_SHIFT; + continue; + } + + // 6. Leaf node, do access permission checks. + + // Check reserved/invalid configurations. + if ((xwr & R5.PTE_R_MASK) == 0 && (xwr & R5.PTE_W_MASK) != 0) { + throw getPageFaultException(accessType, virtualAddress); + } + + // Check privilege. Can only be in S or U mode here, M was handled above. V2p61. + final int userModeFlag = pte & R5.PTE_U_MASK; + if (privilege == R5.PRIVILEGE_S) { + if (userModeFlag != 0 && + (accessType == AccessType.FETCH || (mstatus & R5.STATUS_SUM_MASK) == 0)) + throw getPageFaultException(accessType, virtualAddress); + } else if (userModeFlag == 0) { + throw getPageFaultException(accessType, virtualAddress); + } + + // MXR allows read on execute-only pages. + if ((mstatus & R5.STATUS_MXR_MASK) != 0) { + xwr |= R5.PTE_R_MASK; + } + + // Check access flags. + if ((xwr & accessType.mask) == 0) { + throw getPageFaultException(accessType, virtualAddress); + } + + // 8. Update accessed and dirty flags. + if ((pte & R5.PTE_A_MASK) == 0 || + (accessType == AccessType.WRITE && (pte & R5.PTE_D_MASK) == 0)) { + pte |= R5.PTE_A_MASK; + if (accessType == AccessType.WRITE) { + pte |= R5.PTE_D_MASK; + } + + physicalMemory.store32(pteAddress, pte); + } + + // 9. physical address = pte.ppn[LEVELS-1:i], va.vpn[i-1:0], va.pgoff + final int vpnAndPageOffsetMask = (1 << vpnShift) - 1; + final int ppn = (pte >>> R5.PTE_DATA_BITS) << R5.PAGE_ADDRESS_SHIFT; + return (ppn & ~vpnAndPageOffsetMask) | (virtualAddress & vpnAndPageOffsetMask); + } + + throw getPageFaultException(accessType, virtualAddress); + } + + private static MemoryAccessException getPageFaultException(final AccessType accessType, final int address) { + switch (accessType) { + case READ: + return new LoadPageFaultException(address); + case WRITE: + return new StorePageFaultException(address); + case FETCH: + return new FetchPageFaultException(address); + default: + throw new AssertionError(); + } + } + + /** + * Extract a field encoded in an integer value and shifts it to the desired output position. + * The length is determined by the destination low and high bit indices. + * + * @param value the value that contains the field. + * @param srcBitFrom the lowest bit of the field in the source value. + * @param srcBitUntil the highest bit of the field in the source value. + * @param destBit the lowest bit of the field in the destination (returned) value. + * @return the extracted field at the bit location specified in destBitFrom and destBitUntil. + */ + private static int getField(final int value, final int srcBitFrom, final int srcBitUntil, final int destBit) { + // For bit-shifts Java always only uses the lowest five bits for the right-hand operand, + // so we can't be clever and shift by a negative amount; need to branch here. + // NB: This method is optimized for bytecode size to make sure it gets inlined. + return (destBit >= srcBitFrom + ? value << (destBit - srcBitFrom) + : value >>> (srcBitFrom - destBit)) + & ((1 << (srcBitUntil - srcBitFrom + 1)) - 1) << destBit; + } + + private static int extendSign(final int value, final int width) { + return (value << (32 - width)) >> (32 - width); + } + + private enum AccessType { + READ(R5.PTE_R_MASK), + WRITE(R5.PTE_W_MASK), + FETCH(R5.PTE_X_MASK), + ; + + public final int mask; + + AccessType(final int mask) { + this.mask = mask; + } + } + + private static final class TLBEntry { + public int virtualAddress = -1; + public int toLocal; + public PhysicalMemory memory; + } + + public R5CPUStateSnapshot getState() { + final R5CPUStateSnapshot state = new R5CPUStateSnapshot(); + + state.pc = pc; + System.arraycopy(x, 0, state.x, 0, 32); + +// System.arraycopy(f, 0, state.f, 0, 32); +// state.fflags = fflags; +// state.frm = frm; + + state.reservation_set = reservation_set; + + state.mcycle = mcycle; + + state.mstatus = mstatus; + state.mtvec = mtvec; + state.medeleg = medeleg; + state.mideleg = mideleg; + state.mip = mip; + state.mie = mie; + state.mcounteren = mcounteren; + state.mscratch = mscratch; + state.mepc = mepc; + state.mcause = mcause; + state.mtval = mtval; + state.fs = fs; + + state.stvec = stvec; + state.scounteren = scounteren; + state.sscratch = sscratch; + state.sepc = sepc; + state.scause = scause; + state.stval = stval; + state.satp = satp; + + state.priv = priv; + state.waitingForInterrupt = waitingForInterrupt; + + return state; + } + + public void setState(final R5CPUStateSnapshot state) { + pc = state.pc; + System.arraycopy(state.x, 0, x, 0, 32); + +// System.arraycopy(state.f, 0, f, 0, 32); +// fflags = state.fflags; +// frm = state.frm; + + reservation_set = state.reservation_set; + + mcycle = state.mcycle; + + mstatus = state.mstatus; + mtvec = state.mtvec; + medeleg = state.medeleg; + mideleg = state.mideleg; + mip = state.mip; + mie = state.mie; + mcounteren = state.mcounteren; + mscratch = state.mscratch; + mepc = state.mepc; + mcause = state.mcause; + mtval = state.mtval; + fs = state.fs; + + stvec = state.stvec; + scounteren = state.scounteren; + sscratch = state.sscratch; + sepc = state.sepc; + scause = state.scause; + stval = state.stval; + satp = state.satp; + + priv = state.priv; + waitingForInterrupt = state.waitingForInterrupt; + } +} diff --git a/src/main/java/li/cil/circuity/vm/riscv/R5CPUStateSnapshot.java b/src/main/java/li/cil/circuity/vm/riscv/R5CPUStateSnapshot.java new file mode 100644 index 00000000..9eccc614 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/riscv/R5CPUStateSnapshot.java @@ -0,0 +1,37 @@ +package li.cil.circuity.vm.riscv; + +@SuppressWarnings("SpellCheckingInspection") +public final class R5CPUStateSnapshot { + public int pc; + public final int[] x = new int[32]; + +// public final float[] f = new float[32]; +// public byte fflags; +// public byte frm; + + public int reservation_set = -1; + + public long mcycle; + + public int mstatus; + public int mtvec; + public int medeleg, mideleg; + public int mip, mie; + public int mcounteren; + public int mscratch; + public int mepc; + public int mcause; + public int mtval; + public byte fs; + + public int stvec; + public int scounteren; + public int sscratch; + public int sepc; + public int scause; + public int stval; + public int satp; + + public int priv; + public boolean waitingForInterrupt; +} diff --git a/src/main/java/li/cil/circuity/vm/riscv/R5Disassembler.java b/src/main/java/li/cil/circuity/vm/riscv/R5Disassembler.java new file mode 100644 index 00000000..c8dd8e29 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/riscv/R5Disassembler.java @@ -0,0 +1,1092 @@ +package li.cil.circuity.vm.riscv; + +public final class R5Disassembler { + private static final String ILLEGAL_INSTRUCTION = ""; + private static final String HINT = ""; + private static final String OP_SEP = "\t\t; "; + + public static String disassemble(final int instruction) { + if ((instruction & 0b11) == 0b11) { + return disassembleUncompressed(instruction); + } else { + return disassembleCompressed(instruction); + } + } + + private static String disassembleUncompressed(final int inst) { + final int opcode = getField(inst, 0, 6); + final int rd = getField(inst, 7, 11); + final int rs1 = getField(inst, 15, 19); + final int rs2 = getField(inst, 20, 24); + + switch (opcode) { + case 0b0010011: { + final int funct3 = getField(inst, 12, 14); + final int imm = inst >> 20; // inst[31:20], sign extended + switch (funct3) { + case 0b000: { + return op("addi", reg(rd), reg(rs1), imm) + OP_SEP + + String.format("%s = %s %s %d", reg(rd), reg(rs1), "+", imm); + } + case 0b010: { + return op("slti", reg(rd), reg(rs1), imm) + OP_SEP + + String.format("%s = %s %s %d ? 1 : 0", reg(rd), reg(rs1), "<", imm); + } + case 0b011: { + return op("sltiu", reg(rd), reg(rs1), imm) + OP_SEP + + String.format("%s = (uint)%s %s %d ? 1 : 0", reg(rd), reg(rs1), "<", imm); + } + case 0b100: { + return op("xori", reg(rd), reg(rs1), imm) + OP_SEP + + String.format("%s = %s %s %d", reg(rd), reg(rs1), "^", imm); + } + case 0b110: { + return op("ori", reg(rd), reg(rs1), imm) + OP_SEP + + String.format("%s = %s %s %d", reg(rd), reg(rs1), "|", imm); + } + case 0b111: { + return op("andi", reg(rd), reg(rs1), imm) + OP_SEP + + String.format("%s = %s %s %d", reg(rd), reg(rs1), "&", imm); + } + case 0b001: { + if ((inst & 0b1111111_00000_00000_000_00000_0000000) != 0) + return ILLEGAL_INSTRUCTION; + + return op("slli", reg(rd), reg(rs1), imm) + OP_SEP + + String.format("%s = %s %s %d", reg(rd), reg(rs1), "<<", imm); + } + case 0b101: { + final int funct7 = getField(imm, 5, 11); // imm[11:5] + switch (funct7) { + case 0b0000000: { + return op("srli", reg(rd), reg(rs1), imm) + OP_SEP + + String.format("%s = %s %s %d", reg(rd), reg(rs1), ">>>", imm); + } + case 0b0100000: { + return op("srai", reg(rd), reg(rs1), imm) + OP_SEP + + String.format("%s = %s %s %d", reg(rd), reg(rs1), ">>", imm); + } + } + } + } + + break; + } + + case 0b0110111: { + final int imm = inst & 0b11111111111111111111_00000_0000000; // inst[31:12] + return op("lui", reg(rd), imm) + OP_SEP + + String.format("%s = %d", reg(rd), imm); + } + + case 0b0010111: { + final int imm = inst & 0b11111111111111111111_00000_0000000; // inst[31:12] + return op("auipc", reg(rd), imm) + OP_SEP + + String.format("%s = pc + %d", reg(rd), imm); + } + + case 0b0110011: { + final int funct7 = getField(inst, 25, 31); + switch (funct7) { + case 0b000001: { + final int funct3 = getField(inst, 12, 14); + switch (funct3) { + case 0b000: { + return op("mul", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = %s %s %s", reg(rd), reg(rs1), "*", reg(rs2)); + } + case 0b001: { + return op("mulh", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = %s %s %s", reg(rd), reg(rs1), "*", reg(rs2)); + } + case 0b010: { + return op("mulhsu", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = %s %s %s", reg(rd), reg(rs1), "*", reg(rs2)); + } + case 0b011: { + return op("mulhu", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = %s %s %s", reg(rd), reg(rs1), "*", reg(rs2)); + } + + case 0b100: { + return op("div", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = %s %s %s", reg(rd), reg(rs1), "/", reg(rs2)); + } + case 0b101: { + return op("divu", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = %s %s %s", reg(rd), reg(rs1), "/", reg(rs2)); + } + case 0b110: { // REM + return op("rem", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = %s %s %s", reg(rd), reg(rs1), "%", reg(rs2)); + } + case 0b111: { // REMU + return op("remu", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = %s %s %s", reg(rd), reg(rs1), "%", reg(rs2)); + } + } + + break; + } + case 0b0000000: + case 0b0100000: { + final int funct3 = getField(inst, 12, 14); + switch (funct3 | funct7) { + case 0b000: { + return op("add", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = %s %s %s", reg(rd), reg(rs1), "+", reg(rs2)); + } + case 0b000 | 0b0100000: { + return op("sub", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = %s %s %s", reg(rd), reg(rs1), "-", reg(rs2)); + } + case 0b001: { + return op("sll", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = %s %s %s", reg(rd), reg(rs1), "<<", reg(rs2)); + } + case 0b010: { + return op("slt", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = %s %s %s ? 1 : 0", reg(rd), reg(rs1), "<", reg(rs2)); + } + case 0b011: { + return op("sltu", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = (uint)%s %s (uint)%s ? 1 : 0", reg(rd), reg(rs1), "<", reg(rs2)); + } + case 0b100: { + return op("xor", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = %s %s %s", reg(rd), reg(rs1), "^", reg(rs2)); + } + case 0b101: { + return op("srl", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = %s %s %s", reg(rd), reg(rs1), ">>>", reg(rs2)); + } + case 0b101 | 0b0100000: { + return op("sra", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = %s %s %s", reg(rd), reg(rs1), ">>", reg(rs2)); + } + case 0b110: { + return op("or", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = %s %s %s", reg(rd), reg(rs1), "|", reg(rs2)); + } + case 0b111: { + return op("and", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = %s %s %s", reg(rd), reg(rs1), "&", reg(rs2)); + } + } + + break; + } + } + + break; + } + + case 0b1101111: { + final int imm = extendSign(getField(inst, 31, 31, 20) | + getField(inst, 21, 30, 1) | + getField(inst, 20, 20, 11) | + getField(inst, 12, 19, 12), 20); + return op("jal", reg(rd), imm) + OP_SEP + + String.format("pc += %d", imm); + } + + case 0b110_0111: { + final int imm = inst >> 20; // inst[31:20], sign extended + return op("jalr", reg(rd), reg(rs1), imm) + OP_SEP + + ((rd != 0) + ? String.format("%s = pc + 4, ", reg(rd)) + : "") + + String.format("pc = %s + %d", reg(rs1), imm); + } + + case 0b1100011: { + final int imm = extendSign(getField(inst, 31, 31, 12) | + getField(inst, 25, 30, 5) | + getField(inst, 8, 11, 1) | + getField(inst, 7, 7, 11), 13); + final int funct3 = getField(inst, 12, 14); + switch (funct3) { + case 0b000: { + return op("beq", reg(rs1), reg(rs2), imm) + OP_SEP + + String.format("if (%s == %s) pc += %d", reg(rs1), reg(rs2), imm); + } + case 0b001: { + return op("bne", reg(rs1), reg(rs2), imm) + OP_SEP + + String.format("if (%s != %s) pc += %d", reg(rs1), reg(rs2), imm); + } + case 0b100: { + return op("blt", reg(rs1), reg(rs2), imm) + OP_SEP + + String.format("if (%s < %s) pc += %d", reg(rs1), reg(rs2), imm); + } + case 0b101: { + return op("bge", reg(rs1), reg(rs2), imm) + OP_SEP + + String.format("if (%s >= %s) pc += %d", reg(rs1), reg(rs2), imm); + } + case 0b110: { + return op("bltu", reg(rs1), reg(rs2), imm) + OP_SEP + + String.format("if ((uint)%s < (uint)%s) pc += %d", reg(rs1), reg(rs2), imm); + } + case 0b111: { + return op("bgeu", reg(rs1), reg(rs2), imm) + OP_SEP + + String.format("if ((uint)%s >= (uint)%s) pc += %d", reg(rs1), reg(rs2), imm); + } + } + + break; + } + + case 0b0000011: { + final int funct3 = getField(inst, 12, 14); + final int imm = inst >> 20; // inst[31:20], sign extended + switch (funct3) { + case 0b000: { + return op("lb", reg(rd), addr(rs1, imm)) + OP_SEP + + String.format("%s = *(int8*)(%s + %d)", reg(rd), reg(rs1), imm); + } + case 0b001: { + return op("lh", reg(rd), addr(rs1, imm)) + OP_SEP + + String.format("%s = *(int16*)(%s + %d)", reg(rd), reg(rs1), imm); + } + case 0b010: { + return op("lw", reg(rd), addr(rs1, imm)) + OP_SEP + + String.format("%s = *(int32*)(%s + %d)", reg(rd), reg(rs1), imm); + } + case 0b100: { + return op("lbu", reg(rd), addr(rs1, imm)) + OP_SEP + + String.format("%s = *(uint8*)(%s + %d)", reg(rd), reg(rs1), imm); + } + case 0b101: { + return op("lhu", reg(rd), addr(rs1, imm)) + OP_SEP + + String.format("%s = *(uint16*)(%s + %d)", reg(rd), reg(rs1), imm); + } + } + + break; + } + + case 0b0100011: { + final int funct3 = getField(inst, 12, 14); + final int imm = getField(inst, 25, 31, 5) | + getField(inst, 7, 11); + switch (funct3) { + case 0b000: { + return op("sb", reg(rs2), addr(rs1, imm)) + OP_SEP + + String.format("*(int8*)(%s + %d) = %s", reg(rs1), imm, reg(rs2)); + } + case 0b001: { + return op("sh", reg(rs2), addr(rs1, imm)) + OP_SEP + + String.format("*(int16*)(%s + %d) = %s", reg(rs1), imm, reg(rs2)); + } + case 0b010: { + return op("sw", reg(rs2), addr(rs1, imm)) + OP_SEP + + String.format("*(int32*)(%s + %d) = %s", reg(rs1), imm, reg(rs2)); + } + } + + break; + } + + case 0b0001111: { + final int funct3 = getField(inst, 12, 14); + switch (funct3) { + case 0b000: { + return op("fence"); + } + + case 0b001: { + if (inst != 0b000000000000_00000_001_00000_0001111) + return ILLEGAL_INSTRUCTION; + return op("fence.i"); + } + } + + break; + } + + case 0b1110011: { + final int funct3 = getField(inst, 12, 14); + if (funct3 == 0b100) { + return ILLEGAL_INSTRUCTION; + } + + switch (funct3 & 0b11) { + case 0b00: { + final int funct12 = inst >>> 20; // inst[31:20], not sign-extended + switch (funct12) { + case 0b0000000_00000: { + if ((inst & 0b000000000000_11111_111_11111_0000000) != 0) { + return ILLEGAL_INSTRUCTION; + } + + return op("ecall"); + } + case 0b0000000_00001: { + if ((inst & 0b000000000000_11111_111_11111_0000000) != 0) { + return ILLEGAL_INSTRUCTION; + } + + return op("ebreak"); + } + // 0b0000000_00010: URET + case 0b0001000_00010: { + if ((inst & 0b000000000000_11111_111_11111_0000000) != 0) { + return ILLEGAL_INSTRUCTION; + } + + return op("sret"); + } + case 0b0011000_00010: { + if ((inst & 0b000000000000_11111_111_11111_0000000) != 0) { + return ILLEGAL_INSTRUCTION; + } + + return op("mret"); + } + + case 0b0001000_00101: { // WFI + if ((inst & 0b000000000000_11111_111_11111_0000000) != 0) { + return ILLEGAL_INSTRUCTION; + } + + return op("wfi"); + } + + default: { + final int funct7 = funct12 >>> 5; + if (funct7 == 0b0001001) { + if ((inst & 0b0000000_00000_00000_111_11111_0000000) != 0) { + return ILLEGAL_INSTRUCTION; + } + + return op("sfence.vma"); + } + + break; + } + } + } + + case 0b01: + case 0b10: + case 0b11: { + final int csr = inst >>> 20; // inst[31:20], not sign-extended + switch (funct3) { + case 0b001: { + return op("csrrw", reg(rd), csr2n(csr), reg(rs1)) + OP_SEP + + ((rd != 0) + ? String.format("%s = %s, %s = %s", reg(rd), csr2n(csr), csr2n(csr), reg(rs1)) + : String.format("%s = %s", csr2n(csr), reg(rs1))); + } + case 0b010: { + return op("csrrs", reg(rd), csr2n(csr), reg(rs1)) + OP_SEP + + ((rd != 0) + ? String.format("%s = %s", reg(rd), csr2n(csr)) + : String.format("*%s", csr2n(csr))) + + ((rs1 != 0) + ? String.format(", %s |= %s", csr2n(csr), reg(rs1)) + : ""); + } + case 0b011: { + return op("csrrc", reg(rd), csr2n(csr), reg(rs1)) + OP_SEP + + ((rd != 0) + ? String.format("%s = %s", reg(rd), csr2n(csr)) + : String.format("*%s", csr2n(csr))) + + ((rs1 != 0) + ? String.format(", %s &= ~%s", csr2n(csr), reg(rs1)) + : ""); + } + case 0b101: { + return op("csrrwi", reg(rd), csr2n(csr), rs1) + OP_SEP + + ((rd != 0) + ? String.format("%s = %s, %s = %d", reg(rd), csr2n(csr), csr2n(csr), rs1) + : String.format("%s = %d", csr2n(csr), rs1)); + } + case 0b110: { + return op("csrrsi", rd, csr2n(csr), rs1) + OP_SEP + + ((rd != 0) + ? String.format("%s = %s", reg(rd), csr2n(csr)) + : String.format("*%s", csr2n(csr))) + + ((rs1 != 0) + ? String.format(", %s |= %s", csr2n(csr), rs1) + : ""); + } + case 0b111: { + return op("csrrci", rd, csr2n(csr), rs1) + OP_SEP + + ((rd != 0) + ? String.format("%s = %s", reg(rd), csr2n(csr)) + : String.format("*%s", csr2n(csr))) + + ((rs1 != 0) + ? String.format(", %s &= ~%s", csr2n(csr), rs1) + : ""); + } + } + break; + } + } + + break; + } + + case 0b0101111: { + final int funct3 = getField(inst, 12, 14); + switch (funct3) { // width + case 0b010: { // 32 + final int funct5 = inst >>> 27; // inst[31:27], not sign-extended + switch (funct5) { + case 0b00010: { + if (rs2 != 0) { + return ILLEGAL_INSTRUCTION; + } + + return op("lr.w", reg(rd), reg(rs1)) + OP_SEP + + String.format("%s = *%s, reserve %s", reg(rd), reg(rs1), reg(rs1)); + } + case 0b00011: { + return op("sc.w", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("if (%s reserved) *%s = %s, %s = 0 else %s = 1", reg(rs1), reg(rs1), reg(rs2), reg(rd), reg(rd)); + } + + case 0b00001: { + return op("amoswap.w", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = *%s, *%s = *%s %s", reg(rd), reg(rs1), reg(rs1), reg(rs1), reg(rs2)); + } + case 0b00000: { + return op("amoadd.w", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = *%s, *%s = *%s %s %s", reg(rd), reg(rs1), reg(rs1), reg(rs1), "+", reg(rs2)); + } + case 0b00100: { + return op("amoxor.w", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = *%s, *%s = *%s %s %s", reg(rd), reg(rs1), reg(rs1), reg(rs1), "^", reg(rs2)); + } + case 0b01100: { + return op("amoand.w", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = *%s, *%s = *%s %s %s", reg(rd), reg(rs1), reg(rs1), reg(rs1), "&", reg(rs2)); + } + case 0b01000: { + return op("amoor.w", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = *%s, *%s = *%s %s %s", reg(rd), reg(rs1), reg(rs1), reg(rs1), "|", reg(rs2)); + } + case 0b10000: { + return op("amomin.w", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = *%s, *%s = min(*%s, %s)", reg(rd), reg(rs1), reg(rs1), reg(rs1), reg(rs2)); + } + case 0b10100: { + return op("amomax.w", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = *%s, *%s = max(*%s, %s)", reg(rd), reg(rs1), reg(rs1), reg(rs1), reg(rs2)); + } + case 0b11000: { + return op("amominu.w", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = *%s, *%s = min(*(uint32*)%s, (uint32)%s)", reg(rd), reg(rs1), reg(rs1), reg(rs1), reg(rs2)); + } + case 0b11100: { + return op("amomaxu.w", reg(rd), reg(rs1), reg(rs2)) + OP_SEP + + String.format("%s = *%s, *%s = max(*(uint32*)%s, (uint32)%s)", reg(rd), reg(rs1), reg(rs1), reg(rs1), reg(rs2)); + } + } + + break; + } + case 0b011: { // 64 + return ILLEGAL_INSTRUCTION; + } + } + + break; + } + } + + return ILLEGAL_INSTRUCTION; + } + + private static String disassembleCompressed(final int inst) { + if (inst == 0) { + return ILLEGAL_INSTRUCTION; + } + + final int op = inst & 0b11; + switch (op) { + case 0b00: { // Quadrant 0 + final int funct3 = getField(inst, 13, 15); + final int rd = getField(inst, 2, 4) + 8; + switch (funct3) { + case 0b000: { + final int imm = getField(inst, 11, 12, 4) | + getField(inst, 7, 10, 6) | + getField(inst, 6, 6, 2) | + getField(inst, 5, 5, 3); + if (imm == 0) { + return ILLEGAL_INSTRUCTION; + } + return op("c.addi4spn", reg(rd), imm) + OP_SEP + + String.format("%s = %s + %d", reg(rd), reg(2), imm); + } + // 0b001: C.FLD + case 0b010: { + final int offset = getField(inst, 10, 12, 3) | + getField(inst, 6, 6, 2) | + getField(inst, 5, 5, 6); + final int rs1 = getField(inst, 7, 9) + 8; // V1p100 + return op("c.lw", reg(rd), addr(rs1, offset)) + OP_SEP + + String.format("%s = *(uint32*)(%s + %d)", reg(rd), reg(rs1), offset); + } + // 0b011: C.FLW + // 0b101: C.FSD + case 0b110: { + final int offset = getField(inst, 10, 12, 3) | + getField(inst, 6, 6, 2) | + getField(inst, 5, 5, 6); + final int rs1 = getField(inst, 7, 9) + 8; // V1p100 + return op("c.sw", reg(rd), addr(rs1, offset)) + OP_SEP + + String.format("*(uint32*)(%s + %d) = %s", reg(rs1), offset, reg(rd)); + } + // 0b111: C.FSW + } + + break; + } + + case 0b01: { // Quadrant 1 + final int funct3 = getField(inst, 13, 15); + switch (funct3) { + case 0b000: { + final int imm = extendSign(getField(inst, 12, 12, 5) | + getField(inst, 2, 6), 6); + final int rd = getField(inst, 7, 11); + if (rd != 0) { + return op("c.addi", reg(rd), imm) + OP_SEP + + String.format("%s += %d", reg(rd), imm); + } else if (imm != 0) { + return op("nop"); + } else { + return HINT; + } + } + case 0b001: { + final int offset = extendSign(getField(inst, 12, 12, 11) | + getField(inst, 11, 11, 4) | + getField(inst, 9, 10, 8) | + getField(inst, 8, 8, 10) | + getField(inst, 7, 7, 6) | + getField(inst, 6, 6, 7) | + getField(inst, 3, 5, 1) | + getField(inst, 2, 2, 5), 12); + return op("c.jal", offset) + OP_SEP + + String.format("%s = pc + 2, pc += %d", reg(1), offset); + } + case 0b010: { + final int rd = getField(inst, 7, 11); + if (rd != 0) { + final int imm = extendSign(getField(inst, 12, 12, 5) | + getField(inst, 2, 6), 6); + return op("c.li", reg(rd), imm) + OP_SEP + + String.format("%s = %d", reg(rd), imm); + } else { + return HINT; + } + } + case 0b011: { + final int rd = getField(inst, 7, 11); + if (rd == 2) { + final int imm = extendSign(getField(inst, 12, 12, 9) | + getField(inst, 6, 6, 4) | + getField(inst, 5, 5, 6) | + getField(inst, 3, 4, 7) | + getField(inst, 2, 2, 5), 10); + if (imm == 0) { + return ILLEGAL_INSTRUCTION; + } + return op("c.addi16sp", reg(rd), imm) + OP_SEP + + String.format("%s += %d", reg(rd), imm); + } else if (rd != 0) { + final int imm = extendSign(getField(inst, 12, 12, 17) | + getField(inst, 2, 6, 12), 18); + if (imm == 0) { + return ILLEGAL_INSTRUCTION; + } + return op("c.lui", reg(rd), imm) + OP_SEP + + String.format("%s = %x", reg(rd), imm); + } else { + return HINT; + } + } + case 0b100: { + final int funct2 = getField(inst, 10, 11); + final int rd = getField(inst, 7, 9) + 8; + switch (funct2) { + case 0b00: + case 0b01: { + final int imm = getField(inst, 12, 12, 5) | + getField(inst, 2, 6, 0); + if ((funct2 & 0b1) == 0) { + return op("c.srli", reg(rd), imm) + OP_SEP + + String.format("%s = %s >>> %d", reg(rd), reg(rd), imm); + } else { + return op("c.srai", reg(rd), imm) + OP_SEP + + String.format("%s = %s >> %d", reg(rd), reg(rd), imm); + } + } + case 0b10: { // C.ANDI + final int imm = extendSign(getField(inst, 12, 12, 5) | + getField(inst, 2, 6, 0), 6); + return op("c.andi", reg(rd), imm) + OP_SEP + + String.format("%s &= %d", reg(rd), imm); + } + case 0b11: { + final int funct3b = getField(inst, 5, 6) | + getField(inst, 12, 12, 2); + final int rs2 = getField(inst, 2, 4) + 8; + switch (funct3b) { + case 0b000: { + return op("c.sub", reg(rd), reg(rs2)) + OP_SEP + + String.format("%s -= %s", reg(rd), reg(rs2)); + } + case 0b001: { + return op("c.xor", reg(rd), reg(rs2)) + OP_SEP + + String.format("%s ^= %s", reg(rd), reg(rs2)); + } + case 0b010: { + return op("c.or", reg(rd), reg(rs2)) + OP_SEP + + String.format("%s |= %s", reg(rd), reg(rs2)); + } + case 0b011: { + return op("c.and", reg(rd), reg(rs2)) + OP_SEP + + String.format("%s &= %s", reg(rd), reg(rs2)); + } + // 0b100: C.SUBW + // 0b101: C.ADDW + } + break; + } + } + + break; + } + case 0b101: { + final int offset = extendSign(getField(inst, 12, 12, 11) | + getField(inst, 11, 11, 4) | + getField(inst, 9, 10, 8) | + getField(inst, 8, 8, 10) | + getField(inst, 7, 7, 6) | + getField(inst, 6, 6, 7) | + getField(inst, 3, 5, 1) | + getField(inst, 2, 2, 5), 12); + return op("c.j", offset) + OP_SEP + + String.format("pc += %d", offset); + } + case 0b110: + case 0b111: { + final int offset = extendSign(getField(inst, 12, 12, 8) | + getField(inst, 10, 11, 3) | + getField(inst, 5, 6, 6) | + getField(inst, 3, 4, 1) | + getField(inst, 2, 2, 5), 9); + final int rs1 = getField(inst, 7, 9) + 8; + if ((funct3 & 0b1) == 0) { + return op("c.beqz", reg(rs1), offset) + OP_SEP + + String.format("if (%s == 0) pc += %d", reg(rs1), offset); + } else { + return op("c.bnez", reg(rs1), offset) + OP_SEP + + String.format("if (%s != 0) pc += %d", reg(rs1), offset); + } + } + } + + break; + } + + case 0b10: { // Quadrant 2 + final int funct3 = getField(inst, 13, 15); + final int rd = getField(inst, 7, 11); + switch (funct3) { + case 0b000: { + final int imm = getField(inst, 12, 12, 5) | + getField(inst, 2, 6, 0); + if (rd != 0) { + return op("c.slli", reg(rd), imm) + OP_SEP + + String.format("%s = %s << %d", reg(rd), reg(rd), imm); + } else { + return HINT; + } + } + // 0b001: C.FLDSP + case 0b010: { + final int offset = getField(inst, 12, 12, 5) | + getField(inst, 4, 6, 2) | + getField(inst, 2, 3, 6); + if (rd == 0) { + return ILLEGAL_INSTRUCTION; + } + + return op("c.lwsp", reg(rd), offset) + OP_SEP + + String.format("%s = *(uint32*)(%s + %d)", reg(rd), reg(2), offset); + } + // 0b011: C.FLWSP + case 0b100: { + final int rs2 = getField(inst, 2, 6); + if ((inst & (1 << 12)) == 0) { + if (rs2 == 0) { + if (rd == 0) { + return ILLEGAL_INSTRUCTION; + } + + return op("c.jr", reg(rd)) + OP_SEP + + String.format("pc = %s", reg(rd)); + } else { + if (rd != 0) { + return op("c.mv", reg(rd), reg(rs2)) + OP_SEP + + String.format("%s = %s", reg(rd), reg(rs2)); + } else { + return HINT; + } + } + } else { + if (rs2 == 0) { + if (rd == 0) { + return op("c.ebreak"); + } else { + return op("c.jalr", reg(rd)) + OP_SEP + + String.format("%s = pc + 2, pc + %s", reg(1), reg(rd)); + } + } else { + if (rd != 0) { + return op("c.add", reg(rd), reg(rs2)) + OP_SEP + + String.format("%s += %s", reg(rd), reg(rs2)); + } else { + return HINT; + } + } + } + } + // 0b101: C.FSDSP + case 0b110: { + final int offset = getField(inst, 9, 12, 2) | + getField(inst, 7, 8, 6); + final int rs2 = getField(inst, 2, 6); + return op("c.swsp", reg(rs2), offset) + OP_SEP + + String.format("*(uint32*)(%s + %d) = %s", reg(2), offset, reg(rs2)); + } + // 0b111: C.FSWSP + } + + break; + } + } + + return ILLEGAL_INSTRUCTION; + } + + private static String op(final Object... args) { + final StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < args.length; i++) { + final Object arg = args[i]; + if (i > 1) sb.append(", "); + else if (i == 1) sb.append("\t"); + if (arg instanceof Integer) { + sb.append(String.format("0x%x", arg)); + } else { + sb.append(arg); + } + } + + return sb.toString(); + } + + private static String addr(final int base, final int offset) { + return String.format("%d(%s)", offset, reg(base)); + } + + private static String reg(final Object index) { + if (index instanceof Integer) { + switch ((int) index) { + case 0: + return "zero"; + case 1: + return "ra"; + case 2: + return "sp"; + case 3: + return "gp"; + case 4: + return "tp"; + case 5: + return "t0"; + case 6: + return "t1"; + case 7: + return "t2"; + case 8: + return "s0"; + case 9: + return "s1"; + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + return "a" + ((int) index - 10); + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + case 26: + case 27: + return "s" + ((int) index - 16); + case 28: + case 29: + case 30: + case 31: + return "t" + ((int) index - 25); + default: + return "?"; + } + } + + return index.toString(); + } + + private static String csr2n(final int csr) { + switch (csr) { + case 0x000: + return "ustatus"; + case 0x004: + return "uie"; + case 0x005: + return "utvec"; + + case 0x040: + return "uscratch"; + case 0x041: + return "uepc"; + case 0x042: + return "ucause"; + case 0x043: + return "utval"; + case 0x044: + return "uip"; + + case 0x001: + return "fflags"; + case 0x002: + return "frm"; + case 0x003: + return "fcsr"; + + case 0xC00: + return "cycle"; + case 0xC01: + return "time"; + case 0xC02: + return "instret"; + + case 0xC80: + return "cycleh"; + case 0xC81: + return "timeh"; + case 0xC82: + return "instreth"; + + case 0x100: + return "sstatus"; + case 0x102: + return "sedeleg"; + case 0x103: + return "sideleg"; + case 0x104: + return "sie"; + case 0x105: + return "stvec"; + case 0x106: + return "scountern"; + + case 0x140: + return "sscratch"; + case 0x141: + return "sepc"; + case 0x142: + return "scause"; + case 0x143: + return "stval"; + case 0x144: + return "sip"; + + case 0x180: + return "satp"; + + case 0x600: + return "hstatus"; + case 0x602: + return "hedeleg"; + case 0x603: + return "hideleg"; + case 0x604: + return "hie"; + case 0x606: + return "hcounteren"; + case 0x607: + return "hgeie"; + + case 0x643: + return "htval"; + case 0x644: + return "hip"; + case 0x645: + return "hvip"; + case 0x64A: + return "htinst"; + case 0xE12: + return "hgeip"; + + case 0x680: + return "hgatp"; + + case 0x605: + return "htimedelta"; + case 0x615: + return "htimedeltah"; + + case 0x200: + return "vsstatus"; + case 0x204: + return "vsie"; + case 0x205: + return "vstvec"; + case 0x240: + return "vsscratch"; + case 0x241: + return "vsepc"; + case 0x242: + return "vscause"; + case 0x243: + return "vstval"; + case 0x244: + return "vsip"; + case 0x280: + return "vsatp"; + + case 0xF11: + return "mvendorid"; + case 0xF12: + return "marchid"; + case 0xF13: + return "mimpid"; + case 0xF14: + return "mhartid"; + + case 0x300: + return "mstatus"; + case 0x301: + return "misa"; + case 0x302: + return "medeleg"; + case 0x303: + return "mideleg"; + case 0x304: + return "mie"; + case 0x305: + return "mtvec"; + case 0x306: + return "mcounteren"; + case 0x310: + return "mstatush"; + + case 0x340: + return "mscratch"; + case 0x341: + return "mepc"; + case 0x342: + return "mcause"; + case 0x343: + return "mtval"; + case 0x344: + return "mip"; + case 0x34A: + return "mtinst"; + case 0x34B: + return "mtval2"; + + case 0xB00: + return "mcycle"; + case 0xB02: + return "minstret"; + case 0xB80: + return "mcycleh"; + case 0xB82: + return "minstreth"; + + case 0x320: + return "mcounterhibit"; + + case 0x7A0: + return "tselect"; + case 0x7A1: + return "tdata1"; + case 0x7A2: + return "tdata2"; + case 0x7A3: + return "tdata3"; + + case 0x7B0: + return "dcsr"; + case 0x7B1: + return "dpc"; + case 0x7B2: + return "dscratch0"; + case 0x7B3: + return "dscratch1"; + } + + if (csr >= 0xC03 && csr <= 0xC1F) { + return "hpmcounter" + (3 + (csr - 0xC03)); + } + + if (csr >= 0xC83 && csr <= 0xC9F) { + return "hpmcounter" + (3 + (csr - 0xC03)) + "h"; + } + + if (csr >= 0x3A0 && csr <= 0x3AF) { + return "pmpcfg" + (csr - 0x3A0); + } + if (csr >= 0x3B0 && csr <= 0x3EF) { + return "pmpaddr" + (csr - 0x3B0); + } + + if (csr >= 0xB03 && csr <= 0xB1F) { + return "mhpmcounter" + (3 + (csr - 0xB03)); + } + if (csr >= 0xB83 && csr <= 0xB9F) { + return "mhpmcounter" + (3 + (csr - 0xB83)) + "h"; + } + + if (csr >= 0x323 && csr <= 0x33F) { + return "mhpmevent" + (3 + (csr - 0x323)); + } + + return String.valueOf(csr); + } + + private static int getField(final int value, final int srcBitFrom, final int srcBitUntil, final int destBit) { + // For bit-shifts Java always only uses the lowest five bits for the right-hand operand, + // so we can't be clever and shift by a negative amount; need to branch here. + // NB: This method is optimized for size to make sure it gets inlined at the cost of readability. + return (destBit >= srcBitFrom + ? value << (destBit - srcBitFrom) + : value >>> (srcBitFrom - destBit)) + & ((1 << (srcBitUntil - srcBitFrom + 1)) - 1) << destBit; + } + + private static int getField(final int value, final int srcBitFrom, final int srcBitUntil) { + return getField(value, srcBitFrom, srcBitUntil, 0); + } + + private static int extendSign(final int value, final int width) { + return (value << (32 - width)) >> (32 - width); + } +} diff --git a/src/main/java/li/cil/circuity/vm/riscv/device/R5CoreLocalInterrupter.java b/src/main/java/li/cil/circuity/vm/riscv/device/R5CoreLocalInterrupter.java new file mode 100644 index 00000000..640235f3 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/riscv/device/R5CoreLocalInterrupter.java @@ -0,0 +1,106 @@ +package li.cil.circuity.vm.riscv.device; + +import li.cil.circuity.api.vm.Interrupt; +import li.cil.circuity.api.vm.device.InterruptSource; +import li.cil.circuity.api.vm.device.Steppable; +import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice; +import li.cil.circuity.api.vm.device.rtc.RealTimeCounter; +import li.cil.circuity.vm.riscv.R5; + +import java.util.Arrays; + +/** + * See: https://github.com/riscv/riscv-isa-sim/blob/master/riscv/clint.cc + *
+ * // 0000 msip hart 0
+ * // 0004 msip hart 1
+ * 4000 mtimecmp hart 0 lo
+ * 4004 mtimecmp hart 0 hi
+ * bff8 mtime lo
+ * bffc mtime hi
+ * 
+ */ +public final class R5CoreLocalInterrupter implements Steppable, InterruptSource, MemoryMappedDevice { + private final RealTimeCounter rtc; + private long mtimecmp = -1; + + private final Interrupt msip = new Interrupt(R5.MSIP_SHIFT); + private final Interrupt mtip = new Interrupt(R5.MTIP_SHIFT); + + public R5CoreLocalInterrupter(final RealTimeCounter rtc) { + this.rtc = rtc; + } + + public Interrupt getMachineSoftwareInterrupt() { + return msip; + } + + public Interrupt getMachineTimerInterrupt() { + return mtip; + } + + @Override + public void step(final int cycles) { + checkInterrupt(); + } + + @Override + public Iterable getInterrupts() { + return Arrays.asList(msip, mtip); + } + + @Override + public int getLength() { + return 0x000C0000; + } + + @Override + public int load32(final int offset) { + switch (offset) { + case 0x4000: { + return (int) mtimecmp; + } + case 0x4004: { + return (int) (mtimecmp >>> 32); + } + case 0xBFF8: { + return (int) rtc.getTime(); + } + case 0xBFFC: { + return (int) (rtc.getTime() >> 32); + } + default: { + return 0; + } + } + } + + @Override + public void store32(final int offset, final int value) { + switch (offset) { + case 0x4000: { + mtimecmp = (mtimecmp & ~0xFFFFFFFFL) | (value & 0xFFFFFFFFL); + if (!checkInterrupt()) { + mtip.lowerInterrupt(); + } + break; + } + case 0x4004: { + mtimecmp = (mtimecmp & 0xFFFFFFFFL) | ((long) value << 32); + if (!checkInterrupt()) { + mtip.lowerInterrupt(); + } + break; + } + } + } + + private boolean checkInterrupt() { + if (Long.compareUnsigned(mtimecmp, rtc.getTime()) <= 0) { + mtip.raiseInterrupt(); + return true; + } + + return false; + } +} diff --git a/src/main/java/li/cil/circuity/vm/riscv/device/R5HostTargetInterface.java b/src/main/java/li/cil/circuity/vm/riscv/device/R5HostTargetInterface.java new file mode 100644 index 00000000..b619dfed --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/riscv/device/R5HostTargetInterface.java @@ -0,0 +1,78 @@ +package li.cil.circuity.vm.riscv.device; + +import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice; + +public final class R5HostTargetInterface implements MemoryMappedDevice { + public static final int COMMAND_POWER_OFF = 1; + + private long toHost, fromHost; + + @Override + public int getLength() { + return 16; + } + + @Override + public int load32(final int offset) { + switch (offset) { + case 0: { + return (int) toHost; + } + case 4: { + return (int) (toHost >> 32); + } + + case 8: { + return (int) fromHost; + } + case 12: { + return (int) (fromHost >> 32); + } + default: { + return 0; + } + } + } + + @Override + public void store32(final int offset, final int value) { + switch (offset) { + case 0: { + toHost = (toHost & ~0xFFFFFFFFL) | value; + break; + } + case 4: { + toHost = (toHost & 0xFFFFFFFFL) | ((long) value << 32); + handleCommand(); + break; + } + + case 8: { + fromHost = (fromHost & ~0xFFFFFFFFL) | value; + break; + } + case 12: { + fromHost = (fromHost & 0xFFFFFFFFL) | ((long) value << 32); + break; + } + } + } + + private void handleCommand() { + final int device = (int) (toHost >>> 56); + final int cmd = (int) (toHost >>> 48) & 0xff; + if (toHost == COMMAND_POWER_OFF) { // request power off + System.out.println("power off"); + // todo stop vm + } else if (device == 1 && cmd == 1) { // console output +// console.write_data(toHost & 0xff); + System.out.print((char) (toHost & 0xFF)); + toHost = 0; + fromHost = ((long) device << 56) | ((long) cmd << 48); + } else if (device == 1 && cmd == 0) { // request keyboard interrupt + toHost = 0; + } else { + System.out.printf("HTIF: unsupported tohost=0x%016x\n", toHost); + } + } +} diff --git a/src/main/java/li/cil/circuity/vm/riscv/device/R5PlatformLevelInterruptController.java b/src/main/java/li/cil/circuity/vm/riscv/device/R5PlatformLevelInterruptController.java new file mode 100644 index 00000000..c5f0700a --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/riscv/device/R5PlatformLevelInterruptController.java @@ -0,0 +1,188 @@ +package li.cil.circuity.vm.riscv.device; + +import li.cil.circuity.api.vm.Interrupt; +import li.cil.circuity.api.vm.device.InterruptSource; +import li.cil.circuity.api.vm.device.Interrupter; +import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice; +import li.cil.circuity.vm.components.InterruptSourceRegistry; +import li.cil.circuity.vm.riscv.R5; + +import java.util.Arrays; + +/** + * Implementation of a PLIC with 32 sources. + *

+ * See: https://github.com/riscv/riscv-plic-spec/blob/master/riscv-plic.adoc + * See: https://github.com/riscv/opensbi/blob/master/lib/utils/irqchip/plic.c + *

+ * base + 0x000000: Reserved (interrupt source 0 does not exist)
+ * base + 0x000004: Interrupt source 1 priority
+ * base + 0x000008: Interrupt source 2 priority
+ * ...
+ * base + 0x000FFC: Interrupt source 1023 priority
+ * base + 0x001000: Interrupt Pending bit 0-31
+ * base + 0x00107C: Interrupt Pending bit 992-1023
+ * ...
+ * base + 0x002000: Enable bits for sources 0-31 on context 0
+ * base + 0x002004: Enable bits for sources 32-63 on context 0
+ * ...
+ * base + 0x00207F: Enable bits for sources 992-1023 on context 0
+ * base + 0x002080: Enable bits for sources 0-31 on context 1
+ * base + 0x002084: Enable bits for sources 32-63 on context 1
+ * ...
+ * base + 0x0020FF: Enable bits for sources 992-1023 on context 1
+ * base + 0x002100: Enable bits for sources 0-31 on context 2
+ * base + 0x002104: Enable bits for sources 32-63 on context 2
+ * ...
+ * base + 0x00217F: Enable bits for sources 992-1023 on context 2
+ * ...
+ * base + 0x1F1F80: Enable bits for sources 0-31 on context 15871
+ * base + 0x1F1F84: Enable bits for sources 32-63 on context 15871
+ * base + 0x1F1FFF: Enable bits for sources 992-1023 on context 15871
+ * ...
+ * base + 0x1FFFFC: Reserved
+ * base + 0x200000: Priority threshold for context 0
+ * base + 0x200004: Claim/complete for context 0
+ * base + 0x200008: Reserved
+ * ...
+ * base + 0x200FFC: Reserved
+ * base + 0x201000: Priority threshold for context 1
+ * base + 0x201004: Claim/complete for context 1
+ * ...
+ * base + 0x3FFE000: Priority threshold for context 15871
+ * base + 0x3FFE004: Claim/complete for context 15871
+ * base + 0x3FFE008: Reserved
+ * ...
+ * base + 0x3FFFFFC: Reserved
+ * 
+ */ +public class R5PlatformLevelInterruptController implements MemoryMappedDevice, Interrupter, InterruptSource { + private static final int PLIC_PRIORITY_BASE = 0x0; + private static final int PLIC_PENDING_BASE = 0x1000; + private static final int PLIC_ENABLE_BASE = 0x2000; + private static final int PLIC_ENABLE_STRIDE = 0x80; + private static final int PLIC_CONTEXT_BASE = 0x200000; + private static final int PLIC_CONTEXT_STRIDE = 0x1000; + + private final InterruptSourceRegistry interrupts = new InterruptSourceRegistry(); + private int pending; + private int served; + + private final Interrupt meip = new Interrupt(R5.MEIP_SHIFT); + private final Interrupt seip = new Interrupt(R5.SEIP_SHIFT); + + public Interrupt getMachineExternalInterrupt() { + return meip; + } + + public Interrupt getSupervisorExternalInterrupt() { + return seip; + } + + @Override + public int getLength() { + return 0x04000000; + } + + @Override + public int load32(final int offset) { + switch (offset) { + // 0x0: Reserved. + // 0x1 - 0x000FFC: Priorities; hardcoded to zero. + case PLIC_PENDING_BASE: { // Pending bits 0-31. + return pending & ~served; + } + case PLIC_ENABLE_BASE: { // Enable bits 0-31 on context 0 (hart 0). + return 0xFFFFFFFF; // Hardcoded to enabled. + } + // PLIC_CONTEXT_BASE: Priority threshold on context 0; hardcoded to zero. + case PLIC_CONTEXT_BASE + 4: { // Claim/complete for context 0 + return claim(); + } + default: { + return 0; + } + } + } + + @Override + public void store32(final int offset, final int value) { + switch (offset) { + case PLIC_CONTEXT_BASE + 4: { // Claim/complete for context 0 + complete(value); + break; + } + } + } + + @Override + public void raiseInterrupts(final int mask) { + pending |= mask; + propagate(); + } + + @Override + public void lowerInterrupts(final int mask) { + pending &= ~mask; + propagate(); + } + + @Override + public Iterable getInterrupts() { + return Arrays.asList(meip, seip); + } + + @Override + public int registerInterrupt() { + return interrupts.registerInterrupt(); + } + + @Override + public boolean registerInterrupt(final int id) { + return interrupts.registerInterrupt(id); + } + + @Override + public void releaseInterrupt(final int id) { + interrupts.releaseInterrupt(id); + } + + private int claim() { + final int unserved = pending & ~served; + if (unserved == 0) { + return 0; + } + + final int index = Integer.numberOfTrailingZeros(unserved); + + served |= 1 << index; + propagate(); + + return index + 1; + } + + private void complete(final int value) { + if (value <= 0) { + return; + } + + final int index = value - 1; + if (index >= 32) { + return; + } + + served &= ~(1 << index); + propagate(); + } + + private void propagate() { + final int mask = pending & ~served; + if (mask != 0) { + meip.raiseInterrupt(); + seip.raiseInterrupt(); + } else { + meip.lowerInterrupt(); + seip.lowerInterrupt(); + } + } +} diff --git a/src/main/java/li/cil/circuity/vm/riscv/exception/R5BreakpointException.java b/src/main/java/li/cil/circuity/vm/riscv/exception/R5BreakpointException.java new file mode 100644 index 00000000..c275676a --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/riscv/exception/R5BreakpointException.java @@ -0,0 +1,9 @@ +package li.cil.circuity.vm.riscv.exception; + +import li.cil.circuity.vm.riscv.R5; + +public final class R5BreakpointException extends R5Exception { + public R5BreakpointException() { + super(R5.EXCEPTION_BREAKPOINT); + } +} diff --git a/src/main/java/li/cil/circuity/vm/riscv/exception/R5ECallException.java b/src/main/java/li/cil/circuity/vm/riscv/exception/R5ECallException.java new file mode 100644 index 00000000..4d4bacb9 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/riscv/exception/R5ECallException.java @@ -0,0 +1,31 @@ +package li.cil.circuity.vm.riscv.exception; + +import li.cil.circuity.vm.riscv.R5; + +public final class R5ECallException extends R5Exception { + public R5ECallException(final int privilege) { + super(exceptionForPrivilege(privilege)); + } + + private static int exceptionForPrivilege(final int privilege) { + /* + switch (privilege) { + case R5.PRIVILEGE_U: { + return R5.EXCEPTION_USER_ECALL; + } + case R5.PRIVILEGE_S: { + return R5.EXCEPTION_SUPERVISOR_ECALL; + } + case R5.PRIVILEGE_H: { + return R5.EXCEPTION_HYPERVISOR_ECALL; + } + case R5.PRIVILEGE_M: { + return R5.EXCEPTION_MACHINE_ECALL; + } + } + + Optimized: + */ + return R5.EXCEPTION_USER_ECALL + privilege; + } +} diff --git a/src/main/java/li/cil/circuity/vm/riscv/exception/R5Exception.java b/src/main/java/li/cil/circuity/vm/riscv/exception/R5Exception.java new file mode 100644 index 00000000..f2f6c9cc --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/riscv/exception/R5Exception.java @@ -0,0 +1,17 @@ +package li.cil.circuity.vm.riscv.exception; + +public class R5Exception extends Exception { + private final int exceptionCause; + + public R5Exception(final int exceptionCause) { + this.exceptionCause = exceptionCause; + } + + public int getExceptionCause() { + return exceptionCause; + } + + public int getExceptionValue() { + return 0; + } +} diff --git a/src/main/java/li/cil/circuity/vm/riscv/exception/R5IllegalInstructionException.java b/src/main/java/li/cil/circuity/vm/riscv/exception/R5IllegalInstructionException.java new file mode 100644 index 00000000..ef20d756 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/riscv/exception/R5IllegalInstructionException.java @@ -0,0 +1,17 @@ +package li.cil.circuity.vm.riscv.exception; + +import li.cil.circuity.vm.riscv.R5; + +public final class R5IllegalInstructionException extends R5Exception { + private final int instruction; + + public R5IllegalInstructionException(final int instruction) { + super(R5.EXCEPTION_ILLEGAL_INSTRUCTION); + this.instruction = instruction; + } + + @Override + public int getExceptionValue() { + return instruction; + } +} diff --git a/src/test/java/li/cil/circuity/vm/riscv/ByteBufferMemoryTests.java b/src/test/java/li/cil/circuity/vm/riscv/ByteBufferMemoryTests.java new file mode 100644 index 00000000..b93d1f27 --- /dev/null +++ b/src/test/java/li/cil/circuity/vm/riscv/ByteBufferMemoryTests.java @@ -0,0 +1,80 @@ +package li.cil.circuity.vm.riscv; + +import li.cil.circuity.vm.device.memory.ByteBufferMemory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class ByteBufferMemoryTests { + private ByteBufferMemory memory; + + @BeforeEach + public void initialize() throws Exception { + memory = new ByteBufferMemory(4 * 1024); + memory.store32(0x00, 0x11223344); + memory.store32(0x10, 0x55667788); + memory.store32(0x20, 0x99AABBCC); + } + + @Test + public void testLoad8() throws Exception { + Assertions.assertEquals((byte) 0x44, memory.load8(0x00 + 0)); + Assertions.assertEquals((byte) 0x33, memory.load8(0x00 + 1)); + Assertions.assertEquals((byte) 0x22, memory.load8(0x00 + 2)); + Assertions.assertEquals((byte) 0x11, memory.load8(0x00 + 3)); + + Assertions.assertEquals((byte) 0x88, memory.load8(0x10 + 0)); + Assertions.assertEquals((byte) 0x77, memory.load8(0x10 + 1)); + Assertions.assertEquals((byte) 0x66, memory.load8(0x10 + 2)); + Assertions.assertEquals((byte) 0x55, memory.load8(0x10 + 3)); + + Assertions.assertEquals((byte) 0xCC, memory.load8(0x20 + 0)); + Assertions.assertEquals((byte) 0xBB, memory.load8(0x20 + 1)); + Assertions.assertEquals((byte) 0xAA, memory.load8(0x20 + 2)); + Assertions.assertEquals((byte) 0x99, memory.load8(0x20 + 3)); + } + + @Test + public void testStore8() throws Exception { + memory.store8(0x00 + 0, (byte) 0x11); + memory.store8(0x00 + 1, (byte) 0x22); + memory.store8(0x00 + 2, (byte) 0x33); + memory.store8(0x00 + 3, (byte) 0x44); + + Assertions.assertEquals(0x44332211, memory.load32(0x00 + 0)); + } + + @Test + public void testLoad16() throws Exception { + Assertions.assertEquals((short) 0x3344, memory.load16(0x00 + 0)); + Assertions.assertEquals((short) 0x1122, memory.load16(0x00 + 2)); + + Assertions.assertEquals((short) 0x7788, memory.load16(0x10 + 0)); + Assertions.assertEquals((short) 0x5566, memory.load16(0x10 + 2)); + + Assertions.assertEquals((short) 0x99AA, memory.load16(0x20 + 2)); + Assertions.assertEquals((short) 0xBBCC, memory.load16(0x20 + 0)); + } + + @Test + public void testStore16() throws Exception { + memory.store16(0x00 + 0, (short) 0x2211); + memory.store16(0x00 + 2, (short) 0x4433); + + Assertions.assertEquals(0x44332211, memory.load32(0x00 + 0)); + } + + @Test + public void testLoad32() throws Exception { + Assertions.assertEquals(0x11223344, memory.load32(0x00)); + Assertions.assertEquals(0x55667788, memory.load32(0x10)); + Assertions.assertEquals(0x99AABBCC, memory.load32(0x20)); + } + + @Test + public void testStore32() throws Exception { + memory.store32(0, 0x44332211); + + Assertions.assertEquals(0x44332211, memory.load32(0x00)); + } +} diff --git a/src/test/java/li/cil/circuity/vm/riscv/InstructionTests.java b/src/test/java/li/cil/circuity/vm/riscv/InstructionTests.java new file mode 100644 index 00000000..83b27195 --- /dev/null +++ b/src/test/java/li/cil/circuity/vm/riscv/InstructionTests.java @@ -0,0 +1,150 @@ +package li.cil.circuity.vm.riscv; + +import li.cil.circuity.api.vm.device.memory.MemoryAccessException; +import li.cil.circuity.api.vm.device.memory.PhysicalMemory; +import li.cil.circuity.vm.device.memory.ByteBufferMemory; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.BufferedInputStream; +import java.io.FileInputStream; + +public class InstructionTests { + static final Logger LOGGER = LogManager.getLogger(); + + PhysicalMemory memory; + + R5Board board; + + @BeforeEach + public void initialize() { + board = new R5Board(); + + memory = new ByteBufferMemory(64 * 1014 * 1024); + + board.addDevice(0x80000000, memory); + } + + @Test + public void testLUI() throws MemoryAccessException { + loadProgram("lui a0, 0xf000"); + + board.step(2); + final R5CPUStateSnapshot state = board.getCpu().getState(); + Assertions.assertEquals(0xf000, state.x[10]); + } + + @Test + public void testAUIPC() throws MemoryAccessException { + loadProgram("auipc a0, 0"); + + board.step(2); + final R5CPUStateSnapshot state = board.getCpu().getState(); + Assertions.assertEquals(state.pc - 4, state.x[10]); + } + + @Test + public void testJAL() throws MemoryAccessException { + loadProgram("jal a0, 0xf0"); + + final R5CPUStateSnapshot state1 = board.getCpu().getState(); + board.step(2); + final R5CPUStateSnapshot state2 = board.getCpu().getState(); + Assertions.assertEquals(state2.pc, state1.pc + 0xf0); + Assertions.assertEquals(state1.pc + 4, state2.x[10]); + + board.getCpu().setState(state1); + loadProgram("jal a0, 0xf1"); + + board.step(2); + final R5CPUStateSnapshot state3 = board.getCpu().getState(); + Assertions.assertEquals(state3.pc, state1.pc + 0xf0); + Assertions.assertEquals(state1.pc + 4, state3.x[10]); + } + + @Test + public void testJALR() throws MemoryAccessException { + loadProgram("addi a0, a0, 0x100", + "jalr a0, a0, 0x200"); + + final R5CPUStateSnapshot state1 = board.getCpu().getState(); + board.step(3); + final R5CPUStateSnapshot state2 = board.getCpu().getState(); + Assertions.assertEquals(state2.pc, 0x300); + Assertions.assertEquals(state1.pc + 8, state2.x[10]); + } + + @Test + public void testADDI() throws MemoryAccessException { + loadProgram("addi a0, a0, 42"); + + board.step(2); + final R5CPUStateSnapshot state = board.getCpu().getState(); + Assertions.assertEquals(42, state.x[10]); + } + + @Test + public void testBios() throws Exception { +// final String firmware = "C:\\Users\\fnuecke\\Documents\\Repositories\\Circuity-1.15\\buildroot\\fw_jump.bin"; +// loadProgramFile(firmware, 0); +// +// final String kernel = "C:\\Users\\fnuecke\\Documents\\Repositories\\Circuity-1.15\\buildroot\\Image"; +// loadProgramFile(kernel, 0x400000); +// +// final UARTReader reader = new UARTReader(board); +// final Thread thread = new Thread(reader); +// thread.start(); +// +// final int n = 10000000; +// for (int i = 0; i < n; i++) { +// board.step(100000); +// } +// +// reader.stop(); +// thread.join(); + } + + private void loadProgram(final String... value) throws MemoryAccessException { + R5Assembler.assemble(value, memory, 0); + } + + private void loadProgramFile(final String path, int address) throws Exception { + try (final FileInputStream is = new FileInputStream(path)) { + final BufferedInputStream bis = new BufferedInputStream(is); + for (int value = bis.read(); value != -1; value = bis.read()) { + memory.store8(address++, (byte) value); + } + } + } + + private static final class UARTReader implements Runnable { + private final R5Board board; + private boolean keepRunning = true; + + private UARTReader(final R5Board board) { + this.board = board; + } + + public void stop() { + keepRunning = false; + } + + @Override + public void run() { + try { + while (keepRunning) { + final int i = board.readValue(); + if (i >= 0) { + System.out.print((char) i); + } + Thread.yield(); + } + } catch (final Throwable t) { + LOGGER.error(t); + } + } + } +} diff --git a/src/test/java/li/cil/circuity/vm/riscv/VirtualMachineTests.java b/src/test/java/li/cil/circuity/vm/riscv/VirtualMachineTests.java new file mode 100644 index 00000000..27f38283 --- /dev/null +++ b/src/test/java/li/cil/circuity/vm/riscv/VirtualMachineTests.java @@ -0,0 +1,13 @@ +package li.cil.circuity.vm.riscv; + +import li.cil.circuity.vm.device.memory.ByteBufferMemory; +import org.junit.jupiter.api.Test; + +public class VirtualMachineTests { + @Test + public void testVM() { + final R5Board virtualMachine = new R5Board(); + virtualMachine.addDevice(new ByteBufferMemory(4 * 1024 * 1024)); + virtualMachine.reset(); + } +}