Skip to content

Commit

Permalink
PoC: PicoRV32 interrupts
Browse files Browse the repository at this point in the history
A proof-of-concept of enabling PicoRV32 interrupts. Two interrupt
sources, which can be triggered by writes to memory addresses, are
added.  The design has only been simulated, not run on hardware.

Synthesis:

Ice40 LC utilization is 93% (4934/5280) when built using tkey-builder:4

Simulation:

A `tb_application_fpga_irqpoc` target is added. Running `make
tb_application_fpga_irqpoc` creates `tb_application_fpga_sim.fst` which
can be inspected in GTKWave or Surfer.

Firmware:

A simple firmware is added in `fw/irqpoc`. It enables both interrupts
and triggers each interrupt once.

Custom PicoRV32 instructions are located in `custom_ops.S`. It is
imported from upstream PicoRV32 commit:
YosysHQ/picorv32@70f3c33
  • Loading branch information
agren committed Dec 6, 2024
1 parent 07dec8b commit 89770d7
Show file tree
Hide file tree
Showing 9 changed files with 352 additions and 20 deletions.
26 changes: 24 additions & 2 deletions hw/application_fpga/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ TESTFW_OBJS = \
$(P)/fw/tk1/lib.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.
#-------------------------------------------------------------------
Expand Down Expand Up @@ -180,6 +184,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 $@
Expand Down Expand Up @@ -222,6 +227,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) > $@
Expand All @@ -232,6 +240,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:
Expand Down Expand Up @@ -402,11 +412,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 \
Expand All @@ -430,14 +442,23 @@ 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, $^)
make -C tb_verilated -f Vtb_application_fpga_sim.mk
./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.
#-------------------------------------------------------------------
Expand Down Expand Up @@ -484,6 +505,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

Expand Down
59 changes: 48 additions & 11 deletions hw/application_fpga/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand Down Expand Up @@ -96,6 +98,16 @@ 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.

## `tk1`

See [tk1 README](core/tk1/README.md) for details.
Expand All @@ -115,6 +127,31 @@ Contains:
- SPI main.
- System reset.

### 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.

### Illegal instruction monitor

Execution of illegal instructions will cause the CPU to enter its trap
Expand Down
9 changes: 9 additions & 0 deletions hw/application_fpga/fw/irqpoc/Makefile
Original file line number Diff line number Diff line change
@@ -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)
102 changes: 102 additions & 0 deletions hw/application_fpga/fw/irqpoc/custom_ops.S
Original file line number Diff line number Diff line change
@@ -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)

10 changes: 10 additions & 0 deletions hw/application_fpga/fw/irqpoc/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright (C) 2022, 2023 - Tillitis AB
* SPDX-License-Identifier: GPL-2.0-only
*/

int main(void)
{
while (1) {
}
}
42 changes: 42 additions & 0 deletions hw/application_fpga/fw/irqpoc/start.S
Original file line number Diff line number Diff line change
@@ -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, 0xe0000000 // IRQ31 trigger address
sw zero, 0(t0) // Raise IRQ by writing to interrupt trigger address.
// Writing any data triggers an interrupt.

li t0, 0xe1000000 // 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.

Loading

0 comments on commit 89770d7

Please sign in to comment.