Verilog RTL 新手代码设计 (计数器、状态机、移位寄存器)

9.计数器

首先编写Verilog代码如下:

//////////////////// 计数器代码  /////////////////////////

module top(
  RST   , // 异步复位, 高有效
  CLK   , // 时钟,上升沿有效
  EN    , // 输入的计数使能,高有效
  CLR   , // 输入的清零信号,高有效
  LOAD  , // 输入的数据加载使能信号,高有效
  DATA  , // 输入的加载数据信号
  CNTVAL, // 输出的计数值信号
  OV    );// 计数溢出信号,计数值为最大值时该信号为1

input RST   , CLK   , EN    , CLR   , LOAD  ;                                                                                              
input [3:0] DATA ;
output [3:0] CNTVAL;
output OV;   

reg [3:0] CNTVAL, cnt_next;
reg OV;
// 电路编译参数,最大计数值
parameter CNT_MAX_VAL = 9;

// 组合逻辑,生成cnt_next
// 计数使能最优先,清零第二优先,加载第三优先
always @(EN or CLR or LOAD or DATA or CNTVAL) begin
  if(EN) begin    // 使能有效
    if(CLR) begin // 清零有效
      cnt_next = 0;
    end
    else begin  // 清零无效
      if(LOAD) begin // 加载有效
        cnt_next = DATA;
      end
      else begin     // 加载无效,正常计数
        // 使能有效,清零和加载都无效,根据当前计数值计算下一值
        if(CNTVAL < CNT_MAX_VAL) begin // 未计数到最大值, 下一值加1
          cnt_next = CNTVAL + 1'b1;
        end
        else begin // 计数到最大值,下一计数值为0
          cnt_next = 0;
        end
      end // else LOAD
    end  // else CLR
  end // if EN
  else begin  // 使能无效,计数值保持不动
    cnt_next = CNTVAL;
  end // else EN
end

// 时序逻辑 更新下一时钟周期的计数值
// CNTVAL 会被编译为D触发器
always @ (posedge CLK or posedge RST) begin
  if(RST) 
    CNTVAL <= 0;
  else
    CNTVAL <= cnt_next;
end

// 组合逻辑,生成OV
always @ (CNTVAL) begin
  if(CNTVAL == CNT_MAX_VAL) 
    OV = 1;
  else
    OV = 0;
end

endmodule

RTL View:
在这里插入图片描述
波形仿真:
在这里插入图片描述
设计一个最简单的计数器,只有一个CLK输入和一个OVerflow输出,当计数到最大值的时钟周期CLK输出1

Verilog代码如下:

module top(
CLK,
CNTVAL,
OV);

input CLK;
output [3:0] CNTVAL;
output OV;

parameter CNT_MAX_VAL = 9;

reg [3:0] CNTVAL;
reg OV;

always @(posedge CLK)
begin
  if(CNTVAL<CNT_MAX_VAL)
    CNTVAL <= CNTVAL +1'b1;
  else
    CNTVAL <= 0;
end

always @(CNTVAL)
begin
 if(CNTVAL ==CNT_MAX_VAL)
   OV = 1;
 else
   OV = 0;
end

endmodule

波形仿真:
在这里插入图片描述

设计复杂的计数器,带有多种信号,其中同步清零CLR的优先级最高,使能EN次之,LOAD最低。

Verilog代码如下:

module top(
RST,
CLK,
EN,
CLR,
LOAD,
DATA,
CNTVAL,
OV);

input RST,CLK,EN,CLR,LOAD;
input [3:0] DATA;
output [3:0] CNTVAL;
output OV;

reg [3:0] CNTVAL,cnt_next;
reg OV;

parameter CNT_MAX_VAL=9;

//组合逻辑,生成cnt_next
//清零第一优先,计数使能第二优先,数据加载第三优先
always @(CLR or EN or  LOAD or  DATA or CNTVAL)
begin
  if(CLR)  //同步清零
  begin
    cnt_next = 0;
  end
  else     //清零无效
  begin
    if(EN) //清零无效,使能有效
	begin
      if(CNTVAL<CNT_MAX_VAL)
	  begin
        cnt_next = CNTVAL+1'b1;
	  end
      else
	  begin
        cnt_next = 0;
	  end
    end
    else  //清零无效,使能无效
	begin
	  if(LOAD)  //清零无效,使能无效,加载有效
	  begin
	    cnt_next = DATA;
	  end
	  else     //清零无效,使能无效,加载无效
	  begin
	    cnt_next = CNTVAL;
	  end
    end
  end
