<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>MOMENTS Blog</title>
    <description>Every failure is leading towards success.</description>
    <link>https://moment-new.github.io/</link>
    <atom:link href="https://moment-new.github.io/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Tue, 20 Oct 2026 07:28:52 +0000</pubDate>
    <lastBuildDate>Tue, 20 Oct 2026 07:28:52 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>技术之路</title>
        <description>&lt;p&gt;2022年，我注册了github。彼时我还是个青涩的少年，住在乡下一隅偏僻的小屋。那时的我能想象现在我走上了怎样一条道路吗？难以言说。&lt;/p&gt;
&lt;h1 id=&quot;5年前&quot;&gt;5年前&lt;/h1&gt;
&lt;p&gt;我的文笔怎么这么烂了….后面有时间重写吧&lt;/p&gt;
</description>
        <pubDate>Mon, 19 Oct 2026 00:00:00 +0000</pubDate>
        <link>https://moment-new.github.io/2026/10/19/%E6%8A%80%E6%9C%AF%E4%B9%8B%E8%B7%AF/</link>
        <guid isPermaLink="true">https://moment-new.github.io/2026/10/19/%E6%8A%80%E6%9C%AF%E4%B9%8B%E8%B7%AF/</guid>
        
        <category>感想</category>
        
        <category>学习</category>
        
        
      </item>
    
      <item>
        <title>圣光机数据处理笔记</title>
        <description>&lt;h1 id=&quot;前言&quot;&gt;前言&lt;/h1&gt;
&lt;p&gt;这门课我不算很感兴趣。纯当记录吧。&lt;br /&gt;
这个老师怎么这么爱互动。。。。压力up&lt;br /&gt;
第一二节课基本全是高中信息技术加概率论学过的东西：什么是数据，什么是信息之类云云。不过既然笔记不只是写给自己看的，所以就记了些关键词让AI扩充，把概念展开讲讲，算是顺便帮没学过的同学补个课。&lt;/p&gt;

&lt;h1 id=&quot;数据挖掘data-mining&quot;&gt;数据挖掘（Data Mining）&lt;/h1&gt;
&lt;p&gt;data mining，数据挖掘。名字听着高大上，实际干的事就一句话：&lt;strong&gt;从一大堆数据里把有价值的模式和规律挖出来&lt;/strong&gt;。最经典的案例就是”超市发现买啤酒的人经常顺手买尿布”，于是把俩货架摆到一起，销量双丰收。&lt;/p&gt;

&lt;p&gt;它通常被放在 KDD（Knowledge Discovery in Databases，数据库知识发现）流程的核心环节里：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;原始数据 → 数据预处理 → 数据挖掘 → 解释与评估 → 知识
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;注意挖掘本身只占中间一环，前面的清洗铺垫和后面的解释评估同样是工作量的大头。这也决定了这门课的展开顺序：先认识数据，再学怎么洗数据，最后才是统计与分析。&lt;/p&gt;

&lt;h1 id=&quot;数据长什么样数据的类型&quot;&gt;数据长什么样：数据的类型&lt;/h1&gt;
&lt;h2 id=&quot;结构化-vs-非结构化&quot;&gt;结构化 vs 非结构化&lt;/h2&gt;
&lt;p&gt;按组织形式，数据可以分成三类：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;结构化数据（structured）&lt;/strong&gt;：有固定的字段和格式，能直接塞进二维表。比如 Excel 表格、成绩单、数据库里的学生表。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;半结构化数据（semi-structured）&lt;/strong&gt;：没有严格的表结构，但自带描述信息的标记。典型代表是 JSON 和 XML——写嵌入式的对 JSON 应该不陌生，蓝牙广播包的解析配置就是它。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;非结构化数据（unstructured）&lt;/strong&gt;：完全没有预设格式，文本、图片、音频、视频都是。现实世界里最占存储的恰恰是这类。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;变量类型categorical-vs-numerical&quot;&gt;变量类型：categorical vs numerical&lt;/h2&gt;
&lt;p&gt;数据表里的每一列叫一个&lt;strong&gt;变量（variable）&lt;/strong&gt;。按取值的性质，变量分成两大门派：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;大类&lt;/th&gt;
      &lt;th&gt;子类&lt;/th&gt;
      &lt;th&gt;举例&lt;/th&gt;
      &lt;th&gt;能做的运算&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;分类变量（categorical）&lt;/td&gt;
      &lt;td&gt;定类（nominal）：只有类别之分，没有顺序&lt;/td&gt;
      &lt;td&gt;血型、性别、专业&lt;/td&gt;
      &lt;td&gt;计数、求占比、众数&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;定序（ordinal）：类别之间有高低之分&lt;/td&gt;
      &lt;td&gt;满意度（差/一般/好）、年级&lt;/td&gt;
      &lt;td&gt;上面全部 + 排序&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;定量变量（quantitative / numerical）&lt;/td&gt;
      &lt;td&gt;离散（discrete）：取值可数&lt;/td&gt;
      &lt;td&gt;人数、报错次数&lt;/td&gt;
      &lt;td&gt;上面全部 + 加减、平均&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;连续（continuous）：取值不可数，任意精度&lt;/td&gt;
      &lt;td&gt;身高、温度、电压&lt;/td&gt;
      &lt;td&gt;全部数学运算&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;分这个类绝对不是闲的，&lt;strong&gt;变量类型决定了后面能用什么工具&lt;/strong&gt;：分类变量谈不了”平均血型”，只能数个数看占比；定量变量才有资格算均值、方差、画直方图。拿到数据先看一眼类型，能避开一大半低级错误。&lt;/p&gt;

&lt;p&gt;顺带一提存储类型（data type）：同一个变量在不同软件里还有存储层面的区分，比如 numpy 里的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int64&lt;/code&gt;（64 位整数）、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;float64&lt;/code&gt;（64 位浮点数）、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;string&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bool&lt;/code&gt; 之类。float 和 int 的恩怨情仇、精度和溢出，写过 C 的都懂，不赘述了。第二节课在 elona 上讲的差不多就是这些，依然是很简单的东西。&lt;/p&gt;

&lt;p&gt;（分类变量定量变量这块老师整了个大活（））&lt;/p&gt;

&lt;h1 id=&quot;数据预处理data-preparation&quot;&gt;数据预处理（Data Preparation）&lt;/h1&gt;
&lt;p&gt;必须先建立一个认知：&lt;strong&gt;现实中的数据是脏的&lt;/strong&gt;。预处理就是洗数据，常见工作四类：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;处理错误（handling errors）&lt;/strong&gt;：录入错误（年龄填成 200）、明显异常的值、单位写错。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;处理残缺（handling missing）&lt;/strong&gt;：数据缺了怎么办。简单粗暴是整行删掉，但数据本来就少的时候很亏；更常用的做法是填补（imputation）——数值型变量用均值或中位数填，分类型变量用众数填。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;去重（deduplication）&lt;/strong&gt;：同一条记录被录了两遍，删掉一份。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;格式化（formatting）&lt;/strong&gt;：统一单位（米 vs 厘米）、统一日期格式、统一大小写。不然后面统计的时候”hangzhou”“Hangzhou”“HZ”会被当成三个城市。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;一句话总结：garbage in, garbage out。上游数据是脏的，后面的模型再漂亮也是白搭。&lt;/p&gt;

&lt;h1 id=&quot;统计学的两大门派&quot;&gt;统计学的两大门派&lt;/h1&gt;
&lt;p&gt;统计学可以分为描述性的（总结），另外一种是推理性的（预测）：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;描述统计（descriptive statistics）&lt;/strong&gt;：把手头这堆数据总结成几个数、几张图。全班期末平均分 75——说的就只是这 75 个人，对外面的事不置可否。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;推断统计（inferential statistics）&lt;/strong&gt;：用手头的样本去推测更大总体的性质。尝一勺汤，判断整锅的咸淡。涉及抽样、参数估计、假设检验，是概率论真正的地盘，这门课后面应该会讲到。&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;集中趋势meanmedianmode&quot;&gt;集中趋势：mean、median、mode&lt;/h1&gt;
&lt;p&gt;描述一组定量数据”中心在哪”，有三个最常用的数：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;指标&lt;/th&gt;
      &lt;th&gt;中文&lt;/th&gt;
      &lt;th&gt;怎么算&lt;/th&gt;
      &lt;th&gt;特点&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;mean&lt;/td&gt;
      &lt;td&gt;平均数&lt;/td&gt;
      &lt;td&gt;所有值加起来除以个数&lt;/td&gt;
      &lt;td&gt;用到了每个数据的信息，但&lt;strong&gt;对极端值毫无抵抗力&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;median&lt;/td&gt;
      &lt;td&gt;中位数&lt;/td&gt;
      &lt;td&gt;排序后正中间的那个数&lt;/td&gt;
      &lt;td&gt;对极端值免疫&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;mode&lt;/td&gt;
      &lt;td&gt;众数&lt;/td&gt;
      &lt;td&gt;出现次数最多的值&lt;/td&gt;
      &lt;td&gt;The mode is the value that a quantity takes most often。唯一一个在分类变量上也能用的&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;经典陷阱是”平均工资”：5 个员工月薪 5k，老板月薪 100w，平均下来”人均月薪 17w”，一片祥和；但中位数依然是 5k。&lt;strong&gt;数据分布偏态（有少数极端大值）的时候，中位数比均值诚实得多&lt;/strong&gt;——所以看房价、看收入，正经报告都会把 median 拎出来单独说。&lt;/p&gt;

&lt;h1 id=&quot;分位数percentile-与-quartile&quot;&gt;分位数：percentile 与 quartile&lt;/h1&gt;
&lt;p&gt;想更细致地描述数据的分布位置，就要用到分位数。套路是固定的：先 &lt;strong&gt;sort（排序）&lt;/strong&gt;，再谈位置。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;百分位数（percentile）&lt;/strong&gt;：P 分位数指的是”至少有 P% 的数据不超过它”。比如第 90 百分位数，就是 90% 的数据都排在它下面。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;四分位数（quartile）&lt;/strong&gt;：把排序后的数据切成四份的三个分位点。Q1 是 25% 位置（First quartile），Q2 就是中位数，Q3 是 75% 位置（Third quartile）。&lt;br /&gt;
（这怎么都是概率论的玩意，复习来了）&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;四分位距（IQR, interquartile range）&lt;/strong&gt;：IQR = Q3 − Q1，即中间那 50% 数据的跨度。比起极差（max − min），它不受两端极端值影响，是更稳健的”数据有多散”的度量。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;后面出现的 OUTliers、median、maximum、minimum 这些关键词，拼起来其实是&lt;strong&gt;箱线图（boxplot）&lt;/strong&gt;的五数概括（five-number summary）：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;minimum, Q1, median, Q3, maximum
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;箱线图就是把这五个数画在一张图上，一眼看出数据中心在哪、有多散、分布对不对称。它的隐藏功能是识别离群点（outlier）：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;正常范围：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[Q1 − 1.5×IQR, Q3 + 1.5×IQR]&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;超出这个范围的点就是离群点，在箱线图上画成单独的点点。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;离群点不一定是错误（也可能真是天才/异常样本），但一定会拉偏均值，所以它和预处理那一节是连着的：发现离群点 → 判断是删是留。&lt;/p&gt;

&lt;h1 id=&quot;相关性分析correlation-analysis&quot;&gt;相关性分析（Correlation Analysis）&lt;/h1&gt;
&lt;h2 id=&quot;correlation--causation&quot;&gt;correlation ≠ causation&lt;/h2&gt;
&lt;p&gt;有关系不代表是原因。例如冰淇淋卖得好的时候，溺水的人也多——显然不是冰淇淋把人淹死的，背后的&lt;strong&gt;混杂变量（confounder）&lt;/strong&gt;是气温：天热了，既有人买冰淇淋，也有人去游泳，两个数据于是同涨同跌。&lt;/p&gt;

&lt;p&gt;所以两个变量相关，可能是：A 导致 B、B 导致 A、C 同时导致 A 和 B、或者纯属巧合。相关性分析只负责告诉你”它们一起动”，因果得靠受控实验或者更深的领域知识去论证。&lt;/p&gt;

&lt;h2 id=&quot;pearson-相关系数-r&quot;&gt;Pearson 相关系数 r&lt;/h2&gt;
&lt;p&gt;量化”一起动的程度”最常用的工具是皮尔逊相关系数（Pearson correlation coefficient）r：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;r 值&lt;/th&gt;
      &lt;th&gt;含义&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;r = 1&lt;/td&gt;
      &lt;td&gt;完全正线性相关（一条直线往右上）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;0 &amp;lt; r &amp;lt; 1&lt;/td&gt;
      &lt;td&gt;正相关：一个涨另一个也倾向于涨，越接近 1 越强&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;r = 0&lt;/td&gt;
      &lt;td&gt;无&lt;strong&gt;线性&lt;/strong&gt;相关&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;−1 &amp;lt; r &amp;lt; 0&lt;/td&gt;
      &lt;td&gt;负相关：一个涨另一个倾向于跌&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;r = −1&lt;/td&gt;
      &lt;td&gt;完全负线性相关（一条直线往右下）&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;三个容易踩的坑：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;r = 0 不代表没关系&lt;/strong&gt;，只代表没有线性关系。y = x²（x 关于 0 对称取值）和 x 的 r 就是 0，但关系铁得很。r 只看”像不像一条直线”。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;对离群点极其敏感&lt;/strong&gt;：一个离谱的数据点能把 r 从 0.2 拉到 0.9，所以看 r 之前先画散点图。&lt;/li&gt;
  &lt;li&gt;r 算出来了也回不到因果——参见上一节，冰淇淋警告。&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;数据分析流程&quot;&gt;数据分析流程&lt;/h1&gt;
&lt;p&gt;课上给的标准流程，一步都不能省：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;任务理解（task understanding）&lt;/strong&gt;：到底要回答什么问题。这一步不清晰，后面全白干。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;数据收集（data collection）&lt;/strong&gt;：从数据库、问卷、传感器（没错，你手里那块开发板也是数据源）里把数据弄到手。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;数据预处理&lt;/strong&gt;：就是前面说的洗数据。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;分析与建模&lt;/strong&gt;：统计、可视化、上模型。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;结果解释与汇报&lt;/strong&gt;：把结论翻译成人话，讲给不懂统计的人听。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;课上只展开了前两步，后面的先记个框架（之后再补了，不重要）。&lt;/p&gt;

&lt;h1 id=&quot;假设检验hypothesis-testing&quot;&gt;假设检验（Hypothesis Testing）&lt;/h1&gt;
&lt;p&gt;还是概率论与统计的内容。&lt;/p&gt;

&lt;p&gt;假设检验是推断统计的核心操作，一句话概括：&lt;strong&gt;先立一个默认假设当靶子，再看数据打不打得倒它&lt;/strong&gt;。&lt;/p&gt;

&lt;h2 id=&quot;h0-与-h1&quot;&gt;H0 与 H1&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;原假设（null hypothesis，H0）&lt;/strong&gt;：默认成立、等着被挑战的假设，内容一般是”没效果、没差异、一切正常”——”今年平均分和往年没区别”“新广告对点击率没有提升”。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;备择假设（alternative hypothesis，H1）&lt;/strong&gt;：我们想用数据去证明的东西——”有差异、有效果”。它和 H0 互补：H0 说等于，H1 就说不等于。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;书写上有个铁规矩：&lt;strong&gt;等号永远写在 H0 这边&lt;/strong&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;H0: μ = 175   （默认：圣光机学生平均身高就是 175，没变化）
H1: μ ≠ 175   （备选：平均身高不是 175，有变化）
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;判决逻辑法庭类比&quot;&gt;判决逻辑：法庭类比&lt;/h2&gt;
&lt;p&gt;整套假设检验的流程就是法庭审案，底座是&lt;strong&gt;无罪推定&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;被告默认无罪（H0 默认成立）。想定罪（拒绝 H0、接受 H1），控方得拿出确凿证据（数据）。&lt;/li&gt;
  &lt;li&gt;证据不够：无罪释放。注意措辞——结论是”&lt;strong&gt;无法拒绝 H0&lt;/strong&gt;“，不是”H0 是对的”。就像”证据不足释放”不等于”证明了清白”，可能只是这波证据不行。&lt;/li&gt;
  &lt;li&gt;证据确凿：数据在”H0 成立”的前提下出现的概率小到离谱——如果一切正常，这种数据几乎不可能出现，那有问题的大概率就是”一切正常”这个前提本身。于是拒绝 H0，站队 H1。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;至于”概率小到多少才算离谱”（p 值与显著性水平 α），以及两种冤案——第一类错误（错杀好人：H0 明明为真却被拒绝）和第二类错误（放走坏人：H0 明明为假却没被拒绝）——这里带着做过一些题：&lt;/p&gt;

&lt;p&gt;HADI：&lt;/p&gt;

&lt;h1 id=&quot;小结&quot;&gt;小结&lt;/h1&gt;
&lt;p&gt;前两节课的关键词串成一条线：&lt;strong&gt;认识数据（类型）→ 清洗数据（预处理）→ 描述数据（统计量与箱线图）→ 关联数据（相关系数）&lt;/strong&gt;。原本”90% 是高中和概率论复习”的判断，到假设检验这里宣告失效——推断统计动真格了，后面有新东西了再更新。&lt;/p&gt;
</description>
        <pubDate>Mon, 19 Oct 2026 00:00:00 +0000</pubDate>
        <link>https://moment-new.github.io/2026/10/19/%E5%9C%A3%E5%85%89%E6%9C%BA%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E7%AC%94%E8%AE%B0/</link>
        <guid isPermaLink="true">https://moment-new.github.io/2026/10/19/%E5%9C%A3%E5%85%89%E6%9C%BA%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E7%AC%94%E8%AE%B0/</guid>
        
        <category>数据处理</category>
        
        <category>统计学</category>
        
        <category>学习</category>
        
        
      </item>
    
      <item>
        <title>圣光机计组课堂笔记</title>
        <description>&lt;h1 id=&quot;前言&quot;&gt;前言&lt;/h1&gt;
&lt;p&gt;这门课的资料非常详尽，所以基本上我只是做了翻译和摘录，鲜有改进。&lt;/p&gt;
&lt;h1 id=&quot;课程介绍&quot;&gt;课程介绍&lt;/h1&gt;
&lt;p&gt;这门课是基于计组，计算机系统和电路组成（我上过这课吗）的综合课程，旨在设计集成电路。
参考书籍有：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Harris D., Harris S. Digital Design and Computer Architecture. – 2nd edition. –&lt;br /&gt;
Morgan Kaufmann, 2012. – P. 712.&lt;/li&gt;
  &lt;li&gt;Rabaey J., Chandrakasan A., Nikolic B. Digital Integrated Circuits. – Pearson,&lt;/li&gt;
  &lt;li&gt;– P. 800.&lt;/li&gt;
  &lt;li&gt;Readler B. Verilog by Example: A Concise Introduction for FPGA Design. - Full&lt;br /&gt;
Arc Press; First Edition, 2011. – P. 124.&lt;/li&gt;
  &lt;li&gt;Weste N.H.E., Harris D.M. CMOS VLSI design: A circuits and systems&lt;br /&gt;
perspective. – 4th edition. – Pearson, 2010. – P. 880&lt;/li&gt;
  &lt;li&gt;Baker R. J. CMOS: Circuit Design, Layout, and Simulation. – 3rd edition. – Wiley-&lt;br /&gt;
IEEE Press, 2019. – P. 1280
    &lt;h1 id=&quot;soc&quot;&gt;Soc&lt;/h1&gt;
    &lt;p&gt;system on chip
通常情况下，硅芯片包含多个处理器核心、图形处理单元、各种编解码器（如视频编解码器）、输入/输出设备等。由此可见，集成电路是一种极其复杂的器件。在我们的课程中，我们将学习如何开发这些组件。&lt;/p&gt;
    &lt;h1 id=&quot;数字信号&quot;&gt;数字信号&lt;/h1&gt;
    &lt;p&gt;模拟信号：连续信号，有无穷个值。
数字信号：离散信号，有限个值，（通常来说）使用1和0两种逻辑值。比如以5v为逻辑1,0v为逻辑0
这个电压是随意的，例如5V VCC,3.3V VCC，Vout表示输出高电平，Vin表示输入高电平。对于一些零件来说，也可能会有高电压输入低电压输出的情况，这是为了抑制噪声的考虑或各种环境因素的损耗影响。&lt;br /&gt;
| 符号 | 含义 |
| — | — |
| VCC | 电源电压（供电），比如 5V 系列就是 5.0V，3.3V 系列就是 3.3V |
| VOH | 输出高电平：芯片输出”1”时，最差情况下引脚上至少有这么高的电压 |
| VOL | 输出低电平：芯片输出”0”时，最差情况下引脚上至多这么高 |
| VIH | 输入判高门限：输入电压高于它，接收方才保证当成”1” |
| VIL | 输入判低门限：输入电压低于它，才保证当成”0” |
| VT | 翻转阈值（threshold），实际比较器翻转点，介于 VIL 和 VIH 之间 |
也正是环境的影响，数字信号的上升沿和下降沿都不一定是垂直的，由于电路中的电容和电感，基本上不可能出现理想的数字信号形式。
通常来说，上升沿被称为正沿，下降沿称为负沿，后续我们将继续使用这两个术语。&lt;/p&gt;
    &lt;h1 id=&quot;组合逻辑&quot;&gt;组合逻辑&lt;/h1&gt;
    &lt;p&gt;组合逻辑：输出由输入信号的组合决定。&lt;br /&gt;
