What happened to GETINT?
RossH
Posts: 5,660
in Propeller 2
Did I miss something? What happened to the GETINT instruction? It is mentioned in the P2 documentation, but it seems to no longer exist. PNUT doesn't know about it.
Is there a replacement instruction to allow us to get the interrupt status?
Is there a replacement instruction to allow us to get the interrupt status?

Comments
v32 EEEE 1101011 000 DDDDDDDDD 000110100 GETPTR D EEEE 1101011 CZ0 DDDDDDDDD 000110101 * GETBRK D {WC/WZ/WCZ} EEEE 1101011 00L DDDDDDDDD 000110101 * COGBRK D EEEE 1101011 00L DDDDDDDDD 000110110 * BRK D/# EEEE 1101011 00L DDDDDDDDD 000110111 SETLUTS D/#I have temporarily removed it.
Attached is my first attempt at adding C interrupts. As usual, compiled for the P2 EVAL board, serial interface, 230400 baud.
I have included my proposed interrupt header file (which still mentions "getint") ...
#ifndef __PROPELLER_INTERRUPTS__H #define __PROPELLER_INTERRUPTS__H /* * define the type of an interrupt function. Such a function must have no * arguments, and it must return - but it cannot return a value. It must be * set as an interrupt using one of the _set_int_x functions. */ typedef void (* _interrupt)(void); /* * define the size (in LONGs) of an interrupt block - the block is used to * save the registers of the executing C program or thread during the servicing * of the interrupt. The block should be long aligned. */ #define INT_BLOCK_SIZE 30 /* * define the minimum size (in LONGs) of the interrupt stack - the top of this * space is used for the interrupt block, and the rest is used as stack space. */ #define MIN_INT_STACK_SIZE (INT_BLOCK_SIZE + 10) /* * define the possible interrupt sources */ enum int_src { OFF=0, // interrupts off, or poll/wait for any interrupt 1/2/3 CT1, // CT-passed-CT1, established by _set_ct1 CT2, // CT-passed-CT2, established by _set_ct2 CT3, // CT-passed-CT3, established by _set_ct3 SE1, // SE1 event occurred, established by _set_se1 SE2, // SE2 event occurred, established by _set_se2 SE3, // SE3 event occurred, established by _set_se3 SE4, // SE4 event occurred, established by _set_se4 PAT, // Pin pattern match or mismatch occurred, established by _set_pat FBW, // Hub RAM FIFO interface wrapped and reloaded XMT, // Streamer is ready for another command XFI, // Streamer ran out of commands XRO, // Streamer NCO rolled over XRL, // Streamer read location $1FF of lookup RAM ATN, // Attention requested by other cog(s) QMT // CORDIC read but no results available }; /* * set up initial counter interrupt source (after this, use _add_CTx) */ void _set_CT1(unsigned long CT1); void _set_CT2(unsigned long CT2); void _set_CT3(unsigned long CT3); /* * add to counter interrupt source (initially set up by _set_CTx) */ void _add_CT1(unsigned long CT1); void _add_CT2(unsigned long CT2); void _add_CT3(unsigned long CT3); /* * set up pattern interrupt source (A = 0, B = 1, EQ = 0, NE = 1) */ void _set_PAT(int A_or_B, int EQ_OR_NE, unsigned long MASK, unsigned long MATCH); /* * set up configurable interrupt sources */ void _set_SE1(unsigned long SE1); void _set_SE2(unsigned long SE2); void _set_SE3(unsigned long SE3); /* * nix (cancel) an interrupt */ void _nix_int_1(); void _nix_int_2(); void _nix_int_3(); /* * simulate (trigger) an interrupt */ void _sim_int_1(); void _sim_int_2(); void _sim_int_3(); /* * set up (establish) an interrupt service function */ void _set_int_1(enum int_src SRC, _interrupt SVC, void *stack); void _set_int_2(enum int_src SRC, _interrupt SVC, void *stack); void _set_int_3(enum int_src SRC, _interrupt SVC, void *stack); /* * clear (remove) an interrupt service function */ void _clr_int_1(); void _clr_int_2(); void _clr_int_3(); /* * allow or stall all interrupts */ void _allow_int(); void _stall_int(); /* * get interrupt status */ unsigned long _get_int(); /* * poll for interrupt XXX * returns 0 if event has not occured, 1 if event has occurred */ unsigned long _poll_ANY(); unsigned long _poll_CT1(); unsigned long _poll_CT2(); unsigned long _poll_CT3(); unsigned long _poll_SE1(); unsigned long _poll_SE2(); unsigned long _poll_SE3(); unsigned long _poll_SE4(); unsigned long _poll_PAT(); unsigned long _poll_FBW(); unsigned long _poll_XMT(); unsigned long _poll_XFI(); unsigned long _poll_XRO(); unsigned long _poll_XRL(); unsigned long _poll_ATN(); unsigned long _poll_QMT(); /* * wait for interrupt XXX * returns with 0 if timeout occurred, or 1 if event occurred */ unsigned long _wait_ANY(unsigned long timeout); unsigned long _wait_CT1(unsigned long timeout); unsigned long _wait_CT2(unsigned long timeout); unsigned long _wait_CT3(unsigned long timeout); unsigned long _wait_SE1(unsigned long timeout); unsigned long _wait_SE2(unsigned long timeout); unsigned long _wait_SE3(unsigned long timeout); unsigned long _wait_SE4(unsigned long timeout); unsigned long _wait_PAT(unsigned long timeout); unsigned long _wait_FBW(unsigned long timeout); unsigned long _wait_XMT(unsigned long timeout); unsigned long _wait_XFI(unsigned long timeout); unsigned long _wait_XRO(unsigned long timeout); unsigned long _wait_XRL(unsigned long timeout); unsigned long _wait_ATN(unsigned long timeout); /* * request the attention of all the cogs specified in 'cogs' bits 0 .. 15 */ _cog_ATN(unsigned long cogs); #endifgetbrk val wc 'get skip call depth getbrk val wz 'get skip pattern getbrk val wcz 'get interrupt statusThere are several discussions about GETBRK, but I can't find anything definitive about what the bits mean. Are they the same as GETINT?
https://forums.parallax.com/discussion/comment/1434024/#Comment_1434024
Hmmm. Not sure that helps. And I thought C code was impenetrable!
Here's my interpretation of the last one:
: { 1'b0, // c getbrk with wz ~|skipb, // z skipb[31:0] }; // rC = 0Z = 1 if no skip bits set.
result(D register) = skip bits
EDIT: Inverted Z description.
EDIT2: Corrected C as well. I really don't know my verilog.
Edit: Typo & correction.
{ brk_isr ? !brk_pass[1] : int_stall, // c getbrk with wcz (only show brk_pass in brk isr) hubs, // z brk_code & {8{brk_isr}}, // r (only show brk_code in brk isr) !brk_pass[1] && brk_isr, // (only show brk_pass in brk isr) csc_active, xfr_active, mem_mode, int_select[3:1], int_state[3:1], int_stall, hubs }Yikes!
My verilog is a bit rusty, but looking at it again I read the 32 bits as
Yes, I thought that too - until I realized it meant I had to have two copies of all the library code, and two times as many kernels (and there are 12 of the little buggers already!). One set that could be used in an interrupt-driven system, and one that could not
Have you considered having interrupts use the same stack as regular code? It would simplify things a lot, and be more like other processors.
I was actually mostly using SKIPF in the kernel (i.e. in cog execution mode). But even so, the benefits were becoming more and more doubtful in the cases I was using it.
I don't really like the idea that all stacks have to allow for the possibility of arbitrary interrupt code, when they have no idea how much stack space might be required (I'm thinking primarily of threads here).
The P2 is a microcontroller with limited RAM, not a general-purpose CPU with effectively infinite RAM. Everything needs to be constrained to fixed sizes.
The int_select and int_state registers are not single bits though.
int_select are 4 bits each and int_state are 2 its each.
These are defined elsewhere and not shown in the Verilog snippet.
In the Verilog were referring to we are incorporating elements of these registers.
One day, someone will figure out what the return value actually means and it might prove useful.
LEDs blink if state indicates ISR executing.
'GETBRK int_state bits test P2_ES Eval board dat org getct pa addct1 pa,##20_000_000 addct2 pa,##21_000_000 addct3 pa,##22_000_000 mov ijmp1,#isr1 setint1 #1 'ct1 interrupt mov ijmp2,#isr2 setint2 #2 'ct2 interrupt mov ijmp3,#isr3 setint3 #3 'ct3 interrupt jmp #$ { %0x = waiting for interrupt event %10 = waiting for interrupt branch %11 = executing interrupt service routine } isr1 addct1 pa,##10_000_000 getbrk pb wcz shr pb,#2 and pb,#%11 cmp pb,#%11 wz 'isr1 executing? if_e drvnot #56 reti1 isr2 addct2 pa,##10_000_000 getbrk pb wcz shr pb,#4 and pb,#%11 cmp pb,#%11 wz 'isr2 executing? if_e drvnot #57 reti2 isr3 addct3 pa,##10_000_000 getbrk pb wcz shr pb,#6 and pb,#%11 cmp pb,#%11 wz 'isr3 executing? if_e drvnot #58 reti3Thanks. You can see from this code why the overhead of trying to figure out if you are in an ISR - and you have to check all three interrupts to be sure - makes it not worth the effort in many SKIPF cases.
wire [33:0] getbrk_czr = // GETBRK with WCZ i[wc] ? i[wz] ? { brk_isr ? !brk_pass[1] // c = first-brk if in brk isr : int_stall, // c = STALLI mode if not in brk isr hubs, // z = cog launched in hub-exec mode brk_code & {8{brk_isr}}, // d[31:24] = BRK {#}d code (only shown in brk isr) !brk_pass[1] && brk_isr, // d[23] = first-brk (only shown in brk isr) csc_active, // d[22] = colorspace converter active xfr_active, // d[21] = streamer active mem_mode, // d[20] = WRFAST mode, as opposed to RDFAST mode int_select[3], // d[19:16] = int3 source select (qmt_event..int_event) int_select[2], // d[15:12] = int2 source select int_select[1], // d[11:8] = int1 source select int_state[3], // d[7:6] = int3 status (%00=idle, %10=pending, %11=isr active) int_state[2], // d[5:4] = int2 status int_state[1], // d[3:2] = int1 status int_stall, // d[1] = STALLI mode, as opposed to ALLOWI mode hubs } // d[0] = cog launched in hub-exec mode // GETBRK with WC : { skipb[0], // c = next skip bit in process 1'b0, // z ignored skipk[3:0], // d[31:28] = skip call depth (skip suspended when not 0) skipm, // d[27] = SKIP mode (as opposed to SKIPF/EXECF/xbyte mode) lut_share, // d[26] = LUT sharing enabled stk_xbyte, // d[25] = top_of_stack[19:0] = $001FF xbytet[8:0], // d[24:16] = current xbyte mode (set by '_RET_ SETQ{2}' with top of stack = $001FF trap[15:0] } // d[15:0] = event trap bits (qmt_event..int_event) // GETBRK with WZ : { 1'b0, // c ignored ~|skipb, // z = no skip bits in process skipb[31:0] }; // d[31:0] = skip bits in process (lsb first)Sorry I didn't post this a few days earlier.
I want to go back to this particular point, because I now have interrupts working in conjunction with multi-threading. If the interrupts had to use the same stack space as the thread, then a program could require up to three times as much stack space.
For an actual example, consider the attached program, which combines threads and interrupts (as usual, compiled for the P2 EVAL board, serial interface 230400 baud).
Each thread has a stack of ~100 longs, as does each interrupt - they need a reasonable stack size because they all call various library functions (e.g. to perform I/O).
If the interrupts used the same stack space as the executing thread that they happen to interrupt, then each thread would have to have a stack of 300 longs - (100 longs for the thread itself, another 100 in case interrupt 2 goes off, and another 100 in case interrupt 1 goes off, because interrupt 1 can interrupt interrupt 2). Since there are 100 threads executing, this program would use another 200*100 longs, or around 80kb of additional Hub RAM dedicated to stack space. Of course, this is a fairly pathological example!
/***************************************************************************\ * * * Thread and Interrupt Demo * * * * Demonstrates many threads executing concurrently on a single cog, * * in conjunction with interrupts going off periodically. * * * * NOTE: This program will only work on the P2, since the P1 does not * * support interrupts * * * \***************************************************************************/ /* * Catalina interrupt support */ #include <catalina_interrupts.h> /* * Catalina multi-threading support */ #include <catalina_threads.h> /* * Catalina HMI functions (unlike stdio functions, these * functions can be used in interrupt functions) */ #include <catalina_hmi.h> /* * include some useful multi-threading utility functions (but note * that we cannot call any thread functions while in an interrupt) */ #include <thread_utilities.h> /* * define how many threads we want */ #define THREAD_COUNT 100 /* * define the stack size each thread needs (since this number depends on the * function executed by the thread, the stack size has to be established by * trial and error): */ #define STACK_SIZE (MIN_THREAD_STACK_SIZE + 100) /* * define the stack size each interrupt needs (since this number depends * on the function executed by the interrupt, the stack size has to be * established by trial and error): */ #define INT_STACK_SIZE (MIN_INT_STACK_SIZE + 100) /* * define the number of thread locks we want (we only need one) */ #define NUM_LOCKS 1 /* * define the pins that we will toggle in the interrupt functions */ #if defined (__CATALINA_P2_EVAL) #define PIN_SHIFT (57-1-32) // use pin 57,58 (and dirb, outb) on P2_EVAL #define _dir _dirb #define _out _outb #else #define PIN_SHIFT 0 // use pin 1,2 (and dira, outa) on other platforms #define _dir _dira #define _out _outa #endif /* * define some global variables to be used by the interrupt functions */ static unsigned long mask_1 = 1<<(PIN_SHIFT + 0); static unsigned long on_off_1 = 1<<(PIN_SHIFT + 0); static unsigned long mask_2 = 1<<(PIN_SHIFT + 1); static unsigned long on_off_2 = 1<<(PIN_SHIFT + 1); /* * define some global variables to be used by the thread functions */ static int ping; /* * a pool of thread locks - note that the pool must be 5 bytes larger than * the actual number of locks required (MIN_THREAD_POOL_SIZE = 5) */ static char pool[MIN_THREAD_POOL_SIZE + NUM_LOCKS]; /* * a lock allocated from the pool - required to protect the thread HMI * plugin functions */ static int lock; /* * thread : this function can be executed as a thread. Multiple instances * of this function can be started using the _thread_start function. */ int thread(int me, char *not_used[]) { while (1) { if (ping == me) { // print our id _thread_printf(pool, lock, "%d ", (unsigned)me); ping = 0; } else { // nothing to do, so yield _thread_yield(); } } return 0; } /* * interrupt_x : these functions can serve as interrupt service routines. * Such functions must have no arguments, and must return - but * cannot return a value. They must be set as an interrupt * using one of the _set_int_x functions. */ void interrupt_1(void) { // we are a counter interrupt, so add to the counter for next time _add_CT1(100000000); // now toggle our LED _out(mask_1, (on_off_1 ^= mask_1)); // just to demonstrate that we have full C functionality, do some output // (note 1: we would not usually do this in an interrupt routine!!!) // (note 2: we cannot call ANY thread operations while in an interrupt // routine, and so instead of using _thread_printf we use // t_printf - but this may sometimes result in garbled output // if an interrupt occurs when a thread is also performing I/O) t_printf(" <<Interrupt 1>> "); } void interrupt_2(void) { // we are a counter interrupt, so add to the counter for next time _add_CT2(300000000); // now toggle our LED _out(mask_2, (on_off_2 ^= mask_2)); // just to demonstrate that we have full C functionality, do some output // (note 1: we would not usually do this in an interrupt routine!!!) // (note 2: we cannot call ANY thread operations while in an interrupt // routine, and so instead of using _thread_printf we use // t_printf - but this may sometimes result in garbled output // if an interrupt occurs when a thread is also performing I/O) t_printf(" <<Interrupt 2>> "); } /* * main : Start up to THREAD_COUNT threads, then ping each one in turn. * Also, set up two timer interrupts that use interrupts 1 & 2 * (note that a program that uses threads cannot use interrupt 3). */ void main(void) { int i = 0; void *thread_id; // declare some space that will be used as stack by the threads unsigned long stacks[STACK_SIZE * THREAD_COUNT]; // declare some space that will be used as stack by the interrupts unsigned long int_stack[INT_STACK_SIZE * 2]; // assign a lock to avoid context switch contention _thread_set_lock(_locknew()); // initialize a pool of thread locks (we need only 1 lock) _thread_init_lock_pool (pool, NUM_LOCKS, _locknew()); // assign a thread lock to avoid HMI plugin contention lock = _thread_locknew(pool); _thread_printf(pool, lock, "Press a key to start\n"); k_wait(); // start THREAD_COUNT instances of our thread function. Note that we // need to point to the TOP of the stack space reserved for each thread for (i = 1; i <= THREAD_COUNT; i++) { thread_id = _thread_start(&thread, &stacks[STACK_SIZE*i], i, NULL); _thread_printf(pool, lock, "thread %d ", i); if (thread_id == (void *)0) { _thread_printf(pool, lock, " failed to start\n"); while (1) { }; } else { _thread_printf(pool, lock, " started, id = %d\n", (unsigned)thread_id); } } // the interrupt service routines toggle their LEDs on // interrupt - set up the direction and initial value _dir(mask_1 | mask_2, mask_1 | mask_2); _out(mask_1 | mask_2, on_off_1 | on_off_2); // these are counter interrupts so set up the initial counters _set_CT1(100000000); _set_CT2(300000000); // set up our interrupt service routines - note that we need to point // to the TOP of the stack space we have reserved for each interrupt // note: we cannot use interupt 3 in a multi-threaded program!!! _set_int_1(CT1, &interrupt_1, &int_stack[INT_STACK_SIZE * 1]); _set_int_2(CT2, &interrupt_2, &int_stack[INT_STACK_SIZE * 2]); // now loop forever, pinging each thread in turn. Periodically, an // interrupt will occur ... while (1) { _thread_printf(pool, lock, "\n\nPinging all threads\n"); for (i = 1; i <= THREAD_COUNT; i++) { _thread_printf(pool, lock, "%d:", i); // ping the thread ping = i; // wait till thread responds while (ping) { // nothing to do, so yield _thread_yield(); }; } // slow things down enough to read the messages for (i = 0; i < 100; i++) { _thread_yield(); } } }The attached program is compiled in Native mode, but interrupts now also work in Compact and LMM execution modes.