转载:浅谈可移植激励规范(PSS)复用策略_路科验证-CSDN博客

译者按 :当今硬件设计变得愈加复杂,如何创建出足够的测试来保证设计的正确性是每个硬件工程师需要面对的问题。Accellera的可移植激励测试规范(PSS、又称便携激励标准)[1]旨在希望能够提供一个独立的测试来源,从而实现跨层级的验证复用,即无论是IP级别、子系统、还是SoC级都使用同样的测试来源,他们也希望提供一系列功能来解决不同级别对于验证测试的不同要求,从而达到真正意义上的复用。然而,即使是像Java和C++这样拥有强大的面向对象特性的编程语言,都不能直接保证我们能得到高质量的可复用的代码,PSS本身的语言特性也同样需要工程师按照一定规范使用才能达到高效复用的目的。如果我们正确的使用这些特性,实现设计IP和测试目的的复用,能够显著减少其所消耗的时间成本。这篇文章就是讨论了如何通过前期的计划,针对自身公司的规模和设计需求使用PSS更加正确、有效的通过复用来加速验证过程。

剖析可移植激励

可移植激励(下文简称PSS)语言在设计初期就考虑到了测试意图和自动化创建测试的要求。下图一就很好的展示了PSS是如何通过对于测试意图和测试实例的清晰区分,来达到对于测试意图的平台复用的。在PSS描述文件中,测试意图主要抽象定义了一个测试需要执行什么行为,是通过声明的形式来实现的。就如同在SystemVerilog语言中声明式的约束一样,我们可以通过这种方式得到比较好的复用性。

图一 - PSS描述文件结构详解

通过这种声明式的语言特性,PSS很好地满足了复用和自动化的需求。通过这种特性,定义了合法空间中可以发生的行为,我们能够直接编写程序定义其行为内容而不是如何执行这种行为。如果有读者使用过SystemVerilog的约束特性,就能够明白约束定义的是合法激励数据范围。PSS语言正是将这种特性从数据定义扩展到测试场景的使用上,提供了一种新的元素-动作(action)。

然而支持这种声明式的语言特性并不够,因为测试本质上是需要和设计单元进行底层的互动,如修改寄存器或者中断等等来达到验证的目的。所以测试代码不仅仅是要定义抽象的测试场景,也需要能够定义底部的设计细节。另外绝大部分的验证环境已经拥有大量的测试代码供工程师使用,所以大多数情况下,工程师们会使用他们熟悉的编程语言如SystemVerilog、C++或C来实现所需要的可移植激励测试。

当然,我们使用PSS的最终目的是为了生成更多的测试用例,下图二就向我们展示了一个典型的PSS使用流程。在这种情况下,PSS文件一般会在测试生成过程中被执行。比如说,我们可以使用PSS来提供约束生成仲裁功能。在SoC环境下,我们会系统的处理器执行测试,此时我们可以提前使用PSS生成一些简单的测试用例来让处理器更高效快速的运行。

在这两种使用环境下,PSS都会将抽象的测试场景定义和测试生成两部分完全分离出来。

图二 - PSS流程详解

详解可移植的含义

在详细讨论可移植激励应用设计策略之前,有必要花时间了解“可移植”本身的含义。接下来我们将从三个方面讨论如何更好地实施相应设计策略。

在讨论可移植测试特性时,我们经常会涉及到垂直复用这一术语。这个概念通常是指从项目早期 - 一般是指在IP层级 - 就开发出相应测试意图,以便在之后的验证流程中重复使用,比如子系统和系统层级。对于测试意图在各个层级上的垂直复用,能够提供一个全面健壮的测试库,有效帮助我们更快的搭建测试环境,同时进一步减少在开发过程中带来的程序错误。当然尽管垂直测试的好处多多,但是开发这种测试需要各个团队协作共同努力,比如我们需要IP层级的团队同时支持系统级和子系统级别的测试开发,另外也需要对已存在的IP开发相应的可移植激励描述文件。

水平复用则可以让测试意图文件在不同项目中工作,当然设计本身会有一些参数上的变化。但是可移植激励标准本身声明式的语言特性,能够有效降低对于参数调整带来的工作量。我们只需要调整测试规则即可,而不需要对测试意图本身进行更新。

图三 - 垂直复用实例

