0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

深入淺出了解單調(diào)棧和單調(diào)隊列

Q4MP_gh_c472c21 ? 來源:袁廚的算法小屋 ? 作者:袁廚 ? 2021-02-02 10:18 ? 次閱讀

袁廚攜袁記菜館全體工作人員祝大家在新的一年,健健康康,開開心心。發(fā)量暴增,錢包超大。

哎,元旦假期結(jié)束了,又要繼續(xù)搬磚了,我們接著做題吧,今天我們好好說說單調(diào)棧和單調(diào)隊列。其實很容易理解,單調(diào)棧就是棧內(nèi)單調(diào)遞增或單調(diào)遞減的棧,棧內(nèi)元素是有序的,單調(diào)隊列同樣也是。

下面我們通過幾個題目由淺入深,一點一點挖透他們吧!

a5d57fd4-64cf-11eb-8b86-12bb97331649.png

提綱

單調(diào)隊列

劍指 Offer 59 - II. 隊列的最大值

題目描述:

請定義一個隊列并實現(xiàn)函數(shù) max_value 得到隊列里的最大值

若隊列為空,pop_front 和 max_value 需要返回 -1

示例 1:

輸入: ["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]

[[],[1],[2],[],[],[]]

輸出: [null,null,null,2,1,2]

示例 2:

輸入:

["MaxQueue","pop_front","max_value"]

[[],[],[]]

輸出: [null,-1,-1]

題目解析:

我們先來拆解下上面的示例 1

a615481c-64cf-11eb-8b86-12bb97331649.png

其實我覺得這個題目的重點在理解題意上面,可能剛開始刷題的同學(xué),對題意理解不夠透徹,做起來沒有那么得心應(yīng)手,通過上面的圖片我們簡單了解了一下題意,那我們應(yīng)該怎么做才能實現(xiàn)上述要求呢?

下面我們來說一下雙端隊列。我們之前說過的隊列,遵守先進(jìn)先出的規(guī)則,雙端隊列則可以從隊頭出隊,也可以從隊尾出隊,不用遵守先進(jìn)先出的規(guī)則,我們先通過一個視頻來簡單了解下雙端隊列。

我們可以用雙端隊列做輔助隊列,用輔助隊列來保存當(dāng)前隊列的最大值。我們同時定義一個普通隊列和一個雙端單調(diào)隊列。普通隊列就正常執(zhí)行入隊,出隊操作。max_value 操作則返回咱們的雙端隊列的隊頭即可。下面我們來看一下代碼的具體執(zhí)行過程吧。

我們來對視頻進(jìn)行解析

1.我們需要維護(hù)一個單調(diào)雙端隊列,上面的隊列則執(zhí)行正常操作,下面的隊列隊頭元素則為上面隊列的最大值

2.出隊時,我們需要進(jìn)行對比兩個隊列的隊頭元素是否相等,如果相等則同時出隊,則出隊后的雙端隊列的頭部仍為上面隊列中的最大值。

3.入隊時,我們需要維持一個單調(diào)遞減的雙端隊列,因為我們需要確保隊頭元素為最大值嘛。

題目代碼:

a67690cc-64cf-11eb-8b86-12bb97331649.png

239.滑動窗口最大值

題目描述:

給你一個整數(shù)數(shù)組 nums,有一個大小為 k 的滑動窗口從數(shù)組的最左側(cè)移動到數(shù)組的最右側(cè)。你只可以看到在滑動窗口內(nèi)的 k 個數(shù)字?;瑒哟翱诿看沃幌蛴乙苿右晃?。

返回滑動窗口中的最大值。

示例1:

輸入:nums = [1,3,-1,-3,5,3,6,7], k = 3 輸出:[3,3,5,5,6,7]

題目解析:

題目讓我們找出每個滑動窗口的最大值,那么題目具體含義是怎樣呢?

a6c42648-64cf-11eb-8b86-12bb97331649.png

就是為了讓我們輸出每個窗口的最大值,那我們思考一下,我們一個數(shù)組一共有多少窗口呢?

比如我們這個例子中,我們的窗口長度為 3 ,數(shù)組長度為 8,我們的窗口每次移動一位,所以我們一共有 8 - (3 - 1)也就是 8 - 3 + 1。所以我們返回數(shù)組的長度是跟原數(shù)組長度和滑動窗口的長度有關(guān)的。

也就是 winlen = len(數(shù)組長度) - k(滑動窗口長度) + 1。下面我們來看一個視頻,相信通過這個視頻,大家一下就能搞懂啦。

下面我們對視頻進(jìn)行拆解。

1.先將我們第一個窗口的所有值按照規(guī)則存入單調(diào)雙端隊列中,單調(diào)隊列里面的值為單調(diào)遞減的。如果發(fā)現(xiàn)隊尾元素小于要加入的元素,則將隊尾元素出隊,直到隊尾元素大于等于新元素時,再讓新元素入隊,目的就是維護(hù)一個單調(diào)遞減的隊列。第一個窗口的所有值入隊之后情況,如下圖。是因為 3 要入隊時,此時隊中有 1 ,不能保證單調(diào)遞減,所以需要 1 出隊,然后 3 入隊, -1 入隊時,隊中有 3 ,滿足單調(diào),所以 -1 可以入隊。

a706abf8-64cf-11eb-8b86-12bb97331649.png

2.我們將第一個窗口的所有值,按照單調(diào)隊列的規(guī)則入隊之后,因為隊列為單調(diào)遞減,所以隊頭元素必為當(dāng)前窗口的最大值,則將隊頭元素添加到數(shù)組中。

a7587316-64cf-11eb-8b86-12bb97331649.png

3.移動窗口,判斷當(dāng)前窗口前的元素是否和雙端隊列隊頭元素相等,如果相等則出隊,此時滑動窗口的最大值發(fā)生改變了。

a7aca5b2-64cf-11eb-8b86-12bb97331649.png

4.繼續(xù)然后按照規(guī)則進(jìn)行入隊,維護(hù)單調(diào)遞減隊列,這里和第一條規(guī)則一致。

5.每次將隊頭元素存到返回數(shù)組里。

6.返回數(shù)組

是不是一下就搞懂啦。你真帥,下面我們來看一下代碼吧。

題目代碼

a7e28f42-64cf-11eb-8b86-12bb97331649.png

單調(diào)棧

leetcode 155 最小棧

設(shè)計一個支持 push ,pop ,top 操作,并能在常數(shù)時間內(nèi)檢索到最小元素的棧。

push(x) —— 將元素 x 推入棧中。

pop() —— 刪除棧頂?shù)脑亍?/p>

