An Issue with high level interrupt

opcode_x64
Posts: 47
Joined: Sun Jan 13, 2019 5:39 pm

@ESP_IGGR: High-Level-Interrupt Routine Issue (Code included!)

Postby opcode_x64 » Sat Apr 18, 2020 9:41 pm

Hello ESP_IGGR,

Main Thread: https://esp32.com/viewtopic.php?f=13&t=15213

I cannot find the problem, so I will post my complete assembly interrupt service routine as well as a minimal main.c file. Before posting the codes, I want to explain the setup/idea.

My aim is to communicate with an external ADC (24-Bit resolution, SPI) via an ESP32. For SPI communication I am using simple bit banging method. The ADC got four channels and outputs the samples of all four channels in one bit stream, means: the first 24 bits are channel 1, the second 24 bits are channel 2 etc... To get all all channels there are 96 (24*4) serial's clocks (SCLK) needed. Making the data transfer of the samples from ESP32 to PC easy, I decided to "serialize" the samples in a ADCBuffer:

Code: Select all

ADCBUFFER[0]=Sample1_Channel1
ADCBUFFER[1]=Sample1_Channel2
ADCBUFFER[2]=Sample1_Channel3
ADCBUFFER[3]=Sample1_Channel4
ADCBUFFER[4]=Sample2_Channel1
ADCBUFFER[5]=Sample2_Channel2
ADCBUFFER[6]=Sample2_Channel3
ADCBUFFER[7]=Sample2_Channel4
.
.
.
.
ADCBUFFER[N]=SampleN_Channel4
with N=NumberOfSamplesPerChannel*4
For this, I am using two for loops: the outer "Channel"-Loop and the inner "Bit-Banging"-Loop. With the help of "simple" index arithmetics, the minimal C version of this would look like:

Code: Select all

  iADCBUFFER_OFFSET = SampleCounter*nCh;
  for(iCh=0; iCh<nCh; iCh++){
    for(iBit=23; iBit>=0; iBit--){ 
      GPIO.out_w1tc = (1 << SPI_SCLK_PIN);
      GPIO.out_w1ts = (1 << SPI_SCLK_PIN);
      ADCBIT = (GPIO.in >> SPI_DOUT_PIN) & 0x1;  
      Sample = Sample | (ADCBIT<<iBit)
      ADCBUFFER[iCh + iADCBUFFER_OFFSET]= Sample;
    Sample=0;
  }
  SampleCounter++;
  if(SampleCounter >= nSamplesPerCh){
    GPIO.out_w1ts = (1 << SPI_SCLK_PIN);
    SampleCounter=0;
  }
Now to give the ESP32 time to send the data, I implemented something, which I called a "flip-flop"-Buffer. Instead of using one ADCBUFFER, I am using two of them and switching between them by using pointer arithmetics. All mentioned stuff here were evaluated already in C by using "normal" interrupt and everything is working fine. The goal is now to manage this by high level interrupt to achieve higher sample rates. The pointer arithmetic to serialize samples/data with the mentioned "flip-flop"-Buffer in assembly version was also evaluated within "minimal" tests and console print outs for validation and also here everything worked, so the pointer arithmetic in the attached assembler high level interrupt routine was tested with success.

In the following I will post the minimal main.c and the assembly high level interrupt routine. Especially in the assembler file I tried to explain by comments as much as possible to make understanding easier...

What is working?:
- the high level interrupt is raising and filling the data, but the transferred data (done by tcp connection on core0) contains error. For test purposes I am filling in the assembler high level interrupt routine the sample to be stored on the buffers with a constant value, in case of 24 Bit ADC I am overwriting the sample with 0xFFFFFF. One would now expect that the data received via tcp should be a constant value of 0xFFFFFF but this is not the case....so this means the error is not due to SPI communication. Most of the received data contains the expected value 0xFFFFFF but also it became another value (error)...and this happens randomly. I just edit my code in way that I can switch by a "#define..." between "High Level Interrupt" Version and "Normal Level Interrupt" Version (actually the only thing what is changing is the allocation of the interrupt :D) and with "Normal Level Interrupt" the received data looks fine. Maybe the error is due to "wrong" order of memory access ? I am really not sure about that and I hope than you or another user can help me.

Now the codes:

mimimal version of main.c

Code: Select all

#define CORE_ID_HIGH_LEVEL_INT_HANDLER            1
#define CORE_ID_ADC_BUFFER_SEND                   0
#define nSamplesPerCh                           256
#define nCh                                       4
#define ADCBUFFER_SIZE          4*nSamplesPerCh*nCh

volatile int ADCBUFFER_0[nSamplesPerCh*nCh];
volatile int ADCBUFFER_1[nSamplesPerCh*nCh];
volatile int *Bufs[2];                         
volatile int BufferSelector = 0;
volatile int SampleCounter = 0;
volatile int prevBufferSelector = 0;

/* the adcbuffer send task is running on core 0 */
void task_send_adcbuffer(void * pvParameters){
    while(1){
        if(BufferSelector!=prevBufferSelector){ 
            // BufferSelector is right shifted by 2, because in high-level interrupt handler in assembly
            // is switching the BufferSelector between 0 and 4 (byte addressing!)
            // bit ight shifting by 2 bits makes from 0 -> 0 and from 4 -> 1, so this is fine then for C
            int ret = write(sock, Bufs[(prevBufferSelector>>2)],ADCBUFFER_SIZE ); 
            prevBufferSelector = (BufferSelector>>2);
        }
        vTaskDelay(1); 
    }
} 

void task_config_high_level_interrupt(void * arg){
    gpio_config_t interrupt_pin={
        .intr_type=GPIO_INTR_NEGEDGE,
        .pin_bit_mask=(1ULL<<GPIO_NUM_35),
        .mode=GPIO_MODE_INPUT,
        .pull_up_en=GPIO_PULLUP_DISABLE,
        .pull_down_en=GPIO_PULLDOWN_DISABLE,
    };
    ESP_ERROR_CHECK(gpio_config(&interrupt_pin));

    ESP_INTR_DISABLE(31);
    intr_matrix_set(CORE_ID_HIGH_LEVEL_INT_HANDLER, ETS_GPIO_INTR_SOURCE, 31);
    ESP_INTR_ENABLE(31);
    while(1){
        vTaskDelay(1);
  }
}
void app_main(){
    //.....
     Bufs[0]=ADCBUFFER_0;                     
     Bufs[1]=ADCBUFFER_1;

    //.....
    while(1){
        vTaskDelay(1);
    }
}
Complete High-Level-Interrupt Assembler Routine

Code: Select all

#include <xtensa/coreasm.h>
#include <xtensa/corebits.h>
#include <xtensa/config/system.h>
#include "freertos/xtensa_context.h"
#include "esp_panic.h"
#include "sdkconfig.h"
#include "soc/soc.h"
#include "soc/gpio_reg.h"
#include "soc/dport_reg.h"

#define SPI_SCLK_PIN_BITMASK                (1<<14)    // GPIO14 for SPI SCLK
#define DRDY_INT_PIN_BITMASK                 (1<<3)    // GPIO35 as interrupt pin
#define SPI_DOUT_PIN                             12    // GPIO12 for SPI DATA (note: needed for extui later, so bitmask just value)
#define nSamplesPerCh                          1024    // Numbers of Samples per Channel to aqcuire

#define iCh                                      a4    // reserve a4 as loop counter for the "Channel"-Loop
#define iBit                                     a5    // reserve a5 as loop counter for the "24-Bit-Banging"-Loop

#define aSampleCounter                           a6    // reserve a6 as memory address of volatile variable "SampleCounter" also used in main.c
#define vSample                                  a7    // reserve a7 for the sample value which is constructed by Sample = Sample | (1<<iBit)
#define aADCBUF                                  a8    // reserve a8 as memory address of the ADC Buffers
#define aVar                                     a9    // reserve a9 as "free" variable for addresses
#define vVar                                    a10    // reserve a10 as "free" variable for values
#define temp                                    a11    // reserve a11 as temp variable
#define aGPIO_IN_REG                            a12    // reserve a12 as memory address of GPIO_IN_REG          (used for faster programm flow)
#define vSPI_SCLK_PIN_BITMASK                   a13    // reserve a13 as value of GPIO14 BitMask                (used for faster programm flow)               
#define aGPIO_OUT_W1TS_REG                      a14    // reserve a14 as memory address of GPIO_OUT_W1TS_REG    (used for faster programm flow)
#define aGPIO_OUT_W1TC_REG                      a15    // reserve a15 as memory address of GPIO_OUT_W1TC_REG    (used for faster programm flow)


#define L5_INTR_STACK_SIZE                              48  // size of the level 5 interrupt stack for saving/restoring of all used Address Registers

#define L5_INTR_iCh_OFFSET                               0  // a4 will be kept at stack address 0
#define L5_INTR_iBit_OFFSET                              4  // a5 will be kept at stack address 4
#define L5_INTR_aSampleCounter_OFFSET                    8  // a6 will be kept at stack address 8
#define L5_INTR_vSample_OFFSET                          12  // ...
#define L5_INTR_aADCBUF_OFFSET                          16  // ...
#define L5_INTR_aVar_OFFSET                             20  // ...
#define L5_INTR_vVar_OFFSET                             24  // ...
#define L5_INTR_temp_OFFSET                             28  // ...
#define L5_INTR_aGPIO_IN_REG_OFFSET                     32  // ...
#define L5_INTR_vSPI_SCLK_PIN_BITMASK_OFFSET            36  // ...
#define L5_INTR_aGPIO_OUT_W1TS_REG_OFFSET               40  // ...
#define L5_INTR_aGPIO_OUT_W1TC_REG_OFFSET               44  // a15 will be kept at stack address 44


.data
_l5_intr_stack:
.space      L5_INTR_STACK_SIZE                              // allocate the desired stack of size 48 bytes for saving/restoring of all used Address Registers

.section .iram1,"ax"
 .global     xt_highint5
 .type       xt_highint5,@function
 .align      4
 
xt_highint5:
// save all used address registers, in this case: Address Registers (AR): a4 to a15
movi a0, _l5_intr_stack                                                     // put the memory address (=begin of the stack) to AR a0
s32i iCh,                           a0, L5_INTR_iCh_OFFSET                  // store the content of a4 (=iCh) to L5 stack with offset 0
s32i iBit,                          a0, L5_INTR_iBit_OFFSET                 // store the content of a5 (=iBit) to L5 stack with offset 4
s32i aSampleCounter,                a0, L5_INTR_aSampleCounter_OFFSET       // ...
s32i vSample,                       a0, L5_INTR_vSample_OFFSET              // ...
s32i aADCBUF,                       a0, L5_INTR_aADCBUF_OFFSET              // ...
s32i aVar,                          a0, L5_INTR_aVar_OFFSET                 // ...
s32i vVar,                          a0, L5_INTR_vVar_OFFSET                 // ... 
s32i temp,                          a0, L5_INTR_temp_OFFSET                 // ...
s32i aGPIO_IN_REG,                  a0, L5_INTR_aGPIO_IN_REG_OFFSET         // ...
s32i vSPI_SCLK_PIN_BITMASK, a0, L5_INTR_vSPI_SCLK_PIN_BITMASK_OFFSET        // ...
s32i aGPIO_OUT_W1TS_REG,            a0, L5_INTR_aGPIO_OUT_W1TS_REG_OFFSET   // ...
s32i aGPIO_OUT_W1TC_REG,            a0, L5_INTR_aGPIO_OUT_W1TC_REG_OFFSET   // store the content of a15 (=aGPIO_OUT_W1TC_REG) to L5 stack with offset 44

// cleare the interrupt status of GPIO35(used interrupt pint!)
movi aVar, GPIO_STATUS1_W1TC_REG
movi temp,  DRDY_INT_PIN_BITMASK
s32i temp, aVar, 0
//----
                                                         // AR = Address Register
movi    aSampleCounter, SampleCounter                    // put the memory address of the volatile global integer variable "SampleCounter" into aSampleCounter (AR: a6)
movi    aGPIO_OUT_W1TS_REG, GPIO_OUT_W1TS_REG            // put the memory address of GPIO_OUT_W1TS_REG into aGPIO_OUT_W1TS_REG (AR: a14)
movi    aGPIO_OUT_W1TC_REG, GPIO_OUT_W1TC_REG            // put the memory address of GPIO_OUT_W1TS_REG into aGPIO_OUT_W1TC_REG (AR: a15)
movi    vSPI_SCLK_PIN_BITMASK, SPI_SCLK_PIN_BITMASK      // put the GPIO14 bitmask into vSPI_SCLK_PIN_BITMASK (AR: a13)
movi    aGPIO_IN_REG, GPIO_IN_REG                        // put the memory address of GPIO_IN_REG into aGPIO_IN_REG ()



// the following 8 assembly line are doing "aADCBUF = Bufs[BufferSelector];" where Bufs is a global 
// pointer array "volatile int *Bufs[2]" pointing to two buffers which store the samples of the 
// ADC called ADCBUFFER_0[N] and ADCBUFFER_1[N], with desired size of the buffer N.  
// the "initial" assignment of the two pointers to the ADC buffers are done in app_main(): 
// Bufs[0] = ADCBUFFER_0;
// Bufs[1] = ADCBUFFER_1;
movi    aVar, BufferSelector                             // put the memory address of volatile global integer vatriable  "BufferSelector" into aVar (AR: a9)
l32i    vVar, aVar, 0                                    // load the content of "BufferSelector to vVar (AR: a10)
memw                                                     // BufferSelector is volatile so use memw (refer to xtensa isa)

movi    aVar, Bufs                                       // put the memory address of volatile global pointer array "Bufs" into aVar (AR: a9)
add.n   aVar, aVar, vVar                                 // add to the memory address of Bufs as offset the value of BufferSelector
memw                                                     // Bufs is volatile so use memw (not sure if needed also for add.n operation?!)
l32i    aADCBUF, aVar, 0                                 // load the content (array entry 0 or 1, depends on BufferSelector Value) to aADCBUF (AR: a8)
memw                                                     // Bufs is volatile so use memw
//----


// the follwing 8 assembly lines are doing "*(aADCBUF+SampleCounter*4)"
// multiplication by 4 is done, cause only 4 adc channels are used.
l32i temp, aSampleCounter, 0                             // load the content of the SampleCounter into temp
memw                                                     // SampleCounter is volatile so use memw
slli temp, temp, 4                                       // multiply SampleCounter by 4 using bit shift logical immediate
memw                                                     // to be sure, use memw ?!
add.n aADCBUF, aADCBUF, temp                             // increase the pointer, which shows to Bufs[BufferSelector], by "SampleCounter*4"
memw                                                     // again use mewm...
//----

movi    iCh, 4                      // put 4 into the "Channel"-Loop counter, since in this case 4 adc channels used
loop_Channel:
    movi    iBit, 24                // put 24 into the "Bit-Banging"-Loop counter, since it is a 24-bit adc
    loop_Bit:
        s32i    vSPI_SCLK_PIN_BITMASK, aGPIO_OUT_W1TC_REG, 0          // set GPIO14, used as SPI SCLK, to low
        nop                                                           // wait 4.2ns@fCPU=240MHz
        s32i    vSPI_SCLK_PIN_BITMASK, aGPIO_OUT_W1TS_REG, 0          // set GPIO14, used as SPI SCLK, to high
        nop                                                           // wait 4.2ns@fCPU=240MHz
        l32i    temp, aGPIO_IN_REG, 0                                 // load the GPIO levels of 0-31
        extui   temp, temp, SPI_DOUT_PIN, 1                           // extract only the level of GPIO12, used as SPI DATA
        addi.n  iBit,iBit, -1                                         // decrement "Bit-Banging"-Loop counter
        ssl     iBit                                                  // put the "Bit-Banging"-Loop counter value into the Shift Amount Register (SAR)
        memw                                                          // memw again :-)
        sll     temp, temp                                            // shifting the SPI DATA (read out @GPIO12) left with amount iBit
        memw                                                          // memw again :D
        or      vSample, vSample, temp                                // vSample = vSample | temp or lets say: VSample = vSample | (1<<iBit)
    bnez  iBit, loop_Bit                                              // conditional branch of the "Bit-Banging"-Loop counter
    s32i vSample, aADCBUF, 0                                          // store the "complete" Sample to the aADCBUF
    memw                                                              // for use here memw
    addi.n aADCBUF, aADCBUF, 4                                        // increment the pointer aADCBUFFER by 4 for the next sample to store
    memw                                                              // memw needed ?!
    addi.n  iCh,iCh, -1                                               // decrement "Bit-Banging"-Loop counter
    movi vSample, 0                                                   // put 0 to vSample (AR: a7)
bnez  iCh, loop_Channel                                               // conditional branch of the "Channel"-Loop counter

// increment the SampleCounter
l32i    vVar, aSampleCounter, 0
memw
addi.n  vVar, vVar, 1
s32i    vVar, aSampleCounter, 0
memw
//----

// check if the SampleCounter is equal to nSamplePerCh (desired numbers of samples per channel)
movi    temp, nSamplesPerCh         
bge     vVar, temp, package_complete        // the package is full, jump to "package_complete"
j       l5_intr_handler_end                 // package is not full, jump to "l5_intr_handler_end"



// in the following 9 lines the BufferSelector will be switched: "BufferSelector ^= 1;
// why ?: because one of the two adc buffers ADCBUFFER_X is completed, the BufferSelector
// is switched, so the next other adc buffer can be filled until the previous adc buffer 
// can be transfered... like a "Flip-Flop"-Buffer
package_complete:
movi    aVar, BufferSelector                            // put the memory address of volatile global integer vatriable  "BufferSelector" into aVar (AR: a9)
memw                                     
l32i    vVar, aVar, 0                                   // load the content of BufferSelector to vVar (AR: a10)
memw
movi    temp, 4                                         // put 4 into temp (AR: a11): we have to use 4, because in assembly memory addresses are offset by 4 bytes !
xor     vVar, vVar, temp                                // switch the BufferSelector
memw                                      
s32i    vVar, aVar, 0                                   // store the new BufferSelector !
memw


movi    temp, 0                                         // put 0 into temp (AR: a11)
s32i    temp, aSampleCounter, 0                         // set SampleCounter to 0
memw                                                    // memw needed for sure here..
s32i vSPI_SCLK_PIN_BITMASK, aGPIO_OUT_W1TS_REG, 0       // set GPIO14, used as SPI SCLK, to high



l5_intr_handler_end:
// restore all used address registers, otherwise everything will explode :D
movi a0, _l5_intr_stack                                                         // put the memory address (=begin of the stack) to AR a0
l32i iCh,                           a0, L5_INTR_iCh_OFFSET                      // restore the saved content of a4 (=iCh)....
l32i iBit,                          a0, L5_INTR_iBit_OFFSET                     // ....
l32i aSampleCounter,                a0, L5_INTR_aSampleCounter_OFFSET           // ....
l32i vSample,                       a0, L5_INTR_vSample_OFFSET                  // ....
l32i aADCBUF,                       a0, L5_INTR_aADCBUF_OFFSET                  // ....
l32i aVar,                          a0, L5_INTR_aVar_OFFSET                     // ....
l32i vVar,                          a0, L5_INTR_vVar_OFFSET                     // ....
l32i temp,                          a0, L5_INTR_temp_OFFSET                     // ....
l32i aGPIO_IN_REG,                  a0, L5_INTR_aGPIO_IN_REG_OFFSET             // ....
l32i vSPI_SCLK_PIN_BITMASK, a0, L5_INTR_vSPI_SCLK_PIN_BITMASK_OFFSET            // ....
l32i aGPIO_OUT_W1TS_REG,            a0, L5_INTR_aGPIO_OUT_W1TS_REG_OFFSET       // ....
l32i aGPIO_OUT_W1TC_REG,            a0, L5_INTR_aGPIO_OUT_W1TC_REG_OFFSET       // restore the saved content of a15 (=aGPIO_OUT_W1TC_REG)
//----

rsync                                                                           // better use rsync or isync here ?!
//isync

rsr     a0, EXCSAVE_5                                                           // read special register EXCSAVE (location for high level int. handler) to a0
rfi     5                                                                       // finaly return from high priority interrupt level 5.... puh...

    .global ld_include_xt_highint5
ld_include_xt_highint5:
Thank you very much !

I appreciate every help !

Best regards,
opcode_x64

ESP_igrr
Posts: 2067
Joined: Tue Dec 01, 2015 8:37 am

Re: An Issue with high level interrupt

Postby ESP_igrr » Sat Apr 18, 2020 10:43 pm

Hi opcode_x64,

That's quite a lot of code to digest... One thing i've noticed so far is:
opcode_x64 wrote: // the follwing 8 assembly lines are doing "*(aADCBUF+SampleCounter*4)"
// multiplication by 4 is done, cause only 4 adc channels are used.
l32i temp, aSampleCounter, 0 // load the content of the SampleCounter into temp
memw // SampleCounter is volatile so use memw
slli temp, temp, 4 // multiply SampleCounter by 4 using bit shift logical immediate
memw // to be sure, use memw ?!
add.n aADCBUF, aADCBUF, temp // increase the pointer, which shows to Bufs[BufferSelector], by "SampleCounter*4"
memw // again use mewm...
//----
slli temp, temp, 4 shifts temp left by 4 bits, which is equivalent to multiplying by 16, not by 4.

You can also simplify the logic related to BufferSelector variable by using addx4 instruction:
addx4 ar, as, at means ar := as * 4 + at. This is useful when you need to convert an index of a 32-bit value into an address.

The other important thing, i see absolutely no attempt at synchronization between ISR and the task, or some critical sections. Level 5 interrupt may happen at any time. For example, in this code:
opcode_x64 wrote: while(1){
if(BufferSelector!=prevBufferSelector){
// BufferSelector is right shifted by 2, because in high-level interrupt handler in assembly
// is switching the BufferSelector between 0 and 4 (byte addressing!)
// bit ight shifting by 2 bits makes from 0 -> 0 and from 4 -> 1, so this is fine then for C
int ret = write(sock, Bufs[(prevBufferSelector>>2)],ADCBUFFER_SIZE );
prevBufferSelector = (BufferSelector>>2);
}
vTaskDelay(1);
}
It looks like the interrupt may happen at any point inside the if statement, and change the BufferSelector variable. It may cause a problem, because the code above assumes that BufferSelector at entry and at exit are the same.

One final thing, i've merged the new topic you have created into the original one. Please post in the same topic while discussing a single issue.

opcode_x64
Posts: 47
Joined: Sun Jan 13, 2019 5:39 pm

Re: An Issue with high level interrupt

Postby opcode_x64 » Sun Apr 19, 2020 7:35 am

Hello ESP_iggr,

1.)
One final thing, i've merged the new topic you have created into the original one. Please post in the same topic while discussing a single issue.

