diff --git a/hw/application_fpga/Makefile b/hw/application_fpga/Makefile index 61c9f0d..349b1e3 100644 --- a/hw/application_fpga/Makefile +++ b/hw/application_fpga/Makefile @@ -145,6 +145,10 @@ TESTFW_OBJS = \ $(P)/fw/tk1/assert.o \ $(P)/fw/tk1/blake2s/blake2s.o +IRQPOC_OBJS = \ + $(P)/fw/irqpoc/main.o \ + $(P)/fw/irqpoc/start.o + #------------------------------------------------------------------- # All: Complete build of HW and FW. #------------------------------------------------------------------- @@ -181,6 +185,7 @@ LDFLAGS = -T $(P)/fw/tk1/firmware.lds $(FIRMWARE_OBJS): $(FIRMWARE_DEPS) $(TESTFW_OBJS): $(FIRMWARE_DEPS) +$(IRQPOC_OBJS): $(FIRMWARE_DEPS) firmware.elf: $(FIRMWARE_OBJS) $(P)/fw/tk1/firmware.lds $(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(LDFLAGS) -o $@ @@ -223,6 +228,9 @@ splint: testfw.elf: $(TESTFW_OBJS) $(P)/fw/tk1/firmware.lds $(CC) $(CFLAGS) $(TESTFW_OBJS) $(LDFLAGS) -o $@ +irqpoc.elf: $(IRQPOC_OBJS) $(P)/fw/tk1/firmware.lds + $(CC) $(CFLAGS) $(IRQPOC_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) > $@ @@ -233,6 +241,8 @@ simfirmware.hex: simfirmware.bin simfirmware_size_mismatch python3 $(P)/tools/makehex/makehex.py $< $(BRAM_FW_SIZE) > $@ testfw.hex: testfw.bin testfw_size_mismatch python3 $(P)/tools/makehex/makehex.py $< $(BRAM_FW_SIZE) > $@ +irqpoc.hex: irqpoc.bin + python3 $(P)/tools/makehex/makehex.py $< $(BRAM_FW_SIZE) > $@ .PHONY: check-binary-hashes check-binary-hashes: @@ -406,11 +416,13 @@ application_fpga_testfw.bin: application_fpga.asc bram_fw.hex testfw.hex #------------------------------------------------------------------- # Build testbench simulation for the design #------------------------------------------------------------------- +SIMFIRMWARE = simfirmware.hex + tb_application_fpga: $(SIM_VERILOG_SRCS) \ $(VERILOG_SRCS) \ $(PICORV32_SRCS) \ $(ICE40_SIM_CELLS) \ - simfirmware.hex + $(SIMFIRMWARE) python3 ./tools/app_bin_to_spram_hex.py \ ./tb/app.bin \ ./tb/output_spram0.hex \ @@ -434,7 +446,7 @@ tb_application_fpga: $(SIM_VERILOG_SRCS) \ -DNO_ICE40_DEFAULT_ASSIGNMENTS \ -DAPP_SIZE=$(shell ls -l tb/app.bin| awk '{print $$5}') \ -DBRAM_FW_SIZE=$(BRAM_FW_SIZE) \ - -DFIRMWARE_HEX=\"$(P)/simfirmware.hex\" \ + -DFIRMWARE_HEX=\"$(P)/$(SIMFIRMWARE)\" \ -DUDS_HEX=\"$(P)/data/uds.hex\" \ -DUDI_HEX=\"$(P)/data/udi.hex\" \ $(filter %.v, $^) @@ -442,6 +454,15 @@ tb_application_fpga: $(SIM_VERILOG_SRCS) \ ./tb_verilated/Vtb_application_fpga_sim \ && { echo -e "\n -- Wave simulation saved to tb_application_fpga_sim.fst\n"; true; } +.PHONY: emptyapp +emptyapp: + dd if=/dev/zero of=tb/app.bin bs=1024 count=128 + +.PHONY: tb_application_fpga_irqpoc +tb_application_fpga_irqpoc: SIMFIRMWARE=irqpoc.hex +tb_application_fpga_irqpoc: irqpoc.hex emptyapp tb_application_fpga + + #------------------------------------------------------------------- # FPGA device programming. #------------------------------------------------------------------- @@ -488,6 +509,7 @@ clean_fw: rm -f $(FIRMWARE_OBJS) rm -f testfw.{elf,elf.map,bin,hex} rm -f $(TESTFW_OBJS) + rm -f $(IRQPOC_OBJS) rm -f qemu_firmware.elf .PHONY: clean_fw diff --git a/hw/application_fpga/README.md b/hw/application_fpga/README.md index 1f3ab78..ac98d43 100644 --- a/hw/application_fpga/README.md +++ b/hw/application_fpga/README.md @@ -24,17 +24,19 @@ and bitmasks, see the file `fw/tk1_mem.h`. Rough memory map: -| *name* | *prefix* | -|---------|----------| -| ROM | 0x00 | -| RAM | 0x40 | -| TRNG | 0xc0 | -| Timer | 0xc1 | -| UDS | 0xc2 | -| UART | 0xc3 | -| Touch | 0xc4 | -| FW\_RAM | 0xd0 | -| TK1 | 0xff | +| *name* | *prefix* | +|------------|----------| +| ROM | 0x00 | +| RAM | 0x40 | +| TRNG | 0xc0 | +| Timer | 0xc1 | +| UDS | 0xc2 | +| UART | 0xc3 | +| Touch | 0xc4 | +| FW\_RAM | 0xd0 | +| IRQ30\_SET | 0xe0 | +| IRQ31\_SET | 0xe1 | +| TK1 | 0xff | ## `clk_reset_gen` @@ -96,6 +98,41 @@ hours, days) there is also a 32 bit prescaler. The timer is available to use by firmware and applications. +## `irq30_set` + +Interrupt 30 trigger area. A 32-bit write to the IRQ30\_SET memory +area will trigger interrupt 30. + +## `irq31_set` + +Interrupt 31 trigger area. A 32-bit write to the IRQ31\_SET memory +area will trigger interrupt 31. + +## Interrupts + +Triggering an interrupt will cause the CPU to execute the interrupt +handler att address 0x10. + +The interrupt handler is shared by IRQ30 and IRQ31. Register `x4` can +be inspected to determine the interrupt source. Each interrupt source +is assigned one bit in x4. Triggered interrupts have their bit set to +`1`. + +| *Interrupt source* | *x4 bit* | +|--------------------|----------| +| IRQ30\_SET | 30 | +| IRQ31\_SET | 31 | + +The return address is located in register `x3`. Calling the PicoRV32 +specific instruction `retirq` exits the interrupt handler and clears +the interrupt source. + +No registers are stored/restored when entering/exiting the interrupt +handler. It is up to the software to store/restore as necessary. + +Interrupts can be enabled/disabled using the PicoRV32 specific +`maskirq` instruction. + ## `tk1` See [tk1 README](core/tk1/README.md) for details. @@ -107,7 +144,6 @@ Contains: - RGB LED control. - General purpose input/output (GPIO) pin control. - Application introspection: start address and size of binary. -- BLAKE2s function access. - Compound Device Identity (CDI). - Unique Device Identity (UDI). - RAM memory protection. diff --git a/hw/application_fpga/core/tk1/rtl/tk1.v b/hw/application_fpga/core/tk1/rtl/tk1.v index 939897c..67f2131 100644 --- a/hw/application_fpga/core/tk1/rtl/tk1.v +++ b/hw/application_fpga/core/tk1/rtl/tk1.v @@ -181,6 +181,7 @@ module tk1 #( wire spi_ready; wire [ 7 : 0] spi_rx_data; + //---------------------------------------------------------------- // Concurrent connectivity for ports etc. //---------------------------------------------------------------- diff --git a/hw/application_fpga/fw/irqpoc/Makefile b/hw/application_fpga/fw/irqpoc/Makefile new file mode 100644 index 0000000..82b9262 --- /dev/null +++ b/hw/application_fpga/fw/irqpoc/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/custom_ops.S b/hw/application_fpga/fw/irqpoc/custom_ops.S new file mode 100644 index 0000000..71889b9 --- /dev/null +++ b/hw/application_fpga/fw/irqpoc/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/main.c b/hw/application_fpga/fw/irqpoc/main.c new file mode 100644 index 0000000..c98f474 --- /dev/null +++ b/hw/application_fpga/fw/irqpoc/main.c @@ -0,0 +1,10 @@ +/* + * Copyright (C) 2022, 2023 - Tillitis AB + * SPDX-License-Identifier: GPL-2.0-only + */ + +int main(void) +{ + while (1) { + } +} diff --git a/hw/application_fpga/fw/irqpoc/start.S b/hw/application_fpga/fw/irqpoc/start.S new file mode 100644 index 0000000..cb67e02 --- /dev/null +++ b/hw/application_fpga/fw/irqpoc/start.S @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022, 2023 - Tillitis AB + * SPDX-License-Identifier: GPL-2.0-only +*/ + +#include "custom_ops.S" // PicoRV32 custom instructions + + .section ".text.init" + .globl _start +_start: + j init + + .=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. + + nop // NOPs are not necessary. Only added to make it easier to find + nop // when simulating. + nop + picorv32_retirq_insn() // Return from interrupt + + .=0x20 // Setting location of init to 0x20. Makes it easier to find when + // simulating. +init: + li t0, 0x3fffffff // IRQ31 & IRQ30 mask + picorv32_maskirq_insn(zero, t0) // Enable IRQs + + li t0, 0xe1000000 // IRQ31 trigger address + sw zero, 0(t0) // Raise IRQ by writing to interrupt trigger address. + // Writing any data triggers an interrupt. + + li t0, 0xe0000000 // IRQ30 trigger address + sw zero, 0(t0) // Raise IRQ by writing to interrupt trigger address. + // Writing any data triggers an interrupt. +loop: + j loop + + .align 4 // Padding to please makehex.py which requires even 4-byte file + // sizes. + diff --git a/hw/application_fpga/rtl/application_fpga.v b/hw/application_fpga/rtl/application_fpga.v index 4d86e10..7134308 100644 --- a/hw/application_fpga/rtl/application_fpga.v +++ b/hw/application_fpga/rtl/application_fpga.v @@ -57,11 +57,15 @@ module application_fpga ( localparam UART_PREFIX = 6'h03; localparam TOUCH_SENSE_PREFIX = 6'h04; localparam FW_RAM_PREFIX = 6'h10; + localparam IRQ30_PREFIX = 6'h20; + localparam IRQ31_PREFIX = 6'h21; localparam TK1_PREFIX = 6'h3f; // Instruction used to cause a trap. localparam ILLEGAL_INSTRUCTION = 32'h0; + localparam IRQ30_IRQ_MASK = 2 ** 30; + localparam IRQ31_IRQ_MASK = 2 ** 31; //---------------------------------------------------------------- // Registers, memories with associated wires. @@ -80,11 +84,13 @@ module application_fpga ( wire reset_n; /* verilator lint_off UNOPTFLAT */ + reg [31 : 0] cpu_irq; wire cpu_trap; wire cpu_valid; wire cpu_instr; wire [03 : 0] cpu_wstrb; /* verilator lint_off UNUSED */ + wire [31 : 0] cpu_eoi; wire [31 : 0] cpu_addr; wire [31 : 0] cpu_wdata; @@ -139,6 +145,14 @@ module application_fpga ( wire [31 : 0] touch_sense_read_data; wire touch_sense_ready; + reg irq30_cs; + reg irq30_we; + (* keep *)reg irq30_eoi; + + reg irq31_cs; + reg irq31_we; + (* keep *)reg irq31_eoi; + reg tk1_cs; reg tk1_we; reg [ 7 : 0] tk1_address; @@ -166,12 +180,17 @@ module application_fpga ( picorv32 #( - .ENABLE_COUNTERS(0), - .TWO_STAGE_SHIFT(0), - .CATCH_MISALIGN (0), - .COMPRESSED_ISA (1), - .ENABLE_FAST_MUL(1), - .BARREL_SHIFTER (1) + .ENABLE_COUNTERS (0), + .TWO_STAGE_SHIFT (0), + .CATCH_MISALIGN (0), + .COMPRESSED_ISA (1), + .ENABLE_FAST_MUL (1), + .BARREL_SHIFTER (1), + .ENABLE_IRQ (1), + .ENABLE_IRQ_QREGS(0), + .ENABLE_IRQ_TIMER(0), + .MASKED_IRQ (~(IRQ31_IRQ_MASK | IRQ30_IRQ_MASK)), + .LATCHED_IRQ (IRQ31_IRQ_MASK | IRQ30_IRQ_MASK) ) cpu ( .clk(clk), .resetn(reset_n), @@ -185,11 +204,12 @@ module application_fpga ( .mem_rdata(muxed_rdata_reg), .mem_instr(cpu_instr), + .irq(cpu_irq), + .eoi(cpu_eoi), + // Defined unused ports. Makes lint happy. But // we still needs to help lint with empty ports. /* verilator lint_off PINCONNECTEMPTY */ - .irq(32'h0), - .eoi(), .trace_valid(), .trace_data(), .mem_la_read(), @@ -379,6 +399,23 @@ module application_fpga ( end + //---------------------------------------------------------------- + // irq_ctrl + // Interrupt logic + //---------------------------------------------------------------- + always @* begin : irq_ctrl + reg irq31_set; + reg irq30_set; + + irq31_set = irq31_cs & irq31_we; + irq30_set = irq30_cs & irq30_we; + cpu_irq = {irq31_set, irq30_set, 30'h0}; + + irq31_eoi = cpu_eoi[31]; + irq30_eoi = cpu_eoi[30]; + end + + //---------------------------------------------------------------- // cpu_mem_ctrl // CPU memory decode and control logic. @@ -428,6 +465,12 @@ module application_fpga ( touch_sense_we = |cpu_wstrb; touch_sense_address = cpu_addr[9 : 2]; + irq30_cs = 1'h0; + irq30_we = |cpu_wstrb; + + irq31_cs = 1'h0; + irq31_we = |cpu_wstrb; + tk1_cs = 1'h0; tk1_we = |cpu_wstrb; tk1_address = cpu_addr[9 : 2]; @@ -500,6 +543,16 @@ module application_fpga ( muxed_ready_new = fw_ram_ready; end + IRQ30_PREFIX: begin + irq30_cs = 1'h1; + muxed_ready_new = 1'h1; + end + + IRQ31_PREFIX: begin + irq31_cs = 1'h1; + muxed_ready_new = 1'h1; + end + TK1_PREFIX: begin tk1_cs = 1'h1; muxed_rdata_new = tk1_read_data; diff --git a/hw/application_fpga/tb/application_fpga_sim.v b/hw/application_fpga/tb/application_fpga_sim.v index 94e13ee..94fc49c 100644 --- a/hw/application_fpga/tb/application_fpga_sim.v +++ b/hw/application_fpga/tb/application_fpga_sim.v @@ -70,11 +70,15 @@ module application_fpga_sim ( localparam UART_PREFIX = 6'h03; localparam TOUCH_SENSE_PREFIX = 6'h04; localparam FW_RAM_PREFIX = 6'h10; + localparam IRQ30_PREFIX = 6'h20; + localparam IRQ31_PREFIX = 6'h21; localparam TK1_PREFIX = 6'h3f; // Instruction used to cause a trap. localparam ILLEGAL_INSTRUCTION = 32'h0; + localparam IRQ30_IRQ_MASK = 2 ** 30; + localparam IRQ31_IRQ_MASK = 2 ** 31; //---------------------------------------------------------------- // Registers, memories with associated wires. @@ -92,11 +96,13 @@ module application_fpga_sim ( wire reset_n; /* verilator lint_off UNOPTFLAT */ + reg [31 : 0] cpu_irq; wire cpu_trap; wire cpu_valid; wire cpu_instr; wire [ 3 : 0] cpu_wstrb; /* verilator lint_off UNUSED */ + wire [31 : 0] cpu_eoi; wire [31 : 0] cpu_addr; wire [31 : 0] cpu_wdata; @@ -151,6 +157,14 @@ module application_fpga_sim ( wire [31 : 0] touch_sense_read_data; wire touch_sense_ready; + reg irq30_cs; + reg irq30_we; + (* keep *)reg irq30_eoi; + + reg irq31_cs; + reg irq31_we; + (* keep *)reg irq31_eoi; + reg tk1_cs; reg tk1_we; reg [ 7 : 0] tk1_address; @@ -177,12 +191,17 @@ module application_fpga_sim ( picorv32 #( - .ENABLE_COUNTERS(0), - .TWO_STAGE_SHIFT(0), - .CATCH_MISALIGN (0), - .COMPRESSED_ISA (1), - .ENABLE_FAST_MUL(1), - .BARREL_SHIFTER (1) + .ENABLE_COUNTERS (0), + .TWO_STAGE_SHIFT (0), + .CATCH_MISALIGN (0), + .COMPRESSED_ISA (1), + .ENABLE_FAST_MUL (1), + .BARREL_SHIFTER (1), + .ENABLE_IRQ (1), + .ENABLE_IRQ_QREGS(0), + .ENABLE_IRQ_TIMER(0), + .MASKED_IRQ (~(IRQ31_IRQ_MASK | IRQ30_IRQ_MASK)), + .LATCHED_IRQ (IRQ31_IRQ_MASK | IRQ30_IRQ_MASK) ) cpu ( .clk(clk), .resetn(reset_n), @@ -196,11 +215,12 @@ module application_fpga_sim ( .mem_rdata(muxed_rdata_reg), .mem_instr(cpu_instr), + .irq(cpu_irq), + .eoi(cpu_eoi), + // Defined unused ports. Makes lint happy. But // we still needs to help lint with empty ports. /* verilator lint_off PINCONNECTEMPTY */ - .irq(32'h0), - .eoi(), .trace_valid(), .trace_data(), .mem_la_read(), @@ -391,6 +411,23 @@ module application_fpga_sim ( end + //---------------------------------------------------------------- + // irq_ctrl + // Interrupt logic + //---------------------------------------------------------------- + always @* begin : irq_ctrl + reg irq31_set; + reg irq30_set; + + irq31_set = irq31_cs & irq31_we; + irq30_set = irq30_cs & irq30_we; + cpu_irq = {irq31_set, irq30_set, 30'h0}; + + irq31_eoi = cpu_eoi[31]; + irq30_eoi = cpu_eoi[30]; + end + + //---------------------------------------------------------------- // cpu_mem_ctrl // CPU memory decode and control logic. @@ -442,6 +479,12 @@ module application_fpga_sim ( touch_sense_we = |cpu_wstrb; touch_sense_address = cpu_addr[9 : 2]; + irq30_cs = 1'h0; + irq30_we = |cpu_wstrb; + + irq31_cs = 1'h0; + irq31_we = |cpu_wstrb; + tk1_cs = 1'h0; tk1_we = |cpu_wstrb; tk1_address = cpu_addr[9 : 2]; @@ -534,6 +577,20 @@ module application_fpga_sim ( muxed_ready_new = fw_ram_ready; end + IRQ30_PREFIX: begin + `verbose($display("Access to blake2s interrupt trigger");) + ascii_state = "Blake2s IRQ trigger"; + irq30_cs = 1'h1; + muxed_ready_new = 1'h1; + end + + IRQ31_PREFIX: begin + `verbose($display("Access to syscall interrupt trigger");) + ascii_state = "Syscall IRQ trigger"; + irq31_cs = 1'h1; + muxed_ready_new = 1'h1; + end + TK1_PREFIX: begin `verbose($display("Access to TK1 core");) ascii_state = "TK1 core"; diff --git a/hw/application_fpga/tb/tb_application_fpga_sim.v b/hw/application_fpga/tb/tb_application_fpga_sim.v index df3173d..ad3f7e8 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) - #20000000; + #700; $display("TIMEOUT"); $finish; end