diff --git a/hw/application_fpga/Makefile b/hw/application_fpga/Makefile index 0959ae2..88efeca 100644 --- a/hw/application_fpga/Makefile +++ b/hw/application_fpga/Makefile @@ -157,6 +157,12 @@ IRQPOC_WITH_APP_OBJS = \ $(P)/fw/irqpoc_with_app/main.o \ $(P)/fw/irqpoc_with_app/start.o +IRQPOC_C_EXAMPLE_OBJS = \ + $(P)/fw/irqpoc_c_example/main.o \ + $(P)/fw/irqpoc_c_example/start.o \ + $(P)/fw/tk1/led.o \ + $(P)/fw/tk1/assert.o + #------------------------------------------------------------------- # All: Complete build of HW and FW. #------------------------------------------------------------------- @@ -196,6 +202,7 @@ $(TESTFW_OBJS): $(FIRMWARE_DEPS) $(IRQPOC_OBJS): $(FIRMWARE_DEPS) $(IRQPOC_LED_TOGGLE_OBJS): $(FIRMWARE_DEPS) $(IRQPOC_WITH_APP_OBJS): $(FIRMWARE_DEPS) +$(IRQPOC_C_EXAMPLE_OBJS): $(FIRMWARE_DEPS) firmware.elf: $(FIRMWARE_OBJS) $(P)/fw/tk1/firmware.lds $(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(LDFLAGS) -o $@ @@ -247,6 +254,9 @@ irqpoc_led_toggle.elf: $(IRQPOC_LED_TOGGLE_OBJS) $(P)/fw/tk1/firmware.lds irqpoc_with_app.elf: $(IRQPOC_WITH_APP_OBJS) $(P)/fw/tk1/firmware.lds $(CC) $(CFLAGS) $(IRQPOC_WITH_APP_OBJS) $(LDFLAGS) -o $@ +irqpoc_c_example.elf: $(IRQPOC_C_EXAMPLE_OBJS) $(P)/fw/tk1/firmware.lds + $(CC) $(CFLAGS) $(IRQPOC_C_EXAMPLE_OBJS) $(LDFLAGS) -o $@ + # Generate a fake BRAM file that will be filled in later after place-n-route bram_fw.hex: $(ICESTORM_PATH)icebram -v -g 32 $(BRAM_FW_SIZE) > $@ @@ -261,6 +271,8 @@ irqpoc.hex: irqpoc.bin python3 $(P)/tools/makehex/makehex.py $< $(BRAM_FW_SIZE) > $@ irqpoc_with_app.hex: irqpoc_with_app.bin python3 $(P)/tools/makehex/makehex.py $< $(BRAM_FW_SIZE) > $@ +irqpoc_c_example.hex: irqpoc_c_example.bin + python3 $(P)/tools/makehex/makehex.py $< $(BRAM_FW_SIZE) > $@ .PHONY: check-binary-hashes @@ -485,6 +497,10 @@ tb_application_fpga_irqpoc: irqpoc.hex emptyapp tb_application_fpga tb_application_fpga_irqpoc_with_app: SIMFIRMWARE=irqpoc_with_app.hex tb_application_fpga_irqpoc_with_app: irqpoc_with_app.hex emptyapp tb_application_fpga +.PHONY: tb_application_fpga_irqpoc_c_example +tb_application_fpga_irqpoc_c_example: SIMFIRMWARE=irqpoc_c_example.hex +tb_application_fpga_irqpoc_c_example: irqpoc_c_example.hex emptyapp tb_application_fpga + #------------------------------------------------------------------- # FPGA device programming. @@ -535,6 +551,7 @@ clean_fw: rm -f $(IRQPOC_OBJS) rm -f $(IRQPOC_LED_TOGGLE_OBJS) rm -f $(IRQPOC_WITH_APP_OBJS) + rm -f $(IRQPOC_C_EXAMPLE_OBJS) rm -f qemu_firmware.elf .PHONY: clean_fw diff --git a/hw/application_fpga/fw/irqpoc_c_example/Makefile b/hw/application_fpga/fw/irqpoc_c_example/Makefile new file mode 100644 index 0000000..82b9262 --- /dev/null +++ b/hw/application_fpga/fw/irqpoc_c_example/Makefile @@ -0,0 +1,9 @@ +# Uses ../.clang-format +FMTFILES=main.c +.PHONY: fmt +fmt: + clang-format --dry-run --ferror-limit=0 $(FMTFILES) + clang-format --verbose -i $(FMTFILES) +.PHONY: checkfmt +checkfmt: + clang-format --dry-run --ferror-limit=0 --Werror $(FMTFILES) diff --git a/hw/application_fpga/fw/irqpoc_c_example/custom_ops.S b/hw/application_fpga/fw/irqpoc_c_example/custom_ops.S new file mode 100644 index 0000000..71889b9 --- /dev/null +++ b/hw/application_fpga/fw/irqpoc_c_example/custom_ops.S @@ -0,0 +1,102 @@ +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or +// distribute this software, either in source code form or as a compiled +// binary, for any purpose, commercial or non-commercial, and by any +// means. + +#define regnum_q0 0 +#define regnum_q1 1 +#define regnum_q2 2 +#define regnum_q3 3 + +#define regnum_x0 0 +#define regnum_x1 1 +#define regnum_x2 2 +#define regnum_x3 3 +#define regnum_x4 4 +#define regnum_x5 5 +#define regnum_x6 6 +#define regnum_x7 7 +#define regnum_x8 8 +#define regnum_x9 9 +#define regnum_x10 10 +#define regnum_x11 11 +#define regnum_x12 12 +#define regnum_x13 13 +#define regnum_x14 14 +#define regnum_x15 15 +#define regnum_x16 16 +#define regnum_x17 17 +#define regnum_x18 18 +#define regnum_x19 19 +#define regnum_x20 20 +#define regnum_x21 21 +#define regnum_x22 22 +#define regnum_x23 23 +#define regnum_x24 24 +#define regnum_x25 25 +#define regnum_x26 26 +#define regnum_x27 27 +#define regnum_x28 28 +#define regnum_x29 29 +#define regnum_x30 30 +#define regnum_x31 31 + +#define regnum_zero 0 +#define regnum_ra 1 +#define regnum_sp 2 +#define regnum_gp 3 +#define regnum_tp 4 +#define regnum_t0 5 +#define regnum_t1 6 +#define regnum_t2 7 +#define regnum_s0 8 +#define regnum_s1 9 +#define regnum_a0 10 +#define regnum_a1 11 +#define regnum_a2 12 +#define regnum_a3 13 +#define regnum_a4 14 +#define regnum_a5 15 +#define regnum_a6 16 +#define regnum_a7 17 +#define regnum_s2 18 +#define regnum_s3 19 +#define regnum_s4 20 +#define regnum_s5 21 +#define regnum_s6 22 +#define regnum_s7 23 +#define regnum_s8 24 +#define regnum_s9 25 +#define regnum_s10 26 +#define regnum_s11 27 +#define regnum_t3 28 +#define regnum_t4 29 +#define regnum_t5 30 +#define regnum_t6 31 + +// x8 is s0 and also fp +#define regnum_fp 8 + +#define r_type_insn(_f7, _rs2, _rs1, _f3, _rd, _opc) \ +.word (((_f7) << 25) | ((_rs2) << 20) | ((_rs1) << 15) | ((_f3) << 12) | ((_rd) << 7) | ((_opc) << 0)) + +#define picorv32_getq_insn(_rd, _qs) \ +r_type_insn(0b0000000, 0, regnum_ ## _qs, 0b100, regnum_ ## _rd, 0b0001011) + +#define picorv32_setq_insn(_qd, _rs) \ +r_type_insn(0b0000001, 0, regnum_ ## _rs, 0b010, regnum_ ## _qd, 0b0001011) + +#define picorv32_retirq_insn() \ +r_type_insn(0b0000010, 0, 0, 0b000, 0, 0b0001011) + +#define picorv32_maskirq_insn(_rd, _rs) \ +r_type_insn(0b0000011, 0, regnum_ ## _rs, 0b110, regnum_ ## _rd, 0b0001011) + +#define picorv32_waitirq_insn(_rd) \ +r_type_insn(0b0000100, 0, 0, 0b100, regnum_ ## _rd, 0b0001011) + +#define picorv32_timer_insn(_rd, _rs) \ +r_type_insn(0b0000101, 0, regnum_ ## _rs, 0b110, regnum_ ## _rd, 0b0001011) + diff --git a/hw/application_fpga/fw/irqpoc_c_example/main.c b/hw/application_fpga/fw/irqpoc_c_example/main.c new file mode 100644 index 0000000..9531da7 --- /dev/null +++ b/hw/application_fpga/fw/irqpoc_c_example/main.c @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022, 2023 - Tillitis AB + * SPDX-License-Identifier: GPL-2.0-only + */ + +#include "../tk1/types.h" +#include "../tk1/led.h" +#include "../tk1/assert.h" + +// Proof-of-concept firmware for handling syscalls. +// This is NOT a best-practice example of secure syscall implementation. + +#define SYSCALL_HI (1 << 31) +#define SYSCALL_LO 0 + +#define SYSCALL_HI_SET_LED (SYSCALL_HI | 10) +#define SYSCALL_LO_SET_LED (SYSCALL_LO | 10) + +static void delay(int32_t count) { + volatile int32_t c = count; + while (c > 0) { + c--; + } +} + +int32_t syscall_lo_handler(uint32_t syscall_nr, uint32_t arg1) { + switch (syscall_nr) { + case SYSCALL_LO_SET_LED: + set_led(arg1); + //delay(1000000); + return 0; + default: + assert(1 == 2); + } + + assert(1 == 2); + return -1; // This should never run +} + +int32_t syscall_hi_handler(uint32_t syscall_nr, uint32_t arg1) { + switch (syscall_nr) { + case SYSCALL_HI_SET_LED: + set_led(arg1); + //delay(500000); + return 0; + default: + assert(1 == 2); + } + + assert(1 == 2); + return -1; // This should never run +} + +int main(void) +{ + while (1) { + } +} diff --git a/hw/application_fpga/fw/irqpoc_c_example/start.S b/hw/application_fpga/fw/irqpoc_c_example/start.S new file mode 100644 index 0000000..016a3d8 --- /dev/null +++ b/hw/application_fpga/fw/irqpoc_c_example/start.S @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2022, 2023 - Tillitis AB + * SPDX-License-Identifier: GPL-2.0-only +*/ + +// This firmware copies an app from ROM to app RAM. +// The app continuosly calls the SET_LED syscalls in firmware (main.c). +// +#include "../tk1_mem.h" +#include "custom_ops.S" // PicoRV32 custom instructions + +#define illegal_insn() .word 0 + +#define FW_SP_STORAGE (TK1_MMIO_FW_RAM_BASE + TK1_MMIO_FW_RAM_SIZE - 4) +#define FW_STACK_TOP (TK1_MMIO_FW_RAM_BASE + TK1_MMIO_FW_RAM_SIZE - 2*4) + +#define LED_RED (1 << TK1_MMIO_TK1_LED_R_BIT) +#define LED_GREEN (1 << TK1_MMIO_TK1_LED_G_BIT) +#define LED_BLUE (1 << TK1_MMIO_TK1_LED_B_BIT) + + .section ".text.init" + .globl _start +_start: + j init + +// +// IRQ handler +// + .=0x10 // IRQ handler at fixed address 0x10 +irq_handler: + // PicoRV32 stores the IRQ bitmask in x4. + // If bit 31 is 1: IRQ31 was triggered. + // If bit 30 is 1: IRQ30 was triggered. +irq_syscall_lo_check: + li t4, (1 << 30) + bne x4, t4, irq_syscall_hi_check + + // Run low privilege syscall handler + call syscall_lo_handler + + j irq_source_check_done +irq_syscall_hi_check: + li t4, (1 << 31) + bne x4, t4, unexpected_irq + + // Save app stack pointer. App is responsible for saving the rest of + // the registers. + la t0, FW_SP_STORAGE + sw sp, 0(t0) + + // Setup firmware stack pointer + la sp, FW_STACK_TOP + + // Run high privilege syscall handler + call syscall_hi_handler + + // Restore app stack pointer + la t0, FW_SP_STORAGE + lw sp, 0(t0) + + j irq_source_check_done +unexpected_irq: + illegal_insn() +irq_source_check_done: + // Verify that interrupt return address is in app RAM + li t0, TK1_RAM_BASE // 0x40000000 + blt x3, t0, x3_invalid + li t0, TK1_RAM_BASE + TK1_RAM_SIZE // 0x40020000 + bge x3, t0, x3_invalid + j x3_valid +x3_invalid: + illegal_insn() + j x3_invalid +x3_valid: + + // Remove data left over from the syscall handling + mv x0, zero + mv x1, zero + // x2 (sp) is assumed to be preserved by the interrupt handler + // x3 (interrupt return address) assumed preserved + mv x4, zero + mv x5, zero + mv x6, zero + mv x7, zero + mv x8, zero + mv x9, zero + // x10 (a0) contains syscall return value. And should not be destroyed. + mv x11, zero + mv x12, zero + mv x13, zero + mv x14, zero + mv x15, zero + mv x16, zero + mv x17, zero + mv x18, zero + mv x19, zero + mv x20, zero + mv x21, zero + mv x22, zero + mv x23, zero + mv x24, zero + mv x25, zero + mv x26, zero + mv x27, zero + mv x28, zero + mv x29, zero + mv x30, zero + mv x31, zero + + picorv32_retirq_insn() // Return from interrupt + +// +// Init +// + .=0x100 +init: + li t0, TK1_MMIO_TK1_LED + li t1, LED_RED + sw t1, 0(t0) + + // Enable IRQs + li t0, 0x3fffffff // IRQ31 & IRQ30 mask + picorv32_maskirq_insn(zero, t0) // Enable IRQs + + // Copy app to App RAM + la t0, app_start + la t1, app_end + li t2, 0x40000000 // 0x40000000: App RAM +copy_app: + lw t3, 0(t0) + sw t3, 0(t2) + addi t0, t0, 4 + addi t2, t2, 4 + bleu t0, t1, copy_app + + // Jump to app + li t2, 0x40000000 // 0x40000000: App RAM + jalr zero, 0(t2) + +// +// App +// +#define APP_SYSCALL_HI (1 << 31) +#define APP_SYSCALL_LO 0 +#define APP_SYSCALL_HI_SET_LED (APP_SYSCALL_HI | 10) +#define APP_SYSCALL_LO_SET_LED (APP_SYSCALL_LO | 10) + + .=0x1000 +app_start: + // Set stack pointer to end of app RAM + li sp, 0x4001fffc + +app_loop: + li a0, APP_SYSCALL_LO_SET_LED + li a1, LED_GREEN + call app_syscall + + li a0, APP_SYSCALL_LO_SET_LED + li a1, LED_BLUE + call app_syscall + + li a0, APP_SYSCALL_HI_SET_LED + li a1, LED_RED | LED_GREEN + call app_syscall + + li a0, APP_SYSCALL_HI_SET_LED + li a1, LED_RED | LED_BLUE + call app_syscall + + j app_loop + +app_syscall: + // Save registers to stack + addi sp, sp, -32*4 + sw x0, 0*4(sp) + sw x1, 1*4(sp) + // x2 (sp) is assumed to be preserved by the interrupt handler. + sw x3, 3*4(sp) + sw x4, 4*4(sp) + sw x5, 5*4(sp) + sw x6, 6*4(sp) + sw x7, 7*4(sp) + sw x8, 8*4(sp) + sw x9, 9*4(sp) + // x10 (a0) will contain syscall return value. And should not be saved. + sw x11, 11*4(sp) + sw x12, 12*4(sp) + sw x13, 13*4(sp) + sw x14, 14*4(sp) + sw x15, 15*4(sp) + sw x16, 16*4(sp) + sw x17, 17*4(sp) + sw x18, 18*4(sp) + sw x19, 19*4(sp) + sw x20, 20*4(sp) + sw x21, 21*4(sp) + sw x22, 22*4(sp) + sw x23, 23*4(sp) + sw x24, 24*4(sp) + sw x25, 25*4(sp) + sw x26, 26*4(sp) + sw x27, 27*4(sp) + sw x28, 28*4(sp) + sw x29, 29*4(sp) + sw x30, 30*4(sp) + sw x31, 31*4(sp) + + // Raise interrupt depending on bit 31 in a0 + // 0: Low privilege syscall + // 1: High privilege syscall + li t0, (1 << 31) + and t0, a0, t0 + beqz t0, app_syscall_low_priv + li t1, 0xe1000000 // High privilege interrupt + sw zero, 0(t1) // Trigger interrupt + j app_syscall_restore_registers +app_syscall_low_priv: + li t1, 0xe0000000 // Low privelege interrupt + sw zero, 0(t1) // Trigger interrupt + +app_syscall_restore_registers: + // Restore registers from stack + lw x0, 0*4(sp) + lw x1, 1*4(sp) + // x2 (sp) is assumed to be preserved by the interrupt handler. + lw x3, 3*4(sp) + lw x4, 4*4(sp) + lw x5, 5*4(sp) + lw x6, 6*4(sp) + lw x7, 7*4(sp) + lw x8, 8*4(sp) + lw x9, 9*4(sp) + // x10 (a0) contains syscall return value. And should not be destroyed. + lw x11, 11*4(sp) + lw x12, 12*4(sp) + lw x13, 13*4(sp) + lw x14, 14*4(sp) + lw x15, 15*4(sp) + lw x16, 16*4(sp) + lw x17, 17*4(sp) + lw x18, 18*4(sp) + lw x19, 19*4(sp) + lw x20, 20*4(sp) + lw x21, 21*4(sp) + lw x22, 22*4(sp) + lw x23, 23*4(sp) + lw x24, 24*4(sp) + lw x25, 25*4(sp) + lw x26, 26*4(sp) + lw x27, 27*4(sp) + lw x28, 28*4(sp) + lw x29, 29*4(sp) + lw x30, 30*4(sp) + lw x31, 31*4(sp) + addi sp, sp, 32*4 + + ret + + .align 4 +app_end: + diff --git a/hw/application_fpga/tb/tb_application_fpga_sim.v b/hw/application_fpga/tb/tb_application_fpga_sim.v index e3ab394..5bddf11 100644 --- a/hw/application_fpga/tb/tb_application_fpga_sim.v +++ b/hw/application_fpga/tb/tb_application_fpga_sim.v @@ -84,7 +84,7 @@ module tb_application_fpga_sim (); //---------------------------------------------------------------- initial begin // End simulation after XXX time units (set by timescale) - #3000; + #14000; $display("TIMEOUT"); $finish; end