04-25-2014 02:29 AM
It is often advantageous to be able to disable interrupts in a recursive way. For example if you add an external 16x16->32 bit multiplier on a few IO ports, you might want to disable interrupts while accessing it to prevent data corruption. This allows you to use the multiplier both in main loop and interrupt context.
multiply: DISABLE INTERRUPT ; if this sets carry to interrupt_enable OUTPUT s0, mult_A_lo OUTPUT s1, mult_A_hi OUTPUT s2, mult_B_lo OUTPUT s3, mult_B_hi INPUT s0, mult_res0 INPUT s1, mult_res1 INPUT s2, mult_res2 INPUT s3, mult_res3 RETURN NC ; then this can conditionally re-enable ENABLE INTERRUPT RETURN
If you have flags modifying instructions in between, you can save the carry in a register and restore it with the following code:
DISABLE INTERRUPT ; modify carry LOAD s7, 00 ; clear s7 ADDCY s7, 00 ; move carry into s7 ADD s0, 01 ; destroy old carry ADD s7, FF ; restore carry from s7 RETURN NC ENABLE INTERRUPT RETURN
In the current KCPSM6 implementation both DINT and EINT do not modify the flags. But with the following patch applied to kcpsm6.vhd both instructions will set the carry when interrupts were enabled and clear when the carry when interrupts were disabled before the instruction was handled. It will also change the zero flag, but I haven't looked at how exactly. I would consider it undefined. It adds one LUT which is only half used. It should have no impact on speed.
--- kcpsm6.vhd Thu Oct 04 13:33:46 2012 +++ kcpsm6_DINT_mod.vhd Fri Apr 25 10:43:48 2014 @@ -45,6 +45,10 @@ -- Correction to parity computation logic. -- Version 1.2 - 4th October 2012. -- Addition of WebTalk information. +-- Version DINT - 25th April 2014. Maarten Brock +-- Changed DINT, EINT to put old interrupt_enable +-- in carry flag (and invalidate zero flag?) to +-- enable recursive DINT use -- -- Ken Chapman -- Xilinx Ltd @@ -171,6 +175,7 @@ signal carry_lower_parity : std_logic; signal upper_parity : std_logic; signal parity : std_logic; +signal shadow_ena_carry : std_logic; signal shift_carry_value : std_logic; signal shift_carry : std_logic; signal carry_flag_value : std_logic; @@ -915,7 +920,7 @@ O6 => register_enable_type); register_enable_lut: LUT6_2 - generic map (INIT => X"C0CC0000A0AA0000") + generic map (INIT => X"C0CC0000AAAA0000") port map( I0 => flag_enable_type, I1 => register_enable_type, I2 => instruction(12), @@ -1087,11 +1092,21 @@ CI => carry_lower_parity, O => parity); + shadow_ena_carry_lut: LUT6 + generic map (INIT => X"00000000000000CA") + port map( I0 => shadow_carry_flag, + I1 => interrupt_enable, + I2 => instruction(12), + I3 => '0', + I4 => '0', + I5 => '0', + O => shadow_ena_carry); + shift_carry_lut: LUT6 generic map (INIT => X"FFFFAACCF0F0F0F0") port map( I0 => sx(0), I1 => sx(7), - I2 => shadow_carry_flag, + I2 => shadow_ena_carry, I3 => instruction(3), I4 => instruction(7), I5 => instruction(16),
I hope someone finds this useful,
04-30-2014 06:41 AM
Well you have certainly tickled my brain cells with this one! It took me a while to appreciate your motive but I think I understand that now (even though it was what you described in the first place!).
I can see what you have implemented but I can’t help thinking that your code could be written in a way that would be executed in a predictable way. For example, imagine I have a subroutine called ‘function’ that can be called from either the main program code or from within an interrupt service routine (ISR). It is important that interrupts are disabled when the main program calls ‘function’ because the ISR may also use ‘function’ and that could corrupt values. Likewise, it is important that interrupts remain disabled throughout the processing of the ISR.
DISABLE INTERRUPT ;within main program
function: <instruction> ;Only execute when interrupts disabled
ISR: CALL function
The overhead is 2 physical instructions; DISABLE INTERRUPT and ENABLE INTERRUPT. In terms of execution, those 2 instructions are only executed by the main program code. In your scheme (with a modified KCPSM6) there is an overhead of 3 physical instructions. All 3 instructions are executed by the main code (i.e. when interrupts are enabled) and 1 instruction is executed during an ISR. So that’s a bigger overhead in all ways. Ok, I’m prepared to believe there may be a special case but even if ‘function’ was reused more times we could imagine creating in intermediate sub routine like this…
function_DI: DISABLE INTERRUPT
By the way, SLA and SRA instructions are a quick and easy way to preserve and restore the contents of the carry flag using the least significant bit of a register.
05-01-2014 06:33 AM
Thanks for your reply. And indeed one 'SLA sX' is shorter than 'LOAD sX,0' and 'ADDCY sX,0' together. I'm sure there are many other (probably less efficient) ways of saving and restoring the carry. I just wanted to show the possibility.
Your solution of moving DISABLE INTERRUPT and ENABLE INTERRUPT away from the problematic non-atomic instructions works and may even be the most efficient at runtime. But it makes things hard for the programmer. He/she has to look at the complete program flow / graph to get things right. This means you cannot create reusable (library) functions or have to document this special requirement and hope the user adheres to it. In my opinion it's better to keep the solution close to the problem however. My solution helps when you need to recursively disable interrupts, be it from the ISR or from a normal function.
Maybe it's because I see the picoblaze as a generic processor instead of a programmable state machine that I don't care too much about getting out the very last cycle. I am more interested in getting it ready for use with a compiler where a bit of abstraction is a desirable feature.
I have also played with the idea to enhance EINT so it sets the enable flag to the carry value. I have even implemented it. But I considered the extra logic did not outweigh the extra RETURN NC (or JUMP NC) instruction.
And I also looked at getting the active bank on a REGBANK instruction, but that was too hard to implement for the current REGBANK opcode. Then again I would normally reserve bank 1 completely for the ISR and bank 0 for the main loop and then it is already handled by the stack automatically. This also means I have no use for the STAR instruction.
05-01-2014 09:25 AM
All understood and I can appreciate the angle you are coming from. For the interest of other forum readers I think this is a topic of a little more discussion anyway.
Since you view PicoBlaze as a ‘generic’ processor then one obvious solution is not to use interrupts at all. Let’s face it, interrupt are just not nice! Far better to dedicate a processor to the task it has to implement and let it get on with it. No need to task swap or worry about unpredictable execution. Ok, maybe I could excuse the use of an interrupt for a timeout but it really should be a very special cases rather than an inherent part of the fundamental operation? How can I possibly say such things? Well, it’s because PicoBlaze is implemented in an FPGA and isn’t a standalone processor that has to attempt to do everything. It really is possible and viable to use multiple PicoBlaze processors each dedicate to the task(s) that they need to implement. Indeed, one could be dedicated to the tasks normally associated with interrupt handling and that is what a lot of people do to offload that from a MicroBlaze.
As you know, PicoBlaze is surrounded by programmable hardware. You originally suggested an example in which PicoBlaze was connected to an external multiplier and you would not want an interrupt to occur whilst that multiplication was being used. So you could implement that multiplier interface in such a way that it recognises when PicoBlaze first writes to it and that is used to prevent an interrupt being applied to the INTERRUPT control pin. Once the product has been read back by PicoBlaze the path to the INTURRUPT pin is restored. In this way the software code doesn’t need to do anything to disable/enable interrupts internally. This can also be a useful when the interrupt is a pulse of a duration that might be missed whilst PicoBlaze isn’t looking. The hardware circuit can ‘latch’ the external pulse and then apply an interrupts as soon as it is cleared to do so.
Regarding STAR; even if you reserve bank ‘B’ for interrupt handling then I can still imagine a use for STAR instructions. I quite often find that my main program needs to know that in interrupt has been serviced. So within the ISR I use a STAR instruction to set a value into a register in bank ‘A’. At some point the main program tests the value in that register and knows if there if there is a follow-up action to be performed (e.g. new data captured by the ISR is now waiting to be processed).
05-01-2014 12:01 PM
Good point about the surrounding hardware that could inhibit interrupts for a while. Still I believe that knowing the previous state is a very welcome feature.
I can see your use of STAR in a small handwritten assembly program where it's easy to manage the register assignment. If one aims for compiled programs as I do however, I wouldn't want to reserve one register for this purpose on a permanent basis though and thereby limiting a register allocation algorithm. I guess my preference would be a flag in the scratchpad ram, though some I/O is also possible.
05-02-2014 06:13 AM
I think it is reasonably fair to say that PicoBlaze is pretty user friendly when it comes to writing assembly language. Ok, that assumes someone is accepting of a low level language in the first place; maybe not an unreasonable assumption when the majority of users are hardware engineers used to writing VHDL or Verilog. The relatively large number of registers that are all general purpose and the automatic built-in stack are just two examples that sit behind my claim.
In contrast, it has to be said that compilers and high level languages much prefer a more memory centric architecture of a RISC style. About 30 years ago I was assigned a project involving a small robot containing a Z80 which was considered to be of inadequate performance. To cut a long story short, I found that the real issue was a desperately inefficient compiler; it simply didn’t exploit the architecture and instruction set of the Z80 and it was at least 10x slower than a compiler that did. The decision has to be made, will a compiler be ‘taught’ to work with a processor architecture or does the architecture need to adapt to work with what compilers actually do?
We can more or less forget ‘technology’ because it is really ‘economics’ that becomes the principle driver. The final responsibility lies with the users of tools and devices to understand the capability of what they are using, how they are using it and how efficient is ‘good enough’. This applies to people that write assembler or high level languages; just in somewhat different ways.
Of course the beauty of an FPGA is that you can design whatever architecture you like. Maybe the occasional tweak to PicoBlaze will satisfy you but a clean sheet design would probably be a better approach. Ok, it will be bigger and slower but that is the ‘cost’ aspect coming into play sooner rather than later. Actually, lower clock speed can also cost a lot later when it becomes hard to interface with logic operating on a faster clock around it.
One interesting approach I’m surprised more people haven’t explored is the use of PicoBlaze as the heart of another processor. The PicoBlaze program becomes the micro-code of a processor that fetches and executes programs from another memory attached to peripherals that form such things as program counters and register files. Some people use the same architecture but change the microcode to adjust the instruction set in whatever way is more suitable for a particular compiler. Again, overall size is bigger than PicoBlaze and execution time takes an even bigger hit but at least all the logic works at the high clock rate we expect of a PicoBlaze design.