Verilog代码实例

devtools/2025/2/8 22:05:50/

Verilog语言学习!

 

文章目录

目录

文章目录

前言

一、基本逻辑门代码设计和仿真

1.1 反相器

1.2 与非门

1.3 四位与非门

二、组合逻辑代码设计和仿真

2.1 二选一逻辑

2.2 case语句实现多路选择逻辑

2.3 补码转换

2.4 7段数码管译码器

三、时序逻辑代码设计和仿真

3.1 计数器

3.2 4级伪随机码发生器

3.3 秒计数器,0~9循环

3.4 相邻点累加

四、状态机代码设计和仿真

4.1 三角波发生器

4.2 梯形波发生器

4.3 串口数据接收

4.4 串口数据发送

4.5 串口指令处理器

总结


前言

        本文主要介绍了Verilog代码的基础内容和应用实例。


一、基本逻辑门代码设计和仿真

 

1.1 反相器

代码示例:

//反相器设计
module inv( A,Y);
//inv是模块名,A、Y是端口。//定义端口属性
input       A;
output      Y;//定义输入输出关系
assign      Y=~A;endmodule
//testbench of inv
`timescale 1ns/10ps
module inv_tb;
//因为inv_tb没有端口,所以不写括号。//因为反向器的输入端要有变动,所以定义为reg型变量;反相器的输出定义为wire型变量。
reg          aa;
wire         yy;//反相器例化(异名例化)
inv inv( .A(aa),.Y(yy));//让输入端aa动来观察yy
//initial语句块可以按时间定义各个变量的值
initial beginaa<=0;#10     aa<=1;#10     aa<=0;#10     aa<=1;    #10     $stop;//停止
end 
//仿真停止是用Verilog的系统任务$stop,#10表示过10个时间单位。            
endmodule

波形图:

 

1.2 与非门

 

 

代码示例:

//与非门
module nand_gate(A,B,Y);
input           A;
input           B;
output          Y;//由于是组合逻辑,所以可以用assign语句
assign          Y=~(A&B);endmodule
//testbench of nand_gate
`timescale 1ns/10psmodule nand_gate_tb;//输入都定义为reg型变量
reg                aa;
reg                bb;
wire               yy;nand_gate nand_gate(.A(aa),.B(bb),.Y(yy));initial beginaa<=0;  bb<=0;           #10   aa<=0;  bb<=1;               #10   aa<=1;  bb<=0;               #10   aa<=1;  bb<=1;               #10   $stop;               
end                   
//reg型变量赋值的时候一定用 <=endmodule

波形图:

 

1.3 四位与非门

代码示例:

//与非门
module nand_gate_4bits(A,B,Y);
input   [3:0]         A;
input   [3:0]         B;
output  [3:0]         Y;//由于是组合逻辑,所以可以用assign语句
assign          Y=~(A&B);endmodule
//testbench of nand_gate
`timescale 1ns/10psmodule nand_gate_4bits_tb;//输入都定义为reg型变量
reg     [3:0]                  aa;
reg     [3:0]                  bb;
wire    [3:0]                  yy;nand_gate_4bits nand_gate_4bits(.A(aa),.B(bb),.Y(yy));initial beginaa<=4'b0000;  bb<=4'b1111;           #10   aa<=4'b0010;  bb<=4'b0110;               #10   aa<=4'b0111;  bb<=4'b0100;              #10   aa<=4'b0000;  bb<=4'b1110;             #10   $stop;               
end                   
//reg型变量赋值的时候一定用 <=endmodule

波形图:

 

总结

表中运算符叫位运算符,表示按位进行逻辑运算,位宽为1只是它们的特例。

 

8位反相器和4位与非门的简单画法。

 

 

 

 

二、组合逻辑代码设计和仿真

        多路选择器逻辑设计。

2.1 二选一逻辑

模块 fn_sw 功能:

  • 当 sel 为 0 时,y 是 a 和 b 的与;
  • 当 sel 为 1 时,y 是 a 和 b 的异或;

 

代码示例1:

//二选一逻辑设计
//模块fn_sw功能:当sel为0时,y是a和b的与;当sel为1时,y是a和b的异或。
module fn_sw(a,b,sel,y);input        a;
input        b;
input        sel;
output       y;assign       y=sel?(a^b):(a&b);
//这里使用了问号冒号语句,其含义是:如果sel为1,则y等于(a^b),否则y等于(a&b)
endmodule
//testbench of fn_sw
`timescale 1ns/10ps
module fn_sw_tb;
reg         a;
reg         b;
reg         sel;
wire        y;fn_sw fn_sw(.a   (a),.b   (b),.sel (sel),.y   (y));initial begina<=0; b<=0; sel<=0;#10     a<=0; b<=0; sel<=1;#10     a<=0; b<=1; sel<=0;#10     a<=0; b<=1; sel<=1;#10     a<=1; b<=0; sel<=0;#10     a<=1; b<=0; sel<=1;#10     a<=1; b<=1; sel<=0;#10     a<=1; b<=1; sel<=1;#10     $stop;    end endmodule

代码示例2:

//二选一逻辑设计
//模块fn_sw功能:当sel为0时,y是a和b的与;当sel为1时,y是a和b的异或。
module fn_sw(a,b,sel,y);input        a;
input        b;
input        sel;
output       y;//用always语句块实现组合逻辑
reg          y;//always语句块里赋值的变量需要是reg型always @(a or b or sel) begin//括号里为敏感变量,组合逻辑输入,敏感变量一定要写全。if(sel==1)beginy<=a^b;//reg类型的变量赋值用带箭头的等号 <= end else beginy<=a&b;end 
end endmodule
//testbench of fn_sw
`timescale 1ns/10ps
module fn_sw_tb;
reg         a;
reg         b;
reg         sel;
wire        y;fn_sw fn_sw(.a   (a),.b   (b),.sel (sel),.y   (y));initial begina<=0; b<=0; sel<=0;#10     a<=0; b<=0; sel<=1;#10     a<=0; b<=1; sel<=0;#10     a<=0; b<=1; sel<=1;#10     a<=1; b<=0; sel<=0;#10     a<=1; b<=0; sel<=1;#10     a<=1; b<=1; sel<=0;#10     a<=1; b<=1; sel<=1;#10     $stop;    end endmodule

波形图:

 

2.2 case语句实现多路选择逻辑

模块 fn_sw 功能:

  • 当 sel 为 00 时,y 是 a 和 b 的与;
  • 当 sel 为 01 时,y 是 a 和 b 的或;
  • 当 sel 为 10 时,y 是 a 和 b 的异或;
  • 当 sel 为 11 时,y 是 a 和 b 的同或;

 

代码示例:

//四选一逻辑
module fn_sw_4(a,b,sel,y);input          a;
input          b;
input  [1:0]   sel;
output         y;reg            y;
always@(a or b or sel)begincase(sel)2'b00: begin y<=a&b;    end2'b01: begin y<=a|b;    end2'b10: begin y<=a^b;    end2'b11: begin y<=~(a^b); endendcase
end endmodule
//testbench of fn_sw_4
`timescale 1ns/10ps
module fn_sw_4_tb;reg [3:0]       absel;
wire            y;fn_sw_4 fn_sw_4(.a  (absel[0]),.b  (absel[1]),.sel(absel[3:2]),.y  (y));initial beginabsel<=0;#200    $stop;
endalways #10 absel<=absel+1;endmodule

