人生如逆旅,我亦是行人人生如逆旅,我亦是行人
转载请注明出处
在数字电路(特别是触发器 Flip-Flop)中,为了准确采样数据,输入信号必须在时钟采样沿的前后一段时间内保持绝对稳定。 这段时间构成了“采样窗口”。如果不遵守这个规则,就会发生时序违例(Timing Violation),导致输出出现亚稳态(未知的乱码)。
📸 秒懂比喻:拍一张清晰的照片
时钟有效沿 (Clock Edge): 按下相机快门的那一瞬间。 建立时间 (Setup Time): 摄影师喊“1,2,3”之前,你必须提前摆好姿势并保持不动的时间(提前准备好)。 保持时间 (Hold Time): 按下快门之后,“咔嚓”声结束前,你还必须继续保持不动的时间(不能马上跑开)。 亚稳态 (Metastability): 如果你在按快门前没站稳,或者刚按完快门你就动了,拍出来的照片就是模糊的(芯片不知道存的是0还是1)。
理想情况
Setup 违例
Hold 违例
$T_{seteup}$和$T_{hold}$如何确定?
$T_{seteup}$和$T_{hold}$是触发器(Flip-Flop)物理器件本身的固有属性,它们的大小是由代工厂(Foundry,比如台积电 TSMC、中芯国际 SMIC)决定的,记录在时序库文件(.lib 文件,Liberty format) 中。
具体来说,Foundry 会对这些标准单元进行大量的 SPICE 仿真,把不同输入转换时间(Transition/Slew)和输出负载下的 $T_{setup}$ 和 $T_{hold}$ 值制成一张张二维查找表(Look-up Table),保存在 .lib 文件中。EDA 工具在计算时,是去 .lib 里查表得出的。
如何修复 Setup 违例?
Setup 违例通常是因为数据跑得太慢,修复的核心思路:让数据跑得更快,或者给数据更多的时间。
优化组合逻辑: 这是前端设计中最治本的方法。减少两个触发器之间的逻辑门级数,或者在长路径中间插入新的寄存器,把一条长路切分成两段短路。 缩短物理线长: 在布局阶段,通过算法将有数据交互的 Cell 摆放得更近,能最显著地降低延迟。这是最根本的物理修复手段。 用更快的标准单元: 将路径上的逻辑门替换为阈值电压更低(Low-Vt,速度快但漏电大)的单元,或者换用尺寸更大、驱动能力更强的单元(Upsize),以加快信号翻转速度。 降低时钟频率: 如果所有手段用尽依然无法满足,最后的妥协方案就是降频(增大时钟周期)。 如何修复 Hold 违例?
Hold违例通常是因为数据跑得太快,核心思路:让数据跑得慢一点。
插入缓冲器或延迟单元: 这是最常用、最有效的方法。在数据跑得太快的路径上人为串联几个 Buffer 或 Delay Cell,强行增加物理延迟,让数据晚一点冲到终点。 更换更慢的标准单元: 将路径上的单元替换为高阈值电压(High-Vt,速度慢但极省电)的单元,或者换用尺寸较小、驱动能力较弱的单元(Downsize)。该方法还能优化芯片功耗和面积。 Global Placement 阶段为什么只关注 Setup 违例?
在GP阶段,EDA 工具通常会忽略或关闭 Hold 修复功能,全力以赴解决 Setup 问题。原因主要有:
以前一直都是在 ReadPaper 上阅读文献,然后在 Obsidian 总结文献内容,翻译软件使用的是 沙拉查词插件 + Quicker。自从 Google 浏览器升级后,已经无法使用沙拉查词插件了 且 ReadPaper自带的翻译有次数限制(也不好用),故打算切换文献阅读软件为 Zotero(该软件有翻译插件,通过配置 API 可调用Gemini、GPT 来实现准确的翻译)。此外,Obsidian 中也有插件可以直接导出 Zotero 内的指定文献中的批注,从而能够在 Obsidian 中快速梳理文献内容并形成笔记(再也不用手动截图文献中的批注啦!)。
Zotero配置
配置步骤如下所示:
在 Zotero官网 下载软件并进行安装 点击 编辑→设置→高级 修改数据库存储位置(这一步可选,默认是将数据放在 C 盘的) 打开 https://zotero-chinese.com/plugins/ 下载插件: Translate for Zotero、Better BibTeX for Zotero 打开 Google AI Studio ,申请 Gemini 密钥 (GPI或其他AI的申请地址自行Google) 点击 编辑→设置→翻译 下划到服务,选择你要使用的翻译服务并填入对应密钥 在 翻译 这一栏上划到通用,取消勾选 自动翻译选择 内容,这样设置后,只有按下 Ctrl+T 时才会翻译选中内容(这一步可选) Obsidian配置
配置步骤如下所示:
在 Obsidian 中下载第三方插件 Zotero Integration
在插件配置中,对于 PDF Utility 一栏点击后面的 Download;Database 一栏选择 Zotero;Note Import Location 一栏选择你要存放导出内容的目录
概述
配置模块的作用为提供一个类型安全、集中式管理应用程序配置项的方式,允许开发者定义任意类型的配置变量,为它们设置默认值,并通过YAML文件方便地加载和修改这些配置。
该模块的核心设计思想是将每一个配置项抽象为一个ConfigVar对象。所有这些对象都由一个单例管理的Config类进行统一存储和访问。这种设计避免了配置项散落在代码各处,使得配置的管理、维护和查阅变得非常方便。
该模块主要由以下几部分构成:
ConfigVarBase: 一个抽象基类,定义了所有配置变量的通用接口,如获取名称、描述、类型名以及与字符串的双向转换(序列化/反序列化)。 ConfigVar<T>: 继承自ConfigVarBase的模板类。它是配置变量的具体实现,能够存储任意类型 T 的值。它还实现了一个回调机制,当配置值发生变化时,可以通知所有监听者。 TypeConverter: 一系列模板类及其偏特化,用于在各种C++类型(包括基本类型、STL容器如vector, list, map, set等)和字符串之间进行转换。该模块默认使用 boost::lexical_cast 进行基础类型转换,并为复杂容器类型提供了基于 yaml-cpp 的序列化和反序列化实现。对于自定义类型,只需完成对应的偏特化即可进行转换。 Config: 一个管理类,它包含一个静态的unordered_map用于存储所有的配置变量。它提供了全局的静态方法来创建、获取和加载配置项,是与本模块交互的主要入口。 本模块通过yaml-cpp库解析YAML文件,再将从.yml中解析出来的结构化数据更新到对应的ConfigVar变量中。
主要特性
类型安全: 每个配置项在定义时都强绑定一个类型,在获取时会进行动态类型检查,防止了类型不匹配导致的运行时错误。 集中式管理: 所有配置项都通过一个全局的Config管理器进行注册和访问,便于追踪和管理。 支持复杂数据结构: 内置了对 std::vector, std::list, std::set, std::unordered_set, std::map, std::unordered_map 等常用STL容器的序列化和反序列化支持。 基于YAML: 使用广泛流行的YAML格式作为配置文件,语法清晰,支持复杂数据结构。 动态更新与回调: 支持在运行时通过加载YAML文件来更新配置值。当某个配置值发生变化时,可以触发预先注册的回调函数,使应用程序能够动态地响应配置变化。 重要函数介绍
Config类
getDatas()
1 2 3 4 5 6 7 8 9 using ConfigVarMap = std::unordered_map<std::string, ConfigVarBase::ptr>; /** * @brief 返回所有的配置项 */ static ConfigVarMap& getDatas() { static ConfigVarMap s_datas; return s_datas; } 这里的数据存储的是基类指针ConfigVarBase::ptr,这是典型的多态设计。因为ConfigVar<T> 是模板类,如果用的是std::unordered_map<std::string, ConfigVar<T>::ptr>;则只能放一种类型的配置变量(比如 int、std::string)。
概述
日志模块通过封装 spdlog 库来实现,模块以 velox::log 命名空间组织,且提供了宏定义方便外部调用。该模块支持异步日志输出、多目标日志(控制台+文件)、配置文件定义(通过集成config模块实现)。
这意味着整个日志系统的行为——包括有哪些日志记录器(Logger)、每个记录器的日志级别、输出格式以及输出目标(控制台、文件等)——都可以在 YAML 配置文件中定义。更进一步,当配置文件被重新加载时,日志系统能够动态地、无需重启应用就完成新增、删除或修改日志记录器的操作,实现了日志系统的热重载。
此外,该模块默认会创建一个名为 default 的全局异步日志器,可同时输出到控制台和日志文件,方便快速使用。
主要特性
配置驱动:整个日志系统的结构和行为由 velox::config 模块中的 logs 配置项驱动,实现了代码与配置的分离 动态热重载:通过监听配置项 logs 的变更事件,可以动态地创建、更新或删除日志记录器,极大提升了灵活性 全异步日志:默认创建的所有日志记录器都是异步的,使用全局线程池处理 I/O 操作,最大限度地降低了对业务线程性能的影响 简洁的宏接口:提供了一系列 VELOX_... 宏,如 VELOX_INFO, VELOX_LOGGER_WARN,简化了日志调用,并与 spdlog 的使用方式保持一致 多目标输出:每个日志记录器可以配置多个输出目标(称为 Appender),例如可以同时向控制台和每日轮转的日志文件输出 精细化控制:可以为整个日志记录器及其下的每个 Appender 单独设置不同的日志级别和输出格式 重要函数介绍
初始化函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 /** * @brief 对 spdlog 进行初始化, 并创建和设置配置参数 logs * @param[in] queue_size 用于异步 logger 的队列大小 * @param[in] n_threads 用于异步 logger 的线程数 * @return 成功返回 true */ bool initSpdlog(std::size_t queue_size, std::size_t n_threads) { try { /*------------- 配置默认的日志器 -------------*/ spdlog::init_thread_pool(queue_size, n_threads); // 标准控制台输出 auto stdout_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>(); stdout_sink->set_level(spdlog::level::debug); // 日志文件输出, 0点0分创建新日志 auto log_path = getLogPath("default"); auto file_sink = std::make_shared<spdlog::sinks::daily_file_sink_mt>(log_path.
概述
在现代开发中,多线程可以提高程序的运行效率和响应速度,它已经成为提高应用程序性能、处理并发任务的重要手段。使用多线程需要注意线程同步、资源消耗等问题,当使用的线程数多了时,手动进行管理是十分困难的。为了解决这些问题,线程池作为一种有效的线程管理机制应运而生。
线程池会预先创建一定数量的工作线程,我们只需将待执行任务提交到线程池,线程池会负责任务的分配与执行,从而简化线程管理、减少系统频繁创建与销毁线程的开销、提高资源利用率。其核心思想是避免频繁的创建和销毁线程,减少系统开销。
主要特性
完整的生命周期管理:线程池具有明确的运行状态,构成一个状态机,确保在任何时刻都处于可预期的状态。 支持暂停和恢复:在暂停状态下,线程池会继续接收新任务,但所有工作线程将暂停执行,直到线程池被恢复。 支持优雅关闭:析构函数会自动调用 shutdown函数,安全的销毁线程池,符合 RAII (Resource Acquisition Is Initialization) 原则。 支持调整工作线程数量:可以在运行时通过 increaseThreadCount 和 decreaseThreadCount 方法动态地增加或减少工作线程的数量,以适应不同的负载需求。 支持自动调整工作线程数量:引入核心线程数和最大线程数两个概念,使得线程池能在工作负载变化时自动调整线程数量。核心线程始终保留在池中,而最大线程数则限定线程池可动态扩展的上限。 支持配置文件:可以通过threadpool.yml配置文件修改线程池的相关参数(如核心线程数、最大线程数等),无需重复编译。 基础知识
在正式介绍线程池模块的相关代码前,我们需要先了解一些必要的现代C++编程基础知识。
std::atomic
std::atomic 是 C++11 引入的模板类,用于实现多线程环境下的原子操作,从而避免数据竞争。原子操作是指一个不可被中断的操作,要么完全执行,要么完全不执行,在执行过程中不会被其他线程的调度打断。std::atomic简介
作用
替代互斥锁:对于简单的计数器、标志位等共享状态,使用 std::atomic 比使用互斥锁(std::mutex)的开销更小,性能更高。锁通常会引起线程阻塞和上下文切换,而 「原子操作通常由特殊的 CPU 指令实现,属于无锁(Lock-Free)编程的范畴」。 用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <atomic> #include <iostream> #include <thread> #include <vector> // 使用 std::atomic<int> 作为线程安全的计数器 std::atomic<int> counter(0); void increment() { for (int i = 0; i < 10000; ++i) { // 原子地增加计数器,等价于 counter = counter + 1 // 这个操作是线程安全的 counter.
该笔记只是用于记录和梳理CMake中比较重要的知识,并不会写得很详细(比如不会写出使用project()命令后会创建哪些变量)。学完本笔记即可上手使用CMake,一些很细的知识可以在遇到所用的场景时自行查阅官方资料。
基础框架
最基本的CMake项目是由单个源代码文件构建可执行文件,对于这种简单的项目,只需要给 CMakeLists.txt 文件提供三个命令:
1 2 3 4 5 6 7 8 9 cmake_minimum_required(VERSION x.xx) # 指定CMake最低版本 project( ProjectName # 项目名称 VERSION x.x.x # 项目版本 LANGUAGES CXX # 项目语言, CXX表示C++ ) add_executable(ExecutableName SourceCodePath) # 设置可执行文件的名称和源代码路径 例如:
1 2 3 4 5 6 7 8 9 cmake_minimum_required(VERSION 3.15) project( MBFF VERSION 0.1.12 LANGUAGES CXX ) add_executable(MBFF src/main.cpp) 这样我们就可以产生一个名为MBFF的可执行文件。
其中 cmake_minimum_required()和project()命令需要在项目的顶级CMakeLists.txt给出(当项目十分复杂时,会存在多个CMakeLists.txt文件)。
cmake_minimum_required(): 该命令是CMakeLists.txt中第一条执行的命令,用于控制CMake的版本,禁用一些已启用的特性 project(): 设置项目的相关属性,可以只设置项目名称,但推荐按上面的写法来用 add_executable(): 产生可执行文件,没有该命令是不会产生可执行文件的 注意:CMakeLists.txt的命令执行顺序是从上往下的,所以要有逻辑的编写命令。
使用上面的命令足够处理一些基本情况了,但是当我们在代码中使用了一些C++17或更高标准的特性后,会发现编译失败,这是因为编译器会使用默认标准(通常为 C++98/GCC 或 C++14/Clang, 可通过输出变量 CMAKE_CXX_STANDARD_DEFAULT 的值查询默认值)来进行编译,导致不存在这些特性。这时,就需要使用 set() 命令来进行相关设置:
zyz 发布于 收录于 算法题目
思路
该题就是给定一个编码后的字符串,然后返回解码后的字符串。
由题可知要处理嵌套的情况,即要先解决内层的$k[encoded_string]$,再解决外层,即后进先出,很适合用栈来解决。而且其结构为$k[encoded_string]$,很适合用双栈来解决,一个为数字栈用来存储重复几次,一个为字符串栈用来存储每一层进入 $[$ 时,之前构造的字符串。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 class Solution { public: string decodeString(string s) { string cur_str, pre_str, temp; stack<int> s_n; // 数字栈,存储重复几次 stack<string> s_s; // 字符串栈,存储前面的字符串 for (size_t i = 0; i < s.