例如，在算术逻辑单元（ALU）中，如果我们将 2 放入输入 A，将 3 放入输入 B，ALU 进行加法运算，我们将“立即”在输出 Y 得到 5。得到结果的时间仅与信号通过电路所需的时间有关。如果我们将输入值改为 1 和 5，我们将在输出中得到 6。这就是组合逻辑，我们改变输入，立刻得到结果。&lt;br /&gt;
另外一个概念是时序逻辑。  &lt;br /&gt;
输出由输入信号和先前状态的组合决定。这意味着它是一种具有记忆功能的电路。例如，寄存器、计数器、移位寄存器都基于时序逻辑。这种逻辑可以存储先前的状态，常用于寄存器。&lt;/p&gt;
    &lt;h2 id=&quot;时钟信号&quot;&gt;时钟信号&lt;/h2&gt;
    &lt;p&gt;时钟信号用于时序逻辑的同步，由于使用多个时钟信号会出现亚稳态等问题，因此课程中尽量只用一个时钟信号。&lt;br /&gt;
例如，假设我们有两个 D 触发器（每个触发器存储 1 位数据），需要将一个信号从一个 D 触发器传输到另一个 D 触发器。如果这两个触发器使用不同的时钟信号，则无法进行传输。两个触发器必须通过一个时钟信号的上升沿或下降沿进行同步。在这种情况下，我们可以使用时钟信号上的某个事件，将一位信息存储在第一个或第二个 D 触发器中&lt;br /&gt;
所有时钟信号都存在不确定性。这意味着时钟信号的周期可能会有很小的偏差，因此 T1（第一个周期）不会等于 T2（第二个周期）。不确定性的大小与时钟质量相关：不确定性越小，时钟质量越好。&lt;/p&gt;
    &lt;h1 id=&quot;如何表述&quot;&gt;如何表述？&lt;/h1&gt;
    &lt;p&gt;下一个问题是我们如何表达我们的电路方案。
我们可以使用图片，也可以使用文本（或代码）。使用图片，我们可以直观地看到方案的功能实现方式，而文本表示则提供了功能方面的信息。图片适用于小型方案，而文本表示适用于大型方案。目前有两种流行的硬件描述语言（HDL）：VHDL 和 Verilog HDL。&lt;br /&gt;
HDL 帮助我们用简单的方程来描述复杂的方案。它可以描述硬件电路的结构和行为。&lt;/p&gt;
    &lt;h2 id=&quot;抽象层次&quot;&gt;抽象层次&lt;/h2&gt;
    &lt;p&gt;和语言一样，硬件描述语言也有它自己的抽象层次。&lt;br /&gt;
和玩图灵完备一样，原理图级别我们要用晶体管来创建最基本的电路图。之后，我们可以获得各种门电路，它是若干布尔逻辑门的组合，使得这种元件可以实现布尔运算，例如与门或门非门（这个之后有空会讨论）。
然后，我们进一步组合，获得了由门电路组成的基本功能模块，比如加法器，乘法器和多路复用器，解复用器寄存器计数器…..
最后，我们得到了 模块/IP核 ，包括CPU，GPU,IO控制器等等。这些模块由基本功能模块组成。&lt;br /&gt;
我们将使用基本功能模块来创建电路图。这就是为什么我们的课程叫做functional electronic circuits。我们要做的就是去制作IP核，IP指的是知识产权。&lt;br /&gt;
在电路图中混用不同层次的表示方法是不可取的。如果你的电路图基于晶体管，那么整个电路图都应该用晶体管来表示。例如，在逻辑门电路图中就不应该出现晶体管或功能模块。&lt;br /&gt;
基本功能块级别也称为 RTL = 寄存器传输级别。&lt;/p&gt;
    &lt;h1 id=&quot;开发&quot;&gt;开发&lt;/h1&gt;
    &lt;h2 id=&quot;开发工具&quot;&gt;开发工具&lt;/h2&gt;
    &lt;p&gt;开发过程中的主要目标是创建一些特殊工具，以提高将不同模块从一种表示形式转换为另一种表示形式的自动化水平。&lt;br /&gt;
理想情况下，所有开发人员都希望系统模型、可执行模型、功能电路和集成电路之间的转换能够完全由某些 CAD 工具实现，而无需开发人员干预。但目前，这是不可能的。为了实现不同形式的转换，需要使用不同的语言来正式描述系统。&lt;br /&gt;
为了实现不同形式的转换，我们可以使用不同的语言来正式描述系统级别，例如 SysML、xtUML 和 UML 等。创建可执行模型时，我们可以使用 Matlab、Maple、C/C++、Python、LISP、Haskell 和 Java 等。开发功能性电子电路时，我们可以使用 Bluespec、SystemVerilog/Verilog、VHDL、SystemC 和块设计语言等。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;现在我们有一些高级综合（HLS）（&lt;del&gt;屎山&lt;/del&gt;）工具可以将可执行模型转换为功能电路。也有一些工具可以将功能电子电路转换为集成电路并将其实现为芯片。但是，我们还没有工具可以将系统模型转换为可执行模型，因为从“你想要什么”到“它应该如何工作”的转换目前还无法自动化。这个问题尚未解决。&lt;/p&gt;

&lt;p&gt;目前的HLS通常有：
Vivado HLS (Xilinx赛灵思)&lt;br /&gt;
• Catapult HLS (Siemens西门子)&lt;br /&gt;
• Stratus HLS (Cadence)&lt;br /&gt;
• HLS Compiler (Intel牢英)&lt;br /&gt;
• Synphony HLS (Synopsys)&lt;/p&gt;

&lt;h2 id=&quot;晶体管电路&quot;&gt;晶体管电路&lt;/h2&gt;
&lt;p&gt;现代方案中晶体管一般使用CMOS技术（互补金属氧化物半导体）。这种设计基于两种晶体管，n MOS和p MOS，虽然这是两种晶体管，但是我们的学习不需要了解其基本物理特性和原理，只需要理解为一种抽象的元件就行——互补的意思就是有两种不同工作方式的晶体管，MOS意味着我们使用半导体衬底（金属M-氧化物O-S半导体），以及半导体组成的n沟道和p沟道。它具有称之为源极，栅极，漏极的金属电极，栅极之间以二氧化硅作为材料。
栅极是控制源极和漏极之间电流流动的关键电极。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;对于n-MOS:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;你可以看到一个 n 沟道 MOS 晶体管。它可以导通，也可以关断。栅极触点就像一把钥匙：它控制晶体管的导通或关断状态。当栅极信号为逻辑 0 时，钥匙断开（无电流）；当栅极信号为逻辑 1 时，钥匙关断（有电流）。这就是晶体管的“钥匙模式（key mode）”。&lt;br /&gt;
如果我们把栅极设置为逻辑0，然后源极接地，那电流将无法流动，钥匙会打开（开路），但如果栅极和源极的电压大于0，则漏极和源极之间会有电流流动。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;接下来讨论p-MOS：
它的工作方式与 n-MOS 晶体管不同。源极需要连接到电压源。如果我们将栅极电压设置为逻辑 1，晶体管将导通（没有电流通过）。但如果我们将栅极电压设置为逻辑 0，则源极到漏极之间会有电流通过，晶体管将导通&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;门电路&quot;&gt;门电路&lt;/h2&gt;
&lt;p&gt;回顾一下一些逻辑门的工作原理：
我们将讨论两种类型的逻辑门：与非门（NAND）和或非门（NOR）。它们是 CMOS 逻辑的基础。有些工厂只使用与非门，而有些工厂只使用或非门。这取决于工厂能够生产哪种类型的逻辑门。仅使用与非门或仅使用或非门就可以创建任意复杂度的数字电路。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;真值表
与非门NAND：&lt;br /&gt;
&lt;img src=&quot;/img/de-nand-gate-cmos.png&quot; alt=&quot;与非门 NAND：逻辑符号与 CMOS 电路&quot; /&gt;&lt;br /&gt;
或非门NOR:&lt;br /&gt;
&lt;img src=&quot;/img/de-nor-gate-cmos.png&quot; alt=&quot;或非门 NOR：逻辑符号与 CMOS 电路&quot; /&gt; &lt;br /&gt;
NAND加法器： 
&lt;img src=&quot;/img/de-nand-adder.png&quot; alt=&quot;用 NAND 门搭建的加法器&quot; /&gt;
后续的时序寄存器，SR/RS触发器之类，没有细讲。我也更建议玩图灵完备去理解。我们只需要知道，S为1，R为0，则在时钟边沿（正负取决于SR/RS）可以将1储存到Q，反正S0,R1则储存0.
再就是 JK 触发器。其中 J 代表跳转，K 代表终止。如果我们把 K 输入端设为 1，把 J 输入端设为 0，电路就会复位，相当于存储逻辑零。反之,J为1，K为0，则逻辑1，都为1，则反转其值。&lt;br /&gt;
利用SR触发器和JK触发器可以构建D触发器，D触发器可以由SR或者JK触发器构建，D触发器用于储存1位信息，它是一个1位储存单元。如果我们需要储存更多位数的信息，可以把多个D触发器串联起来，得到一个1byte的数据寄存器。
    &lt;h2 id=&quot;基本功能模块&quot;&gt;基本功能模块&lt;/h2&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;多路复用器解复用器&quot;&gt;多路复用器、解复用器&lt;/h3&gt;
&lt;p&gt;多路复用器是一种数据选择器。多路复用器是一种组合逻辑网络，它从多个可能的输入信号中选择一个，并将该信号导向单个输出端。特定输入的选择由一组选择变量控制。一个具有 n 个选择变量的多路复用器通常可以从 2^n 个输入信号中选择一个。&lt;br /&gt;
在电路图中，它可以表示为一个具有 2 位地址、4 位输入和 1 位输出的块。&lt;/p&gt;

&lt;p&gt;解复用器（或数据分配器）与复用器正好相反。它接收单个数据输入，并将其路由到多个可能的输出之一。它是具有 2 位地址、1 个输入和 4 位输出的模块。&lt;/p&gt;
&lt;h3 id=&quot;译码器&quot;&gt;译码器&lt;/h3&gt;
&lt;p&gt;下一个基本功能模块是译码器。您可以在幻灯片中看到，它有一个输入代码和一个输出代码。这里我们看到的是一个位置译码器。这意味着我们以位置作为输入，并在相应的输出中得到逻辑 1。例如，输入 010 会将 Q2 设置为 1，而输入 110 会将 Q6 设置为 1。译码器可以有很多种，这里展示的只是其中一种。&lt;/p&gt;

&lt;p&gt;编码是将一组输入形成编码表示的过程，它是译码操作的逆过程。编码器是一种组合网络，它生成一个 n 位二进制代码，该代码唯一标识 m 个激活输入中的一个（0 ≤ m ≤ 2− 1）。
编码器的功能与解码器相反：它将位置编码转换为二进制编码。幻灯片中展示的是一个位置编码器。同样的，编码器还有其他类型。&lt;/p&gt;
&lt;h3 id=&quot;算数模块&quot;&gt;算数模块&lt;/h3&gt;
&lt;p&gt;下一类模块是算术模块。它们可以用逻辑门构建。在电路图中，我们将算术模块称为 ALU 模块。ALU 的符号表示输入 A 和 B、一些操作码输入、输出以及对输出执行的操作符号。它可以执行加法、乘法等运算。&lt;/p&gt;
&lt;h4 id=&quot;加法器半加器&quot;&gt;加法器，半加器&lt;/h4&gt;

&lt;h3 id=&quot;寄存器&quot;&gt;寄存器&lt;/h3&gt;
&lt;p&gt;接着的功能模块是一个 4 位寄存器。可以看到，它是一个时序逻辑模块，可以存储 4 位信息，是一个 4 位存储单元。&lt;br /&gt;
我们可以创建一个移位寄存器，它在每个时钟信号（本例中为上升沿）时将值从一位移到另一位。它用于 I/O 控制器中的顺序数据传输。&lt;/p&gt;
&lt;h3 id=&quot;计数器&quot;&gt;计数器&lt;/h3&gt;
&lt;p&gt;最后一个基本模块是计数器。你可以从图中看到它的工作原理：每个时钟周期，计数器改变 Q0 的输出值；每两个时钟周期改变 Q1 的输出值；每四个时钟周期改变 Q2 的输出值。因此，每个输出都会产生一个频率比前一个输出低两倍的方波信号。&lt;/p&gt;

&lt;p&gt;对于每个时钟周期，我们将在 Q 输出上得到相应的代码：000、001、010、011、100、101、110、111、000 等等。&lt;/p&gt;
&lt;h1 id=&quot;时序部分&quot;&gt;时序部分&lt;/h1&gt;
&lt;p&gt;第二讲主要涉及数字设计中架构和微架构的定义，然后讨论数字方案中的同步和时钟问题，接着探讨通信基础设施及其在集成电路和数字设计中的实现方式。&lt;/p&gt;

&lt;p&gt;架构是一套描述计算机系统功能，组织和实现的规则和方法。微架构是实现架构的方式。我们的目的就是为了一些简单方案，CPU和加速器等开发微架构。&lt;/p&gt;

&lt;p&gt;MIPS处理器&lt;/p&gt;

&lt;p&gt;微架构主要可以分为两部分：数据流和控制流&lt;/p&gt;
&lt;h2 id=&quot;数据流&quot;&gt;数据流&lt;/h2&gt;
&lt;p&gt;描述数据如何从一个模块到另外一个模块，影响计算性能。&lt;/p&gt;
&lt;h2 id=&quot;控制流&quot;&gt;控制流&lt;/h2&gt;
&lt;p&gt;描述各个功能单元中操作的执行方式，控制单元负责实现与数据流处理相关的算法。&lt;/p&gt;

&lt;p&gt;如果我们再来看看简单的单周期 MIPS 处理器的微架构图，就会发现：控制流相关的模块和连接被标为蓝色，而数据流相关的模块和连接则被标为黑色。在开发项目时，必须明确区分这些控制流和数据流部分。如果将控制流和数据流相关的模块混在一起，虽然该架构在当前可能还能使用，但将来很难进行扩展和进一步开发。这样的架构也难以理解。为了使设计更易于理解，最好将微架构分为两个主要部分：控制流和数据流。因此，在设计时，应分别绘制这两个部分。控制流通常由控制单元构成（例如，可以由微程序控制单元或有限状态机来实现），此外还包括一些控制相关的组件&lt;/p&gt;

&lt;p&gt;接下来你需要熟悉的新术语是实现类型。微架构可以通过多种方式实现：可以实现单周期实现、多周期实现和流水线实现。&lt;/p&gt;
&lt;h1 id=&quot;one-cycle-implementation&quot;&gt;One-Cycle implementation&lt;/h1&gt;
&lt;p&gt;一周期实现——处理任务可在一个周期内完成。所有计算都是通过将数据传递到多个模块的组合中来完成的。
例如取y=x^5，它应该会进行乘以 x 五次的操作。对于一个周期实现，我们应逐个使用五个乘法器，然后将结果保存到寄存器中。从图片中你可以看到，每个时钟周期都会保存到寄存器。所以我们每个时钟&lt;/p&gt;

&lt;p&gt;单周期实现是处理单元或者任何功能模块中最简单的组成部分。Onecycle 实现每次操作都需要独立的计算资源，且运行频率相对较低。&lt;/p&gt;

&lt;p&gt;您可以看到，这里我们只使用了一个乘法器，但重复使用了五次。如果乘法器的传播延迟相同，则最小时钟周期应大于 1 ns。但计算过程需要 4 个时钟周期。此方案适用于高频时钟。左侧的示意图展示了一些控制逻辑和一个计数器。&lt;/p&gt;
&lt;h1 id=&quot;multicycle-implementation流水线实现&quot;&gt;Multicycle implementation（流水线实现）&lt;/h1&gt;
&lt;p&gt;多周期实现，能够在多个周期内使用相同的计算资源来完成。例如，要进行多个加法，你先做一个加法器块，并在不同时刻多次使用。
多周期实现允许通过在请求处理的不同阶段重复利用计算资源来节省计算资源，因此，它需要缓冲区，&lt;/p&gt;

&lt;p&gt;对于同上的计算，我们需要四个时钟周期来完成。但是对于第二个及以后的数字，我们只需要一个时钟周期来计算 x。&lt;/p&gt;
&lt;h1 id=&quot;时间参数&quot;&gt;时间参数&lt;/h1&gt;
&lt;p&gt;每个在寄存器（D 触发器）上实现的静态存储器都有建立时间和保持时间这两个参数。
建立时间 (tsu) 指的是同步输入 (D) 的数据在时钟有效边沿到来之前必须保持稳定的时间。图中显示了 tsu 周期和时钟上升沿。在整个 tsu 时间内，D 输出端的逻辑信号值应该保持稳定。
&lt;img src=&quot;/img/de-setup-hold-time.png&quot; alt=&quot;D 触发器的建立时间 tsu 与保持时间 th&quot; /&gt;
我们应该在上升沿之后保持输出信号值一段时间。这段时间称为保持时间。&lt;br /&gt;
保持时间 (th) – 时钟有效边沿过后，同步输入 (D) 处的数据必须保持稳定的时间。在这种情况下，您将获得稳定的数据输出(Q)。
我们应该在组合电路之间插入一些寄存器，并使用相同的时钟信号进行同步。例如（见图），您可以将值存储在 DFF2 中，然后将该值提供给组合电路，并在下一个时钟信号到来时将新值（计算结果）存储在 DFF3 中。在这种情况下，运行此电路所需的最小时钟周期为:
&lt;img src=&quot;/img/de-min-clock-period.png&quot; alt=&quot;寄存器间最小时钟周期：Tclk,min = Tclk-to-Q + Tcomb,max + Tsetup&quot; /&gt;&lt;br /&gt;
这里，第一个 Tc 是时钟到 Q 信号的传播延迟，第二个 Tm 是组合电路的传播延迟，最后一个 Tm 是建立时间。这个值给出了该方案的最大频率。&lt;br /&gt;
如果使用多个时钟同步各个器件，电路中可能会出现亚稳态信号。如图所示，时钟 Clk_B 违反了信号 A 的保持时间要求，导致输出 B 出现错误信号。&lt;br /&gt;
&lt;img src=&quot;/img/de-metastability.png&quot; alt=&quot;多时钟设计中的亚稳态问题&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;时钟域交叉技术cdc&quot;&gt;时钟域交叉技术（CDC）&lt;/h1&gt;
&lt;p&gt;有几种技术可以避免这种情况，但它们相当复杂，所以最好使用一个时钟信号来同步方案中的所有设备。&lt;/p&gt;

&lt;p&gt;避免电路中出现亚稳态信号的第一种方法是使用两个触发器而不是一个。这种方法称为双触发器同步器。其中，第一个触发器处于亚稳态，从而降低了第二个触发器输出端出现亚稳态的可能性。&lt;/p&gt;

&lt;p&gt;另一种技术采用所谓的“握手”方法。它使用两个信号：请求信号和确认信号。如果源控制和目标控制使用不同的时钟，则需要使用请求信号和确认信号，并通过两个触发器同步器来避免方案中的亚稳态信号。该方案的工作原理如下：如果源设备想要发送数据，它会生成请求信号。目标设备生成确认信号作为响应，然后源设备可以将数据发送到目标设备。在我们的实验中，为了避免此类问题，我们将只使用一个时钟信号。&lt;/p&gt;

&lt;p&gt;我们在此总结了几条钟表设计规则：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;当信号从一个时钟域传输到另一个时钟域时，需要消除两个时钟域之间的组合逻辑路径。这需要插入寄存器。&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;构成同步电路的触发器必须彼此靠近放置，以尽可能减小它们之间的时钟偏差。你需要将触发器紧密地排列在一起。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;切勿在多个地方同步同一信号。&lt;/li&gt;
  &lt;li&gt;数据总线上不要使用双触发器同步方案，而应使用握手同步技术。&lt;/li&gt;
  &lt;li&gt;花时间设计CDC是值得的。&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;模块通信基础&quot;&gt;模块通信基础&lt;/h1&gt;