波形图:

 

2.3 补码转换

补码转换逻辑:

  • 正数补码与原码相同。
  • 负数补码转换方法是符号位不变,幅度值按位取反加1.

 

代码示例:

//补码转换逻辑
//正数补码与原码相同
//负数补码转换方法是符号位不变,幅度值按位取反加1.
module comp_conv(a,a_comp);input  [7:0]     a;
output [7:0]     a_comp;wire   [6:0]     b;//按位取反的幅度位(原码的低7位按位取反得到)
wire   [7:0]     y;//负数的补码assign           b=~a[6:0];//原码的低7位按位取反
assign           y[6:0]=b+1;//按位取反+1   y[6:0]=~a[6:0]+1;//若不定义b,就可以简化为这一句代码
assign           y[7]=a[7];//符号位不变    y={a[7],~a[6:0]+1};//y的值可以简化为这一句代码assign           a_comp=a[7]?y:a;//二选一
//若原码的符号位(最高位)是1,那原码就是负数,补码就是y;
//若原码的符号位(最高位)是0,那原码就是正数,补码就是它自己;//assign           a_comp=a[7]?{a[7],~a[6:0]+1}:a;//最终可以不定义y和b,所有assign语句可简化为这一句代码。endmodule
//testbench of comp_conv
`timescale 1ns/10ps
module comp_conv_tb;reg   [7:0]         a_in; //原码的输入
wire  [7:0]         y_out;//输出结果comp_conv comp_conv(.a     (a_in) ,.a_comp(y_out));initial begina_in<=0;#3000   $stop;
end always #10 a_in<=a_in+1;endmodule

波形图:

 

 

2.4 7段数码管译码器

        图中数码管是由a、b、c、d、e、f、g共7截杠组成。每一截杠按特定的亮灭可以组成数字。

        数字电路一般只会只会按二进制产生数据,二进制数据让对应的数码管点亮还需要一定的逻辑来控制。

代码示例:

//七段码译码器
module seg_dec(num,a_g);input  [3:0]   num;
output [6:0]   a_g;//a_g-->{a,b,c,d,e,f,g}reg    [6:0]   a_g;//由于要用always语句赋值,所以要定义为reg型变量,虽然定义了reg型,但仍然是组合逻辑always@(num)begincase(num)4'd0:    begin a_g<=7'b111_1110; end 4'd1:    begin a_g<=7'b011_0000; end 4'd2:    begin a_g<=7'b110_1101; end 4'd3:    begin a_g<=7'b111_1001; end 4'd4:    begin a_g<=7'b011_0011; end 4'd5:    begin a_g<=7'b101_1011; end 4'd6:    begin a_g<=7'b101_1111; end  4'd7:    begin a_g<=7'b111_0000; end 4'd8:    begin a_g<=7'b111_1111; end 4'd9:    begin a_g<=7'b111_1011; end default: begin a_g<=7'b000_0001; end  //中杠,num超出0~9时,用default来统一处理,显示为中杠。endcase
endendmodule
//testbench of seg_dec
`timescale 1ns/10ps
module seg_dec_tb;reg   [3:0]     n_in;
wire  [6:0]     a_out;seg_dec seg_dec(.num(n_in),.a_g(a_out));initial beginn_in<=0;#120    $stop;
endalways #10 n_in<=n_in+1;endmodule

波形图:

 

三、时序逻辑代码设计和仿真

3.1 计数器

        电路图中,右边是一个触发器,左边是一个加法器,做加1运算。
        组合逻辑+触发器=时序逻辑。像图中电路中由组合逻辑加触发器构成的逻辑电路就叫它时序逻辑电路。

代码示例:

//计数器
module counter(clk,res,y);input          clk;
input          res;
output [7:0]   y;reg   [7:0]    y;//y是触发器,要定义为reg型变量。y虽然是输出,但是要在always里对它进行赋值,要做reg型变量的定义。wire  [7:0]    sum;//+1运算的结果。assign赋值,wire型变量assign         sum=y+1;//组合逻辑部分always@(posedge clk or negedge res)//敏感变量为时钟的上升沿和复位信号的下降沿,这是时序逻辑电路的特点if(~res)begin//触发器复位时y<=0;//res为低时 y寄存器复位end else begin //时钟触发,也就是触发器正常工作时y<=sum;//y得到sum值//y<=y+1;//可以不定义sum,也不用assign语句写组合逻辑部分,直接用这一个语句来实现逻辑。end 
//if里面是触发器复位时的动作,else是触发器正常工作时的动作
//always块在只有见到时钟的上升沿或者复位信号的下降沿的时候,always块里的逻辑才会执行
//只要定义了一个寄存器,也就是reg型变量,always的敏感列表里又是时钟和复位的话,在实际电路当中一定会对应一个触发器。复位的时候一定要对寄存器赋值。endmodule
//testbench of counter
`timescale 1ns/10ps
module counter_tb;//reg型是要变动的;wire型是要看的输出结果
reg             clk;
reg             res;
wire  [7:0]     y;counter counter(.clk(clk),.res(res),.y  (y));
//用initial语句给clk和res赋初值为0,过几个时间单位拉高res复位信号                
initial beginclk<=0;res<=0;#17     res<=1;#6000   $stop;
end//用always语句让clk时钟信号每隔5ns高低电平翻转一次,也就是10ns一个时钟周期
always #5 clk<=~clk;endmodule

波形图:

 

3.2 4级伪随机码发生器

        4级伪随机码发生器的电路是由4个触发器构成的,4个触发器理论上最多是有16个状态,但是从电路图的结构也可以看出4个触发器是形成了一串,时钟信号一来数据是从左向右移动的,第一个数据,也就是D3的数据来自于D3和D0输出的模2加。

        数据不能出现全0的状态,全0的话模2加之后还是0,所以这种状态下最大理论重复周期是15,就是在16个状态里把全0排除。即2^4-1=15

        可以得到最大理论重复周期的这种电路被称为伪随机码发生器

        GPS为10级伪随机码(C/A码),BDS北斗为11级。

代码示例:

//4级伪随机码发生器
module m_gen(clk,res,y);
input        clk;
input        res;
output       y;reg [3:0]    d;//定义4位寄存器,也就是4个触发器
assign       y=d[0];//输出连接,输出是y,让y接给d[0]always@(posedge clk or negedge res)
if(~res)begind<=4'b1111;//不能复位成全0
end
else begin d[2:0]<=d[3:1];//右移一位,寄存器d的高三位给低三位。(不推荐使用双箭头 >> 写右移)d[3]<=d[3]+d[0];//模二加,自动舍去进位。d[3]+d[0]的值给d[3],d[3]+d[0]的值本来应该是2位,给d[3]只能存1位的值,高位自动溢出,只存下低位,数值结果上就相当于模2
end endmodule
//testbench of m_gen
`timescale 1ns/10ps
module m_gen_tb;reg         clk;        
reg         res;
wire        y;m_gen m_gen(.clk(clk),.res(res),.y  (y  ));initial beginclk<=0;res<=0;#17     res<=1;#600   $stop;
end             always #5 clk<=~clk;endmodule 

