【System Verilog and UVM实力进阶1】SVA语法

news2025/1/19 10:40:43

毛主席说过:人不犯我我不犯人,人若犯我我必犯人。


目录

1 SVA介绍

1.1 什么是断言

1.2 为什么用System Verilog 断言(SVA)

 1.3 System Verilog的调度

1.4 SVA术语

1.4.1 并发断言

1.4.2 即时断言

1.5 建立SVA块

1.6 一个简单的序列

1.7 边沿定义的序列

1.8 逻辑关系的序列 

1.9 序列表达式 

1.10 时序关系的序列

1.11 SVA中时钟定义 

1.12 禁止属性

11.3 一个简单的执行块

1.14 蕴含操作符

 1.14.1 交叠蕴含

1.14.2 非交叠蕴含

1.14.3 后续算子带固定延迟的蕴含

1.14.4 使用序列作为先行算子的蕴含

1.15 SVA 检验器的时序窗口

1.15.1 重叠的时序窗口

 1.15.2 无限时的时序窗口

 后记:


1 SVA介绍

1.1 什么是断言

断言是设计的属性描述。

        ●如果一个在模拟中被检查的属性(property)不像我们期望的那样表现,那么这个断言失败。
        ● 如果一个被禁止在设计中出现的属性在模拟过程中发生,那么这个断言失败。

 一系列的属性可以从设计的功能描述中推知,并且被转换成断言。这些断言能在功能的模拟中不断地被监视。使用形式验证技术,相同的断言能被重用来验证设计。断言,又被称为监视器或者检验器,已经被用作一种调试技术的方式,在设计验证流程中使用了很长时间。传统上,它们由过程语言,比如 Verilog,来实现。它们也能用 PLI 和 C/C++的程序来实现。下面的代码显示了相互断言条件检查的 Verilog 实现,其中信号 a 和信号 b不能同时为高电平。如果这种情况发生,则显示这是一个错误信息。

 这种监视器仅作为模拟的一部分而存在,因此只有当需要时才被纳入设计环境中。这可以通过允许 Verilog 代码条件编译的指令“ `ifdef”来实现。

1.2 为什么用System Verilog 断言(SVA)

虽然 Verilog 可以很容易地用来实现一些检查,它仍有一些不足之处:
        (1) Verilog 是一种过程语言,因此并不能很好地控制时序。
        (2) Verilog 是一种冗长的语言,随着断言的数量增加,维护代码将变得很困难。
        (3) 语言的过程性这一本质使得测试同一时间段内发生的并行事件相当困难。在一些情况下,一个 Verilog 的检验器甚至可能无法捕捉到所有被触发的事件。
        (4) Verilog 语言没有提供内嵌的机制来提供功能覆盖的数据。用户必须自己实现这部分代码。

SVA 是一种描述性语言,可以完美地描述时序相关的状况。语言的描述性本质提供了对时间卓越的控制。语言本身非常精确且易于维护。 SVA 也提供了若干个内嵌函数来测试特定的设计情
况,并且提供了一些构造来自动收集功能覆盖数据。

例子 1.1 显示了分别用 Verilog 和 SVA 实现的检验器。

这个检验器验证当信号 a 在当前时钟周期为高电平时,下面 1~3 个时钟周期内,信号 b 应该变为高电平。

 

 通过这幅图也可以清晰的看到前两次的判断都是成功的,最后一次是失败的。

 1.3 System Verilog的调度

 SystemVerilog 语言被定义成一种基于事件的执行模式。在每个时隙(time slot),许多事件按照安排的顺序发生。这个事件的列表依照标准定义的算法执行。依照这个算法,模拟器可以防止任
何在设计和测试平台互动中的不一致。断言的评估和执行包括以下三个阶段:
预备(Preponed) 在这个阶段,采样断言变量,而且信号(net)或变量(variable)的状态不能改变。这样确保在时隙开始的时候采样到最稳定的值。
观察(Observed) 在这个阶段,对所有的属性表达式求值。
响应(Reactive) 在这个阶段,调度评估属性成功或失败的代码。

图 1-2 显示了一个简化了的 SystemVerilog 事件进程安排流程表 。


1.4 SVA术语

 SystemVerilog 语言中定义了两种断言:并发断言和即时断言。

1.4.1 并发断言

基于时钟周期。
● 在时钟边缘根据调用的变量的采样值计算测试表达式。
● 变量的采样在预备阶段完成,而表达式的计算在调度器的观察阶段完成。
● 可以被放到过程块(procedural block)、模块(module)、接口(interface),或者一个程序(program)的定义中。
● 可以在静态(形式的)验证和动态验证(模拟)工具中使用。一个并发断言的例子如下:

 

 a的波形晚时钟一个Δclk,b的波形与时钟同步。箭头朝上显示成功,所有的失败显示成向下的箭头。这个例子的核心内容是属性在每一个时钟的上升沿都被检验,不论信号 a 和信号 b 是否有值的变化。

1.4.2 即时断言


●基于模拟事件的语义。
● 测试表达式的求值就像在过程块中的其他 Verilog 的表达式一样。它们本质不是时序相关的,而且立即被求值。
● 必须放在过程块的定义中。
● 只能用于动态模拟

一个即时断言的例子如下:
 

即时断言 a_ia 被写成一个过程块的一部分,它遵循和信号 a、b 相同的事件调度。当信号 a 或者信号 b 发生变化时, always 块被执行。区别即时断言和并发断言的关键词是“ property”

1.5 建立SVA块

在任何设计模型中,功能总是由多个逻辑事件的组合来表示的。这些事件可以是简单的同一个时钟边缘被求值的布尔表达式,或者是经过几个时钟周期的求值的事件。 SVA 用关键词“ sequence”
来表示这些事件。序列(sequence)的基本语法是:

许多序列可以逻辑或者有序地组合起来生成更复杂的序列。SVA 提供了一个关键词“ property”来表示这些复杂的有序行为。属性(property)的基本语法是:

属性是在模拟过程中被验证的单元。它必须在模拟过程中被断言来发挥作用。 SVA 提供了关键词“ assert”来检查属性。断言(assert)的基本语法是:

建立 SVA 检验器的步骤如图 1-5 所示:
 


1.6 一个简单的序列

序列 s1 检查信号“ a”在每个时钟上升沿都为高电平。如果信号“ a”在任何一个时钟上升沿不为高电平,断言将失败。注意,这相当于“ a == 1’b1”。
 


 

a信号的波形相较clk时钟往后延迟了一个Δclk

1.7 边沿定义的序列

序列 s1 使用的是信号的逻辑值。 SVA 也内嵌了边缘表达式,以便用户监视信号值从一个时钟周期到另一时钟周期的跳变。这使得用户能检查边沿敏感的信号。三种这样有用的内嵌函数如下:


$rose(boolean expression or signal_name)
● 当信号/表达式的最低位变成 1 时返回真。
$fell(boolean expression or signal_name)
● 当信号/表达式的最低位变成 0 时返回真。
$stable(boolean expression or signal_name)
● 当表达式不发生变化时返回真。
序列 s2 检查信号“ a”在每一个时钟上升沿都跳变成 1。如果跳变没有发生,断言失败。

 

 信号a相较于CLK延迟一个ΔCLK

1.8 逻辑关系的序列 

 序列 s3 检查每一个时钟上升沿,信号“ a”或信号“ b”是高电平。如果两个信号都是低电平,断言失败。

1.9 序列表达式 

 通过在序列定义中定义形参,相同的序列能被重用到设计中具有相似行为的信号上。例如,我们可以定义下面这个序列

通用的序列 s3_lib 能重用在任何两个信号上。例如,我们有两个信号“ req1”和“ req2”,它们中至少一个信号应该在时钟周期的上升沿为 1,我们可以使用下列的序列。

一些在设计中常见的通常的属性可以被开发成一个库以便于重用。比如, one-hot 状态机检查,等效性检查等都适合放在这样的检验器库中。

1.10 时序关系的序列


简单的布尔表达式在每个时钟边缘都会被检查。换句话说,它们只是简单的组合逻辑检查。很多时候,我们关心的是检查需要几个时钟周期才能完成的事件。也就是所谓的“时序检查”。
在 SVA 中,时钟周期延迟用“ ##”来表示。例如, ##3 表示 3 个时钟周期。
序列 s4 检查信号“ a”在一个给定的时钟上升沿为高电平,如果信号“ a”不是高电平,序列失败。如果信号“ a”在任何一个给定的时钟上升沿为高电平,信号“ b”应该在两个时钟周期后为高电平。如果信号“ b”在两个时钟周期后不为 1,断言失败。
注意,序列以信号“ a”在时钟上升沿为高电平开始
 

 与前面小节中的例子不同的是,序列 s4 的开始时间和结束时间不同。如果信号“ a”在任何时钟周期不为高电平,序列在同一个时钟周期开始并失败。如果信号“ a”是高电平,序列开始。在两个时钟周期后,如果信号“ b”是高电平,序列成功(第 5 和第14 时钟周期)。另一方面,如果在两个时钟周期后,信号“ b”不是高电平,序列失败。应注意的是,在图中,成功的序列总是标注在序列开始的位置。

 

 信号A相较于Clk延迟ΔCLK

1.11 SVA中时钟定义 

一个序列或者属性在模拟过程中本身并不能起什么作用。它们必须像下面的例子那样被断言才能发挥作用。

注意,序列 s5 中指定了时钟。这是一种把检查和时钟关联起来的方法,但是还有其他的方法。在序列、属性,甚至一个断言的语句中都可以定义时钟。下面的代码显示了在属性 p5a 的定义中指定时钟。

 通常情况下,在属性(property)的定义中指定时钟,并保持序列(sequence)独立于时钟是一种好的编码风格。这可以提高基本序列定义的可重用性。

断言一个序列并不一定需要定义一个独立的属性。因为断言语句调用属性,在断言的语句中可以直接调用被检查的表达式,如下面的断言 a5b 所示。

 当我们在断言的陈述中要调用已经定义了时钟的序列,就不能再次在断言语句中定义时钟。下面的断言 a5c 就显示了这种错误的编程风格。

1.12 禁止属性

在之前的所有例子中,属性检查的都是正确的条件。属性也可以被禁止发生。换句话说,我们期望属性永远为假。当属性为真时,断言失败。

序列 s6 检查当信号“ a”在给定的时钟上升沿为高电平,那么两个时钟周期以后,信号“ b”不允许是高电平。关键词“ not”用来表示属性应该永远不为真。
 


 

11.3 一个简单的执行块

SystemVerilog 语言被定义成每当一个断言检查失败,模拟器在默认情况下都会打印出一条错误信息。模拟器不需要对成功的断言打印任何东西。读者同样也可以使用断言陈述中的“执行块”
(action block)来打印自定义的成功或失败信息。执行块的基本语法如下所示。

下面显示的检验器 a7 在执行块中使用了简单的显示语句来打印成功和失败信息。

1.14 蕴含操作符

 属性 p7 有下列特别之处:
(1) 属性在每一个时钟上升沿寻找序列的有效开始。在这种情况下,它在每个时钟上升沿检查信号“ a”是否为高。

(2) 如果信号“ a”在给定的任何时钟上升沿不为高,检验器将产生一个错误信息。这并不是一个有效的错误信息,因为我们并不关心只检查信号“ a”的电平。这个错误只表明我们在这个时钟周期没有得到检验器的有效起始点。 虽然这些错误是良性的,它们会在一段时间内产生大量的错误信息,因为检查在每个时钟周期都被执行。为了避免这些错误, 某种约束技术需要被定义来在检查的起始点不有效时忽略这次检查。

SVA 提供了一项技术来实现这个目的。这项技术叫作“蕴含”(Implication)

蕴含等效于一个 if-then 结构。蕴含的左边叫作先行算子”(antecedent),右边叫作“后续算子” (consequent)。先行算子是约束条件。当先行算子成功时,后续算子才会被计算。如果先行算
子不成功,那么整个属性就默认地被认为成功。这叫作“空成功”(vacuous success)。蕴含结构只能被用在属性定义中,不能在序列中使用。
蕴含可以分为两类:交叠蕴含(Overlapped implication)和非交叠蕴含(Non-overlapped implication)。
 

 1.14.1 交叠蕴含

交叠蕴含用符号“ |->”表示。如果先行算子匹配,在同一个时钟周期计算后续算子表达式。下面用一个简单的例子解释。属性 p8 检查信号“ a”在给定的时钟上升沿是否为高电平,如果 a为高,信号“ b”在相同的时钟边沿也必须为高。

图 1-11 显示了断言 a8 在模拟中的响应。 表 1-5 总结了信号“ a”和信号“ b”的采样值和断言的状态。表中一共显示了三种结果。当信号“ a”检测为有效的高电平,而且信号“ b”在同一个时钟沿也检测为高,这是一个真正的成功。若信号“ a”不为高,断言默认地自动成功,则称为空成功。相应的,失败指的是信号“ a”检测为高且在同一个时钟沿信号“ b”未能检测为有效的高电平。
 

1.14.2 非交叠蕴含

非交叠蕴含用符号“ |=>”表示。如果先行算子匹配,那么在下一个时钟周期计算后续算子表达式后续算子表达式的计算总是有一个时钟周期的延迟。下面以属性 p9 举个简单的例子。该属
性检查信号“ a”在给定的时钟上升沿是否为高,如果为高,信号“ b”必须在下一个时钟边沿为高。
 

 

1.14.3 后续算子带固定延迟的蕴含

属性 p10 检查如果信号“ a” 在给定时钟上升沿为高,在两个时钟周期后信号“ b”应该为高。类似的检查在前面已经用不使用蕴含的方式介绍过了。使用蕴含使得所有误报的错误都被消除。只有属性有效开始(信号“ a”为高)时,才进行后续算子的检查(信号“ a” )。图 1-13 显示了属性 p10 的一个模拟的例子。
 

1.14.4 使用序列作为先行算子的蕴含

属性 p10 在先行算子的位置使用的是信号。先行算子同样可以使用序列的定义。在这种情况下,仅当先行算子中的序列成功时,才计算后续算子中的序列或者布尔表达式。在任何给定的时钟周期,序列 s11a 检查如果信号“ a”和信号“ b”都为高,一个时钟周期之后信号“ c”应该为高。序列 s11b 检查当前时钟上升沿的两个时钟周期后,信号“ d”应为低。最终的属性检查如果序列 s11a 成功,那么序列 s11b 被检查。如果没有监测到有效的序列s11a,那么序列 s11b 将不被检查,属性检查得到一次空成功。

 

1.15 SVA 检验器的时序窗口

到目前为止,带延迟的例子使用的都是固定的正延迟。在下面几个例子中,我们将讨论几种不同的描述延迟的方法。属性 p12 检查布尔表达式“ a && b”在任何给定的时钟上升沿为真。如果表达式为真,那么在接下去的 1~3 周期内,信号“ c”应该至少在一个时钟周期为高。 SVA 允许使用时序窗口来匹配后续算子。时序窗口表达式左手边的值必须小于右手边的值。左手边的值可以是 0。如果它是 0,表示后续算子必须在先行算子成功的那个时钟边沿开始计算。

图 1-15 显示了属性 p12 在模拟中的响应。每声明一个时序窗口,就会在每个时钟沿上触发多个线程来检查所有可能的成功。
p12 实际上以下面三个线程展开。
(a && b) |-> ##1 c 或
(a && b) |-> ##2 c 或
(a && b) |-> ##3 c
属性有三个机会成功。所有三个线程具有相同的起始点,但是一旦第一个成功的线程将使整个属性成功。应当注意,在任何时钟上升沿只能有一个有效的开始,但是可以有多个有效的结束。这是因为每个有效的起始可以有三个机会成功。

1.15.1 重叠的时序窗口

属性 p13 与属性 p12 相似。两者最大的区别是 p13 的后续算子在先行算子成功的同一个时钟沿开始计算。
 

 图 1-16 显示了 p13 在模拟中的响应。与属性 p12 最大的区别在于一个成功的开始发生在时钟周期 12。这个成功是因为检查发生了重叠。信号“ c”的值在先行算子成功的同一个时钟沿被检测为高。

 1.15.2 无限时的时序窗口

在时序窗口的窗口上限可以用符号“ $”定义,这表明时序没有上限。这叫作“可能性” (eventuality)运算符。检验器不停地检查表达式是否成功直到模拟结束。因为会对模拟的性能产生巨大的负面影响,所以这不是编写 SVA 的一个高效的方式。最好总是使用有限的时序窗口上限

属性 p14 在任何给定的时钟上升沿检查信号“ a”是否为高。如果为高,那么信号“ b”从下一个时钟周期往后最终将为高,而信号“ c”在信号“ b”为高的时钟周期开始往后最终将为高。


 

 后记

笔者为什么要写这篇文章,原因是之前在一家公司里面,管我的验证小领导,怎么看我不顺眼,觉得我什么也不会,就是想逼我离职。总是拿SV断言说事,语气中充斥着各种轻蔑与鄙视,斜眼看人的不屑的表情让我很难受,真的很难受。

对于帮助过我的人,永远心存感恩。只是痛苦的记忆是我不能释怀。这篇文章整体而言比较简单明了,如果你没有太多的时间的话,没有关系,看一下,基本上就都会了!

持续更新......

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1370336.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

抖音矩阵云混剪系统源码 短视频矩阵营销系统V2.2.1(免授权版)

抖音矩阵云混剪系统源码 短视频矩阵营销系统V2.2.1(免授权版) 中网智达矩阵营销系统多平台多账号一站式管理,一键发布作品。智能标题,关键词优化,排名查询,混剪生成原创视频,账号分组&#xff…

基于Jackson自定义json数据的对象转换器

1、问题说明 后端数据表定义的id主键是Long类型,一共有20多位。 前端在接收到后端返回的json数据时,Long类型会默认当做数值类型进行处理。但前端处理20多位的数值会造成精度丢失,于是导致前端查询数据出现问题。 测试前端Long类型的代码 …

单机多卡训练报错NCCL版本有问题

torch.distributedtorch.distributed…DistBackendErrorDistBackendError: : NCCL error in: …/torch/csrc/distributed/c10d/ProcessGroupNCCL.cpp:1275, internal error, NCCL version 2.14.3 这个不知道什么原因,然后解决方法是 增加环境变量NCCL_SOCKET_IFNAM…

FreeRTOS——软件定时器

一、什么是定时器 简单可以理解为闹钟,到达指定一段时间后,就会响铃。 STM32 芯片自带硬件定时器,精度较高,达到定时时间后会触发中断,也可以生成 PWM 、输入捕获、输出 比较,等等,功能强大&a…

HarmonyOS应用开发学习笔记 应用上下文Context 获取文件夹路径

1、 HarmoryOS Ability页面的生命周期 2、 Component自定义组件 3、HarmonyOS 应用开发学习笔记 ets组件生命周期 4、HarmonyOS 应用开发学习笔记 ets组件样式定义 Styles装饰器:定义组件重用样式 Extend装饰器:定义扩展组件样式 5、HarmonyOS 应用开发…

用html和css实现一个加载页面【究极简单】

要创建一个简单的加载页面&#xff0c;你可以使用 HTML 和 CSS 来设计。以下是一个基本的加载页面示例&#xff1a; HTML 文件 (index.html): <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"…

Python 工具 | conda 基本命令

Hi&#xff0c;大家好&#xff0c;我是源于花海。本文主要了解 Python 的工具的 conda 相关的基本命令。Conda 是一个开源的软件包管理系统和环境管理系统&#xff0c;用于安装多个版本的软件包及其依赖关系&#xff0c;并在它们之间轻松切换。在Windows下&#xff0c;需要安装…

赋能智慧农业生产,基于YOLOv3开发构建农业生产场景下油茶作物成熟检测识别系统

AI赋能生产生活场景&#xff0c;是加速人工智能技术落地的有利途径&#xff0c;在前文很多具体的业务场景中我们也从实验的角度来尝试性地分析实践了基于AI模型来助力生产生活制造相关的各个领域&#xff0c;诸如&#xff1a;基于AI硬件实现农业作物除草就是一个比较熟知的场景…

数据挖掘在制造业中的预测与优化应用

随着大数据时代的到来&#xff0c;数据挖掘技术在各行各业的应用日益广泛&#xff0c;尤其在制造业中&#xff0c;其对于提升生产效率、降低运营成本、优化供应链管理等方面发挥着不可替代的作用。本文将探讨数据挖掘在制造业中的预测与优化应用&#xff0c;通过深入剖析实际案…

强化学习求解TSP(一):Qlearning求解旅行商问题TSP(提供Python代码)

一、Qlearning简介 Q-learning是一种强化学习算法&#xff0c;用于解决基于奖励的决策问题。它是一种无模型的学习方法&#xff0c;通过与环境的交互来学习最优策略。Q-learning的核心思想是通过学习一个Q值函数来指导决策&#xff0c;该函数表示在给定状态下采取某个动作所获…

2023.10.13 求逆序对,二分,求极小值

求逆序对 划分归并对数组进行调整的合理性在于 每次划分数组后&#xff0c;在前面数组的元素与后面数组元素相对次序不会颠覆&#xff0c;就是前面元素在前面划分出的数组里随便调整&#xff0c;也依然在后面数组的任意元素里的前面&#xff0c;而不可能调整到后面数组的任意…

TF-IDF(Term Frequency-Inverse Document Frequency)算法详解

目录 概述 术语解释 词频&#xff08;Term Frequency&#xff09; 文档频率&#xff08;Document Frequency&#xff09; 倒排文档频率&#xff08;Inverse Document Frequency&#xff09; 计算&#xff08;Computation&#xff09; 代码语法 代码展示 安装相关包 测…

2024年甘肃省职业院校技能大赛 “信息安全管理与评估”赛项样题卷①

2024年甘肃省职业院校技能大赛 高职学生组电子与信息大类信息安全管理与评估赛项样题 第一阶段&#xff1a;第二阶段&#xff1a;模块二 网络安全事件响应、数字取证调查、应用程序安全第二阶段 网络安全事件响应第一部分 网络安全事件响应第二部分 数字取证调查第三部分 应用程…

黑马程序员JavaWeb开发|案例:tlias智能学习辅助系统(上)准备工作、部门管理

一、准备工作 1.明确需求 根据产品经理绘制的页面原型&#xff0c;对部门和员工进行相应的增删改查操作。 2.环境搭建 将使用相同配置的不同项目作为Module放入同一Project&#xff0c;以提高相同配置的复用性。 准备数据库表&#xff08;dept, emp&#xff09; 资料中包含…

.NET Framework 与 .NET Core 与 .NET Standard

介绍 在本文中&#xff0c;我们将探讨 .NET Framework、.NET Core 和 .NET Standard 之间的差异。 .NET Framework 与 .NET Core .NET框架.NET核心 历史 .NET Framework 是 .NET 的第一个实现。 .NET Core 是 .NET 的最新实现。 开源 .NET Framework 的某些组件是开源的。 .N…

CSS渐变透明

文章目录 一、前言1.1、MDN 二、实现2.1、源码2.2、线上源码 三、最后 一、前言 使用场景&#xff1a;在做两个元素的连接处的UI适配时&#xff0c;图片的颜色不能保证一定跟背景颜色或者是主色调保持一致时&#xff0c;会显得比较突兀。 1.1、MDN MDN的文档&#xff0c;点击【…

ChatGLM2-6B 大语言模型本地搭建

ChatGLM模型介绍&#xff1a; ChatGLM2-6B 是清华 NLP 团队于不久前发布的中英双语对话模型&#xff0c;它具备了强大的问答和对话功能。拥有最大32K上下文&#xff0c;并且在授权后可免费商用&#xff01; ChatGLM2-6B的6B代表了训练参数量为60亿&#xff0c;同时运用了模型…

uniapp中uview组件库丰富的Table 表格的使用方法

目录 #平台差异说明 #基本使用 #兼容性 #API #Table Props #Td Props #Th Props 表格组件一般用于展示大量结构化数据的场景 #平台差异说明 AppH5微信小程序支付宝小程序百度小程序头条小程序QQ小程序√√√√√√√ #基本使用 本组件标签类似HTML的table表格&#…

【Unity】Timer计时器属性及使用

可以代替协程完成延时操作 可以不用Update进行计时 GitHub开源计时插件 网址&#xff1a;https://github.com/akbiggs/UnityTimer/tree/master 导入&#xff1a;URL&#xff1a;https://github.com/akbiggs/UnityTimer.git 基本功能&#xff1a; 创建计时器&#xff1a; Time…

计算机毕业设计 基于SpringBoot的项目申报系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…