&lt;p&gt;现有的拓扑结构有多种：星型、环型/链型、公共总线型。这些拓扑结构适用于不同的应用。例如，星型拓扑可用于设备网络，例如计算机中的 PCI Express 设备。环型拓扑可用于调试接口，例如 JTAG 接口，以减少信号数量。公共总线型拓扑用于片上系统设计和其他类型的设计中，将所有功能核心相互连接。如图所示，该系统由总线矩阵组成，该总线矩阵用于连接其他模块。&lt;/p&gt;

&lt;p&gt;虽然总线标准各不相同，但它们彼此之间非常相似。例如，ARM 处理器使用 AMBA AXI 总线标准。一些 MIPS 处理器使用 OCP 总线，而一些开放式设计的芯片则使用 Wishbone 总线。我们应该明白，如果使用某种通信标准，就可以轻松扩展系统（以后可以根据需要添加新模块），因为它遵循相同的规则。例如，每种总线标准都有明确的数据传输规则。例如，需要先设置地址，然后改变 HWRITE 信号，最后才能读取数据。写入数据时，也需要先设置地址，然后设置 HWRITE 信号，最后写入数据。这种规范（如幻灯片所示）适用于所有总线。&lt;br /&gt;
但它们彼此之间的标准都非常相似。不过，在您的设计中，您可以创建自己的总线并指定通信规则。&lt;/p&gt;
&lt;h2 id=&quot;总线实现&quot;&gt;总线实现&lt;/h2&gt;

&lt;p&gt;总线实现方式有两种：共享总线和交叉开关。共享总线&lt;/p&gt;

&lt;h3 id=&quot;共享总线&quot;&gt;共享总线：&lt;/h3&gt;
&lt;p&gt;硬件成本最低，结构最简单&lt;/p&gt;

&lt;p&gt;系统扩展会降低性能，因为每个模块都可以与其他模块一起工作，而其他模块则在等待公共总线空闲。&lt;/p&gt;

&lt;p&gt;修改硬件系统配置很容易，因为我们可以毫无问题地连接新的模块。&lt;/p&gt;

&lt;p&gt;我们不能同时从所有内存模块传输数据，因为公共总线只能使用一个内存块。&lt;/p&gt;
&lt;h3 id=&quot;交叉总线&quot;&gt;交叉总线：&lt;/h3&gt;

&lt;p&gt;第二种类型是交叉开关。交叉开关是一个矩阵，它允许 CPU 与内存模块通信。当一个 CPU 正在使用一个内存模块时，另一个 CPU 可以同时使用另一个内存模块。这样就可以并行执行操作。&lt;/p&gt;

&lt;p&gt;仅适用于多处理器，因为只需要一个基本的开关矩阵，因此具有成本效益。&lt;/p&gt;

&lt;p&gt;系统扩展可以提升性能&lt;/p&gt;

&lt;p&gt;传输速率高，但更复杂&lt;/p&gt;

&lt;p&gt;系统扩展受限，仅可通过开关矩阵的大小进行扩展。&lt;/p&gt;

&lt;p&gt;我们可以同时从所有内存模块进行数据传输。&lt;/p&gt;

&lt;h2 id=&quot;芯片网络&quot;&gt;芯片网络&lt;/h2&gt;
&lt;p&gt;另一种现代通信组织形式是芯片网络。&lt;/p&gt;

&lt;p&gt;片上网络（NoC）是一种新兴的范式，用于在单个硅芯片上实现大型 VLSI 系统内的通信。&lt;/p&gt;

&lt;p&gt;片上网络 (NoC) 链路可以降低线缆设计的复杂性，从而实现可预测的速度、功率、噪声和可靠性。&lt;/p&gt;

&lt;p&gt;由于片上网络中的所有链路都可以同时处理不同的数据包，因此可以实现高度并行性。&lt;/p&gt;

&lt;p&gt;片上网络（NoC）的核心思想是将网络的基本原理应用到单个芯片上。图中可以看到多个核心。&lt;/p&gt;

&lt;p&gt;片上网络（NoC）的核心思想是将网络的基本原理应用到单个芯片上。图中可以看到多个核心。&lt;/p&gt;

&lt;p&gt;这里，IP 核是一个功能单元，可以执行以下操作：&lt;/p&gt;

&lt;h1 id=&quot;hdl语言&quot;&gt;HDL语言&lt;/h1&gt;
&lt;p&gt;这里我旷了节课，根据印象简单讲讲：&lt;/p&gt;

&lt;h2 id=&quot;软核和硬核处理器&quot;&gt;软核和硬核处理器&lt;/h2&gt;
&lt;p&gt;任何数字电路都可以通过两种方式实现：集成电路ASIC，或现场可编程门阵列FPGA。&lt;/p&gt;

&lt;p&gt;ip核：是半导体产业中的一种产品，有可以在许多设计中复用的形式。&lt;br /&gt;
软处理器核心：处理器的实现可以在不同平台间迁移。
硬处理器核心：与部分工艺流程，门电路基础等相关的不可改变的因素相关。&lt;/p&gt;

&lt;p&gt;实验课上，我们通过编写 Verilog HDL 代码来实现软核，这些代码可以转换成集成电路或 FPGA 代码。而且，我们可以将它从一个平台移植到另一个平台，这意味着它是软核。&lt;br /&gt;
常见是软处理器核心有：
SRC1
Rocket
OpenRISC（以上的其实都是RISC-V）
ZPU
Microblaze
NIOS&lt;/p&gt;
&lt;h3 id=&quot;fpga&quot;&gt;FPGA&lt;/h3&gt;
&lt;p&gt;FPGA是由
CLB-可配置逻辑块
CB-连接盒
SB-开关盒
组成的。每个CLB模块都可以与其他的LCB模块协同合作，因为它们通过CB和SB彼此相连。  &lt;br /&gt;
容易想象得出，CLB&lt;/p&gt;

&lt;p&gt;CLB是由查找表LUT和触发器FF组成的。CLB可用于实现组合逻辑电路和时序逻辑电路（前面提到过的）。对于组合逻辑电路可用LUT,后者可用CLB。它通过组合两种资源实现各种各样的逻辑。&lt;br /&gt;
LUT（Look-Up Table，查找表）的本质是一块极小的 1 位存储器 + 地址译码器。&lt;/p&gt;

&lt;p&gt;原理：任何布尔函数都可以用真值表完整描述。以图中 4 输入 LUT 为例——4 个输入有 2⁴ = 16 种组合，每种组合对应一个确定的输出值。那干脆把这 16 个输出值预先存进 16 个 SRAM 单元里，把 A₁~A₄ 四根输入线当成 4 位地址送进去，地址译码后选中对应的单元，“读”出来的值就是函数输出。&lt;/p&gt;

&lt;p&gt;比如要实现 F = A₁·A₂ + A₃·A₄，工具软件会把 16 种输入组合下的正确结果算出来，在配置时写进这 16 个存储位。之后不管来什么输入，查表就是答案——不搭任何门电路，用存储代替运算。图中 LUT 下面那个小方框，就是保存这 16 位配置数据的配置单元。&lt;/p&gt;

&lt;p&gt;这也是 COA 里讲过的思想：任何组合函数都能用 ROM 实现，LUT 就是把这个思路做到极致、做成可重写的小 ROM。&lt;/p&gt;

&lt;p&gt;LUT 只能做组合逻辑：输出只取决于当前输入，断电即忘，没有状态。但计数器、状态机、寄存器这类时序逻辑必须记住“上一拍是什么”。&lt;/p&gt;

&lt;p&gt;FF（Flip-Flop，触发器）就是边缘触发的 D 触发器：在 CLK 时钟上升沿，把 D 端的值锁存进 Q，其余时间 Q 保持不变。图里每个 LUT 配一个 FF，还带 R（复位）、S（置位）控制线，用来异步清零或置一。&lt;/p&gt;

&lt;p&gt;简单来说，就是你可以通过模拟真值表的行为来模拟任何一种组合逻辑电路。&lt;br /&gt;
现在有个例子,用CLB实现异或门。&lt;br /&gt;
图中的是一个四输入查找表 (LUT) 结构，它可以实现任何组合电路、任何逻辑门以及任何基于逻辑门的电路方案。右侧是异或门的真值表，它表明当异或门的输入不同时，其输出为真。 &lt;br /&gt;
图中四个空白方格代表配置存储器。我们需要在其中放置一些值来实现异或（XOR）真值表。例如，如果输入 i1 和 i2 均为 0，则输出 y 应为 0。如何实现呢？三个输入分别为 i1 和 i2 的模块是多路复用器，它们可以根据控制信号 i1 和 i2 改变输出。如果我们将 i1 和 i2 都设置为 0，则输出 y 应为 0。为此，我们需要在最上面的方格中写入 0。在第二个序列中，i1 为 0，i2 为 1，输出 y 应为 1。因此，第三个方格的路径将被激活。为此，我们需要在第三个方格存储单元中写入 1。依此类推。最终，配置存储器中的数据将为 0 1 1 0。因此，配置存储器中的这些值将使该方案能够像异或门一样工作。&lt;/p&gt;

&lt;p&gt;现代的FPGA还包含高速接口，BRAM,DSP块和MCU，用于各种计算和外部连接。&lt;br /&gt;
当然，FPGA本质上是集成电路的组合，它能做的集成电路也能做：&lt;br /&gt;
这些功能实现方案各有特点：就频率而言（例如时钟信号的频率），FPGA 的最大可用频率始终低于集成电路的频率。FPGA 的最大频率约为 200 MHz，而集成电路的最大时钟频率约为 3 GHz。&lt;/p&gt;

&lt;h3 id=&quot;费林处理器分类法&quot;&gt;费林处理器分类法&lt;/h3&gt;
&lt;p&gt;根据指令数据的不同，处理器又有以下分类：&lt;/p&gt;

&lt;p&gt;SISD（单指令单数据流，单核处理器）。例如，我们有一个处理器、一个数据存储器和一个指令存储器。我们可以使用一个处理核心来执行一些数据相加操作。&lt;/p&gt;

&lt;p&gt;单指令多数据流（SIMD）（GPU、Intel SIMD 扩展）。例如，我们可以在这里将多个值相乘，为此可以使用多个处理器单元。这些处理器单元可以是 GPU 或向量协处理器。这种结构可用于数学运算，例如向量乘法。&lt;/p&gt;

&lt;p&gt;MISD 多指令单数据（脉动阵列等）意味着我们有不同的结构对应一种数据。&lt;/p&gt;

&lt;p&gt;这种架构使用单条指令处理同一组数据，例如，在某些数字信号处理中，我们需要对同一组数据执行多个操作。数据从一个处理器单元定向到另一个处理器单元。这种架构常用于脉动阵列。&lt;/p&gt;

&lt;p&gt;MIMD（多指令多数据流，并行计算机）。所有处理器单元都是独立的，可以处理各自的数据并执行各自的指令。这种处理器用于并行计算机。&lt;/p&gt;
&lt;h2 id=&quot;硬件加速器&quot;&gt;硬件加速器&lt;/h2&gt;
&lt;p&gt;我们常听到FPGA和ASIC加速了各种东西，这就是所谓的硬件加速器。 &lt;br /&gt;
们可以创建并行硬件加速器，使其能够在较低的时钟频率下工作；而使用较低的时钟频率就相当于降低了功耗。&lt;/p&gt;

&lt;p&gt;硬件加速器可以通过多种方式集成到电路中：&lt;/p&gt;

&lt;p&gt;第一种方法是开发自定义指令。&lt;/p&gt;

&lt;p&gt;处理器架构可以通过硬件加速器实现的自定义指令进行扩展。例如，如果某个处理器只能实现加法、减法、移位运算和逻辑运算，我们可以添加一些自定义运算，例如除法、乘法、幂运算或对数计算等等。我们可以设计一些硬件加速器来实现这些自定义指令，并将它们添加到计算机的指令集中。&lt;/p&gt;

&lt;p&gt;所有自定义指令都有类似的结构：它们最多包含两个数据输入和一个数据输出，以及可选的时钟、复位、模式、地址和状态信号，以便进行更复杂的多周期操作，从而了解何时可以获得结果。&lt;/p&gt;

&lt;p&gt;自定义指令是一种阻塞操作，它会阻止处理器执行其他指令，直到自定义指令完成。虽然这会降低性能，但自定义指令的执行时间通常很短，因此对整体性能的影响不会非常显著。这种做法通常又叫做协处理器。&lt;br /&gt;
另一种硬件加速器集成方式是使用独立的模块。在示意图中可以看到，CPU 可以通过系统总线与硬件加速器 (HW1) 通信。&lt;br /&gt;
这种情况下它和普通的外设类似，都在内存里映射。&lt;br /&gt;
每个硬件加速器的寄存器都映射到 CPU 地址空间，该地址空间显示在幻灯片的右侧。可以看到，该地址空间的一部分与存储数据和指令的主内存相关联，下一部分与硬件加速器 1 相关联，再下一部分与硬件加速器 2 相关联。CPU 需要使用内存操作来获取和存储硬件加速器中的数据。没有特定的指令来执行计算。CPU 需要知道硬件加速器寄存器在内存中的位置，才能寻址、获取和存储数据。GPU 就是这样连接到 CPU 的。&lt;br /&gt;
最常见的一种协处理器：向量协处理器。&lt;br /&gt;
向量协处理器拥有多个寄存器，这些寄存器可以划分成多个槽位。每个槽位可以表示特定格式的数字，例如浮点数、双精度浮点数、整数、双字节格式和单字节格式。我们还可以使用白名单寄存器。这是英特尔处理器中 SSE/AVX 数据类型的示例。我们可以利用这些格式，在一个时钟周期内通过一条指令对 32 个 8 位值进行运算。这可以显著提高处理矩阵或数组时的性能。&lt;br /&gt;
我们可以用向量进行两种基本类型的操作：垂直向量指令和水平向量指令。&lt;br /&gt;
虽然具体的细节最好是参考手册
https://www.ics.uci.edu/~swjun/courses/2019S-CS295/slides/，但是我这里可以粗略地讲一讲它们实际长啥样；&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;垂直向量指令：
你可以把一系列的矩阵操作，每个矩阵元素都提取出来占用一个专门的寄存器位置，然后相对的操作数元素就放在同一列的位置，每次执行指令就是对一整行的数据加上或者乘以下一行的数据。&lt;br /&gt;
它用于加法、减法、乘法、求最大值和最小值、进行移位、计算平方根以及融合乘法和加法运算。&lt;/li&gt;
  &lt;li&gt;水平向量指令：
第二种向量指令是水平指令。它允许我们计算一个寄存器中两个相邻值的结果。例如，将两个双精度浮点数相加，并将结果存储到另一个寄存器中。这样，我们就可以在一个寄存器中操作特定字段。这种指令常用于数据数组操作。&lt;br /&gt;
它更倾向于在寄存器中水平排列元素，然后使得相邻的几个元素相加/相乘。&lt;br /&gt;
当我们需要执行乘加运算时，可以使用 for 循环。如果使用 SISD 处理器，则需要执行大量运算（8 次加法、8 次乘法、8 次比较和 8 次加法加法，总共 32 次运算），并且一个循环需要 8 次迭代。但如果使用向量协处理器，只需一条指令 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_mm256_fmadd_ps&lt;/code&gt; 即可完成相同的计算，并在一个循环内获得结果。因此，与 SISD 处理器相比，性能将提升 32 倍。
    &lt;h3 id=&quot;gpu&quot;&gt;GPU&lt;/h3&gt;
    &lt;p&gt;耳熟能详的GPU实际上是一种图形硬件加速器。
多核 CPU 包含多个核心，但每个核心都是一个复杂的设备，根据特定的程序运行。GPU 则是一种 SIMD 设备。GPU 由数百个核心组成，但每个核心的结构比 CPU 核心的结构简单得多。GPU 核心可以执行诸如加法或乘法等简单运算。每个现代 GPU 都包含多个流式多处理器（SM）。幻灯片中展示了 GPU 的结构。每个流式多处理器中包含多个核心。它不仅可以用于图形处理，还可以用于处理通用数据，例如科学计算、处理大型数组或大数据等等。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;典型的编程流程如图所示。在一个处理器（红色）中，我们使用顺序执行；而在 GPU 中，并行执行是通过单指令实现的。首先，我们需要将数据从主内存复制到 GPU 内存。然后，我们需要将配置信息发送给 GPU，之后开始处理：数据将由多个核心并行处理，最后，我们需要将结果复制到主内存。&lt;/p&gt;

&lt;p&gt;当使用系统总线连接硬件加速器时，数据在主内存和内存之间的复制会成为瓶颈。为了解决这个问题，我们可以使用共享内存，即在芯片上集成一个系统，其中包含 GPU，并且内存和 GPU 都位于同一设备中。在这种情况下，主多核 CPU 无需在内存之间移动数据，GPU 可以直接从主内存中获取数据。&lt;/p&gt;
&lt;h3 id=&quot;npu&quot;&gt;npu&lt;/h3&gt;

&lt;p&gt;现在我们将简要讨论可作为基于神经网络的硬件加速器的神经网络处理器。&lt;/p&gt;

&lt;p&gt;第一种架构类型是流水线架构。它具有全局内存，以及分别用于存储输入数据和输出数据的局部内存块（MemRD 和 MemWR）。输入数据从全局内存传输到输入局部内存，然后在网络的不同层执行一些计算（例如卷积、池化等）。计算结果存储在输出局部内存中，然后被复制回全局内存。&lt;/p&gt;

&lt;p&gt;每个块中的计算都在多个计算单元中执行，并使用流水线式乘加器树来执行所有计算。&lt;/p&gt;

&lt;p&gt;这里再举一个例子：谷歌张量处理器单元TPU（Tensor Processor Unit）。它的结构与脉动数组的结构类似。它也使用脉动数组来执行矩阵乘法并生成所有数据并行计算。&lt;br /&gt;
CPU、GPU 和 TPU 的区别在于：CPU 需要逐个执行每个神经元的计算，速度非常慢；GPU 可以使用数组或向量并行计算神经元，但需要将中间计算结果存储在内存中，导致功耗巨大；而 TPU 可以使用矩阵运算，无需访问内存。因此，GPU 更适合训练神经网络，而 TPU 更适合推理（无需访问内存即可计算输出）。&lt;/p&gt;

&lt;h1 id=&quot;hdl&quot;&gt;HDL&lt;/h1&gt;
&lt;p&gt;Datapath（数据通路）就是数字系统里“数据实际流过、被加工”的那部分硬件：寄存器、ALU、加法器、移位器、多路选择器（mux）、连接它们的总线。它只负责搬运和运算，不负责决定“什么时候、按什么顺序做”。&lt;/p&gt;

&lt;p&gt;与之相对的是 controller（控制器），通常是一个有限状态机（FSM），根据当前状态和状态标志产生控制信号去指挥 datapath：mux 选哪条分支、ALU 做哪种运算、哪个寄存器使能写入。&lt;/p&gt;

&lt;p&gt;HDL 教材反复强调这个划分，因为它是 RTL 设计的标准方法论：先把要实现的功能映射成一个由寄存器和运算单元组成的 datapath 结构，再单独写一个 FSM 驱动它，顶层把两者 instantiate 到一起。这种结构有个名字叫 FSMD（FSM with Datapath）。&lt;/p&gt;

&lt;p&gt;Systolic array（脉动阵列）是由大量相同的小处理单元（PE）排成规则网格、数据像脉搏一样周期性“泵”过阵列的并行计算结构。 这个词是 H.T. Kung 和 Leiserson 在 1978 年造的，取自医学术语“心脏收缩期”（systole）：外部存储器扮演心脏，把数据有节奏地泵进阵列，每个 PE 用一次手头的数据再传给邻居——同份数据沿途被反复利用，而不是每个运算都回存储器取一遍。&lt;/p&gt;

&lt;p&gt;单个PE通常就是上述的DATAPATH写法：&lt;/p&gt;

&lt;p&gt;算矩阵乘法 C = A×B 时，每个单元固定存一个权重 w[k][j]（所以叫 weight-stationary，权重静止），激活值 a[i][k] 沿行从左往右流，部分和沿列从上往下流，一个周期每个单元恰好做一次乘加。输入按对角线错拍（skewed）送入，保证正确的 a 和正确的部分和恰好在同一拍相遇；进入稳态后每个周期从阵列底部流出一个结果。阵列整体像一个二维流水线，数据以“波前”形式扫过。&lt;/p&gt;

&lt;p&gt;它高效的原因有三条，全都直接来自结构：&lt;/p&gt;

&lt;p&gt;数据复用：一个操作数从存储器取出一次，在流过阵列的途中被 N 个 PE 使用，计算/访存比是 O(N)——这正是针对冯·诺依曼瓶颈（访存跟不上计算）设计的。
局部互连：PE 只连上下左右，没有长导线和全局总线，布线短，时钟频率高，天然适合 VLSI/FPGA。
极致规则：只设计一个 PE，然后用 HDL 的 generate 循环平铺 N×N 份，几行代码综合出几万个乘加器。&lt;/p&gt;