波形图:

 

3.3 秒计数器,0~9循环

        把系统时钟clk进行分频,得到秒脉冲,然后对秒脉冲计数,这样就得到了一个秒计数器。

        假设系统时钟clk是24MHz,秒分频产生秒脉冲s_pulse;

        秒计数模块对秒脉冲计数,计数范围是0~9,计数结果为s_num(位宽4)。

设计波形图:

        设一个计数器,计数器基于系统时钟计数,计数范围是0~(24000000-1),计数器每循环一圈就是1s

        设一个寄存器也就是触发器,每次遇到计数器为0以后,寄存器就置1;计数器非0时,寄存器置0.这样就能得到一个秒脉冲尖。

        对秒脉冲尖进行计数,秒脉冲的寄存器一置1,秒计数就加1,一直加到9。到9之后遇到的下一次秒脉冲就将秒计数清0.

代码示例:

module s_counter(clk,res,s_sum);input            clk  ; 
input            res  ;
output [3:0]     s_sum;parameter        frequency_clk = 24;//24MHzreg    [24:0]    con_t;//秒脉冲分频计数器,con_t位数计算:如代码中最高支持24MHz系统频率,24000000对应二进制是25位数。
reg              s_pulse;//秒脉冲尖 
reg    [3:0]     s_sum;//秒脉冲尖计数器//秒脉冲分频计数器con_t赋值
always@(posedge clk or negedge res)if(~res)begin//复位时con_t<=0;//秒脉冲分频计数器为0s_pulse<=0;//秒脉冲尖的寄存器为0s_sum<=0;//秒脉冲尖计数器为0end else beginif(con_t==frequency_clk*1000000-1)begin//计数到最大范围值时con_t<=0;//秒脉冲分频计数器清0endelse begin//还没计数到最大值时con_t<=con_t+1;//秒脉冲分频计数器累加1endif(con_t==0)begin//秒脉冲分频计数器为0时s_pulse<=1;//秒脉冲尖的寄存器置1end else begin //秒脉冲分频计数器不为0时s_pulse<=0;//秒脉冲尖的寄存器置0end if(s_pulse)begin//当有秒脉冲尖s_pulse时,也就是s_pulse为1时,秒脉冲尖计数器s_sum进行0~9的循环计数if(s_sum==9)begin//秒脉冲尖计数器计到9时清零s_sum<=0;endelse begins_sum<=s_sum+1;//秒脉冲尖计数器自增1end     endend endmodule
//testbench of s_counter
`timescale 1ns/10ps
module s_counter_tb;reg                 clk;
reg                 res;
wire  [3:0]         s_sum; s_counter s_counter(.clk  (clk),.res  (res),.s_sum(s_sum));initial beginclk<=0;res<=0;#17     res<=1;#1000   $stop;
end always #5 clk<=~clk;endmodule

波形图:

 

3.4 相邻点累加

 

        相邻16点累加


电路结构图:

        data_in是采样信号,syn_in是采样时钟。

        data_in在syn_in上升沿变化,syn_in采样时钟的频率比系统时钟频率低很多,对相邻16点相加得到data_out,并由syn_out同步,syn_out为一个系统时钟周期宽度的脉冲。

        输入信号data_in为8位带符号位的原码,输出data_out为补码。


以上电路图对应的工作逻辑:

        首先对输入采样信号求补码,然后再对信号进行升位,升位之后在进行16点的相加,最后输出。

        为了保证相加的节奏和采样时钟的节奏相同,就需要对采样时钟的上升沿进行识别,同时为了得到输出同步脉冲,还需要对采样时钟进行16分频。


波形图设计:

 

采样时钟信号和采样时钟延时信号相与就得到采样脉冲尖,采样脉冲尖和采样数据同时变化。对采样脉冲尖进行0~15的循环计数也就是脉冲尖循环计数信号,用来控制累加的节奏,每当计数器等于15的时候就把累加结果传给data_out并产生一个累加和同步脉冲。

 

代码示例:

//相邻16点相加
module sigma_16p(clk     ,res     ,data_in ,syn_in  ,data_out,syn_out );input            clk      ;
input            res      ;
input  [7:0]     data_in  ; //采样信号
input            syn_in   ; //采样时钟
output [11:0]    data_out ; //累加结果输出
output           syn_out  ; //累加结果同步脉冲reg              syn_in_n1; //syn_in的反向延时,延时一拍就是用寄存器,采样时钟延时信号延时一拍,也就是一个系统时钟的周期,所以会定义为reg型的变量,在aways语句中赋值时,会比采样时钟迟一个系统时钟的周期。
wire             syn_pulse; //采样时钟上升沿识别脉冲(采样脉冲尖)
reg    [3:0]     con_syn  ; //采样时钟循环计数器
wire   [7:0]     comp_8   ; //补码
wire   [11:0]    d_12     ; //升位结果,12位是因为要在8位的补码再上升4位,所以提前定义好12位
reg    [11:0]    sigma    ; //累加计算
reg    [11:0]    data_out ; //累加和
reg              syn_out  ; //累加和同步脉冲//从输入到补码再到升位的过程都是组合逻辑,因此comp_8和d_12都定义为wire
assign           syn_pulse = syn_in & syn_in_n1;//采样脉冲尖 由 采样时钟信号 和 采样时钟延时信号 相与 得到
assign           comp_8 = data_in[7]?{data_in[7],~data_in[6:0]+1}:data_in;//补码运算,{符号位不变,幅度位按位取反加一}
//补码运算:data_in的最高位为1,表示负数,最高位符号位不变,其它位按位取反加一;data_in的最高位为0,就是正数,补码与原码相同。
assign           d_12 = {comp_8[7],comp_8[7],comp_8[7],comp_8[7],comp_8};//升位,直接用符号位扩展。always @(posedge clk or negedge res)
if(~res)begin   //寄存器复位syn_in_n1 <= 0;con_syn <= 0;sigma <= 0;data_out <= 0;syn_out <= 0;
end 
else beginsyn_in_n1 <= ~syn_in;//输入时钟的反向延时,因为syn_in_n1为reg型,在aways语句中赋值时会延时一个时钟周期,syn_in_n1就是syn_in反向的一个系统时钟clk周期的延时。if(syn_pulse)begin //在采样脉冲尖来的时候,因为采样时钟循环计数器计数是基于采样脉冲尖syn_pulse而不是基于系统时钟clk,所以需要加采样脉冲尖等于1时的条件。con_syn <= con_syn + 1; //采样时钟循环计数器加1,由于是16个点计数的反复循环,而con_syn又定义了4位,就形成了天然的16点循环计数。end //相邻16点相加://当con_syn累加到16点时(也就是从0加到15),需要把sigma的结果送出去给data_out。同时新一轮的第一个点d_12来时,要把第一个点d_12作为sigma的初始值。//当con_syn还没累加到16点时,sigma累加升位结果。if(syn_pulse)begin //在采样脉冲尖来的时候if(con_syn == 15)begin //在累加到16点时sigma <= d_12;//sigma累加计算的值是新一轮相邻16点的第一个点的值。data_out <= sigma;//送出上一轮16点累加计算的值给data_outsyn_out <= 1;//产生累加和同步脉冲end else begin //con_syn还没累加到15时sigma <= sigma + d_12;//升位结果的累加计算end  end else beginsyn_out <= 0; //相邻16点没有累加到16点的情况下,不产生累加和同步脉冲。这样可以在波形中保证syn_out累加和同步脉冲只冒一个尖。end end endmodule

 data_in = 1 时:

//testbench of sigma_16p
`timescale 1ns/10ps
module tb_sigma_16p;reg                 clk     ;
reg                 res     ;
reg   [7:0]         data_in ;
reg                 syn_in  ; 
wire  [11:0]        data_out;
wire                syn_out ;sigma_16p sigma_16p(.clk     (clk     ),.res     (res     ),.data_in (data_in ),.syn_in  (syn_in  ),.data_out(data_out),.syn_out (syn_out ));initial beginclk <= 0; res <= 0; data_in = 1; syn_in <= 0;#17     res <= 1;#500   $stop;
endalways #5 clk <= ~clk;always #100 syn_in <= ~syn_in;endmodule 

波形图:

 

 data_in = -1 时:

//testbench of sigma_16p
`timescale 1ns/10ps
module tb_sigma_16p;reg                 clk     ;
reg                 res     ;
reg   [7:0]         data_in ;
reg                 syn_in  ; 
wire  [11:0]        data_out;
wire                syn_out ;sigma_16p sigma_16p(.clk     (clk     ),.res     (res     ),.data_in (data_in ),.syn_in  (syn_in  ),.data_out(data_out),.syn_out (syn_out ));initial beginclk <= 0; res <= 0; data_in=8'b1000_0001; syn_in <= 0;//data_in=8'b1000_0001;#17     res <= 1;#500   $stop;
endalways #5 clk <= ~clk;always #100 syn_in <= ~syn_in;endmodule 

波形图:

 

四、状态机代码设计和仿真

4.1 三角波发生器

 

        设计三角波里向上的波为0状态,向下的波为1状态,这样就组成了两个状态的状态机。

        要组成一段三角波就需要从0状态跳到1状态,然后从1状态跳到0状态,循环跳跃0-1-0-1-......就形成三角波的向上波-向下波-向上波-向下波-......

  • 0状态的任务是波形计数器加1,越加越多,加到299时就不加了,跳转到1状态;
  • 1状态的任务是波形计数器减1,越减越少,减到1时就不减了,跳转到0状态;

代码示例:

//最简单的状态机,三角波发生器
module tri_gen(clk,res,d_out);input          clk   ;
input          res   ;
output [8:0]   d_out ;reg            state ; //主状态机的寄存器
reg    [8:0]   d_out ; //波形计数器,因为输出d_out也会在always里赋值,所以也定义为寄存器always @(posedge clk or negedge res)
if(~res)begin //复位时寄存器清零state<=0;d_out<=0;
end
else begincase(state)0://0状态时,d_out自增,三角波的上升部分begind_out<=d_out+1;if(d_out==299)beginstate<=1;end end 1://1状态时,d_out自减,三角波的下降部分begind_out<=d_out-1;if(d_out==1)beginstate<=0;endendendcase
end 
//这里0状态时d_out每见到时钟上升沿就会加1,当d_out=299的时候,状态就由0跳到1,在状态跳变的过程中,d_out的加1也完成了,所以真正到1状态时,d_out是以300的值进1状态来的。
//同理1状态时d_out一直在减1,当d_out=1的时候,状态就由1跳到0,在状态跳变的过程中,d_out的减1也完成了,所以跳到0状态时d_out是0.
//状态机就是一个case语句,在case语句里有0状态和1状态,每个状态里有执行的动作和状态跳转。
endmodule

注:

        这里0状态时d_out每见到时钟上升沿就会加1,当d_out=299的时候,状态就由0跳到1,在状态跳变的过程中,d_out的加1也完成了,所以真正到1状态时,d_out是以300的值进1状态来的。

        同理1状态时d_out一直在减1,当d_out=1的时候,状态就由1跳到0,在状态跳变的过程中,d_out的减1也完成了,所以跳到0状态时d_out是0.

        状态机就是一个case语句,在case语句里有0状态和1状态,每个状态里有执行的动作和状态跳转。

//testbench of tri_gen
`timescale 1ns/10ps
module tb_tri_gen;reg          clk   ;
reg          res   ;
wire [8:0]   d_out ;tri_gen tri_gen(.clk  (clk  ),.res  (res  ),.d_out(d_out));initial beginclk<=0;res<=0;#17   res<=1;#8000 $stop;
end               always #5 clk<=~clk;endmodule

波形图:

4.2 梯形波发生器

 

        设计梯形波里向上的波为0状态,平顶为1状态,向下的波为2状态,这样就组成了三个状态的状态机。

        要组成一段梯形波就需要从0状态跳到1状态,再从1状态跳到2状态,循环跳跃0-1-2-0-1-2-......就形成梯形波的向上波-平顶-向下波-向上波-平顶-向下波......

  • 0状态的任务是波形计数器加1,越加越多,加到299时就不加了,跳转到1状态;
  • 1状态的任务是波形计数器保持原值,一段时钟周期后,跳转到2状态;
  • 2状态的任务是波形计数器减1,越减越少,减到1时就不减了,跳转到0状态;

