KV260 进阶开发(PYNQ驱动开发+Pixel Pack)

news2024/11/13 0:19:07

目录

1. 简介

2. PixelPacker HLS 实现

2.1 PixelPacker HLS 源码

2.2 PixelPacker 功能简介

2.3 头文件介绍

2.4 启动间隔 II

2.5 Case V24 片段解释

3. PixelPacker Py 驱动

3.1 PixelPacker Py 源码

3.2 PixelPacker 类详解

3.3 property 装饰器

3.4 操作寄存器

3.5 DefaultIP 类源码

3.6 DefaultIP 类详解

4. Takeaways


1. 简介

  • 本文讨论在 PYNQ 框架下,使用 Python 驱动 Vitis HLS Kernel。
  • 使用 Registermap 也可以完成驱动,即直接操作所有寄存器。
  • 通过 Python 方式编写的驱动更直观,可以理解为对 Registermap 的封装
  • Registermap 和 Python 驱动,底层都是调用 MMIO 的读写。

 

2. PixelPacker HLS 实现

2.1 PixelPacker HLS 源码

Pixel Packericon-default.png?t=O83Ahttps://github.com/Xilinx/PYNQ/tree/master/boards/ip/hls/pixel_pack

#include <ap_fixed.h>
#include <ap_int.h>
#include "hls_stream.h"
#include <ap_axi_sdata.h>

typedef ap_axiu<24,1,0,0> narrow_pixel;
typedef ap_axiu<32,1,0,0> wide_pixel;

typedef hls::stream<narrow_pixel> narrow_stream;
typedef hls::stream<wide_pixel>   wide_stream;

#define V_24 	0
#define V_32 	1
#define V_8 	2
#define V_16 	3
#define V_16C 	4

void pixel_pack(narrow_stream& stream_in_24 ,
				  wide_stream& stream_out_32,
				  	  	   int mode,
				    ap_uint<8> alpha) {
#pragma HLS INTERFACE mode=axis register_mode=both depth=24 port=stream_in_24  register
#pragma HLS INTERFACE mode=axis register_mode=both depth=24 port=stream_out_32 register
#pragma HLS INTERFACE mode=s_axilite    port=mode  register
#pragma HLS INTERFACE mode=s_axilite    port=alpha register
#pragma HLS INTERFACE mode=ap_ctrl_none port=return

	bool last = false;
	bool delayed_last = false;
	narrow_pixel in_pixel;
	wide_pixel out_pixel;
	switch (mode) {
	case V_24:
		while (!delayed_last) {
#pragma HLS pipeline II=4
			delayed_last = last;
			ap_uint<96> buffer;
			ap_uint<4> has_last;
			ap_uint<4> has_user;
			for (int j = 0; j < 4; ++j) {
				if (!last) {
					stream_in_24.read(in_pixel);
					buffer(j*24 + 23, j*24) = in_pixel.data;
					has_user[j] = in_pixel.user;
					has_last[j] = in_pixel.last;
					last = in_pixel.last;
				}
			}
			if (!delayed_last) {
				for (int i = 0; i < 3; ++i) {
					out_pixel.data = buffer(i*32 + 31, i*32);
					out_pixel.user = has_user[i];
					out_pixel.last = has_last[i+1];
					stream_out_32.write(out_pixel);
				}
			}
		}
		break;
	case V_32:
		while (!last) {
#pragma HLS pipeline II=1
			ap_uint<32> data;
			stream_in_24.read(in_pixel);
			data(23, 0) = in_pixel.data;
			data(31, 24) = alpha;
			out_pixel.data = data;
			out_pixel.last = in_pixel.last;
			out_pixel.user = in_pixel.user;
			last = in_pixel.last;
			stream_out_32.write(out_pixel);

		}
		break;
	case V_8:
		while (!delayed_last) {
#pragma HLS pipeline II=4
			delayed_last = last;
			bool user = false;
			ap_uint<32> data;
			for (int i = 0; i < 4; ++i) {
				if (!last) {
					stream_in_24.read(in_pixel);
					user |= in_pixel.user;
					last = in_pixel.last;
					data(i*8 + 7, i * 8) = in_pixel.data(7,0);
				}
			}
			if (!delayed_last) {
				out_pixel.user = user;
				out_pixel.last = last;
				out_pixel.data = data;
				stream_out_32.write(out_pixel);
			}
		}
		break;
	case V_16:
		while (!last) {
#pragma HLS pipeline II=2
			bool user = false;
			ap_uint<32> data;
			for (int i = 0; i < 2; ++i) {
				stream_in_24.read(in_pixel);
				user |= in_pixel.user;
				last = in_pixel.last;
				data(i*16 + 15, i*16) = in_pixel.data(16,0);
			}
			out_pixel.user = user;
			out_pixel.last = last;
			out_pixel.data = data;
			stream_out_32.write(out_pixel);
		}
		break;
	case V_16C:
		while (!last) {
#pragma HLS pipeline II=2
			bool user = false;
			ap_uint<48> data;
			for (int i = 0; i < 2; ++i) {
				stream_in_24.read(in_pixel);
				user |= in_pixel.user;
				last = in_pixel.last;
				data(i*24 + 23, i*24) = in_pixel.data;
			}
			ap_uint<32> out_data;
			ap_uint<9> out_c1 = ap_uint<9>(data(15,8))  + ap_uint<9>(data(39,32));
			ap_uint<9> out_c2 = ap_uint<9>(data(23,16)) + ap_uint<9>(data(47,40));
			out_data(7,0) = data(7,0);
			out_data(15,8) = out_c1(8,1);
			out_data(23,16) = data(31,24);
			out_data(31,24) = out_c2(8,1);
			out_pixel.user = user;
			out_pixel.last = last;
			out_pixel.data = out_data;
			stream_out_32.write(out_pixel);
		}
		break;
	}
}