&lt;p&gt;DATA PATH:数据通路，除了各种控制相关的，比如control unit，都走这条通路。&lt;/p&gt;
</description>
        <pubDate>Mon, 12 Oct 2026 00:00:00 +0000</pubDate>
        <link>https://moment-new.github.io/2026/10/12/%E5%9C%A3%E5%85%89%E6%9C%BA%E6%95%B0%E7%94%B5%E7%AC%94%E8%AE%B0/</link>
        <guid isPermaLink="true">https://moment-new.github.io/2026/10/12/%E5%9C%A3%E5%85%89%E6%9C%BA%E6%95%B0%E7%94%B5%E7%AC%94%E8%AE%B0/</guid>
        
        <category>数字电路</category>
        
        <category>学习</category>
        
        
      </item>
    
      <item>
        <title>MGS手表复刻历程</title>
        <description>&lt;h1 id=&quot;前言&quot;&gt;前言&lt;/h1&gt;
&lt;p&gt;这个项目旨在复刻合金装备幻痛5中出现的精工G757手表。由于抽到了nordic的DK，所以主要以nRf54l15为主。这里的蓝牙特指的是低功耗蓝牙（LE）而不是classic蓝牙。&lt;/p&gt;
&lt;h1 id=&quot;蓝牙组成&quot;&gt;蓝牙组成&lt;/h1&gt;
&lt;p&gt;这里大部分的内容都来自于Nordic的蓝牙教程，因此非常推荐去亲自看看。&lt;br /&gt;
&lt;img src=&quot;/img/ble-stack-architecture.png&quot; alt=&quot;蓝牙 LE 协议栈的 Host 与 Controller 分层&quot; /&gt;
从图中可以看到，蓝牙被分成了两大块，HOST和Controller。上层是软件协议层，下层是硬件抽象link layer和硬件层。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;逻辑链路控制与适配协议（L2CAP）：为上层提供数据封装服务。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;安全管理协议（SMP）：定义并提供安全通信的方法。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;属性协议（ATT）：允许设备将某些数据片段暴露给另一设备。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;通用属性配置文件（GATT）：定义使用 ATT 层所需的子程序。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;通用访问配置文件（GAP）：直接与应用程序接口，处理设备发现和连接相关服务。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;controller层有多种实现，官方推荐使用softdevice控制器，你也可以更换成其他开源，包括zephyr的LE蓝牙控制器。&lt;/p&gt;
&lt;h1 id=&quot;蓝牙的模式&quot;&gt;蓝牙的模式&lt;/h1&gt;
&lt;p&gt;蓝牙有两种模式：连接模式和广播模式。
广播是单向的，同时因为不需要建立点对点的连接，其完全可以在广播范围内向无数个设备同时发送信息。通常来说，这也更省电。
连接是双向的，
广播模式下又分为：&lt;/p&gt;

&lt;h2 id=&quot;central-and-peripheral--中央与外围&quot;&gt;Central and peripheral  中央与外围&lt;/h2&gt;
&lt;p&gt;(&lt;del&gt;什么外围？&lt;/del&gt;)&lt;br /&gt;
蓝牙可以被设置为中央设备和外围设备。
中央设备负责轮询扫描，外围设备又被称为外设，负责数据输送给中央设备。&lt;br /&gt;
连接模式下，中央和外围可以相互通信，而广播中外围只能向中央广播，无法接收任何回馈&lt;br /&gt;
值得注意的是，广播模式下，中央依然是负责接收的那一方，发送的是外设方。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;GAP 的中央/外设到底规定了什么:
GAP的规则大概可以分为两个问题的组合：“发不发广播” × “参不参与连接”：&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;只发广播、不理连接 → Broadcaster（信标）
只收广播、不建连接 → Observer（静默观察）
发可连接广播、等人来连 → Peripheral（外设）
收广播、主动发连接请求 → Central（中央设备）&lt;/p&gt;

&lt;p&gt;广播模式出现在发现期：外设按自己的节奏在 3 个广播信道上循环发包，中央盲扫。中央一旦决定连接，就在监听到广播包后的 150 μs 固定间隔处回一包 CONNECT_IND，从此双方脱离广播信道、进入数据信道，进入同步的连接态——ATT/GATT 就是在这个阶段开始干活的。&lt;/p&gt;

&lt;p&gt;关于拓扑，这里不赘述了，建议参考https://academy.nordicsemi.com/courses/bluetooth-low-energy-fundamentals/lessons/lesson-1-bluetooth-low-energy-introduction/topic/gap-device-roles-and-topologies/#connected-topology
随着蓝牙 5.4 中周期性广告响应（PAwR）的引入，实现了无连接模式下的双向通信，不过Nordic并没有深入探讨。&lt;/p&gt;

&lt;p&gt;以上的部分都可以归为GAP层，即描述设备角色和网络拓扑情况的层。&lt;/p&gt;

&lt;p&gt;广告期间的通信仅用于设备发现或数据广播，由 GAP 层本身处理。然而，建立连接后，需要双向数据交换。这需要专门为这些目的定制的数据结构和协议。&lt;/p&gt;
&lt;h1 id=&quot;att层与gatt层&quot;&gt;ATT层与GATT层&lt;/h1&gt;
&lt;p&gt;GAP层只负责设备间的互相发现和广播。真正连接后，ATT层会转而负责数据传输、接收和处理。这里也有一个服务端-客户端的概念，不过和前面的主机/外设是独立的，主机可以是客户端也可以是服务端，取决于实际的需求。
ATT定义属性（attribute）这个数据结构。服务器的全部数据就是一张扁平数组，每行四个字段：
Handle：16 位编号，相当于内存地址
Type：一个 UUID，标记“这是什么类型的条目”
Value：任意字节
Permissions：可读？可写？要不要加密？
同时，其还定义一组操作动词。读请求/读响应、写请求/写响应、通知、指示、交换 MTU……全都是“对某个 handle 做某事”。
在ATT阶段，会规定服务端/客户端，因为ATT只是规定了一种数据结构，因此上层的GATT可以拥有多重属性，也就是可以同时是客户端或者服务端。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;通用属性配置文件（GATT）层直接位于 ATT 层之上，通过将属性层级分类为配置文件、服务和特性来构建。GATT 层利用这些概念来规范蓝牙 LE 设备之间的数据传输。  
nordic举了个例子：用一个测量心率的传感器设备为例。心率值将被保存为一个属性，称为特征值属性 。还有另一个属性，存储价值属性中存储的数据元数据，称为特征声明属性 。这两个属性共同构成了所谓的特征 。在这个例子中，它是心率测量特征。
所有特征都包含在所谓的服务中。服务通常包含多个特性。在本例中，心率测量特征包含在心率服务中。该服务还有其他特性，比如人体传感器位置特性。
再往上，配置文件是一个或多个服务，针对相同的使用场景。心率服务可在心率配置文件中找到，同时还有设备信息服务。设备信息服务包含诸如制造商名称特征和固件修订特性等特征。   GATT的特征规定大概是：https://www.bluetooth.com/specifications/specs/ 在客户端开始与服务器交互之前，客户端并不知道该服务器上存储属性的性质。因此，客户端首先执行所谓的服务发现 ，即向服务器询问属性。  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;至于PHY层，东西很少，具体实现因人而异，但是具体有三种规范：
1M,2M,自定义;&lt;/p&gt;
&lt;h1 id=&quot;蓝牙的连接流程&quot;&gt;蓝牙的连接流程&lt;/h1&gt;
&lt;p&gt;前面提到，蓝牙可以分为广播和连接模式，实际上不太准确，准确地说，广播应该是连接流程的一部分，只不过你可以停在这个部分不继续深入。蓝牙 LE 中的广告主要用于两个目的。用于向邻近设备广播数据，或向其他设备展示其存在。Nordic的Lesson2讲的就是如何纯粹在广播阶段先搭建好蓝牙。&lt;/p&gt;
&lt;h2 id=&quot;advertise广告广播阶段&quot;&gt;Advertise广告/广播阶段&lt;/h2&gt;
&lt;p&gt;在这个阶段，作为外设的蓝牙设备会开始间隔性地发送广播包。发送广告/广播 包的时间间隔就是广告/广播间隔（之后统一称为广告，不再称之为广播了）&lt;br /&gt;
设备通过40个不同的频率信道进行通信。这些频道分为三个主广告频道和37个次级广告频道。主要广告渠道是指主要用于广告目的的频道。次级广告通道有时也可用于广告目的，但主要用于建立连接后的数据传输。&lt;br /&gt;
为确保一定程度的冗余，广告包会在三个主要广告通道——频道37、38和39上发送。同时，扫描设备会扫描这三个通道，以寻找广告设备。&lt;br /&gt;
由于广告包对于建立连接至关重要，主要广告渠道会被仔细选择。37、38 和 39 频道虽然是连续编号，但实际上并不是相邻频道，如上图所示。三信道之间的分离旨在避免相邻频段的干扰。此外，这三个特定频道在使用 ISM 频段的其他技术（如 Wi-Fi）噪声影响最小。 &lt;br /&gt;
与广告间隔类似，扫描间隔指的是扫描仪扫描广告包的频率。扫描窗口指的是扫描器扫描数据包的时间，实际上代表设备在每个扫描间隔内扫描与未扫描的占空比。&lt;br /&gt;
由于广告是在三个频道上进行的，所以扫描也需要如此。每轮扫描间隔后，扫描器都会轮换一个频道。
扫描比广告耗电的多，因此一般是作为中央设备，在供电情况更好的设备上使用。&lt;br /&gt;
显然的，扫描/广告间隔越短，连接越快，耗电越快。所以通常来说，如果你同时控制中央设备和外设，你可以进行适当的间隔调整来达到最省电且连接最快的效果。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;扫描请求
在这个阶段中，中央设备可以对扫描到的外设发送扫描请求以期获取更多信息。如果扫描请求被接受，外设会通过三个主要广告通道进行扫描响应。这是一种无需连接就可以交流的方式，如果外设没有更多信息可提供，也可以选择返回空扫描响应。&lt;/li&gt;
  &lt;li&gt;拓展广告包
另一种增加外设一次性可投放数据量的方法是使用一种称为扩展广告的功能。通过该功能，主广告信道上广播的广告包指向了在次级广告信道上宣传的补充信息。扩展广告超出本课程范围，我们将只关注传统广告。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;广告类型&quot;&gt;广告类型&lt;/h3&gt;
&lt;p&gt;广告可以分为以下类型：
可连接与不可连接： 决定中央电源能否连接到外设。&lt;/p&gt;

&lt;p&gt;可扫描与不可扫描： 判断外设是否接受扫描仪的扫描请求。&lt;/p&gt;

&lt;p&gt;有向与无向： 确定广告包是否针对特定扫描器。&lt;/p&gt;

&lt;p&gt;这些类型在广告包中就包含了。Nordic的教程这一部分就是教我们如何编写这个类型包。
| 类型 | PDU 名称 | 可连接 | 可扫描 | 特点说明 | 典型用例 |
| — | — | — | — | — | — |
| 可扫描且可连接 | ADV_IND | ✅ | ✅ | 最常见的广播类型。外设广播其存在，允许 Central 发送扫描请求并以扫描响应回应，随后可建立连接 | 普通外设设备的常规广播 |
| 定向连接 | ADV_DIRECT_IND | ✅ | ❌ | 定向通告，不接受扫描请求。用于广播方已知晓对方、只想快速重连的场景，可缩短连接过程 | 蓝牙鼠标断连后向已知 PC 快速重连 |
| 不可连接且可扫描 | ADV_SCAN_IND | ❌ | ✅ | 只接受扫描请求，但不允许与其建立连接 | 仅提供信息、无需连接的设备 |
| 不可连接且不可扫描 | ADV_NONCONN_IND | ❌ | ❌ | 既不接受扫描请求，也不接受建立连接。无需切换到接收模式，可降低电池消耗 | Beacon 信标类低功耗设备 |&lt;/p&gt;

&lt;h3 id=&quot;蓝牙地址&quot;&gt;蓝牙地址&lt;/h3&gt;
&lt;p&gt;每个蓝牙 LE 设备都有一个唯一的 48 位地址。蓝牙地址分为公共地址和随机地址。随机地址还被分为静态地址或私有地址，具体取决于它们是否会改变。最后，私有地址分为可解析或不可解析。下图展示了蓝牙地址的分类方式。注意，随机地址和私有地址仅是分类类型，而非实际地址类型&lt;br /&gt;
这部分依旧是nordic讲的很详细了，这里只贴原文链接和拓展资料吧：
https://academy.nordicsemi.com/courses/bluetooth-low-energy-fundamentals/lessons/lesson-2-bluetooth-le-advertising/topic/bluetooth-address/?version=v3.4.1&lt;/p&gt;
&lt;h3 id=&quot;蓝牙数据包的组成&quot;&gt;蓝牙数据包的组成&lt;/h3&gt;
&lt;p&gt;蓝牙数据包有一个统一的格式：&lt;br /&gt;
&lt;img src=&quot;/img/ble-packet-format.png&quot; alt=&quot;蓝牙 LE 数据包格式与广播 PDU 结构&quot; /&gt;
即分为包头，地址，PDU,CRC校验位组成。&lt;br /&gt;
最重要的是所谓的协议数据单元（PDU）。PDU 由广播 PDU（有时称为广播信道 PDU）或数据 PDU（有时称为数据信道 PDU）组成，具体取决于 BLE 数据包是用于广播还是数据传输。&lt;br /&gt;
如图所示，广告 PDU 由头部和有效载荷组成。
更具体一点的话，PDU的头部又由若干比特段组成,这里我们写成结构体方便理解：&lt;/p&gt;
&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;union&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;uint16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RAW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;                 &lt;span class=&quot;cm&quot;&gt;/* 整体读写时用这个 */&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;uint16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PDU_Type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;    &lt;span class=&quot;cm&quot;&gt;/* [3:0]  广播类，就是前面提到过的广告类型
        0=ADV_IND 1=ADV_DIRECT_IND
        2=ADV_NONCONN_IND 
        6=ADV_SCAN_IND      */&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;uint16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RFU&lt;/span&gt;      &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;    &lt;span class=&quot;cm&quot;&gt;/* [4]    保留位，置 0                    */&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;uint16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ChSel&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;    &lt;span class=&quot;cm&quot;&gt;/* [5]   
         1=支持信道选择算法#2（仅连接类广播有效）*/&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;uint16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TxAdd&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;    &lt;span class=&quot;cm&quot;&gt;/* [6]    发送方地
        0=Public 1=Random   */&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;uint16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RxAdd&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;    &lt;span class=&quot;cm&quot;&gt;/* [7]    接收方地
        0=Public 1=Random
        （仅 ADV_DIRECT_IND 等有效）           */&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;uint16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Length&lt;/span&gt;   &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;    &lt;span class=&quot;cm&quot;&gt;/* [15:8] 载荷字节数, 传统广播 6~37        */&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;adv_pdu_header_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;广告 PDU 中的数据被分为两个部分：前 6 个字节用于表示广告主的地址（AdvA），其余部分则用于存储实际的广告内容（AdvData）。
这个AdvData又可以继续拆分，拆为若干个AD包（AD0 AD1…etc）
&lt;img src=&quot;/img/ble-advdata-structure.png&quot; alt=&quot;AdvData 由多个 AD 数据结构串联组成&quot; /&gt;
这些AD包规定了其大小，类型，并包含了具体的数据。需要注意的是，这个广告数据类型长度并不是固定的，最常见的广告数据类型长度为 1 字节。
广告数据类型由蓝牙规范所定义，相关详细信息可在 nRF Connect SDK 的“EIR/AD 数据类型定义”部分 https://docs.nordicsemi.com/bundle/zephyr-apis-latest/page/group_bt_gap_defines.html 找到&lt;/p&gt;

&lt;p&gt;在zephyr的代码中，我们的Ad包用bt_data来表示：&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;//这个就是AdvData的组成，一系列的Ad数据&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//zephyr中，一个 struct bt_data 就对应一条 AD 结构（tyep，datalen，*data）因此这里其实是AD结构的数组&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bt_data&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;cm&quot;&gt;/* 设置type，展开后其实就是几个预填了部分数值的成员 */&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;BT_DATA_BYTES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BT_DATA_FLAGS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BT_LE_AD_NO_BREDR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//展开是：{{1, 1, &amp;amp;(uint8_t[1]){4}[0]}, {9, 9, &amp;amp;&quot;MGS_Watch&quot;}}&lt;/span&gt;
	&lt;span class=&quot;cm&quot;&gt;/* STEP 4.1.3 - Set the advertising packet data  */&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;BT_DATA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BT_DATA_NAME_COMPLETE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DEV_NAME&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DEV_NAME&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//上面那个宏其实就是下面的简化形式。&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;由于nordic academy的教程中实现的是可连接广播模式，因此还有一个“扫描响应”包。这个包称之为SCAN_RSP包（sd包）
前面提到过，中央设备可以对扫描到的外设发送扫描请求以期获取更多信息，这个信息就是在主频道上用sd包回复的。&lt;br /&gt;
至于为什么要是个独立的包，原因很简单…塞不下。legacy 广播 PDU 载荷是协议早期的固定尺寸，31字节，塞 flags + 名字 + 服务 UUID + 厂商数据很快爆掉。
因此它只在PDU的head上有区别 ,数据结构一模一样。
我们的例子里我拿来宣传我的博客：&lt;/p&gt;
&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;//\x表示这段是转写，\x 后面连续的十六进制合法字符都会被转义成16进制，因此要隔离。&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\x17&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;//moment-new.github.io&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;


&lt;span class=&quot;c1&quot;&gt;//扫描响应的包&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bt_data&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;BT_DATA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BT_DATA_URI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;之所以要在data字符串前面加上0x17，是因为这个首字母的URI 类型（0x24）的 AD 结构规定首字节是“scheme 压缩码”，来自蓝牙 SIG 的 URI Schemes 表——0x16 = http://www.、0x17 = https://www.、0x2B = https:。一个字节替掉十几个字符的 scheme，接收设备收到后还原成完整链接(具体替换规则参考蓝牙 SIG 官方 Assigned Numbers 文档) &lt;br /&gt;
（Nordic权威.jpg）&lt;br /&gt;
顺便一提，\x 表示16进制转义，\表示八进制转义。&lt;/p&gt;

</description>
        <pubDate>Fri, 02 Oct 2026 00:00:00 +0000</pubDate>
        <link>https://moment-new.github.io/2026/10/02/MGS%E6%89%8B%E8%A1%A8%E5%A4%8D%E5%88%BB%E5%8E%86%E7%A8%8B/</link>
        <guid isPermaLink="true">https://moment-new.github.io/2026/10/02/MGS%E6%89%8B%E8%A1%A8%E5%A4%8D%E5%88%BB%E5%8E%86%E7%A8%8B/</guid>
        
        <category>嵌入式</category>
        
        <category>蓝牙</category>
        
        <category>操作系统</category>
        
        <category>学习</category>
        
        
      </item>
    
      <item>
        <title>圣光机计组课堂笔记</title>
        <description>&lt;h1 id=&quot;前言&quot;&gt;前言&lt;/h1&gt;
&lt;p&gt;叠甲：部分使用了AI解释，不保证一定正确&lt;br /&gt;
这门课主要是理论课，不存在lab，但是知识点繁多，因此我记录笔记如下。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;后记：这门课教材大概是：William Stallings 的《Computer Organization and Architecture: Designing for Performance》(中译名《计算机组成与体系结构:性能设计》,Pearson 出版)
    &lt;h1 id=&quot;第一节课basic-concept-and-computer-architecture&quot;&gt;第一节课：basic concept and computer architecture.&lt;/h1&gt;
    &lt;p&gt;有四次期中考，平时成绩和期末占比6:4&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;ISA:Instruction set architecture.指令集，分为精简指令集(RISC)和复杂指令集（CISC）&lt;/li&gt;
  &lt;li&gt;Micro-architecture:
DataPath:数据通路
Controller:控制器
    &lt;h2 id=&quot;计算机系统的概念&quot;&gt;计算机系统的概念：&lt;/h2&gt;
    &lt;p&gt;包含了从汇编到处理器，IO,内存，数据通路/控制器这一块。更底层的digital design和circuit design，transistors 不涉及这段内容。
（接下来是些为什么要学这门课的碎碎念我就跳过了）&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;数据处理
数据储存
数据移动
计算机控制：以上所有行为的控制
（然后又是一大段历史，懒得记了）
embedded system&lt;/p&gt;

&lt;p&gt;input unit MICR…
output unit..
ALU(Arithmetic logic unit):在处理器内部的逻辑处理器单元，用于加法触发等&lt;/p&gt;

&lt;p&gt;第二节课开始有测试。
binary，line by line，address，第四点elona记不得了（笑），大概是common memory:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;binary（二进制）：计算机里的一切信息——数据也好、程序也好——全都是用二进制表示的。内存里没有“这是数据、那是代码”的天然区分，全都是一串串的 0 和 1。