如图三的实例,不同的SoC设计中可能包含不同数目的DMA引擎。如果使用直接测试的方式,我们需要检查所有测试文件,以确保他们检查SoC时使用正确的DMA引擎数目。但是使用可移植激励规范时,我们只需要调整DMA参数,即可以重新生成正确的测试文件。

最后一项可能比较难以理解 - 测试技术的可移植性。例如SystemVerilog的受约束随机特性,虽然可以有效帮助我们提升验证效率和速度,但是这种特性只能使用在模拟测试环境当中,无法使用在以C++为主的高级综合环境(HLS)中,也并不能使用在嵌入式软件测试中,因为一般来说,嵌入式系统的硬件资源有限,没有办法运行一个完整的SystemVerilog模拟器。

但是可移植激励规范的出现,让这些测试技术,比如自动化的受约束随机测试,能够在不同的测试环境中得以应用。这一益处本身也许就足够可以说服团队采用可移植激励规范。

在一个项目初期,团队就需要从以上三方面来考虑如何设计PSS策略,比如哪方面的复用更为重要,需要团队投入更多的资源进行开发。从而让整个项目能够最大化利用可移植激励带来的好处。

实例详解

我们接下来将用一个非常简单的实例来探讨PSS的实际应用,我们将继续使用图三展示的SoC设计框架,其中包含一个四核RISC-V处理器、一个外围设备子系统以及一些其他控制器组件。

接下来我们也会从组件层级讨论DMA IP。如下图四,我们可以看到一个组件级别的验证环境框架图。另外如图五所示,我们也会探讨一下如何从子系统级别嵌入一个DMA引擎、一个UART控制器和一个中断控制器。

图四 - DMA引擎模块级环境

图五 - 外围子系统验证环境

现有测试用例的复用

当我们完成文档的编写,接下来就是对代码库进行分析,来确定哪一些可以进行复用。我们可以利用这段时间来评估一下有多少现有代码可以继续用于当前项目。另外如果时机允许也可以借此来评估对整个组织内的相关代码,通过整合资源可以更好的提升开发效率。

约束

PSS描述文件绝大部分基于约束语句,这是由其声明特性所决定的。所以现有的一些约束文件也可以很轻易的转换成PSS格式进一步节省开发时间。

SystemVerilog的约束文件由于其声明特性,通常可以作为PSS描述文件二次再开发的来源。另外其本身语法特性与PSS非常相似,我们甚至可以直接拷贝复制进PSS文件直接进行使用。这种情况的使用案例是一些IP或者子系统的配置对象代码,可以这样复用在PSS文件当中。

通常约束文件可读性比较低,比如SoC的内存映射文件。但是通过一点点改动,我们就能将其改写成PSS约束,来专门定义不同内存空间的读取权限。

测试案例实现细节

团队现存的测试环境通常是一个非常好的实现基础,当然这其中需要花时间将其按照PSS的语法进行改动。

UVM环境中,我们会使用utility sequence来对IP模块进行简单的操作,比如初始化设置,命令其进行数据计算等等。这些序列在定义时通常有一些随机约束和变量。其他情况下,我们会通过输入参数来控制其工作模式。无论如何,我们都可以通过一些方法将其转换成PSS描述文件。

task init_single_transfer(
int unsigned channel,
int unsigned src,
int unsigned inc_src,
int unsigned dst,
int unsigned inc_dst,
int unsigned sz
);
wb_dma_ch ch = m_regs.ch[channel];
uvm_status_e status;
uvm_reg_data_t value; // Disable the channel
ch.CSR.read(status, value);
value[0] = 0;
ch.CSR.write(status, value); // These registers are volatile. Read-back the content
// so the register model knows to re-write them
ch.A0.read(status, value);
ch.A1.read(status, value);

图六 - SystemVerilog测试复用实例

图六截取了一段SystemVerilog代码,其中我们定义了一个virtual sequence的task,来设计DMA引擎在一条特定的数据通路上传输数据。这段代码就可以通过简单改写将其使用在测试DMA引擎的PSS模型中。需要注意的是,因为我们本身的代码使用了UVM,所以底层是使用了UVM寄存器模型来读取DMA引擎的相关寄存器。