2.2 PixelPacker 功能简介

pixel_pack kernel,从一个输入流 stream_in_24 读取24位像素数据,并根据给定的模式 mode 将这些数据转换和打包成32位的数据,然后将转换后的数据写入到输出流 stream_out_32 中。

Kernel 中定义了五种模式:V_24, V_32, V_8, V_16, V_16C,每种模式都有其特定的处理逻辑。 

1). V_24: 这个模式将读取4个24位的像素,将它们合并成一个96位的缓冲区,然后从这个缓冲区中提取出3个32位的像素并写入到输出流中。

2). V_32: 在这个模式中,每个24位的输入像素被扩展到32位,通过在最高的8位加上一个 alpha 值(透明度信息)。然后将这32位的数据写入到输出流中。

3). V_8: 此模式将4个24位的输入像素的最低8位提取出来,并将这些8位数据打包成一个32位的像素。

4). V_16: 在这个模式下,每两个24位的输入像素被组合成一个32位的输出像素,只包含输入像素的最低16位。

5). V_16C: 此模式处理两个24位的输入像素,并对其中的某些通道进行加法运算,最终生成一个32位的输出像素。它将第一个和第二个输入像素的第二个通道和第三个通道的值分别相加,然后将结果的高8位作为输出像素的相应通道值。

2.3 头文件介绍

1). #include <ap_fixed.h> 和 #include <ap_int.h>:

  • 这两个头文件提供了定点数和整数的数据类型。
  • ap_int.h 定义了 ap_int 和 ap_uint 数据类型,这些类型用于表示固定宽度的有符号和无符号整数。
  • ap_fixed.h 提供了定点数的实现,设计者可以指定整数部分和小数部分的位宽。

2). #include "hls_stream.h":

  • 这个头文件包含了 hls::stream 类的定义,用于在 HLS 设计中创建和管理流接口。
  • stream 一种 FIFO(先进先出)缓冲区,适用于数据流处理。

3). #include <ap_axi_sdata.h>:

  • 这个头文件定义了与 AXI4-Stream 协议兼容的数据结构。
  • 其中,ap_axiu 是一种带有用户信号(user)最后信号(last)的 AXI4-Stream 数据类型。

2.4 启动间隔 II

源码中,#pragma HLS pipeline 有三种不同的 II(Initiation Interval)值:II=1、II=2 和 II=4。这些值决定了流水线的启动间隔,即每个循环迭代之间的时钟周期数。不同的 II 值用于优化不同的处理模式。以下是每种模式的解释:

  • II=1:

这种模式下,流水线每个时钟周期启动一次新的迭代。它用于 V_32 模式,因为每次处理一个像素,且没有复杂的操作或数据依赖。

  • II=2:

这种模式下,流水线每两个时钟周期启动一次新的迭代。它用于 V_16 和 V_16C 模式,因为每次处理两个像素,且需要一些额外的计算(如 V_16C 模式下的颜色通道计算)。

  • II=4:

这种模式下,流水线每四个时钟周期启动一次新的迭代。它用于 V_24 和 V_8 模式,因为每次处理四个像素,且需要更多的时间来读取和处理数据。

不同的 II 值是为了在不同的处理模式下达到最佳的性能和资源利用率

2.5 Case V24 片段解释

case V_24:
	while (!delayed_last)
    {
    #pragma HLS pipeline II=4

		ap_uint<96> buffer;
		ap_uint<4>  tmp_last;
		ap_uint<4>  tmp_user;
        
        delayed_last = last;

		for (int j = 0; j < 4; ++j) {
			if (!last) {
				stream_in_24.read(in_pixel);
				buffer(j*24 + 23, j*24) = in_pixel.data;
				tmp_user[j] = in_pixel.user;
				tmp_last[j] = in_pixel.last;
				       last = in_pixel.last;
			}
		}

		if (!delayed_last) {
			for (int i = 0; i < 3; ++i) {
				out_pixel.data = buffer(i*32 + 31, i*32);
				out_pixel.user = tmp_user[i];
				out_pixel.last = tmp_last[i+1];
				stream_out_32.write(out_pixel);
			}
		}
	}

	break;

主循环

  • while (!delayed_last):这个循环持续运行,直到 delayed_last 为 true。delayed_last 是一个延迟的 last 信号,用于确保在处理完所有数据之前不会退出循环。
  • #pragma HLS pipeline II=4:这个指令告诉 HLS 工具将这个循环进行流水线化,启动间隔为 4 个时钟周期。

变量定义

  • delayed_last:延迟一拍的 last 信号,用于控制循环退出。
  • buffer:一个 96 位的缓冲区,用于存储 4 个 24 位的像素数据。
  • tmp_last 和 tmp_user:分别用于存储 last 和 user 信号。

内部循环

  • for (int j = 0; j < 4; ++j):这个循环读取 4 个 24 位的像素数据,并将它们存储在 buffer 中。
    • stream_in_24.read(in_pixel):从输入流中读取一个像素。
    • buffer(j*24 + 23, j*24) = in_pixel.data:将读取的像素数据存储在 buffer 中。
    • tmp_user[j] = in_pixel.user 和 tmp_last[j] = in_pixel.last:存储每个像素的 user 和 last 信号。
    • last = in_pixel.last:更新 last 信号。

输出逻辑

  • if (!delayed_last):如果 delayed_last 为 false,则处理输出。
    • for (int i = 0; i < 3; ++i):这个循环将 buffer 中的 96 位数据分成 3 个 32 位的数据,并写入输出流。
      • out_pixel.data = buffer(i*32 + 31, i*32):从 buffer 中提取 32 位数据。
      • out_pixel.user = tmp_user[i] 和 out_pixel.last = tmp_last[i+1]:设置输出像素的 user 和 last 信号。
      • stream_out_32.write(out_pixel):将输出像素写入输出流。

3. PixelPacker Py 驱动

3.1 PixelPacker Py 源码

文件位置:/usr/local/share/pynq-venv/lib/python3.10/site-packages/pynq/lib/video/pipeline.py

from pynq import DefaultIP
from .common import *