line by line（逐行存放）：程序是指令的序列，写好后被翻译成二进制，一条接一条地存进内存里，就像存数据一样。执行时 CPU 一条一条往外取。

address（地址）：内存的每一个格子都有编号，也就是地址。CPU 里有个程序计数器（PC），记着“下一条要执行的指令在哪个地址”，取完一条就加一，所以程序能顺序执行下去。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;普林斯顿架构中，数据和程序共享内存。(普林斯顿架构就是冯·诺依曼架构的另一个名字（普林斯顿大学是最早实现它的地方）。它的定义就是：程序和数据放在同一块内存里，走同一条总线。对比的是哈佛架构（Harvard architecture）——程序存一块独立的内存，数据存另一块，比如很多单片机、DSP 就是这么做的。)&lt;/p&gt;

&lt;p&gt;ALU
Interface(接口)的概念
Bottle neck（瓶颈），指出总线/处理器等的宽带/计算频率中最弱的部分会限制整体的发挥
virtual port，virtual memory&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/coa-instruction-format-board.jpg&quot; alt=&quot;板书：指令格式与 IR/AR 寻址&quot; /&gt;
这里elona画了一张图来表示指令是怎么写的&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;“IR” 和 “AR”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;IR（Instruction Register，指令寄存器）：CPU 从内存取出一条指令后，先放进 IR，硬件再把它的 Code 段和 Address 段拆开。
AR（Address Register，地址寄存器）：拆出来的地址段被送进 AR，AR 指向内存，按这个地址去取数据——所以图里箭头从 AR 画到 Memory。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;” ISA → instruction set — count of functions — hardware” ISA 的定义：它规定了处理器支持哪些指令（多少种功能），以及由硬件来实现。这是 ISA 和纯硬件设计的分界线。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;3.指令格式。
“N = 2^k” 和 “count of bits” 
“0000”、”F200” 这是举例：把一条 16 位的指令写成十六进制就是 F200，展开成二进制是：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;“Register 64 bits → M block 64 bits” 
这是说寄存器和内存数据块宽度一致：寄存器是 64 位，内存一个块（block）也是 64 位，所以一次访存正好搬一个寄存器宽度的数据，不需要拼拆。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;所以指令就是指令段+地址段，地址段会被拆分送入AR，然后对其执行指令段的命令。&lt;/p&gt;

&lt;p&gt;在进入下半部分之前有个小例子。主要是关于一个灯光控制系统的，也就是红绿灯。要求是能通过按钮变成绿灯，可以红绿灯切换，然后还有倒计时闪烁。然后分析了一下需要的元件（cpu。memory什么的）以及控制流。
然后不知道为什么他又觉得要加入input buffer register(IBR)，circuit，amplifier（放大器）
然后引入了中断（&lt;del&gt;虽然我觉得没必要吧，事件驱动+前后台就行了&lt;/del&gt;）然后有中断就得有栈stack，然后就得有ROM（只读储存器，这里用了其断电不掉数据的特性）RAM(随机访问内存，就是通常意义上的内存)&lt;/p&gt;

&lt;p&gt;最后统计了可能用到的寄存器，总线，等等&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Instruction cycle:
Instruction cycle（指令周期）就是 CPU 不断重复的一个固定流程：取出一条指令 → 执行它 → 再取下一条。
具体来说就是以下这张图：&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;指令有多种表达方式，2进制，16进制，以及助记符(Mnemonic)。
op:operational code操作数&lt;/p&gt;

&lt;p&gt;所以，实际上这一段是在建立一个整体的运行概念，指令是从哪里来的，如何执行的，长什么样&lt;/p&gt;

&lt;p&gt;在建立这个概念之后，我们就需要逐步分析各个环节的具体过程。&lt;/p&gt;

&lt;p&gt;CPU:
输入flag，clock
解码器:
和图灵完备里的一样，这里是个3位解码器&lt;/p&gt;

&lt;p&gt;IO:&lt;/p&gt;

&lt;p&gt;Memory:&lt;/p&gt;

&lt;p&gt;总线（BUS):
control line
address line
data line&lt;/p&gt;

&lt;p&gt;点对点传输：
不经过总线的传输，比如多核CPU的核间交流，&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/coa-hex-to-octal-quiz.png&quot; alt=&quot;课堂测验：十六进制 (1E.53)₁₆ 转八进制&quot; /&gt;
这里要注意这是表示数字的转换，不是大小的转换，也就是没有整数进位之类的东西&lt;/p&gt;

&lt;p&gt;cache memory&lt;/p&gt;

&lt;p&gt;内存分为primary memory和secondary memory。（主内存和次级内存）
其实就是中文里面内存(通常来说的RAM)和储存(通常来说的ROM或者其他的磁盘)的概念而已（
register memory，cache memory
on-line mass memory
offline bulk memory&lt;/p&gt;

&lt;p&gt;后面非常无聊地讲了储存和内存的区别…..
神他妈shark Mashine
Счётная машина(俄语“计算机”)——如果你看的是苏联时代的纪录片或翻译教材，электронно-счётная машина(电子计算机)音译过来很容易被写成”shark machine”;&lt;/p&gt;

&lt;p&gt;内存是分成虚拟的和真实的，通过内存表来映射到物理内存（为了寻址方便）。
后面讲了缓存到内存的映射,AI是这么解释的：&lt;/p&gt;

&lt;h3 id=&quot;右边-memory&quot;&gt;右边 Memory：&lt;/h3&gt;
&lt;p&gt;主存，按字（word）编址，每个字 32 位 = 4 字节。字地址 0、1、2…… 每 8 个字（8 × 4B = 32 字节）划成一个块（block）：字 0–7 是 block 0，字 8–15 是 block 1，字 16–23 是 block 2。彩色格子表示正在运行的程序访问到的那些字。&lt;/p&gt;
&lt;h3 id=&quot;左边-cache&quot;&gt;左边 Cache：&lt;/h3&gt;
&lt;p&gt;直接映射缓存，共 8 行（line 0–7）。每行有三个字段：Valid Flag（有效位，标记这一行是否有有效数据）、Block Number（标签，记录当前缓存的数据来自哪个内存块，用来区分“同位置但不同块”的情况）、Value（真正缓存的那 4 字节数据）。
红色箭头：展示了映射关系——内存字 3、字 11、字 19 都指向缓存的第 3 行。
核心规则：直接映射&lt;/p&gt;

&lt;p&gt;每个内存块只能放进缓存的固定一行，位置由取模决定：&lt;/p&gt;

&lt;p&gt;cache line = (block 地址) mod (缓存行数)
缓存有 8 行，所以字 3、11、19 满足 3 mod 8 = 11 mod 8 = 19 mod 8 = 3，全都被映射到第 3 行。这就是图中三支箭头汇聚到同一行的原因。&lt;/p&gt;

&lt;p&gt;这个设计的代价&lt;/p&gt;

&lt;p&gt;因为多个内存块共享同一缓存行，就必须靠 Block Number（tag） 来区分“现在这行里存的到底是 block 0、block 1 还是 block 2 的字 3”。CPU 访问时把地址拆成三段：低位是行内偏移（4B 一个字 → 2 位），中间是行号（8 行 → 3 位），剩下的高位是 tag，与缓存行里存的 tag 比较；命中且有效位为 1 才能直接用，否则就要把整块数据从内存换进来，把原来占位置的块挤出去——这就是直接映射缓存最容易发生&lt;strong&gt;冲突失效（conflict miss）&lt;/strong&gt;的原因：哪怕缓存没满，两个轮流访问的地址只要 mod 8 同余，就会不停地互相顶替。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简而言之的话&lt;/strong&gt;，就是缓存是分行的，每行对应一个固定大小的内存块。CPU为了快速起见每次读取内存都是一块一块取的，这个映射就是为了让你明白CPU索要的内存数据块是否是存在缓存里的那块。为了保证这个是正确的，缓存会以行为单位映射多个块的内存。例如行3对应第三个、第十一个、第十九个块。&lt;br /&gt;
当CPU想访问一个地址的时候，首先会去缓存进行查询。原始的内存地址是连续的一系列数字，缓存会对其做处理，例如缓存字地址19：
tag = 10（block 2）  index = 011（第 3 行）  offset = 11&lt;/p&gt;

&lt;p&gt;字 19 在真实内存里占的是字节地址 76–79（19 × 4），它只是个普通的数。&lt;/p&gt;

&lt;p&gt;“块结构”是缓存硬件强加给地址的，不是地址自带的&lt;/p&gt;

&lt;p&gt;缓存设计者先定下两个参数：一行装多大（比如 64 字节）、一共多少行（比如 512 行）。然后才把任何拿来的地址机械地切三段：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;低 log2(64)=6 位 → 行内偏移
中间 log2(512)=9 位 → 行号
剩下的高位 → tag
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;切法完全由缓存的几何参数决定。同一台机器上，L1 和 L2 的行数、行大小不同，同一份地址会被切成完全不同的三段；如果是全相联缓存（无行号），干脆没有 index 段。所以不是地址“天生”带了块信息，而是缓存拿着尺子去切地址——尺子刻度变了，切出来的块就变了&lt;/p&gt;

&lt;p&gt;现在我们CPU依然要找19这个地址的数据，然后19的地址给到了缓存：&lt;br /&gt;
缓存将其理解为这块内存要到缓存的line3，然后会开始匹配Tag，如果匹配成功，那么就会把这里的valid flag置为1，接着CPU会开始读。&lt;br /&gt;
如果tag不正确，这块内存作废，重新从内存里面读（比如可能这行存的是3或者11的数据块）&lt;br /&gt;
&lt;strong&gt;自始至终，CPU永远使用原始内存地址，只不过缓存巧妙地实现了以块形式的映射和寻址&lt;/strong&gt;（当然现代计算机里面多个MMU，会多映射一层，就是前面提到的物理内存变成虚拟内存）
(教材17页有细讲这个)&lt;/p&gt;
&lt;h3 id=&quot;三级缓存&quot;&gt;三级缓存：&lt;/h3&gt;
&lt;p&gt;为了解决CPU转的太快RAM太慢，我们需要缓存。但是缓存通常来说不够大，所以引入了多级缓存。
&lt;img src=&quot;/img/coa-cache-hierarchy.png&quot; alt=&quot;三级缓存层次：CPU → L1 → L2 → L3 → 主存&quot; /&gt;
在这张图里，每个缓存都把下一级当成内存看待，因此形成了逐层寻找的机制，最大程度避免了miss的影响。&lt;/p&gt;

&lt;h3 id=&quot;缓存命中率&quot;&gt;缓存命中率&lt;/h3&gt;
&lt;ol&gt;
  &lt;li&gt;命中率 h（hit ratio）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;定义就是一行字：命中的访问次数 ÷ 总访问次数。把程序跑起来统计所有内存访问，命中占的比例就是 h。课件给的典型值是 0.90–0.97，意思是绝大多数访问都被缓存拦下了，根本轮不到内存出场。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;缺失率 m = 1 − h&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;就是命中率的反面，两者互补：命中 95%，缺失就是 5%。有的资料爱用这个口径，本质同一个数。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;平均访存时间（核心公式）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;T = h × T缓存 + (1−h) × T内存
这里的T是时间，也就是平均访问内存的时间是命中率与内存/缓存读取时间的加权和。
最后，“如果有多级缓存，公式要按显然的方式修改”，指的就是上面那种方式：每一级都有自己的平均访存时间，把他们看成一系列的缓存/内存集合体，我们就可以得到：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;T = h₁×T₁ + (1−h₁)×h₂×T₂ + (1−h₁)(1−h₂)×h₃×T₃ + …
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;lru&quot;&gt;LRU&lt;/h3&gt;
&lt;p&gt;缓存满了之后的替换策略。&lt;/p&gt;

&lt;h2 id=&quot;internal-memory内部储存器&quot;&gt;internal memory(内部储存器)&lt;/h2&gt;
&lt;p&gt;直译可能会变成内存，但是这个指的不是通常意义上的内存，main memory。这个是在CPU内部的最昂贵的内存。
memory有两种分法：一种是可读写次数来分，一种是通过读写顺序来分：&lt;/p&gt;

&lt;p&gt;| 缩写 | 全称 | 中文 | 存取方式 | 读写能力 | 典型代表 |
|—|—|—|—|—|—|
| RAM | Random Access Memory | 随机存取存储器 | 随机 | 读写、易失 | SRAM、DRAM |
| RWM | Read-Write Memory | 读写存储器 | 随机 | 读写、易失 | =RAM 本体 |
| ROM | Read-Only Memory | 只读存储器 | 随机 | 只读、非易失 | 掩膜 ROM |
| RMM | Read-Mostly Memory | 以读为主的存储器 | 随机 | 可改写但很慢、非易失 | EPROM、EEPROM、Flash |
| SAM | Sequential Access Memory | 顺序存取存储器 | 顺序 | 可读写 | 磁带、纸带 |
&lt;img src=&quot;/img/coa-sram-cell.jpg&quot; alt=&quot;课件：SRAM 六管存储单元（1 bit = 6 transistors）&quot; /&gt;
SRAM (Static RAM)的存储单元内部电路:
这个本质上是一个锁存器，它的原理大概要玩图灵完备才能学会。所以我们只要会其是如何储存状态的八股就行了：&lt;/p&gt;

&lt;p&gt;对于逻辑1：
C1 高电平，C2低电平
T1(transistor 1,后略)T4开启，&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/coa-dram-cell.jpg&quot; alt=&quot;课件：DRAM 的 1T1C 存储单元（晶体管 + 电容）&quot; /&gt;
 DRAM （dynamic RAM，手机/电脑常用)的存储单元——和 SRAM 的 6 管豪华配置正相对，这里整个单元只有两样东西：1 只晶体管 + 1 个电容(老师红圈圈出来的就是)，教科书称为 1T1C 结构&lt;/p&gt;

&lt;p&gt;permanent storage(永久性存储)= 非易失存储&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;nonvolatile 非易失性(nonvolatile / permanent)
DRAM 有 data remanence(数据余留)：断电后电荷不会瞬间消失，残留几秒到几分钟——forensics(取证)领域真有“断电后冰袋冷冻内存条再读出密钥”的攻击。但工程分类上它仍算易失，因为不能依赖这个残留&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上一张PPT说了：电容会漏电，电荷只撑几毫秒。所以 DRAM 必须在数据漏光之前定期把每一行读出来、放大、写回去，这个例行公事就是刷新。这个过程被称为：&lt;br /&gt;
&lt;strong&gt;refresh cycle&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;read cycle&lt;/p&gt;

&lt;p&gt;address 11位
但是对于16M的 DRAM,我们有4M*4，显然大于2^11的2048,4M = 2²² 个位置 → 需要 22 位地址，所以我们需要：&lt;/p&gt;

&lt;p&gt;multiplexing(分时复用)&lt;/p&gt;

&lt;p&gt;同一组 11 根地址线，分两个时钟拍、当两套地址用：&lt;/p&gt;

&lt;p&gt;第一拍：送上低 11 位 = Row Address(行地址)，同时拉一下 RAS(Row Address Strobe,行选通信号)——芯片把这 11 位锁存进行地址缓存，驱动行译码器，选中一整行；
第二拍：同样的 11 根线改送高 11 位 = Column Address(列地址)，拉一下 CAS(Column Address Strobe)——锁进列地址缓存，从刚选中的那一行里挑出对应列的 4 bit,送到数据引脚。&lt;/p&gt;

&lt;p&gt;ROW Address
COLUMN Address
同时：
刷新是按“行”为单位的——有了行地址就够了，不需要列地址(这叫 RAS-only refresh)。2048 行要在 64 ms 里刷完，就是 64 ms ÷ 2048 ≈ 每 31 µs 刷一行&lt;/p&gt;

&lt;p&gt;256Kbyte module organization&lt;/p&gt;

&lt;p&gt;1MByte Module Organization&lt;/p&gt;

&lt;p&gt;SIMM Module&lt;/p&gt;

&lt;p&gt;Erro correction&lt;/p&gt;

&lt;p&gt;hard failure&lt;/p&gt;

&lt;p&gt;soft erro&lt;/p&gt;

&lt;p&gt;detected using hamming error code&lt;/p&gt;
&lt;h3 id=&quot;汉明码&quot;&gt;汉明码&lt;/h3&gt;
&lt;p&gt;hamming code就是为了避免电磁辐射导致的位反转产生的（常用于ECC）。它的算法与CRC类似但是排列不同：
数据位和校验位混排，第 1、2、4、8… 位留给校验位，其余位置放数据。以最经典的 Hamming(7,4) 为例——4 个数据位，3 个校验位，共 7 位：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;位置&lt;/th&gt;
      &lt;th&gt;1&lt;/th&gt;
      &lt;th&gt;2&lt;/th&gt;
      &lt;th&gt;3&lt;/th&gt;
      &lt;th&gt;4&lt;/th&gt;
      &lt;th&gt;5&lt;/th&gt;
      &lt;th&gt;6&lt;/th&gt;
      &lt;th&gt;7&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;内容&lt;/td&gt;
      &lt;td&gt;p1&lt;/td&gt;
      &lt;td&gt;p2&lt;/td&gt;
      &lt;td&gt;d1&lt;/td&gt;
      &lt;td&gt;p4&lt;/td&gt;
      &lt;td&gt;d2&lt;/td&gt;
      &lt;td&gt;d3&lt;/td&gt;
      &lt;td&gt;d4&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;关键规则：&lt;strong&gt;校验位 p_k 负责所有”位置编号的二进制里第 k 位是 1”的位&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;p1 管位置 1,3,5,7（二进制 xx1）&lt;/li&gt;
  &lt;li&gt;p2 管位置 2,3,6,7（二进制 x1x）&lt;/li&gt;
  &lt;li&gt;p4 管位置 4,5,6,7（二进制 1xx）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;三组覆盖互相重叠，任何一位都恰好被若干组包含。&lt;/p&gt;

&lt;h3 id=&quot;手算一遍考试大概率考这个&quot;&gt;手算一遍（考试大概率考这个）&lt;/h3&gt;

&lt;p&gt;数据 1011（d1 d2 d3 d4 = 1 0 1 1），偶校验：
注：⊕ 这个符号指的是模加法，就是加起来然后对 2 取模。&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;p1 = d1⊕d2⊕d4 = 1⊕0⊕1 = 0
p2 = d1⊕d3⊕d4 = 1⊕1⊕1 = 1
p4 = d2⊕d3⊕d4 = 0⊕1⊕1 = 0
码字：0 1 1 0 0 1 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;假设第 5 位翻转变成了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0 1 1 0 1 1 1&lt;/code&gt;。接收端把三组校验重新各算一遍，得到 s1=1, s2=0, s4=1，拼成二进制 (s4 s2 s1) = &lt;strong&gt;101 = 5&lt;/strong&gt;——出错位置直接被报出来了，翻回去即纠错完成。&lt;/p&gt;

&lt;p&gt;这就是汉明码最漂亮的地方：&lt;strong&gt;校验位的位置选取使得”哪组校验失败”恰好构成出错位置的二进制地址&lt;/strong&gt;。每组校验本质上是在回答”出错位置编号在这一位上是不是 1”，三组问完，答案自动拼出来。&lt;/p&gt;

&lt;h3 id=&quot;扩展到真实内存ecc-就是这套东西&quot;&gt;扩展到真实内存（ECC 就是这套东西）&lt;/h3&gt;

&lt;p&gt;校验位数量 k 要满足 2^k ≥ n + k + 1（n 是数据位数）：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;数据位 n&lt;/th&gt;
      &lt;th&gt;校验位 k&lt;/th&gt;
      &lt;th&gt;总位宽&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;8&lt;/td&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt;12&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;16&lt;/td&gt;
      &lt;td&gt;5&lt;/td&gt;
      &lt;td&gt;21&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;32&lt;/td&gt;
      &lt;td&gt;6&lt;/td&gt;
      &lt;td&gt;38&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;64&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;7&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;71&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;这就是为什么 ECC 内存条是 72 位宽而不是 64 位——64 数据 + 7 校验 + 1 额外整体校验位。多出来的那一位构成扩展汉明码（SEC-DED）：纯汉明码最小距离是 3，能纠 1 位错；若同时错 2 位，syndrome 会指向一个错误的位置，改了反而更错。加上整体奇偶位后最小距离变 4，可以&lt;strong&gt;纠 1 位、同时发现 2 位错&lt;/strong&gt;——double error 只报错不乱改，交给上层处理。&lt;/p&gt;

&lt;p&gt;补充两点：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;开销随字长摊薄：8 位数据要 50% 冗余，64 位只要 12.5%，所以 ECC 按整个字（64 位）编码而不是按字节&lt;/li&gt;
  &lt;li&gt;通用规律：最小距离 d 的编码，纠 t 位错需要 d ≥ 2t+1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;logs linux：correctable ECC error&lt;/p&gt;

