簡體   English   中英

Arm 裝配中的意外時序

[英]Unexpected timing in Arm assembly

我需要一個非常精確的時間,所以我寫了一些匯編代碼(用於 ARM M0+)。 但是,在示波器上進行測量時,時間並不是我所期望的。

#define LOOP_INSTRS_CNT                 4 // subs: 1, cmp: 1, bne: 2 (when branching)
#define FREQ_MHZ                        (BOARD_BOOTCLOCKRUN_CORE_CLOCK / 1000000)
#define DELAY_US_TO_CYCLES(t_us)        ((t_us * FREQ_MHZ + LOOP_INSTRS_CNT / 2) / LOOP_INSTRS_CNT)

static inline __attribute__((always_inline)) void timing_delayCycles(uint32_t loopCnt)
{
  // note: not all instructions take one cycle, so in total we have 4 cycles in the loop, except for the last iteration.
   __asm volatile(
    ".syntax unified \t\n"  /* we need unified to use subs (not for sub, though) */
    "0: \t\n"
    "subs %[cyc], #1 \t\n"  /* assume cycles > 0 */
    "cmp %[cyc], #0 \t\n"
    "bne.n 0b\t\n"          /* this instruction costs 2 cycles when branching! */
    : [cyc]"+r" (loopCnt)   /* actually input, but we need a temporary register, so we use a dummy output so we can also write to the input register */
    :                       /* input specified in output */
    :                       /* no clobbers */
  );
}

// delay test
#define WAIT_TEST_US 100
gpio_clear(PIN1);
timing_delayCycles(DELAY_US_TO_CYCLES(WAIT_TEST_US));
gpio_set(PIN1);

非常基本的東西。 但是,延遲(通過將 GPIO 引腳設置為低電平、循環然后再次設置為高電平來測量)時序始終比預期高 50%。 我嘗試了低值(1 us 給 1.56 us),最多 500 ms 給 750 ms。

我嘗試單步執行,循環實際上只執行 3 個步驟:subs (1)、cmp (1)、branch (2)。 括號是預期的時鍾周期數。

任何人都可以闡明這里發生了什么嗎?

經過一些好的建議,我發現問題可以通過兩種方式解決:

  1. 以相同頻率運行內核時鍾和閃存時鍾(如果代碼從閃存運行)
  2. 將代碼放在 SRAM 中以避免閃存訪問等待狀態。

注意:如果有人復制了上面的代碼,請注意您可以刪除 cmp,因為 subs 設置了 s 標志。 如果這樣做,請記住將指令計數設置為 3 而不是 4。這將為您提供更好的時間分辨率。

您不能像使用 PIC 那樣使用這些處理器,時序不一樣。 我已經在這里多次演示了這一點,你可以環顧四周,也許會在這里再做一次,但不是現在。

首先這些是流水線的,所以你的平均性能是一回事,但是一旦進入循環,緩存和分支預測學習等因素已經解決,那么你可以獲得一致的性能,對於該實現。 現在忽略與流水線處理器每條指令的時鍾相關的任何文檔,無論多么淺,這是理解為什么時序不能按預期工作的第一個問題。

對齊發揮了作用,人們厭倦了我敲這個鼓,但我已經證明了很多次。 您可以在 cortex-m0 TRM 中搜索 fetch,您應該立即看到這將影響基於對齊的性能。 如果芯片供應商僅將內核編譯為 16 位,那么這將是可預測的或更可預測的(忽略其他因素)。 但是,如果它們已經在其他特性中進行了編譯,並且如果按照描述進行預取,則循環在地址空間中的位置可以通過加減影響完成循環的總時間的提取來影響循環,這可以通過以下方式進行測量或沒有范圍。

分支預測,這在 arm 文檔中沒有顯示為 arm 這樣做,但芯片供應商可以完全自由地這樣做。

緩存。 如果是 STM32 或其他品牌的 cortex-m0+,則存在或可能存在無法關閉的緩存。 閃存速度是處理器速度的一半並因此閃存等待狀態設置的情況並不少見,但通常零等待狀態意味着額外的零,並且需要兩個時鍾才能完成一次提取,或者至少可以測量閃存中的執行速度是一半在 ram 中執行速度與所有其他設置相同(系統時鍾速度等)。 ST 有一個非常好的預取/緩存解決方案,帶有一些商標名稱,也許是知道的專利。 並且很少可以關閉或擊敗它,因此第一次通過或進入循環的時間會看到延遲,並且從技術上講,預取器可以減慢循環(請參閱對齊)。

閃存,正如前面提到的,取決於芯片供應商和部件的年齡,閃存速度為內核的一半是很常見的。 然后根據您的時鍾頻率,當您閱讀芯片文檔中的閃存設置時,其中顯示了所需的等待狀態與系統時鍾速度相關的內容,這是閃存技術的關鍵性能指標以及您是否應該真的是把系統時鍾調得太高了,閃光燈沒有變得更快它有速度限制,根據我的經驗,sram 可以跟上,到目前為止我沒有看到它們有等待狀態,但閃光燈曾經是兩個或部件支持的時鍾速度范圍內的三個設置,較新發布的部件閃存往往覆蓋較慢內核的整個范圍,例如 m0+ 但 m7 等不斷獲得更高的時鍾速率,因此您仍然期望供應商需要等待狀態。

中斷/異常。 您是否在 rtos 上運行它,是否有中斷正在增加和/或保證它以更長的延遲被中斷?

