FPGA 的数字信号处理:Verilog 实现简单的 FIR 滤波器
该项目介绍了如何使用 Verilog 实现具有预生成系数的简单 FIR 滤波器。

绪论
不起眼的 FIR 滤波器是 FPGA 数字信号处理中最基本的模块之一,因此了解如何将具有给定抽头数及其相应系数值的基本模块组合在一起非常重要。因此,在这个关于 FPGA 上 DSP 基础实用入门的教程中,将从一个简单的 15 抽头低通滤波器 FIR 开始,在 Matlab 中为其生成初始系数值,然后转换这些值用于编写 Verilog 模块。
有限脉冲响应或 FIR 滤波器定义为脉冲响应在特定时间段内稳定为零值的滤波器。脉冲响应稳定到零所花费的时间与滤波器阶数(抽头数)直接相关,滤波器阶数是 FIR 的基础传递函数多项式的阶数。FIR 的传递函数不包含反馈,因此如果输入一个值为 1 的脉冲,然后输入一串零值,输出将只是滤波器的系数值。
滤波器的作用基本都是用于信号调节,主要集中在选择滤除或允许通过哪些频率。最简单的例子之一是低通滤波器,它允许低于某个阈值(截止频率)的频率通过,同时大大衰减高于该阈值的频率,如下图所示。

该项目的主要重点是在 HDL(具体为 Verilog)中实现 FIR,它可以分解为三个主要逻辑组件:一个循环缓冲器,用于将每个样本计时到适当地考虑了串行输入的延迟、每个抽头系数值的乘法器以及每个抽头输出的求和结果的累加器。

由于本项目专注于 FPGA 逻辑中 FIR 的设计机制,所以只是使用 Simulink 中的 FDA 工具和 Matlab 为低通滤波器插入一些简单参数,然后使用生成的系数值放到 Verilog 模块中完成滤波器的设计(在后面的步骤中完成)。

选择实现一个简单的 15 抽头低通滤波器 FIR,采样率为 1Ms/s,通带频率为 200kHz,阻带频率为 355kHz,得到以下系数:
-0.0265
0
0.0441
0
-0.0934
0
0.3139
0.5000
0.3139
0
-0.0934
0
0.0441
0
-0.0265
为 FIR 模块创建设计文件
在 Vivado 项目中添加源文件。