&lt;p&gt;SDRAM(Synchronous DRAM)&lt;/p&gt;

&lt;p&gt;把 DRAM 的所有操作都挂到系统总线时钟上的 DRAM——”Synchronous” 同步的是时钟（tick generator）。
这里elona举了个时间的例子，其实是在说DRAM的问题：
控制器送地址、拉 RAS/CAS,然后只能干等，等够 t_AA 才能取数据。问题在于：芯片什么时候好，控制器不可预测，只能按最坏情况傻等，或者反复询问。总线时钟跑得再快也没用，因为内存这头是“自由散漫”的，两边的节拍对不上。&lt;/p&gt;

&lt;p&gt;内存控制器不再“发完命令干等”，而是和芯片约定：“第 3 个时钟沿数据准到”(这个约定就是 CL,Cas Latency 等参数)。因为延迟是以时钟周期数明码标价的，控制器拿到了可预测性——发出读命令后，它精确知道数据哪一拍回来，中间这几拍就可以去干别的：发出下一个命令、访问另一个 bank……流水线就转起来了。这就是为什么 SDRAM 的有效带宽远超异步 DRAM:不是单次变快了，是等待被填满了。&lt;br /&gt;
给一个起始地址后，后续数据每个时钟沿自动吐一个，连发一串(比如一次 8 个字),这个称为burst，和DMA里面那个概念一样，利于Cache。&lt;br /&gt;
CAS Latency(CL)&lt;/p&gt;

&lt;p&gt;DDR SDRAM
这个是以上的升级，在上升沿和下降沿都工作。&lt;/p&gt;
&lt;h3 id=&quot;external-memory&quot;&gt;external memory&lt;/h3&gt;
&lt;p&gt;这块通常称之为储存。磁带，磁盘，光盘，SD卡都属于这个范畴。&lt;br /&gt;
这种内存通常可以在离电的情况下长期储存&lt;br /&gt;
RAID:（Redundant Array of Independent Disks，独立磁盘冗余阵列）指用多个物理磁盘组成一个阵列，对操作系统而言呈现为单个逻辑磁盘，数据分布在阵列的各物理盘上，并用部分容量存储冗余校验信息，使得某块盘故障时数据可恢复。通常分为RAID1345这几种：
| 级别 | 名称 | 冗余方式 | 特点 | 最少盘数 |
|——|——|———-|——|———-|
| RAID 0 | 非冗余分条 | 无 | 不是”真”RAID，只为提速；坏一块全丢 | 2 |
| RAID 1 | 镜像 | 每块盘一块副本盘 | 可靠性最高，容量利用率 50% | 2 |
| RAID 2 | 汉明码冗余 | 位级分条 + 一个专门的汉明校验盘（ECC） | 可纠正一位错，现已不用 | 3 |
| RAID 3 | 位交叉奇偶校验 | 位/字节级分条 + 1 块专用校验盘 | 所有盘并行访问，只检错不纠错 | 3 |
| RAID 4 | 块交叉奇偶校验 | 块级分条 + 1 块专用校验盘 | 支持独立读，但校验盘是&lt;strong&gt;写瓶颈&lt;/strong&gt; | 3 |
| RAID 5 | 块交叉分布式校验 | 块级分条，校验分散到所有盘 | 消除校验盘瓶颈，&lt;strong&gt;最常用&lt;/strong&gt; | 3 |
| RAID 6 | 双重冗余 | P+Q 双校验（Reed-Solomon 码） | 可同时容忍 2 块盘故障 | 4 |
HHD:HDD（Hard Disk Drive，机械硬盘/硬磁盘驱动器）&lt;br /&gt;
机械硬盘由head磁头，track磁道，width宽度，gap和sector扇区组成&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;sector 扇区
  磁道分段，最小读写单元，传统 512 B&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Gap（间隙）
  磁道上不记录数据的空白间隔，起分隔和缓冲作用：&lt;/p&gt;

    &lt;p&gt;扇区间隙：分隔相邻扇区&lt;br /&gt;
  扇区内部：扇区结构 = 前导码（同步/地址信息）+ 数据（512 B）+ ECC 校验码，各字段之间也有间隙&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Width（这里指磁道宽度 Track Width）
  每个同心圆磁道的物理宽度：&lt;/p&gt;

    &lt;p&gt;磁道越窄，同样半径内能容纳的磁道越多 → 道密度（TPI，每英寸磁道数）越高 → 容量越大。
  但磁道太窄要求磁头定位更精确，且容易受相邻磁道干扰
（此外还有个platter表示一块磁盘片）&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CAV（Constant Angular Velocity，恒定角速度）
指磁盘以固定转速旋转（如 7200 rpm），每个磁道上的扇区数相同。&lt;/p&gt;

&lt;p&gt;由此带来的特点：&lt;/p&gt;

&lt;p&gt;每个磁道容量一样，但外圈磁道周长更长 → 外圈的位密度更低，外圈空间被浪费了&lt;br /&gt;
换磁道读写时不用调速，控制电路简单 → 机械硬盘普遍采用
对比 CLV（恒定线速度）：CD 光盘用的方案，内外圈线速度一致（位密度处处相同），但转速要随磁头位置变化，适合顺序读整张光盘&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Seek Time
寻道时间&lt;br /&gt;
磁头移动到目标磁道的时间，平均约 5–10 ms&lt;/li&gt;
  &lt;li&gt;Rotational Latency
旋转延迟&lt;br /&gt;
目标扇区转到磁头下方，平均为半圈：60/(2×RPM) 秒&lt;/li&gt;
  &lt;li&gt;Access Time
存取时间&lt;br /&gt;
寻道时间 + 旋转延迟（定位数据的时间）&lt;/li&gt;
  &lt;li&gt;Transfer Time
传输时间&lt;br /&gt;
数据量 ÷（转速 × 每道容量）&lt;/li&gt;
  &lt;li&gt;Command Overhead
命令开销&lt;br /&gt;
控制器解析命令的时间，通常忽略不计&lt;/li&gt;
  &lt;li&gt;RPM
转速&lt;br /&gt;
每分钟转数，常见 5400 / 7200 / 10000 / 15000&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;磁盘io时间&quot;&gt;磁盘IO时间&lt;/h3&gt;
&lt;p&gt;CPU进行一次IO需要：
&lt;img src=&quot;/img/coa-disk-io-timing.png&quot; alt=&quot;课件：磁盘 I/O 传输的一般时序（寻道 + 旋转延迟 + 数据传输）&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Wait for Device（等待设备） 磁盘可能正在处理别的请求，你的请求只能在队列里排队。这段时间磁盘没在为你服务。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Wait for Channel（等待通道） CPU 到磁盘之间的 I/O 通道/总线被占用，还得等通道空闲才能下达命令。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Seek（寻道） 音圈电机驱动磁头臂移动，从当前磁道移到目标磁道。这是机械动作，是最耗时的部分（毫秒级）。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Rotational Delay（旋转延迟） 磁头到位后，还要等目标扇区转到磁头正下方。平均等半圈：60/(2×RPM)。如 7200 rpm 的盘 ≈ 4.17 ms。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Data Transfer（数据传输） 盘片继续旋转，磁头把扇区数据读出（或写入），耗时 = 数据量 ÷ 磁盘传输速率。
设备忙的时间就是除了1之外的时间。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;功能上磁盘属于存储层次（memory hierarchy，和内存一起构成“寄存器-缓存-内存-磁盘”的金字塔），结构上却挂在 I/O 系统里（靠 I/O 模块访问）。因为它俩访问方式完全一样：都是“给定地址读/写数据”，所以逻辑上算存储；但它速度慢、挂在控制器后面，所以物理上走 I/O 通路&lt;/p&gt;

&lt;h2 id=&quot;io设备&quot;&gt;IO设备&lt;/h2&gt;
&lt;p&gt;由于外部输入输出设备的内存大小，宽带，编解码方式不一致性，因此不能直连总线，需要经过I/O controller或者I/O Bus。
I/O controller的组成是：控制逻辑（control logic）+ 寄存器组（数据寄存器、控制寄存器、状态寄存器）+ 数据缓冲（buffer），外加两个接口——朝上的总线接口和朝下的设备接口
它主要负责：一台/一类特定的设备——把总线上通用的读写命令翻译成这种设备才懂的专属操作，比如SPI,I2C
I/O Bus是一种更慢的总线，它主要负责：把所有设备（的控制器）连到 CPU/芯片组上，为多种外设提供一条公共的、共享的传输通路。关键词是 all/multiple devices / common path。&lt;/p&gt;

&lt;p&gt;IO设备有以下功能：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Control &amp;amp; Timing.&lt;/li&gt;
  &lt;li&gt;CPU Communication.&lt;/li&gt;
  &lt;li&gt;Device Communication.&lt;/li&gt;
  &lt;li&gt;Data Buffering.&lt;/li&gt;
  &lt;li&gt;Error Detection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;经过这些抽象的隔离，CPU可以把这些五花八门的外设都统一为一种形式，它只要知道：每个外设对应的 I/O 模块/控制器的地址（端口号或内存映射地址）——也就是“往哪个地址的寄存器写命令、读写数据、读状态”。所有设备都统一成了“总线上一段可读写的地址空间”&lt;/p&gt;

&lt;p&gt;计算机的通信可以分为内部Intra（分为internal的内部设备和external的如IO设备）和外部Inter通信（两台计算机之间通信）&lt;/p&gt;

&lt;p&gt;当CPU想使用外设的时候，它必须经过这个流程：&lt;/p&gt;

&lt;p&gt;反过来，外设想知会CPU的时候：&lt;/p&gt;

&lt;p&gt;据此，我们引出了memory mapped IO的概念:&lt;/p&gt;

&lt;p&gt;这通常依赖反复轮询，所以我们引入了中断的概念：&lt;/p&gt;

&lt;p&gt;又因为中断之间有重要性区分，因此不同外设的中断，外设内部的中断都有一个优先级表，就如图中的一样，一级套一级有各种优先级的中断表。&lt;br /&gt;
如何实现中断：
| 方案 | 谁来找“是谁” | 优先级怎么定 | 痛点 |
| — | — | — | — |
| 每设备一根线 | 不用找，线即身份 | 硬件天然区分 | 耗引脚，设备数受限 |
| Software poll | CPU 软件逐个查 | 查询顺序决定 | 慢，轮询又回来了 |
| Daisy chain | ACK 沿硬件链传递 | 物理位置决定 | 链尾设备可能被“饿死” |
| Bus Master | 设备先仲裁总线 | 仲裁机制决定 | 逻辑最复杂 |
当然这些问题最终被解决了，PCI Express 采用了MSI（Message Signaled Interrupts，消息信号中断）：设备不再拉电平线，而是往一个约定的内存地址发一次普通的写事务，写操作的目标地址本身就编码了“我是谁、我是什么中断”。没有共享线、没有菊花链、没有仲裁，一台设备可用的中断数成百上千.&lt;br /&gt;
但是每次读写IO依然还是很占用CPU，因此我们设想能否绕过内存直接把数据喂给CPU，不需要经过内存和层层中断，仲裁。
DMA由此而生。
早期的DMA，比如PPT中的这个，只负责替CPU执行搬运指令，并且要占用总线。
| 信号 | 全称 | 谁发给谁 | 意思 |
| — | — | — | — |
| DREQ | DMA request | 磁盘控制器 → 8237 | “我有一批数据要搬” |
| HRQ | HOLD request | 8237 → CPU | “请把总线让给我” |
| HLDA | HOLD acknowledge | CPU → 8237 | “行，总线归你了”（CPU 引脚置为高阻态，图里那几个圆点开关断开就是表示 CPU 从总线上“下车”） |
| DACK | DMA acknowledge | 8237 → 磁盘控制器 | “选你了，上数据线吧” |&lt;/p&gt;

&lt;h2 id=&quot;io-channel&quot;&gt;IO Channel&lt;/h2&gt;
&lt;p&gt;这段讲的是I/O设备的发展史。其实根据前面的内容，我们也能大概了解到：
CPU 直接控制外设——没有模块的理想化起点，现实中几乎不存在（CPU 会被外设拖死）；
加入 I/O 模块 + 程序查询 I/O（无中断）→ 就是你那页 PIO 和键盘 516/517 例程；
同样结构，引入中断，CPU 不必空等 → 中断识别那页（“谁举的手？”）；
I/O 模块获得 DMA 能力，整块搬运只在开头结尾惊动 CPU → 8237 那页；
I/O 模块升级为专职处理器，拥有一套为 I/O 定制的指令集 → 这就是 I/O Channel（通道）；
I/O 模块有自己的本地内存，本身就是一台计算机，可以统管一大群设备，CPU 几乎不参与。
贯穿六步的主线只有一句话：CPU 的参与度逐级递减，I/O 模块的智能逐级递增——每一代设计者都在问“这件事能不能别劳烦 CPU”。
最终，I/O设备走向了自己独立的内存，处理器和总线，越来越像一个外部的小计算机。&lt;/p&gt;

&lt;h2 id=&quot;流水线架构和多核架构&quot;&gt;流水线架构和多核架构&lt;/h2&gt;
&lt;p&gt;fetch-decode-execute-cycle（前面提到过的）&lt;br /&gt;
指令可以被细分为各种micro-instruction的组合。&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;机器指令  ADD R1, R2            ← 汇编/机器码层面看到的东西
  └─ 指令周期阶段  FI→DI→CO→FO→EI→WO   ← 流水线的切法，每级至少占一拍
       └─ 微操作 (micro-operations)     ← 一拍内发生的一组寄存器传输
            └─ 微指令 (microinstruction) ← 控制存储器里驱动这些微操作的控制字
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;FSM&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/coa-fetch-cycle-dataflow.jpg&quot; alt=&quot;取指周期的数据流图（Stallings）&quot; /&gt;
这是 Stallings 教材里很经典的取指周期数据流图（Data Flow, Instruction Fetch），画的是 CPU 从内存里取出一条指令时，数据在寄存器和总线之间怎么一步步流动。
| 缩写 | 全称 | 作用 |
| — | — | — |
| PC | Program Counter 程序计数器 | 存“下一条要取的指令的地址” |
| MAR | Memory Address Register 地址寄存器 | 要访问的内存地址先放到这里，再送上地址总线 |
| MBR | Memory Buffer Register 数据寄存器 | 从内存读出来（或要写进去）的数据先落在这里 |
| IR | Instruction Register 指令寄存器 | 存当前正在处理的那条指令，供译码执行 |
| CU | Control Unit 控制单元 | 发号施令的：发读/写命令、让 PC 自增等 |&lt;/p&gt;

&lt;p&gt;图里很容易看出，PC在干活的时候，MBR啊MAR什么的都在摸鱼。为了让第一条指令执行的时候第二条指令就开始，我们发明了指令流水线：
6 级流水线时序图（timing diagram）
&lt;img src=&quot;/img/coa-pipeline-timing.jpg&quot; alt=&quot;6 级指令流水线时序图&quot; /&gt;
(这里懒得把图截出来了….所以如果图中被框进去的漂亮妹妹/姐姐感到冒犯我再改)
每一行是一条指令（I1、I2、I3）；
每一列是一个时钟周期（顶部标的 1~11 就是时间轴，一格一拍）；
每个格子表示“这条指令在这拍处于哪个阶段”；
每过一个周期，整行往右挪一格——因为流水线的规矩是：每拍结束，每条指令往前推进一个阶段，同时新指令从入口进来。（很像栈吧？）&lt;br /&gt;
这里的表述和常用的有一些不同，因此列个表格如下：
| 本页（Stallings） | MIPS 五级 | 干什么 |
| — | — | — |
| FI | IF | 取指 |
| DI | ID | 译码（顺便读寄存器） |
| CO | （并入 EX） | 算操作数地址 |
| FO | MEM | 取操作数（访存） |
| EI | EX | 执行 |
| WO | WB | 写回 |&lt;/p&gt;

&lt;p&gt;后面是一些计算，我让AI总结了…&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;流水线性能计算（平均每条指令耗时）

已知：
  T_logic  = 不分段时一段逻辑的总时间（例：750 ps）
  d        = 每级流水线寄存器的锁存/建立开销（例：90 ps）
  N        = 流水线级数
  CPI      = 实际平均每指令周期数（含冒险 stall，例：1.25）

第 1 步：求流水线后的时钟周期
  T_clock = T_logic / N + d
  例：750/5 + 90 = 240 ps

第 2 步：求平均每条指令耗时
  T_avg = CPI × T_clock
  例：1.25 × 240 = 300 ps

第 3 步：求真实加速比
  S = T_logic / T_avg
  例：750 / 300 = 2.5×

注意：
  - d 每拍只加一次：一条指令一拍只跨一道锁存器，
    各级锁存器并联同时动作，开销不叠加
  - 单条指令延迟 = N × T_clock = T_logic + N·d（这是另一本账，不是性能指标）
  - N→∞ 时 T_clock → d，加速比上限 = T_logic / d，收益递减
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;当然这是理想条件下的，很多情况下，接下来的指令依赖于上一条指令的情况，比如ifelse，jump。因此会出现多个孤立的流水线块组合形成实际的指令周期的情况。在图中的例子里，如果你jumo到了8-15这个位置，之前的哪些复用流水线就浪费了。或者本来就不应该被执行。
因此，Pipline或者portion of Pipline必须stall。
stall（冻结）：各级原地保持自己的指令不动，等依赖解决（典型：数据冒险）。指令没白取，只是晚走。
flush（冲刷）：把某些级里的指令直接扔掉（典型：跳转猜错）。指令白取了。&lt;/p&gt;

&lt;p&gt;types of harzards（冲突）：
resource
data
control
| 冒险 | 成因 | 对策 |
| — | — | — |
| 结构/资源 | 两级同一拍抢同一硬件（FI 和 FO 都要访存） | 哈佛结构（指令/数据分开存储）、复制硬件 |
| 数据 | 后条要用前条还没写回的结果 | 转发/旁路（forwarding）减小惩罚，不够再 stall |
| 控制 | 分支结果出来前不知道下一条取哪 | 分支预测（猜错再 flush）、延迟分支 |
这就是前面“资源冒险”的由来。&lt;/p&gt;

&lt;p&gt;bubble/IDLE —— 就是时序图里的空格子：一个气泡 = 某一级在这一拍空转 = 浪费一拍。气泡越多，实际 CPI 越偏离理想值 1。之前那个 CPI=1.25 就是这里来的（0.25 就是平均每条指令摊到的气泡）。&lt;/p&gt;

&lt;p&gt;后续的内容就是对三种harzard的解释，例如寄存器冲突类型（结构/资源），控制冲突（branch hazard）&lt;/p&gt;

&lt;p&gt;这就引入了一些解决办法：
分支预测：&lt;/p&gt;

&lt;p&gt;又叫Speculative branch processing（投机执行）：猜一条路继续跑，结果出来猜对了白赚，猜错了 flush回滚不亏。这就是现代 CPU 的标准答案——它 = 左边列的 branch prediction（负责猜）+ 硬件回滚机制（负责兜底）。一般来说猜得算准（现代预测器 95%+）。分成静态预测和动态预测，前者是SPARC,MIPS，后者是：[待补充喵]
之后还有什么公共分支预测，单独分支预测之类云云，不展开了，即使可能是考点。&lt;/p&gt;

&lt;p&gt;Delayed branching（延迟分支）:&lt;/p&gt;

&lt;p&gt;往bubble里面塞一些莫名其妙的指令，就是不管有没有分支都要执行的。&lt;/p&gt;

&lt;p&gt;Guarded execution（守卫/谓词执行）：&lt;/p&gt;

&lt;p&gt;釜底抽薪：直接消灭分支。if-else 两个分支的指令都执行，但每条带一个“守卫位”（谓词），条件成立的那份结果生效，另一份自动丢弃。分支比较多的时候好用。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/coa-branch-handling.jpg&quot; alt=&quot;分支处理方法分类：延迟分支、阻塞分支、推测执行、多路分支、守卫执行&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/coa-branch-prediction-unit.jpg&quot; alt=&quot;分支预测单元：Trace Cache 与 4096 项 BTB&quot; /&gt;&lt;/p&gt;
</description>
        <pubDate>Tue, 15 Sep 2026 00:00:00 +0000</pubDate>
        <link>https://moment-new.github.io/2026/09/15/%E5%9C%A3%E5%85%89%E6%9C%BA%E8%AE%A1%E7%BB%84%E8%AF%BE%E5%A0%82%E7%AC%94%E8%AE%B0/</link>
        <guid isPermaLink="true">https://moment-new.github.io/2026/09/15/%E5%9C%A3%E5%85%89%E6%9C%BA%E8%AE%A1%E7%BB%84%E8%AF%BE%E5%A0%82%E7%AC%94%E8%AE%B0/</guid>
        
        <category>计算机组成</category>
        
        <category>学习</category>
        
        
      </item>
    
      <item>
        <title>我确实搞不明白硬件</title>
        <description>&lt;h1 id=&quot;前言&quot;&gt;前言&lt;/h1&gt;
