热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

计算机组成原理,硬件模拟语言,Vivado仿真

工具链-ChiselGTKWave本次综合实验中我采用了UCBerkeley的Chisel3作为硬件描述的工具。Chisel相比Verilog,有如下优势

工具链 - Chisel + GTKWave

本次综合实验中我采用了 UC Berkeley 的 Chisel3 作为硬件描述的工具。

Chisel 相比 Verilog,有如下优势:


  1. Verilog 在设计之初只是硬件模拟语言,其中很多语法不适合硬件综合;Chisel 对此有仔细区分
  2. Verilog 的一些语法概念在如何映射到硬件实现方面是非常不直观的,或一不小心就会导致非常低效的电路结构(比如锁存器)
  3. Verilog 缺少现代编程语言的机制,诸如 OOP/FP,类型推导和反射
  4. Verilog 缺乏规模化构建的机制,比如「成批链接」I/O 端口的语法和好用的 function 机制
  5. Verilog 的可扩展性大大弱于 Scala

Chisel 是基于 Scala 语言设计构建的。 Scala 是一个以 Java 为基础的「Scalable Language」,在执行前会被翻译为 Class 文件,最终在 JVM 中运行。

Chisel 经过 Firrtl 中间层后,会被翻译为 Verilog 代码,然后再用 Vivado 导入综合/实现/烧写。


配置工具链

主要参考 https://github.com/ucb-bar/chisel-tutorial.git。

在 ArchLinux 下只需要 pacman -Sy sbt scala 安装 sbt 构建工具和 scala; Chisel 的相关代码会在第一次使用引用 Chisel 的 sbt 项目时由 sbt 经由 Maven 构建系统下载。下载可能需要网络加速服务,或切换镜像(比较困难,因为硬写在了 sbtjar 包中了),因为 Maven 主镜像非常缓慢。


从模板开始

为了方便大家熟悉 Chisel,Chisel 项目准备了 chisel-template 项目。用 git clone https://github.com/freechipsproject/chisel-template 即可 clone 到本地。
http://www.biyezuopin.vip
目录结构大致如下所示:

.
├── build.sbt # sbt 构建文件
├── project # sbt 生成的文件,无需关心
│ ├── build.properties
│ └── plugins.sbt
├── README.md # template 的介绍
├── scalastyle-config.xml
├── scalastyle-test-config.xml
└── src # 源码目录├── main # 一般用来放 Design Sources│ └── scala│ └── gcd│ └── GCD.scala└── test # 一般用来放 Testbench└── scala└── gcd├── GCDMain.scala└── GCDUnitTest.scala8 directories, 9 files

这个目录结构和 sbt 的构建有关系,更详细的介绍请参见 sbt 的手册,大概是 src 下面每一个目录都是一个子的 sbt 构建单位(项目),可以写独立的 build.sbt 的那种。


从哪里获得帮助

我主要用到了四个方面的帮助


  1. 关于 Scala 的:参考 Spec,谷歌和 Scala 自己的 doc
  2. 关于 Chisel 的:
    1. 一般的用法直接参见 https://github.com/freechipsproject/chisel3.wiki 和 https://github.com/ucb-bar/chisel-tutorial
    2. 特别的用法要去看源码(在 https://github.com/freechipsproject/chisel3/)和 API 文档(在 https://chisel.eecs.berkeley.edu/api/3.0.0/chisel3/ )
      1. 比如说 ListLookup/BitPad 和某些 deprecated 的用法(比如支持 BitPad 的类似 MuxCase 的调用 MuxLookup)
  3. 关于 sbt 的:参考 sbt 的文档即可
  4. 代码风格和复用的设计思路:RISCV Mini 项目( freechipsproject @ Github)

调试

一般采用 Chisel 的 PeekPokeTester 的 poke(置数)和 step(前进 n 个时钟周期),在运行的时候加上 --generate-vcd-output on 选项,然后用 GTKWave 打开生成的 vcd 文件。此法调试和 Vivado 体验近似。


[NEW!] 我的源码结构

.
├── ALU.anno.json
├── ALU.fir
├── ALU.v
├── BlackBoxCGROM.v
├── black_box_verilog_files.f
├── build.sbt
├── CPU.anno.json
├── CPU.fir
├── CPU.v
├── DDU.anno.json
├── DDU.fir
├── DDU.v
├── project
├── scalastyle-config.xml
├── scalastyle-test-config.xml
├── src
│ ├── main
│ │ └── scala # 除新加入的 .scala,剩余基本为原 CPU 的代码
│ │ ├── ALU.scala
│ │ ├── Control.scala
│ │ ├── CPU.scala # 加入了几条新指令
│ │ ├── Datapath.scala # 加入了几条新指令
│ │ ├── DDU.scala
│ │ ├── Instr.scala
│ │ ├── Mem.scala
│ │ ├── MMap.scala # 进行程序存储器,UART Cell 和 Textmode 显存的内存地址转换
│ │ ├── RegFile.scala
│ │ ├── TestField.scala
│ │ └── VGA # VGA 相关代码
│ │ ├── Counter.scala # VGACounter 类,用来便捷构建 reset + carry 的计数器
│ │ ├── VGACore.scala # VGA 核心模块
│ │ └── VGA.scala # 字模 ROM <&#61;&#61;> VGACore <&#61;&#61;> 显存
│ └── test
│ ├── resources # 资源文件
│ │ ├── BlackBoxCGROM.1.v # 使用 Block RAM IP 核的 CGROM Blackbox 模型
│ │ ├── BlackBoxCGROM.v # 使用 Dist RAM IP 核的 CGROM Blackbox 模型
│ │ ├── build.sh # 根据 util.c 构建内存 COE&#xff08;仅有效载荷部分&#xff09;的构建脚本
│ │ ├── inst_rom.coe # Deprecated
│ │ ├── inst_rom.S # Deprecated
│ │ ├── linker.ld # *链接器脚本
│ │ ├── main.asm # Deprecated&#xff0c;下同
│ │ ├── main_prog.asm
│ │ ├── main_prog.txt
│ │ ├── main.S
│ │ ├── mips1.asm
│ │ ├── NOTE.md
│ │ ├── start.o
│ │ ├── start.S # 用来设置栈地址的汇编代码&#xff0c;必须保证紧跟 main_loop# &#xff08;如果把 main_loop 写在 util.c 的最前面&#xff0c;一般是可以的&#xff09;# 尽管这样&#xff0c;还是应该想办法把这里的行为改进。# &#xff08;主要的困难&#xff1a;在这里添加 j main_loop 会生成额外的 .pic 段&#xff0c;为什么&#xff1f;&#xff09;
│ │ ├── test_jal_jr.asm # 用来测试 jal 和 jr 指令是否工作正常的汇编代码
│ │ ├── test_jal_jr.txt
│ │ ├── util.1.c
│ │ ├── util.c # util.c&#xff0c;CPU 运行的主程序
│ │ ├── util.c.S
│ │ ├── util.elf
│ │ ├── util_final.bin # 进行 objcopy&#xff0c;剔除所有符号后的 util_final.elf
│ │ ├── util_final.elf # 和 start.S 一同编译链接后的 util.c&#xff0c;详情参见 build.sh
│ │ ├── util_final.elf.objdump.d # 反汇编后的 util_final.elf
│ │ ├── util.o
│ │ └── xxd_c4_util_final_bin.txt # 利用 xxd 输出二进制后的 util_final.bin
│ └── scala
│ ├── ALU.scala
│ ├── CPU.scala
│ ├── DDU.scala
│ ├── RegFile.scala
│ ├── TestField.scala
│ └── VGA.scala # VGA Testbench
├── target
└── test_run_dir

构建命令

http://www.biyezuopin.vip
例&#xff1a;在与 build.sbt 同层的目录下运行 sbt 后&#xff1a;


  • test:runMain ddu.DDUGen 用于生成 DDU 的 Verilog 代码
  • test:runMain ddu.DDUTest --generate-vcd-output on 用于运行 DDU &#43; CPU Testbench 并生成 VCD 波形文件&#xff0c;可以用 GTKWave 打开
  • test:runMain multicpu.CPUTest --generate-vcd-output on 运行 CPU Testbench
  • test:run 可以查看所有可以运行的 main classes

下面为 test:run 的示例&#xff1a;

sbt:MultiCycleCpu> test:run
[warn] Multiple main classes detected. Run &#39;show discoveredMainClasses&#39; to see the listMultiple main classes detected, select one to run:[1] ddu.DDUGen[2] ddu.DDUTest[3] gcd.GCDMain[4] gcd.GCDRepl[5] multicpu.ALUGen[6] multicpu.ALUTest[7] multicpu.CPUGen[8] multicpu.CPUTest[9] multicpu.RegFileGen[10] multicpu.RegFileTest[11] testfield.TestFieldGen[12] testfield.TestFieldTestEnter number:

构建生成的内容一般在 test_run_dir&#xff0c;ALUGen/DDUGen 等生成的一般在项目主目录下。


Chisel 的问题


  1. 不支持内存初值
    • 因为 Chisel 的硬件描述完全面向综合&#xff0c;而可以赋初值的内存本质上是个高级功能
  2. 对 IP 核只能用 Verilog Blackbox 封装起来&#xff0c;到 Vivado Simulator 仿真
    • 不是个大问题&#xff0c;但是还是诸多不便&#xff08;如果必须用 IP 核的话&#xff09;
  3. Chisel 的 poke&#xff08;置数&#xff09;操作总是慢半个时钟周期&#xff0c;以及 iotester 执行效率和 Vivado Simulator 一样感人
    • 没找到解决方法&#xff0c;并且 RISCV-Mini 项目为了解决内存模拟的问题用的 Verilator C&#43;&#43; 写的模拟插件

Vivado 项目和 top.v

DDU 缺乏时钟分频&#xff0c;所以在 Vivado 中写一个 top.v 用来实例化 Clocking Wizard IP 核&#xff0c;同时把 xdc 处理好。


MIPS C &#43; 汇编混合编程

利用 C 语言可以极大的简化计算地址和想指令的苦恼。

本次设计采用 mipsel-linux-gnu-gcc 进行编译。在 Baremetal 环境编译需要注意以下事项&#xff1a;


  • 开启 -nostdlib -ffreestanding -static 选项
  • 因为我没有实现流水线&#xff0c;所以 MIPS 的分支延迟指令会有问题
    • 利用 -fno-delayed-branch -Wa,-O0 来禁用之&#xff08;参见 这篇 StackOverflow&#xff09;
  • 在链接的时候要用自定义的链接脚本&#xff08;linker.ld&#xff09;&#xff0c;并且在链接后用 objcopy 把符号裁掉
    • 这部分详见代码和 build.sh

逻辑设计

扩展的指令&#xff1a;


  • JALJR - 用来实现 C 的过程调用和返回
  • LUI - C 编译器经常使用 LUI 和 LW/SW 指令用来实现装入/写出
  • ADDIU SLTIU 等带 U 的指令 - C 编译器经常使用此版本&#xff0c;避免异常&#xff08;虽然我也没实现&#xff09;
  • SLL - C 编译器利用移位和加法来进行乘以常数的运算


拓扑关系&#xff1a;

UART RX 通过内存映射 IO 的形式&#xff0c;连接到 CPU&#xff1b;CPU 轮询 UART Cell 地址&#xff08;0xFF00&#xff09;&#xff0c;检测到数据有效后&#xff0c;取出数据并且写入显存&#xff08;0x3000~0x4F40&#xff09;&#xff1b;显存和 CGROM&#xff08;Character Generation ROM&#xff09;一起来显示对应字母。




  1. UART Rx 通过检测下降沿开始移位&#xff0c;在 CLK_PER_BITS/2 时间后检测是否仍为低电平&#xff08;此时应该是 Start Bit 的一半&#xff09;

  2. 如果仍为低电平&#xff0c;则开始接收&#xff0c;否则认为是错误数据&#xff08;毛刺等&#xff09;&#xff0c;放弃&#xff0c;否则进入 &#xff08;3&#xff09;

  3. 每隔 CLK_PER_BITS 采样&#xff0c;共八次&#xff0c;最后再等待 CLK_PER_BITS/2 后进入等待 CPU 取走的状态

    • 在此状态&#xff0c;Valid 为高&#xff1b;不停检测&#xff0c;如果 Ready 为高&#xff0c;则进入可以接收下一个字节的模式&#xff08;1&#xff09;

Memory Cell 如下&#xff1a;

// &#43;----------------------------------------------&#43;
// | 31 | 30 | 29 ... 8 | 7 ... 0 |
// | READY | VALID | don&#39;t care | Serial data |
// &#43;----------------------------------------------&#43;

核心代码&#xff08;相对于 Lab5 的增加&#xff09;


C 主程序 - util.c

int cursor_h &#61; 0;
int cursor_v &#61; 0;void putchar(int ch);
int getchar();
void clear_scr();
void backspace();void main_loop() {cursor_h &#61; 0;cursor_v &#61; 0;int ch;//clear_scr();putchar(&#39;>&#39; | 0xF00);for (;;) {// putchar(&#39;_&#39; | 0x700);ch &#61; getchar();if (ch &#61;&#61; &#39;\n&#39; || ch &#61;&#61; &#39;\r&#39;) {cursor_h &#61; 0;cursor_v &#43;&#61; 1;} else if (ch &#61;&#61; 8) {// Backspaceif (cursor_h &#61;&#61; 0) {cursor_h &#61; 24;cursor_v -&#61; 1;putchar(&#39; &#39; | 0xF00);cursor_h &#61; 24;cursor_v -&#61; 1;} else {cursor_h -&#61; 1;putchar(&#39; &#39; | 0xF00);cursor_h -&#61; 1;}} else if (ch &#61;&#61; 27) {for (int i &#61; 0; i < 4000; i&#43;&#43;) {putchar(&#39; &#39; | 0xF00);}cursor_h &#61; 0;cursor_v &#61; 0;} else {putchar(ch | 0xF00);}}
}void putchar(int ch) {volatile int *vram_offset &#61; 0x3000;vram_offset[cursor_v * 80 &#43; cursor_h] &#61; ch;cursor_h&#43;&#43;;if (cursor_h &#61;&#61; 80) {cursor_h &#61; 0;cursor_v&#43;&#43;;if (cursor_v &#61;&#61; 25) {cursor_v &#61; 0;}}
}int getchar() {// uart offsetvolatile int *uart_offset &#61; 0xFF00;int valid_mask &#61; 0x40000000;int byte_mask &#61; 0x000000ff;while ((*uart_offset & valid_mask) &#61;&#61; 0) {// Ready; do nothing//__asm__("nop");}int ret &#61; byte_mask & *uart_offset;*uart_offset &#61; (1 << 31);return ret;
}

VGACore.scala

可以看到&#xff0c;此处利用 object VGAConfig 有效减少硬编码&#xff0c;实现优雅的参数化。

// Thanks to iBug for its VGA Sources.package vgaimport chisel3._
import chisel3.util._/* hd: Horizontal Visible Area* hf: Horizontal Front Porch* hs: Horizontal Sync Pulse* hb: Horizontal Back Porch* vd: Vertical Visible Area* vf: Vertical Front Porch* vs: Vertical Sync Pulse* vb: Vertical Back Porch*/object VGAConfig {val config &#61; Map(// | hd | hf | hs | hb | vd | vf | vs | vb |"640x480&#64;60" -> List( 640 , 16 , 96 , 48 , 480 , 10 , 2 , 31 ),// Not widely supported, at 85Hz"720x400&#64;85" -> List( 720 , 36 , 72 , 108 , 400 , 1 , 3 , 42 ),"720x400&#64;70" -> List( 720 , 15 , 108 , 51 , 400 , 11 , 2 , 32 ),"800x600&#64;60" -> List( 800 , 40 , 128 , 88 , 600 , 1 , 4 , 23 ),"800x600&#64;72" -> List( 800 , 56 , 120 , 64 , 600 , 37 , 6 , 23 ),"1024x768&#64;60" -> List( 1024 , 24 , 136 , 160 , 768 , 3 , 6 , 29 ))val refresh_freq &#61; Map("640x480&#64;60" -> 25175000, // 25.175 MHz Pixel Freq"720x400&#64;85" -> 35500000, // 35.500 MHz Pixel Freq"720x400&#64;70" -> 28322000,"800x600&#64;60" -> 40000000, // 40.000 MHz Pixel Freq"800x600&#64;72" -> 50000000, // 50.000 MHz Pixel Freq"1024x768&#64;60" -> 65000000 // 65.000 MHz Pixel Freq)val mode_selected &#61; "720x400&#64;70"
}class VGASig extends Bundle {val r &#61; Output(UInt(4.W))val g &#61; Output(UInt(4.W))val b &#61; Output(UInt(4.W))val hsync &#61; Output(Bool())val vsync &#61; Output(Bool())
}class VGACore extends Module {val io &#61; IO(new Bundle {val row &#61; Output(UInt(32.W))val col &#61; Output(UInt(32.W))val ready &#61; Output(Bool())// Indicate that in next cycle// ready will assertval pre_ready &#61; Output(Bool())val color &#61; Input(UInt(12.W))val sig &#61; new VGASig()})val cfg_list &#61; VGAConfig.config(VGAConfig.mode_selected)val hd &#61; cfg_list(0).U(32.W) // Must have this, or compr will be buggyval hf &#61; cfg_list(1).U(32.W) // for h_tick >&#61; hs &#43; hb it&#39;ll do val hs &#61; cfg_list(2).U(32.W)val hb &#61; cfg_list(3).U(32.W)val vd &#61; cfg_list(4).U(32.W)val vf &#61; cfg_list(5).U(32.W)val vs &#61; cfg_list(6).U(32.W)val vb &#61; cfg_list(7).U(32.W)val h_tick &#61; RegInit(0.U(32.W))val v_tick &#61; RegInit(0.U(32.W))val h_max &#61; hs &#43; hb &#43; hd &#43; hfval v_max &#61; vs &#43; vb &#43; vd &#43; vf// Layered counterwhen (h_tick >&#61; h_max - 1.U) {h_tick :&#61; 0.Uwhen (v_tick >&#61; v_max - 1.U) {v_tick :&#61; 0.U} .otherwise {v_tick :&#61; v_tick &#43; 1.U}} .otherwise {h_tick :&#61; h_tick &#43; 1.U}// 0 ~ hs&#43;hb-1 &#61; total hs&#43;hb cyclesio.ready :&#61; (h_tick >&#61; hs &#43; hb) && (h_tick < hs &#43; hb &#43; hd) && (v_tick >&#61; vs &#43; vb) && (v_tick < vs &#43; vb &#43; vd)io.pre_ready :&#61; (h_tick >&#61; hs &#43; hb - 1.U) && (h_tick < hs &#43; hb &#43; hd - 1.U) && (v_tick >&#61; vs &#43; vb - 1.U) && (v_tick < vs &#43; vb &#43; vd - 1.U)io.sig.r :&#61; Mux(io.ready, io.color(3,0), 0.U)io.sig.g :&#61; Mux(io.ready, io.color(7,4), 0.U)io.sig.b :&#61; Mux(io.ready, io.color(11,8), 0.U)io.col :&#61; h_tick - hs - hb // BUG!!io.row :&#61; v_tick - vs - vbio.sig.hsync :&#61; h_tick >&#61; hsio.sig.vsync :&#61; v_tick >&#61; vs
}

内存映射模块 MMap.scala

package multicpuimport chisel3._
import chisel3.util._import vga._// // Datapath perspective
// class MemPort extends Bundle {
// val mem_rdata &#61; Input(UInt(32.W))
// val mem_addr &#61; Output(UInt(32.W))
// val mem_wdata &#61; Output(UInt(32.W))
// val mem_wen &#61; Output(Bool())// // Extra reading port for debugging
// val mem_addr2 &#61; Output(UInt(32.W))
// val mem_rdata2 &#61; Input(UInt(32.W))
// }class MemCellPort extends Bundle {val mem_rdata &#61; Input(UInt(32.W))val mem_wdata &#61; Output(UInt(32.W))val mem_wen &#61; Output(Bool())
}class VRamPort extends Bundle {val mem_addr &#61; Output(UInt(32.W))val mem_wdata &#61; Output(UInt(16.W))val mem_wen &#61; Output(Bool())
}
// 0 ~ 3FF(1023) : Prog Mem, 1024 bytes (as 128 Words)
// 3000(12288) ~ 4F40(20288) : Disp Mem, 80*25 words (but upper 2 byte is always zero)
// FF00(65280) : UART Mem
class MMap extends Module {val io &#61; IO(new Bundle {// MMap -> Datapathval mmap_port &#61; Flipped(new MemPort)// Mem -> MMapval mem_port &#61; new MemPort// Uart -> MMapval uart_port &#61; new MemCellPort// VRam -> MMapval vram_port &#61; new VRamPort})val addr_in_mem &#61; ((io.mmap_port.mem_addr >&#61; 0.U) && (io.mmap_port.mem_addr < 1024.U))val addr_in_uart &#61; (io.mmap_port.mem_addr &#61;&#61;&#61; 65280.U)val addr_in_vram &#61; (io.mmap_port.mem_addr >&#61; 12288.U) && (io.mmap_port.mem_addr < 20289.U)io.mmap_port.mem_rdata :&#61; MuxCase(0.U, Seq(addr_in_mem -> io.mem_port.mem_rdata,addr_in_uart -> io.uart_port.mem_rdata,addr_in_vram -> 0.U))//io.mem_port.mem_rdataio.mem_port.mem_addr :&#61; io.mmap_port.mem_addrio.mem_port.mem_wdata :&#61; io.mmap_port.mem_wdataio.mem_port.mem_wen :&#61; (io.mmap_port.mem_wen && addr_in_mem)io.mem_port.mem_addr2 :&#61; io.mmap_port.mem_addr2io.mmap_port.mem_rdata2 :&#61; io.mem_port.mem_rdata2//io.uart_port.mem_rdataio.uart_port.mem_wdata :&#61; io.mmap_port.mem_wdataio.uart_port.mem_wen :&#61; (io.mmap_port.mem_wen && addr_in_uart)io.vram_port.mem_addr :&#61; (io.mmap_port.mem_addr - 12288.U) >> 2io.vram_port.mem_wdata :&#61; io.mmap_port.mem_wdata(15,0)io.vram_port.mem_wen :&#61; (io.mmap_port.mem_wen && addr_in_vram)
}

其余未尽事宜请参见压缩包内源码。


资源占用


仿真结果

上面是全部导到 Vivado 后的仿真结果。限于篇幅&#xff0c;下面 VideoSys 和 CPU Sig 没有截出。


下载结果

请参见同压缩包下的视频&#xff0c;非常清晰的展现了功能。


实验总结


注意事项


  1. Vivado 生成某些查找表结构的时候非常慢&#xff08;至少 O(n^3)&#xff09;&#xff1b;诊断这种问题的时候&#xff0c;就应该尝试注释代码 &#43; 记录时间&#xff0c;会得到很直观的认识。
    • 然后就要用 IP 核&#xff08;如 Dist/Block ROM&#xff09;替换&#xff0c;或者单独拎出来进行 Per Module Synthesis&#xff08;可以保存综合结果&#xff09;&#xff1b;详情可以参见《Vivado 从此开始》一书。

推荐阅读
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • YOLOv7基于自己的数据集从零构建模型完整训练、推理计算超详细教程
    本文介绍了关于人工智能、神经网络和深度学习的知识点,并提供了YOLOv7基于自己的数据集从零构建模型完整训练、推理计算的详细教程。文章还提到了郑州最低生活保障的话题。对于从事目标检测任务的人来说,YOLO是一个熟悉的模型。文章还提到了yolov4和yolov6的相关内容,以及选择模型的优化思路。 ... [详细]
  • 本文介绍了使用kotlin实现动画效果的方法,包括上下移动、放大缩小、旋转等功能。通过代码示例演示了如何使用ObjectAnimator和AnimatorSet来实现动画效果,并提供了实现抖动效果的代码。同时还介绍了如何使用translationY和translationX来实现上下和左右移动的效果。最后还提供了一个anim_small.xml文件的代码示例,可以用来实现放大缩小的效果。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • 本文介绍了如何使用JSONObiect和Gson相关方法实现json数据与kotlin对象的相互转换。首先解释了JSON的概念和数据格式,然后详细介绍了相关API,包括JSONObject和Gson的使用方法。接着讲解了如何将json格式的字符串转换为kotlin对象或List,以及如何将kotlin对象转换为json字符串。最后提到了使用Map封装json对象的特殊情况。文章还对JSON和XML进行了比较,指出了JSON的优势和缺点。 ... [详细]
author-avatar
佳鈺佳琴欣怡
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有