在确定 FIR 的顺序(抽头数)并获得系数值后,接下来需要定义的下一组参数就是输入样本、输出样本和系数本身的位宽。
对于这个 FIR,选择将输入样本和系数寄存器设置为 16 位宽,并将输出样本寄存器设置为 32 位,因为两个 16 位值的乘积是一个 32 位值(两个值的宽度相乘得到乘积的宽度,所以如果选择了 8 位抽头的 16 位输入样本,那么输出样本将为 24 位宽)。
这些值也都是带符号的,因此 MSB 用作符号位,在选择输入样本寄存器的初始宽度时一定要记住这一点。要在 Verilog 中将这些值设置为有符号数据类型,使用关键字signed :
reg signed [15:0] register_name;
接下来要解决的是如何在 Verilog 中处理系数值,小数点值需要转换为定点值。由于所有系数值都小于 1,因此寄存器的所有 15 位(总共 16 位,MSB 是有符号位)都可以用于小数位。通常,必须决定要将寄存器中的多少位用于数字的整数部分与数字的小数部分。因此,转换分数值抽头的数学是:(fractional coefficient value)*(2^(15))该乘积的小数值被四舍五入,并且如果系数为负,则计算该值的二进制补码:
tap0 = twos(-0.0265 * 32768) = 0xFC9C
tap1 = 0
tap2 = 0.0441 * 32768 = 1445.0688 = 1445 = 0x05A5
tap3 = 0
tap4 = twos(-0.0934 * 32768) = 0xF40C
tap5 = 0
tap6 = 0.3139 * 32768 = 10285.8752 = 10285 = 0x282D
tap7 = 0.5000 * 32768 = 16384 = 0x4000
tap8 = 0.3139 * 32768 = 10285.8752 = 10285 = 0x282D
tap9 = 0
tap10 = twos(-0.0934 * 32768) = 0xF40C
tap11 = 0
tap12 = 0.0441 * 32768 = 1445.0688 = 1445 = 0x05A5
tap13 = 0
tap14 = twos(-0.0265 * 32768) = 0xFC9C
现在我们终于准备好关注 FIR 模块的逻辑,第一个是循环缓冲区,它引入串行输入样本流并为滤波器的 15 个抽头创建一个包含 15 个输入样本的数组。
always @ (posedge clk)beginif(enable_buff == 1'b1)beginbuff0 <= in_sample;buff1 <= buff0; buff2 <= buff1; buff3 <= buff2; buff4 <= buff3; buff5 <= buff4; buff6 <= buff5; buff7 <= buff6; buff8 <= buff7; buff9 <= buff8; buff10 <= buff9; buff11 <= buff10; buff12 <= buff11; buff13 <= buff12; buff14 <= buff13; endend
接下来,乘法阶段将每个样本乘以每个系数值:
/* Multiply stage of FIR */always @ (posedge clk)beginif (enable_fir == 1'b1)beginacc0 <= tap0 * buff0;acc1 <= tap1 * buff1;acc2 <= tap2 * buff2;acc3 <= tap3 * buff3;acc4 <= tap4 * buff4;acc5 <= tap5 * buff5;acc6 <= tap6 * buff6;acc7 <= tap7 * buff7;acc8 <= tap8 * buff8;acc9 <= tap9 * buff9;acc10 <= tap10 * buff10;acc11 <= tap11 * buff11;acc12 <= tap12 * buff12;acc13 <= tap13 * buff13;acc14 <= tap14 * buff14;endend
乘法阶段的结果值通过加法累加到寄存器中,最终成为滤波器的输出数据流。
/* Accumulate stage of FIR */ always @ (posedge clk) beginif (enable_fir == 1'b1)beginm_axis_fir_tdata <= acc0 + acc1 + acc2 + acc3 + acc4 + acc5 + acc6 + acc7 + acc8 + acc9 + acc10 + acc11 + acc12 + acc13 + acc14;endend
最后,逻辑的最后一部分是将数据流进和流出 FIR 模块的接口。AXI Stream 接口是最常见的接口之一。关键方面是允许控制上游和下游设备之间的数据流的tready和tvalid信号。这意味着 FIR 模块需要向其下游设备提供tvalid信号以指示其输出是有效数据,并且如果下游设备解除其tready信号,则能够暂停(但仍保留)其输出。FIR 模块还必须能够与其主端接口上的上游设备以同样的方式运行。

以下是 FIR 模块的逻辑设计概述:

请注意tready和tvalid信号如何设置输入循环缓冲器的使能值和 FIR 的乘法级以及数据或系数通过的每个寄存器都被声明为有符号的。
FIR模块Verilog代码:
`timescale 1ns / 1psmodule FIR(input clk,input reset,input signed [15:0] s_axis_fir_tdata, input [3:0] s_axis_fir_tkeep,input s_axis_fir_tlast,input s_axis_fir_tvalid,input m_axis_fir_tready,output reg m_axis_fir_tvalid,output reg s_axis_fir_tready,output reg m_axis_fir_tlast,output reg [3:0] m_axis_fir_tkeep,output reg signed [31:0] m_axis_fir_tdata);always @ (posedge clk)beginm_axis_fir_tkeep <= 4'hf;endalways @ (posedge clk)beginif (s_axis_fir_tlast == 1'b1)beginm_axis_fir_tlast <= 1'b1;endelsebeginm_axis_fir_tlast <= 1'b0;endend// 15-tap FIR reg enable_fir, enable_buff;reg [3:0] buff_cnt;reg signed [15:0] in_sample; reg signed [15:0] buff0, buff1, buff2, buff3, buff4, buff5, buff6, buff7, buff8, buff9, buff10, buff11, buff12, buff13, buff14; wire signed [15:0] tap0, tap1, tap2, tap3, tap4, tap5, tap6, tap7, tap8, tap9, tap10, tap11, tap12, tap13, tap14; reg signed [31:0] acc0, acc1, acc2, acc3, acc4, acc5, acc6, acc7, acc8, acc9, acc10, acc11, acc12, acc13, acc14; /* Taps for LPF running @ 1MSps with a cutoff freq of 400kHz*/assign tap0 = 16'hFC9C; // twos(-0.0265 * 32768) = 0xFC9Cassign tap1 = 16'h0000; // 0assign tap2 = 16'h05A5; // 0.0441 * 32768 = 1445.0688 = 1445 = 0x05A5assign tap3 = 16'h0000; // 0assign tap4 = 16'hF40C; // twos(-0.0934 * 32768) = 0xF40Cassign tap5 = 16'h0000; // 0assign tap6 = 16'h282D; // 0.3139 * 32768 = 10285.8752 = 10285 = 0x282Dassign tap7 = 16'h4000; // 0.5000 * 32768 = 16384 = 0x4000assign tap8 = 16'h282D; // 0.3139 * 32768 = 10285.8752 = 10285 = 0x282Dassign tap9 = 16'h0000; // 0assign tap10 = 16'hF40C; // twos(-0.0934 * 32768) = 0xF40Cassign tap11 = 16'h0000; // 0assign tap12 = 16'h05A5; // 0.0441 * 32768 = 1445.0688 = 1445 = 0x05A5assign tap13 = 16'h0000; // 0assign tap14 = 16'hFC9C; // twos(-0.0265 * 32768) = 0xFC9C/* This loop sets the tvalid flag on the output of the FIR high once * the circular buffer has been filled with input samples for the * first time after a reset condition. */always @ (posedge clk or negedge reset)beginif (reset == 1'b0) //if (reset == 1'b0 || tvalid_in == 1'b0)beginbuff_cnt <= 4'd0;enable_fir <= 1'b0;in_sample <= 8'd0;endelse if (m_axis_fir_tready == 1'b0 || s_axis_fir_tvalid == 1'b0)beginenable_fir <= 1'b0;buff_cnt <= 4'd15;in_sample <= in_sample;endelse if (buff_cnt == 4'd15)beginbuff_cnt <= 4'd0;enable_fir <= 1'b1;in_sample <= s_axis_fir_tdata;endelsebeginbuff_cnt <= buff_cnt + 1;in_sample <= s_axis_fir_tdata;endend always @ (posedge clk)beginif(reset == 1'b0 || m_axis_fir_tready == 1'b0 || s_axis_fir_tvalid == 1'b0)begins_axis_fir_tready <= 1'b0;m_axis_fir_tvalid <= 1'b0;enable_buff <= 1'b0;endelsebegins_axis_fir_tready <= 1'b1;m_axis_fir_tvalid <= 1'b1;enable_buff <= 1'b1;endend/* Circular buffer bring in a serial input sample stream that * creates an array of 15 input samples for the 15 taps of the filter. */always @ (posedge clk)beginif(enable_buff == 1'b1)beginbuff0 <= in_sample;buff1 <= buff0; buff2 <= buff1; buff3 <= buff2; buff4 <= buff3; buff5 <= buff4; buff6 <= buff5; buff7 <= buff6; buff8 <= buff7; buff9 <= buff8; buff10 <= buff9; buff11 <= buff10; buff12 <= buff11; buff13 <= buff12; buff14 <= buff13; endelsebeginbuff0 <= buff0;buff1 <= buff1; buff2 <= buff2; buff3 <= buff3; buff4 <= buff4; buff5 <= buff5; buff6 <= buff6; buff7 <= buff7; buff8 <= buff8; buff9 <= buff9; buff10 <= buff10; buff11 <= buff11; buff12 <= buff12; buff13 <= buff13; buff14 <= buff14;endend/* Multiply stage of FIR */always @ (posedge clk)beginif (enable_fir == 1'b1)beginacc0 <= tap0 * buff0;acc1 <= tap1 * buff1;acc2 <= tap2 * buff2;acc3 <= tap3 * buff3;acc4 <= tap4 * buff4;acc5 <= tap5 * buff5;acc6 <= tap6 * buff6;acc7 <= tap7 * buff7;acc8 <= tap8 * buff8;acc9 <= tap9 * buff9;acc10 <= tap10 * buff10;acc11 <= tap11 * buff11;acc12 <= tap12 * buff12;acc13 <= tap13 * buff13;acc14 <= tap14 * buff14;endend /* Accumulate stage of FIR */ always @ (posedge clk) beginif (enable_fir == 1'b1)beginm_axis_fir_tdata <= acc0 + acc1 + acc2 + acc3 + acc4 + acc5 + acc6 + acc7 + acc8 + acc9 + acc10 + acc11 + acc12 + acc13 + acc14;endend endmodule
创建仿真文件
要测试 FIR 模块,需要创建一个测试平台作为其仿真源:

在 FIR 模块中有两个主要的东西需要测试:滤波器算法和 AXI 流接口。为实现这一点,测试台中创建了一个状态机,它生成一个简单的 200kHz 正弦波,并切换从属端的有效信号和 FIR 接口主控端的tready信号。
FIR 模块的测试平台:
`timescale 1ns / 1psmodule tb_FIR;reg clk, reset, s_axis_fir_tvalid, m_axis_fir_tready;reg signed [15:0] s_axis_fir_tdata;wire m_axis_fir_tvalid;wire [3:0] m_axis_fir_tkeep;wire [31:0] m_axis_fir_tdata;/** 100Mhz (10ns) clock */always beginclk = 1; #5;clk = 0; #5;endalways beginreset = 1; #20;reset = 0; #50;reset = 1; #1000000;endalways begins_axis_fir_tvalid = 0; #100;s_axis_fir_tvalid = 1; #1000;s_axis_fir_tvalid = 0; #50;s_axis_fir_tvalid = 1; #998920;endalways beginm_axis_fir_tready = 1; #1500;m_axis_fir_tready = 0; #100;m_axis_fir_tready = 1; #998400;end/* Instantiate FIR module to test. */FIR FIR_i(.clk(clk),.reset(reset),.s_axis_fir_tdata(s_axis_fir_tdata), .s_axis_fir_tkeep(s_axis_fir_tkeep), .s_axis_fir_tlast(s_axis_fir_tlast), .s_axis_fir_tvalid(s_axis_fir_tvalid), .m_axis_fir_tready(m_axis_fir_tready),.m_axis_fir_tvalid(m_axis_fir_tvalid), .s_axis_fir_tready(s_axis_fir_tready), .m_axis_fir_tlast(m_axis_fir_tlast), .m_axis_fir_tkeep(m_axis_fir_tkeep), .m_axis_fir_tdata(m_axis_fir_tdata)); reg [4:0] state_reg;reg [3:0] cntr;parameter wvfm_period = 4'd4;parameter init = 5'd0;parameter sendSample0 = 5'd1;parameter sendSample1 = 5'd2;parameter sendSample2 = 5'd3;parameter sendSample3 = 5'd4;parameter sendSample4 = 5'd5;parameter sendSample5 = 5'd6;parameter sendSample6 = 5'd7;parameter sendSample7 = 5'd8;/* This state machine generates a 200kHz sinusoid. */always @ (posedge clk or posedge reset)beginif (reset == 1'b0)begincntr <= 4'd0;s_axis_fir_tdata <= 16'd0;state_reg <= init;endelsebegincase (state_reg)init : //0begincntr <= 4'd0;s_axis_fir_tdata <= 16'h0000;state_reg <= sendSample0;endsendSample0 : //1begins_axis_fir_tdata <= 16'h0000;if (cntr == wvfm_period)begincntr <= 4'd0;state_reg <= sendSample1;endelsebegin cntr <= cntr + 1;state_reg <= sendSample0;endend sendSample1 : //2begins_axis_fir_tdata <= 16'h5A7E; if (cntr == wvfm_period)begincntr <= 4'd0;state_reg <= sendSample2;endelsebegin cntr <= cntr + 1;state_reg <= sendSample1;endend sendSample2 : //3begins_axis_fir_tdata <= 16'h7FFF;if (cntr == wvfm_period)begincntr <= 4'd0;state_reg <= sendSample3;endelsebegin cntr <= cntr + 1;state_reg <= sendSample2;endend sendSample3 : //4begins_axis_fir_tdata <= 16'h5A7E;if (cntr == wvfm_period)begincntr <= 4'd0;state_reg <= sendSample4;endelsebegin cntr <= cntr + 1;state_reg <= sendSample3;endend sendSample4 : //5begins_axis_fir_tdata <= 16'h0000;if (cntr == wvfm_period)begincntr <= 4'd0;state_reg <= sendSample5;endelsebegin cntr <= cntr + 1;state_reg <= sendSample4;endend sendSample5 : //6begins_axis_fir_tdata <= 16'hA582; if (cntr == wvfm_period)begincntr <= 4'd0;state_reg <= sendSample6;endelsebegin cntr <= cntr + 1;state_reg <= sendSample5;endend sendSample6 : //6begins_axis_fir_tdata <= 16'h8000; if (cntr == wvfm_period)begincntr <= 4'd0;state_reg <= sendSample7;endelsebegin cntr <= cntr + 1;state_reg <= sendSample6;endend sendSample7 : //6begins_axis_fir_tdata <= 16'hA582; if (cntr == wvfm_period)begincntr <= 4'd0;state_reg <= sendSample0;endelsebegin cntr <= cntr + 1;state_reg <= sendSample7;endend endcaseendendendmodule
运行行为仿真
FIR 模块及其测试平台文件就位后,从 Flow Navigator 窗口启动 Vivado 中的仿真器,选择 Run Behavioral Simulation 选项。

如行为仿真所示,FIR 正确过滤信号并正确响应 AXI 流信号。

总结
代码都在上面大家有兴趣可以自行运行,但是大家可能会注意到,这个 FIR 模块在设计上运行综合和实现时时序应该是不能通过的。我们将在下一篇文章中详细介绍如何在无法满足时序要求时重新设计你的设计~

相关文章:

FPGA 的数字信号处理:Verilog 实现简单的 FIR 滤波器
该项目介绍了如何使用 Verilog 实现具有预生成系数的简单 FIR 滤波器。 绪论 不起眼的 FIR 滤波器是 FPGA 数字信号处理中最基本的模块之一,因此了解如何将具有给定抽头数及其相应系数值的基本模块组合在一起非常重要。因此,在这个关于 FPGA 上 DSP 基础…...

使用粒子群优化算法(PSO)辨识锂电池二阶RC模型参数(附MATLAB代码)
目录 一、原理部分 二、代码详解部分 三、结果及分析 一、原理部分 PSO算法由美国学者于 1995 年提出,因其算法简单、效果良好,而在很多领域得到了广泛应用。该算法的起源是模拟鸟群的觅食过程,形成一种群体智能搜索算法。 其核心是&#…...

如何利用地面控制点实现倾斜摄影三维模型数据的几何坐标变换和纠正?
如何利用地面控制点实现倾斜摄影三维模型数据的几何坐标变换和纠正? 倾斜摄影是一种在空中拍摄地表物体的技术,可以获得高分辨率、高精度的三维模型数据,广泛应用于城市规划、建筑设计、土地管理等领域。然而,由于航拍时无法避免姿…...
设计规则之里氏替换原则
tip: 作为程序员一定学习编程之道,一定要对代码的编写有追求,不能实现就完事了。我们应该让自己写的代码更加优雅,即使这会费时费力。 相关规则: 推荐:体系化学习Java(Java面试专题) 1.6大设…...

【叠高高】叠蛋糕游戏的微信小程序开发流程详解
记得小时候玩过的搭积木游戏吗,和叠高高游戏原理差不多的,与之类似的还有盖高楼游戏,就是看谁盖的(叠的)最高,这里讲一下比较基础的叠高高游戏小程序实现过程,对编程感兴趣的同学可以参考学习一…...

收集关键词的方法有哪些?(如何查找精准的行业流量关键词)
关键词的收集通常可以通过以下几种方法: 关键词收集方法 1.根据市场价值、搜索词竞争性和企业实际产品特征进行筛选:确定您的关键词列表之前,建议先进行市场分析,了解您的竞争对手、行业状况和目标受众等信息,以更好地了解所需的特…...
【GreenDao】RxQuery查询并修改GreenDao数据库,完成后更新UI
GreenDao是一个轻量级的ORM(对象关系映射)数据库,而RxJava是一个响应式编程库,可以帮助我们更轻松地处理异步事件。在 Android 应用程序中,您可以使用这两个库一起处理数据库查询和更新,并使用观察者模式来…...
Modifier ‘public‘ is redundant for interface methods错误
java中接口的方法默认是 public abstract 的 所以放心的删掉public即可,如果改为protected 或者 private还会报错 接口的方法及变量的默认修饰符 1.接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract (只能是 public abst…...

Redis缓存击穿及解决问题
缓存击穿的意思是对于设置了过期时间的key,缓存在某个时间点过期的时候,恰好这时间点对这个 Key有大量的并发请求过来,这些请求发现缓存过期- -般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把DB压垮。 解决方案有两种…...

环境感知算法——2.CenterNet基于KITTI数据集训练
1. CenterNet简介 CenterNet采用了一种新的检测思路,即以目标中心点为基础,直接回归出目标的位置和大小。而传统的目标检测算法通常会先产生大量候选框(Anchor),再通过分类器进行筛选,这种方法比较复杂。C…...

JUC 高并发编程基础篇
JUC 高并发编程基础篇 • 1、什么是 JUC • 2、Lock 接口 • 3、线程间通信 • 4、集合的线程安全 • 5、多线程锁 • 6、Callable 接口 • 7、JUC 三大辅助类: CountDownLatch CyclicBarrier Semaphore • 8、读写锁: ReentrantReadWriteLock • 9、阻塞队列 • 10、ThreadPo…...

【十二】设计模式~~~行为型模式~~~命令模式(Java)
命令模式-Command Pattern【学习难度:★★★☆☆,使用频率:★★★★☆】 1.1. 模式动机 在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个…...

可再生能源的不确定性和储能系统的时间耦合的鲁棒性和非预期性区域微电网的运行可行性研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

Revit中如何使创建的族文件内存变小
族文件的大小直接影响到项目文件的大小和软件运行速度,如何将族文件做的最小并且满足项目需求呢? 方法一:清除未使用项 1. 族制作完成可以把族文件中未用到的外部载入族或其他多余数据删掉,点击“管理”选项卡下拉的“清除未使用项”命令; 2…...

ClassLoader源码
介绍 ClassLoader 顾名思义就是类加载器 ClassLoader 是一个抽象类 没有父类 作用 1.负责将 Class 加载到 JVM 中 2.审查每个类由谁加载(父优先的等级加载机制) 3.将 Class 字节码重新解析成 JVM 统一要求的对象格式 常量&变量 //注册本地方法…...

Kafka分区消息积压排查指南
针对某个TOPIC只有几个分区积压的场景,可以采用以下方法进行排查: 消息生产是否指定key? 如果指定了消息key,那么消息会指定生产到hash(key)的分区中。如果指定了key,那么有下列几种可能: 生产该key的消息体…...

数据库 期末复习(4) 概念数据库的设计
第一部分 为啥要引入概念数据库 感觉只有一个重点 实体联系模型----ER模型 第二部分-----实体联系模型 这个例子可以全看完之后再来看 举个例子:根据COMPANY数据库的需求来构造数据库模式:The company is organized into DEPARTMENTs. Each department has a name, number …...
WuThreat身份安全云-TVD每日漏洞情报-2023-05-26
漏洞名称:Barracuda Email Security Gateway TAR文件命令注入 漏洞级别:严重 漏洞编号:CVE-2023-2868,CNNVD-202305-2128 相关涉及:Barracuda Email Security Gateway 5.1.3.001 漏洞状态:在野 参考链接:https://tvd.wuthreat.com/#/listDetail?TVD_IDTVD-2023-12949 漏洞名称…...

关于Idea的一些常用设置项
1. 输出中文不乱码 设置工程项目编码 file -> settings -> Editor -> File Encodings-> 如下图通通UTF-8 2. 创建文件自动设置本文模板 File–>settings–>Editor–>File and Code Templates–>Includes -> 输入类注释模板 /*** Classname ${N…...

Python使用WMI模块获取Windows系统的硬件信息,并使用pyinstaller库编译打包成exe的可执行文件
引言 今天给大家分享一篇有关Python和Windows系统的文章。你有没有想过如何获取Windows系统的硬件信息?或者你是否曾经尝试过将Python脚本编译打包成可执行文件?如果你对这些问题感兴趣,那么这篇文章一定适合你。 背景 由于公司现阶段大多…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...

vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...

多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...