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_CEN | CENT1IN==0 | ST_0_CENT |
ST_0_CENT | CENT1IN==1 | ST_1_CENT |
ST_1_CENT | CENT1IN==0 | ST_1_CENT |
ST_1_CENT | CENT1IN==1 | ST_2_CENT |
ST_2_CENT | CENT1IN==0 | ST_2_CENT |
ST_2_CENT | CENT1IN==1 | ST_3_CENT |
ST_3_CENT | Donot care | ST_0_CEN |
输出逻辑表
当前状态 | 输出 TINOUT |
---|---|
ST_0_CENT | 0 |
ST_1_CENT | 0 |
ST_2_CENT | 0 |
ST_3_CENT | 1 |
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_0 | 0 |
ST_1 | 0 |
ST_2 | 0 |
ST_3 | 0 |
ST_4 | 1 |
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
波形仿真:
验证试验正确。