cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
vanmierlo
Mentor
Mentor
5,788 Views
Registered: ‎06-10-2008

Recursive DINT enhancement

Hi,

 

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,

Maarten Brock

0 Kudos
5 Replies
chapman
Xilinx Employee
Xilinx Employee
5,738 Views
Registered: ‎09-05-2007

Dear Maarten,

 

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
          CALL function

          ENABLE INTERRUPT

                       

 

function: <instruction>             ;Only execute when interrupts disabled

          <instruction>

          RETURN  

 

 

     ISR: CALL function

          RETURNI ENABLE 

 

 

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
             CALL function

             ENABLE INTERRUPT
             RETURN  

 

 

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.

 

Regards,

 

Ken Chapman
Principal Engineer, Xilinx UK
0 Kudos
vanmierlo
Mentor
Mentor
5,733 Views
Registered: ‎06-10-2008

Hello Ken,

 

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.

 

Maarten

0 Kudos
chapman
Xilinx Employee
Xilinx Employee
5,731 Views
Registered: ‎09-05-2007

Dear Maarten,

 

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

 

Regards,

Ken Chapman
Principal Engineer, Xilinx UK
0 Kudos
vanmierlo
Mentor
Mentor
5,728 Views
Registered: ‎06-10-2008

Hi Ken,

 

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.

 

Maarten

0 Kudos
chapman
Xilinx Employee
Xilinx Employee
5,720 Views
Registered: ‎09-05-2007

Dear Maarten,

 

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.     

 

Regards,

Ken Chapman
Principal Engineer, Xilinx UK
0 Kudos