top() —— 獲取棧頂元素。

getMin() —— 檢索棧中的最小元素。

輸入:

["MinStack","push","push","push","getMin","pop","top","getMin"]

[[],[-2],[0],[-3],[],[],[],[]]

輸出:

[null,null,null,null,-3,null,0,-2]

題目解析

感覺這個題目的難度就在讀懂題意上面,讀懂之后就沒有什么難的了,我們在上面的滑動窗口的最大值已經(jīng)進(jìn)行了詳細(xì)描述,其實這個題目和那個題目思路一致。

該題讓我們設(shè)計一個棧,該棧具有的功能有,push,pop,top等操作,并且能夠返回棧的最小值。比如此時棧中的元素為 5,1,2,3。我們執(zhí)行 getMin() ,則能夠返回 1。這塊是這個題目的精髓所在,見下圖, 這個題目也可以不利用輔助棧解決,但是不符合本文主題,所以在這里先不進(jìn)行詳細(xì)描述。大致思路為,把當(dāng)前最小值用一個變量保存,需要入棧的值小于當(dāng)前最小值時,先把當(dāng)前最小值入棧,再將需要入棧的值入棧,并更新當(dāng)前最小值。如果大于當(dāng)前最小值,則直接入棧。getMin()函數(shù)則直接返回變量保存的值即可。下面我們來看一下我們借助輔助棧,如何解決這個題目吧。

a83e070a-64cf-11eb-8b86-12bb97331649.png

我們一起先通過一個視頻先看一下具體解題思路,通過視頻一定可以整懂的,我們注意觀察棧 B 內(nèi)的元素。

我們來對視頻進(jìn)行解析

1.我們執(zhí)行入棧操作時,先觀察需要入棧的元素是否小于棧 B 的棧頂元素,如果小于則兩個棧都執(zhí)行入棧操作。

2.棧 B 的棧頂元素則是棧 A 此時的最小值。則 getMin() 只需返回棧 B 的棧頂元素即可。

3.出棧時,需要進(jìn)行對比,若棧 A 和棧 B 棧頂元素相同,則同時出棧,出棧后B 的棧頂保存的仍為此時棧 A 的最小元素

題目代碼

a877d566-64cf-11eb-8b86-12bb97331649.png

leetcode 739 每日溫度

題目描述:

請根據(jù)每日 氣溫 列表,重新生成一個列表。對應(yīng)位置的輸出為:要想觀測到更高的氣溫,至少需要等待的天數(shù)。如果氣溫在這之后都不會升高,請在該位置用 0 來代替。

示例1:

輸入:temperatures = [73, 74, 75, 71, 69, 72, 76, 73]

輸出:arr = [1, 1, 4, 2, 1, 1, 0, 0]

示例2:

輸入:temperatures = [30,30,31,45,31,34,56]

輸出:arr = [2,1,1,3,1,1,0]

題目解析

其實我們可以換種方式理解這個題目,比如我們 temperatures[0] = 30,則我們需要找到后面第一個比 30 大的數(shù),也就是 31,31的下標(biāo)為 2,30 的下標(biāo)為 0 ,則我們的返回數(shù)組 arr[0] = 2。理解了題目之后我們來說一下解題思路。

遍歷數(shù)組,數(shù)組中的值為待入棧元素,待入棧元素入棧時會先跟棧頂元素進(jìn)行對比,如果小于等于該值則入棧,如果大于則將棧頂元素出棧,新的元素入棧。

例如棧頂為69,新的元素為72,則69出棧,72入棧。并賦值給 arr,69 的索引為4,72的索引為5,則 arr[4] = 5 - 4 = 1,這個題目用到的是單調(diào)棧的思想,下面我們來看一下視頻解析。

注:棧中的括號內(nèi)的值,代表索引對應(yīng)的元素,我們的入棧的為索引值,為了便于理解將其對應(yīng)的值寫在了括號中

a8c1a42a-64cf-11eb-8b86-12bb97331649.png

leetcode 42 接雨水

這道接雨水也是一道特別經(jīng)典的題目,一道必刷題目,我們也用單調(diào)棧來解決。下面我們來看一下題目吧

題目描述:

給定 n 個非負(fù)整數(shù)表示每個寬度為 1 的柱子的高度圖,計算按此排列的柱子,下雨之后能接多少雨水。

示例1:

輸入:height =[0,1,0,2,1,0,1,3,2,1,2,1]

輸出:6

示例2:

輸入:height =[4,2,0,3,2,5]

輸出:9

示例2:

輸入:[4,3,2,0,1,1,5

輸出:13

題目解析:

看了上面的示例剛開始刷題的同學(xué)可能有些懵逼,那我們結(jié)合圖片來理解一下,我們就用示例3的例子進(jìn)行舉例,他的雨水到底代表的是什么。輸入代表的是黃色箱子的個數(shù),藍(lán)色箱子代表雨水?dāng)?shù)量。縫隙之間可以裝多少水

a90fa828-64cf-11eb-8b86-12bb97331649.png

說明:上面是由數(shù)組 [4,3,2,0,1,1,5]表示的高度圖,在這種情況下,可以接 13個單位的雨水(見下圖)。

上圖則為我們的題目描述,是不是理解了呢?你也可以這樣理解我們在地上放置了若干高度的黃色箱子,他們中間有空隙,然后我們想在他們里面插入若干藍(lán)色箱子,并保證插入之后,這些箱子的左視圖和右視圖都不能看到藍(lán)色箱子。 好啦題目我們已經(jīng)理解了,下面我們來看一下接雨水問題到底該怎么做,其實原理也很簡單,我們通過我們的例3來進(jìn)行說明。 首先我們依次入棧4,3,2,0我們的數(shù)組前四個元素是符合單調(diào)棧規(guī)則的。但是我們的第五個1,是大于0的。那我們就需要0出棧1入棧。但是我們這樣做是為了什么呢?有什么意義呢?別急我們來看下圖。