class PixelPacker(DefaultIP):
    def __init__(self, description):
        super().__init__(description)
        self._bpp = 24
        self.write(0x10, 0)
        self._resample = False

    bindto = [
        "xilinx.com:hls:pixel_pack:1.0",
        "xilinx.com:hls:pixel_unpack:1.0",
        "xilinx.com:hls:pixel_pack_2:1.0",
        "xilinx.com:hls:pixel_unpack_2:1.0",
    ]

    @property
    def bits_per_pixel(self):
        mode = self.read(0x10)
        if mode == 0:
            return 24
        elif mode == 1:
            return 32
        elif mode == 2:
            return 8
        elif mode <= 4:
            return 16

    @bits_per_pixel.setter
    def bits_per_pixel(self, value):
        if value == 24:
            mode = 0
        elif value == 32:
            mode = 1
        elif value == 8:
            mode = 2
        elif value == 16:
            if self._resample:
                mode = 4
            else:
                mode = 3
        else:
            raise ValueError("Bits per pixel must be 8, 16, 24 or 32")
        self._bpp = value
        self.write(0x10, mode)

    @property
    def resample(self):
        return self._resample

    @resample.setter
    def resample(self, value):
        self._resample = value
        # Make sure the mode is updated
        if self.bits_per_pixel == 16:
            self.bits_per_pixel = 16

3.2 PixelPacker 类详解

PixelPacker 类是像素格式转换驱动程序。它继承自 DefaultIP 类。

该用于更改视频流中每像素的位数。在更改宽度之前,应暂停流。这可以针对像素打包或像素解包IP核心。对于打包器,输入始终为每像素24位;而对于解包器,输出则为每像素24位。

类的属性和方法:

1). 构造函数 (__init__)

  • 初始化父类 DefaultIP。
  • 设置初始的每像素位数为24位。
  • 设置初始模式寄存器(位于0x10地址)为0。
  • _resample 属性被设置为 False,这个属性用于决定在 16bpp 模式下的采样行为。

2). bindto 属性

  • 一个包含硬件 IP 核标识的列表,表明这个类可以绑定到哪些特定的硬件实现上。

3). bits_per_pixel 属性

  • 一个属性装饰器,提供对每像素位数(_bpp)的读取和设置功能。
  • 读取功能通过读取模式寄存器(0x10)的值来确定当前的每像素位数。
  • 设置功能允许设置每像素位数为8、16、24或32位,并根据这个值更新模式寄存器(0x10)。
mode   _bpp      pack                   unpack
-------------------------------------------------------
0      24 bpp    不改变像素格式          不改变像素格式
1      32 bpp    用0填充第四个通道	    丢弃第四个通道
2       8 bpp    只保留第一个通道	    用0填充其他通道
3/4    16 bpp    取决于重采样	        取决于重采样

4). resample 属性

  • 一个布尔类型的属性装饰器,指示是否在 16bpp 模式下执行色度重采样。
  • 设置器在更改值之后会更新模式寄存器,以确保在 16bpp 模式下采样行为与设置的值一致。

3.3 property 装饰器

在 Python 中,@property 装饰器可以被用来将一个方法变成一个属性,允许你用属性的方式访问方法,同时保持方法的功能。

常用于实现对属性的访问控制,以及在访问属性时执行额外的逻辑,比如参数检查或者转换。

示例:

class Celsius:
    def __init__(self, temperature=0):
        self._temperature = temperature

    @property
    def temperature(self):
        print("Getting temperature")
        return self._temperature

    @temperature.setter
    def temperature(self, value):
        if value < -273.15:
            raise ValueError("Temperature below -273.15 is not possible")
        print("Setting temperature to", value)
        self._temperature = value

# 创建 Celsius 类的实例
temp = Celsius()

# 设置温度
temp.temperature = 30

# 获取温度
print(temp.temperature)

# 尝试设置不合理的温度值
try:
    temp.temperature = -300
except ValueError as e:
    print(e)

此例中:

  • 定义了一个 Celsius 类,它有一个私有属性 _temperature。
  • 使用 @property 装饰了一个名为 temperature 的方法,使之成为一个属性的 getter。
  • 定义了 temperature 的 setter 方法,允许在设置温度值时加入额外的逻辑(例如检查温度是否低于物理极限)。
  • 当尝试获取或设置 temperature 属性时,会自动调用对应的方法。

3.4 操作寄存器

在 HLS 中我们可以看到 mode 寄存器对应的地址是 0x10:

* S_AXILITE Registers
+---------------+----------+--------+-------+--------+----------------------+
| Interface     | Register | Offset | Width | Access | Description          |
+---------------+----------+--------+-------+--------+----------------------+
| s_axi_control | mode     | 0x10   | 32    | W      | Data signal of mode  |
| s_axi_control | alpha    | 0x18   | 32    | W      | Data signal of alpha |
+---------------+----------+--------+-------+--------+----------------------+

