SysTick handling in Cortex-M4 runtime

A bit of a STM32F429/Cortex-M4/Ada Runtime question, I am attempting to measure how long (in clock cycles) some procedure takes when running on the mentioned board by directly using the SysTick timer registers and am faced with a couple of issues.

More specifically, I am using the following procedure to capture the total cycles:

function Measure_Proc_Cycles(Iterations: Natural; 
                                Overflow: out Boolean) return Natural is
  Start_Time: Natural := 0;
  Stop_Time: Natural := 0;
begin
  STCSR_Reg.Enable := Off;
  STRVR_Reg.Value := 16#FFFFFF# - 1; -- set reload value to the maximum possible
  STCVR_Reg.Value := 0; -- set current value to zero (also clears the STCSR_Reg.Count_Flag)
  STCSR_Reg.Clk_Source := On; -- use processor clock (i.e. not an external reference one)
  STCSR_Reg.Enable := On; -- turn timer on
  STCSR_Reg.TickInt := Off; -- don't generate any SysTick interrupts
  
  loop
     exit when STCSR_Reg.Count_Flag = On;
  end loop;
       
  Start_Time := Natural(STCVR_Reg.Value);
  -- Start timer
  STCSR_Reg.Enable := On;
  
  Burn_Cycles(Iterations);

  -- Stop timer
  STCSR_Reg.Enable := Off;
  
  if STCSR_Reg.Count_Flag = On then
     Overflow := True;
  end if;
        
  Stop_Time := Natural(STCVR_Reg.Value);
  -- SysTick is a decrement counter so Start_Time > Stop_Time
  return Start_Time - Stop_Time;
   end Measure_Proc_Cycles;

with Burn_Cycles merely being:

procedure Burn_Cycles(Iterations: Natural) is
  Acc: Natural := 1;
 begin
    for I in 1 .. Iterations loop
       Acc := Acc + 1;
    end loop;
 end Burn_Cycles;

(with all SysTick registers appropriately mapped)

Firstly, my gut instinct tells me this is a bad idea as changing the SysTick Reload Value might interfere with the runtime and completely invalidate any timing assumptions made by it. Or would that only be relevant if there were any delay statements involved? Or would that also maybe interfere with the environment task which is running the code under test? I do however get a larger value every time I increase the number of iterations in Burn_Cycles.

The second issue I’m having is that if a subprogram takes more than 2**24 cycles to run (the SysTick reload value register is 24 bits and includes zero in the counting) then the only way to count its time is by enabling the triggering of the SysTick interrupt every time the counter counts down and then incrementing a counter in the handler (so, effectively the total running time is Counter_Value*(2**24) + cycles measured in the last counting). However, registering a handler for the SysTick interrupt traps instantly when the application starts with gdb reporting:

Program received signal SIGTRAP, Trace/breakpoint trap.
0x0800a782 in system.semihosting.put()

Is that because a handler is already registered for SysTick? (I believe the logic for this is in s-bbcppr.adb?)

However, registering a handler for an MCU-specific interrupt works fine. To prove that, I mapped the NVIC registers and then software-triggered the first non-Cortex interrupt (which based on the handler.s file corresponds to WWDG_Interrupt in the a-intnam.ads file) and had the handler increase a counter which I then printed in gdb.

Any input would be appreciated. - Thank you.

(In case that makes any difference, I’m using the runtime that comes under the 2021-arm-elf directory of the GNAT installation)

If you are using are run-time that provides tasking features (Ravenscar, Jorvik), then indeed you must not touch the systick registers. But If you are not using the tasking (ZFP, light), then no problem.

There might be other ways in this device to count time. Look at the other timers.

To handle the 24-bit SysTick counter overflow, you just have to keep the previous value somewhere:

   Count : UInt24 := 0;
   Prev_Count : UInt24 := 0;
   Overflows : Natural := 0;
begin

   loop
      Prev_Count := Count;
      Cout := STCVR_Reg.Value;

      --  If the new counter value is lower than the previous one,
      --  it means the counter overflowed.
      if Count < Prev_Count then
         Overflow := @ + 1;
         Prev_Count := 0;
      end if;
   end loop;

The number of cycles is therefore something like Count + 2**24 * Overflow.

1 Like

If you are running a system with tasking, then you can sometimes emulate what you need by using a small dummy task that delays for some amount of time (less than 24 bits of time in systick) and increments an atomic “period count” there. you can then read that and the sys tick register to get an augmented time:

type Sys_Tick_Counts is mod 2 ** 24;
type Tick_Type is record
     Periods : Unsigned_32    := 0;  -- NOTE:  make sure that the global this reads is atomic
     Ticks   : Sys_Tick_Ticks := 0;  -- The source from this will be atomic
end record;

function Get_Ticks return Tick_Type is
    Result : Tick_Type;
begin
    loop 
        Result.Periods := Your_Global_Atomic_Period_Counter;
        Result.Ticks   := Sys_Tick_Value;
    -- Double check the periods to catch any rollover during the 
    -- Sys Ticks value assignment
    exit when  Result.Periods := Your_Global_Atomic_Period_Counter;
    end loop;
    return Result;
end Get_Ticks;

After that you can use the periods count and ticks count to figure out some time values

Doing this does depend on your system requirements. Like how often you are guaranteed to get back to your task that delays and increments the period count. You need to ensure that either the task can’t be pushed past the 24bit value of the systick counter or otherwise be ok with it happening occasionally.

Thanks very much for your reply,

Ok great, thanks for confirming and exploring other timers sounds like a good idea.

Out of curiosity, can only a single handler be attached for an interrupt i.e. is there a one-to-one relationship?

Yeah, keeping the previous count and checking the range sounds like an interesting idea. I guess this logic would need to run in its own task and have the task which calls the subprogram under measurement tell it when to start looping and when to stop via some condition variable?

thanks very much for your reply,

Ok, that’s an interesting idea. I guess the way Get_Ticks would be used is something along the lines of:

  • Start timer
  • Start periodic task
  • Call subprogram under measurement
  • Stop timer
  • Stop periodic task
  • Call Get_Ticks and get result (that should be quick as the condition would evaluate to true immediately)

It does feel like tapping into the SysTick interrupt would be the most straightforward solution, if I’m honest, but that’s probably not possible for runtime implication reasons.

No it would more be:
Call Get_Ticks to get the current value
Run your subprogram
Call Get_Ticks again to get the time after
Do some sort of subraction of the two sets of values to see how much time it took

the periodic would just run indefinitely in the background so you could poll it for values anytime.

of course…the starting ticks need to be captured first! Thank you.