aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClifford Wolf <clifford@clifford.at>2015-06-26 10:03:37 +0200
committerClifford Wolf <clifford@clifford.at>2015-06-26 10:03:37 +0200
commit9a4a06d981d1b2787b3f42907c3cd21b95cdfaf0 (patch)
tree70b9b773cec4a59578e6a370a3383cc89b0cb302
parent9d26ebcf58a7326167d175d9458e04950371b9cb (diff)
downloadpicorv32-9a4a06d981d1b2787b3f42907c3cd21b95cdfaf0.tar.gz
picorv32-9a4a06d981d1b2787b3f42907c3cd21b95cdfaf0.zip
Refactoring of IRQ handling
-rw-r--r--README.md73
-rw-r--r--firmware/sieve.c57
-rw-r--r--firmware/start.S2
-rw-r--r--picorv32.v134
-rw-r--r--testbench.v16
5 files changed, 168 insertions, 114 deletions
diff --git a/README.md b/README.md
index 5d2effb..6dc9b19 100644
--- a/README.md
+++ b/README.md
@@ -83,24 +83,19 @@ transaction. In the default configuration the PicoRV32 core only expects the
`mem_rdata` input to be valid in the cycle with `mem_valid && mem_ready` and
latches the value internally.
-#### ENABLE_EXTERNAL_IRQ (default = 0)
+#### ENABLE_IRQ (default = 0)
-Set this to 1 to enable external IRQs.
+Set this to 1 to enable IRQs.
-#### ENABLE_ILLINSTR_IRQ (default = 0)
+#### MASKED_IRQ (default = 32'h 0000_0000)
-Set this to 1 to enable the illegal instruction IRQ. This can be used for
-software implementations of instructions such as `MUL` and `DIV`.
+A 1 bit in this bitmask corresponds to a permanently disabled IRQ.
-#### ENABLE_TIMER_IRQ (default = 0)
-
-Set this to 1 to enable the built-in timer and timer IRQ.
-
-#### PROGADDR_RESET (default = 0)
+#### PROGADDR_RESET (default = 32'h 0000_0000)
The start address of the program.
-#### PROGADDR_IRQ (default = 16)
+#### PROGADDR_IRQ (default = 32'h 0000_0010)
The start address of the interrupt handler.
@@ -141,11 +136,30 @@ Custom Instructions for IRQ Handling
The following custom instructions are supported when IRQs are enabled.
+The PicoRV32 core has a built-in interrupt controller with 32 interrupts. An
+interrupt can be triggered by asserting the corresponding bit in the `irq`
+input of the core.
+
+When the interrupt handler is started, the `eoi` End Of Interrupt (EOI) signals
+for the handled interrupts goes high. The `eoi` signal goes low again when the
+interrupt handler returns.
+
+The IRQs 0-2 can be triggered internally and have the following meaning:
+
+| IRQ | Interrupt Source |
+| ---:| -----------------------------------|
+| 0 | Timer Interrupt |
+| 1 | SBREAK or Illegal Instruction |
+| 2 | BUS Error (Unalign Memory Access) |
+
The core has 4 additional 32-bit registers `q0 .. q3` that are used for IRQ
handling. When an IRQ triggers, the register `q0` contains the return address
-and `q1` contains the IRQ number. Registers `q2` and `q3` are uninitialized
-and can be used as temporary storage when saving/restoring register values
-in the IRQ handler.
+and `q1` contains a bitmask of all active IRQs. I.e. one call to the interrupt
+handler might need to service one than more IRQ when more than one bit is set
+in `q1`.
+
+Registers `q2` and `q3` are uninitialized and can be used as temporary storage
+when saving/restoring register values in the IRQ handler.
#### getq rd, qs
@@ -182,7 +196,7 @@ Example assembler code using the `custom0` mnemonic:
#### retirq
Return from interrupt. This instruction copies the value from `q0`
-to the program counter and enables interrupts. The Instruction is
+to the program counter and re-enables interrupts. The Instruction is
encoded under the `custom0` opcode:
0000010 00000 00000 000 00000 0001011
@@ -196,36 +210,26 @@ Example assembler code using the `custom0` mnemonic:
#### maskirq
+The "IRQ Mask" register contains a birtmask of masked (disabled) interrupts.
+This opcodes writes a new value to the irq mask register and reads the old
+value.
+
Enable/disable interrupt sources. The Instruction is encoded under the
`custom0` opcode:
- 0000011 XXXXX 00000 000 00000 0001011
+ 0000011 00000 XXXXX 000 XXXXX 0001011
f7 f5 rs f3 rd opcode
-The following interrupt sources occupy the following bits
-in the `f5` field:
-
-| Bit | Interrupt Source |
-| ------| ---------------------|
-| f5[0] | External IRQ |
-| f5[1] | Timer Interrupt |
-| f5[2] | Illegal Instruction |
-| f5[3] | Reserved |
-| f5[4] | Reserved |
-
-Set bits in the IRQ mask correspond to enabled interrupt sources.
-
Example assembler code using the `custom0` mnemonic:
| Instruction | Assember Code |
| ------------------| --------------------|
-| maskirq 0 | custom0 0, 0, 0, 3 |
-| maskirq 1 | custom0 0, 0, 1, 3 |
+| maskirq x1, x2 | custom0 1, 2, 0, 3 |
The processor starts with all interrupts disabled.
-An illegal instruction while the illegal instruction interrupt is disabled will
-cause the processor to halt.
+An illegal instruction or bus error while the illegal instruction or bus error
+interrupt is disabled will cause the processor to halt.
#### waitirq (unimplemented)
@@ -243,7 +247,7 @@ Example assembler code using the `custom0` mnemonic:
#### timer
-Reset the timer counter to a new value. The counter counts down cycles and
+Reset the timer counter to a new value. The counter counts down clock cycles and
triggers the timer interrupt when transitioning from 1 to 0. Setting the
counter to zero disables the timer.
@@ -261,6 +265,7 @@ Todos:
------
- Optional FENCE support
+- Optional Co-Processor Interface
- Optional write-through cache
- Optional support for compressed ISA
- Improved documentation and examples
diff --git a/firmware/sieve.c b/firmware/sieve.c
index 7f1c82e..6fb5b17 100644
--- a/firmware/sieve.c
+++ b/firmware/sieve.c
@@ -33,7 +33,7 @@ static void print_dec(int val)
{
char buffer[10];
char *p = buffer;
- while (val) {
+ while (val || p == buffer) {
*(p++) = val % 10;
val = val / 10;
}
@@ -88,24 +88,28 @@ void sieve()
}
}
-void irq(uint32_t *regs, uint32_t irqnum)
+uint32_t *irq(uint32_t *regs, uint32_t irqs)
{
- static int ext_irq_count = 0;
+ static int ext_irq_4_count = 0;
+ static int ext_irq_5_count = 0;
static int timer_irq_count = 0;
- if (irqnum == 0) {
- ext_irq_count++;
- // print_str("[EXT-IRQ]");
- return;
+ if ((irqs & (1<<4)) != 0) {
+ ext_irq_4_count++;
+ // print_str("[EXT-IRQ-4]");
}
- if (irqnum == 1) {
+ if ((irqs & (1<<5)) != 0) {
+ ext_irq_5_count++;
+ // print_str("[EXT-IRQ-5]");
+ }
+
+ if ((irqs & 1) != 0) {
timer_irq_count++;
// print_str("[TIMER-IRQ]");
- return;
}
- if (irqnum == 2)
+ if ((irqs & 6) != 0)
{
int i, k;
uint32_t pc = regs[0] - 4;
@@ -114,12 +118,22 @@ void irq(uint32_t *regs, uint32_t irqnum)
print_str("\n");
print_str("------------------------------------------------------------\n");
- if (instr == 0x00100073) {
- print_str("SBREAK instruction at 0x");
- print_hex(pc);
- print_str("\n");
- } else {
- print_str("Illegal Instruction at 0x");
+ if ((irqs & 2) != 0) {
+ if (instr == 0x00100073) {
+ print_str("SBREAK instruction at 0x");
+ print_hex(pc);
+ print_str("\n");
+ } else {
+ print_str("Illegal Instruction at 0x");
+ print_hex(pc);
+ print_str(": 0x");
+ print_hex(instr);
+ print_str("\n");
+ }
+ }
+
+ if ((irqs & 4) != 0) {
+ print_str("Bus error in Instruction at 0x");
print_hex(pc);
print_str(": 0x");
print_hex(instr);
@@ -164,8 +178,12 @@ void irq(uint32_t *regs, uint32_t irqnum)
print_str("------------------------------------------------------------\n");
- print_str("Number of external IRQs counted: ");
- print_dec(ext_irq_count);
+ print_str("Number of fast external IRQs counted: ");
+ print_dec(ext_irq_4_count);
+ print_str("\n");
+
+ print_str("Number of slow external IRQs counted: ");
+ print_dec(ext_irq_5_count);
print_str("\n");
print_str("Number of timer IRQs counted: ");
@@ -173,7 +191,8 @@ void irq(uint32_t *regs, uint32_t irqnum)
print_str("\n");
__asm__("sbreak");
- return;
}
+
+ return regs;
}
diff --git a/firmware/start.S b/firmware/start.S
index cdb8da0..3e4705a 100644
--- a/firmware/start.S
+++ b/firmware/start.S
@@ -10,7 +10,7 @@
jal zero,n; n ## _ret:
reset_vec:
- custom0 0,0,7,3 // maskirq 7
+ custom0 0,0,0,3 // maskirq zero, zero
j start
nop
nop
diff --git a/picorv32.v b/picorv32.v
index 58edeaa..7ff1023 100644
--- a/picorv32.v
+++ b/picorv32.v
@@ -26,17 +26,16 @@
***************************************************************/
module picorv32 #(
- parameter ENABLE_COUNTERS = 1,
- parameter ENABLE_REGS_16_31 = 1,
- parameter ENABLE_REGS_DUALPORT = 1,
- parameter LATCHED_MEM_RDATA = 0,
- parameter ENABLE_EXTERNAL_IRQ = 0,
- parameter ENABLE_ILLINSTR_IRQ = 0,
- parameter ENABLE_TIMER_IRQ = 0,
- parameter PROGADDR_RESET = 0,
- parameter PROGADDR_IRQ = 16
+ parameter [ 0:0] ENABLE_COUNTERS = 1,
+ parameter [ 0:0] ENABLE_REGS_16_31 = 1,
+ parameter [ 0:0] ENABLE_REGS_DUALPORT = 1,
+ parameter [ 0:0] LATCHED_MEM_RDATA = 0,
+ parameter [ 0:0] ENABLE_IRQ = 0,
+ parameter [31:0] MASKED_IRQ = 32'h 0000_0000,
+ parameter [31:0] PROGADDR_RESET = 32'h 0000_0000,
+ parameter [31:0] PROGADDR_IRQ = 32'h 0000_0010
) (
- input clk, resetn, irq,
+ input clk, resetn,
output reg trap,
output reg mem_valid,
@@ -53,9 +52,15 @@ module picorv32 #(
output mem_la_write,
output [31:0] mem_la_addr,
output reg [31:0] mem_la_wdata,
- output reg [ 3:0] mem_la_wstrb
+ output reg [ 3:0] mem_la_wstrb,
+
+ // IRQ interface
+ input [31:0] irq,
+ output reg [31:0] eoi
);
- localparam ENABLE_IRQ = ENABLE_EXTERNAL_IRQ || ENABLE_ILLINSTR_IRQ || ENABLE_TIMER_IRQ;
+ localparam integer irq_timer = 0;
+ localparam integer irq_sbreak = 1;
+ localparam integer irq_buserror = 2;
localparam integer irqregs_offset = ENABLE_REGS_16_31 ? 32 : 16;
localparam integer regfile_size = (ENABLE_REGS_16_31 ? 32 : 16) + 4*ENABLE_IRQ;
@@ -69,8 +74,8 @@ module picorv32 #(
wire [31:0] next_pc;
reg irq_active;
- reg [4:0] irq_mask;
- reg [4:0] irq_pending;
+ reg [31:0] irq_mask;
+ reg [31:0] irq_pending;
reg [31:0] timer;
// Memory Interface
@@ -185,6 +190,7 @@ module picorv32 #(
reg [regindex_bits-1:0] decoded_rd, decoded_rs1, decoded_rs2;
reg [31:0] decoded_imm, decoded_imm_uj;
reg decoder_trigger;
+ reg decoder_trigger_q;
reg decoder_pseudo_trigger;
reg is_lui_auipc_jal;
@@ -272,7 +278,7 @@ module picorv32 #(
if (instr_waitirq) new_instruction = "waitirq";
if (instr_timer) new_instruction = "timer";
- if (new_instruction)
+ if (decoder_trigger_q)
instruction = new_instruction;
end
@@ -362,7 +368,7 @@ module picorv32 #(
instr_setq <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000001 && ENABLE_IRQ;
instr_maskirq <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000011 && ENABLE_IRQ;
instr_waitirq <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000100 && ENABLE_IRQ;
- instr_timer <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000101 && ENABLE_TIMER_IRQ;
+ instr_timer <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000101 && ENABLE_IRQ;
is_slli_srli_srai <= is_alu_reg_imm && |{
mem_rdata_q[14:12] == 3'b001 && mem_rdata_q[31:25] == 7'b0000000,
@@ -432,6 +438,8 @@ module picorv32 #(
reg [31:0] current_pc;
assign next_pc = latched_store && latched_branch ? reg_out : reg_next_pc;
+ reg [31:0] next_irq_pending;
+
reg [31:0] alu_out;
reg alu_out_0;
@@ -484,16 +492,19 @@ module picorv32 #(
if (ENABLE_COUNTERS)
count_cycle <= resetn ? count_cycle + 1 : 0;
- if (ENABLE_TIMER_IRQ && timer) begin
+ next_irq_pending = irq_pending;
+
+ if (ENABLE_IRQ && timer) begin
if (timer - 1 == 0)
- irq_pending[1] <= 1;
+ next_irq_pending[irq_timer] = 1;
timer <= timer - 1;
end
- if (ENABLE_EXTERNAL_IRQ && irq) begin
- irq_pending[0] <= 1;
+ if (ENABLE_IRQ) begin
+ next_irq_pending = next_irq_pending | irq;
end
+ decoder_trigger_q <= decoder_trigger;
decoder_trigger <= mem_do_rinst && mem_done;
decoder_pseudo_trigger <= 0;
@@ -509,9 +520,10 @@ module picorv32 #(
latched_is_lh <= 0;
latched_is_lb <= 0;
irq_active <= 0;
- irq_mask <= 0;
- irq_pending <= 0;
+ irq_mask <= ~0;
+ next_irq_pending = 0;
irq_state <= 0;
+ eoi <= 0;
timer <= 0;
cpu_state <= cpu_state_fetch;
end else
@@ -546,16 +558,9 @@ module picorv32 #(
mem_do_rinst <= 1;
end else
if (ENABLE_IRQ && irq_state[1]) begin
- cpuregs[latched_rd] <=
- irq_pending[0] && irq_mask[0] ? 0 :
- irq_pending[1] && irq_mask[1] ? 1 :
- irq_pending[2] && irq_mask[2] ? 2 :
- irq_pending[3] && irq_mask[3] ? 3 : 4;
- irq_pending <=
- irq_pending[0] && irq_mask[0] ? irq_pending & 5'b11110 :
- irq_pending[1] && irq_mask[1] ? irq_pending & 5'b11101 :
- irq_pending[2] && irq_mask[2] ? irq_pending & 5'b11011 :
- irq_pending[3] && irq_mask[3] ? irq_pending & 5'b10111 : irq_pending & 5'b01111;
+ eoi <= irq_pending & ~irq_mask;
+ cpuregs[latched_rd] <= irq_pending & ~irq_mask;
+ next_irq_pending = next_irq_pending & irq_mask;
end
reg_pc <= current_pc;
@@ -569,7 +574,7 @@ module picorv32 #(
latched_is_lb <= 0;
latched_rd <= decoded_rd;
- if (ENABLE_IRQ && ((decoder_trigger && !irq_active && |(irq_pending & irq_mask)) || irq_state)) begin
+ if (ENABLE_IRQ && ((decoder_trigger && !irq_active && |(irq_pending & ~irq_mask)) || irq_state)) begin
irq_state <=
irq_state == 2'b00 ? 2'b01 :
irq_state == 2'b01 ? 2'b10 : 2'b00;
@@ -600,14 +605,14 @@ module picorv32 #(
reg_op1 <= 'bx;
reg_op2 <= 'bx;
`ifdef DEBUG
- $display("DECODE: 0x%08x %-0s", reg_pc, instruction);
+ $display("DECODE: 0x%08x %-0s", reg_pc, instruction ? instruction : "UNKNOWN");
`endif
if (instr_trap) begin
`ifdef DEBUG
$display("SBREAK OR UNSUPPORTED INSN AT 0x%08x", reg_pc);
`endif
- if (ENABLE_ILLINSTR_IRQ && irq_mask[2] && !irq_active) begin
- irq_pending[2] <= 1;
+ if (ENABLE_IRQ && !irq_mask[irq_sbreak] && !irq_active) begin
+ next_irq_pending[irq_sbreak] = 1;
cpu_state <= cpu_state_fetch;
end else
cpu_state <= cpu_state_trap;
@@ -645,6 +650,7 @@ module picorv32 #(
cpu_state <= cpu_state_fetch;
end else
if (ENABLE_IRQ && instr_retirq) begin
+ eoi <= 0;
irq_active <= 0;
latched_branch <= 1;
latched_store <= 1;
@@ -652,10 +658,12 @@ module picorv32 #(
cpu_state <= cpu_state_fetch;
end else
if (ENABLE_IRQ && instr_maskirq) begin
- irq_mask = decoded_rs2;
+ latched_store <= 1;
+ reg_out <= irq_mask;
+ irq_mask <= (decoded_rs1 ? cpuregs[decoded_rs1] : 0) | MASKED_IRQ;
cpu_state <= cpu_state_fetch;
end else
- if (ENABLE_TIMER_IRQ && instr_timer) begin
+ if (ENABLE_IRQ && instr_timer) begin
timer <= cpuregs[decoded_rs1];
cpu_state <= cpu_state_fetch;
end else begin
@@ -806,20 +814,29 @@ module picorv32 #(
`ifdef DEBUG
$display("MISALIGNED WORD: 0x%08x", reg_op1);
`endif
- cpu_state <= cpu_state_trap;
+ if (ENABLE_IRQ && !irq_mask[irq_buserror] && !irq_active) begin
+ next_irq_pending[irq_buserror] = 1;
+ end else
+ cpu_state <= cpu_state_trap;
end
if (mem_wordsize == 1 && reg_op1[0] != 0) begin
`ifdef DEBUG
$display("MISALIGNED HALFWORD: 0x%08x", reg_op1);
`endif
- cpu_state <= cpu_state_trap;
+ if (ENABLE_IRQ && !irq_mask[irq_buserror] && !irq_active) begin
+ next_irq_pending[irq_buserror] = 1;
+ end else
+ cpu_state <= cpu_state_trap;
end
end
if (resetn && mem_do_rinst && reg_pc[1:0] != 0) begin
`ifdef DEBUG
$display("MISALIGNED INSTRUCTION: 0x%08x", reg_pc);
`endif
- cpu_state <= cpu_state_trap;
+ if (ENABLE_IRQ && !irq_mask[irq_buserror] && !irq_active) begin
+ next_irq_pending[irq_buserror] = 1;
+ end else
+ cpu_state <= cpu_state_trap;
end
if (!resetn || mem_done) begin
@@ -836,6 +853,8 @@ module picorv32 #(
if (set_mem_do_wdata)
mem_do_wdata <= 1;
+ irq_pending <= next_irq_pending & ~MASKED_IRQ;
+
reg_pc[1:0] <= 0;
reg_next_pc[1:0] <= 0;
current_pc = 'bx;
@@ -848,14 +867,15 @@ endmodule
***************************************************************/
module picorv32_axi #(
- parameter ENABLE_COUNTERS = 1,
- parameter ENABLE_REGS_16_31 = 1,
- parameter ENABLE_REGS_DUALPORT = 1,
- parameter ENABLE_EXTERNAL_IRQ = 0,
- parameter ENABLE_ILLINSTR_IRQ = 0,
- parameter ENABLE_TIMER_IRQ = 0
+ parameter [ 0:0] ENABLE_COUNTERS = 1,
+ parameter [ 0:0] ENABLE_REGS_16_31 = 1,
+ parameter [ 0:0] ENABLE_REGS_DUALPORT = 1,
+ parameter [ 0:0] ENABLE_IRQ = 0,
+ parameter [31:0] MASKED_IRQ = 32'h 0000_0000,
+ parameter [31:0] PROGADDR_RESET = 32'h 0000_0000,
+ parameter [31:0] PROGADDR_IRQ = 32'h 0000_0010
) (
- input clk, resetn, irq,
+ input clk, resetn,
output trap,
// AXI4-lite master memory interface
@@ -880,7 +900,11 @@ module picorv32_axi #(
input mem_axi_rvalid,
output mem_axi_rready,
- input [31:0] mem_axi_rdata
+ input [31:0] mem_axi_rdata,
+
+ // IRQ interface
+ input [31:0] irq,
+ output [31:0] eoi
);
wire mem_valid;
wire [31:0] mem_addr;
@@ -923,13 +947,13 @@ module picorv32_axi #(
.ENABLE_COUNTERS (ENABLE_COUNTERS ),
.ENABLE_REGS_16_31 (ENABLE_REGS_16_31 ),
.ENABLE_REGS_DUALPORT(ENABLE_REGS_DUALPORT),
- .ENABLE_EXTERNAL_IRQ (ENABLE_EXTERNAL_IRQ ),
- .ENABLE_ILLINSTR_IRQ (ENABLE_ILLINSTR_IRQ ),
- .ENABLE_TIMER_IRQ (ENABLE_TIMER_IRQ )
+ .ENABLE_IRQ (ENABLE_IRQ ),
+ .MASKED_IRQ (MASKED_IRQ ),
+ .PROGADDR_RESET (PROGADDR_RESET ),
+ .PROGADDR_IRQ (PROGADDR_IRQ )
) picorv32_core (
.clk (clk ),
.resetn (resetn ),
- .irq (irq ),
.trap (trap ),
.mem_valid(mem_valid),
.mem_addr (mem_addr ),
@@ -937,7 +961,9 @@ module picorv32_axi #(
.mem_wstrb(mem_wstrb),
.mem_instr(mem_instr),
.mem_ready(mem_ready),
- .mem_rdata(mem_rdata)
+ .mem_rdata(mem_rdata),
+ .irq (irq ),
+ .eoi (eoi )
);
endmodule
diff --git a/testbench.v b/testbench.v
index b9914f5..5bfff25 100644
--- a/testbench.v
+++ b/testbench.v
@@ -6,9 +6,15 @@ module testbench;
reg clk = 1;
reg resetn = 0;
- wire irq = &uut.picorv32_core.count_cycle[12:0];
+ reg [31:0] irq;
wire trap;
+ always @* begin
+ irq = 0;
+ irq[4] = &uut.picorv32_core.count_cycle[12:0];
+ irq[5] = &uut.picorv32_core.count_cycle[15:0];
+ end
+
always #5 clk = ~clk;
initial begin
@@ -39,13 +45,10 @@ module testbench;
reg [31:0] mem_axi_rdata;
picorv32_axi #(
- .ENABLE_EXTERNAL_IRQ (1),
- .ENABLE_ILLINSTR_IRQ (1),
- .ENABLE_TIMER_IRQ (1)
+ .ENABLE_IRQ(1)
) uut (
.clk (clk ),
.resetn (resetn ),
- .irq (irq ),
.trap (trap ),
.mem_axi_awvalid(mem_axi_awvalid),
.mem_axi_awready(mem_axi_awready),
@@ -63,7 +66,8 @@ module testbench;
.mem_axi_arprot (mem_axi_arprot ),
.mem_axi_rvalid (mem_axi_rvalid ),
.mem_axi_rready (mem_axi_rready ),
- .mem_axi_rdata (mem_axi_rdata )
+ .mem_axi_rdata (mem_axi_rdata ),
+ .irq (irq )
);
reg [31:0] memory [0:64*1024/4-1];