end

//时序逻辑 更新下一时钟周期的计数值
//CNTVAL 会被编译为D触发器
always @(posedge CLK or posedge RST)
begin
  if(RST)
    CNTVAL <= 0;
  else
    CNTVAL <= cnt_next;
end

//组合逻辑,生成OV
always @(CNTVAL)
begin
  if(CNTVAL == CNT_MAX_VAL)
    OV = 1;
  else
    OV = 0;
end
endmodule	

波行仿真:
在这里插入图片描述

10.状态机

有限状态机(Finite State Machine)同样是数字电路设计中非常常用的模块,其在EDA设计中的地位等同于C语言中的If-else语句。当设计状态机时,强烈建议先把这两个表格画出来再开始写代码

状态跳转逻辑表

当前状态输入次态
ST_0_CENCENT1IN==0ST_0_CENT
ST_0_CENTCENT1IN==1ST_1_CENT
ST_1_CENTCENT1IN==0ST_1_CENT
ST_1_CENTCENT1IN==1ST_2_CENT
ST_2_CENTCENT1IN==0ST_2_CENT
ST_2_CENTCENT1IN==1ST_3_CENT
ST_3_CENTDonot careST_0_CEN

输出逻辑表

当前状态输出 TINOUT
ST_0_CENT0
ST_1_CENT0
ST_2_CENT0
ST_3_CENT1

Verilog代码如下:

////////////////////  三段式状态机代码  /////////////////////////
module test_rtl(
  CLK       ,   // clock
  RST       ,   // reset
  CENT1IN   ,   // input 1 cent coin
  TINOUT    );  // output 1 tin cola

input  CLK       ; 
input  RST       ; 
input  CENT1IN   ; 
output TINOUT    ;

parameter ST_0_CENT = 0;
parameter ST_1_CENT = 1;
parameter ST_2_CENT = 2;
parameter ST_3_CENT = 3;

reg [2-1:0]stateR       ;
reg [2-1:0]next_state   ;
reg        TINOUT       ;

// calc next state
always @ (CENT1IN or stateR) begin
  case (stateR)
    ST_0_CENT :begin if(CENT1IN) next_state = ST_1_CENT ; else next_state = ST_0_CENT; end
    ST_1_CENT :begin if(CENT1IN) next_state = ST_2_CENT ; else next_state = ST_1_CENT; end
    ST_2_CENT :begin if(CENT1IN) next_state = ST_3_CENT ; else next_state = ST_2_CENT; end
    ST_3_CENT :begin next_state = ST_0_CENT; end
  endcase
end

// calc output
always @ (stateR) begin
  if(stateR == ST_3_CENT) 
    TINOUT = 1'b1;
  else 
    TINOUT = 1'b0;
end

// state DFF
always @ (posedge CLK or posedge RST)begin
  if(RST)
    stateR <= ST_0_CENT;
  else
    stateR <= next_state;
end

endmodule

编译代码之后,可以在Tools-Netlist Viewers-State Machine Viewer 里面看到你写的状态机的状态转移图和表达式。这对你调试电路非常有帮助。
在这里插入图片描述
下面设计一个用于识别2进制序列“1011”的状态机

基本要求:
电路每个时钟周期输入1比特数据,当捕获到1011的时钟周期,电路输出1,否则输出0
使用序列101011010作为输出的测试序列
扩展要求:
给你的电路添加输入使能端口,只有输入使能EN为1的时钟周期,才从输入的数据端口向内部获取1比特序列数据。

状态跳转逻辑表
在这里插入图片描述
输出逻辑表

当前状态输出
ST_00
ST_10
ST_20
ST_30
ST_41

Verilog 代码如下:

module top(
CLK,  // clock
RST,  // reset
IN,   // input
EN,   // EN
OUT); // output

input CLK;
input RST;
input IN;
input EN;
output OUT;

parameter ST_0 = 0;
parameter ST_1 = 1;
parameter ST_2 = 2;
parameter ST_3 = 3;
parameter ST_4 = 4;

reg [2:0] stateR;
reg [2:0] next_state;
reg OUT;

