-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlocal-search.xml
211 lines (101 loc) · 62.8 KB
/
local-search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>通信算法-Moose算法估计CFO</title>
<link href="/2024/04/20/%E9%80%9A%E4%BF%A1%E7%AE%97%E6%B3%95-Moose%E7%AE%97%E6%B3%95%E4%BC%B0%E8%AE%A1CFO/"/>
<url>/2024/04/20/%E9%80%9A%E4%BF%A1%E7%AE%97%E6%B3%95-Moose%E7%AE%97%E6%B3%95%E4%BC%B0%E8%AE%A1CFO/</url>
<content type="html"><![CDATA[<h1 id="intro">Intro</h1><p>最近做通信的项目遇到了一个问题,在基带中还存在频偏,这种残余频偏被称之为载波频偏(Carrier Frequency Offset,CFO),基带信号处理时需要对CFO进行估计和补偿。</p><p>1994年P.H. Moose在其论文《A technique for orthogonal frequency division multiplexing frequency offset correction》<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>中提到过的算法是比较经典的CFO估计算法。 虽然该论文中讨论的调制方式是OFDM,但实际上该方法也适用于单载波通信的场景,其原理也非常简单,即用载波在两段相同的训练序列上的相位差来估计频偏。</p><p>CFO是接收端经过下变频之后存在的残余频偏,残余频偏来自于2个部分,其一是收发两段的LO本身存在频率差导致的,根源在于硬件时钟频率不可能严格相等,其二是由于收发双方有相对运动引入。</p><h1 id="过程推导">过程推导</h1><p>Moose算法中提到了2段相同的训练序列,设定发送序列中包含两段相同的序列记为<span class="math inline">\(x[n]\)</span>和<span class="math inline">\(x[n+L]\)</span>,设相同序列部分的长度为<span class="math inline">\(N\)</span>,如下图所示(均在数字域中处理)。</p><figure><img src="https://image-bed-of-zorogh.oss-cn-beijing.aliyuncs.com/img/blog-moose-tranningseq.png" alt="序列示意" /><figcaption aria-hidden="true">序列示意</figcaption></figure><p>即满足 <span class="math display">\[x[n]=x[n+L], \forall n \in [0,N-1]\]</span></p><p>设信号经过高斯白噪声信道,经过收端下变频后存在残余频偏<span class="math inline">\(\Delta f\)</span>,则接受信号可表示为:</p><p><span class="math display">\[r(t) = s(t)\exp(j2\pi \Delta f t + \varphi) + n(t)\]</span></p><p>于是对于此处的两段序列而言可得: <span class="math display">\[\begin{aligned}r[n] &= x[n] \exp(j2\pi\Delta f n + \varphi) + n[n]\\r[n+L] & =x[n+L] \exp\left(j2\pi\Delta f (n+L) + \varphi\right) + n[n+L]\end{aligned}\]</span></p><p>这里参考一下,<a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a>中写了详细推导,最终结果为: <span class="math display">\[\hat{\Delta f} = \frac{f_{smp}}{2\pi L}\cdot Arg\left\{ \sum_{n=0}^{N-1}r^*[n]r[n+L] \right\}\]</span> 原始论文中采用的是MLE准则来估计参数,此处可以采用一个更简单的办法,即直接求期望,至于这种方法的正确性有待考证,只是一个思路。 若使用期望来进行求解,则有: <span class="math display">\[\begin{aligned}\mathbb{E}\left[r^*[n]r[n+L]\right]=& \mathbb{E}\Big[\left\{ x[n] e^{j(2\pi\Delta f n + \varphi)} +n[n]\right\}^* \left\{ x[n+L] e^{j(2\pi\Delta f (n+L) + \varphi)} + n[n+L] \right\}\Big]\\=&\mathbb{E}\left[ x[n]^*x[n+L]e^{j(2\pi \Delta fL)}\right] + \mathbb{E}\{\dots\}\end{aligned}\]</span> 上式中除了第一项外,其他均含有噪声分量,此处考虑噪声分量为0均值高斯白噪声,自相关函数为狄拉克函数<span class="math inline">\(\delta(t)\)</span>,而由于发送的是两段相同的序列,因此有: <span class="math display">\[\begin{aligned}\mathbb{E}\left[r^*[n]r[n+L]\right] &= \mathbb{E}\left[ x[n]^*x[n+L]e^{j(2\pi \Delta fL)}\right]\\&=\left\|x[n]\right\|^2e^{j2\pi\Delta fL}\end{aligned}\]</span> 于是可得: <span class="math display">\[\begin{aligned}2\pi\Delta f L &= Arg\{\mathbb{E}\left[r^*[n]r[n+L]\right]\}\\\\\Delta f &= \frac{Arg\{\mathbb{E}\left[r^*[n]r[n+L]\right]\}}{2\pi L}\\\\\hat{\Delta f} &=\frac{Arg\left\{ \sum_{n=0}^{N-1}r^*[n]r[n+L] \right\}}{2\pi L} \end{aligned}\]</span></p><p>上式中利用了求均值来求期望的过程,这个是本推导中不严谨的一步。仔细分析上式,其本质是求相同序列经过L个采样点后相位的偏差,即CFO在经过<span class="math inline">\(L\)</span>采样点时间后,在后一个采样点上积累了相位差,上式即是将这种相位差值求取期望,得到估计值(这里需要提出前提假设,CFO恒定不变)。</p><p>Moose算法的原论文是利用MLE推导求得估计值,文章<a href="#fn3" class="footnote-ref" id="fnref3" role="doc-noteref"><sup>3</sup></a>中利用的是最小二乘法求得估计值,此处我利用期望求得估计,(本质上应该是一样,但是不会推数学公式了)。 这里的<span class="math inline">\(\Delta f\)</span>是数字归一化频率,范围是<span class="math inline">\([0,1]\)</span>,由于辐角范围为<span class="math inline">\((-\pi,\pi]\)</span>,因此可得Moose方法所能估计的CFO最大范围为:</p><p><span class="math display">\[\left|\hat{\Delta f}\right| \le \frac{1}{2\pi L} \cdot \pi = \frac{1}{2L}\]</span> <span class="math inline">\(L\)</span>为两段相同序列的间隔,<span class="math inline">\(L\)</span>越大,CFO最大估计范围越小,精度也越高。此外,相同序列点数长度<span class="math inline">\(N\)</span>影响了估计中对噪声的影响,长度<span class="math inline">\(N\)</span>越长,噪声对估计结果的影响越小,估计误差越小。</p><h1 id="simulation">Simulation</h1><p>以QPSK信号为例子(实际上两段训练序列不需要关注具体是什么信号,只需要两段序列相同即可)。 matlab仿真代码如下:</p><figure class="highlight matlab"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><code class="hljs matlab">clc;clear;close all;<br><br>N = <span class="hljs-number">102400</span>; <span class="hljs-comment">% points of trainning seq</span><br>L = <span class="hljs-number">204800</span>; <span class="hljs-comment">% interval sample points between two seqs' head, must be greater than N</span><br>fs = <span class="hljs-number">50e6</span>;<br>INTERPOLATION_RATE = <span class="hljs-number">4</span>;<br>MAX_FREQ_DEVIATION = <span class="hljs-number">1</span>/(<span class="hljs-number">2</span>*L*INTERPOLATION_RATE);<br>alpha = <span class="hljs-number">0.15</span>;<br>EbN0 = <span class="hljs-number">2</span>;<br>hrc = rcosdesign(alpha,<span class="hljs-number">6</span>,INTERPOLATION_RATE,<span class="hljs-string">"sqrt"</span>);<br>qpskmod = @(N) pskmod(randi([<span class="hljs-number">0</span> <span class="hljs-number">3</span>],<span class="hljs-number">1</span>,N),<span class="hljs-number">4</span>);<br>fprintf(<span class="hljs-string">"max CFO :% 7.2E , max CFO fs % 7.4EHz\n"</span>,MAX_FREQ_DEVIATION,MAX_FREQ_DEVIATION * fs);<br><br><span class="hljs-comment">%% tx</span><br>tranning_seq = qpskmod(N); <span class="hljs-comment">% this can use any seq in fact</span><br>data = [tranning_seq qpskmod(L-N) tranning_seq];<br>sBB = conv(upsample(data,INTERPOLATION_RATE),hrc);<br><br><span class="hljs-comment">%% channel</span><br>df = (<span class="hljs-number">2</span>*<span class="hljs-built_in">rand</span><span class="hljs-number">-1</span>)*MAX_FREQ_DEVIATION;<br>sBB_df = sBB.*<span class="hljs-built_in">exp</span>(<span class="hljs-number">1</span><span class="hljs-built_in">j</span>*(<span class="hljs-number">2</span>*<span class="hljs-built_in">pi</span>*df*(<span class="hljs-number">0</span>:<span class="hljs-built_in">length</span>(sBB)<span class="hljs-number">-1</span>) + <span class="hljs-number">2</span>*<span class="hljs-built_in">pi</span>*<span class="hljs-built_in">rand</span> ));<br><span class="hljs-comment">% sBB_multipath = Multipath(sBB_df,INTERPOLATION_RATE);</span><br>sBB_multipath = sBB_df;<br>snr = EbN0 + <span class="hljs-number">10</span>*<span class="hljs-built_in">log10</span>(<span class="hljs-number">2</span>) - <span class="hljs-number">10</span>*<span class="hljs-built_in">log10</span>(INTERPOLATION_RATE);<br>rBB = awgn(sBB_multipath,snr,<span class="hljs-string">"measured"</span>);<br><br><span class="hljs-comment">%% recv</span><br>rBB_mf = conv(rBB,hrc);<span class="hljs-comment">%% match filter</span><br><br><span class="hljs-comment">%% moose algorithm</span><br><span class="hljs-comment">% remove some symbol to avoid the convolution's tail</span><br>offset = <span class="hljs-number">32</span>*INTERPOLATION_RATE;<br>ob1 = rBB_mf(<span class="hljs-number">1</span>+offset:N*INTERPOLATION_RATE);<br>ob2 = rBB_mf(<span class="hljs-number">1</span>+offset+L*INTERPOLATION_RATE:(N+L)*INTERPOLATION_RATE);<br><br>res = ob2.*<span class="hljs-built_in">conj</span>(ob1);<br>res_mean = <span class="hljs-built_in">mean</span>(res);<br>df_hat = <span class="hljs-built_in">angle</span>(res_mean)/(<span class="hljs-number">2</span>*<span class="hljs-built_in">pi</span>*L*INTERPOLATION_RATE);<br><span class="hljs-comment">% analysis</span><br>err = (df_hat - df);<br>err_r = <span class="hljs-built_in">abs</span>((df_hat - df)/df);<br>fprintf(<span class="hljs-string">"hat df :% 7.3E \tdf : % 7.3E \n"</span>,df_hat,df);<br>fprintf(<span class="hljs-string">"err a :% 7.3E \terr r: % 7.3E\n"</span>,err,err_r);<br>fprintf(<span class="hljs-string">"hat df fs:% 7.3fHz \tdf fs: % 7.3fHz \terr a fs:% 7.3fHz\n"</span>,df_hat*fs,df*fs,err*fs);<br><br><br>scr_siz = get(<span class="hljs-number">0</span>,<span class="hljs-string">'ScreenSize'</span>);<br>f = <span class="hljs-built_in">figure</span>();<br>f.Position = [scr_siz(<span class="hljs-number">3</span>)/<span class="hljs-number">2</span> scr_siz(<span class="hljs-number">4</span>)/<span class="hljs-number">2</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span>] + <span class="hljs-number">800</span>*[<span class="hljs-number">-0.5</span> <span class="hljs-number">-0.5</span> <span class="hljs-number">1</span> <span class="hljs-number">1</span>];<br><span class="hljs-built_in">scatter</span>(<span class="hljs-built_in">real</span>(res),<span class="hljs-built_in">imag</span>(res),<span class="hljs-string">'b.'</span>,<span class="hljs-string">'linewidth'</span>,<span class="hljs-number">0.2</span>,<span class="hljs-string">'DisplayName'</span>,<span class="hljs-string">'differential points'</span>);<br><span class="hljs-built_in">hold</span> on;<br>esti = <span class="hljs-built_in">linspace</span>(<span class="hljs-number">0</span>,<span class="hljs-built_in">max</span>(<span class="hljs-built_in">abs</span>(res)).*<span class="hljs-built_in">exp</span>(<span class="hljs-number">1</span><span class="hljs-built_in">j</span>*<span class="hljs-built_in">angle</span>(res_mean)));<br><span class="hljs-built_in">scatter</span>(<span class="hljs-built_in">real</span>(esti),<span class="hljs-built_in">imag</span>(esti),<span class="hljs-string">'ro'</span>,<span class="hljs-string">'DisplayName'</span>,<span class="hljs-string">'frequency estimation'</span>);<br>axis(<span class="hljs-built_in">max</span>(<span class="hljs-built_in">abs</span>(res))*[<span class="hljs-number">-1</span> <span class="hljs-number">1</span> <span class="hljs-number">-1</span> <span class="hljs-number">1</span>]);<br>ax = gca();<br>ax.XAxisLocation = <span class="hljs-string">"origin"</span>;<br>ax.YAxisLocation = <span class="hljs-string">"origin"</span>;<br>ax.Box = <span class="hljs-string">"on"</span>;<br>ax.Position = [<span class="hljs-number">0.05</span> <span class="hljs-number">0.05</span> <span class="hljs-number">0.9</span> <span class="hljs-number">0.9</span>];<br>grid on;<br><span class="hljs-built_in">legend</span>();<br></code></pre></td></tr></table></figure><p>仿真结果如下: <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs shell">max CFO : 6.10E-07 , max CFO fs 3.0518E+01Hz<br>hat df :-1.994E-07 df : -1.989E-07 <br>err a :-4.295E-10 err r: 2.159E-03<br>hat df fs: -9.968Hz df fs: -9.947Hz err a fs: -0.021Hz<br></code></pre></td></tr></table></figure></p><figure><img src="https://image-bed-of-zorogh.oss-cn-beijing.aliyuncs.com/img/EbN0_20dB.png" alt="MATLAB仿真结果 高信噪比" /><figcaption aria-hidden="true">MATLAB仿真结果 高信噪比</figcaption></figure><figure><img src="https://image-bed-of-zorogh.oss-cn-beijing.aliyuncs.com/img/EbN0_2dB.png" alt="MATLAB仿真结果 低信噪比" /><figcaption aria-hidden="true">MATLAB仿真结果 低信噪比</figcaption></figure><p>可以看出低信噪比下相对误差仍然在<span class="math inline">\(10^{-3}\)</span>这个量级,换算到50MHz的采样率下,经过纠正后的残余频偏几乎不剩多少,其估计效果还是很不错的。</p><h1 id="notice">Notice</h1><h2 id="卷积拖尾的影响">卷积拖尾的影响</h2><p>MATLAB代码中,有一个细节不能忽略,即 <figure class="highlight matlab"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs matlab"><span class="hljs-comment">% remove some symbol to avoid the convolution's tail</span><br>offset = <span class="hljs-number">32</span>*INTERPOLATION_RATE;<br></code></pre></td></tr></table></figure> 这里移除了前32个符号,因为实际发射机进行成型滤波后,前一个符号的波形会影响到后续,那么在这两段波形的最开始一定会收到其前面数据成型滤波拖尾的影响,这是因为Moose算法是在采样点级别做的。若不去掉该部分,则会影响最终估计准确性。</p><h2 id="硬件实现精度">硬件实现精度</h2><p>这里只给出了频偏估计范围(<span class="math inline">\(\frac{1}{2L}\)</span>),从数学上看,若实际收端CFO落入到了该区间内,则一定能准确得到CFO的估计值,但在硬件实现的时候,受限于量化精度问题,verilog代码实现时会存在最小精度误差。</p><h1 id="reference">Reference</h1><ul><li><a href="https://ieeexplore.ieee.org/document/328961">A technique for orthogonal frequency division multiplexing frequency offset correction</a></li><li><a href="https://zhuanlan.zhihu.com/p/344861051">OFDM同步基础(二)|知乎专栏</a></li><li><a href="https://zhuanlan.zhihu.com/p/337633382">第8章:OFDM同步技术(2)——小数倍载波频率偏差估计 | 知乎专栏</a></li></ul><section class="footnotes" role="doc-endnotes"><hr /><ol><li id="fn1" role="doc-endnote"><p><a href="https://ieeexplore.ieee.org/document/328961">A technique for orthogonal frequency division multiplexing frequency offset correction</a><a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li><li id="fn2" role="doc-endnote"><p><a href="https://zhuanlan.zhihu.com/p/343055259">OFDM同步基础(一)|知乎专栏</a><a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li><li id="fn3" role="doc-endnote"><p><a href="https://zhuanlan.zhihu.com/p/343055259">OFDM同步基础(一)|知乎专栏</a><a href="#fnref3" class="footnote-back" role="doc-backlink">↩︎</a></p></li></ol></section>]]></content>
<categories>
<category>通信算法</category>
</categories>
<tags>
<tag>载波同步</tag>
<tag>算法</tag>
</tags>
</entry>
<entry>
<title>浅谈文件管理</title>
<link href="/2023/06/16/%E6%B5%85%E8%B0%88%E6%96%87%E4%BB%B6%E7%AE%A1%E7%90%86/"/>
<url>/2023/06/16/%E6%B5%85%E8%B0%88%E6%96%87%E4%BB%B6%E7%AE%A1%E7%90%86/</url>
<content type="html"><![CDATA[<h1 id="文件管理">文件管理</h1><p>文件管理涉及到了文件夹的管理、文件的管理、文件与文件夹的命名等。</p><p>需要达到的效果是,你能非常方便的找到某一个文件,并从文件名称上知道其大致内容,同时还能兼具有一定的版本管理功能。</p><h1 id="文件">📝文件</h1><p>对于目前我所面临的工作环境,大致有2类文件需要处理:</p><ol type="1"><li>代码文件。比如 Vivado 项目、C/C++、Python、MATLAB等,这些不同的文件可以根据项目所使用的编程语言以及IDE来进行分类,如<code>vivado/</code>、<code>C/</code>、<code>CPP/</code>、<code>Python/</code>、<code>MATLAB</code>。这些文件夹既包含了原理验证部分的代码,也包含了实际项目代码。值得注意的是,许多部分可以通过git来管理版本。</li><li>文档文件。比如Xilinx官方手册、硬件手册、临时干活所收集到的文档以及项目文档(还包含一系列的图片)等。这里项目文档一般放置于项目对应的文档下,但是需要在文档文件夹下建立快捷方式。</li><li>其他文件。比如收集的视频、云文件、下载的.torrent以及资源等。</li></ol><h1 id="文件夹"><span class="github-emoji" data-alias="file_folder" style="" data-fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/1f4c1.png?v8">📁</span>文件夹</h1><p>不管是何种OS,其文件系统都是树形结构的。对于如何管理文件,其核心是制定工作流,参考<a href="https://blog.fkynjyq.com/manage-your-computer-files#a8b5ae8e7fad40b4aa8ebb855e08a252">这篇博客</a>,我大概总结出的工作模式是:</p><ul><li>特殊文件夹,比如<code>Download/</code>文件夹,这个文件夹下可以管理很多软件安装包😂。</li><li>定期Archive,定期整理临时文件,并把文件进行归档,注意写<code>README.md</code></li><li>定义好分类,这里的分类是指树结构:</li></ul><p><img src="https://image-bed-of-zorogh.oss-cn-beijing.aliyuncs.com/img/202306161707449.png" alt="file sys tree" style="zoom:50%;" /></p><h1 id="可能会遇到的问题"><span class="github-emoji" data-alias="question" style="" data-fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2753.png?v8">❓</span>可能会遇到的问题</h1><ol type="1"><li>如果距离上一次打开某一个文件时间比较久远,可能无法迅速找到对应的文件,此时可以借助工具。</li><li>对于Dev文件夹下的项目,必须配备相应的项目doc文档文件夹,用于管理项目,比如vivado文件夹下的工程需要配备一个doc文件夹,里面包含了各种.v文件的说明,Update Log以及整个项目的README.md文件。</li><li>重复的文件,比如遇到某一个datasheet,可能有不同的版本(厂商提供的,开发板提供的,官网的)。文件更新以及去重是一个比较麻烦的事情,其执行后的效果是能让你管理和检索文件起来更加方便、清晰,对于文档文件,因此这就要求文件有统一的命名方式。</li><li>版本问题,对于有git管理的文件夹而言,版本并不是大问题,但是其中的文件与版本是否对应则是一个需要考虑的事情,比如对于cadence中or cad软件中本身包括了<code>Rev.</code>和<code>Last Edit Time</code>两个字段,那么需要使用者自己去同步所有的字段,这部分所造成的时间消耗将会变得很多,需要通过制定工作流的方式,以及约定好版本名称管理的方式来进行项目管理。</li></ol><h1 id="命名习惯">命名习惯</h1><p>项目代码文件按照项目的命名规范来执行,比如Vivado项目中的一些固定文件夹。</p><p>对于文档文件,如果没有git进行管理,那么需要按照<code><name>_<date>.extenion</code>来命名,文件夹前不需要有序号标识,要减少使用含义模糊的文件夹和文件名称,比如<code>Data/dataAnalysis.xlsx</code>。</p><p>如果是临时文件,特别是项目合作中的文件,可以进一步划分为<code><author>_<name>_<date>_<time>.extension</code></p><p><strong><em>如果是代码文件,请尽可能的使用git来管理版本,如果要手动管理版本,那么需要在README.md文件中自行添加特殊版本标识的信息</em></strong></p><h1 id="文件管理工作流">文件管理工作流</h1><p>项目文件</p><figure><img src="http://image-bed-of-zorogh.oss-cn-beijing.aliyuncs.com/img/项目管理流程.png" alt="项目管理流程" /><figcaption aria-hidden="true">项目管理流程</figcaption></figure><p>文档文件</p><figure><img src="http://image-bed-of-zorogh.oss-cn-beijing.aliyuncs.com/img/文档管理流程.png" alt="文档管理流程" /><figcaption aria-hidden="true">文档管理流程</figcaption></figure><p>该工作流只是临时想出来的一个参考,不一定完全适用。</p>]]></content>
<tags>
<tag>管理</tag>
</tags>
</entry>
<entry>
<title>大学4年</title>
<link href="/2023/06/03/%E5%A4%A7%E5%AD%A64%E5%B9%B4/"/>
<url>/2023/06/03/%E5%A4%A7%E5%AD%A64%E5%B9%B4/</url>
<content type="html"><![CDATA[<h1 id="写在4年的结尾">写在4年的结尾</h1><p>本科就4年,时间太短了。今天晚上吃完饭从新食堂出来,看见对面的一片小林子,风和阳光刚刚好,可是四年就这么结束了,不免有点惋惜。</p><p>大四最后一段时间,我在实验室过得还算不错。认真的说,我觉得本科毕业设计做的并不完美,学到了很多东西,但是很零碎,没有成体系。也罢,哪有那么完美的事情呢。</p><p><img src="https://image-bed-of-zorogh.oss-cn-beijing.aliyuncs.com/img/202306051204954.jpg" alt="5月喜欢上的咖啡" style="zoom:20%;" /></p><p>我经常在想自己做的是不是已经OK了,是不是已经做自己的极限了。</p><p>今天走在路上,想了想自己以前学习的时候,和现在相比,也许是少了很多杂念。大学四年你不仅要学会专业课知识,还要学会做人,做一个社会意义上的人。但从一个有各种牵挂在身上的人,再进入那种忘我的状态,实在是不一样了。若能抛弃掉生活的压力潜心做自己喜欢的事情,那可真是美好,可现实是不能。</p><h1 id="这四年你过得怎么样">这四年你过得怎么样?</h1><p>值得,总结来看就是<strong>值得</strong>。</p><h2 id="学习">学习</h2><p>大学四年,虽然被疫情搅浑了3年,但也让我看明白了自己。实际上,成绩排名什么的并没有那么重要,它只是一个你是否有花时间在课程上的一个证明。学习新的技术,就与课程脱节,特别是当课程还教授着上个世纪最古老的技术时,此刻就觉得学习课程意义不大了,其意义仅剩下应付考试。</p><p>我花了很多时间折腾,折腾电脑、硬盘、网络,乱七八糟的什么东西都尝试一番,我觉得值,至少我曾去了解过了某一个行业、某一个领域。虽然这对于成绩没有任何意义,但是能开拓你的眼界。</p><h2 id="生活">生活</h2><p>羞愧的说,四年来我的身体素质并没有变好,大二时期是身体的巅峰,后来一路下滑,现在更是菜的不行。想恢复曾经的活力,还需要很多努力。强壮的身体确实非常之重要。</p><h2 id="爱情">爱情</h2><p>幸运的是,我在大学脱单了,从长远的角度来看,这是最值得的一件事情。</p><h1 id="未来会如何">未来会如何?</h1><p>在BIT读研继续深造,希望自己还是能保持折腾的心,人生的意义就是在于折腾。还有很多想学的东西,希望自己能认真学习,认真做项目,认真生活。</p>]]></content>
<categories>
<category>生活</category>
</categories>
<tags>
<tag>life</tag>
</tags>
</entry>
<entry>
<title>ZYNQ | GPIO、MIO、EMIO</title>
<link href="/2023/02/05/ZYNQ-MIO%E3%80%81EMIO/"/>
<url>/2023/02/05/ZYNQ-MIO%E3%80%81EMIO/</url>
<content type="html"><![CDATA[<h1 id="intro">Intro</h1><p>MIO与EMIO是ZYNQ芯片PS部分一个相当重要的接口。GPIO是MIO的一部分,要了解GPIO,首先需要了解ZYNQ的整体架构。</p><figure><img src="http://image-bed-of-zorogh.oss-cn-beijing.aliyuncs.com/img/image-20230205211220050.png" alt="ZYNQ架构" /><figcaption aria-hidden="true">ZYNQ架构</figcaption></figure><p>ZYNQ主要由以下主要功能区块组成,这些模块均能在上图中找到对应的位置:</p><ul><li>Processing System(PS)<ul><li>Application processor unit(APU)</li><li>Memory interface</li><li>I/O peripherals(IOP)</li><li>Interconnect</li></ul></li><li>Programmable Logic(PL)</li></ul><p>GPIO处于IOP中,是一个外设,能通过MIO模块能对器件的引脚进行操作,同时也通过EMIO提供了对PL的访问能力(包括64个inputs和128个outputs)。</p><blockquote><p>The general purpose I/O (GPIO) peripheral provides software with observation and control of up to 54 device pins via the MIO module. It also provides access to 64 inputs from the Programmable Logic (PL) and 128 outputs to the PL through the EMIO interface. <strong>The GPIO is organized into four banks of registers</strong> that group related interface signals.</p><p>Each GPIO is independently and dynamically programmed as input, output, or interrupt sensing. Software can read all GPIO values within a bank using a single load instruction, or write data to one or more GPIOs (within a range of GPIOs) using a single store instruction. The GPIO control and status registers are memory mapped at base address 0xE000_A000.</p></blockquote><p><img src="http://image-bed-of-zorogh.oss-cn-beijing.aliyuncs.com/img/image-20230205212958106.png" alt="GPIO Block Diagram" style="zoom: 67%;" /></p><p>对GPIO的控制是通过一系列<strong><em>存储器映射(Memory Mapped)</em></strong>的寄存器实现的。</p><p><img src="http://image-bed-of-zorogh.oss-cn-beijing.aliyuncs.com/img/image-20230205213504858.png" alt="image-20230205213504858" style="zoom:80%;" /></p><p>可以看到这里的MIO/EMIO与寄存器的连接方式都是一样的,</p>]]></content>
<categories>
<category>ZYNQ</category>
</categories>
<tags>
<tag>ZYNQ</tag>
<tag>GPIO</tag>
</tags>
</entry>
<entry>
<title>ZYNQ | QSPI控制器</title>
<link href="/2023/02/05/ZYNQ-QSPI%E6%8E%A7%E5%88%B6%E5%99%A8/"/>
<url>/2023/02/05/ZYNQ-QSPI%E6%8E%A7%E5%88%B6%E5%99%A8/</url>
<content type="html"><![CDATA[<p><span class="github-emoji" data-alias="lock" style="" data-fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/1f512.png?v8">🔒</span></p>]]></content>
<categories>
<category>ZYNQ</category>
<category>QSPI</category>
</categories>
<tags>
<tag>ZYNQ</tag>
<tag>Flash</tag>
<tag>QSPI</tag>
</tags>
</entry>
<entry>
<title>ZYNQ | 可重构技术PCAP(2)——Flash启动</title>
<link href="/2023/02/05/ZYNQ-%E5%8F%AF%E9%87%8D%E6%9E%84%E6%8A%80%E6%9C%AFPCAP-Flash%E5%90%AF%E5%8A%A8/"/>
<url>/2023/02/05/ZYNQ-%E5%8F%AF%E9%87%8D%E6%9E%84%E6%8A%80%E6%9C%AFPCAP-Flash%E5%90%AF%E5%8A%A8/</url>
<content type="html"><![CDATA[<h1 id="intro">Intro</h1><p>上一篇博客<a href="/2023/02/04/ZYNQ-%E5%8F%AF%E9%87%8D%E6%9E%84%E6%8A%80%E6%9C%AFPCAP/" title="ZYNQ|可重构技术PCAP--例程学习">ZYNQ|可重构技术PCAP--例程学习</a>中实现了从DDR到PL端的过程。然而在实际生产中,不能通过JTAG下载elf文件到PS再进行可重构,这就要求产品需要用非易失存储器来保存代码。ZYNQ官方文档指定的非易失存储器有4种:QSPI、SD卡、NAND、NOR,具体参考UG585第六章。笔者使用的是Alinx7020开发板,其PS端的MIO[2:6]连接着一片Flash,而对该Flash的读写则可通过ZYNQ内置QSPI控制器来读写。</p><p>生产环境下的效果应该是:</p><ol type="1"><li>选择QSPI模式进行启动</li><li>上电,开发板加载启动镜像</li><li>PS端通过QSPI控制器读取Flash中的比特流文件并烧录到PL端</li><li>PL端开始工作</li></ol><p>为了简化开发流程,这里省略掉前2步,使用JTAG模式将PS端代码下载到开发板,然后执行步骤3和4。</p><h1 id="preparation">Preparation</h1><p>首先要了解FLash存储器中包含的即是我们要写到PL端的完整数据,而FPGA需要的是<code>.bit</code>文件而非<code>.bin</code>文件,这一点在上篇博客中也提到过,具体两者的区别可以参考Xilinx官方的一篇回答<a href="https://support.xilinx.com/s/question/0D52E00006iHjvDSAS/how-to-use-pcap-to-config-the-pl-in-zynq?language=en_US">How to use PCAP to config the PL in zynq</a>,该帖子中<code>ckohn</code>是<em>部分可重构技术</em>文档的作者,他提到</p><blockquote><p>A <code>.bin</code> file is the binary representation of the configuration bitstream. The <code>.bit</code> contains the configuration data plus additional data in the bit file header.</p></blockquote><p>具体转换方法参考<a href="/2023/02/04/ZYNQ-%E5%8F%AF%E9%87%8D%E6%9E%84%E6%8A%80%E6%9C%AFPCAP/" title="上一篇博客">上一篇博客</a>。</p><h2 id="烧写flash">烧写Flash</h2><p>打开SDK,将准备好的<code>.bin</code>文件烧写进Flash中。</p><figure><img src="http://image-bed-of-zorogh.oss-cn-beijing.aliyuncs.com/img/image-20230205134538834.png" alt="烧写Flash" /><figcaption aria-hidden="true">烧写Flash</figcaption></figure><p>这里的<code>Offset</code>即为文件在Flash中的起始地址,默认用0即可。</p><h2 id="通过qspi读取flash">通过QSPI读取Flash</h2><p>导入QSPI轮询例程,如果没有,需要返回到Vivado,在Block Design中将MIO部分的QSPI的打开。</p><p>将QSPI例程中的ReadBuffer修改至合适大小,注意,这里可以选择直接在全局声明数组,或者在main中调用malloc函数。如果选择后者,那么需要在<code>lscript.ld</code>中将堆的大小修改至大于文件大小。</p><p>简化后的函数如下</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-type">int</span> <span class="hljs-title function_">QspiFlashPolled</span><span class="hljs-params">(XQspiPs *QspiInstPtr, u16 QspiDeviceId,u32 Addr,u32 DataSize)</span><br>{<br><span class="hljs-type">int</span> Status;<br> <span class="hljs-comment">// 经典套路,查询并初始化</span><br>XQspiPs_Config * QspiConfig = XQspiPs_LookupConfig(QSPI_DEVICE_ID);<br>XQspiPs_CfgInitialize(QspiInstPtr, QspiConfig,QspiConfig->BaseAddress);<br><span class="hljs-comment">// 自检</span><br> Status = XQspiPs_SelfTest(QspiInstPtr);<br><span class="hljs-keyword">if</span>(Status!=XST_SUCCESS)<br>{<br>xil_printf(<span class="hljs-string">"Self Test Failed\r\n"</span>);<br><span class="hljs-keyword">return</span>;<br>}<br><span class="hljs-comment">// 例程自带选项不用改</span><br>XQspiPs_SetOptions(QspiInstPtr, <br> XQSPIPS_MANUAL_START_OPTION | <br> XQSPIPS_FORCE_SSELECT_OPTION |<br> XQSPIPS_HOLD_B_DRIVE_OPTION);<br>XQspiPs_SetClkPrescaler(QspiInstPtr, XQSPIPS_CLK_PRESCALE_8);<br>XQspiPs_SetSlaveSelect(QspiInstPtr);<br><span class="hljs-comment">// 使能QSPI</span><br>FlashQuadEnable(QspiInstPtr);<br> <span class="hljs-comment">// Flash读函数,将数据读取到ReadBuffer中,ReadBuffer前4字节为Overhead</span><br>FlashRead(QspiInstPtr, Addr, DataSize, READ_CMD);<br>xil_printf(<span class="hljs-string">"End reading flash\r\n"</span>);<br><span class="hljs-keyword">return</span> XST_SUCCESS;<br>}<br></code></pre></td></tr></table></figure><p>这里就能将Flash中文件读取进来。即<code>u8 * binFile = ReadBuffer + 4;</code></p><p>为了检验是否读取完整,可以通过<code>XSCT</code>窗口命令来将数据保存到电脑中进行验证。</p><p>这里提前用串口将buf的地址打印出来,我这里是<code>0x1104CC</code>,文件长度是4045564,除以4得到1011391。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">xsct% </span><span class="language-bash"><span class="hljs-built_in">cd</span> C:/Dev/temp</span><br>mrd -bin -file flashRead.bin 0x1104CC 1011391<br><span class="hljs-meta prompt_"># </span><span class="language-bash">mrd -bin -file name.bin startAddr SizeInWords</span><br></code></pre></td></tr></table></figure><p>这样就能把文件保存到电脑上了,至于如何验证该文件是否与源文件一致,可以使用SHA-1校验,校验码一致则表示两个文件相同,这里不再过多赘述。</p><div class="note note-danger"> <p>这里是个坑,当你发现两个文件校验通过时,完整的文件到了内存中,也就是完成了从Flash到DDR的过程。</p> </div><p>实际上,实验做到这里,应该说接近完成了,总共分两步:</p><ol type="1"><li>从Flash读取文件</li><li>将文件烧写到PL端</li></ol><p>第一步已经完成,第二步参考上一篇博客,只是需要将参数进行修改:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-comment">// 例程调用方式</span><br>XDcfgPolledExample(&DcfgInstance, DCFG_DEVICE_ID,BIT_STREAM_LOCATION,BIT_STREAM_SIZE_WORDS);<br><span class="hljs-comment">// 修改后,注意这里的bitLoc = ReadBuffer+4</span><br><span class="hljs-comment">// 而 bitLoc+1是根据API的注释来的,表明这是最后一次DMA传输</span><br>XDcfgPolledExample(&DcfgInstance, DCFG_DEVICE_ID,(u8*)(bitLoc+<span class="hljs-number">1</span>),(<span class="hljs-number">4045564</span>)>><span class="hljs-number">2</span>);<br></code></pre></td></tr></table></figure><p>至此,从理论上讲,第二步也应该完成了。但实际上程序运行后板子并没有相应的结果显示,Done指示灯也不会亮。这个Bug困扰了我好几天。</p><h2 id="坑-填坑">坑 & 填坑</h2><h3 id="坑">坑</h3><p>与其说是坑,不如说自己对体系理解不到位。一开始,我不断Debug,总结下来就是:仅仅改变bit文件的地址,就会有不同的效果,官方的例程就是能成功,而我的就是不行。后来尝试了将flash读出来的文件填写到高地址(即在高地址声明指针),可是这种操作仍然不起作用。</p><p>后来在师兄的帮助下,填了坑。</p><p>XDcfgPolledExample()函数中实际起到关键作用的是XDcfg_Transfer()函数,参考API文档:</p><blockquote><p>This function starts the DMA transfer.</p><p>This function only starts the operation and returns before the operation may be completed. If the interrupt is enabled, an interrupt will be generated when the operation is completed, otherwise it is necessary to poll the Status register to determine when it is completed. It is the responsibility of the caller to determine when the operation is completed by handling the generated interrupt or polling the Status Register.</p></blockquote><blockquote><p><strong><em>Note</em></strong></p><p><strong><em>It is the responsibility of the caller to ensure that the cache is flushed and invalidated both before the DMA operation is started and after the DMA operation completes if the memory pointed to is cached.</em></strong> The caller must also ensure that the pointers contain physical address rather than a virtual address if address translation is being used.</p><p>The 2 LSBs of the SourcePtr (Source)/ DestPtr (Destination) address when equal to 2'b01 indicates the last DMA command of an overall transfer.</p></blockquote><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs C">u32 <span class="hljs-title function_">XDcfg_Transfer</span><span class="hljs-params">(</span><br><span class="hljs-params"> XDcfg * InstancePtr,</span><br><span class="hljs-params"> <span class="hljs-type">void</span> * SourcePtr,</span><br><span class="hljs-params"> u32 SrcWordLength,</span><br><span class="hljs-params"> <span class="hljs-type">void</span> * DestPtr,</span><br><span class="hljs-params"> u32 DestWordLength,</span><br><span class="hljs-params"> u32 TransferType </span><br><span class="hljs-params">)</span><br></code></pre></td></tr></table></figure><p>文档提到这里是该函数本质上是DMA传输,DMA是对DDR3这个器件直接进行数据操作,因此,如果数据不在DDR中,自然也不会达到想要的效果。</p><p>文档也提到了,DMA传输前后均要检查<code>cache</code>,而cache,正是这个坑,具体而言就是从Flash中读出来的文件还在Cache中而没有被更新到DDR中,因此导致了传输的文件和理论上的不一致。</p><h3 id="填坑">填坑</h3><p>解决办法很简单,既然问题出现在cache,应对方法有两种:</p><ol type="1"><li>关掉cache</li><li>在DMA前进行Cache的Flush操作。</li></ol><h3 id="为什么会出现这个坑">为什么会出现这个坑</h3><p>这个问题实际上叫“Cache一致性问题”<sup id="fnref:1" class="footnote-ref"><a href="#fn:1" rel="footnote"><span class="hint--top hint--rounded" aria-label="[ZYNQ Cache一致性问题和使用](https://blog.csdn.net/wangjie36/article/details/121460830)">[1]</span></a></sup>。</p><p>实验过程中,笔者通过XSCT窗口<code>mrd</code>命令下载了文件,进行了一次文件校验,校验结果显示从Flash中读取的文件没有错,的确如此,但是要注意,这里的XSCT窗口是直接与CPU打交道,因此当CPU再读取某一段数据时,如果数据存在于Cache中则会优先读取Cache中的数据,而非DDR中的数据。因此<code>mrd</code>命令是站在CPU的角度来观察整个<em>存储体系</em>。</p><p>而DMA控制器作为DDR的另一个主控,直接与DDR进行数据交换,其对应的不是<strong><em>CPU视角下的整个存储体系</em></strong>。明白了这一点问题就容易多了。</p><p>至此可重构实验基本完成。</p><h1 id="reference">Reference</h1><section class="footnotes"><div class="footnote-list"><ol><li><span id="fn:1" class="footnote-text"><span><a href="https://blog.csdn.net/wangjie36/article/details/121460830">ZYNQ Cache一致性问题和使用</a> <a href="#fnref:1" rev="footnote" class="footnote-backref"> ↩︎</a></span></span></li></ol></div></section>]]></content>
<categories>
<category>ZYNQ</category>
<category>可重构</category>
</categories>
<tags>
<tag>ZYNQ</tag>
<tag>Flash</tag>
<tag>QSPI</tag>
<tag>可重构</tag>
</tags>
</entry>
<entry>
<title>ZYNQ | 可重构技术PCAP(1)——例程学习</title>
<link href="/2023/02/04/ZYNQ-%E5%8F%AF%E9%87%8D%E6%9E%84%E6%8A%80%E6%9C%AFPCAP/"/>
<url>/2023/02/04/ZYNQ-%E5%8F%AF%E9%87%8D%E6%9E%84%E6%8A%80%E6%9C%AFPCAP/</url>
<content type="html"><![CDATA[<h1 id="intro-concept">Intro & Concept</h1><p>这学期开始着手做毕设了,毕设里一个重要的组成部分是使用Zynq7020实现可重构技术,大概的目标是板子上电后,能从存储器(Flash、ROM啥啥啥的)中读取bit stream并将其下载到PL端。网上参考都是利用PS把<strong>bit stream</strong>下载到PL端。注意,这里是全局重构(Full configuration,我瞎起的名字),指的是把完整的bit stream文件下载到PL端,而与之对应的还有另外一种技术叫<strong><em>Partial Reconfiguration</em></strong>,本人暂时还妹学会,跳过。</p><p>为什么是从PS下载到PL端,而不能直接把bit stream下载到PL端呢?<s>当然是没有参考我不会写啦。</s>实际上这是对ZYNQ整个架构的理解问题。</p><p>ZYNQ一般被看成PS+PL,但是在下载程序时的PS与PL实际上地位不等,不能简单的认为是ARM芯片+FPGA芯片,然后中间用许多线连着。(虽然也可以这么说)参考<em>UG585 Chapter 6 Figure 6-1</em>,这里很明显可以看到在ZYNQ启动后(假设是从Flash启动),那么APU会先执行BootROM,然后是FSBL、SSBL,在非安全模式下(non-secure mode),PL端是在FSBL和SSBL期间被Programed。</p><blockquote><p>The FSBLcode can <strong>clear</strong>, program and <strong>enable</strong> the PL.</p></blockquote><p><img src="http://image-bed-of-zorogh.oss-cn-beijing.aliyuncs.com/img/image-20230116175747052.png" alt="image-20230116175747052" /></p><p>这就意味着PL是后于PS启动。继续阅读<em>UG585 Chapter 6</em>,这里摘一下文档。</p><blockquote><p><strong>The PL can be configured and reconfigured by PS software in secure or non-secure mode. The PCAP</strong> <strong>path is the most commonly deployed method as it does not require that the PL be pre-programmed</strong> <strong>with a bitstream.</strong> The PL can also be configured by the TAP controller on the JTAG chain in non-secure mode. Multiplexing of the datapath is done in the PL configuration module using the devc.CTRL [PCAP_MODE] and [PCAP_PR] bits.</p><p>-- from 6.1.8 PL Configuration Paths.</p></blockquote><p>这段话就表明了可重构技术是可能实现的。(废话</p><p>UG585给了总共3条路,具体参考下图:</p><p><img src="http://image-bed-of-zorogh.oss-cn-beijing.aliyuncs.com/img/image-20230117191409551.png" alt="image-20230117191409551" /></p><ul><li>JTAG Debug Path</li><li>PCAP Path</li><li>ICAP Path</li></ul><p>三条路都可以对PL进行配置(配置指configure,烧写比特流)。但<code>ICAP Path</code>需要占用PL的逻辑,对于部署而言不友好,处于一个很尴尬的地位。那剩下的两条路,JTAG模式是在调试环境下使用,实际生产工作环境中,PS端从Flash读取启动镜像后,就会通过<code>PCAP Path</code>来Program PL。因此,在启动阶段,可以把PL看成PS的一个外设,下载数据的通路即是PCAP Path。可以看到图中两个多路选择器实际上都是devc的寄存器值,所以可重构的关键就是正确配置devc的寄存器。具体编程指南参考UG585 Chapter 6,(虽然我也妹读完呐)。</p><p>理论成立,实践开始。</p><h1 id="start-code">Start Code</h1><h2 id="preparation">Preparation</h2><p>本次实验使用的是Alinx7020开发板,连接电源线、UART、JTAG。一通操作,创建Vivado工程、创建Block Design、添加ZYNQ IP核、打开MIO的QSPI(后续要用)、UART、GPIO(optional)。添加好后继续无脑操作,Auto连线、Validate验证、Wrapper、Generate,导出硬件,打开SDK,创建空工程。</p><p>另外,要提前准备一份比特流,并通过JTAG下载到PL端进行验证,记住该bit流的效果。笔者准备了一个LED闪烁的bit stream,这里记住,从PS端烧写到PL端的不是<code>.bit</code>文件,而是比特流对应的<code>.bin</code>文件,通过Vivado自带的工具可以进行转换。</p><p>选择<code>Generate Memory Configuration File...</code>,</p><p><img src="http://image-bed-of-zorogh.oss-cn-beijing.aliyuncs.com/img/image-20230204210839579.png" alt="image-20230204210839579" /></p><p>参数如下,该操作即将<code>.bit</code>文件转换为<code>.bin</code>文件。</p><p><img src="http://image-bed-of-zorogh.oss-cn-beijing.aliyuncs.com/img/image-20230204211044519.png" alt="image-20230204211044519" /></p><h2 id="devc-example">DevC Example</h2><p>打开SDK后并创建好工程后,在板级支持包中找到<code>system.mss</code>,可以找到<code>devcfg</code>的例程,导入轮询例程,下面截取一部分代码:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-type">int</span> <span class="hljs-title function_">XDcfgPolledExample</span><span class="hljs-params">(XDcfg *DcfgInstPtr, u16 DeviceId)</span><br>{<br> <span class="hljs-type">int</span> Status;<br> u32 IntrStsReg = <span class="hljs-number">0</span>;<br> u32 StatusReg;<br> u32 PartialCfg = <span class="hljs-number">0</span>;<br> <br> XDcfg_Config *ConfigPtr;<br> <span class="hljs-comment">// 常规初始化流程,查询、初始化</span><br> ConfigPtr = XDcfg_LookupConfig(DeviceId);<br> Status = XDcfg_CfgInitialize(DcfgInstPtr, ConfigPtr, ConfigPtr->BaseAddr);<br> <span class="hljs-keyword">if</span> (Status != XST_SUCCESS)<br> {<br> <span class="hljs-keyword">return</span> XST_FAILURE;<br> }<br><span class="hljs-comment">// 自检</span><br> Status = XDcfg_SelfTest(DcfgInstPtr);<br> <span class="hljs-keyword">if</span> (Status != XST_SUCCESS)<br> {<br> <span class="hljs-keyword">return</span> XST_FAILURE;<br> }<br><span class="hljs-comment">// 这部分是关于部分可重构的,本实验不用管</span><br> <span class="hljs-comment">/*</span><br><span class="hljs-comment"> * Check first time configuration or partial reconfiguration</span><br><span class="hljs-comment"> */</span><br> IntrStsReg = XDcfg_IntrGetStatus(DcfgInstPtr);<br> <span class="hljs-keyword">if</span> (IntrStsReg & XDCFG_IXR_DMA_DONE_MASK)<br> {<br> PartialCfg = <span class="hljs-number">1</span>;<br> }<br><span class="hljs-comment">// 使能时钟</span><br> <span class="hljs-comment">/*</span><br><span class="hljs-comment"> * Enable the pcap clock.</span><br><span class="hljs-comment"> */</span><br> StatusReg = Xil_In32(SLCR_PCAP_CLK_CTRL);<br> <span class="hljs-keyword">if</span> (!(StatusReg & SLCR_PCAP_CLK_CTRL_EN_MASK))<br> {<br> Xil_Out32(SLCR_UNLOCK, SLCR_UNLOCK_VAL);<br> Xil_Out32(SLCR_PCAP_CLK_CTRL, (StatusReg | SLCR_PCAP_CLK_CTRL_EN_MASK));<br> Xil_Out32(SLCR_UNLOCK, SLCR_LOCK_VAL);<br> }<br><br> <span class="hljs-comment">/*</span><br><span class="hljs-comment"> * Disable the level-shifters from PS to PL.</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">if</span> (!PartialCfg)<br> {<br> Xil_Out32(SLCR_UNLOCK, SLCR_UNLOCK_VAL);<br> Xil_Out32(SLCR_LVL_SHFTR_EN, <span class="hljs-number">0xA</span>);<br> Xil_Out32(SLCR_LOCK, SLCR_LOCK_VAL);<br> }<br><br> <span class="hljs-comment">/*</span><br><span class="hljs-comment"> * Select PCAP interface for partial reconfiguration</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">if</span> (PartialCfg)<br> {<br> XDcfg_EnablePCAP(DcfgInstPtr);<br> XDcfg_SetControlRegister(DcfgInstPtr, XDCFG_CTRL_PCAP_PR_MASK);<br> }<br><br> <span class="hljs-comment">/*</span><br><span class="hljs-comment"> * Clear the interrupt status bits</span><br><span class="hljs-comment"> */</span><br> XDcfg_IntrClear(DcfgInstPtr, (XDCFG_IXR_PCFG_DONE_MASK |<br> XDCFG_IXR_D_P_DONE_MASK |<br> XDCFG_IXR_DMA_DONE_MASK));<br><br> <span class="hljs-comment">/* Check if DMA command queue is full */</span><br> StatusReg = XDcfg_ReadReg(DcfgInstPtr->Config.BaseAddr,<br> XDCFG_STATUS_OFFSET);<br> <span class="hljs-keyword">if</span> ((StatusReg & XDCFG_STATUS_DMA_CMD_Q_F_MASK) ==<br> XDCFG_STATUS_DMA_CMD_Q_F_MASK)<br> {<br> <span class="hljs-keyword">return</span> XST_FAILURE;<br> }<br><br> <span class="hljs-comment">/*</span><br><span class="hljs-comment"> * Download bitstream in non secure mode</span><br><span class="hljs-comment"> * 该函数就是实际将bit流下载到PL端的函数</span><br><span class="hljs-comment"> */</span><br> XDcfg_Transfer(DcfgInstPtr, (u8 *)BIT_STREAM_LOCATION,<br> BIT_STREAM_SIZE_WORDS,<br> (u8 *)XDCFG_DMA_INVALID_ADDRESS,<br> <span class="hljs-number">0</span>, XDCFG_NON_SECURE_PCAP_WRITE);<br><br> <span class="hljs-comment">/* Poll IXR_DMA_DONE */</span><br> IntrStsReg = XDcfg_IntrGetStatus(DcfgInstPtr);<br> <span class="hljs-keyword">while</span> ((IntrStsReg & XDCFG_IXR_DMA_DONE_MASK) !=<br> XDCFG_IXR_DMA_DONE_MASK)<br> {<br> IntrStsReg = XDcfg_IntrGetStatus(DcfgInstPtr);<br> }<br><br> <span class="hljs-keyword">if</span> (PartialCfg)<br> {<br> <span class="hljs-comment">/* Poll IXR_D_P_DONE */</span><br> <span class="hljs-keyword">while</span> ((IntrStsReg & XDCFG_IXR_D_P_DONE_MASK) !=<br> XDCFG_IXR_D_P_DONE_MASK)<br> {<br> IntrStsReg = XDcfg_IntrGetStatus(DcfgInstPtr);<br> }<br> }<br> <span class="hljs-keyword">else</span><br> {<br> <span class="hljs-comment">/* Poll IXR_PCFG_DONE */</span><br> <span class="hljs-keyword">while</span> ((IntrStsReg & XDCFG_IXR_PCFG_DONE_MASK) !=<br> XDCFG_IXR_PCFG_DONE_MASK)<br> {<br> IntrStsReg = XDcfg_IntrGetStatus(DcfgInstPtr);<br> }<br> <span class="hljs-comment">/*</span><br><span class="hljs-comment"> * Enable the level-shifters from PS to PL.</span><br><span class="hljs-comment"> */</span><br> Xil_Out32(SLCR_UNLOCK, SLCR_UNLOCK_VAL);<br> Xil_Out32(SLCR_LVL_SHFTR_EN, <span class="hljs-number">0xF</span>);<br> Xil_Out32(SLCR_LOCK, SLCR_LOCK_VAL);<br> }<br><br> <span class="hljs-keyword">return</span> XST_SUCCESS;<br>}<br></code></pre></td></tr></table></figure><p>实际上这个函数非常简单,即初始化+DMA,<code>XDcfg_Transfer()</code>函数是实际将bit流写入到PL端的函数。函数原型如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs C">u32 <span class="hljs-title function_">XDcfg_Transfer</span><span class="hljs-params">(</span><br><span class="hljs-params"> XDcfg * InstancePtr, <span class="hljs-comment">// XDcfg 实例指针</span></span><br><span class="hljs-params"> <span class="hljs-type">void</span> * SourcePtr,<span class="hljs-comment">// bit Stream 地址</span></span><br><span class="hljs-params"> u32 SrcWordLength,<span class="hljs-comment">// bit stream 长度÷4(Size in Words,32bit)</span></span><br><span class="hljs-params"> <span class="hljs-type">void</span> * DestPtr,<span class="hljs-comment">// 目标指针</span></span><br><span class="hljs-params"> u32 DestWordLength,<span class="hljs-comment">// 待传输到目标地址的数据长度(Size in Words)</span></span><br><span class="hljs-params"> u32 TransferType <span class="hljs-comment">// 传输类型,参考xdevcfg.h中宏定义</span></span><br><span class="hljs-params">)</span><br></code></pre></td></tr></table></figure><p>很明显,这里DMA传输的目的地为PL端,参考程序中只需要修改源地址和数据长度即可将任意bit流下载到PL端。例程中的宏定义<code>BIT_STREAM_LOCATION</code>以及<code>BIT_STREAM_SIZE_WORDS</code>,可以认为是一种不安全的操作,直接指定地址会造成数据的冲突。</p><h2 id="experiment">Experiment</h2><h3 id="从ddr到pl">从DDR到PL</h3><p>例程即是从DDR的某一个地址将bit文件通过DMA的方式传输到PL端。使用Xilinx SDK自带的工具将预先准备好的二进制文件</p><figure><img src="http://image-bed-of-zorogh.oss-cn-beijing.aliyuncs.com/img/image-20230204210807644.png" alt="image-20230204210807644" /><figcaption aria-hidden="true">image-20230204210807644</figcaption></figure><p>配置示例如下:</p><figure><img src="http://image-bed-of-zorogh.oss-cn-beijing.aliyuncs.com/img/image-20230204211528008.png" alt="image-20230204211528008" /><figcaption aria-hidden="true">image-20230204211528008</figcaption></figure><p>注意这里的写到DDR内存中的地址一定要注意,不能超出实际DDR的内存空间,地址也不能过低,由于运行PS端裸机代码运行过程中一般都会在较低的地址运行程序,因此需要避开低地址空间。(这也是这段例程最不好的地方,直接访问一个地址,既不安全也不可靠。)</p><p>注意,在将文件Restore进Memory之前,首先需要运行一下CPU,直接run,不管结果如何,目的是先让CPU启动。</p><p>然后将例程中的宏定义进行修改:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-comment">/* The 2 LSBs of the Source/Destination address when equal to 2'b01 indicate the last DMA command of an overall transfer.</span><br><span class="hljs-comment"> * The 2 LSBs of the BIT_STREAM_LOCATION in this example is set to 2b01 indicating that this is the last DMA transfer (and the only one).*/</span><br><span class="hljs-comment">// 实际这里如果是00也可以,但不懂为啥</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> BIT_STREAM_LOCATION0x30000001<span class="hljs-comment">/* Bitstream location */</span></span><br><span class="hljs-comment">// 笔者生成的bin文件大小为 4045564 Bytes,对应0xf6ebf in Words(32bit) ,下面这个值只要比文件值大即可,这里保持默认</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> BIT_STREAM_SIZE_WORDS0xF6EC0<span class="hljs-comment">/* Size in Words (32 bit)*/</span> </span><br></code></pre></td></tr></table></figure><p>修改完后,Restore Memory,运行程序,即可观察到相应的现象。</p><p>至此从DDR3到PL的可重构实验即完成。后续介绍从Flash读取文件并将其烧写到PL端。</p>]]></content>
<categories>
<category>ZYNQ</category>
<category>可重构</category>
</categories>
<tags>
<tag>ZYNQ</tag>
<tag>可重构</tag>
</tags>
</entry>
<entry>
<title>写博客的目的是什么</title>
<link href="/2023/01/11/%E5%86%99%E5%8D%9A%E5%AE%A2%E7%9A%84%E7%9B%AE%E7%9A%84%E6%98%AF%E4%BB%80%E4%B9%88/"/>
<url>/2023/01/11/%E5%86%99%E5%8D%9A%E5%AE%A2%E7%9A%84%E7%9B%AE%E7%9A%84%E6%98%AF%E4%BB%80%E4%B9%88/</url>
<content type="html"><![CDATA[<h1 id="写博客的目的是什么">写博客的目的是什么?</h1><center><b><p style="font-size:24px">日拱一卒,功不唐捐</p></b></center><h2 id="原因之一">原因之一</h2><p>曾经遇到过很多次这种情况,就是当我使用某一个“即时的”知识点时,直接去网上搜索就能找到答案,比如某一个语法、某一个bug,然后立马执行便能解决问题。但是过了一段时间,也许不超过半天,当再次遇到这个bug时,又需要重新去检索这些知识,这就造成了时间的浪费,不如用碎片时间将这些小bug的解决办法集中起来,这样就降低了检索难度。</p><h2 id="原因之二">原因之二</h2><p>做工程需要在各个领域反复横跳,往往是记不住一些知识点,甚至最后忘得一干二净,导致收获随着时间会不断流失。因此记录下当时的收获很有必要。</p><h2 id="原因之三">原因之三</h2><p>加强印象,积累。写博客不像做笔记,博客是写给别人看的,笔记是写给自己看的,尽量让知识传播的门槛降低,这样也有助于未来再看博客时加深自己的理解。</p>]]></content>
</entry>
</search>