a9500dfa-64cf-11eb-8b86-12bb97331649.png

上圖我們的,4,3,2,0已經(jīng)入棧了,我們的另一個元素為1,棧頂元素為0,棧頂下的元素為2。那么我們在這一層接到的雨水?dāng)?shù)量怎么算呢?2,0,1這三個元素可以接住的水為一個單位(見下圖)這是我們第一層接到水的數(shù)量。 注:能接到水的情況,肯定是中間低兩邊高的情況

a9a1d252-64cf-11eb-8b86-12bb97331649.png

因為我們需要維護(hù)一個單調(diào)棧,所以我們則需要將0出棧1入棧,那么此時棧內(nèi)元素為4,3,2,1。下一位元素為1,我們?nèi)霔#藭r棧內(nèi)元素為4,3,2,1,1。 下一元素為5,棧頂元素為1,棧頂?shù)南乱辉厝詾?,則需要再下一個元素,為2,那我們求當(dāng)前層接到的水的數(shù)量。 注:棧內(nèi)保存的應(yīng)是索引值,這里為了便于理解用了value值

a9f9ee38-64cf-11eb-8b86-12bb97331649.png

我們是通過2,1,1,5這四個元素求得第二層的接水?dāng)?shù)為1*3=3;1是因為min(2-1,5-1)=min(1,4)得來的,大家可以思考一下木桶效應(yīng)。裝水的多少,肯定是按最短的那個木板來的,所以高度為1,3的話是因為5的索引為6,2的索引為2,他們之間共有三個元素(3,4,5)也就是3個單位。所以為6-2-1=3。 將1出棧之后,我們棧頂元素就變成了2,下一元素變成了3,那么3,2,5這三個元素同樣也可以接到水。

aa566686-64cf-11eb-8b86-12bb97331649.png

這是第三層的接水情況,能夠接到4個單位的水,下面我們繼續(xù)出棧2,那么我們的4,3,5仍然可以接到水啊。

aad0ff86-64cf-11eb-8b86-12bb97331649.png

這是我們第四層接水的情況,一共能夠接到5個單位的水,那么我們總的接水?dāng)?shù)加起來,那就是1+3+4+5=13。你學(xué)會了嗎?別急還有動圖我們,我們再來深入理解一哈。

題目代碼:

ab34aed2-64cf-11eb-8b86-12bb97331649.png

好啦,咱們的單調(diào)隊列和單調(diào)棧的題目到這里就算總結(jié)完畢啦,希望對你能有一點點幫助。

原文標(biāo)題:深入淺出搞通單調(diào)隊列單調(diào)棧

文章出處:【微信公眾號:嵌入式ARM】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

責(zé)任編輯:haq

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴

原文標(biāo)題:深入淺出搞通單調(diào)隊列單調(diào)棧