&lt;p&gt;笔者在RM时期，就深感硬件知识薄弱，以至于处处受其掣肘，一直想找个时间补习。现在对嵌入式的研究愈发深入后，对硬件知识不足便更加苦恼。为此，我翻出了很久之前收藏的【【教程】零基础入门PCB设计-国一学长带你学嘉立创EDA专业版 全程保姆级教学 】 https://www.bilibili.com/video/BV1At421h7Ui/?p=7&amp;amp;share_source=copy_web&amp;amp;vd_source=8b034ca64cb8bbbd15b0969aebfe1501
等硬件知识教程，并且做做记录，以期有所收获。
最后，由于我是CS专业的，对于电路知识几乎是一无所知的程度，因此这篇文章仅是个人学习的简单记录，若有谬误，欢迎斧正。&lt;/p&gt;

&lt;h1 id=&quot;元件&quot;&gt;元件&lt;/h1&gt;
&lt;h2 id=&quot;电阻&quot;&gt;电阻&lt;/h2&gt;
&lt;p&gt;贴片电阻：&lt;/p&gt;
&lt;h2 id=&quot;电容&quot;&gt;电容&lt;/h2&gt;

&lt;h1 id=&quot;电路专有名词&quot;&gt;电路专有名词&lt;/h1&gt;
&lt;h2 id=&quot;独立回路&quot;&gt;独立回路&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/img/hw-circuit-loops.png&quot; alt=&quot;回路与独立回路：3 个回路中独立回路只能任选两个&quot; /&gt;
什么是独立回路呢？和独立方程组一样，就是能够引进新的参数的，不能被其他方程（回路）推出的东西。电路分析里的“独立回路”本质上就是图论概念,&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;从图论角度理解
把电路抽象成一张图：&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;节点 = 电路中的连接点（三条及以上导线交汇的点）&lt;br /&gt;
支路 = 一个二端元件（或串联的元件组）连在两个节点之间，对应图中的一条边&lt;br /&gt;
回路 = 图中的一个简单环（回到起点且不重复经过节点/边）&lt;br /&gt;
独立回路的定义：一个回路中至少包含一条其他已选回路都没用过的支路。按这个规则一直选下去，直到选不出为止，选出的回路互相独立——每个都带来一条新的约束信息,也就是新的参数。
这里有一个公式：有 b 条支路、n 个节点的连通电路，独立回路数 = b − n + 1，本质上也是基于图论的&lt;/p&gt;
&lt;h2 id=&quot;网孔&quot;&gt;网孔&lt;/h2&gt;
&lt;p&gt;不可再分电路。&lt;strong&gt;平面电路&lt;/strong&gt;中被支路围出来的最小“格子”，中间不套任何其他支路。&lt;/p&gt;
&lt;h2 id=&quot;基尔霍夫定律&quot;&gt;基尔霍夫定律&lt;/h2&gt;
&lt;p&gt;基尔霍夫定律是一个描述电压/电流关系的定律&lt;/p&gt;
&lt;h3 id=&quot;集总参数电路&quot;&gt;集总参数电路&lt;/h3&gt;
&lt;p&gt;在这里的电路分析，都是默认电路是集总参数电路的。由于实际的电子元件，并不是只有电能，实际上有其他的能量转化，例如热能，电势能等等，会产生电磁波等等。
因此，电阻就不是单纯的电阻，在不同频率下，电阻可能是电感/电容/电阻的组合。集总参数电路，即一种简化的，不考虑电磁波等的简化模型。
&lt;img src=&quot;/img/hw-element-models.png&quot; alt=&quot;实际电阻元件：理想、低频、高频等效模型&quot; /&gt;
具体假设是：
电阻就是一个纯粹的电阻，能量耗散只发生在电阻“内部”那一点
电感、电容同理，电磁能的存储/转换只发生在元件本体
导线是理想导线：没有电阻、没有电感、没有电容，而且电流从导线一端流到另一端不需要时间——同一时刻，导线上任意一点的电流都相等。
这个假设，是以下定律的前提。&lt;/p&gt;
&lt;h4 id=&quot;为什么高频电路要求特殊&quot;&gt;为什么高频电路要求特殊&lt;/h4&gt;
&lt;p&gt;前面说到，在不同频率下，电阻可能是电感/电容/电阻的组合。而c=uλ，因此对于一个元器件，其电信号频率越高，波长越短。波长越短，其越与尺寸相关，为了简化，我们通常默认电子元器件的尺寸远小于其波长。&lt;/p&gt;
&lt;h3 id=&quot;kcl&quot;&gt;KCL&lt;/h3&gt;
&lt;p&gt;也就是基尔霍夫电流定律&lt;br /&gt;
流入任一节点的电流之和 = 流出该节点的电流之和。&lt;/p&gt;

&lt;p&gt;本质是电荷守恒：节点只是一根导线上的一个点，不储存电荷，所以每秒流进多少库仑就必须流出多少，否则电荷会凭空堆积。
&lt;img src=&quot;/img/hw-kcl-principle.png&quot; alt=&quot;基尔霍夫电流定律（KCL）的实质&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;kvl&quot;&gt;KVL&lt;/h3&gt;
&lt;p&gt;KVL：基尔霍夫电压定律
沿任一闭合回路绕行一圈，所有电压降的代数和 = 0。&lt;/p&gt;

&lt;p&gt;本质是能量守恒：电压是单位电荷的能量差。电荷沿回路走一圈回到出发点，它的电势必须回到原值——中间在电阻上“掉”下去的所有电势，必须被电源“抬”上来的电势补齐。你绕圈回到起点，海拔净变化为零。&lt;/p&gt;

&lt;h1 id=&quot;原理图&quot;&gt;原理图&lt;/h1&gt;

&lt;p&gt;NC:Not Connected不连接
出现在芯片引脚定义表里。表示这个引脚内部没有连接任何东西，你设计电路时悬空不接即可（有些芯片注明”NC, do not connect”，那种内部其实连了东西但不许你用，照悬空处理）。
NF:Not Fixed不安装
出现在原理图和 BOM 表里。意思是这个元件位置画了、预留了，但默认不焊接，留给你按需选用（比如预留的调试电阻、可选的滤波电容）。同义的还有 DNP（Do Not Populate）、DNI（Do Not Install）、N.M.。日系厂商（罗姆、村田、TDK 等）的数据手册特别爱用 NF。
0R: 0 Ω ,可以被短路
字面意思：一个标称阻值为 0Ω 的电阻（实际约几毫欧到 50 毫欧），封装和普通贴片电阻一模一样（0402、0603、0805 等），丝印上印 “0” 或 “000”。它不提供任何阻值，存在的意义是当一根“可控的导线”用。&lt;/p&gt;
&lt;h2 id=&quot;ldodcdc&quot;&gt;LDO/DCDC&lt;/h2&gt;
&lt;h2 id=&quot;电平转换&quot;&gt;电平转换&lt;/h2&gt;

&lt;h1 id=&quot;pcb&quot;&gt;PCB&lt;/h1&gt;
&lt;h2 id=&quot;pcb结构&quot;&gt;PCB结构&lt;/h2&gt;
&lt;p&gt;PCB通常由三种原材料组成：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;芯板（Core，棕色那张）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;一块硬质的“玻璃纤维 + 环氧树脂”板，也就是 FR4——你见过的所有绿色板子的“骨头”。它的关键特性：出厂时两面已经各覆了一层铜箔。所以芯板 = 绝缘骨架 + 两面铜。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;半固化片（PP，黄色那张）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;也是玻璃纤维浸环氧树脂，但树脂只半固化，还是“半熟的胶”。它在高温高压下会软化流动，冷却后彻底变硬——它就是三明治里的“胶水层”，负责把多层板粘成一体。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;铜箔（纯铜的薄 sheet）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;单独的铜皮，用来做最外层的走线&lt;/p&gt;

&lt;p&gt;所谓的多层PCB，就是以上三者的堆叠。最表面有一层阻焊层，盖住铜箔。
这里有一些细节需要注意：
铜厚：1oz/2oz，铜箔厚度，决定走线载流能力，具体好像有个计算网站。
板厚：1.6MM是默认的。看工况改
铝基板/MCPCB：FR4换成铝合金，为了散热用的，通常来说LED会用，这个嘉立创不免费。
FPC:柔性板，聚酰亚胺基材能弯折，基本上是屏幕排线什么的
高频板：特殊介质，射频什么的才会用&lt;/p&gt;
&lt;h2 id=&quot;阻焊层开窗&quot;&gt;阻焊层/开窗&lt;/h2&gt;

&lt;h2 id=&quot;铺铜&quot;&gt;铺铜&lt;/h2&gt;
&lt;h2 id=&quot;过孔&quot;&gt;过孔&lt;/h2&gt;
&lt;p&gt;通孔，埋孔，盲孔，一图就看懂了：
&lt;img src=&quot;/img/hw-pcb-vias.png&quot; alt=&quot;PCB 板层：通孔、盲孔、埋孔&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;焊盘&quot;&gt;焊盘&lt;/h2&gt;
&lt;h2 id=&quot;丝印&quot;&gt;丝印&lt;/h2&gt;
&lt;h1 id=&quot;eda&quot;&gt;EDA&lt;/h1&gt;
&lt;p&gt;值得一提的是,工业设计上EDA不仅包括AD等，还有一系列自动化分析/处理软件，是个规模非常大的集群。这里全部以嘉立创EDA为例子：&lt;/p&gt;
&lt;h2 id=&quot;pcb设计流程&quot;&gt;PCB设计流程&lt;/h2&gt;
&lt;p&gt;一个完整的工程从建工程开始，分成原理图和 PCB 两条并行的线，最后各自导出产物：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TD
    A[&quot;新建工程&quot;] --&amp;gt; B[&quot;新建板子&quot;]
    B --&amp;gt; C[&quot;新建原理图&quot;]
    B --&amp;gt; D[&quot;新建PCB&quot;]

    C -.-&amp;gt;|可选| E[&quot;新建器件&quot;]
    E --&amp;gt; E1[&quot;绑定符号&quot;] --&amp;gt; E2[&quot;绑定封装&quot;] --&amp;gt; E3[&quot;添加元件属性&quot;]

    C --&amp;gt; C1[&quot;放置器件&quot;] --&amp;gt; C2[&quot;布线&quot;] --&amp;gt; C3[&quot;检查DRC&quot;] --&amp;gt; C4[&quot;转为PCB&quot;] --&amp;gt; C5[&quot;导出BOM&quot;]

    D --&amp;gt; D1[&quot;放置板框&quot;] --&amp;gt; D2[&quot;布局&quot;] --&amp;gt; D3[&quot;布线&quot;] --&amp;gt; D4[&quot;检查DRC&quot;] --&amp;gt; D5[&quot;导出Gerber&quot;]

    C4 --&amp;gt; D2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;需要注意的几个点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;左侧的“新建器件”是可选的。库里已经有现成的符号和封装就直接跳过；没有才自己画，画完依次绑定符号、绑定封装、添加元件属性。&lt;/li&gt;
  &lt;li&gt;“转为PCB”是两条线的交汇口：原理图侧的连线关系在这里被同步到 PCB 侧，然后接上 PCB 的“布局”。&lt;/li&gt;
  &lt;li&gt;DRC（Design Rule Check，设计规则检查）：一种用于确保电子设计满足特定制造工艺要求的质量保证措施。原理图侧和 PCB 侧各查一次，前者看连接关系，后者看线宽、间距、孔径这些能不能做出来。&lt;/li&gt;
  &lt;li&gt;产物分两份：原理图侧导出 BOM（物料清单，用于采购和贴片），PCB 侧导出 Gerber（光绘文件，交给板厂生产用）。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;eda分层&quot;&gt;EDA分层&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/img/hw-eda-layer-types.png&quot; alt=&quot;EDA 分层：导电层、绘图辅助层与掩模层&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;信号层&quot;&gt;信号层&lt;/h3&gt;
&lt;p&gt;信号层，在双层板中需要注意的只有顶层和底层。顶层是红色，底层是蓝色。&lt;/p&gt;
&lt;h3 id=&quot;阻焊层&quot;&gt;阻焊层&lt;/h3&gt;
&lt;p&gt;紫色，画上去的表示扣掉（不覆盖阻焊剂，也就是开窗）&lt;/p&gt;
&lt;h3 id=&quot;丝印层&quot;&gt;丝印层&lt;/h3&gt;
&lt;p&gt;顾名思义&lt;/p&gt;
&lt;h3 id=&quot;锡膏层&quot;&gt;锡膏层&lt;/h3&gt;
&lt;p&gt;用于生成钢网的层，和元件的区别基本上就是有没有通孔&lt;/p&gt;
&lt;h3 id=&quot;多层&quot;&gt;多层&lt;/h3&gt;
&lt;p&gt;对于多层板，过孔由于穿过所有层，一般会以灰色来表示，称之为多层，也可以手动指定来避免铺铜覆盖&lt;/p&gt;
&lt;h3 id=&quot;机械层板框层&quot;&gt;机械层/板框层&lt;/h3&gt;
&lt;p&gt;用于指出PCB的外观/尺寸&lt;/p&gt;
&lt;h3 id=&quot;禁止区文档层&quot;&gt;禁止区/文档层&lt;/h3&gt;
&lt;p&gt;禁止区：用于和其他人合作的时候避免有人丢奇怪的线路进去的地方，我们基本上用不到
文档层：顾名思义。用不到&lt;/p&gt;

&lt;h2 id=&quot;元件符号与封装&quot;&gt;元件符号与封装&lt;/h2&gt;
&lt;p&gt;小圆点通常来说是拿来对齐的&lt;/p&gt;
&lt;h2 id=&quot;eda快捷键这里用ai整理了&quot;&gt;EDA快捷键（这里用AI整理了）&lt;/h2&gt;
&lt;p&gt;以下整理自官方用户指南的《&lt;a href=&quot;https://prodocs.lceda.cn/cn/introduction/hotkeys/&quot;&gt;快捷键&lt;/a&gt;》和《&lt;a href=&quot;https://prodocs.lceda.cn/cn/introduction/settings/&quot;&gt;个人设置&lt;/a&gt;》两页，默认风格为“嘉立创EDA专业版”。&lt;/p&gt;

&lt;p&gt;有个前提要先知道：专业版从 v1.8 开始用&lt;strong&gt;单键呼出顶部菜单&lt;/strong&gt;，所以原来靠单键触发的功能基本都改成了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt+&lt;/code&gt; 组合。如果你看的是老教程（W 画线、P 放焊盘、V 放过孔那种），那是标准版的路子，在专业版里得多按一个 Alt。快捷键入口在顶部菜单 → 设置 → 快捷键，支持切换“专业版 / 标准版 / Altium Designer”三种风格，也能逐个自定义；冲突时会标出来，不解决不让保存。菜单项上按住 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+左键&lt;/code&gt; 也能直接打开快捷键编辑弹窗。&lt;/p&gt;

&lt;h3 id=&quot;通用&quot;&gt;通用&lt;/h3&gt;
&lt;p&gt;| 功能 | 快捷键 |
| — | — |
| 取消绘制状态（默认）/关闭弹窗 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;鼠标右键单击&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Esc&lt;/code&gt; |
| 打开工程 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+O&lt;/code&gt; |
| 新建工程 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+N&lt;/code&gt; |
| 保存 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+Shift+S&lt;/code&gt; |
| 保存全部 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+S&lt;/code&gt; |
| 撤销 / 重做 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+Z&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+Y&lt;/code&gt; |
| 剪切 / 复制 / 粘贴 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+X&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+C&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+V&lt;/code&gt; |
| 交叉选择（原理图与 PCB 联动） | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+X&lt;/code&gt; |
| 布局传递 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+Shift+X&lt;/code&gt; |
| 根据中心移动 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;M&lt;/code&gt; |
| 删除所选 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Delete&lt;/code&gt; |
| 上一页 / 下一页（新建图页） | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Page Up&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Page Down&lt;/code&gt; |
| 全屏 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F11&lt;/code&gt; |
| 全选 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+A&lt;/code&gt; |
| 查找替换 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+F&lt;/code&gt; |
| 查找相似对象 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+Shift+F&lt;/code&gt; |
| 适应全部 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;K&lt;/code&gt; |
| 视图向左/右/上/下滚动 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Left&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Right&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Up&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Down&lt;/code&gt; |
| 高亮网络 / 取消高亮 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+H&lt;/code&gt; |
| 切换单位 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Q&lt;/code&gt; |
| 展开/收起底部面板 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\&lt;/code&gt; |
| 左向旋转 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Space&lt;/code&gt; |
| 对齐：左 / 右 / 顶 / 底 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+Shift+L&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;R&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B&lt;/code&gt; |
| 居中：左右 / 上下 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+Alt+E&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+Alt+H&lt;/code&gt; |
| 对齐网格 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+Shift+G&lt;/code&gt; |
| 等距分布：水平 / 垂直 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+Shift+H&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+Shift+E&lt;/code&gt; |
| 微调所选图形：左/右/上/下 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Left&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Right&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Up&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Down&lt;/code&gt; |
| 放置元素时显示属性对话框 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Tab&lt;/code&gt; |
| 选中对象时切换选中范围 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Tab&lt;/code&gt; |
| 长按 Shift：矩形保持正方形、导线锁定横竖 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift&lt;/code&gt;（长按） |
| 移动画布（默认） | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;鼠标右键&lt;/code&gt;（长按） |
| 重复到光标 / 重复到其他层 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+D&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+Shift+D&lt;/code&gt; |
| 组合选中 / 取消组合 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+G&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+G&lt;/code&gt; |
| 新窗口打开文档 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+Shift+左键&lt;/code&gt; |
| 关闭当前标签 / 关闭所有标签 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+&amp;amp;#96;&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+Shift+&amp;amp;#96;&lt;/code&gt; |
| 打开帮助文档 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F1&lt;/code&gt; |&lt;/p&gt;

&lt;h3 id=&quot;原理图符号&quot;&gt;原理图/符号&lt;/h3&gt;
&lt;p&gt;| 功能 | 快捷键 |
| — | — |
| 绘制导线 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt+W&lt;/code&gt; |
| 绘制总线 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt+B&lt;/code&gt; |
| 绘制引脚 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt+P&lt;/code&gt; |
| 绘制矩形 / 圆形 / 折线 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt+R&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt+C&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt+L&lt;/code&gt; |
| 绘制文本 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt+T&lt;/code&gt; |
| 放置网络标签 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt+N&lt;/code&gt; |
| 高亮 / 取消高亮网络导线 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+H&lt;/code&gt; |
| 放置器件 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+F&lt;/code&gt; |
| 放置引脚/网络标签/网络端口时弹出属性对话框 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Tab&lt;/code&gt; |&lt;/p&gt;

&lt;h3 id=&quot;pcb封装&quot;&gt;PCB/封装&lt;/h3&gt;
&lt;p&gt;| 功能 | 快捷键 |
| — | — |
| 单路布线 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt+W&lt;/code&gt; |
| 布线时切换常用线宽 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+W&lt;/code&gt; |
| 布线/放置过孔时切换常用过孔 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+V&lt;/code&gt; |
| 差分对布线 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt+D&lt;/code&gt; |
| 等长调节 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+A&lt;/code&gt; |
| 等长调节时增大/减小间隙 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Num+&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Num-&lt;/code&gt; |
| 高亮 / 取消高亮网络导线 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt+H&lt;/code&gt; |
| 显示/隐藏所选飞线 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+R&lt;/code&gt; |
| 取消全部高亮 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+H&lt;/code&gt; |
| 切换图层亮度 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+S&lt;/code&gt; |
| 翻转板子 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt+F&lt;/code&gt; |
| 放置过孔 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt+V&lt;/code&gt; |
| 放置单个焊盘 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt+P&lt;/code&gt; |
| 左右翻转 / 上下翻转 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Y&lt;/code&gt; |
| 布线拐角 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;L&lt;/code&gt; |
| 距离测量 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt+M&lt;/code&gt; |
| 切换单位 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Q&lt;/code&gt; |
| 切换到顶层 / 底层 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt+T&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt+B&lt;/code&gt; |
| 切换到内层 1~4 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4&lt;/code&gt; |
| 切换到上一个/下一个铜层 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+Num*&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Num*&lt;/code&gt; |
| 隐藏/显示铺铜区域 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+M&lt;/code&gt; |
| 重建所有铺铜 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+B&lt;/code&gt; |
| 完成 / 取消 / 回退 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enter&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Esc&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Backspace&lt;/code&gt; |
| 选择重叠图元 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;G&lt;/code&gt; |
| 绘制时翻转路径 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Space&lt;/code&gt; |
| 显示所有图层 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+L&lt;/code&gt; |
| 临时显示/隐藏网络名 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+Q&lt;/code&gt; |
| 吸附 | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt+S&lt;/code&gt; |&lt;/p&gt;

