Verilog代码实例

news/2025/2/9 6:17:31 标签: fpga开发

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 begin
            aa<=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/10ps

module nand_gate_tb;

//输入都定义为reg型变量
reg                aa;
reg                bb;
wire               yy;


nand_gate nand_gate(
                   .A(aa),
                   .B(bb),
                   .Y(yy)
                   );

initial begin
          aa<=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/10ps

module 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 begin
          aa<=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 begin
            a<=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)begin
        y<=a^b;//reg类型的变量赋值用带箭头的等号 <= 
    end 
    else begin
        y<=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 begin
            a<=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)begin
    case(sel)
    2'b00: begin y<=a&b;    end
    2'b01: begin y<=a|b;    end
    2'b10: begin y<=a^b;    end
    2'b11: begin y<=~(a^b); end
    
    endcase
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 begin
            absel<=0;
    #200    $stop;
end

always #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 begin
            a_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)begin
    case(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
end

endmodule
//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 begin
                n_in<=0;
        #120    $stop;
end

always #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 begin
            clk<=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)begin
    d<=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 begin
            clk<=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;//24MHz


reg    [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;//秒脉冲分频计数器为0
        s_pulse<=0;//秒脉冲尖的寄存器为0
        s_sum<=0;//秒脉冲尖计数器为0
    end 
    else begin
        if(con_t==frequency_clk*1000000-1)begin//计数到最大范围值时
            con_t<=0;//秒脉冲分频计数器清0
        end
        else begin//还没计数到最大值时
            con_t<=con_t+1;//秒脉冲分频计数器累加1
        end
        
        if(con_t==0)begin//秒脉冲分频计数器为0时
            s_pulse<=1;//秒脉冲尖的寄存器置1
        end 
        else begin //秒脉冲分频计数器不为0时
            s_pulse<=0;//秒脉冲尖的寄存器置0
        end 
        
        if(s_pulse)begin//当有秒脉冲尖s_pulse时,也就是s_pulse为1时,秒脉冲尖计数器s_sum进行0~9的循环计数
            if(s_sum==9)begin//秒脉冲尖计数器计到9时清零
                s_sum<=0;
            end
            else begin
                s_sum<=s_sum+1;//秒脉冲尖计数器自增1
            end     
        end
       
    end 
    
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 begin
            clk<=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 begin
    syn_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_out
            syn_out <= 1;//产生累加和同步脉冲
        end 
        else begin //con_syn还没累加到15时
            sigma <= sigma + d_12;//升位结果的累加计算
        end  
    end 
    else begin
        syn_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 begin
            clk <= 0; res <= 0; data_in = 1; syn_in <= 0;
    #17     res <= 1;
    #500   $stop;
end

always #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 begin
            clk <= 0; res <= 0; data_in=8'b1000_0001; syn_in <= 0;//data_in=8'b1000_0001;
    #17     res <= 1;
    #500   $stop;
end

always #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 begin
    case(state)
    0://0状态时,d_out自增,三角波的上升部分
    begin
        d_out<=d_out+1;
        if(d_out==299)begin
            state<=1;
        end 
    end 
    1://1状态时,d_out自减,三角波的下降部分
    begin
        d_out<=d_out-1;
        if(d_out==1)begin
            state<=0;
        end
    end
    endcase
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 begin
            clk<=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 begin
    case(state)
    0://0状态时,d_out自增,梯形波的上升部分
    begin
        d_out<=d_out+1;
        if(d_out==299)begin
            state<=1;
        end 
    end 
    1://1状态时,d_out保持原值,梯形波的平顶部分
    begin
        if(con==200)begin
            state<=2;
            con<=0;
        end 
        else begin
            con<=con+1;
        end 
    end 
    2://2状态时,d_out自减,梯形波的下降部分
    begin
        d_out<=d_out-1;
        if(d_out==1)begin
            state<=0;
        end
    end
    default://状态机最后最好使用default来控制没有列出的状态执行的动作,在此代码中就是状态3,因为state定义为两位,就是0,1,2,3四个数,这里状态3并没有设计执行动作,最后统一用default来控制没有设计执行动作的状态。
    begin    
        state<=0;
        con<=0;
        d_out<=0;
    end
    endcase
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 begin
            clk<=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 begin
    RX_delay<=RX;//开始工作就无条件将RX赋值给RX的延时
    case(state)
    0://等空闲
    begin
        if(con==5000-1)begin //con计完一圈数后,也就是1个Tbit的宽度后,con清零。清零后好进行第二圈的计数
            con<=0; 
        end
        else 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 begin
                con_bits<=0;
            end     
        end 
        if(con_bits==12)begin//如果con_bits连续累加到12,可以肯定当前处于空闲状态。
            state<=1;
        end 
        
    end
    1://等起始位
    begin
        en_data_out<=0;
        if(~RX&RX_delay)begin//等到起始位识别的脉冲尖,就状态跳出给收数据最低位。
            state<=2;
        end
    end
    2://收最低位b0;
    begin
        if(con==7500-1)begin //等con计数到1.5个Tbit就可以采数了,否则con继续累加
            con<=0;          //con清零
            data_out[0]<=RX; //从RX采到b0位的数
            state<=3;        //状态往下跳转
        end 
        else begin
            con<=con+1;
        end 
    end
    3://收最低位b1;
    begin
        if(con==5000-1)begin //等con计数到1个Tbit就可以采数了,否则con继续累加
            con<=0;          //con清零
            data_out[1]<=RX; //从RX采到b1位的数
            state<=4;        //状态往下跳转
        end 
        else begin
            con<=con+1;
        end 
    end
    4://收最低位b2;
    begin
        if(con==5000-1)begin //等con计数到1个Tbit就可以采数了,否则con继续累加
            con<=0;          //con清零
            data_out[2]<=RX; //从RX采到b2位的数
            state<=5;        //状态往下跳转
        end 
        else begin
            con<=con+1;
        end 
    end
    5://收最低位b3;
    begin
        if(con==5000-1)begin //等con计数到1个Tbit就可以采数了,否则con继续累加
            con<=0;          //con清零
            data_out[3]<=RX; //从RX采到b3位的数
            state<=6;        //状态往下跳转
        end 
        else begin
            con<=con+1;
        end 
    end
    6://收最低位b4;
    begin
        if(con==5000-1)begin //等con计数到1个Tbit就可以采数了,否则con继续累加
            con<=0;          //con清零
            data_out[4]<=RX; //从RX采到b4位的数
            state<=7;        //状态往下跳转
        end 
        else begin
            con<=con+1;
        end 
    end
    7://收最低位b5;
    begin
        if(con==5000-1)begin //等con计数到1个Tbit就可以采数了,否则con继续累加
            con<=0;          //con清零
            data_out[5]<=RX; //从RX采到b5位的数
            state<=8;        //状态往下跳转
        end 
        else begin
            con<=con+1;
        end 
    end
    8://收最低位b6;
    begin
        if(con==5000-1)begin //等con计数到1个Tbit就可以采数了,否则con继续累加
            con<=0;          //con清零
            data_out[6]<=RX; //从RX采到b6位的数
            state<=9;        //状态往下跳转
        end 
        else begin
            con<=con+1;
        end 
    end
    9://收最低位b7;
    begin
        if(con==5000-1)begin //等con计数到1个Tbit就可以采数了,否则con继续累加
            con<=0;          //con清零
            data_out[7]<=RX; //从RX采到b7位的数
            state<=10;       //状态往下跳转
        end 
        else begin
            con<=con+1;
        end 
    end 
    10://产生使能脉冲;数据收全后产生一个信号
    begin
        en_data_out<=1;//产生使能脉冲
        state<=1;//状态回到1状态
    end 
    default://其他状态清零
    begin
        state<=0;
        con<=0;
        con_bits<=0;
        en_data_out<=0;
    end 
    
    endcase
end



endmodule
`timescale 1ns/10ps

//testbench of UART_RXer


module 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 begin
                 clk<=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) begin
    if(con==5000-1)begin//con循环计数1个Tbit的宽度
        con<=0;
    end 
    else begin 
        con<=con+1;
    end

    if(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)begin
    state <= 0;
    send_buf <= 1; //send_buf最低位是接TX,所以最低位不能是0,最低位必须是1,因为TX在空闲的时候是1,所以要给send_buf值为1
    con <= 0;
    send_flag <= 10'b10_0000_0000;//
    rdy<=0;
end
else begin
    case(state)
    0://等待使能信号
    begin
        if(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 
    end
    1://串口发送,寄存器右移;
    begin
        if(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 
        
    end
    
    endcase 
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 begin
              clk<=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)begin
    state   <= 0 ;
    cmd_reg <= 0 ;
    A_reg   <= 0 ;
    B_reg   <= 0 ;
    dout_pro<= 0 ;
	en_dout_pro<= 0 ;
end
else begin
    case(state)
        0: //等指令
            begin
                en_dout_pro <= 0;//拉低指令输出使能
                if(en_din_pro)begin //输入使能一来,就说明串口接收模块已经收到完整的1个字节了
                    cmd_reg <= din_pro;//收指令,收到的字节放入存放指令寄存器,第1个字节
                    state <= 1;//状态跳转到状态1
                end
            end
        1: //收A
            begin
                if(en_din_pro)begin 
                    A_reg <= din_pro;//收指令,收到的字节放入存放指令寄存器,第2个字节,操作数A
                    state <= 2;//状态跳转到状态2
                end
            end
        2: //收B
            begin
                if(en_din_pro)begin 
                    B_reg <= din_pro;//收指令,收到的字节放入存放指令寄存器,第3个字节,操作数B
                    state <= 3;//状态跳转到状态3
                end
            end    
        3: //指令译码和执行
            begin
                state <= 4;
                case(cmd_reg)
                    add_ab: //A+B
                        begin
                            dout_pro <= A_reg + B_reg ;
                        end 
                    sub_ab: //A-B
                        begin
                            dout_pro <= A_reg - B_reg ;
                        end 
                    and_ab: //A&B
                        begin
                            dout_pro <= A_reg & B_reg ;
                        end 
                    or_ab: //A|B
                        begin
                            dout_pro <= A_reg | B_reg ;
                        end     
                    
                endcase
            end
        4: //发送指令执行结果
            begin 
                if(~rdy)begin //表示空闲时
                    en_dout_pro <= 1; //拉高指令输出使能,其目的是为了形成一个脉冲尖作为发送指令的信号。
                    state <= 0;//状态跳转到状态0,在rdy为1的空闲状态就表示发送指令已经结束了,就该跳到等指令的状态等待新的指令
                end
            end 
        default: 
            begin
                state <= 0;//回到0状态
                en_dout_pro <= 0;//拉低指令输出使能
            end
    endcase
end


endmodule

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 begin
                 clk<=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;  
end

always #5 clk<=~clk;

always @(posedge clk) begin
    if(con==5000-1)begin//con循环计数1个Tbit的宽度
        con<=0;
    end 
    else begin 
        con<=con+1;
    end

    if(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.niftyadmin.cn/n/5845685.html

相关文章

k8s常见面试题1

k8s常见面试题1 Kubernetes 基础知识核心组件及作用K8S生成pod过程Pod、Deployment、Service的区别保证Pod高可用ConfigMap vs Secret网络模型与Pod通信Ingress 与 Service 的主要区别什么是 Ingressk8s中为什么要做ServiceIngress 与 Service 的区别 资源配额 Kubernetes 实践…

Golang:精通sync/atomic 包的Atomic 操作

在本指南中&#xff0c;我们将探索sync/atomic包的细节&#xff0c;展示如何编写更安全、更高效的并发代码。无论你是经验丰富的Gopher还是刚刚起步&#xff0c;你都会发现有价值的见解来提升Go编程技能。让我们一起开启原子运算的力量吧&#xff01; 理解Go中的原子操作 在快…

25/2/8 <机器人基础> 阻抗控制

1. 什么是阻抗控制&#xff1f; 阻抗控制旨在通过调节机器人与环境的相互作用&#xff0c;控制其动态行为。阻抗可以理解为一个力和位移之间的关系&#xff0c;涉及力、速度和位置的协同控制。 2. 阻抗控制的基本概念 力控制&#xff1a;根据感测的外力调节机械手的动作。位置…

【RandLA-Net】大场景语义分割网络RandLA-Net复现

【RandLA-Net】大场景语义分割网络RandLA-Net复现 文章目录 【RandLA-Net】大场景语义分割网络RandLA-Net复现0. 相关文章1. 实验条件2. 代码3. 数据集4. 环境搭建5. 训练模型6. 测试模型7. 可视化8. 参考博客 0. 相关文章 PointNet模型搭建 基于自建数据训练PointNet分割网络…

雷蛇曼巴无线版更换微动后左键失灵后续——丝血复活

本文首发于只抄博客&#xff0c;欢迎点击原文链接了解更多内容。 前言 上一期《雷蛇曼巴无线版更换左右键、侧键、DPI 键微动翻车记录》中&#xff0c;把鼠标所有的按键都更换了一遍&#xff0c;结果原本唯一正常的左键换完之后无法触发了。 经过评论区大哥指点&#xff0c;猜…

【填坑】新能源汽车三电设计之常用半导体器件系统性介绍

# 在新能源汽车的三电&#xff08;电池、电机、电控&#xff09;系统中&#xff0c;半导体器件扮演着至关重要的角色。它们如同系统的“大脑”和“神经末梢”&#xff0c;精确地控制着电能的流向与转换&#xff0c;确保新能源汽车高效、稳定且安全地运行。今天&#xff0c;就让…

6.Python函数:函数定义、函数的类型、函数参数、函数返回值、函数嵌套、局部变量、全局变量、递归函数、匿名函数

1. 函数定义 Python函数通过def关键字定义。一个函数通常包括函数名、参数列表和函数体。 def greet(name):return f"Hello, {name}!"2. 函数的类型 Python中的函数主要有以下几种类型&#xff1a; 普通函数&#xff1a;具有明确的输入参数和返回值。递归函数&am…

深入解析:React 事件处理的秘密与高效实践

在 React 中&#xff0c;事件处理是构建交互式应用的核心。本文将带你深入探索 React 事件处理的机制、最佳实践以及如何避免常见陷阱&#xff0c;助你写出更高效、更健壮的代码。 1. React 事件处理的独特之处 合成事件&#xff08;SyntheticEvent&#xff09; React 使用合…