在 bits_per_pixel 的 getter 和 setter 方法中,是通过读取或者写入 0x10 来实现的:

    @property
    def bits_per_pixel(self):
        mode = self.read(0x10)
        if mode == 0:
            return 24
        elif mode == 1:
            return 32
        elif mode == 2:
            return 8
        elif mode <= 4:
            return 16

    @bits_per_pixel.setter
    def bits_per_pixel(self, value):
        if value == 24:
            mode = 0
        elif value == 32:
            mode = 1
        elif value == 8:
            mode = 2
        elif value == 16:
            if self._resample:
                mode = 4
            else:
                mode = 3
        else:
            raise ValueError("Bits per pixel must be 8, 16, 24 or 32")
        self._bpp = value
        self.write(0x10, mode)

3.5 DefaultIP 类源码

DefaultIP 类是一个复杂的基础设施,支持多种高级功能,如硬件中断处理、内存映射I/O操作、寄存器映射和硬件加速器的调度执行。

它为开发针对特定硬件IP的定制驱动提供了框架和工具,使得硬件功能的集成和利用更加高效和系统化。

class DefaultIP(metaclass=RegisterIP):
    def __init__(self, description):
        if "device" in description:
            self.device = description["device"]
        else:
            from .pl_server.device import Device

            self.device = Device.active_device
        self.mmio = MMIO(
            description["phys_addr"], description["addr_range"], device=self.device
        )
        if "interrupts" in description:
            self._interrupts = description["interrupts"]
        else:
            self._interrupts = {}
        if "gpio" in description:
            self._gpio = description["gpio"]
        else:
            self._gpio = {}
        for interrupt, details in self._interrupts.items():
            try:
                setattr(self, interrupt, Interrupt(details["fullpath"]))
            except ValueError as e:
                warnings.warn("Interrupt {} not created: {}".format(interrupt, str(e)))
                setattr(self, interrupt, None)
        for gpio, entry in self._gpio.items():
            gpio_number = GPIO.get_gpio_pin(entry["index"])
            setattr(self, gpio, GPIO(gpio_number, "out"))
        if "registers" in description:
            self._registers = description["registers"]
            self._fullpath = description["fullpath"]
            self._register_name = description["fullpath"].rpartition("/")[2]
            if "CTRL" in self._registers and self.device.has_capability("CALLABLE"):
                (
                    self._signature,
                    struct_string,
                    self._ptr_list,
                    self.args,
                ) = _create_call(self._registers)
                self._call_struct = struct.Struct(struct_string)
                self._ctrl_reg = True
                self.start_ert = self._start_ert
                self.start_sw = self._start_sw
                self.call = self._call
                if self.device.has_capability("ERT"):
                    self.start = self._start_ert
                else:
                    self.start = self._start_sw
        else:
            self._registers = None
        if "index" in description:
            cu_index = self.device.open_context(description)
            self.cu_mask = 1 << cu_index
            self._setup_packet_prototype()
        if "streams" in description:
            self.streams = {}
            for k, v in description["streams"].items():
                stream = self.device.get_memory_by_name(v["stream_id"])
                self.streams[k] = stream
                if v["direction"] == "output":
                    stream.source_ip = self
                elif v["direction"] == "input":
                    stream.sink_ip = self

    def _setup_packet_prototype(self):
        self._packet = ert.ert_start_kernel_cmd()
        self._packet.m_uert.m_start_cmd_struct.state = (
            ert.ert_cmd_state.ERT_CMD_STATE_NEW
        )
        self._packet.m_uert.m_start_cmd_struct.unused = 0
        self._packet.m_uert.m_start_cmd_struct.extra_cu_masks = 0
        if hasattr(self, "_ctrl_reg"):
            self._packet.m_uert.m_start_cmd_struct.count = (
                self._call_struct.size // 4
            ) + 1
        self._packet.m_uert.m_start_cmd_struct.opcode = ert.ert_cmd_opcode.ERT_START_CU
        self._packet.m_uert.m_start_cmd_struct.type = ert.ert_cmd_type.ERT_DEFAULT
        self._packet.cu_mask = self.cu_mask

    @property
    def register_map(self):
        if not hasattr(self, "_register_map"):
            if self._registers:
                self._register_map = RegisterMap.create_subclass(
                    self._register_name, self._registers
                )(self.mmio.array)
            else:
                raise AttributeError(
                    "register_map only available if the .hwh is provided"
                )
        return self._register_map

    @property
    def signature(self):
        """The signature of the `call` method"""
        if hasattr(self, "_signature"):
            return self._signature
        else:
            return None

    def _call(self, *args, **kwargs):
        self.start(*args, **kwargs).wait()

    def _start_sw(self, *args, ap_ctrl=1, waitfor=None, **kwargs):
        if not self._signature:
            raise RuntimeError("Only HLS IP can be called with the wrapper")
        if waitfor is not None:
            raise RuntimeError("waitfor only supported on newer versions of XRT")
        if kwargs:
            # Resolve any kwargs to make a single args tuple
            args = self._signature.bind(*args, **kwargs).args
        # Resolve and pointers that need .device_address taken
        args = [
            a.device_address if p else a
            for a, p in itertools.zip_longest(args, self._ptr_list)
        ]
        self.mmio.write(0, self._call_struct.pack(0, *args))
        self.mmio.write(0, ap_ctrl)
        return WaitHandle(self)

    def _start_ert(self, *args, waitfor=(), **kwargs):
        if not self._signature:
            raise RuntimeError("Only HLS IP can be called with the wrapper")
        if kwargs:
            # Resolve any kwargs to make a single args tuple
            args = self._signature.bind(*args, **kwargs).args
        args = [a.device_address if p else a for a, p in zip(args, self._ptr_list)]
        arg_data = self._call_struct.pack(0, *args)
        bo = self.device.get_exec_bo()
        exec_packet = bo.as_packet(ert.ert_start_kernel_cmd)
        exec_packet.m_uert.header = self._packet.m_uert.header
        exec_packet.cu_mask = self.cu_mask
        ctypes.memmove(exec_packet.data, arg_data, len(arg_data))
        wait_bos = tuple(w._bo for w in waitfor if w is not None and w._has_bo)
        if wait_bos:
            return self.device.execute_bo_with_waitlist(bo, wait_bos)
        else:
            return self.device.execute_bo(bo)

    def read(self, offset=0):
        return self.mmio.read(offset)

    def write(self, offset, value):
        self.mmio.write(offset, value)