外設時序,外設不會在單個時鍾中響應加載或存儲,它們可以根據需要花費很長時間,並且取決於內部或購買的時鍾系統和芯片供應商 IP,外設可能不會在處理器時鍾頻率並以分頻運行使事情變慢。 您的代碼無疑是延遲調用此函數,然后在此計時循環之外,您擺動 gpio 引腳以查看示波器上的某些內容,這會導致您如何進行基准測試以及基於上述因素和此因素的其他問題.

以及我必須記住的其他因素。

像 x86、全尺寸 ARM 等高端處理器一樣,處理器不再決定性能。 芯片和主板可以/做。 你基本上不能不斷地給管道喂食,到處都是攤位。 Dram 很慢,因此緩存層試圖處理它,但緩存有時會幫助並傷害其他人,分支預測器的傷害與他們的幫助一樣多。 依此類推,但它在很大程度上由處理器內核之外的系統驅動,以確定您可以為內核提供多少信息,然后您將了解與管道及其自身獲取策略相關的內核屬性。 理想情況下,使用總線的寬度而不是指令的大小、事務開銷,因此總線的多種寬度比一種寬度更理想,等等。

當相同的機器代碼用於不同的對齊方式時,在任何核心上導致像這樣的緊密循環有不穩定的運動和/或在時間上不一致。 現在考慮到尺寸/功率/等因素,m0+ 有一個小管子,但它仍然應該顯示出它的影響。 這些不是圖片或 avrs 或 msp430s 沒有理由期望時序循環是一致的。 充其量您可以對 spi 和 i2c bit banging 之類的事情使用定時循環,其中您需要大於或等於某個時間值,但是如果您需要准確或在一個范圍內,那么每個實現在技術上都是可能的,如果您控制許多因素,但通常不值得付出努力,並且您現在遇到此維護問題或代碼的可讀性或可理解性。

所以底線是沒有理由期望一致的時間。 如果您碰巧獲得了一致/線性的時序,那就太好了。 您要做的第一件事是檢查當您更改和重新構建代碼以對循環使用不同的值時,它不會影響此循環的對齊。

你展示了一個這樣的循環

loop:
   subs r0,#1
   cmp r0,#0
   bne loop

切線為什么是 cmp,為什么不只是

loop:
   subs r0,#1
   bne loop

但其次,您聲稱要在范圍內進行測量,這很好,因為您測量事物的方式會影響基准的質量,因此基准通常會因測量方式而異 標尺是問題而不是被測量的事物,或者兩者都有問題,然后測量結果更加不一致。 您是否使用過 systick 或其他工具來測量這一點,具體取決於您的測量方式,測量本身可能會導致變化,即使您使用 gpio 切換可能也可能會影響這一點的引腳。 所有其他事情都保持不變,只需根據立即數更改循環計數,所使用的值可能會在拇指和拇指2指令之間推動您改變某些循環的對齊方式。

您所展示的內容意味着您有這個可能受許多系統問題影響的計時循環,然后您將其與其他一些循環本身受到影響一起包裝,加上可能會調用可能受這些因素影響的 gpio 庫函數從性能的角度來看也是如此。 使用內聯匯編和您發布的編寫此函數的風格意味着您已經暴露了自己,並且可以很容易地看到在運行看似相同的代碼時的各種性能差異,甚至實際上測試中的代碼是相同的機器碼。

除非這是一個微芯片 PIC,而不是 PIC32,或者其他特定品牌和芯片系列的非常短的列表。 忽略每條指令的循環計數,假設它們是錯誤的,除非您控制這些因素,否則不要嘗試准確計時。

使用硬件,例如,如果您嘗試使用 ws8212/neopixel LED 並且您有一個嚴格的計時窗口,那么您將不會成功或使用指令計時的成功有限。 在這種特定情況下,您有時可以在零件中使用 spi 控制器或計時器來生成准確的定時(遠遠超過您使用軟件計時器管理位碰撞或其他方式)。 使用 PIC,我能夠使用定時循環和 nops 生成具有載波頻率和開關的電視紅外信號,以生成高度准確的信號。 對於這些可編程的 LED 中的一個,我在 cortex-m 上使用了很長的線性指令列表並依靠執行性能重復了其中的一小部分,它工作但非常有限,因為它是編譯時間且快速和骯臟。 與比特敲擊相比,SPI 控制器是一種痛苦,但在另一個晚上使用 SPI 控制器並且可以發送任何長度的高度准確的定時信號。

您需要將注意力轉移到以非正常方式使用定時器和/或芯片外設(如 uart、spi、i2c)來生成您想要生成的任何信號。 對於大於或等於情況而不是在時間范圍內的情況,讓定時循環或什至基於計時器的循環被其他循環包裹。 如果不能用一個芯片做到這一點,那么看看其他的,通常在制造產品時,你必須購買組件,跨供應商等。推來推去使用 CPLD 或 PAL 或 GAL 或類似的東西獲得高度准確但自定義的計時。 取決於你在做什么以及你更大的系統圖片看起來像什么 帶有 mpsse 的 ftdi usb 芯片有一個通用狀態機,你可以編程來生成一系列信號,他們用這個通用可編程來執行 i2c、spi、jtag、swd 等系統。 但是,如果您沒有 USB 主機,那將無法正常工作。

您沒有指定芯片,我手頭有很多不同的芯片/板,但只有一小部分,所以如果我想做一個演示,它可能不值得,如果我的核心編譯了一個我可能無法讓它展示一種變體,其中來自 arm 的完全相同的內核在另一個芯片上以另一種方式編譯可能很容易。 我懷疑首先你的很多變化是因為你在一個更大的循環中進行調用,調用延遲調用狀態更改 gpio,並且你正在為實驗重新編譯它。 或者更糟糕的是,如您的問題所示,如果您進行的是單次傳遞而不是圍繞呼叫進行循環,那么這會最大限度地增加不一致。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM