-
-
Save btashton/4aa7ed42bc81ff4716821636997d9df9 to your computer and use it in GitHub Desktop.
/* uartblink.S -- Brennan Ashton <[email protected]> | |
* | |
*************************************************************************** | |
* 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. | |
*************************************************************************** | |
* | |
* ABOUT: | |
* This is a test for handling interrupts and ecall for the SERV core | |
* it can be run like this: | |
* fusesoc run --target=verilator_tb servant --uart_baudrate=115200 | |
* --firmware=uartblink.hex --timeout=10000000 | |
* | |
* The expected result is a stream of 'a\n' with some 'b\n' on the console. | |
* the a is a busy loop, while the b indicates an interrupt is handled. | |
* right now the interrupts should be disabled until ecall is issued in | |
* which case machine interrupts are re-enabled via the mpie bit in mstatus. | |
* | |
* It appears that this bit does not actually work with this core. So you | |
* will see a single 'b\n' from the ecall, but no interrupts from the timer. | |
* The expected behavior can be seen when the mie bit is set before ecall | |
* This line is currently commented out. | |
*/ | |
#ifndef UART_BASE | |
#define UART_BASE 0x40000000 | |
#endif | |
#ifndef TIMER_BASE | |
#define TIMER_BASE 0x80000000 | |
#endif | |
#ifndef DELAY | |
#define DELAY 10 /* Loop 10 times before writing UART */ | |
#endif | |
#ifndef TICK_COUNT | |
#define TICK_COUNT 40000 /* 40000 cycles per tick */ | |
#endif | |
/* | |
* "RESERVED" registers: | |
* a0 = UART Base address | |
* a1 = Timer Base address | |
* a2 = One | |
* t1 = delay max value | |
* t2 = Current timer value | |
* t3 = timer tick | |
*/ | |
.globl _start | |
.globl __trap_vec | |
_start: | |
/* Load GPIO base address to a0 */ | |
li a0, UART_BASE | |
/* Load TIMER base address to a1 */ | |
li a1, TIMER_BASE | |
/* Set busy timer value to control blink speed */ | |
li t1, DELAY | |
/* Initialize UART (stop bit) */ | |
li a2, 1 | |
sb a2, 0(a0) | |
nop | |
nop | |
/* Initialize Timer */ | |
li t3, TICK_COUNT | |
lw t4, 0(a1) | |
add t4, t4, t3 | |
sw t4, 0(a1) | |
/* Disable Interrupts */ | |
csrw mstatus, zero | |
/* setup trap */ | |
lui t0, %hi(__trap_vec) | |
addi t0, t0, %lo(__trap_vec) | |
csrw mtvec, t0 | |
/* Initialize timer interrupt */ | |
li t6, 0x80 | |
csrs mie, t6 | |
/* We should be able to enable interrupts via the MPIE bit of mstatus | |
* in the exception handler logic. These two instructions will enable | |
* interrupts ahead of the exception handler being called. | |
*/ | |
//li t5, 0x8 | |
//csrs mstatus, t5 | |
ecall | |
blinky: | |
/* "blink 'a\n' on the UART */ | |
/* Reset timer */ | |
and t2, zero, zero | |
/* Enter critical */ | |
li t0, 0x8 | |
csrrc t4, mstatus, t0 | |
jal uart_a | |
jal uart_nl | |
/* Leave critical */ | |
csrw mstatus, t4 | |
/* Delay loop */ | |
time1: | |
addi t2, t2, 1 | |
bne t1, t2, time1 | |
j blinky | |
/* Trap Vector */ | |
__trap_vec: | |
/* Increment the PC (only for ecall?) */ | |
csrr t4, mepc | |
addi t4, t4, 4 | |
csrw mepc, t4 | |
/* add to the timer offset */ | |
li t3, TICK_COUNT | |
lw t4, 0(a1) | |
add t4, t4, t3 | |
sw t4, 0(a1) | |
/* "blink" 'b\n' on UART */ | |
jal uart_b | |
jal uart_nl | |
/* Let's see if the mstatus mpie works. | |
* Interrupts should be enabled after this returns | |
*/ | |
li t4, 0x80 | |
csrw mstatus, t4 | |
mret | |
/* uart utility functions */ | |
uart_a: | |
/* 'a' */ | |
sb zero, 0(a0) | |
nop | |
nop | |
sb a2, 0(a0) | |
nop | |
nop | |
sb zero, 0(a0) | |
nop | |
nop | |
sb zero, 0(a0) | |
nop | |
nop | |
sb zero, 0(a0) | |
nop | |
nop | |
sb zero, 0(a0) | |
nop | |
nop | |
sb a2, 0(a0) | |
nop | |
nop | |
sb a2, 0(a0) | |
nop | |
nop | |
sb zero, 0(a0) | |
nop | |
nop | |
sb a2, 0(a0) | |
nop | |
nop | |
ret | |
uart_b: | |
/* 'b' */ | |
sb zero, 0(a0) | |
nop | |
nop | |
sb zero, 0(a0) | |
nop | |
nop | |
sb a2, 0(a0) | |
nop | |
nop | |
sb zero, 0(a0) | |
nop | |
nop | |
sb zero, 0(a0) | |
nop | |
nop | |
sb zero, 0(a0) | |
nop | |
nop | |
sb a2, 0(a0) | |
nop | |
nop | |
sb a2, 0(a0) | |
nop | |
nop | |
sb zero, 0(a0) | |
nop | |
nop | |
sb a2, 0(a0) | |
nop | |
nop | |
ret | |
uart_nl: | |
/* '\n' */ | |
sb zero, 0(a0) | |
nop | |
nop | |
sb zero, 0(a0) | |
nop | |
nop | |
sb a2, 0(a0) | |
nop | |
nop | |
sb zero, 0(a0) | |
nop | |
nop | |
sb a2, 0(a0) | |
nop | |
nop | |
sb zero, 0(a0) | |
nop | |
nop | |
sb zero, 0(a0) | |
nop | |
nop | |
sb zero, 0(a0) | |
nop | |
nop | |
sb zero, 0(a0) | |
nop | |
nop | |
sb a2, 0(a0) | |
nop | |
nop | |
ret |
I have not been able to set the mie.mtie bit. Is this a related problem? I'm using asm from C to read / write the csr regs, eg
inline uint32_t read_mie()
{
uint32_t value;
__asm__ volatile ("csrr %0, mie" : "=r"(value));
return value;
}
inline void write_mie(uint32_t value)
{
__asm__ volatile ("csrw mie, %0" : : "r"(value));
}
inline uint32_t read_mtvec()
{
uint32_t value;
__asm__ volatile ("csrr %0, mtvec" : "=r"(value));
return value;
}
inline void write_mtvec(uint32_t value)
{
__asm__ volatile ("csrw mtvec, %0" : : "r"(value));
}
...
I can set the interrupt enable bit in mstatus, but not in mie, eg :
write_mie(0xffffffff); // this apparently does nothing!
write_mstatus(0x8);
write_mtvec((uint32_t) irq_handler);
This compiles to :
100340: fff00793 li a5,-1
100344: 30479073 csrw mie,a5
100348: 00800793 li a5,8
10034c: 30079073 csrw mstatus,a5
100350: 001007b7 lui a5,0x100
100354: 0cc78793 addi a5,a5,204 # 1000cc <irq_handler>
100358: 30579073 csrw mtvec,a5
If I set the mie.mtie bit permanently in rtl/serv_csr.v the timer interrupts are enabled :
diff --git a/rtl/serv_csr.v b/rtl/serv_csr.v
index 897e272..f638ed8 100644
--- a/rtl/serv_csr.v
+++ b/rtl/serv_csr.v
@@ -77,7 +77,7 @@ module serv_csr
mstatus_mie <= csr_in;
if (i_mie_en & i_cnt7)
- mie_mtie <= csr_in;
+ mie_mtie <= 1; // csr_in;
mstatus <= i_cnt2 & mstatus_mie;
The system then runs as expected.
@DaveBerkeley have you tried running the assembly in this gist? It was broken in the way mentioned in description, but was resolved by my PR olofk/serv#31. @olofk implemented a different approach that should also fix this issue, as well as another one I found and mentioned in the PR. I have not validating the change that he made in master, but you might try my PR and we can get to the bottom of all of this.
Just tried your mpie branch. Still not working for my case.
I'm new to risc-v, so I may be missing something. I've been wrapping SERV with a SoC to allow XIP (execute in place) directly from Flash on the icebreaker.
https://github.com/DaveBerkeley/fpga/tree/master/serv
This is all working fine, except I can't enable the timer interrupt.
Oh excellent work! I can take a look later today. Do you have a full code listing where you use these functions I could look at?
https://github.com/DaveBerkeley/fpga/blob/master/serv/firmware.c
But I'm using my own timer hardware
https://github.com/DaveBerkeley/fpga/blob/master/serv/timer.v
I was embarrassed to find that it adds 500 LUTs to the usage (including the irq code in SERV). My timer has 2 x 64-registers and a 32-bit temp register.
If you compile with -O1
optimisation gcc does a great job of producing minimal code.
I'm also using a hardware uart for tx which you might like. It delays ack until the next byte can be sent, which removes the need for bit-banging or polling the uart for busy.
@olofk Here is the test that I did that shows how interrupts are not being restored by the MPIE bit in mstatus. How this manifests itself in the RTOS is that an
ecall
might be issued in a critical section such as when a new task is created. The MPIE bit is set to restore interrupts for things like the system tick.