3.6 DefaultIP 类详解

DefaultIP 类概述:

  • DefaultIP 类是一个用于操作硬件 IP 的 Python 类。
  • 它是一个基类,用于更具体的驱动程序继承和扩展。
  • 它使用元类 RegisterIP 来自动注册继承自 DefaultIP 的子类,以便可以自动识别和绑定到特定的IP。

主要特点和功能:

1). 初始化与属性设置:

  • 在初始化过程中,该类首先尝试从传入的 description 字典中获取设备信息。如果没有提供设备信息,它会使用默认的活动设备。
  • 使用 MMIO(Memory-Mapped I/O)对象来提供对硬件设备内存映射的访问。
  • 根据 description 字典中的信息,设置与中断 (_interrupts) 和通用输入输出 (_gpio) 相关的属性。
  • 为每个中断和GPIO创建相应的 Interrupt 或 GPIO 对象,并将其作为类属性。

2). 寄存器映射:

  • 如果提供了寄存器描述,类将创建一个寄存器映射,允许以属性的方式访问和控制这些寄存器。

3). 支持ERT和软件启动:

  • 如果设备支持ERT(Embedded Runtime),则使用ERT调度器来管理和调度IP核心的执行。
  • 否则,使用软件控制来启动和管理IP核心。

4). 数据流支持:

  • 类可以配置和管理与IP相关的数据流(例如DMA通道),包括输入和输出流。

5)执行和等待处理:

  • 提供了 _start_sw 和 _start_ert 方法来启动硬件加速器,并支持等待执行完成。
  • _call 方法用于直接调用硬件加速器,等待其完成。

6). 读写操作:

  • 提供 read 和 write 方法以直接从MMIO地址读取数据或向其写入数据。