void wb_dma_drv_init_single_xfer(
wb_dma_drv_t *drv,
uint32_t ch,
uint32_t src,
uint32_t inc_src,
uint32_t dst,
uint32_t inc_dst,
uint32_t sz
) {
uint32_t sz_v, csr; csr = WB_DMA_READ_CH_CSR(drv, ch); csr |= (1 << 18); // interrupt on done
csr |= (1 << 17); // interrupt on error
if (inc_src) {
csr |= (1 << 4); // increment source
} else {
csr &= ~(1 << 4);
}
if (inc_dst) {
csr |= (1 << 3); // increment destination
} else {
csr &= ~(1 << 3); // increment destination
}
csr |= (1 << 2); // use interface 0 for source
csr |= (1 << 1); // use interface 1 for destination csr |= (1 << 0); // enable channel // Setup source and destination addresses
WB_DMA_WRITE_CH_A0(drv, ch, src);
WB_DMA_WRITE_CH_A1(drv, ch, dst); sz_v = WB_DMA_READ_CH_SZ(drv, ch);
sz_v &= ~(0xFFF); // Clear tot_sz
sz_v |= (sz & 0xFFF);
WB_DMA_WRITE_CH_SZ(drv, ch, sz_v); // Start the transfer
WB_DMA_WRITE_CH_CSR(drv, ch, csr); drv->status[ch] = 1;
}

图七 - 相关测试的C代码复用

上图是一个相似功能的C代码实现。我们同样也可以将其转换用在PSS测试中。

当然需要注意的是虽然两种代码实现的测试目的相似,但我们会讨论哪一种更适合用在PSS文件中。

创建可复用的PSS库

当我们开始考虑在公司内部创建PSS代码库时,通用的数据结构是需要优先考虑的一点。因为PSS还是一个比较新的标准,所以业界现在还没有一个标准化的通用数据库。但是我们非常建议在项目开始初期就为常用的数据结构创建一个通用库。这样做的原因是PSS描述文件本身会经常使用一些非常类似的数据结构,例如一个memory buffer需要定义其地址和内存大小。

struct data_mem_t {
rand bit[31:0] addr;
rand bit[31:0] sz;
}

图八 - 可复用的数据缓存结构

我们并不期望三个负责不同IP的工程师能够定义出相同的memory buffer structure。所以提前定义出共用的数据结构库,并确保团队及时的使用和维护,能够让PSS模型在子系统和SoC层面的复用变得不那么复杂。

另外我们也推荐创建一个IP层面的PSS代码规范。比如,所有IP层面的PSS action,都需要从一个通用的abstract action type继承出来。例如图九所示。