文章出處:【微信號:gh_c472c2199c88,微信公眾號:嵌入式微處理器】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    深入淺出RISC-V調(diào)試

    一、JTAG簡介 目前RISC-V官方支持的調(diào)試方式是JTAG(Joint Test Action Group),而ARM支持的調(diào)試方式有JTAG和SWD(Serial Wire Debug)這兩種。 JTAG是一種國際標(biāo)準(zhǔn)的調(diào)試方式(IEEE1149.1),而SWD是ARM開發(fā)的。標(biāo)準(zhǔn)JTAG采用四線方式,分別是TCK、TMS、TDI和TDO,有一個可選的TRST引腳。 ● TCK:測試時鐘輸入。 ● TMS:測試模式選擇。 ● TDI:測試數(shù)據(jù)輸入。 ● TDO:測試數(shù)據(jù)輸出。 在調(diào)試時需要用到一個工具,比如JLink或者CMSIS-DAP,對于這個工具,在這里稱為JTAG主機(jī)(JTAG host),而嵌入在芯片內(nèi)部的JTAG稱為JTAG從機(jī)(JTAG slave),需要注意的是上面這些信號的輸入輸出方向是對于JTAG從機(jī)來說的。下文中如無特別說明,JTAG都是指JTAG從機(jī)。 一個JTAG主機(jī)可以同時對多個JTAG從機(jī)進(jìn)行調(diào)試,這通過JTAG掃描鏈(JTAG Scan Chain)完成,如圖1所示。 圖1 一個JTAG主機(jī)連接多個JTAG從機(jī) JTAG內(nèi)部有一個TAP(Test Access Port)控制器(或者說狀態(tài)機(jī)),通過TCK和TMS信號來改變狀態(tài)機(jī)的狀態(tài)。這個狀態(tài)機(jī)的核心是兩路SCAN,分別是IR SCAN和DR SCAN,TAP狀態(tài)機(jī)如圖2所示。 圖2 TAP狀態(tài)機(jī) 箭頭上的0或1表示的是TMS信號的電平。JTAG在每一個TCK信號的上升沿采樣TMS信號和TDI信號,決定狀態(tài)機(jī)的狀態(tài)是否發(fā)生變化,在每一個TCK信號的下降沿輸出TDO信號。可以看到,無論TAP目前處于哪一個狀態(tài),只要TMS保持高電平并持續(xù)5個TCK時鐘,則TAP一定會回到Test-Logic-Reset狀態(tài)。 JTAG內(nèi)部有一個IR(instruction register)寄存器和多個DR(data register)寄存器,IR寄存器決定要訪問的是哪一個DR寄存器。DR寄存器有IDCODE、BYPASS等。在Test-Logic-Reset狀態(tài)下IR寄存器默認(rèn)選擇的是IDCODE這個DR寄存器。 JTAG主機(jī)通過IR SCAN設(shè)置IR寄存器的值,然后通過DR SCAN來讀、寫相應(yīng)的DR寄存器。 二、RISC-V調(diào)試Spec 調(diào)試模塊在CPU芯片設(shè)計里是最為不起眼的,但又是最為復(fù)雜的模塊之一,大部分開源的處理器IP都沒有調(diào)試模塊。 下面的內(nèi)容基于RISC-V debug spec 0.13版本。 目前RISC-V的官方調(diào)試上位機(jī)是openocd,調(diào)試工具可以是JLink或者CMSIS-DAP,RISC-V調(diào)試系統(tǒng)框架如圖3所示。 圖3 RISC-V調(diào)試系統(tǒng)框架 可以看到主要分為3個部分,分別是Debug Host,可以理解為PC;Debug Hardware,可以理解為JLink或者CMSIS-DAP這樣的調(diào)試工具;第三部分就是嵌入在芯片內(nèi)部的調(diào)試模塊。在調(diào)試模塊內(nèi)部,與調(diào)試工具直接交互的是DTM模塊,DTM模塊通過DMI接口與DM模塊交互。 1>DTM模塊 在DTM模塊里實現(xiàn)了一個TAP控制器(狀態(tài)機(jī)),其中IR寄存器的長度最少為5位,當(dāng)TAP控制器復(fù)位時,IR的值默認(rèn)為5\'b00001,即選擇的是IDCODE寄存器。DTM模塊的寄存器(DR寄存器)定義如圖4所示。 圖4 DTM寄存器 其中紅色框起來的寄存器是必須要實現(xiàn)的。下面簡單介紹一下這幾個寄存器。 ① IDCODE寄存器(0x01) 當(dāng)TAP狀態(tài)機(jī)復(fù)位時,IR寄存器的值默認(rèn)為0x01,即選擇的是IDCODE寄存器。IDCODE寄存器的每一位含義如圖5所示。IDCODE是只讀寄存器。 圖5 IDCODE寄存器 ● Version:只讀,版本號,可為任意值。 ● PartNumber:只讀,可為任意值。 ● Manufld:只讀,廠商號,遵循JEP106標(biāo)準(zhǔn)分配,實際中可為任意值,只要不與已分配的廠商號沖突即可。 ② DTM控制和狀態(tài)寄存器(dtmcs,0x10) dtmcs寄存器的每一位含義如圖6所示。 圖6 dtmcs寄存器 ● dmihardreset:DTM模塊硬復(fù)位,寫1有效。 ● dmireset:清除出錯,寫1有效。 ● idle:只讀,JTAG 主機(jī)在Run-Test-Idle狀態(tài)停留的時鐘周期數(shù),0表示不需要進(jìn)入Run-Test-Idle狀態(tài),1表示進(jìn)入Run-Test-Idle狀態(tài)后可以馬上進(jìn)入下一個狀態(tài),以此類推。 ● dmistat:只讀,上一次操作的狀態(tài)。0表示無出錯,1或者2表示操作出錯,3表示操作還未完成。 ● abits:只讀,dmi寄存器中address域的大小(位數(shù))。 ● version:只讀,實現(xiàn)所對應(yīng)的spec版本,0表示0.11版本,1表示0.13版本。 ③ DM模塊接口訪問寄存器(dmi,0x11) dmi寄存器的每一位含義如圖7所示。 圖7 dmi寄存器 ● address:可讀可寫,DM寄存器的長度(位數(shù))。 ● data:可讀可寫,往DM寄存器讀、寫的數(shù)據(jù),固定為32位。 ● op:可讀可寫,讀或者寫這個域時有不同的含義。當(dāng)寫這個域時,寫0表示忽略address和data的值,相當(dāng)于nop操作;寫1表示從address指定的寄存器讀數(shù)據(jù);寫2表示把data的數(shù)據(jù)寫到address指定的寄存器。寫3為保留值。當(dāng)讀這個域時,0表示上一個操作正確完成;1為保留值;2表示上一個操作失敗,這個狀態(tài)是會被記住的,因此需要往dtmcs寄存器的dmireset域?qū)?才能清除這個狀態(tài)。3表示上一個操作還未完成。 在Update-DR狀態(tài)時,DTM開始執(zhí)行op指定的操作。在Capture-DR狀態(tài)時,DTM更新data域。 ④ BYPASS寄存器(0x1f) 只讀,長度為1,值固定為0。 2>DM模塊 從圖3可知,DM模塊訪問RISC-V Core有兩種方式,一種是通過abstract command,另一種是通過system bus。abstract command方式是必須要實現(xiàn)的,system bus的方式是可選的。 DM模塊的寄存器都為32位,定義如圖8所示。 圖8 DM寄存器 下面介紹一下紅色框起來這幾個重要的寄存器。 ① data寄存器(data0-data11,0x04-0x0f) 這12個寄存器是用于abstract command的數(shù)據(jù)寄存器,長度為32位,可讀可寫。 ② DM控制寄存器(dmcontrol,0x10) dmcontrol寄存器的每一位含義如圖9所示。 圖9 dmcontrol寄存器 ● haltreq:只寫,寫1表示halt(暫停)當(dāng)前hart(hart表示CPU核,存在多核的情況)。 ● resumereq:只能寫1,寫1表示resume(恢復(fù))當(dāng)前hart,即go。 ● hartreset:可讀可寫,寫1表示復(fù)位DM模塊,寫0表示撤銷復(fù)位,這是一個可選的位。 ● ackhavereset:只能寫1,寫1表示清除當(dāng)前hart的havereset狀態(tài)。 ● hasel:可讀可寫,0表示當(dāng)前只有一個已經(jīng)被選擇了的hart,1表示當(dāng)前可能有多個已經(jīng)被選擇了的hart。 ● hartsello:可讀可寫,當(dāng)前選擇的hart的低10位。1位表示一個hart。 ● hartselhi:可讀可寫,當(dāng)前選擇的hart的高10位。1位表示一個hart。如果只有一個hart,那么hasel的值為0,hartsello的值為1,hartselhi的值為0。 ● setresethaltreq:只能寫1,寫1表示當(dāng)前選擇的hart復(fù)位后處于harted狀態(tài)。 ● clrresethaltreq:只能寫1,寫1表示清除setresethaltreq的值。 ● ndmreset:可讀可寫,寫1表示復(fù)位整個系統(tǒng),寫0表示撤銷復(fù)位。 ● dmactive:可讀可寫,寫0表示復(fù)位DM模塊,寫1表示讓DM模塊正常工作。正常調(diào)試時,此位必須為1。 ③ DM狀態(tài)寄存器(dmstatus,0x11) dmstatus寄存器是一個只讀寄存器,每一位含義如圖10所示。 圖10 dmstatus寄存器 ● impebreak:1表示執(zhí)行完progbuf的指令后自動插入一條ebreak指令,這樣就可以節(jié)省一個progbuf。當(dāng)progbufsize的值為1時,此值必須為1。 ● allhavereset:1表示當(dāng)前選擇的hart已經(jīng)復(fù)位。 ● anyhavereset:1表示當(dāng)前選擇的hart至少有一個已經(jīng)復(fù)位。 ● allresumeack:1表示當(dāng)前選擇的所有hart已經(jīng)應(yīng)答上一次的resume請求。 ● anyresumeack:1表示當(dāng)前選擇的hart至少有一個已經(jīng)應(yīng)答上一次的resume請求。 ● allnonexistent:1表示當(dāng)前選擇的hart不存在于當(dāng)前平臺。 ● anynonexistent:1表示至少有一個選擇了的hart不存在于當(dāng)前平臺。 ● allunavail:1表示當(dāng)前選擇的hart都不可用。 ● anyunavail:1表示至少有一個選擇了的hart不可用。 ● allrunning:1表示當(dāng)前選擇的hart都處于running狀態(tài)。 ● anyrunning:1表示至少有一個選擇了的hart處于running狀態(tài)。 ● allhalted:1表示當(dāng)前選擇的hart都處于halted狀態(tài)。 ● anyhalted:1表示至少有一個選擇了的hart處于halted狀態(tài)。 ● authenticated:0表示使用DM模塊之前需要進(jìn)行認(rèn)證,1表示已經(jīng)通過認(rèn)證。 ● authbusy:0表示可以進(jìn)行正常的認(rèn)證,1表示認(rèn)證處于忙狀態(tài)。 ● hasresethaltreq:1表示DM模塊支持復(fù)位后處于halted狀態(tài),0表示不支持。 ● confstrptrvalid:1表示confstrptr0~3寄存器保存了配置字符串的地址。 ● version:0表示DM模塊不存在,1表示DM模塊的版本為0.11,2表示DM模塊的版本為0.13。 ④ abstract控制和狀態(tài)寄存器(abstractcs,0x16) abstractcs寄存器定義如圖11所示。 圖11 abstractcs寄存器 ● progbufsize:只讀,program buffer的個數(shù),取值范圍為0~16,每一個的大小為32位。 ● busy:只讀,1表示abstract命令正在執(zhí)行,當(dāng)寫command寄存器后該位應(yīng)該馬上被置位直到命令執(zhí)行完成。 ● cmderr:可讀、只能寫1,cmderr的值僅當(dāng)busy位為0時有效。0表示無錯誤,1表示正在操作command、abstractcs、data或者progbuf寄存器,2表示不支持當(dāng)前命令,3表示執(zhí)行命令時出現(xiàn)異常,4表示由于當(dāng)前hart不可用,或者不是處于halted/running狀態(tài)而不能被執(zhí)行,5表示由于總線出錯(對齊、訪問大小、超時)導(dǎo)致的錯誤,7表示其他錯誤。寫1清零cmderr。 ● datacount:只讀,所實現(xiàn)的data寄存器的個數(shù)。 ⑤ abstract命令寄存器(command,0x17) 當(dāng)寫這個寄存器時,相應(yīng)的操作就會被執(zhí)行。command寄存器只能寫,定義如圖12所示。 圖12 command寄存器 ● cmdtype:只寫,命令類型,0為表示訪問寄存器,1表示快速訪問,2表示訪問內(nèi)存。 ● control:只寫,不同的命令類型有不同的含義,說明如下。 當(dāng)cmdtype為0時,control定義如圖13所示。 圖13 訪問寄存器 ● cmdtype:值為0。 ● aarsize:2表示訪問寄存器的最低32位,3表示訪問寄存器的最低64位,4表示訪問寄存器的最低128位。如果大于實際寄存器的大小則此次訪問是失敗的。 ● aarpostincrement:1表示成功訪問寄存器后自動增加regno的值。 ● postexec:1表示執(zhí)行progbuf里的內(nèi)容(指令)。 ● transfer:0表示不執(zhí)行write指定的操作,1表示執(zhí)行write指定的操作。 ● write:0表示從指定的寄存器拷貝數(shù)據(jù)到arg0指定的data寄存器。1表示從arg0指定的data寄存器拷貝數(shù)據(jù)到指定的寄存器。 ● regno:要訪問的寄存器。 綜上,可知: Ⅰ. 當(dāng)write=0,transfer=1時,從regno指定的寄存器拷貝數(shù)據(jù)到arg0對應(yīng)的data寄存器。 Ⅱ. 當(dāng)write=1,transfer=1時,從arg0對應(yīng)的data寄存器拷貝數(shù)據(jù)到regno指定的寄存器。 Ⅲ. 當(dāng)aarpostincrement=1時,將regno的值加1。 Ⅳ. 當(dāng)postexec=1時,執(zhí)行progbuf寄存器里的指令。 arg對應(yīng)的data寄存器如圖14所示。 圖14 arg對應(yīng)的data寄存器 即當(dāng)訪問的寄存器位數(shù)為32位時,arg0對應(yīng)data0寄存器,arg1對應(yīng)data1寄存器,arg2對應(yīng)data2寄存器。 當(dāng)cmdtype為1時,control定義如圖15所示。 圖15 快速訪問 ● cmdtyte:值為1。 此命令會執(zhí)行以下操作: 1)halt住當(dāng)前hart。 2)執(zhí)行progbuf寄存器里的指令。 3)resume當(dāng)前hart。 當(dāng)cmdtype為2時,control定義如圖16所示。 圖16 訪問內(nèi)存 ● cmdtype:值為2。 ● aamvirtual:0表示訪問的是物理地址,1表示訪問的是虛擬地址。 ● aamsize:0表示訪問內(nèi)存的低8位,1表示訪問內(nèi)存的低16位,2表示訪問內(nèi)存的低32位,3表示訪問內(nèi)存的低64位,4表示訪問內(nèi)存的低128位。 ● aampostincrement:1表示訪問成功后,將arg1對應(yīng)的data寄存器的值加上aamsize對應(yīng)的字節(jié)數(shù)。 ● write:0表示從arg1指定的地址拷貝數(shù)據(jù)到arg0指定的data寄存器,1表示從arg0指定的data寄存器拷貝數(shù)據(jù)到arg1指定的地址。 ● target-specific:保留。 綜上,可知: Ⅰ. 當(dāng)write=0時,從arg1指定的地址拷貝數(shù)據(jù)到arg0指定的data寄存器。 Ⅱ. 當(dāng)write=1時,從arg0指定的data寄存器拷貝數(shù)據(jù)到arg1指定的地址。 Ⅲ. 當(dāng)aampostincrement=1時,增加arg1對應(yīng)的data寄存器的值。 ⑥ 系統(tǒng)總線訪問控制和狀態(tài)寄存器(sbcs,0x38) sbcs寄存器定義如圖17所示。 圖17 sbcs寄存器 ● sbversion:只讀,0表示system bus是2018.1.1之前的版本,1表示當(dāng)前debug spec的版本,即0.13版本。 ● sbbusyerror:只讀,寫1清零,當(dāng)debugger要進(jìn)行system bus訪問操作時,如果上一次的system bus訪問還在進(jìn)行中,此時會置位該位。 ● sbbusy:只讀,1表示system bus正在忙。在進(jìn)行system bus訪問前必須確保該位為0。 ● sbreadonaddr:可讀可寫,1表示每次往sbaddress0寄存器寫數(shù)據(jù)時,將會自動觸發(fā)system bus從新的地址讀取數(shù)據(jù)。 ● sbaccess:可讀可寫,訪問的數(shù)據(jù)寬度,0表示8位,1表示16位,2表示32位,3表示64位,4表示128位。 ● sbautoincrement:可讀可寫,1表示每次system bus訪問后自動將sbaddress的值加上sbaccess的大小(字節(jié))。 ● sbreadondata:可讀可寫,1表示每次從sbdata0寄存器讀數(shù)據(jù)后將自動觸發(fā)system bus從新的地址讀取數(shù)據(jù)。 ● sberror:可讀,寫1清零,0表示無錯誤,1表示超時,2表示訪問地址錯誤,3表示地址對齊錯誤,4表示訪問大小錯誤,7表示其他錯誤。 ● sbasize:只讀,system bus地址寬度(位數(shù)),0表示不支持system bus訪問。 ● sbaccess128:只讀,1表示system bus支持128位訪問。 ● sbaccess64:只讀,1表示system bus支持64位訪問。 ● sbaccess32:只讀,1表示system bus支持32位訪問。 ● sbaccess16:只讀,1表示system bus支持16位訪問。 ● sbaccess8:只讀,1表示system bus支持8位訪問。 ⑦ 系統(tǒng)總線地址0寄存器(sbaddress0,0x39) 可讀可寫,如果sbcs寄存器中的sbasize的值為0,那么此寄存器可以不用實現(xiàn)。 當(dāng)寫該寄存器時,會執(zhí)行以下流程: Ⅰ. 設(shè)置sbcs.sbbusy的值為1。 Ⅱ. 從新的sbaddress地址讀取數(shù)據(jù)。 Ⅲ. 如果讀取成功并且sbcs.sbautoincrement的值為1,則增加sbaddress的值。 Ⅳ. 設(shè)置sbcs.sbbusy的值為0。 ⑧ 系統(tǒng)總線數(shù)據(jù)0寄存器(sbdata0,0x3c) 可讀可寫,如果sbcs寄存器中的所有sbaccessxx的值都為0,那么此寄存器可以不用實現(xiàn)。 當(dāng)寫該寄存器時,會執(zhí)行以下流程: Ⅰ. 設(shè)置sbcs.sbbusy的值為1。 Ⅱ. 將sbdata的值寫到sbaddress指定的地址。 Ⅲ. 如果寫成功并且sbcs.sbautoincrement的值為1,則增加sbaddress的值。 Ⅳ. 設(shè)置sbcs.sbbusy的值為0。 當(dāng)讀該寄存器時,會執(zhí)行以下流程: Ⅰ. 準(zhǔn)備返回讀取的數(shù)據(jù)。 Ⅱ. 設(shè)置sbcs.sbbusy的值為1。 Ⅲ. 如果sbcs.sbautoincrement的值為1,則增加sbaddress的值。 Ⅳ. 如果sbcs.sbreadondata的值為1,則開始下一次讀操作。 Ⅴ. 設(shè)置sbcs.sbbusy的值為0。 三、RISC-V調(diào)試上位機(jī)分析 RISC-V官方支持的調(diào)試器上位機(jī)是openocd。openocd是地表最強(qiáng)大(沒有之一)的開源調(diào)試上位機(jī),支持各種target(ARM(M、A系列)、FPGA、RISC-V等),支持各種調(diào)試器(Jlink、CMSIS-DAP、FTDI等),支持JTAG和SWD接口。 這里不打算詳細(xì)分析整個openocd的實現(xiàn),只是重點關(guān)注針對RISC-V平臺的初始化、讀寫寄存器和讀寫內(nèi)存這幾個流程。 1>openocd啟動過程 openocd啟動時需要通過-f參數(shù)制定一個cfg文件,比如: openocd.exe -f riscv.cfg riscv.cfg文件的內(nèi)容如下: adapter_khz1000 reset_config srst_only adapter_nsrst_assert_width 100 interface cmsis-dap transport select jtag set _CHIPNAME riscv jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x1e200a6d set _TARGETNAME $_CHIPNAME.cpu target create $_TARGETNAME riscv -chain-position $_TARGETNAME ■ 第一行設(shè)置TCK的時鐘為1000KHz。 ■ 第二行表示不支持通過TRST引腳復(fù)位,只支持TMS為高電平并持續(xù)5個TCK時鐘這種方式的復(fù)位。 ■ 第三行是復(fù)位持續(xù)的延時。 ■ 第四行指定調(diào)試器為CMSIS-DAP。 ■ 第五行指定調(diào)試接口為JTAG。 ■ 第六行指定調(diào)試的target類型為riscv。 ■ 第七行指定生成一個IR寄存器長度為5位、IDCODE為0x1e200a6d的JTAG TAP。 ■ 第八、九行指定生成一個riscv target。 openocd啟動時的主要流程如圖18所示。 圖18 openocd啟動流程 下面重點關(guān)注一下examine target這個流程。 這里的target是指riscv,對于riscv,首先會讀取dtmcontrol這個寄存器,因為openocd支持0.11和0.13版本的DTM,通過這個寄存器可以知道當(dāng)前調(diào)試的DTM是哪一個版本。這里選擇0.13版本來分析。通過讀取dtmcontrol,還可以知道idle、abits這些參數(shù)。接下來會將dmcontrol這個寄存器的dmactive域?qū)?后再寫1來復(fù)位DM模塊。接下來再讀取dmstatus,判斷version域是否為2。接下來還會讀取sbcs和abstractcs寄存器,最后就是初始化每一個hart的寄存器。 2>read register過程 讀寄存器時,先構(gòu)建command寄存器的內(nèi)容,首先將cmdtype的值設(shè)為0,aarsize的值設(shè)為2(寄存器的寬度為32位),transfer的值設(shè)為1,regno的值設(shè)為要讀的寄存器的number,其他值設(shè)為0,然后寫到command寄存器里。然后一直讀取abstractcs寄存器,直到abstractcs寄存器的busy位為0或者超時。然后再判斷abstractcs寄存器的cmderr的值是否為0,如果不為0則表示此次讀取寄存器失敗,如果為0則繼續(xù)讀取data0寄存器,這樣就可以得到想要讀的寄存器的值。 3>write register過程 寫寄存器時,先將需要寫的值寫到data0寄存器,然后構(gòu)建command寄存器的內(nèi)容,首先將cmdtype的值設(shè)為0,aarsize的值設(shè)為2(寄存器的寬度為32位),transfer的值設(shè)為1,write的值設(shè)為1,regno的值設(shè)為要寫的寄存器的number,其他值設(shè)為0,然后寫到command寄存器里。然后一直讀取abstractcs寄存器,直到abstractcs寄存器的busy位為0或者超時。然后再判斷abstractcs寄存器的cmderr的值是否為0,如果不為0則表示此次寫寄存器失敗,如果為0則表示寫寄存器成功。 4>read memory過程 如果progbufsize的值大于等于2,則會優(yōu)先使用通過執(zhí)行指令的方式來讀取內(nèi)存。這里不分析這種方式,而是分析使用system bus的方式。通過前面的分析可知,system bus有兩個版本V0和V1,這里以V1版本來說明。 先將sbcs寄存器的sbreadonaddr的值設(shè)為1,sbaccess的值設(shè)為2(32位),然后將要讀內(nèi)存的地址寫入sbaddress0寄存器。接著讀sbdata0寄存器,最后讀sbcs寄存器,如果其中的sbbusy、sberror和sbbusyerror都為0,則從sbdata0讀取到的內(nèi)容就是要讀的內(nèi)存的值。 5>write memory過程 和read memory類似,同樣以V1版本來說明。 先將要寫的內(nèi)存地址寫到sbaddress0寄存器,然后將要寫的數(shù)據(jù)寫到data0寄存器,最后讀sbcs寄存器,如果其中的sbbusy、sberror和sbbusyerror都為0,則此次寫內(nèi)存成功。 四、RISC-V JTAG的實現(xiàn) 通過在STM32F103C8T6上實現(xiàn)(模擬)RISC-V調(diào)試標(biāo)準(zhǔn),進(jìn)一步加深對RISC-V JTAG調(diào)試的理解。 使用STM32的四個GPIO作為JTAG信號的四根線,其中TCK所在的引腳設(shè)為外部中斷,即上升沿和下降沿觸發(fā)方式,實現(xiàn)了可以通過openocd以RISC-V的調(diào)試標(biāo)準(zhǔn)來訪問STM32的寄存器和內(nèi)存。程序流程如圖19所示。 圖19 JTAG實現(xiàn)的程序流程 五、參考資料 1、在STM32上模擬RISC-V JTAG的實現(xiàn):stm32_riscv_jtag_slave 2、一個從零開始寫的易懂的RISC-V處理器核:tinyriscv
    發(fā)表于 11-28 22:00

    JavaWeb消息隊列使用指南

    在現(xiàn)代的JavaWeb應(yīng)用中,消息隊列(Message Queue)是一種常見的技術(shù),用于異步處理任務(wù)、解耦系統(tǒng)組件、提高系統(tǒng)性能和可靠性。 1. 消息隊列的基本概念 消息隊列是一種應(yīng)用程序?qū)?yīng)
    的頭像 發(fā)表于 11-25 09:27 ?242次閱讀

    探索字節(jié)隊列的魔法:多類型支持、函數(shù)重載與線程安全

    的數(shù)據(jù)結(jié)構(gòu),它能夠高效地存儲和管理數(shù)據(jù)流。通過使用字節(jié)隊列,我們可以靈活地處理不同類型的數(shù)據(jù)、確保數(shù)據(jù)的完整性,并在多線程環(huán)境中安全地進(jìn)行操作。本文將深入探討字節(jié)
    的頭像 發(fā)表于 11-15 01:08 ?988次閱讀
    探索字節(jié)<b class='flag-5'>隊列</b>的魔法:多類型支持、函數(shù)重載與線程安全

    Linux網(wǎng)絡(luò)協(xié)議的實現(xiàn)

    請求并與底層的網(wǎng)絡(luò)硬件進(jìn)行交互。本文將深入探討 Linux 網(wǎng)絡(luò)協(xié)議的架構(gòu)與實現(xiàn),涵蓋數(shù)據(jù)包處理流程、關(guān)鍵模塊、協(xié)議層次以及性能優(yōu)化等方面。
    的頭像 發(fā)表于 09-10 09:51 ?430次閱讀
    Linux網(wǎng)絡(luò)協(xié)議<b class='flag-5'>棧</b>的實現(xiàn)

    深入了解PCI轉(zhuǎn)XMC載板轉(zhuǎn)接卡

    電子發(fā)燒友網(wǎng)站提供《深入了解PCI轉(zhuǎn)XMC載板轉(zhuǎn)接卡.docx》資料免費下載
    發(fā)表于 09-06 14:35 ?0次下載

    嵌入式環(huán)形隊列與消息隊列的實現(xiàn)原理

    嵌入式環(huán)形隊列,也稱為環(huán)形緩沖區(qū)或循環(huán)隊列,是一種先進(jìn)先出(FIFO)的數(shù)據(jù)結(jié)構(gòu),用于在固定大小的存儲區(qū)域中高效地存儲和訪問數(shù)據(jù)。其主要特點包括固定大小的數(shù)組和兩個指針(頭指針和尾指針),分別指向隊列的起始位置和結(jié)束位置。
    的頭像 發(fā)表于 09-02 15:29 ?794次閱讀

    【「時間序列與機(jī)器學(xué)習(xí)」閱讀體驗】+ 簡單建議

    這本書以其系統(tǒng)性的框架和深入淺出的講解,為讀者繪制了一幅時間序列分析與機(jī)器學(xué)習(xí)融合應(yīng)用的宏偉藍(lán)圖。作者不僅扎實地構(gòu)建了時間序列分析的基礎(chǔ)知識,更巧妙地展示了機(jī)器學(xué)習(xí)如何在這一領(lǐng)域發(fā)揮巨大潛力,使得
    發(fā)表于 08-12 11:21

    深入淺出系列之代碼可讀性

    原創(chuàng)聲明:該文章是個人在項目中親歷后的經(jīng)驗總結(jié)和分享,如有搬運需求請注明出處。 這是“深入淺出系列”文章的第一篇,主要記錄和分享程序設(shè)計的一些思想和方法論,如果讀者覺得所有受用,還請“一鍵三連
    的頭像 發(fā)表于 08-09 16:00 ?340次閱讀

    深入了解表面貼裝晶體諧振器DSX1210A

    深入了解表面貼裝晶體諧振器DSX1210A
    的頭像 發(fā)表于 07-25 14:27 ?562次閱讀
    <b class='flag-5'>深入了解</b>表面貼裝晶體諧振器DSX1210A

    深入了解恒溫晶體振蕩器DC5032AS

    深入了解恒溫晶體振蕩器DC5032AS
    的頭像 發(fā)表于 07-25 10:37 ?420次閱讀
    <b class='flag-5'>深入了解</b>恒溫晶體振蕩器DC5032AS

    深入淺出談TDR阻抗測試

    、脈寬、時序、抖動或噪聲內(nèi)容的任何事物都會影響整個系統(tǒng)的性能和可靠性。為保證信號完整性,必須了解和控制信號經(jīng)過的傳輸環(huán)境的阻抗。阻抗不匹配和不連續(xù)會導(dǎo)致反射,增加系
    的頭像 發(fā)表于 06-06 08:28 ?7221次閱讀
    <b class='flag-5'>深入淺出</b>談TDR阻抗測試

    深入淺出帶你搞懂-MOSFET柵極電阻

    一、MOSFET簡介MOSFET是金屬(metal)—氧化物(oxide)—半導(dǎo)體(semiconductor)場效應(yīng)晶體管,屬于電壓控制電流型元件,是開關(guān)電路中的基本元件,其柵極(G極)內(nèi)阻極高。以N溝道增強(qiáng)型為例,其結(jié)構(gòu)為在一塊濃度較低的P型硅上擴(kuò)散兩個濃度較高的N型區(qū)作為漏極和源極,半導(dǎo)體表面覆蓋二氧化硅絕緣層并引出一個電極作為柵極。由于mos管本身的
    的頭像 發(fā)表于 05-09 08:10 ?2.4w次閱讀
    <b class='flag-5'>深入淺出</b>帶你搞懂-MOSFET柵極電阻

    【大語言模型:原理與工程實踐】探索《大語言模型原理與工程實踐》

    的未來發(fā)展方向進(jìn)行了展望,包括跨領(lǐng)域、跨模態(tài)和自動提示生成能力方向,為讀者提供了對未來技術(shù)發(fā)展的深刻見解?!洞笳Z言模型原理與工程實踐》是一本內(nèi)容豐富、深入淺出的技術(shù)書籍。它不僅為讀者提供了大語言模型
    發(fā)表于 04-30 15:35

    進(jìn)程間通信的消息隊列介紹

    消息隊列是一種非常常見的進(jìn)程間通信方式。
    的頭像 發(fā)表于 04-08 17:27 ?380次閱讀

    深入淺出帶你了解磁共振成像(MRI)基本原理

    磁共振成像技術(shù)原本稱為核磁共振成像。很多人聽到“核磁”,第1反應(yīng)是這個對人體有害嗎,因為名稱中不是有“核”嗎。其實,此處的”核“指”原子核“確實不假,但磁共振成像只與原子核的磁場相關(guān),與原子核聚變、裂變等的能量放射并無關(guān)系。因此,磁共振成像其實是利用人體組織中某種原子核的核磁共振現(xiàn)象,將所得射頻信號經(jīng)過計算機(jī)處理,重構(gòu)出人體某一層面的圖像的診斷技術(shù)。
    的頭像 發(fā)表于 04-03 17:04 ?1434次閱讀
    <b class='flag-5'>深入淺出</b>帶你<b class='flag-5'>了解</b>磁共振成像(MRI)基本原理