4. Takeaways

  • ap_axiu<24,1,0,0> 是一个数据结构。
  • hls::stream<ap_axiu<24,1,0,0>> 是定义一个FIFO。
  • #pragma HLS INTERFACE mode=axis 是定义一个顶层接口 AXI-Stream。
  • delayed_last = false 延迟一拍。
  • while (!delayed_last) 在 AXI-Stream 比较常见。
  • @property 装饰器在 PYNQ 框架下经常使用。
  • PixelPacker 类继承自 DefaultIP 类,后者提供 register_map 方法。

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

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

相关文章

一、(JS)JS中鼠标事件-mouseenter、mouseleave和mouseover、mouseout区别

一、单个元素下mouseenter、mouseleave和mouseover、mouseout没有区别 我们先来一个demo&#xff0c;设置一个div <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"…

INIC6081量产工具下载,initio6081开卡软件分享

国内固态硬盘常用&#xff0c;且有量产工具流传出来的主控厂商包括慧荣、群联、点序、英韧、得一微、瑞昱、联芸、迈威、国科、华澜微等等。 每个主控需要用各自对应的量产工具&#xff0c;不同的量产工具支持的闪存颗粒也有差异&#xff0c;因此要根据固态硬盘实际的主控型号…

基于SSM的酒店客房管理系统+LW示例参考

系列文章目录 1.基于SSM的洗衣房管理系统原生微信小程序LW参考示例 2.基于SpringBoot的宠物摄影网站管理系统LW参考示例 3.基于SpringBootVue的企业人事管理系统LW参考示例 4.基于SSM的高校实验室管理系统LW参考示例 5.基于SpringBoot的二手数码回收系统原生微信小程序LW参考示…

Visual Studio 设置文件默认编码格式、行尾符等

文章目录 1.命令方式2.EditorConfig配置 1.命令方式 2.EditorConfig配置 微软官方文档 使用EditorConfig方式配置&#xff0c;无需Visual Studio软件自带对EditorConfig的支持&#xff0c;无需插件 将下面.editorconfig文件放在项目根目录下 root true # 所在目录是根目录…

基于SSM的二手交易管理系统的设计与实现 (含源码+sql+视频导入教程+文档)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于SSM的二手交易管理系统1拥有两种角色 管理员&#xff1a;商品管理、订单管理、充值管理、用户管理等用户&#xff1a;发布商品、查看闲置、充值账户、查看所有订单、发布求购信息、修…

今年白银市场的供需关系矛盾

自从2020年以来&#xff0c;白银手持连续4年都出现了供需缺口&#xff0c;预计今年的供需缺口将进一步扩大。2015年以来&#xff0c;白银总产量始终维持10亿盎司水平上下波动&#xff0c;2015~2023年的年均复合增速在0.4%&#xff0c;预计2024年的产量将下降1%。矿产银的产量从…

day-54 求出最多标记下标

思路 假设nums的长度为len&#xff0c;则返回数最大最大为&#xff08;len/2&#xff09;*2,所以可以将数组分为两部分&#xff0c;[0(len-1)/2]为一部分&#xff0c;[(len-1&#xff09;/2len-]为第二部分 解题过程 指针right从第二部分从右向左开始遍历&#xff0c;指针left…

Tensorboard 基础与使用-——界面介绍

在导入运行tensorboard得到一个event file文件。 tensorboard基本原理是这样的 python代码中将可视化的数据记录到event file中&#xff0c;保存至硬盘 采用tensorboard对event file文件进行读取&#xff0c;并在web端进行可视化 指令启动&#xff1a; tensorboard --logdir…

大数据Flink(一百一十七):Flink SQL的窗口操作

文章目录 Flink SQL的窗口操作 一、窗口的概述 二、Group Windows 1、​​​​​​​滚动窗口&#xff08;TUMBLE&#xff09; 2、​​​​​​​​​​​​​​滑动窗口&#xff08;HOP&#xff09; 3、​​​​​​​​​​​​​​Session 窗口&#xff08;SESSION&am…

军事目标无人机视角检测数据集 3500张 坦克 带标注voc

数据集概述 该数据集包含3500张无人机拍摄的图像&#xff0c;主要用于坦克目标的检测。数据集已经按照VOC&#xff08;Visual Object Classes&#xff09;标准进行了标注&#xff0c;适用于训练深度学习模型&#xff0c;特别是物体检测模型。 数据集特点 目标明确&#xff1…

