SV学习笔记(五)

news2024/11/15 9:37:47

文章目录

  • 线程的使用
    • 程序和模块
    • 什么是线程
    • 线程的概念澄清
  • 线程的控制
    • fork并行线程语句块
    • fork…join
    • fork…join_any
    • 等待所有衍生线程
    • 停止单个线程
    • 停止多个线程
    • 停止被多次调用的任务
  • 线程的通信
    • 写在前面
    • event事件
    • 通知的需求
    • semaphore旗语
    • mailbox信箱
    • 三种通信的比较和应用
  • 参考资料

线程的使用

程序和模块

  • module(模块)作为SV从verilog继承过来的概念,自然地保持了它的特点,除了作为RTL模型的外壳包装和实现硬件行为,在更高层的集成层面,模块之间也需要通信和同步。

  • 对于硬件的过程块,他们之间的通信可理解为不同逻辑/时序块之间的通信或同步,是通过信号的变化来完成的。

  • 从硬件实现角度来看,Verilog通过Always、initial过程语句块和信号数据连接实现进程间通信。

  • 我们可以将不同的module作为独立的程序块,他们之间的同步通过信号的变化(event触发)、等待特定事件(时钟周期)或时间(固定延时)来完成。

  • 如果按照软件的思维理解硬件仿真,仿真中各个模块首先是独立运行的线程(thread)。

  • 模块(线程)在仿真一开始便并行执行,除了每个线程会依照自身内部产生的事件来触发过程语句块之外,也同时依靠相邻模块间信号变化来完成模块之间的线程同步。

什么是线程

  • 线程即独立运行的程序。
  • 线程需要被触发,可以结束或者不结束。
  • 在module中的initial和always,都可以看做独立的线程,它们会在仿真0时刻开始,而选择结束或者不结束(对initial而言)。
  • 硬件模型中由于都是always语句块,所以可以看成是多个独立运行的线程,而这些线程会一直占用仿真资源,因为它们不会结束。
  • 软件测试平台中的验证环境都需要initial语句块去创建,而在仿真过程中,验证环境中的对象可以创建和销毁,因此软件测试端的资源占用是动态的。
  • 软件环境中的initial块对语句有两种分组方式,使用 begin…end 或 fork…join 。
  • begin…end 中的语句是 顺序执行 的,而 fork…join 中的语句是 并发执行 的。
  • 与 fork…join 类似的并发执行语句还有 fork…join_any 和 fork…join_none。

线程的概念澄清

  • 线程的执行轨迹是 呈树状结构的,即任何线程都应该有父线程 。
  • 父线程可以开辟若干子线程,父线程可以暂停或终止子线程
  • 当子线程终止时,父线程可以继续执行。
  • 当父线程终止时,其开辟的所有子线程都应当会终止。

线程的控制

fork并行线程语句块

linux中的线程也是通过fork来创建的

fork…join

initial begin
    $display("@%0t: start fork...join example", $time);
    #10 $display("@%0t: sequential after #10", $time);
    fork
        $display("@%0t: parallel start", $time);
        #50 $display("@%0t: parallel after #50", $time);
        #10 $display("@%0t: parallel after #10", $time);
        begin
            #30 $display("@%0t: sequential after #30", $time);
            #10 $display("@%0t: sequential after #10", $time);
        end
    join
    $display("@%0t: after join", $time);
    #80 $display("@%0t: finish after 80", $time);
end
@0: start fork...join example
@10: sequential after #10
@10: parallel start
@20: parallel after #10
@40: sequential after #30
@50: sequential after #10
@60: parallel after #50
@60: after join
@140: finish after 80

总结:可见fork…join内是并行执行的,initial块内程序作为父线程,并创建了四个子线程。

  • $display…
  • #50 $display…
  • #10 $display…
  • begin #30 $display… #10 $display… end

fork…join_any

initial begin
    $display("@%0t: start fork...join_any example", $time);
    #10 $display("@%0t: sequential after #10", $time);
    fork
        $display("@%0t: parallel start", $time);
        #50 $display("@%0t: parallel after #50", $time);
        #10 $display("@%0t: parallel after #10", $time);
        begin
            #30 $display("@%0t: sequential after #30", $time);
            #10 $display("@%0t: sequential after #10", $time);
        end
    join_any
    $display("@%0t: after join_any", $time);
    #80 $display("@%0t: finish after 80", $time);
end
@0: start fork...join_any example
@10: sequential after #10
@10: parallel start
@10: after join_any
@20: parallel after #10
@40: sequential after #30
@50: sequential after #10
@60: parallel after #50
@90: finish after 80

总结:子线程的创建与fork…join是一致的,不同的是,fork…join要等所有子线程执行完毕才会继续执行父线程的程序,而fork…join_any中只要有一个子线程(最短的)执行完毕,父线程的程序就会被执行的。

等待所有衍生线程

  • 在SV中,当程序中的initial块全部执行完毕,仿真器就退出了,也就是如果以上测试代码中没有最后的…finish after…语句,对于join_any和join_none其子线程没有全部执行完毕仿真就结束了。

  • 如果需要等待所有fork块中的子线程全部执行完毕在退出结束initial块,可以使用wait fork语句来等待所有子线程结束。

task run_thread;
    ...
    fork
        check_trans(tr1); //thread1
        check_trans(tr2); //thread2
        check_trans(tr3); //thread3
    join_none
    ...

    // 等待所有fork中的线程结束
    wait fork;
endtask

停止单个线程

在使用了fork…join_any和fork…join_none以后,我们可以使用disable来指定需要停止的线程。

parameter TIME_OUT = 1000;
task check_trans(transaction tr);
    fork begin
        // 等待回应或达到某个最大延时
        fork : timeout_block
            begin
                wait (bus.cb.addr == tr.addr);
                $display("@%0t: Addr match %d", $time, tr.addr);
            end
            #TIME_OUT $display("@%0t: Error: timeout", $time);
        join_any
        disable timeout_block;
    end
    join_none
endtask

停止多个线程

disable fork可以停止从当前线程中衍生出来的所有子线程。

initial begin
    check_trans(tr0); //线程0
    //创建一个线程来控制disable fork的作用范围
    fork //线程1
        begin
            check_trans(tr1); //线程2
            fork //线程3
                check_trans(tr2); //线程4
            join
            // 停止线程1-4, 单独保留线程0
            #(TIME_OUT/2) disable fork;
        end
    join
end

其实最准确的关闭线程的方法是,给定线程的标号,关闭指定的线程。

停止被多次调用的任务

  • 如果给线程指明标号,那么当这个任务或线程被多次调用后,使用disable去终止这个线程时,会将所有的同名线程全部终止。
  • 在创建task时,如果使用disable进程标号,一定要确认该task是否会被多处调用,如果多处调用要避免使用disable进程标号。

线程的通信

写在前面

  • 测试平台中的所有线程都需要同步并交换数据。
  • 一个线程可能需要等待另一个线程。
  • 多个线程可能同时访问同一个资源。
  • 线程之间可能需要交换数据。
  • 所有这些数据交换和同步称之为线程间的通信(IPC,Interprocess Communication)。

event事件

  • verilog中,一个线程总是要等待一个带@操作符的事件。这个操作符是边沿敏感的,所以它总是阻塞着、等待着事件的变化。
  • 其他线程可以通过->操作符来触发事件,结束对一个线程的阻塞。
  • 这就像在打电话时,一个人等待另一个人的呼叫。
  • 测试代码(注意,event需声明而不需要new):
event el,	e2;
initial begin
    $display("@t0t: 1: before trigger", $time);
    -> el;
    @e2;
    $display("@%0t: 1: after trigger", $time);
end

initial begin
    $display("@t0t: 2: before trigger", $time);
    -> e2;
    @e1;
    $display("@t0t: 2: after trigger", $time);
end
@0: 1: before trigger
@0: 2: before trigger
@0: 1: after trigger

总结:

  • 第一个initial启动,触发e1事件,然后阻塞在e2上。
  • 第二个initial启动,触发e2事件,然后阻塞在e1上。
  • e1和e2同一时刻被触发,但由于delta cycle的时间差使得两个initial块可能无法等到e1或e2。(根据上面的执行结果,可以分析得出,虽然是同一时刻,initial 1 还是被先执行了,先打印了before,然后拨打了电话(e1),然后等待接电话(e2),此时initial 2被执行,打印了before,拨打电话(e2),此时initial已经等待接听电话,所以initial 1打印了after,也就是接通了电话,而initial 2是接不到电话的,因为在接听之前tinitial 1已经拨出去了,所以没有打印出after)
  • 也就是,等待事件触发要在事件发生之前,否则将错过事件,所以,更安全的方式可以使用event的方法triggered(),相当于为拨打电话提供了留言功能。

进一步:

event el,	e2;
initial begin
    $display("@t0t: 1: before trigger", $time);
    -> el;
    wait (e2.triggered());
    $display("@%0t: 1: after trigger", $time);
end

initial begin
    $display("@t0t: 2: before trigger", $time);
    -> e2;
    wait (e1.triggered());
    $display("@t0t: 2: after trigger", $time);
end
@0: 1: before trigger
@0: 2: before trigger
@0: 1: after trigger
@0: 2: after trigger

总结:

  • 对于triggered()而言,如果事件在当前时刻已经被触发,则不会引起阻塞,否则会一直等到事件被触发为止。
  • 这个方法相对@而言,更有能力保证,只要event被触发过,就可以防止引起阻塞。

通知的需求

不同的线程之间,有时会有互相告知的需求。比如,我们要开一辆车,在踩油门要行驶之前,首先得看一下汽车有没有发动,那么这辆车可能是这样设计的:

module road;
    initial begin
        automatic car byd = new();
        byd.drive();
    end
endmodule

class car;
    bit start=0;
    task launch();
        start=1;
        $display(”car is launched”);
    endtask

    task move();
        wait(start==1);
        $display(”car is moving”);
    endtask

    task drive();
        fork
            this.launch();
            this.move() ;
        join
     endtask
endclass

//输出结果:
// car is launched
// car is moving
  • 以上的例子可以看出,两个线程launch和move是通过线程间共享变量car::start和wait语句来实现了线程launch通知线程move的功能。
  • 我们也可以将线程间共享变量car::start改为事件来触发。
class car;
    event e_start;
    task launch();
        -> e_start;
        $display(”car is launched”);
    endtask

    task move();
        wait(e_start.triggered());
        $display(”car is moving”);
    endtask

    task drive();
        fork
            this.launch();
            this.move() ;
        join
     endtask
endclass

当汽车要加速的时候,添加速度显示功能:

module road;
    initial begin
        automatic car byd = new();
        byd.drive();
        byd.speedup();
        byd.speedup();
        byd.speedup();
    end
endmodule

class car;
    event e_start;
    event e_speedup;
    int speed = 0;
    ......
    task speedup();
        #10ns;
        -> e_speedup;
    endtask

    task display();
        forever begin
            @e_speedup;
            speed++;
            $display("speed is %0d", speed);
        end
    endtask

    task launch();
        start=1;
        $display(”car is launched”);
    endtask

    task move();
        wait(start==1);
        $display(”car is moving”);
    endtask

    task drive();
        fork
            this.launch();
            this.move() ;
            this.display() ;
        join_none
     endtask
endclass

//输出结果:
// car is launched
// car is moving
// speed is 1
// speed is 2
// speed is 3

这里有两点值得分析:

  • 为什么使用@e_speedup?

因为使用 triggered的话,再第一次触发以后,下次会认为已经触发过而不再继续等待触发,这和设计功能是违背的,这里需要对@和triggerd按功能区分使用(UVM中提供一种event使用方式,当第一次事件触发以后,会清除此次触发,这样就不存在triggerd的问题了)。

  • 为什么改为join_none?

因为父线程中的speedup需要和三个子线程并行执行。

  • 总结:

从这个汽车加速的例子来看,如果你需要一直踩着油门不放的话,这个加速的event必定会被不断触发,而当线程A要给线程B传递超过一次事件时,使用公共变量就不再是一个好的选择了。

通过event的触发,可以多次通知另一个线程,注意此时应该使用@。

semaphore旗语

  • semaphore可以实现对同一资源的访问控制。
  • 对于初学者而言,无论线程之间在共享什么资源,都应该使用semaphore等资源访问控制的手段, 以此避免可能出现的问题。
  • semaphore有三种基本操作。new() 方法可以创建一个带单个或者多个钥匙的semaphore,使用get() 可以获取一个或者多个钥匙,而put()可以返回一个或者多个钥匙。
  • 如果你试图获取一个semaphore而希望不被阻塞, 可以使用try_get() 函数。它返回1表示有足够多的钥匙, 而返回0则表示钥匙不够。
program automatic test(bus_ifc.TB bus);
    semaphore sem; //创建一个semaphore
    initial begin
        sem=new(1) ; //分配一个钥匙
        fork
            sequencer() ; //产生两个总线事务线程
            sequencer() ;
        join
    end

    task sequencer;
        repeat($urandom%10) //随机等待0-9个周期
        @bus.cb;

        send Trans() ; //执行总线事务
    endtask

    task sendTrans;
        sem.get(1) ; //获取总线钥匙
        @bus.cb; //把信号驱动到总线上
        bus.cb.addr<=t.addr;
        ......
        sem.put(1) ; //处理完成时把钥匙返回
    endtask
endprogram

白话一刻

这段代码是一个简单的自动测试程序,使用了SystemVerilog(SV)语言。它的主要目的是模拟总线上的事务,并确保这些事务是顺序执行的,通过使用信号量(semaphore)来同步这些事务。

以下是对代码各个部分的解释:

  • 程序定义
program automatic test(bus_ifc.TB bus);

这一行定义了一个名为test的自动程序,该程序接受一个类型为bus_ifc.TB的接口参数bus。

  • 信号量定义
semaphore sem;

这定义了一个信号量sem。信号量通常用于同步多个线程或任务,确保它们以正确的顺序执行。

  • 初始化块
initial begin  
    sem=new(1) ;  
    fork  
        sequencer() ;  
        sequencer() ;  
    join  
end

在initial块中,首先为信号量sem分配了一个初始值1(这通常表示有一个“钥匙”可用)。然后,使用fork和join结构并行启动了两个sequencer任务。这两个任务将并发执行。

  • sequencer 任务
task sequencer;  
    repeat($urandom%10)  
    @bus.cb;  
  
    send Trans() ;  
endtask

sequencer任务首先会随机等待0到9个周期($urandom%10生成一个0到9的随机数)。然后,它会等待bus.cb上的某个信号(可能是一个时钟信号或其他同步事件)。最后,它调用sendTrans任务来发送一个总线事务。

  • sendTrans 任务
task sendTrans;  
    sem.get(1) ;  
    @bus.cb;  
    bus.cb.addr<=t.addr;  
    ......  
    sem.put(1) ;  
endtask

在sendTrans任务中,首先尝试获取信号量sem的一个“钥匙”(如果sem为0,则任务将等待直到有钥匙可用)。然后,它再次等待bus.cb上的某个信号,并执行一些操作(例如,将地址t.addr写入bus.cb.addr)。在任务完成并处理完总线事务后,它释放(放回)信号量的“钥匙”。

  • 总结

这个程序创建了两个并发的sequencer任务,每个任务在随机延迟后尝试发送一个总线事务。通过使用信号量sem,确保了这两个事务是顺序执行的,即一个事务必须等待另一个事务完成后才能开始。这避免了可能的总线冲突或同步问题。

  • 线程之间除了”发球”和接球”这样的打乒乓以外,还有更深入的友谊,比如共用一些资源。

  • 对于线程间共享资源的使用方式, 应该遵循互斥访问(mutex access) 原则。

  • 控制共享资源的原因在于,如果不对其访问做控制,可能会出现多个线程对同一资源的访问,进而导致不可预期的数据损坏和线程的异常,这种现象称之为"线程不安全”。

以这里比亚迪为例:

class car;
    semaphore key;
    function new();
        key=new(1) ;
    endfunction

    task get_on(string p);
        $display("%s is waiting for the key", p);
        key.get();
        #1ns;
        $display("%s got on the car", p);
    endtask

    task get_off(string p);
        $display("%s got off the car", p);
        key.put();
        #1ns;
        $display("%s returned the key", p);
    endtask
endclass

module family;
    car byd=new();
    string p1="husband";
    string p2="wife";

    initial begin
        fork
            begin//丈夫开车
                byd.get_on(p1);
                byd.get_off(p1);
            end

            begin//妻子开车
                byd.get_on(p2);
                byd.get_off(p2);
            end
        join
    end
endmodule

//打印结果
// husband is waiting for the key
// wife is waiting for the key
// husband got on the car
// husband got off	the car
// husband returned the key
// wife got on the car
// wife got off the car
// wife returned the key
  • 一开始在拿到这辆车的时候,只有一把钥匙,而丈夫和妻子如果都想开车的话,也得遵循先到先得的原则。所以,当丈夫和妻子同时都想用车的时候, 一把钥匙(semaphore key) 只可以交给他们中的一位, 另外一位则需要等待,直到那把钥匙归还之后才可以使用。
  • 从上面的输出结果来看,也是能够看出来,虽然丈夫和妻子在同一时间想开这辆车,然而也只能允许一位家庭成员来驾驶。直到丈夫从车上下来,归还了钥匙以后,妻子才可以上车。
  • 我们用这个生动的例子来解释semaphore对于控制访问共享资源的帮助, 从上面对于semaphore key的使用来看, key在使用前必须要做初始化,即要告诉用户它原生自带几把钥匙。
  • 从例子来看,它只有1把钥匙,而丈夫和妻子在等待和归还钥匙时,没有在semaphore::get() /put() 函数中传递参数, 即默认他们等待和归还的钥匙数量是1。semaphore可以被初始化为多个钥匙, 也可以支持每次支取和归还多把钥匙用来控制资源访问。
  • semaphore存在一些问题,这些问题就需要在coding时格外注意。
    • 即使semaphore中没有钥匙,仍然可以执行还钥匙。
    • 即使A拿到了钥匙,B没拿到钥匙,B仍然可以还钥匙。

mailbox信箱

  • 线程之间如果传递信息,可以使用mailbox,mailbox和队列queue有相近之处。
  • mailbox是一种对象,因此也需要使用new()来例化。例化时有一个可选的参数size来限定其存储的最大数量。如果size是0或者没有指定,则信箱是无限大的,可以容纳任意多的条目。
  • 使用put()可以把数据放入mailbox,使用get()可从信箱移除数据。
  • 如果信箱为满,则put()会阻塞;如果信箱为空,则get()会阻塞。
  • peek()可以获取对信箱里数据的拷贝而不移除它。
  • 线程之间的同步方法需要注意,哪些是阻塞方法,哪些是非阻塞方法,即哪些是立即返回的,而哪些可能需要等待时间的。
program automatic bounded;
    mailbox mbx;
    initial begin
        mbx=new(1); //容量为1
        fork
            //Producer线程
            for(inti=1; i<4; i++) begin
            $display("Producer: before put(%0d)", i);
            mbx.put(i);
            $display("Producer: after put(%0d)", i);
            end
            //consumer线程
            repeat(3) begin
                int j;
                #1ns mbx.get(j);
                $display("Consumer: after get(%0d)", j);
            end
        join
    end
endprogram

//测试结果
// Producer: before put(1)
// Producer: after put(1)
// Producer: before put(2)
// Consumer: after get(1)
// Producer: after put(2)
// Producer: before put(3)
// Consumer: after get(2)
// Producer: after put(3)
// Consumer: after get(3)
  • mailbox和queue的区别:
    • maibox必须通过new() 例化, 而队列只需要声明。
    • mailbox可以将不同的数据类型同时存储,不过这么做是不建议的;对于队列来讲,它内部存储的元素类型必须一致。
    • maibox的存取方法put() 和get() 是阻塞方法,即使用时方法不一定立即返回;而队列所对应的存取方式,push_back()和pop_front()方法是非阻塞的,会立即返回。注意在用队列时要先判断空满,根据功能需求可以用wait等待,也可以使用if判断;此外调用阻塞方法时,要在task中调用,因为阻塞方法是耗时的,调用非阻塞可以在task也可以在function。
    • mailbox只能够用作FIFO,而queue除了按照FIFO使用,还有其它应用的方式例如LIFO(Last In First Out) 。
    • 对于mailbox变量的操作, 在传递形式参数时,实际传递并拷贝的是mailbox的指针;等同于队列参数声明为ref,也就是指针,区别就是对参数是引用而不是拷贝。
  • 关于mailbox的其它特性:
    • mailbox在例化时, 通过new(N) 的方式可以使其变为定长(fixed length)容器。这样在负载到长度N以后, 无法再对其写入。如果用new() 的方式, 则表示信箱容量不限大小。
    • 除了put() /get() /peek() 这样的阻塞方法, 用户也可以考虑使用try_put() /try_get() /try_peek() 等非阻塞方法。
    • 如果要显式地限定mailbox中元素的类型, 可以通过mailbox #(type=T)的方式来声明。例如上面的三个mailbox存储的是int, 则可以在声明时进一步限定其类型为mailbox #(int) 。

三种通信的比较和应用

  • event: 最小信息量的触发,即单一的通知功能。可以用来做事件的触发, 也可以多个event组合起来用来做线程之间的同步。
  • semaphore: 共享资源的安全卫士。如果多线程间要对某一公共资源做访问,即可以使用此方式。
  • mailbox: 精小的SV原生FIFO。在线程之间做数据通信或者内部数据缓存时可以考虑使用此方式。

参考资料

  • Wenhui’s Rotten Pen
  • SystemVerilog
  • chipverify

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

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

相关文章

探索数据结构:特殊的双向队列

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;数据结构与算法 贝蒂的主页&#xff1a;Betty’s blog 1. 双向队列的定义 **双向队列(double‑ended queue)**是一种特殊的队列…

[羊城杯 2020]Easyphp2 ---不会编程的崽

摆烂一周了&#xff0c;继续更&#xff01;&#xff01;题目还是简单哦。 提示明显要我们修改数据包&#xff0c;第一反应是修改referer。试了一下不太对。url很可能存在文件包含 使用伪协议读取一下源码吧。它过滤了base64关键字。尝试url编码绕过&#xff0c;这里可以使用二…

实景三维技术在推进城市全域数字化转型的作用

4月2日&#xff0c;国家数据局发布《深化智慧城市发展推进城市全域数字化转型的指导意见&#xff08;征求意见稿&#xff09;》&#xff08;下称&#xff1a;《指导意见》&#xff09;&#xff0c;向社会公开征求意见。 《指导意见》作为推进城市数字化转型的重要文件&#xf…

11、子串-滑动窗口最大值

题解&#xff1a; 双端队列是一种特殊的队列&#xff0c;允许你在队列的两端进行插入和删除操作。在滑动窗口问题中&#xff0c;我们使用它来存储可能是当前窗口最大值的元素的索引。 维护队列的顺序&#xff1a; 当新元素进入窗口时&#xff0c;我们将它与队列尾部的元素进…

echarts 毕节区县地图 包含百管委、高新区 (手扣)

百度网盘 链接&#xff1a;https://pan.baidu.com/s/14yiReP8HT_bNCGMOBajexg 提取码&#xff1a;isqi

MQ简介和面试题

一&#xff0c;什么是MQ MQ全称是Mwessage Queue(消息队列)&#xff0c;是在消息传输过程中保存消息的容器&#xff0c;多用于分布式系统之间进行通信&#xff0c;解耦和低耦合性 二&#xff0c;常见的MQ产品 RebbitMQ,RocketMQ, ActiveMQ, Kafka, ZeroMQ, MetaMQ 其中我们…

(学习日记)2024.04.06:UCOSIII第三十四节:互斥量函数接口讲解

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

STM32CubeMX+MDK通过I2S接口进行音频输入输出(全双工读写一个DMA回调)

一、前言 目前有一个关于通过STM32F411CEUx的I2S总线接口控制SSS1700芯片进行音频输入输出的研究。 SSS1700 是具有片上振荡器的 3S 高度集成的USB音频控制器芯片 。 SSS1700 功能支持96 KHz 24 位采样率&#xff0c;带外部音频编解码器&#xff08;24 位/96KHz I2S 输入和输出…

工具推荐-针对Nacos利器-NacosExploitGUI_v4.0

Nacos是由阿里所开发的一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 工具简介 集成Nacos的各种poc Nacos控制台默认口令漏洞(nacos,nacos)Nacostoken.secret.key默认配置(QVD-2023-6271)Nacos-clientYaml反序列化漏洞Nacos Jraft Hessian反序列化漏洞…

【Hadoop技术框架-MapReduce和Yarn的详细描述和部署】

前言&#xff1a; &#x1f49e;&#x1f49e;大家好&#xff0c;我是书生♡&#xff0c;今天的内容主要是Hadoop的后两个组件&#xff1a;MapReduce和yarn的相关内容。同时还有Hadoop的完整流程。希望对大家有所帮助。感谢大家关注点赞。 &#x1f49e;&#x1f49e;前路漫漫&…

使用GDAL进行简单的坐标系转换

使用GDAL进行简单的坐标系转换 使用python GDAL进行简单的坐标系转换&#xff0c;暂时不考虑不同基准坐标系转换的精度问题。 安装环境 使用UbuntuAnaconda python 环境 conda install gdal 定义坐标系 from osgeo import gdal from osgeo import osrsrs_wgs84 osr.Spati…

ICP配准算法

配准算法 问题定义ICP(point to point)算法思想步骤分解point to point和point to plane的区别ICP配准算法的标准流程NDT 本篇将介绍配准算法&#xff0c;将介绍ICP(point to point)、ICP(point to plane)和NDT算法。其中ICP有两种&#xff0c;point to point表示通过构建点与点…

力扣347. 前 K 个高频元素

思路&#xff1a;记录元素出现的次数用map&#xff1b; 要维护前k个元素&#xff0c;不至于把所有元素都排序再取前k个&#xff0c;而是新建一个堆&#xff0c;用小根堆存放前k个最大的数。 为什么是小根堆&#xff1f;因为堆每次出数据时只出堆顶&#xff0c;每次把当前最小的…

文旅元宇宙|“元宇宙+”全面赋能智慧文旅场景建设

元宇宙作为下一代互联网入口&#xff0c;正在潜移默化的改变着人生的生活方式&#xff0c;不断催生新业态&#xff0c;带给人们前所未有的体验。元宇宙概念的崛起&#xff0c;正以其独特的魅力&#xff0c;引领着一场全新的智慧文旅革命。元宇宙&#xff0c;这个融合了虚拟现实…

物联网实战--入门篇之(九)安卓QT--开发框架

目录 一、QT简介 二、开发环境 三、编码风格 四、设计框架 五、总结 一、QT简介 QT是一款以C为基础的开发工具&#xff0c;已经包含了很多常用的库&#xff0c;除了基本的GUI以外&#xff0c;还有网络、数据库、多媒体、进程通信、串口、蓝牙等常用库&#xff0c;开发起来…

Vue3_2024_7天【回顾上篇watch常见的后两种场景】

随笔&#xff1a;这年头工作不好找咯&#xff0c;大家有学历提升的赶快了&#xff0c;还有外出人多注意身体&#xff0c;没错我在深圳这边阳了&#xff0c;真的绝啊&#xff0c;最尴尬的还给朋友传染了&#xff01;&#xff01;&#xff01; 之前三种的监听情况&#xff0c;监听…

Godot 4 教程《勇者传说》依赖注入 学习笔记(0):环境配置

文章目录 前言相关地址环境配置初始化环境配置文件夹结构代码结构代码运行 资源文件导入像素风格窗口环境设置背景设置,Tileap使用自动TileMap 人物场景动画节点添加站立节点添加移动动画添加 通过依赖注入获取Godot的全局属性项目声明 当前项目逻辑讲解角色下降添加代码位置问…

ssm028蜀都天香酒楼的网站设计与实现+jsp

基于JSP的蜀都天香酒楼管理系统的设计与实现 摘要 近年来&#xff0c;信息化管理行业的不断兴起&#xff0c;使得人们的日常生活越来越离不开计算机和互联网技术。首先&#xff0c;根据收集到的用户需求分析&#xff0c;对设计系统有一个初步的认识与了解&#xff0c;确定蜀都…

C语言数据结构(11)——归并排序

欢迎来到博主的专栏C语言数据结构 博主ID&#xff1a;代码小豪 文章目录 归并排序两个有序数组的合并归并归并排序 归并排序的代码 归并排序 两个有序数组的合并 当前有两个有序数组arr1和arr2&#xff0c;我们创建一个可以容纳arr1和arr2同等元素个数的新数组arr。 让一个…

AGILEFORMER:用于医学图像分割的空间敏捷 Transformer UNET

AGILEFORMER&#xff1a;用于医学图像分割的空间敏捷 Transformer UNET 摘要IntroductionMethodDeformable Patch Embedding2.1.1 Rigid patch embedding2.1.2 Deformable patch embedding Spatially Dynamic Self-AttentionDeformable Multi-head Self-Attention (DMSA)Neighb…