First, I want to apologize this :|

2.)
slli temp, temp, 4 shifts temp left by 4 bits, which is equivalent to multiplying by 16, not by 4.

You can also simplify the logic related to BufferSelector variable by using addx4 instruction:
addx4 ar, as, at means ar := as * 4 + at. This is useful when you need to convert an index of a 32-bit value into an address.

Yes you are completely right, it is multiplication by 16 and this is correct, I just made a mistake by writing the comment...

3.)
The other important thing, i see absolutely no attempt at synchronization between ISR and the task, or some critical sections. Level 5 interrupt may happen at any time. For example, in this code:

Do I have to use speical instructions like "L32AI" and "S32I" for synchronization (see Xtensa ISA) or can I solve this issue by using a logic with additional volatile variables ?!

Thank you very much for your help and efforts !

Best regards,
opcode_x64

ESP_igrr
Posts: 2067
Joined: Tue Dec 01, 2015 8:37 am

Re: An Issue with high level interrupt

Postby ESP_igrr » Sun Apr 19, 2020 8:27 pm

Regarding synchronization — if the task and the ISR are running on the same CPU, then you can implement the critical section in a task by disabling the interrupt. If they are running on different CPUs, you need to use the S32C1I (atomic compare and set) instruction.

opcode_x64
Posts: 47
Joined: Sun Jan 13, 2019 5:39 pm

Re: An Issue with high level interrupt

Postby opcode_x64 » Sun Apr 19, 2020 9:44 pm

Hello ESP_iggr,

thanks for reply. They are running in different cores:
core0: task_adcbuffeer_send
core1: high level interrupt 5

So I have to use the S32C1l instruction for all volatile variables then ? In this case: BufferSelector, SampleCounter and the *Bufs (Pointer Array)

Thanks !

best regards,
opcode_x64

opcode_x64
Posts: 47
Joined: Sun Jan 13, 2019 5:39 pm

Re: An Issue with high level interrupt

Postby opcode_x64 » Mon Apr 20, 2020 8:59 pm

Hey ESP_iggr,

I just tried to understand the atomic compare and set instruction, but really didn't get it, also I am not sure if this instruction is needed, since in the tcp task I am not changing the content/data, so acting like a consumer on core 0 and consuming the data from the producer sitting in Level 5 interrupt on core 1.

Can you please help me in using the S32C1L instruction in my case? A minimal example related to my use case would be very helpful.

thank you very much.

best regards,
opcode_x64

Who is online

Users browsing this forum: Google [Bot] and 116 guests