【芯片前端】以vcs编译环境为例做一个适用于芯片前端的极简版Makefile实操教程

简介: 【芯片前端】以vcs编译环境为例做一个适用于芯片前端的极简版Makefile实操教程

前言

Makefile有一个经典教程,相信大部分人认识和学习Makefile都是通过这篇文章入手的:

跟我一起写 Makefile —— 陈皓

不过之前和朋友聊,觉得这篇文章很完美但是篇幅有点长,同时教程以c语言编译环境为基础讲解,实际上比我们常用的编译环境还是要复杂挺多的;

感觉我们用不了这么多但是不学有怪可惜的,那么不如做一个极简版硅农专用的Makefile教程,本文内除vcs相关的内容外,基本所有信息均可以在上文中查阅(因此有引用部分就不在单独标注),属于n手资料吧~~~

指令和变量

当前目录如果存在Makefile或makefile文件时,那么代表我们能在这一文件夹内通过make xxx来执行命令,比如我们常用的仿真指令:

make run tc=sanity seed=0 wave=on ccov=on

或者lint检查指令:

make full_lint

Makefile中命令的组织极为简单,标准的形式为:

1. target ... :prerequisites ...
2.     command ...

target为目标文件,prerequisites为依赖文件,command为执行命令,执行过程即在终端中的当前目录内键入 make target,则系统检查prerequisites内的文件是否有比target更新的文件,有的话则执行对应的command;当然了这个过程类似于一个链式反应,如何知道prerequisites内的文件是不是比target新呢,makefile会去找prerequisites的依赖文件或依赖项看一下prerequisites本身时候需要先更新,这样一级一级的找直到找到最底下的文件有修改,那么就处理整天线路;

而我们做编译仿真环境时最长使用的是伪目标的组织形式。在伪目标的组织形式下,prerequisites中的项相当于当前目标的前提条件,即要执行target那么要先执行prerequisites中的伪目标,之后再无脑执行command中设定好的命令,这样一来呢对于各种显式推导规则隐式推导规则啥的就没必要学的那么清了;

命令如同血液,变量如同骨骼,Makefile中变量定义与使用与sh语法基本一致,同时sh环境中的变量也可以在Makefile中直接使用:

1. 创建与赋值
2. export seed ?= random
3. 
4. 使用
5. SEED := $(seed)

Makefile中的变量近似于于systemverilog中的,在解释时候会在调用处进行原地展开为字符串,区别在于systemverilog中的宏是编译到了就直接展开,Makefile变量展开的时间点是在这个变量在宏观解释完成后进行的,而不是执行到这一句就展开了,比如下面这句:

1. x = aaa
2. y = $(x) bbb
3. x = ccc
4. 
5. test:
6.  @echo $(y)
7. 
8. x = ddd

执行结果为:

1. [xiaotu@xiaotu-eda ~/my_work/uvm_demo/sim]$make test
2. ddd bbb

变量大小写敏感,可以包含数字字符下划线,赋值时不用加$等号前后可以有空格(这个我喜欢),调用时需要加上$并且最好以()或{}作为分隔;

创建变量时加export是为了传递变量的值到下级Makefile中,不过目前我们常用的Makefile形式中一般通过include的方式来(至少目前在前端设计和验证人员使用和调整的维度来看)来展开多层次的Makefile,而不是层层传递的方式,因此export在我看来意义不是太大,当然加上肯定是没有问题的,关于加export的具体行为很多地方都有说明,因为感觉用不到所以不赘述了~

关于赋值方式,Makefile中有四种等号:= / ?= / := / +=,他们之间的差别也是很明显的:

  • =

b = $(a)的方式赋值,那么$(a)这个变量在全局任何位置被修改含义了,$(b)的值也会使用跟着修改,比如刚刚上面的例子,又或者下面这样写,其实对$(y)效果还是一样的:

1. x = aaa
2. y = $(x) bbb
3. x := ccc
4. 
5. test:
6.  @echo $(y)
7. 
8. x := ddd

这样导致的问题就是,你在前面写了一个赋值后,这个变量随时都有被后面“篡改”的可能性,除非你想清楚了就是要这样做哈,否则这种不确定性就搞得人很心慌,因此呢基本上我们避免这种赋值方式;

  • :=

这个赋值方式就非常主流非常友好了,表示把编译到这句代码为止时该变量的值展开在此处,比如下面这种写法:

1. x = aaa
2. y := $(x) bbb
3. x = ccc
4. 
5. test:
6.  @echo $(y)
7. 
8. x = ddd

执行结果:

1. [xiaotu@xiaotu-eda ~/my_work/uvm_demo/sim]$make test
2. aaa bbb
  • +=

在当前值的基础上继续加上后面的值,一般用来叠buff,比如vcs的指令那么多,总不能一口气写完吧,所以就分多次分多情况来写:

1. CMP_OPTIONS += -top $(TOP_MOD)
2. CMP_OPTIONS += -timescale=1ns/1ps -unit_timescale=1ns/1ps
3. CMP_OPTIONS += +vcs+initreg+random
4. #CMP_OPTIONS += -xprop=tmerge
5. 
6. ifeq ($(ccov), on)
7.  CMP_OPTIONS += -cm line+fsm+cond+tgl+assert+branch
8.  CMP_OPTIONS += -cm_cond allops+for+tf -cm_libs yv -cm_cond obs -cm_tgl portsonly -cm_glitch 0
9.  CMP_OPTIONS += -cm_dir $(SIM_PATH)/cov/simv.vdb
10. endif

当然了,+=本身具有=的性质,这以为着如果在其中使用了变量,那么要提防变量的值在后面被修改:

1. x = aaa
2. y = bbb
3. y += $(x)
4. x = ccc
5. 
6. test:
7.  @echo $(y)
8. 
9. x = ddd

执行结果:

1. [xiaotu@xiaotu-eda ~/my_work/uvm_demo/sim]$make test
2. bbb ddd

使用的时候心里时刻想着这个事就行了;

  • ?=

?=的含义是如果这个值没有在其他地方被赋值(注意不只是在之前),那么就使用?=的赋值结果,通常把要外部传参的那些信号习惯用?=来赋值:

1. export seed ?= random
2. export tc   ?= sanity_case
3. export wave ?= off
4. export ccov ?= off
5. export mode ?= sim_base

当然了,?=还是具有=的特性,如果在其他地方有赋值行为,那么?=即使在前面也无法生效:

1. x ?= aaa
2. 
3. test:
4.  @echo $(x)
5. 
6. x = ddd

执行结果:

1. [xiaotu@xiaotu-eda ~/my_work/uvm_demo/sim]$make test
2. ddd

因此呢,使用+=和?=的时候,一定要留心其中使用变量的行为;

实操

了解了伪指令和变量后,我们就可以开始通过实操来进一步学习Makefile了,那么不如就做一个make run的仿真执行指令吧;先来确定下Makefile是放在哪个位置呢,显然必须放在sim仿真目录下,但是有时我们需要多个sim目录,而Makefile文件都是一样的,那么我们在Makefile中使用include将文件直接建立在cfg目录就好了:

include ../cfg/cfg.mk

好,那么进入正题,分析下make run指令的行为实际就是编译 + 用例仿真,因此实际上这个指令我们可以拆解为两个更小粒度的指令make cmp + make ncrun:

1. .PHONY: cmp ncrun run verdi clean clean_all
2. run: cmp ncrun

OK,这种写法使用了prerequisites区域来设置了两个依赖项,由于cmp和ncrun是另外两个伪指令,因此这样写的含义就是:make run等价于先执行make cmp,再执行make ncrun,再后面就结束了,因为command项中;

.PHONY中罗列了所有的伪指令,一般来说伪指令都会加入到.PHONY中,不过不放在里面并不会影响伪指令的执行,但是否有其他场景或者其他情况有影响我不确认,因此保险起见所有的伪指令请都加入进去吧;

好的,那么run指令拆解为cmp+nurun了,下一步当然是分别组织这两个伪指令;

cmp

先来想一下,执行编译工作一共分几步?

  1. clean一下当前的目录,把之前那些没用的中间文件啊删一删;
  2. 建立一下编译目录,主要是建立log和exec目录,当然了如果wave=on的时候呢把wave目录也要建立一下,如果ccov=on的时候呢要把cov目录建立一下;
  3. 启动vcs通过命令行来进行文件编译;

那么,我们的cmp指令的大概形式就出来了,注意command如果不和target一行的话,前面一定要用tab键缩进

1. cmp: clean
2.  @$(PRE_PROC)
3.  @vcs $(CMP_OPTIONS)

在这种组织方式下呢,clean作为一个指令是cmp的前提指令,他的形式其实是很简单的:

1. export SIM_PATH  := ./$(mode) #这里不用要,就是给仿真子目录根据mode起个名字
2. 
3. clean:
4.  @-rm -rf $(SIM_PATH)/exec ucli.key csrc vc_hdrs.h novas.conf  novas_dump.log  novas.rc verdiLog

可以看到clean的作用就是删除各种中间文件以及exec文件夹,这里的rm加了一个-rm,作用是如果遇到了没有执行成功的时候,不要停继续执行下去,在这里还看不清楚,下面可能会看的更明白;rm前面还加了一个@符号,原因是makefile会将其执行的命令行在执行前输出到屏幕上,@做前缀可以使这个命令不在屏幕上打印出来

完成删除操作后呢,下面就是建立各种文件夹的$(PRE_PROC)操作了,这个操作的初始组织形式如下:

1. export PRE_PROC:= mkdir $(SIM_PATH)/log $(SIM_PATH)/exec 
2. ifeq ($(wave), on)
3.  PRE_PROC += $(SIM_PATH)/wave 
4. endif
5. ifeq ($(ccov), on)
6.  PRE_PROC += $(SIM_PATH)/cov
7. endif

然后执行的时候就会发现这个问题:

1. [xiaotu@xiaotu-eda ~/my_work/uvm_demo/sim]$make cmp
2. mkdir: cannot create directory `./sim_base/log': File exists
3. make: *** [cmp] Error 1
4.

什么意思呢?我们在clean的时候不是没有把log目录删除嘛(本来就不应该删除,不能跑一次仿真就把之前case的log都干掉吧),然后这里执行的时候mkdir文件夹就报了一个无伤大雅的error:log目录已存在。那么要如何解决这个问题呢?

一种方法是使用mkdir -p xxx,这样mkdir本身就会忽略已经存在的目录;

另一种方法就是使用-mkdir xxx,在makefile中主动忽略这个linux执行过程中的报错;

用哪个都行所以最后我就用的-p的方式来做的;

然后下一个知识点,ifeq($(ccov), on)这句看着就是条件判断,makefile里还是提供了几种条件判断语句如ifeq-else-endif,ifneq,ifdef这几个了,当然了还有其他一些函数啥的,可以在开头的教程中看看,这里不做。于是乎呢$(PRE_PROC)呢就被组织完成,只要在cmp中直接调用既可以;

最后一步,组织vcs $(CMP_OPTIONS),也就是vcs真正的编译命令,这步的难点不在于makefile中,在于你对vcs命令行的熟悉程度,因此看一下就可以了,哦对了顺便说一句makefile中使用#来做注释,使用\来换行,同时可以直接使用环境变量

1. SIM_LOG   ?= $(SIM_PATH)/log/sim.log
2. RUN_LOG   ?= $(SIM_PATH)/log/$(tc)_$(SEED).log
3. RUN_WAVE  ?= $(SIM_PATH)/wave/$(tc)_$(SEED).fsdb
4. EXEC_SIMV ?= $(SIM_PATH)/exec/simv
5. RUN_COV   ?= $(tc)_$(SEED)
6. FILELIST  ?= ../cfg/tb.f
7. TOP_MOD   ?= harness
8. 
9. VERDI_P := $(NOVAS_HOME)/share/PLI/VCS/LINUX64/verdi.tab \
10.             $(NOVAS_HOME)/share/PLI/VCS/LINUX64/pli.a
11. 
12. ##############################################################
13. ##
14. ##vcs cmp command
15. ##
16. ##############################################################
17. 
18. export CMP_OPTIONS :=
19. CMP_OPTIONS += -f $(FILELIST) -P $(VERDI_P) -l $(SIM_LOG) -o $(EXEC_SIMV)
20. CMP_OPTIONS += +libext+.sv+.v +indir+/home/xiaotu/my_work/code_lib
21. CMP_OPTIONS += +v2k +define+RTL_SAIF +notimingcheck +nospecify +vpi +memcbk +vcsd +plusarg_save +nospecify +udpsched
22. CMP_OPTIONS += +vcs+lic+wait
23. CMP_OPTIONS += -sverilog -full64 -sverilog -debug_all -ntb_opts uvm-1.2
24. CMP_OPTIONS += -sv_pragma -lca -kdb
25. CMP_OPTIONS += -top $(TOP_MOD)
26. CMP_OPTIONS += -timescale=1ns/1ps -unit_timescale=1ns/1ps
27. CMP_OPTIONS += +vcs+initreg+random
28. #CMP_OPTIONS += -xprop=tmerge
29. 
30. ifeq ($(ccov), on)
31.   CMP_OPTIONS += -cm line+fsm+cond+tgl+assert+branch
32.   CMP_OPTIONS += -cm_cond allops+for+tf -cm_libs yv -cm_cond obs -cm_tgl portsonly -cm_glitch 0
33.   CMP_OPTIONS += -cm_dir $(SIM_PATH)/cov/simv.vdb
34. endif

其实可以看得出来,组织makefile的过程就是把工具命令行搬到makefile文件中来,伴随着if else把工具命令行往上堆,你如果不愿意做makefile其实做个脚本一样能实现同样的功能的,只不过make提供了更标准更便捷的途径;

ncrun

ncrun顾名思义就是no-cmp run,不用编译直接仿真了,然后在不知道这个nc是啥意思的时候我一直称之为“脑残run”,反正就啥也不要管楞仿吧~那么在ncrun里我们做了几件事呢:

1. ncrun:
2.  @$(EXEC_SIMV) $(RUN_OPTIONS)
3.  @$(WORK_ENV)/check_fail.pl $(RUN_LOG)
4.  @echo "[Note] report log path: $(RUN_LOG)"
  1. 用刚刚编译好的exec执行文件来执行用例仿真;
  2. 检查仿真的log,有没有设定好的关键字,如果有则反馈error/warning等信息在屏幕上;
  3. 最后打印log的路径;

对于第一步$(EXEC_SIMV)刚刚已经定义过了:

EXEC_SIMV ?= $(SIM_PATH)/exec/simv

只需要看下$(RUN_OPTIONS),那么先看下我们有意通过命令行来配置的几个量:

1. ##############################################################
2. ##
3. ##user cfg
4. ##
5. ##############################################################
6. 
7. export seed ?= random
8. export tc   ?= sanity_case
9. export wave ?= off
10. export ccov ?= off
11. export mode ?= sim_base

这几个配置是在键入make ncrun时可以通过外部修改的,因此在这里我使用了?=来赋默认值,其实呢这里不用?=用:=也是可以的,因为命令行输入是有最高优先级的:

1. x ?= aaa
2. 
3. test:
4.  @echo $(x)
5. 
6. x := ddd

测试结果:

1. [xiaotu@xiaotu-eda ~/my_work/uvm_demo/sim]$make test x=aaaaaaaaaaaaaaaaaaaaaaaaaaa
2. aaaaaaaaaaaaaaaaaaaaaaaaaaa

好的,在有了上面的配置之后呢,我们就可以组织$(RUN_OPTIONS)了,核心工作还是vcs的命令行添加:

1. ##############################################################
2. ##
3. ##vcs run command
4. ##
5. ##############################################################
6. 
7. export RUN_OPTIONS :=
8. RUN_OPTIONS += +ntb_random_seed=$(SEED) +tc_name=$(tc) -l $(RUN_LOG)
9. RUN_OPTIONS += -assert nopostproc
10. RUN_OPTIONS += +vcs+lic+wait
11. RUN_OPTIONS += +vcs+initreg+$(SEED)
12. RUN_OPTIONS += +UVM_TESTNAME=$(tc)
13. ifeq ($(wave), on)
14.   RUN_OPTIONS += +fsdbfile+$(RUN_WAVE) -ucli -do ../cfg/run.do
15. endif
16. ifeq ($(ccov), on)
17.   RUN_OPTIONS += -cm line+cond+tgl+fsm+branch+assert
18.   RUN_OPTIONS += -cm_dir $(SIM_PATH)/cov/simv.vdb -cm_name $(RUN_COV)
19. endif

还有这里面用了一个run.do文件,这个文件是用来产生波形的,附在文章的最后;执行完这一步实际就已经把仿真工作完成了,后面两步只不是就是调用脚本检查仿真中的error关键字以及打印log,没有必要赘述;

好的,那么到这里一个简单的仿真编译环境就完成了;

最有一个要提的小点,在sim目录下直接键入make,会执行Makefile中设定的第一个指令,比如我的设置中就会执行cmp指令,因此千万不要不clean_all命令放在第一个位置,否则哭都来不及了。

附:

完整的Makefile:

1. ##############################################################
2. ##
3. ##user cfg
4. ##
5. ##############################################################
6. 
7. export seed ?= random
8. export tc   ?= sanity_case
9. export wave ?= off
10. export ccov ?= off
11. export mode ?= sim_base
12. 
13. ##############################################################
14. ##
15. ##env path
16. ##
17. ##############################################################
18. export SIM_PATH  := ./$(mode)
19. 
20. ifeq ($(seed), random)
21.   SEED := $(shell python -c "from random import randint; print randint(0,99999999)")
22.   #SEED := $(shell perl -e "print int(rand(100000000))")
23. else
24.   SEED := $(seed)
25. endif
26. 
27. export PRE_PROC:= mkdir -p $(SIM_PATH)/log $(SIM_PATH)/exec 
28. #export PRE_PROC:= -mkdir $(SIM_PATH)/log $(SIM_PATH)/exec 
29. ifeq ($(wave), on)
30.   PRE_PROC += $(SIM_PATH)/wave 
31. endif
32. ifeq ($(ccov), on)
33.   PRE_PROC += $(SIM_PATH)/cov
34. endif
35. 
36. SIM_LOG   ?= $(SIM_PATH)/log/sim.log
37. RUN_LOG   ?= $(SIM_PATH)/log/$(tc)_$(SEED).log
38. RUN_WAVE  ?= $(SIM_PATH)/wave/$(tc)_$(SEED).fsdb
39. EXEC_SIMV ?= $(SIM_PATH)/exec/simv
40. RUN_COV   ?= $(tc)_$(SEED)
41. FILELIST  ?= ../cfg/tb.f
42. TOP_MOD   ?= harness
43. 
44. VERDI_P := $(NOVAS_HOME)/share/PLI/VCS/LINUX64/verdi.tab \
45.             $(NOVAS_HOME)/share/PLI/VCS/LINUX64/pli.a
46. 
47. ##############################################################
48. ##
49. ##vcs cmp command
50. ##
51. ##############################################################
52. 
53. export CMP_OPTIONS :=
54. CMP_OPTIONS += -f $(FILELIST) -P $(VERDI_P) -l $(SIM_LOG) -o $(EXEC_SIMV)
55. CMP_OPTIONS += +libext+.sv+.v +indir+/home/xiaotu/my_work/code_lib
56. CMP_OPTIONS += +v2k +define+RTL_SAIF +notimingcheck +nospecify +vpi +memcbk +vcsd +plusarg_save +nospecify +udpsched
57. CMP_OPTIONS += +vcs+lic+wait
58. CMP_OPTIONS += -sverilog -full64 -sverilog -debug_all -ntb_opts uvm-1.2
59. CMP_OPTIONS += -sv_pragma -lca -kdb
60. CMP_OPTIONS += -top $(TOP_MOD)
61. CMP_OPTIONS += -timescale=1ns/1ps -unit_timescale=1ns/1ps
62. CMP_OPTIONS += +vcs+initreg+random
63. #CMP_OPTIONS += -xprop=tmerge
64. 
65. ifeq ($(ccov), on)
66.   CMP_OPTIONS += -cm line+fsm+cond+tgl+assert+branch
67.   CMP_OPTIONS += -cm_cond allops+for+tf -cm_libs yv -cm_cond obs -cm_tgl portsonly -cm_glitch 0
68.   CMP_OPTIONS += -cm_dir $(SIM_PATH)/cov/simv.vdb
69. endif
70. 
71. ##############################################################
72. ##
73. ##vcs run command
74. ##
75. ##############################################################
76. 
77. export RUN_OPTIONS :=
78. RUN_OPTIONS += +ntb_random_seed=$(SEED) +tc_name=$(tc) -l $(RUN_LOG)
79. RUN_OPTIONS += -assert nopostproc
80. RUN_OPTIONS += +vcs+lic+wait
81. RUN_OPTIONS += +vcs+initreg+$(SEED)
82. RUN_OPTIONS += +UVM_TESTNAME=$(tc)
83. ifeq ($(wave), on)
84.   RUN_OPTIONS += +fsdbfile+$(RUN_WAVE) -ucli -do ../cfg/run.do
85. endif
86. ifeq ($(ccov), on)
87.   RUN_OPTIONS += -cm line+cond+tgl+fsm+branch+assert
88.   RUN_OPTIONS += -cm_dir $(SIM_PATH)/cov/simv.vdb -cm_name $(RUN_COV)
89. endif
90. 
91. ##############################################################
92. ##
93. ## PHONY order
94. ##
95. ##############################################################
96. .PHONY: cmp ncrun run verdi clean clean_all
97. 
98. cmp: clean
99.   @$(PRE_PROC)
100.  @vcs $(CMP_OPTIONS)
101. 
102. ncrun:
103.  @$(EXEC_SIMV) $(RUN_OPTIONS)
104.  @$(WORK_ENV)/check_fail.pl $(RUN_LOG)
105.  @echo "[Note] report log path: $(RUN_LOG)"
106. 
107. run: cmp ncrun
108. 
109. verdi:
110.  @verdi -simflow -dbdir $(SIM_PATH)/exec/simv.daidir
111. 
112. clean:
113.  @-rm -rf $(SIM_PATH)/exec ucli.key csrc vc_hdrs.h novas.conf  novas_dump.log  novas.rc verdiLog
114. 
115. clean_all: clean
116.  @rm -rf $(SIM_PATH)

run.do文件:

1. call \$fsdbDumpvars 0 "harness"
2. 
3. run

check_fail.pl文件:

1. #!/usr/bin/perl -w
2. my $pass = 1;
3. while(<ARGV>){
4.     if($_ =~ /error|Error|failed|Failed|UVM_FATAL|UVM_ERROR/){
5.         $pass = 0;
6.         last;
7.     }
8.     if($_ =~ /Report counts by severity/){
9.         last;
10.     }
11. }
12. if($pass == 1){
13.     print "\033[42;37m SIMULATION PASS! \033[0m \n";
14.     print_pass();
15. } else {
16.     print "\033[41;37m SIMULATION FAIL! \033[0m \n";
17.     print_fail();
18. }
19. 
20. sub print_pass{
21.     print "\n";
22.     print "#############           #              ##############       ##############";       print "\n";
23.     print "#############          ###             ##############       ##############";       print "\n";
24.     print "##         ##         # # #            ##                   ##";                   print "\n";
25.     print "##         ##        ##   ##           ##                   ##";                   print "\n";
26.     print "##         ##       ##      ##         ##                   ##";                   print "\n";
27.     print "##         ##      ##        ##        ##                   ##";                   print "\n";
28.     print "#############     ##############       ##############       ##############";       print "\n";
29.     print "#############     ##############       ##############       ##############";       print "\n";
30.     print "##                ##          ##                   ##                   ##";       print "\n";
31.     print "##                ##          ##                   ##                   ##";       print "\n";
32.     print "##                ##          ##                   ##                   ##";       print "\n";
33.     print "##                ##          ##                   ##                   ##";       print "\n";
34.     print "##                ##          ##       ##############       ##############";       print "\n";
35.     print "##                ##          ##       ##############       ##############";       print "\n";
36.     print "\n";
37. }
38. 
39. sub print_fail{
40.     print "\n";
41.     print "#############           #              ##############       ##";                   print "\n";
42.     print "#############          ###             ##############       ##";                   print "\n";
43.     print "##                    # # #                  ##             ##";                   print "\n";
44.     print "##                   ##   ##                 ##             ##";                   print "\n";
45.     print "##                  ##      ##               ##             ##";                   print "\n";
46.     print "##                 ##        ##              ##             ##";                   print "\n";
47.     print "#############     ##############             ##             ##";                   print "\n";
48.     print "#############     ##############             ##             ##";                   print "\n";
49.     print "##                ##          ##             ##             ##";                   print "\n";
50.     print "##                ##          ##             ##             ##";                   print "\n";
51.     print "##                ##          ##             ##             ##";                   print "\n";
52.     print "##                ##          ##             ##             ##";                   print "\n";
53.     print "##                ##          ##       ##############       ##############";       print "\n";
54.     print "##                ##          ##       ##############       ##############";       print "\n";
55.     print "\n";
56. }


相关文章
|
3月前
|
缓存 前端开发 JavaScript
前端开发的必修课:如何让你的网页在弱网环境下依然流畅运行?
【10月更文挑战第30天】随着移动互联网的普及,弱网环境下的网页性能优化变得尤为重要。本文详细介绍了如何通过了解网络状况、优化资源加载、减少HTTP请求、调整弱网参数和代码优化等方法,提升网页在弱网环境下的加载速度和流畅性,从而改善用户体验。
196 4
|
1月前
|
Dart 前端开发 架构师
【01】vs-code如何配置flutter环境-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈-供大大的学习提升
【01】vs-code如何配置flutter环境-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈-供大大的学习提升
112 26
|
6月前
|
JSON 前端开发 开发工具
初探在WSL中设置vim前端开发环境
初探在WSL中设置vim前端开发环境
|
3月前
|
机器学习/深度学习 自然语言处理 前端开发
前端神经网络入门:Brain.js - 详细介绍和对比不同的实现 - CNN、RNN、DNN、FFNN -无需准备环境打开浏览器即可测试运行-支持WebGPU加速
本文介绍了如何使用 JavaScript 神经网络库 **Brain.js** 实现不同类型的神经网络,包括前馈神经网络(FFNN)、深度神经网络(DNN)和循环神经网络(RNN)。通过简单的示例和代码,帮助前端开发者快速入门并理解神经网络的基本概念。文章还对比了各类神经网络的特点和适用场景,并简要介绍了卷积神经网络(CNN)的替代方案。
597 1
|
4月前
|
前端开发 JavaScript
轻松上手:基于single-spa构建qiankun微前端项目完整教程
轻松上手:基于single-spa构建qiankun微前端项目完整教程
115 0
|
6月前
|
前端开发 Java Maven
【前端学java】全网最详细的maven安装与IDEA集成教程!
【8月更文挑战第12天】全网最详细的maven安装与IDEA集成教程!
138 2
【前端学java】全网最详细的maven安装与IDEA集成教程!
|
6月前
|
资源调度 前端开发 JavaScript
前端 nodejs 命令行自动调用编译 inno setup 的.iss文件
前端 nodejs 命令行自动调用编译 inno setup 的.iss文件
|
6月前
|
iOS开发 Android开发 MacOS
从零到全能开发者:解锁Uno Platform,一键跨越多平台应用开发的神奇之旅,让你的代码飞遍Windows、iOS、Android、macOS及Web,技术小白也能秒变跨平台大神!
【8月更文挑战第31天】从零开始,踏上使用Uno Platform开发跨平台应用的旅程。只需编写一次代码,即可轻松部署到Windows、iOS、macOS、Android及Web(通过WASM)等多个平台。Uno Platform为.NET生态带来前所未有的灵活性和效率,简化跨平台开发。首先确保安装了Visual Studio或VS Code及.NET SDK,然后选择合适的项目模板创建新项目。项目结构类似传统.NET MAUI或WPF项目,包含核心NuGet包。通过简单的按钮示例,你可以快速上手并构建应用。Uno Platform让你的技术探索之旅充满无限可能。
160 0
|
6月前
|
前端开发 JavaScript
【Azure 环境】前端Web通过Azure AD获取Token时发生跨域问题(CORS Error)
【Azure 环境】前端Web通过Azure AD获取Token时发生跨域问题(CORS Error)
|
6月前
|
前端开发 芯片
用于生物电测量的低功耗八通道模拟前端芯片 Low-Power, 8-Channel AFE for Biopotential Measurement
此低功耗八通道模拟前端芯片专为生物电测量设计,集成了八个低噪声放大器与24位高精度ADC,支持125SPS至8kSPS数据速率及多种增益设置。芯片配备内置时钟、参考电压源与断线检测等功能,并兼容多种电极类型。适用于心电图、肌电图和个人健康监测设备,采用VQFN与TQFP封装,尺寸紧凑,确保医疗设备兼具性能与便携性。

热门文章

最新文章

  • 1
    【Java若依框架】RuoYi-Vue的前端和后端配置步骤和启动步骤
  • 2
    【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 3
    【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
  • 4
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 5
    详解智能编码在前端研发的创新应用
  • 6
    巧用通义灵码,提升前端研发效率
  • 7
    【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
  • 8
    智能编码在前端研发的创新应用
  • 9
    【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
  • 10
    抛弃node和vscode,如何用记事本开发出一个完整的vue前端项目