代码示例:

//状态机,梯形波发生器
module trape_gen(clk,res,d_out);input          clk   ;
input          res   ;
output [8:0]   d_out ;reg [1:0]      state;//主状态机的寄存器
reg [8:0]      d_out;//波形计数器,因为输出d_out也会在always里赋值,所以也定义为寄存器
reg [7:0]      con  ;//记录平顶周期个数计数器always @(posedge clk or negedge res)
if(~res)begin //复位时寄存器清零state <= 0;d_out <= 0;con   <= 0;
end
else begincase(state)0://0状态时,d_out自增,梯形波的上升部分begind_out<=d_out+1;if(d_out==299)beginstate<=1;end end 1://1状态时,d_out保持原值,梯形波的平顶部分beginif(con==200)beginstate<=2;con<=0;end else begincon<=con+1;end end 2://2状态时,d_out自减,梯形波的下降部分begind_out<=d_out-1;if(d_out==1)beginstate<=0;endenddefault://状态机最后最好使用default来控制没有列出的状态执行的动作,在此代码中就是状态3,因为state定义为两位,就是0,1,2,3四个数,这里状态3并没有设计执行动作,最后统一用default来控制没有设计执行动作的状态。begin    state<=0;con<=0;d_out<=0;endendcase
end 
//这里0状态时d_out每见到时钟上升沿就会加1,当d_out=299的时候,状态就由0跳到1,在状态跳变的过程中,d_out的加1也完成了,所以真正到1状态时,d_out是以300的值进1状态来的。
//同理1状态时d_out一直在减1,当d_out=1的时候,状态就由1跳到0,在状态跳变的过程中,d_out的减1也完成了,所以跳到0状态时d_out是0.
//状态机就是一个case语句,在case语句里有0状态和1状态,每个状态里有执行的动作和状态跳转。
endmodule

注:

        这里0状态时d_out每见到时钟上升沿就会加1,当d_out=299的时候,状态就由0跳到1,在状态跳变的过程中,d_out的加1也完成了,所以真正到1状态时,d_out是以300的值进1状态来的。

        同理1状态时d_out一直在减1,当d_out=1的时候,状态就由1跳到0,在状态跳变的过程中,d_out的减1也完成了,所以跳到0状态时d_out是0.

        状态机就是一个case语句,在case语句里有0状态和1状态,每个状态里有执行的动作和状态跳转。

//testbench of trape_gen
`timescale 1ns/10ps
module tb_trape_gen;reg          clk   ;
reg          res   ;
wire [8:0]   d_out ;trape_gen trape_gen(.clk  (clk  ),.res  (res  ),.d_out(d_out));initial beginclk<=0;res<=0;#17   res<=1;#8000 $stop;
end               always #5 clk<=~clk;endmodule

波形图:

 

4.3 串口数据接收

串口时序图:

  • 串口发送端口空闲时为高;
  • 发送端口拉低表示数据传送即将开始;
  • 字节数据低位先发;
  • 字节发送后拉高,表示字节传送结束;
  • 字节位宽可以不为8;
  • 常用的波特率有4800、9600、115200等

串口接收模块:

  • RX为串口输入
  • data_out为接收到的串口字节(8位);
  • 每接受完成一个字节,en_data_out就产生一个同步脉冲;
  • 用户见到en_data_out即可收数;
  • 波特率为4800,系统时钟频率24MHz

状态规划:

  • 状态1:等空闲,空闲识别
  • 状态2:等起始位
  • 状态3:收b0
  • 状态4:收b1
  • 状态5:收b2
  • 状态6:收b3
  • 状态7:收b4
  • 状态8:收b5
  • 状态9:收b6
  • 状态10:收b7

收完之后再返回去等待起始位。


空闲时别:

        空闲时别:空闲期间一直为1,能连续收到15个1,就说明一定是在空闲状态。


起始位识别:

        起始位识别:找起始位的方法实际就是找空闲到起始位的下降沿,把这个下降沿RX做一个时钟周期的延时RX_delay,再把RX反向一下和RX_delay与就得到一个脉冲尖。以后一看到这个脉冲尖就认为起始位到了。


收数据方法:

        收数据方法:假设接受1位数据的宽度为Tbit,在每次收数据的时候把采数的点设计在每一位数据的中间能最可靠的收到准确数据,因此在识别到起始位时也就是空闲到起始位的下降沿这个位置时,再等到1.5Tbit时长后再去收一下RX数据,就在b0数据位的中间采到了第一个数据,接下来一个Tbit收下一个数据位b1,然后b2、b3、b4、b5、b6、b7,当收完b7以后,就表示这串数据收完了。

        收完数据以后,又会回到等起始位这个状态。空闲识别是每次复位只识别一次。


状态转换图:

状态转换图:

  • 首先是一个等空闲的状态,也就是RX长时间为1;
  • 然后就进入等起始位的状态,也就是下降沿识别起始位;
  • 再然后就开始收数据,从b0到b7,收满8bit后再跳转到等起始位的状态。等空闲只需一次,这个状态就是在复位的时候取一次。

代码示例:

//串口数据接收
module UART_RXer(clk,res,RX,data_out,en_data_out);
input            clk          ;
input            res          ;
input            RX           ;
output  [7:0]    data_out     ;//接收字节输出
output           en_data_out  ;//输出使能reg     [7:0]    data_out     ;
reg              en_data_out  ;//使能脉冲
reg     [7:0]    state        ;//主状态机,状态机位数定义时建议多放几位。
reg     [12:0]   con          ;//用于计算Tbit宽度,也就是一个数据位的时间有多少个系统时钟。con的位数计算:1个Tbit宽度:24000000(系统频率24MHz)/4800(波特率)=5000,那么1.5个Tbit宽度换算成二进制就给个13位。
reg     [3:0]    con_bits     ;//用于计算Tbit个数    reg              RX_delay     ;//RX的延时always@(posedge clk or negedge res)
if(~res)begin//一旦定义了寄存器就要马上记得给寄存器复位清零 state<=0;con<=0;con_bits<=0;RX_delay<=0;data_out<=0;en_data_out<=0;
end
else beginRX_delay<=RX;//开始工作就无条件将RX赋值给RX的延时case(state)0://等空闲beginif(con==5000-1)begin //con计完一圈数后,也就是1个Tbit的宽度后,con清零。清零后好进行第二圈的计数con<=0; endelse begin con <= con + 1;//con一直累加end if(con==0)begin //con_bits用于计算con的圈数,当con计完一圈数清零时,con_bits就累加if(RX)begin //con_bits的累加还需要加一个条件,就是在RX为1的时候。RX为0的情况表示空闲长度还不够时就有一个0,那就说明此时有可能正在一串数据的中间,而不是等空闲状态。此时需要给con_bits清零。这样就能保证con_bits累加了一个很大的数的话一定是连续的RX长时间都为1,一个0都没有。con_bits<=con_bits+1;end else begincon_bits<=0;end     end if(con_bits==12)begin//如果con_bits连续累加到12,可以肯定当前处于空闲状态。state<=1;end end1://等起始位beginen_data_out<=0;if(~RX&RX_delay)begin//等到起始位识别的脉冲尖,就状态跳出给收数据最低位。state<=2;endend2://收最低位b0;beginif(con==7500-1)begin //等con计数到1.5个Tbit就可以采数了,否则con继续累加con<=0;          //con清零data_out[0]<=RX; //从RX采到b0位的数state<=3;        //状态往下跳转end else begincon<=con+1;end end3://收最低位b1;beginif(con==5000-1)begin //等con计数到1个Tbit就可以采数了,否则con继续累加con<=0;          //con清零data_out[1]<=RX; //从RX采到b1位的数state<=4;        //状态往下跳转end else begincon<=con+1;end end4://收最低位b2;beginif(con==5000-1)begin //等con计数到1个Tbit就可以采数了,否则con继续累加con<=0;          //con清零data_out[2]<=RX; //从RX采到b2位的数state<=5;        //状态往下跳转end else begincon<=con+1;end end5://收最低位b3;beginif(con==5000-1)begin //等con计数到1个Tbit就可以采数了,否则con继续累加con<=0;          //con清零data_out[3]<=RX; //从RX采到b3位的数state<=6;        //状态往下跳转end else begincon<=con+1;end end6://收最低位b4;beginif(con==5000-1)begin //等con计数到1个Tbit就可以采数了,否则con继续累加con<=0;          //con清零data_out[4]<=RX; //从RX采到b4位的数state<=7;        //状态往下跳转end else begincon<=con+1;end end7://收最低位b5;beginif(con==5000-1)begin //等con计数到1个Tbit就可以采数了,否则con继续累加con<=0;          //con清零data_out[5]<=RX; //从RX采到b5位的数state<=8;        //状态往下跳转end else begincon<=con+1;end end8://收最低位b6;beginif(con==5000-1)begin //等con计数到1个Tbit就可以采数了,否则con继续累加con<=0;          //con清零data_out[6]<=RX; //从RX采到b6位的数state<=9;        //状态往下跳转end else begincon<=con+1;end end9://收最低位b7;beginif(con==5000-1)begin //等con计数到1个Tbit就可以采数了,否则con继续累加con<=0;          //con清零data_out[7]<=RX; //从RX采到b7位的数state<=10;       //状态往下跳转end else begincon<=con+1;end end 10://产生使能脉冲;数据收全后产生一个信号beginen_data_out<=1;//产生使能脉冲state<=1;//状态回到1状态end default://其他状态清零beginstate<=0;con<=0;con_bits<=0;en_data_out<=0;end endcase
endendmodule
`timescale 1ns/10ps//testbench of UART_RXermodule UART_RXer_tb;
reg                 clk         ;
reg                 res         ;
wire                RX          ;//因为把RX最低位作为发送端了,所以RX定义为wire
wire   [7:0]        data_out    ;
wire                en_data_out ;reg    [25:0]       RX_send     ;//定义寄存器里装有串口字节发送数据reg    [12:0]       con;//计算Tbit宽度assign RX=RX_send[0];//连接RX; 将RX_send最低位传给RX    UART_RXer UART_RXer(.clk         (clk        ),.res         (res        ),.RX          (RX         ),.data_out    (data_out   ),.en_data_out (en_data_out));initial beginclk<=0;res<=0;RX_send<={1'b1,8'haa,1'b0,16'hffff};con<=0;//RX_send触发器的数据连续右移,最开始先出很多1代表空闲状,然后发一个0,再把aa发出去,最后发一个结束位。#17      res<=1;#1000    $stop;        
end                     always #5 clk<=~clk;always @(posedge clk) beginif(con==5000-1)begin//con循环计数1个Tbit的宽度con<=0;end else begin con<=con+1;endif(con==0)begin//con计数到1个Tbit的宽度后,RX_send数据就右移RX_send[24:0]<=RX_send[25:1];//将高25位的值赋给低25位,依次赋值就可以使数据右移RX_send[25]<=RX_send[0];//最低位的值赋给最高位//这样RX_send就会反复地右移循环。end end endmodule 

波形图:

 

4.4 串口数据发送

串口发送模块:

  • TX为串口输出端口
  • rdy为空闲标志,字节发送到rdy为高
  • data_in为准备发送的字节
  • en_data_in为字节发送使能端口,高使能
  • 发送波特率4800,系统时钟频率24MHz

状态图:

        状态图:第一个状态等待发送使能就是en_data_in为1;第二个状态填充发送寄存器,位拼接发送寄存器中放入{结束位,数据,起始位};第三个状态发送寄存器右移,右移发送寄存器,最低位连接TX,右移结束就代表发送完毕,然后再等待发送使能,接受新数据继续发送。


代码示例:

//串口发送模块
module UART_TXer(clk,res,data_in,en_data_in,TX,rdy);input             clk         ; 
input             res         ;
input  [7:0]      data_in     ; //准备发送的数据
input             en_data_in  ; //发送使能
output            TX          ; 
output            rdy         ; //空闲标志,0表示空闲,1表示在发送数据,为1时不能灌数据。reg    [3:0]      state       ; //主状态机寄存器
reg    [9:0]      send_buf    ; //发送寄存器assign            TX = send_buf[0]; //连接TX,send_buf最低位连接给TX,send_buf里的数据要按节奏(4800波特率)右移就相当于串口发送。reg    [9:0]      send_flag; //用于判断右移结束reg    [12:0]     con; //用于计算波特周期
reg               rdy; //空闲标志,字节发送时rdy为高,表示正忙;rdy为0时表示空闲always@(posedge clk or negedge res)
if(~res)beginstate <= 0;send_buf <= 1; //send_buf最低位是接TX,所以最低位不能是0,最低位必须是1,因为TX在空闲的时候是1,所以要给send_buf值为1con <= 0;send_flag <= 10'b10_0000_0000;//rdy<=0;
end
else begincase(state)0://等待使能信号beginif(en_data_in)begin//当发送使能一来,给send_buf一个位拼接send_buf = {1'b1,data_in,1'b0};//在这个10位寄存器里面最低位应该是起始位0,最高位因该是结束位1,中间的8位才是需要的数据。send_flag <= 10'b10_0000_0000;rdy<=1;//一旦有发送数据rdy为高,表示正忙state<=1;//状态就可以跳走end end1://串口发送,寄存器右移;beginif(con == 5000-1)begin //con循环计数1个Tbit的宽度con<=0;end else begin con <= con + 1;end if(con == 5000-1)begin //con计数到1个Tbit的宽度后,send_buf数据就右移send_buf[8:0] <= send_buf[9:1]; //右移,将高9位的值赋给低9位,依次赋值就可以使数据右移send_flag[8:0] <= send_flag[9:1];//send_flag和send_buf一样一起右移,当send_flag里的数10'b10_0000_0000右移到最低位也为1时表示10位数据全部右移完,证明同时右移的send_buf里的10位数据也右移结束。end if(send_flag[0])begin//当send_flag的最低位为1时,就表示send_buf的右移可以结束了。rdy<=0;//数据一旦发送结束,变为空闲状态state<=0;//状态回到等待使能end endendcase 
end endmodule

       上述数据右移的方法为算术右移,空出高位补位为原来数据的符号位也就是:

  • 原数据最高位为1则右移后空出的最高位补1;
  • 原数据最高位为0则右移后空出的最高位补0.

 数据右移:

  • 10'b10_0000_0000 
  • 10'b11_0000_0000 
  • 10'b11_1000_0000 
  • 10'b11_1100_0000 
  • 10'b11_1110_0000 
  • 10'b11_1111_0000 
  • 10'b11_1111_1000
  • 10'b11_1111_1100
  • 10'b11_1111_1110
  • 10'b11_1111_1111

 

`timescale 1ns/10ps
//testbench of UART_TXer
module UART_TXer_tb;reg                  clk        ;
reg                  res        ;
reg    [7:0]         data_in    ;
reg                  en_data_in ;
wire                 TX         ;
wire                 rdy        ;UART_TXer UART_TXer(.clk        (clk       ),.res        (res       ),.data_in    (data_in   ),.en_data_in (en_data_in),.TX         (TX        ),.rdy        (rdy       ));initial beginclk<=0; res<=0; data_in<=8'h0a; en_data_in<=0;//data_in<=8'h7f;#17       res<=1;#30       en_data_in<=1;#10       en_data_in<=0;#1000     $stop;end                    always #5 clk<=~clk;                    endmodule                    

波形图:

发送数据 data_in 为 8'h0a

发送数据 data_in 为 8'h7f

 

 

4.5 串口指令处理器

结构图:

        串口指令处理器:将指令处理模块和串口发送模块、串口接收模块合在一起可以得到一个串口指令处理器。

  • 串口接收模块接收指令和数据,把接收到的指令和数据以并行输入的模式传给指令处理模块。
  • 指令处理模块对指令和数据进行处理得到的结果并行输出给串口发送模块。
  • 串口发送模块再将运算结果输出。

电路图:

  • RX为串口输入端口
  • TX为串口输出端口
  • UART_RXer为串口接收模块
  • UART_TXer为串口发送模块
  • cmd_pro为指令处理模块
  • 合成的UART_top为串口指令处理器

cmd_pro指令集格式:

        每次连续接收三个字节,第一字节为指令CMD,第二字节为操作数A,第三字节为操作数B。

指令集如下:

CMD操作
8'h0aA + B
8'h0bA - B
8'h0cA & B
8'h0dA | B

状态图:

        状态图:0状态接收指令和数据;1状态处理指令和数据;2状态返回指令执行结果;2状态结束后再回到0状态继续接收指令和数据。

代码示例:

UART_RXer串口接收模块为 4.3串口数据接收代码

//串口数据接收
module UART_RXer(
......

UART_TXer串口发送模块为 4.4串口数据发送代码

//串口发送模块
module UART_TXer(
......

cmd_pro指令处理模块:

//指令处理module cmd_pro(clk        ,res        ,din_pro    ,en_din_pro ,dout_pro   ,en_dout_pro,rdy);input           clk        ;
input           res        ;
input  [7:0]    din_pro    ; //指令和数据输入端口
input           en_din_pro ; //输入使能
output [7:0]    dout_pro   ; //指令执行结果
output          en_dout_pro; //指令输出使能
output          rdy        ; //串口发送模块空闲标志,0表示空闲parameter       add_ab = 8'h0a;
parameter       sub_ab = 8'h0b;
parameter       and_ab = 8'h0c;
parameter       or_ab  = 8'h0d;reg    [2:0]    state      ; //主状态机寄存器
reg    [7:0]    cmd_reg    ; //存放指令,第1个字节
reg    [7:0]    A_reg      ; //存放指令,第2个字节,操作数A
reg    [7:0]    B_reg      ; //存放指令,第3个字节,操作数B
reg    [7:0]    dout_pro   ; //指令执行结果
reg             en_dout_pro; //指令输出使能always@(posedge clk or negedge res)
if(~res)beginstate   <= 0 ;cmd_reg <= 0 ;A_reg   <= 0 ;B_reg   <= 0 ;dout_pro<= 0 ;en_dout_pro<= 0 ;
end
else begincase(state)0: //等指令beginen_dout_pro <= 0;//拉低指令输出使能if(en_din_pro)begin //输入使能一来,就说明串口接收模块已经收到完整的1个字节了cmd_reg <= din_pro;//收指令,收到的字节放入存放指令寄存器,第1个字节state <= 1;//状态跳转到状态1endend1: //收Abeginif(en_din_pro)begin A_reg <= din_pro;//收指令,收到的字节放入存放指令寄存器,第2个字节,操作数Astate <= 2;//状态跳转到状态2endend2: //收Bbeginif(en_din_pro)begin B_reg <= din_pro;//收指令,收到的字节放入存放指令寄存器,第3个字节,操作数Bstate <= 3;//状态跳转到状态3endend    3: //指令译码和执行beginstate <= 4;case(cmd_reg)add_ab: //A+Bbegindout_pro <= A_reg + B_reg ;end sub_ab: //A-Bbegindout_pro <= A_reg - B_reg ;end and_ab: //A&Bbegindout_pro <= A_reg & B_reg ;end or_ab: //A|Bbegindout_pro <= A_reg | B_reg ;end     endcaseend4: //发送指令执行结果begin if(~rdy)begin //表示空闲时en_dout_pro <= 1; //拉高指令输出使能,其目的是为了形成一个脉冲尖作为发送指令的信号。state <= 0;//状态跳转到状态0,在rdy为1的空闲状态就表示发送指令已经结束了,就该跳到等指令的状态等待新的指令endend default: beginstate <= 0;//回到0状态en_dout_pro <= 0;//拉低指令输出使能endendcase
endendmodule

UART_top串口指令处理器:

//串口指令处理器
module UART_top(clk ,res ,RX  ,TX);input           clk ;
input           res ;
input           RX  ;
output          TX  ;//需要定义5个中间信号,就以cmd_pro中的5个信号名为主,由于这些信号在UART_top里只是一个连接关系,所以这些信号都是wire型。
wire   [7:0]    din_pro    ;
wire            en_din_pro ;
wire   [7:0]    dout_pro   ;
wire            en_dout_pro;
wire            rdy        ;UART_RXer UART_RXer(.clk         (clk        ),.res         (res        ),.RX          (RX         ),.data_out    (din_pro    ),.en_data_out (en_din_pro ));UART_TXer UART_TXer(.clk        (clk        ),.res        (res        ),.data_in    (dout_pro   ),.en_data_in (en_dout_pro),.TX         (TX         ),.rdy        (rdy        ));cmd_pro cmd_pro(.clk         (clk        ),.res         (res        ),.din_pro     (din_pro    ),.en_din_pro  (en_din_pro ),.dout_pro    (dout_pro   ),.en_dout_pro (en_dout_pro),.rdy         (rdy        ));endmodule

注意:

        在做程序封装顶层的例化的时候,信号之间的连接不管对谁来说是输入还是输出,从顶层看都是纯连接关系就都是wire型。

        在测试文件testbench中例化的时候会需要设计输入信号数据变动的情况下会将这个变动的信号定义为reg型。

        在做程序顶层封装的文件的时候除了例化连接模块,不要有其他的逻辑语句在里面,顶层文件里是纯连接关系。

测试代码:

//testbench of UART_top
`timescale 1ns/10ps
module UART_top_tb;reg             clk ;
reg             res ;
wire            RX  ;//因为把RX最低位作为发送端了,所以RX定义为wire
wire            TX  ;reg [45:0]      RX_send; 定义寄存器里面装有串口字节发送数据
assign          RX = RX_send[0]; //连接RX; 将RX_send最低位传给RX reg [12:0]      con;//计算Tbit宽度UART_top UART_top(.clk (clk),.res (res),.RX  (RX ),.TX  (TX ));initial beginclk<=0;res<=0;RX_send<={1'b1,8'h09,1'b0,1'b1,8'h06,1'b0,1'b1,8'h0a,1'b0,16'hffff};con<=0;//RX_send触发器的数据连续右移,最开始先出很多1代表空闲状,发一个起始位0,再把第一个数据8'h0a发出去(控制指令A+B),然后发一个结束位;发一个起始位0,再把第二个数据8'h06发出去,然后发一个结束位;发一个起始位0,再把第三个数据8'h09发出去,最后发一个结束位。#17      res<=1;#4000000 $stop;  
endalways #5 clk<=~clk;always @(posedge clk) beginif(con==5000-1)begin//con循环计数1个Tbit的宽度con<=0;end else begin con<=con+1;endif(con==0)begin//con计数到1个Tbit的宽度后,RX_send数据就右移RX_send[44:0]<=RX_send[45:1];//将高45位的值赋给低45位,依次赋值就可以使数据右移RX_send[45]<=RX_send[0];//最低位的值赋给最高位//这样RX_send就会反复地右移循环。end end endmodule

波形图:

 

 


总结

        以上就是今天要讲的内容,本文仅仅简单介绍了Verilog代码的基础内容和应用实例。

 

 

 


http://www.ppmy.cn/devtools/157183.html

相关文章

PbootCMS 修改跳转提示,修改笑脸时间

在使用时&#xff0c;每次都提示这个&#xff1a; 修改方法&#xff1a; 修改跳转时间&#xff1a;找到 handle.php 文件编辑 &#xff0c;调整 setTimeout 函数的时间参数。 修改提示文字&#xff1a;编辑 handle.php 文件&#xff0c;修改提示文字的内容。 隐藏提示页面&am…

python的pre-commit库的使用

在软件开发过程中&#xff0c;保持代码的一致性和高质量是非常重要的。pre-commit 是一个强大的工具&#xff0c;它可以帮助我们在提交代码到版本控制系统&#xff08;如 Git&#xff09;之前自动运行一系列的代码检查和格式化操作。通过这种方式&#xff0c;我们可以确保每次提…

GPIO【ARM接口技术】

GPIO&#xff08;General-Purpose IO ports&#xff09; &#xff0c;也就是通用 IO 接口。在嵌入式系统中常常有数量众多&#xff0c;但是结构却比较简单的外部设备/电路&#xff0c;对这些设备/电路&#xff0c;有的需要 CPU 为之提供控制手段&#xff08;输出&#xff09;&a…

Pygame介绍与游戏开发

提供pygame功能介绍的文档&#xff1a;Pygame Front Page — pygame v2.6.0 documentation 基础语法和实现逻辑 与CLI不同&#xff0c;pygame提供了图形化使用界面GUI&#xff08;graphical user interface&#xff09;基于图像的界面可以创建一个有图像和颜色的窗口 要让py…

多光谱成像技术在华为Mate70系列的应用

华为Mate70系列搭载了光谱技术的产物——红枫原色摄像头&#xff0c;这是一款150万像素的多光谱摄像头。 相较于普通摄像头&#xff0c;它具有以下优势&#xff1a; 色彩还原度高&#xff1a;色彩还原准确度提升约 120%&#xff0c;能捕捉更多光谱信息&#xff0c;使拍摄照片色…

神经网络常见激活函数 4-LeakyReLU函数

LeakyReLU LeakyReLU&#xff1a; Leaky Rectified Linear Unit 函数导函数 LeakyReLU函数 L e a k y R e L U { x x > 0 p x x < 0 p ∈ ( 0 , 1 ) \rm Leaky{ReLU} \left\{ \begin{array}{} x \quad x>0 \\ px \quad x<0 \end{array} \right. \quad p \in …

DeepSeek让 Obsidian 更强大:Text generator与 Copilot 使用指南

DeepSeek让 Obsidian 更强大&#xff1a;Text generator与 Copilot 使用指南 Text generator 使用指南Copilot 使用指南 Obsidian作为新一代知识管理工具&#xff0c;凭借本地化存储、双链图谱和开放生态正在改变学习与创作方式&#xff0c;以下是结合DeepSeek大模型的Text Gen…

【实战篇】Idea 集成主流 AI 插件的方法实战

家人们,今天必须来给大家分享一个超实用的干货,那就是如何在 Idea 中集成各种主流 AI 插件!这简直就是提升咱们开发效率的超级神器,不管你是刚入门的编程小白,还是已经在代码世界里摸爬滚打好多年的资深程序员,掌握了这些方法,都能让你的代码编写过程变得轻松又高效,就…