//calc next_state
always @(IN or EN or stateR)
begin
  case(stateR)
    ST_0:begin if(IN==0&&EN==0) next_state = ST_0; else if(IN==1&&EN==0) next_state = ST_0; 
    else if(IN==0&&EN==1) next_state = ST_0; else next_state = ST_1;end
	ST_1:begin if(IN==0&&EN==0) next_state = ST_1; else if(IN==1&&EN==0) next_state = ST_1; 
	else if(IN==0&&EN==1) next_state = ST_2; else next_state = ST_1;end
	ST_2:begin if(IN==0&&EN==0) next_state = ST_2; else if(IN==1&&EN==0) next_state = ST_2; 
	else if(IN==0&&EN==1) next_state = ST_0; else next_state = ST_3;end
	ST_3:begin if(IN==0&&EN==0) next_state = ST_3; else if(IN==1&&EN==0) next_state = ST_3; 
	else if(IN==0&&EN==1) next_state = ST_2; else next_state = ST_4;end
	ST_4:begin next_state = ST_0;end
  endcase
end
//calc output
always @(stateR)
begin
  if(stateR == ST_4)
    OUT = 1'b1;
  else
    OUT = 1'b0;
end

//state DFF
always @(posedge CLK or posedge RST)
begin
  if(RST)
    stateR <= ST_0;
  else
    stateR <= next_state;
end

endmodule

波形仿真:
在这里插入图片描述

11.移位寄存器

串行数据和并行数据之间的相互转换是在接口设计中很常见的功能,一般而言,数据在FPGA内部都是并行传递的,当通过串行接口协议(例如SPI,I2C,I2S等)把数据从FPGA内部传送到一个外部芯片(例如一片EEPROM存储器或是一片音频DAC)时就需要用到串并转换了,其核心的电路是移位寄存器。
常用的串并转换电路有“串行-并行”和“并行-串行”两种,其RTL电路如下

在这里插入图片描述
串入并出移位寄存器Verilog代码如下:

//////////////////// 串入并出移位寄存器  /////////////////////////
module top(
  RST   ,   // 异步复位, 高有效
  CLK   ,   // 时钟,上升沿有效
  EN    ,   // 输入数据串行移位使能
  IN    ,   // 输入串行数据
  OUT   );  // 并行输出数据

input RST, CLK, EN;
input IN;
output[3:0] OUT;
reg [3:0] shift_R;

assign OUT[3:0] = shift_R[3:0];
// 时序逻辑 根据输入使能进行串行移位
// shift_R 会被编译为D触发器
always @ (posedge CLK or posedge RST) begin
  if(RST) 
    shift_R[3:0] <= 0;
  else
    if(EN) begin // 串行移位的使能有效
      shift_R[3:1] <= shift_R[2:0];
      shift_R[0]   <= IN;
    end
    else begin // 使能无效保持不动
      shift_R[3:0] <= shift_R[3:0];
    end
end // always
endmodule

波形仿真:
在这里插入图片描述
设计一个“带加载使能和移位使能的并入串出”的移位寄存器。

首先编写Verilog代码如下:

module top(
CLK,     // 时钟信号
RST,     // 复位信号输入
EN_LOAD, // 加载输入数据使能
EN_SHIFT, // 移位使能
IN,      // 并行输入数据
OV,      // 一组数据完全并行输出提示信号
OUT);    // 串行输出信号

input CLK,RST,EN_LOAD,EN_SHIFT;
input [3:0] IN;
output OUT,OV;
reg shift_R,OV;
reg [3:0] shift_L;
reg [3:0] n;

assign OUT = shift_R;

always @(posedge CLK or posedge RST) begin
  if(RST) begin
    shift_R <= 0;
	shift_L <= 0;
	n <= 0;
  end
  else begin
    if(EN_SHIFT) begin
	  if(EN_LOAD) begin
	    shift_L <=IN;
	  end
	  else begin
      shift_R <= shift_L[3];
	  shift_L[3:1] <= shift_L[2:0];
	  shift_L[0] <= 0;
	  n <= n+1;
      end
    end
    else begin
	  shift_R <= shift_R;
	  end
  end
end

always @(n) begin
  if(n%4==0&&n!=0)
  begin
    OV = 1'b1;
  end
  else
    OV = 0;
end

endmodule

波形仿真:
在这里插入图片描述
验证试验正确。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值