abstract action dma_dev_a : pvm_dev_a {
// All transfers involve a channel
rand bit[7:0] in [0..7] channel;
// Size of each transfer
rand bit[4] in [1,2,4] trn_sz; } /**
* Transfer memory-to-memory
*/
action mem2mem_a : dma_dev_a {
input data_ref_mem_b dat_i;
output data_ref_mem_b dat_o; } /**
* Transfers data to a memory address
*/
action dev2mem_a : dma_dev_a {
output data_ref_mem_b dat_o;
input data_ref_s info_i; rand bit[31:0] src_addr

图九 - IP specific common base action

Type extension是PSS的一大特性,只有将所有PSS action全部从base action继承出来,才能在整个IP验证流程中更好的使用该特性。

创建可复用的数据产生和检查流程

测试结果的检查方式对于IP验证和SoC验证流程来说,有着很大的不同。IP层级的验证我们大多会使用基于scoreboard的检查,这样我们能够看到IP中的每一个数据操作是如何被执行的,也可以检查到最终的运行结果。但是在SoC层级,由于设计本身的局限性,我们一般只会检查最终的数据结果。

因此,如果公司内部比较看重垂直复用性,那么定义一个在IP层级和SoC层级可以同时使用的数据核查策略就显得非常重要。通常来说,我们在PSS描述文件当中会更多侧重对in-memory data最终结果的检查。

当然,我们也可以在implementation check基础上增加更多的functional check。例如在IP层级中,DMA引擎操作就可以通过纯粹功能性上的验证来进行测试,即接收端的数据和发送端的数据是否相符。IP层级上的scoreboard仍旧可用来检查DMA的传输操作,与此同时子系统和SoC层级上也能够复用这种验证策略,比如可以使用在对SoC的性能测试中。

测试实现的复用

针对相同的测试目的我们会有多种实现方式,对于验证工程师来说我们要学会利用UVM本身的各种特性,例如寄存器模型等。与此同时,我们也要评估并利用本身公司为硬件设计的嵌入式软件自带的各种特性,例如直接使用其定义的寄存器访问方式并引进到验证测试当中以避免重复定义。

定义通用APIs

话虽如此,能够为不同的测试实现提供共用组件肯定会带来更积极的作用,这其中第一步就是设计通用的API。

task mem2mem(
int unsigned channel,
int unsigned src,
int unsigned dst,
int unsigned sz);
init_single_transfer(channel, src, 1, dst, 1, sz);
wait_complete_irq(channel); endtask

图十 - 通用DMA API示例

上图的实例代码是通过对于IP层级的SV task的复用定义的一个DMA数据操作API。

而图十一则是相同功能在嵌入式软件当中的C代码实现。需要注意的是C代码实现当中的devid变量是由于其本身并不是面向对象的程序语言导致的。在SV环境中,mem2mem task是作为一个类的成员,在这个类的定义里devid会由PSS模型当中其他合适的对象代码所提供。但是由于C语言中并没有相关特性,用户需要提供这个变量以便于和其他模型进行通信。保持功能上一致的API能够非常显著的简化将PSS移植到不同测试解决方案的工作量,即使在实现细节上由于不同语言会有些许不同之处。

 void wb_dma_dev_mem2mem(
uint32_t devid,
uint32_t channel,
uint32_t src,
uint32_t dst,
uint32_t sz,
uint32_t trn_sz) {
wb_dma_dev_t *drv = (wb_dma_dev_t *)uex_get_device(devid);
uint32_t csr, sz_v;
uex_info_low(0, "--> wb_dma_dev_mem2mem %s channel=%d src=0x%08x dst=0x%08x sz=%d",
drv->base.name, channel, src, dst, sz);
// Disable the channel
csr = uex_ioread32(&drv->regs->channels[channel].csr);
csr &= ~(1);
uex_iowrite32(csr, &drv->regs->channels[channel].csr); // Program channel registers
csr = uex_ioread32(&drv->regs->channels[channel].csr);

图十一 - 通用DMA API C语言示例

如果垂直复用在项目中更为重要,那可考虑投入资源开发一个提升环境兼容性的中间件。比如图十二所示的UEX硬件访问层。UEX硬件访问层[2]能够提供以C为主的API来访问平台内存和多线程操作。使用这种中间件能够使得测试实现代码可以在嵌入式软件环境中开发并且可以在项目初期就可以进行debug调试,也可以在接下来多个验证环境中复用。

图十二 - UEX硬件访问层

无论是否使用上述的访问兼容层,还是通过一系列API来提供这种支持,我们都需要考虑到不同IP的测试实现将如何配合。所有IP组件的测试实现代码可能都需要访问内存,而且绝大部分的IP都需要在中断发生时得到系统通知。在实际生产环境中,我们会通过操作系统来协调不同驱动同时工作。但是在验证环境中,不论是UVM还是嵌入式软件,我们都需要一个更为轻量化的解决方案。

static void wb_dma_dev_irq(struct uex_dev_s *devh) {
wb_dma_dev_t *dev = (wb_dma_dev_t *)devh;
uint32_t i;
uint32_t src_a; src_a = uex_ioread32(&dev->regs->int_src_a); // Need to spin through the channels to determine
// which channel to activate
for (i=0; i<8; i++) {
if (src_a & (1 << i)) {
// Read the CSR to clear the interrupt
uint32_t csr = uex_ioread32(&dev->regs->channels[i].csr);
dev->status[i] = 0;
uex_event_signal(&dev->xfer_ev[i]);
}
}
}

图十三 - DMA IRQ Routine(通过UEX API 实现)

上图展示了DMA IP可以通过使用UEX API实现中断服务,读取DMA寄存器并在DMA传输完成之后发出通知。UEX组件库能够提供相同功能的代码,运行在UVM、或者嵌入式裸金属环境下,当然在操作系统下也能够运行。这种复用方式能够确保在项目早期即可对IP进行debug调试。

定义通用PSS接口

在一个项目中,测试代码会与一个IP类型的多个实例进行数据通信,那么我们也就需要定义一种通用的方式来选择实例以便进行测试。

component pvm_dev_c {
bit[7:0] devid;
action pvm_dev_a { } }

图十四 - 测试实现基础组件定义

如上图所示,我们可以通过提供一个devid变量来声明一个IP中的哪个实例正在被使用。当然如果保证复用性,则需要在一个项目所有的PSS描述文件使用相同的声明方式。

extend action wb_dma_c::mem2mem_a {
exec body SV = """
wb_dma_dev_mem2mem({{devid}}, {{channel}}, {{dat_i.addr}},
{{dat_o.addr}}, {{dat_i.sz}}, {{trn_sz}});
""";
}

图十五 - 引用组件的devid变量

图十五则展示了在PSS exec组件中如何引用devid变量。

最小化数据交换

在使用PSS时,我们也通常会尽可能的降低PSS模型与测试实现代码之间的数据交换量。这种最佳实践方式也被推荐使用在其他程序语言当中,例如System Verilog和Java[3]。一般而言,PSS描述文件是在抽象层面上定义出数据操作方式,测试实现代码来实现细节。

比如说,一个涉及到DMA transfer的PSS动作(action)。在数据传输到一个内存地址之前,该内存地址需要先初始化。那么PSS描述文件不会执行这个初始化操作,则是在其代码中指定了要初始化的存储区域,并将如何初始化内存的详细信息指定给测试代码来执行。

action gendata_a {
input data_mem_b dat_i;
output data_ref_mem_b dat_o; constraint dat_o.addr == dat_i.addr;
constraint dat_o.sz == dat_i.sz;
}

图十六 - 初始化内存的Action定义示例

void pvm_gendata(uint32_t ref, uintptr_t addr, uint32_t sz) {
pvm_rand_t r;
void *addr_p = (void *)addr;
int i; pvm_rand_init(&r, ref); for (i=0; i<sz; i++) {
uint8_t v = pvm_rand_next(&r);
uex_iowrite8(v, addr_p+i);
}
}

图十七 - C测试代码进行内存初始化

上图的C代码通过gendata这个函数来实现了内存初始化。通过这种形式,PSS只需要定义抽象层面,所以其本身是一种描述性语言,保证了其高效性。

结论

便携式激励实现了多个方面的复用:验证层级的复用(垂直复用性),跨项目的复用实现(水平复用性),对于测试环境的复用。当然,我们也需要不同公司的不同需求来进行优先级的划分以保证PSS的优点得到有效的放大。以及对于公司内部现有工具的审核以保证前期资源投入不会被浪费,并继续投入资源研发符合公司产品的解决方案和PSS通用组件库。最后,通过为测试实现代码开发通用API来确保不同IP能够顺畅的在PSS复用环境下协同工作。

我们相信,上述建议确保您的组织能够最大化的利用PSS以提高自身的生产效率。

PSS--待看的更多相关文章

  1. 看完此文还不懂NB-IoT,你就过来掐死我吧...【转】

    转自:https://www.cnblogs.com/pangguoming/p/9755916.html 看完此文还不懂NB-IoT,你就过来掐死我吧....... 1 1G-2G-3G-4G-5G ...

  2. 看完此文还不懂NB-IoT,你就过来掐死我吧...

    看完此文还不懂NB-IoT,你就过来掐死我吧....... 1 1G-2G-3G-4G-5G 不解释,看图,看看NB-IoT在哪里? 2 NB-IoT标准化历程 3GPP NB-IoT的标准化始于20 ...

  3. 移动端测试===Android内存管理: 理解App的PSS

    Android内存管理: 理解App的PSS 原文链接:http://www.littleeye.co/blog/2013/06/11/android-memory-management-unders ...

  4. NB-IoT的介绍最终版 !看明白了吗?(转自 top-iot)

    标签: NB-IOT 1  1G-2G-3G-4G-5G 不解释,看图,看看NB-IoT在哪里? 2  NB-IoT标准化历程 3GPP NB-IoT的标准化始于2015年9月,于2016年7月R13 ...

  5. 从源码看Azkaban作业流下发过程

    上一篇零散地罗列了看源码时记录的一些类的信息,这篇完整介绍一个作业流在Azkaban中的执行过程,希望可以帮助刚刚接手Azkaban相关工作的开发.测试. 一.Azkaban简介 Azkaban作为开 ...

  6. 看完SQL Server 2014 Q/A答疑集锦:想不升级都难!

    看完SQL Server 2014 Q/A答疑集锦:想不升级都难! 转载自:http://mp.weixin.qq.com/s/5rZCgnMKmJqeC7hbe4CZ_g 本期嘉宾为微软技术中心技术 ...

  7. 一看就懂的ReactJs入门教程-精华版

    现在最热门的前端框架有AngularJS.React.Bootstrap等.自从接触了ReactJS,ReactJs的虚拟DOM(Virtual DOM)和组件化的开发深深的吸引了我,下面来跟我一起领 ...

  8. 透过WinDBG的视角看String

    摘要 : 最近在博客园里面看到有人在讨论 C# String的一些特性. 大部分情况下是从CODING的角度来讨论String. 本人觉得非常好奇, 在运行时态, String是如何与这些特性联系上的 ...

  9. DDD 领域驱动设计-看我如何应对业务需求变化,愚蠢的应对?

    写在前面 阅读目录: 具体业务场景 业务需求变化 "愚蠢"的应对 消息列表实现 消息详情页实现 消息发送.回复.销毁等实现 回到原点的一些思考 业务需求变化,领域模型变化了吗? 对 ...

  10. 在知乎上看到 Web Socket这篇文章讲得确实挺好,从头看到尾都非常形象生动,一口气看完,没有半点模糊,非常不错

    在知乎上看到这篇文章讲得确实挺好,从头看到尾都非常形象生动,一口气看完,没有半点模糊,非常不错,所以推荐给大家,非常值得一读. 作者:Ovear链接:https://www.zhihu.com/que ...

随机推荐

  1. Linux 文本相关命令(1)

    Linux 文本相关命令(1) 前言 最近线上环境(Windows Server)出现了一些问题,需要分析一下日志.感觉 Windows 下缺少了一些 Linux 系统中的小工具,像在这波操作中用到的 ...

  2. Sentry 监控 - Dashboards 事件数据可视化大屏

    系列 1 分钟快速使用 Docker 上手最新版 Sentry-CLI - 创建版本 快速使用 Docker 上手 Sentry-CLI - 30 秒上手 Source Maps Sentry For ...

  3. learn git(本地仓库)

    #本地 在Windows上安装Git 在Windows上使用Git,可以从Git官网直接https://git-scm.com/downloads下载,然后按默认选项安装即可. 装完成后,在开始菜单里 ...

  4. LINUX服务器带宽跑满、负载过高问题排查

    1.centos 安装流量监控iftop apt-get  install iftop -y 2.查看网卡名称 ifconfig 3.查看端口占用情况 iftop -i 网卡名称 -P 执行 nets ...

  5. git 操作 :从远程仓库gitLab上拉取指定分支到本地仓库;git如何利用分支进行多人开发 ;多人合作代码提交实践

    例如:将gitLab 上的dev分支拉取到本地 git checkout -b dev origin/dev 在本地创建分支dev并切换到该分支 git pull origin dev 就可以把git ...

  6. django setting.py配置文件解读-02

    定义项目目录常量 import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR ...

  7. PolarDB PostgreSQL Buffer Management 原理

    背景介绍 传统数据库的主备架构,主备有各自的存储,备节点回放WAL日志并读写自己的存储,主备节点在存储层没有耦合.PolarDB的实现是基于共享存储的一写多读架构,主备使用共享存储中的一份数据.读写节 ...

  8. YbtOJ-大收藏家【分层图,最大流】

    正题 题目链接:https://www.ybtoj.com.cn/contest/117/problem/2 题目大意 \(n\)个人,每人有\(a_i\)个属于自己的物品.\(m\)次交换依次进行, ...

  9. Docker入门系列之五:15个 Docker 命令

    在这篇文章中,我们将学习15个Dockers CLI命令.如果你还不了解Docker,请查看这个系列的其他部分进行学习,Docker概念,生态系统,Dockerfile,Docker镜像. Docke ...

  10. 从零搭建基于webpack的Electron-Vue3项目(1)——基于webpack的Vue3项目搭建

    从零搭建基于webpack的Electron-Vue3项目(1)--基于webpack的Vue3项目搭建 前言 本篇文章内容,主要是基于webpack的Vue3项目开发环境进行搭建,暂时还不涉及到El ...