通信工程学习:什么是GFP通用成帧规范

GFP&#xff1a;通用成帧规范 GFP通用成帧规范&#xff08;Generic Framing Procedure&#xff09;是一种先进的数据业务适配的通用协议和映射技术&#xff0c;由国际电联ITU-T的G.7041标准定义。该技术旨在透明地将各种不同物理层或逻辑链路层信号适配进入SDH&#xff08;同步…

C语言初识编译和链接

目录 翻译环境和运行环境编译环境预编译编译词法分析语法分析语义分析 汇编 链接运行环境 翻译环境和运行环境 在ANSI C的任何⼀种实现中&#xff0c;存在两个不同的环境。 第1种是翻译环境&#xff0c;在这个环境中源代码被转换为可执⾏的机器指令&#xff08;⼆进制指令&…

【Vue】1.v-指令、computed、watch

1 Vue 实例 注&#xff1a;此文件是 vue 根实例&#xff0c;data 可以 是一个对象 即 data:{ } 但是在其他 .vue 组件文件中&#xff0c;data 必须 是一个函数&#xff0c;返回一个新的对象&#xff0c;以避免多个组件实例之间的数据相互干扰 即 data(){ } <!DOCTYPE html&g…

前端正确设置资源上下文路径ContextPath(发布目录outDir 、公共基础路径),保证打包部署后站点能正常加载资源。

文章目录 引言I 处理资源上下文路径ContextPathjavascript对象获取上下文路径使用`./` 加载资源文件Vite 的basepublicPath是webpack部署应用包时的基本 URLII 知识扩展:URL的识别2.1 标准的链接格式2.2 URL中的?涵义2.3 URL中的&涵义2.4 传参III #fragment3.1为网页位置…

Vue2使用Vue CLI学习笔记

Vue2构建项目分析 Vue学习官网 Vue CLI官方 # 全局安装&#xff0c;只要装一次&#xff0c;以管理员身份 npm install -g vue/cli # 查看脚手架工具版本 vue --version # 创建项目&#xff0c;注意路径&#xff0c;名称不能是中文 vue create my-project # 启动项目&#xff…

基于Ant-Design-Vue设计的配置化表单

适用vue 3.4 版本以上 在日常的前端开发中&#xff0c;表单开发必不可少&#xff0c;尤其是在遇到一些大型的复杂的表单&#xff0c;这往往会变成一个痛点。于是就想着开发一个可以list来配置的表单组件。 先上组件代码 <!-- 该组件 VUE 版本在 3.4 以上可使用--> <…

【AI绘画】Midjourney进阶:景别详解

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AI绘画 | Midjourney 文章目录 &#x1f4af;前言&#x1f4af;为什么要学习景别景别的作用景别应用实例 &#x1f4af;大景别&#x1f4af;远景特点提示词书写技巧测试 &#x1f4af;全景特点提示词书写技巧测试注意点 &#x1f…

ozon免费选品工具,OZON免费选品神器

在跨境电商的浩瀚海洋中&#xff0c;寻找那片属于自己的盈利蓝海&#xff0c;是每个商家梦寐以求的目标。随着俄罗斯电商市场的迅速崛起&#xff0c;Ozon平台以其庞大的用户基数和不断增长的市场份额&#xff0c;成为了众多跨境卖家眼中的“香饽饽”。然而&#xff0c;面对琳琅…

【渗透测试】——DVWA靶场搭建

&#x1f4d6; 前言&#xff1a;DVWA&#xff08;Damn Vulnerable Web Application&#xff09;是一个用于安全漏洞测试的 PHP/MySQL 网络应用&#xff0c;旨在为安全专业人士提供一个合法的环境&#xff0c;以测试他们的技能和工具&#xff0c;同时帮助 Web 开发者更好地理解 …

计算机的信息编码和基本运算(上)

大家好我是清墨&#xff0c;今天同同同样来分享一下笔记。 计算机的信息编码 计算机用二进制编码的方式来表示和存储信息&#xff0c;我们见到的信息&#xff08;文字、图片等&#xff09;都是经过转换处理的。 ASCII&#xff08;American Standard Code for Information Int…