&lt;h3 id=&quot;不在表里但很值得记的几个&quot;&gt;不在表里但很值得记的几个&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+W&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+V&lt;/code&gt; 切换的候选值来自个人设置里预先填好的“常用线宽”和“常用过孔尺寸”，不先去设置里加几条，按了也没得选。&lt;/li&gt;
  &lt;li&gt;个人设置里还能调画布缩放方式（滚轮缩放 or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+滚轮&lt;/code&gt;，会影响触摸板的行为）、中键是拖画布还是缩放、面板自动收起、网格尺寸等。&lt;/li&gt;
  &lt;li&gt;在画布上按住 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+左键&lt;/code&gt; 拖动元素，等于“复制到光标处”，松开即粘贴；再按一下 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Esc&lt;/code&gt; 或右键退出。对名称以数字结尾的导线、网络标签、文本，会自动把编号 +1。&lt;/li&gt;
  &lt;li&gt;选中图元后按 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enter&lt;/code&gt; 直接展开右侧属性面板并聚焦到关键属性上，比右键找属性快。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;经典电路&quot;&gt;经典电路&lt;/h2&gt;
</description>
        <pubDate>Wed, 09 Sep 2026 00:00:00 +0000</pubDate>
        <link>https://moment-new.github.io/2026/09/09/%E6%88%91%E7%A1%AE%E5%AE%9E%E6%90%9E%E4%B8%8D%E6%98%8E%E7%99%BD%E7%A1%AC%E4%BB%B6/</link>
        <guid isPermaLink="true">https://moment-new.github.io/2026/09/09/%E6%88%91%E7%A1%AE%E5%AE%9E%E6%90%9E%E4%B8%8D%E6%98%8E%E7%99%BD%E7%A1%AC%E4%BB%B6/</guid>
        
        <category>硬件</category>
        
        <category>嵌入式</category>
        
        
      </item>
    
      <item>
        <title>P-Com库的开发笔记</title>
        <description>&lt;h1 id=&quot;前言&quot;&gt;前言&lt;/h1&gt;
&lt;p&gt;大概7月末我萌生了写一个通用通信库的念头，打算照着CPP的类的形式，在C上实现一些多态，但是实际动起手来反而感觉十分生疏，可能是码量太少了的缘故。因此边写边做笔记，避免思路混乱。&lt;/p&gt;
&lt;h1 id=&quot;基本类型&quot;&gt;基本类型&lt;/h1&gt;
&lt;p&gt;所有的P-com类都是从一个结构体衍生出来的：&lt;/p&gt;
&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_com_base&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_com_backend_t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;   &lt;span class=&quot;cm&quot;&gt;/* 指向具体后端表 —— 与实现唯一的耦合点 */&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;p_com_type_t&lt;/span&gt;           &lt;span class=&quot;n&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt;               &lt;span class=&quot;n&quot;&gt;ref_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* 引用计数（255个绝对够了） */&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;                  &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;priv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;      &lt;span class=&quot;cm&quot;&gt;/* 具体后端私有数据（可不用） */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;其中，backend指向的是一个虚表，其中储存了可能需要的方法函数指针。由于虚表存在自己的一套继承关系，这里先撇开不谈。
然后又由于：&lt;/p&gt;
&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;P_ERR&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;P_ERR&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deinit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_com_backend_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这里规定了最基本的p_com_backend_t类型，因此我们可以认为，p_com_base的第一个（和第二个）元素都是&lt;/p&gt;
&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;P_ERR&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;P_ERR&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deinit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这是我们的核心：
在C中，只要第一个元素是一致的，我们就能在多种结构体中互相强制转换。所以我们后续的其他子类结构体，只需要符合第一个元素是init（当然第二个是deinit更好），就能实现”多态”和”继承”。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;什么是多态？
同一种类型，在不同参数情况下可以表现出不同的行为&lt;/li&gt;
  &lt;li&gt;什么是继承？
子类型可以获取父类的特征，减少重复代码&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们在这里引入这些的目的，就是为了使用一套尽可能通用的init/deinit框架，使得所有的类型都可以用一个函数进行初始化，不用为了每个类型单独写一个初始化函数。
不过，值得注意的是，我们这里只规定了类长什么样，有什么静态的内容。而动态的“方法”，我们还没有做规定，他们是存在虚函数表中的。
这样，我们拥有了一个可以规范其他类长什么的基类之后，我们就可以开始着手把需求分解成类了。
作为一个通信库，我们肯定要涉及多种通信方式，可能是SPI，可能是UART,可能是CAN，可能是网口或者USB等等。他们的共同点就是都有一个数据输入/输出，都在某条总线上，都可能存在多个设备，我们使用的时候肯定遵循某种协议。因此，我们可以把类分成总线类，协议类，和设备类。
总线上，经常需要一些总线操作，因此我们总线需要一个虚函数表，对总线进行一些操作。
设备上，我们肯定要直接获取数据什么的，要和HAL打交道，因此有个函数表也是比较好的。
协议类，我们也可以搞一个，这样就可以让协议更自由。
那么我们就用了三个主类，以及他们对应的操作虚函数表（backend）&lt;/p&gt;

&lt;p&gt;最后，我们需要一个文件来解决中断问题。这个应该与总线层相结合&lt;/p&gt;

&lt;h1 id=&quot;虚函数表类型&quot;&gt;虚函数表类型&lt;/h1&gt;

&lt;h1 id=&quot;oop-dod优化&quot;&gt;OOP-DOD优化&lt;/h1&gt;
&lt;p&gt;某天晚上我觉得这样做出来的东西太平庸了，嵌入式框架多如牛毛，我的优点在哪？于是我想到了DOD（面向数据编程）的优化，据此开发了一套OOP转DOD的数据结构。&lt;/p&gt;
</description>
        <pubDate>Tue, 18 Aug 2026 00:00:00 +0000</pubDate>
        <link>https://moment-new.github.io/2026/08/18/P-Com%E5%BA%93%E7%9A%84%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/</link>
        <guid isPermaLink="true">https://moment-new.github.io/2026/08/18/P-Com%E5%BA%93%E7%9A%84%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/</guid>
        
        <category>编程</category>
        
        <category>嵌入式</category>
        
        
      </item>
    
      <item>
        <title>我真的搞明白调试了吗</title>
        <description>&lt;h1 id=&quot;性能分析&quot;&gt;性能分析&lt;/h1&gt;
&lt;h2 id=&quot;采样分析&quot;&gt;采样分析&lt;/h2&gt;
&lt;p&gt;采样式性能分析”（sampling profiler），俗称 poor man’s profiler——不修改被测程序，靠反复“暂停看一眼调用栈”来统计热点。它的原理和工作流程如下：&lt;/p&gt;

&lt;h3 id=&quot;核心原理&quot;&gt;核心原理&lt;/h3&gt;
&lt;p&gt;程序在某一时刻“卡在哪”是随机的。如果对运行中的程序随机采样几百上千次调用栈，那么某个函数出现在栈里的频率，就近似等于它的执行时间占比。比如函数 A 出现在 30% 的采样里，就说明它大约消耗 30% 的 CPU 时间。这就是统计采样的思想，不需要在代码里插桩计时。&lt;/p&gt;

&lt;h3 id=&quot;具体做法&quot;&gt;具体做法&lt;/h3&gt;
&lt;p&gt;用 shell 循环反复执行那条 GDB 批处理命令，每次执行完成一轮“采样”：&lt;/p&gt;

&lt;p&gt;target remote :3333 连接 OpenOCD——连接的瞬间 MCU 被 halt（暂停）；
thread apply all bt 打印出此刻所有线程的调用栈快照；
-batch 模式下命令跑完自动退出，断开连接，MCU 恢复运行；
shell 脚本循环几百上千次，把每次的回溯输出追加到日志里。
采完后再离线解析日志：统计每个函数名出现的次数、看它出现在栈的哪一层，就能得到热点排行，甚至用 addr2line 解地址后合成火焰图。&lt;/p&gt;

&lt;p&gt;其优缺点很明显，MemFault如是说道：&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;不幸的是，使用这种方法无法获得更精确的结果。使用 GDB 和 OpenOCD 会带来巨大的开销，这意味着我们只能每 50 毫秒获取一个数据点。鉴于我们的 MPU 每秒能执行约 2 亿次时钟周期，这意味着我们实际上只能每 1000 万次时钟周期获取一个样本。这样的采样频率显然远远不够，无法进行有效的分析。因此，我们必须寻找更好的方法。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;它确实不影响性能，但是也不够精确。因此很容易想到其他办法：&lt;/p&gt;
&lt;h2 id=&quot;itm采样&quot;&gt;ITM采样&lt;/h2&gt;
&lt;p&gt;Instrumented Trace Macrocell (ITM)是cortex-M内核的一种可选功能，它能够将固件或硬件直接产生的跟踪信息进行格式化处理，然后通过专用总线将这些信息输出出来。你也可以通过它来实现日志功能，例如https://blog.japaric.io/itm/ 中描述的那样。它的实现非常简单：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;void putc(char c) {
  while (ITM_STIM32(0) == 0);
  ITM_STIM8(0) = c;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;仅仅是这样就可以做到打印调试信息了，而且能够通过Trace或者SWO口输出到调试器。
关于这个的详细信息，https://interrupt.memfault.com/blog/profiling-firmware-on-cortex-m 里面讲的非常好了，我不想再做多余的搬运。所以不如来看看他其中涉及的一些调试相关寄存器：&lt;/p&gt;
&lt;h3 id=&quot;dwt-control-register&quot;&gt;DWT Control Register&lt;/h3&gt;
&lt;h3 id=&quot;cyccnt-register&quot;&gt;CYCCNT register&lt;/h3&gt;
&lt;p&gt;现在一想，调试用的相关寄存器和逻辑真是繁多，为什么没有一个割舍这些东西，单独出的精简版MCU呢？算了，先不碎碎念，我们看看更现代的一些调试工具：&lt;/p&gt;
&lt;h2 id=&quot;systemview&quot;&gt;SystemView&lt;/h2&gt;
&lt;h2 id=&quot;perf&quot;&gt;perf&lt;/h2&gt;

&lt;h1 id=&quot;openocd&quot;&gt;OpenOcd&lt;/h1&gt;
&lt;h2 id=&quot;gdb&quot;&gt;GDB&lt;/h2&gt;

&lt;h2 id=&quot;参考链接如果正文中没提到的话&quot;&gt;参考链接（如果正文中没提到的话）&lt;/h2&gt;
&lt;p&gt;https://openocd.org/doc/html/Architecture-and-Core-Commands.html#index-tpiu-config&lt;/p&gt;

</description>
        <pubDate>Tue, 18 Aug 2026 00:00:00 +0000</pubDate>
        <link>https://moment-new.github.io/2026/08/18/%E6%88%91%E7%9C%9F%E7%9A%84%E6%90%9E%E6%98%8E%E7%99%BD%E8%B0%83%E8%AF%95%E4%BA%86%E5%90%97/</link>
        <guid isPermaLink="true">https://moment-new.github.io/2026/08/18/%E6%88%91%E7%9C%9F%E7%9A%84%E6%90%9E%E6%98%8E%E7%99%BD%E8%B0%83%E8%AF%95%E4%BA%86%E5%90%97/</guid>
        
        <category>编程</category>
        
        <category>嵌入式</category>
        
        
      </item>
    
      <item>
        <title>我真的搞明白编程范式了吗</title>
        <description>&lt;h1 id=&quot;前言&quot;&gt;前言&lt;/h1&gt;
&lt;p&gt;依旧是开新坑。&lt;/p&gt;
</description>
        <pubDate>Sat, 15 Aug 2026 00:00:00 +0000</pubDate>
        <link>https://moment-new.github.io/2026/08/15/%E6%88%91%E7%9C%9F%E7%9A%84%E6%90%9E%E6%98%8E%E7%99%BD%E7%BC%96%E7%A8%8B%E8%8C%83%E5%BC%8F%E4%BA%86%E5%90%97/</link>
        <guid isPermaLink="true">https://moment-new.github.io/2026/08/15/%E6%88%91%E7%9C%9F%E7%9A%84%E6%90%9E%E6%98%8E%E7%99%BD%E7%BC%96%E7%A8%8B%E8%8C%83%E5%BC%8F%E4%BA%86%E5%90%97/</guid>
        
        <category>编程</category>
        
        
      </item>
    
      <item>
        <title>我真的搞明白C了吗</title>
        <description>&lt;h1 id=&quot;前言&quot;&gt;前言&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;我真的搞明白C了吗？&lt;/strong&gt;&lt;br /&gt;
这是我在和胡文信讨论memcpy的时候想到的。内存我们自以为已经很清楚了（因为嵌入式中flash可以取指执行所以这里也算在内存里），栈，栈的栈顶指针，flash的分页，RAM的对齐，链接器的地址….但是内存地址，我们真的理解了吗？为什么有的0x08000000,有的0x00000000指的是同一个地方呢？
Tsoding的评论区底下，也有非常多人不理解，为什么J语言中指针指向的内容长度为0，我们就可以进行空解引用？我已经做了很多指针的相关运用了，应当是很懂指针了才对，但是….我们真的懂吗，我们奉为圭臬的不能解引用空指针等思想，到底是不是和前文的main一样，是一种路径依赖或者说思维惯式呢？&lt;br /&gt;
因此我写了这篇文章，旨在以新的角度看待内存和指针，以实践的角度搞清楚。&lt;/p&gt;
&lt;h1 id=&quot;内存&quot;&gt;内存&lt;/h1&gt;
&lt;h2 id=&quot;它连续吗&quot;&gt;它连续吗？&lt;/h2&gt;

&lt;h1 id=&quot;指针&quot;&gt;指针&lt;/h1&gt;
&lt;blockquote&gt;
  &lt;p&gt;In Jai size of void is zero. Which means it is safe to dereference null because it reads 0 bytes from memory&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;tsoding发了这么一条推文。&lt;/p&gt;
&lt;h2 id=&quot;什么是指针&quot;&gt;什么是指针？&lt;/h2&gt;
&lt;h1 id=&quot;c与多线程&quot;&gt;C与多线程&lt;/h1&gt;

&lt;h1 id=&quot;volatile&quot;&gt;volatile&lt;/h1&gt;
&lt;p&gt;在计算机编程中，如果一个变量的值可以被当前执行线程以外的其他程序异步读取或修改，则称该变量为 volatile 变量 。&lt;br /&gt;
volatile 的值可能会因以下原因而自发改变：与其他线程共享值；与异步信号处理程序共享值；通过内存映射 I/O 访问硬件设备（通过读写内存来接收和发送来自外围设备的消息）。不同编程语言对这些用例的支持程度差异很大，即使它们都支持 volatile 关键字。volatile 特性会对函数调用约定以及变量的存储、访问和缓存方式产生影响。&lt;br /&gt;
在C/C++中，volatile 关键字的行为有时被描述为抑制编译器优化：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;不移除已有的 volatile 读取和写入操作；&lt;/li&gt;
  &lt;li&gt;不添加新的 volatile 读取和写入操作；&lt;/li&gt;
  &lt;li&gt;不重新排列 volatile 读取和写入操作的顺序。&lt;br /&gt;
乍一看，我们可能认为volatile就是原子操作的保证。但是事实并非如此。它只能实现：允许访问内存映射 I/O 设备，允许在 longjmp 过程中保留值，允许在信号处理程序和程序的其余部分之间共享 volatile sig_atomic_t 对象中的值。&lt;br /&gt;
人们普遍误以为 volatile 关键字在 C 和 C++ 的多线程代码中很有用。事实上， volatile 关键字在 C 和 C++ 中从未作为任何多线程场景下的有效可移植工具发挥作用  &lt;br /&gt;
与 Java 和 C# 编程语言不同，C 和 C++ 中对 volatile 变量的操作并非原子操作，并且缺乏足够的 volatile 顺序保证（即内存屏障 ）。大多数 C 和 C++ 编译器、链接器和运行时环境都无法提供必要的内存顺序保证，因此 volatile 关键字在任何多线程场景中都无法发挥作用。在 C11 和 C++11 标准出现之前，程序员只能依赖各个实现和平台（例如 POSIX 和 WIN32）提供的保证来编写多线程代码。而有了现代的 C11 和 C++11 标准，程序员可以使用诸如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::atomic&amp;lt;T&amp;gt;&lt;/code&gt; 模板之类的新型可移植结构来编写可移植的多线程代码。&lt;br /&gt;
一个常见的例子是指向外设的内存指针：
    &lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;//定义一个指针变量，使其等于某段地址强转为的指针，以便解引用。&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;uint16_t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uint16_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x400100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
 &lt;span class=&quot;c1&quot;&gt;//do something&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;编译器会认为foo除了初始化之外没有任何东西能修改它，循环会被优化为一个常驻循环while(true);
这里，我们只需要加入volatile就能避免优化，这就是内存映射IO的用例。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;内存屏障&quot;&gt;内存屏障&lt;/h1&gt;
&lt;p&gt;内存屏障 （也称为内存栅栏 、 内存围栏或栅栏指令 ）是一种屏障指令 ，它使中央处理器 (CPU) 或编译器强制执行屏障指令前后执行的内存操作的顺序约束。这通常意味着，在屏障指令之前执行的操作保证会在屏障指令之后执行的操作之前执行。&lt;br /&gt;
不同的指令集架构中，内存屏障的实现各不相同，可能之后会单独开一篇文章来讲。&lt;br /&gt;
内存屏障存在的原因是CPU的乱序执行：&lt;/p&gt;
&lt;h2 id=&quot;乱序执行&quot;&gt;乱序执行&lt;/h2&gt;
&lt;p&gt;在计算机工程中， 乱序执行 （或更正式地称为动态执行 ）是一种指令调度范式，用于高性能中央处理器 (CPU) 中，以利用原本会被浪费的指令周期 。在这种范式中，处理器根据输入数据和执行单元的可用性来执行指令 ，  而不是按照指令在程序中的原始顺序执行。 这样，处理器可以避免因等待前一条指令完成而处于空闲状态，同时可以处理能够立即独立运行的后续指令。&lt;br /&gt;
常见的例子如下：&lt;/p&gt;

&lt;p&gt;线程 #1 核心 #1：&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Memory fence required here&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;%d&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;线程 #2 核心 #2：&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Memory fence required here&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;按常理，可能会预期打印语句总是打印数字“ 42 ”；然而，如果线程#2 的存储操作乱序执行，则 f 可能在 x 之前更新，因此打印语句可能会打印“ 0 ”。类似地，线程#1 的加载操作也可能乱序执行，并且 x 可能在 f 被检查之前被读取，因此打印语句同样可能会打印出意外的值。对于大多数程序而言，这两种情况都是不可接受的。必须在线程#2 将 f 赋值给 x 之前插入一个内存屏障，以确保 x 的新值在 f 的值发生变化时或之前对其他处理器可见。另一个重点是，在线程#1 访问 x 之前也必须插入一个内存屏障，以确保 x 的值不会在 f 的值发生变化之前被读取。&lt;/p&gt;

&lt;h1 id=&quot;优先级反转&quot;&gt;优先级反转&lt;/h1&gt;
&lt;p&gt;考虑两个优先级分别为高和低的任务 H 和 L ，它们都可以独占使用共享资源 R。 如果 H 在 L 获得 R 之后尝试获取它，那么 H 将被阻塞，直到 L 释放该资源。在一个设计良好的系统中，共享独占资源（本例中为 R ）通常意味着 L 会及时释放 R， 以避免 H （优先级更高的任务）长时间被阻塞。然而，即使设计良好，也可能出现这样的情况：在 L 使用 R 期间 ，优先级为中等的第三个任务 M 变为可运行状态。此时，由于 M 的优先级高于 L ，它会抢占 L （因为 M 不依赖于 R ），导致 L 无法及时释放 R ，进而导致优先级最高的进程 H 无法运行（也就是说， H 遭受了由低优先级任务（例如 M ）间接导致的意外阻塞）。&lt;/p&gt;
</description>
        <pubDate>Sat, 15 Aug 2026 00:00:00 +0000</pubDate>
        <link>https://moment-new.github.io/2026/08/15/%E6%88%91%E7%9C%9F%E7%9A%84%E6%90%9E%E6%98%8E%E7%99%BDC%E4%BA%86%E5%90%97/</link>
        <guid isPermaLink="true">https://moment-new.github.io/2026/08/15/%E6%88%91%E7%9C%9F%E7%9A%84%E6%90%9E%E6%98%8E%E7%99%BDC%E4%BA%86%E5%90%97/</guid>
        
        <category>编程</category>
        
        <category>嵌入式</category>
        
        
      </item>
    
  </channel>
</rss>
