概述
题目描述:设计一个4 连续0 或者4 个连续1 的序列检测FSM(有限状态机),定义一个长序列,在七段管上分别显示检测的4 个连续0 和4 个连续1 的个数。显示连续1 和连续0 的个数在七段管上的显示,分别用函数和任务实现。
目录
- 题目分析
- 第一次尝试(只有函数时,逻辑最简单)
- 有趣的尝试(十位使用函数,个位使用任务)
- 最终定解
题目分析
分析一:如何去实现这个这个功能?
对于连续数字的检测,根据题目描述,我们可以使用有限状态机来表示,那么我们可以对其进行抽象成如下的表示图:
在这张图中,我们分别使用后了9个状态来表示输入的状态,用带有数字的箭头表示状态之间的转移。从状态0 到 状态 9 分别表示为
状态值 | 状态意义 |
---|---|
0 | 初始化状态,即模块遇到状态错误或者重置时的初始化状态,在正常工作时是不会进入到初始化状态的 |
1 | 连续输入一个1 |
2 | 连续输入两个1 |
3 | 连续输入三个1 |
4 | 连续输入四个或四个以上1 |
5 | 连续输入一个0 |
6 | 连续输入两个0 |
7 | 连续输入三个0 |
8 | 连续输入四个或者四以上个0 |
当状态进行到4或者8的时候就可以有信号输出了。
分析二:如何显示出当前有几个1 或者 0?
显然我们需要定义一个数值变量来记录下0 和 1的个数,再对这个数进行分解,输出到七段管上,当然,标准一点的写法可以写成若干10位计数器的串联,对于这道题来说,用取模10 除以10 这样的分数方法当然也可以完成。最后就是调用七段管了。
分析三:函数和任务??
这道题甲方又提出了令人费解的需求,这两个不是差不多吗?写一个调用多次就挺好,还非要搞俩个。在增加代码复杂度的同时,又明显劣化了性能。(这道题显然使用函数会好做一些)可谓一石二鸟。但没办法,只好先分析一下函数和任务的主要不同。
函数
对于一个函数来说他的写法是这样的
module test(a, b, out);
input[1:0] a; // 时钟信号
input[1:0] b;
output[2:0] out;
function[2:0] my_add;
input[2:0] a;
input[2:0] b;
my_add = a + b;
endfunction
assign out = my_add(a, b);
endmodule
可以看到对于函数来说,在其内部自己的函数名就是返回值,我们会将结果直接赋予他,它的使用有以下注意点:
注意点 |
---|
(1)函数定义只能在模块中完成,不能出现在过程块中; |
(2)函数至少要有一个输入端口;不能包含输出端口和双向端口; |
(3) 在函数结构中, 不能使用任何形式的时间控制语句 (#、 wait 等) |
(4)函数结构中不能使用 disable中止语句; |
(5)函数定义结构体中不能出现过程块语句(always 语句) ; |
(6)函数内部可以调用函数,但不能调用任务。 |
(7)函数调用语句不能单独作为一条语句出现,只能作为赋值语句的右端操作数。 |
任务
module test(a, b, out);
input[1:0] a; // 时钟信号
input[1:0] b;
output reg[2:0] out;
// 将数值变成七段管中的明暗状态
task my_add;
input[1:0] a;
input[1:0] b;
output[2:0] c;
c = a + b;
endtask
always @(a,b) begin
my_add(a, b, out);
end
endmodule
可以看出,对于一个任务来说,我们的输出输出都是在任务内部定义的,在调用的时候,我们要注意的是传入变量的顺序应该和变量在内部定义的顺序一致,任务还有以下需要注意以下内容。
注意点 |
---|
(1)在第一行“task”语句中不能列出端口名称,而应该统一在任务体内部定义 |
(2)任务的输入、输出端口和双向端口数量不受限制,甚至可以没有输入、输出以及双向端口。 |
(3)在任务定义的描述语句中,可以使用出现不可综合操作符合语句。 |
(4)在任务中可以调用其他的任务或函数,也可以调用自身。 |
(5)在任务定义结构内不能出现 initial和 always过程块。 |
(6)在任务定义中可以出现“disable 中止语句” ,任务会被中断,程序流程将返回到调用任务的地方继续向下执行。(非综合) |
(7)任务调用语句只能出现在过程块( initial和 always)内; |
(8)可综合任务只能实现组合逻辑,也就是说调用可综合任务的时间为“0” 。出现不可综合代码是调用时间可不为“0” |
第一次尝试(只有函数时,逻辑最简单)
综合代码
module fsm(clk,clr,sign,out,qout,tout1, tout2, num, pn);
input clk; // 时钟信号
input clr; // 复位信号
input sign; // 输入信号
output reg out; // 输出信号
output reg[3:0] qout; // 状态型号,由于寄存器状态一个有9种, 所以长度为4
output[6:0] tout1;
output[6:0] tout2; // 进行连续计数的变量
output reg [7:0] num;
output reg pn; // 输出信号的正负
// 函数:将数值变成七段管中的明暗状态
function[6:0] dig2tube;
input[3:0] in;
case(in)
0: dig2tube = 8'b0000001;
1: dig2tube = 8'b0011111;
2: dig2tube = 8'b0010010;
3: dig2tube = 8'b0000110;
4: dig2tube = 8'b1001100;
5: dig2tube = 8'b0100100;
6: dig2tube = 8'b0100000;
7: dig2tube = 8'b0001111;
8: dig2tube = 8'b0000000;
9: dig2tube = 8'b0000100;
endcase
endfunction
//此过程定义状态转换,转化过程见状态转移图
always @(posedge clk or posedge clr) begin
pn <= sign; // 表示现在输出num是在计数0还是在计数1
if(clr) qout<=0; //异步复位
else begin // 每个状态对于信号的高低都有两种不同的走向
if(sign)begin // 如果是 1
case(qout) // 根据当前状态修改qout(状态)和 num 计数
4'b0000: begin qout<= 4'b0001; num <=1; end // 0
4'b0001: begin qout<= 4'b0010; num <=2; end // 1
4'b0010: begin qout<= 4'b0011; num <=3; end // 2
4'b0011: begin qout<= 4'b0100; num <=4; end // 3
4'b0100: begin qout<= 4'b0100; num <=num + 1; end // 4
4'b0101: begin qout<= 4'b0001; num <=1; end // 5
4'b0110: begin qout<= 4'b0001; num <=1; end // 6
4'b0111: begin qout<= 4'b0001; num <=1; end // 7
4'b1000: begin qout<= 4'b0001; num <=1; end // 8
default: begin qout<= 0; num <=0; end // 默认
endcase
end else begin // 如果信号是 0
case(qout) // 根据当前状态修改qout(状态)和 num 计数
4'b0000: begin qout<= 4'b0101; num <=1; end // 0
4'b0001: begin qout<= 4'b0101; num <=1; end // 1
4'b0010: begin qout<= 4'b0101; num <=1; end // 2
4'b0011: begin qout<= 4'b0101; num <=1; end // 3
4'b0100: begin qout<= 4'b0101; num <=1; end // 4
4'b0101: begin qout<= 4'b0110; num <=2; end // 5
4'b0110: begin qout<= 4'b0111; num <=3; end // 6
4'b0111: begin qout<= 4'b1000; num <=4; end // 7
4'b1000: begin qout<= 4'b1000; num <=num + 1; end // 8
default: begin qout<= 0; num <=0; end // 默认
endcase
end
end
end
// 此过程产生输出逻辑
always @(qout) begin
case(qout)
4'b1000: out=1'b1;
4'b0100: out=1'b1;
default: out=1'b0;
endcase
end
// 将寄存器里的数值通过函数赋值到输出上
assign tout1 = dig2tube(num/10%10);
assign tout2 = dig2tube(num%10);
endmodule
测试代码
`timescale 1 ps/ 1 ps
module fsm_vlg_tst();
reg clk;
reg clr;
reg sign;
wire out;
wire pn;
wire [3:0] qout;
wire [6:0] tout1;
wire [6:0] tout2;
wire [7:0] num;
fsm i1 (
.clk(clk),
.clr(clr),
.out(out),
.pn(pn),
.qout(qout),
.sign(sign),
.num(num),
.tout1(tout1),
.tout2(tout2)
);
function[3:0] tube2dig;
input[6:0] in;
case(in)
8'b0000001: tube2dig = 0;
8'b0011111: tube2dig = 1;
8'b0010010: tube2dig = 2;
8'b0000110: tube2dig = 3;
8'b1001100: tube2dig = 4;
8'b0100100: tube2dig = 5;
8'b0100000: tube2dig = 6;
8'b0001111: tube2dig = 7;
8'b0000000: tube2dig = 8;
8'b0000100: tube2dig = 9;
endcase
endfunction
initial begin
clr = 0;clk=0;
sign =0;
#2 clr = 1;
#2 clr =0;
#116 sign = 1;
#200 $stop;
end
always begin
#5 clk =~ clk;
end
always @(num) begin
// 这里延时一秒是由于 out 是reg 类型, 当变化的瞬间, out 输出的是上一时刻的,所以要延时一下
#1 $display("sign=%d -- num=%d -- out=%d -- tout1=%d -- tout2=%d",
sign, num, out, tube2dig(tout1), tube2dig(tout2));
end
endmodule
效果
有趣的尝试(十位使用函数,个位使用任务)
综合代码
没有修改的地方已注释。可以看到,由于task要求在过程块里使用,所以说我们必须把输出也给改成reg 类型。
//module fsm(clk,clr,sign,out,qout,tout1, tout2, num, pn);
//
// input clk; // 时钟信号
// input clr; // 复位信号
// input sign; // 输入信号
// output reg out; // 输出信号
// output reg[3:0] qout; // 状态型号,由于寄存器状态一个有9种, 所以长度为4
// output[6:0] tout1;
output reg [6:0] tout2; // 进行连续计数的变量
// output reg [7:0] num;
// output reg pn; // 输出信号的正负
//
//
// // 函数:将数值变成七段管中的明暗状态
// function[6:0] dig2tube;
// input[3:0] in;
// case(in)
// 0: dig2tube = 8'b0000001;
// 1: dig2tube = 8'b0011111;
// 2: dig2tube = 8'b0010010;
// 3: dig2tube = 8'b0000110;
// 4: dig2tube = 8'b1001100;
// 5: dig2tube = 8'b0100100;
// 6: dig2tube = 8'b0100000;
// 7: dig2tube = 8'b0001111;
// 8: dig2tube = 8'b0000000;
// 9: dig2tube = 8'b0000100;
// endcase
// endfunction
//
// //此过程定义状态转换,转化过程见状态转移图
// always @(posedge clk or posedge clr) begin
// pn <= sign;
// if(clr) qout<=0; //异步复位
// else begin // 每个状态对于信号的高低都有两种不同的走向
// if(sign)begin // 如果是 1
// case(qout) // 根据当前状态修改qout(状态)和 num 计数
// 4'b0000: begin qout<= 4'b0001; num <=1; end // 0
// 4'b0001: begin qout<= 4'b0010; num <=2; end // 1
// 4'b0010: begin qout<= 4'b0011; num <=3; end // 2
// 4'b0011: begin qout<= 4'b0100; num <=4; end // 3
// 4'b0100: begin qout<= 4'b0100; num <=num + 1; end // 4
// 4'b0101: begin qout<= 4'b0001; num <=1; end // 5
// 4'b0110: begin qout<= 4'b0001; num <=1; end // 6
// 4'b0111: begin qout<= 4'b0001; num <=1; end // 7
// 4'b1000: begin qout<= 4'b0001; num <=1; end // 8
// default: begin qout<= 0; num <=0; end // 默认
// endcase
// end else begin // 如果信号是 0
// case(qout) // 根据当前状态修改qout(状态)和 num 计数
// 4'b0000: begin qout<= 4'b0101; num <=1; end // 0
// 4'b0001: begin qout<= 4'b0101; num <=1; end // 1
// 4'b0010: begin qout<= 4'b0101; num <=1; end // 2
// 4'b0011: begin qout<= 4'b0101; num <=1; end // 3
// 4'b0100: begin qout<= 4'b0101; num <=1; end // 4
// 4'b0101: begin qout<= 4'b0110; num <=2; end // 5
// 4'b0110: begin qout<= 4'b0111; num <=3; end // 6
// 4'b0111: begin qout<= 4'b1000; num <=4; end // 7
// 4'b1000: begin qout<= 4'b1000; num <=num + 1; end // 8
// default: begin qout<= 0; num <=0; end // 默认
// endcase
// end
// end
dig2tube2(num%10, tout2);
// end
// 此过程产生输出逻辑
// always @(qout) begin
// case(qout)
// 4'b1000: out=1'b1;
// 4'b0100: out=1'b1;
// default: out=1'b0;
// endcase
// end
// 将寄存器里的数值通过函数赋值到输出上
assign tout1 = dig2tube(num/10%10);
// 任务:将数值变成七段管中的明暗状态
task dig2tube2;
input[3:0] _in;
output[6:0] _out;
case(_in)
0: _out = 8'b0000001;
1: _out = 8'b0011111;
2: _out = 8'b0010010;
3: _out = 8'b0000110;
4: _out = 8'b1001100;
5: _out = 8'b0100100;
6: _out = 8'b0100000;
7: _out = 8'b0001111;
8: _out = 8'b0000000;
9: _out = 8'b0000100;
endcase
endtask
//endmodule
出现的问题
可以看到,由于加了一个reg 把out2 放到了always 里直接导致了out2 与其他变量不同步。
问题的原因
出现这个问题的本质是由于输出和输出之间经过了两次寄存器,一次是num, 一次是out2本身,使得他发生变化要比经过一次寄存器的生效慢,现在要解决的这个问题就要让输入信号和out2 之间进过的寄存器减少。
最终定解
经过了两次测试我们认识到了实现函数和任务时的一些基本注意点,之后就要完成甲方这不合理的请求了。为了让管子的输出同步,在使用task的时候只能使用一次寄存器,即在本题中,我们直接使用最后的七段管,去除了中间的计数器。这样带来了代码的复杂性。即我们需要将七段管的值变回数值用于计算。而在使用函数时,则不用。
综合代码
module fsm(clk,clr,sign,out,qout,num1, tubep1, tubep2, tuben1, tuben2);
input clk; // 时钟信号
input clr; // 复位信号
input sign; // 输入信号
output reg out; // 输出信号
output reg[3:0] qout; // 状态型号,由于寄存器状态一个有9种, 所以长度为4
output reg [7:0] num1; // 用于统计
output [6:0] tubep1; // 用于输出1 的计数
output [6:0] tubep2; // 用于输出1 的计数
output reg [6:0] tuben1; // 用于输出0 的计数
output reg [6:0] tuben2; // 用于输出0 的计数
// output reg pn; // 由于这时候有两个输出,无需考虑正负
// 函数:将数值变成七段管中的明暗状态
function[6:0] dig2tube;
input[3:0] in;
case(in)
0: dig2tube = 8'b0000001;
1: dig2tube = 8'b0011111;
2: dig2tube = 8'b0010010;
3: dig2tube = 8'b0000110;
4: dig2tube = 8'b1001100;
5: dig2tube = 8'b0100100;
6: dig2tube = 8'b0100000;
7: dig2tube = 8'b0001111;
8: dig2tube = 8'b0000000;
9: dig2tube = 8'b0000100;
endcase
endfunction
// 函数:将七段管的明暗状态 变成 数值
function[3:0] tube2dig;
input[6:0] in;
case(in)
8'b0000001: tube2dig = 0;
8'b0011111: tube2dig = 1;
8'b0010010: tube2dig = 2;
8'b0000110: tube2dig = 3;
8'b1001100: tube2dig = 4;
8'b0100100: tube2dig = 5;
8'b0100000: tube2dig = 6;
8'b0001111: tube2dig = 7;
8'b0000000: tube2dig = 8;
8'b0000100: tube2dig = 9;
endcase
endfunction
// 任务:将数值变成七段管中的明暗状态
task dig2tube2;
input[3:0] _in;
output[6:0] _out;
case(_in)
0: _out = 8'b0000001;
1: _out = 8'b0011111;
2: _out = 8'b0010010;
3: _out = 8'b0000110;
4: _out = 8'b1001100;
5: _out = 8'b0100100;
6: _out = 8'b0100000;
7: _out = 8'b0001111;
8: _out = 8'b0000000;
9: _out = 8'b0000100;
endcase
endtask
//此过程定义状态转换,转化过程见状态转移图
always @(posedge clk or posedge clr) begin
if(clr) qout<=0; //异步复位
else begin // 每个状态对于信号的高低都有两种不同的走向
if(sign)begin // 如果是 1
tuben1 <= dig2tube(0);
tuben2 <= dig2tube(0);
case(qout) // 根据当前状态修改qout(状态)和 num 计数
4'b0000: begin qout<= 4'b0001; end // 0
4'b0001: begin qout<= 4'b0010; end // 1
4'b0010: begin qout<= 4'b0011; end // 2
4'b0011: begin qout<= 4'b0100; num1 <= 1; end // 3
4'b0100: begin qout<= 4'b0100; num1 <=num1 + 1; end // 4
4'b0101: begin qout<= 4'b0001; end // 5
4'b0110: begin qout<= 4'b0001; end // 6
4'b0111: begin qout<= 4'b0001; end // 7
4'b1000: begin qout<= 4'b0001; end // 8
default: begin qout<= 0; end // 默认
endcase
end
else begin // 如果信号是 0
num1 <= 0;
case(qout) // 根据当前状态修改qout(状态)和 num 计数
4'b0000: begin qout<= 4'b0101; end // 0
4'b0001: begin qout<= 4'b0101; end // 1
4'b0010: begin qout<= 4'b0101; end // 2
4'b0011: begin qout<= 4'b0101; end // 3
4'b0100: begin qout<= 4'b0101; end // 4
4'b0101: begin qout<= 4'b0110; end // 5
4'b0110: begin qout<= 4'b0111; end // 6
4'b0111: begin qout<= 4'b1000; end // 7
4'b1000: begin qout<= 4'b1000; end // 8
default: begin qout<= 0; end // 默认
endcase
if(qout>=7) begin // 如果是从 5 - 8状态转变过来的,就让管子里的数字增加
// 开始增加数值
if(tube2dig(tuben2) == 9) begin // 如果个位是9
dig2tube2( 4'b0000 , tuben2); // 个位变为0
if(tube2dig(tuben1) == 9) begin // 如果是 99
dig2tube2( 4'b0000 , tuben1); // 十位也变为 0
end
else begin
dig2tube2( tube2dig(tuben1) + 1 , tuben1); // 如果十位不是9 各位 是9,个位自增, 十位不变
end
end
else begin
dig2tube2( tube2dig(tuben2) + 1 , tuben2); // 如果个位不是9, 考虑进位,直接个位自增
end
// 数值增加结束
end
else begin
dig2tube2(4'b0000, tuben2);
dig2tube2(4'b0000, tuben1);
end
end
end
end
// 此过程产生输出逻辑
always @(qout) begin
case(qout)
4'b1000: out=1'b1;
4'b0100: out=1'b1;
default: out=1'b0;
endcase
end
// 将num1寄存器里的数值通过函数赋值到
assign tubep1 = dig2tube(num1/10%10);
assign tubep2 = dig2tube(num1%10);
endmodule
测试代码
`timescale 1 ps/ 1 ps
module fsm_vlg_tst();
reg clk;
reg clr;
reg sign;
wire [7:0] num1;
wire out;
wire [3:0] qout;
wire [6:0] tuben1;
wire [6:0] tuben2;
wire [6:0] tubep1;
wire [6:0] tubep2;
fsm i1 (
.clk(clk),
.clr(clr),
.num1(num1),
.out(out),
.qout(qout),
.sign(sign),
.tuben1(tuben1),
.tuben2(tuben2),
.tubep1(tubep1),
.tubep2(tubep2)
);
// 函数:将七段管的明暗状态 变成 数值
function[3:0] tube2dig;
input[6:0] in;
case(in)
8'b0000001: tube2dig = 0;
8'b0011111: tube2dig = 1;
8'b0010010: tube2dig = 2;
8'b0000110: tube2dig = 3;
8'b1001100: tube2dig = 4;
8'b0100100: tube2dig = 5;
8'b0100000: tube2dig = 6;
8'b0001111: tube2dig = 7;
8'b0000000: tube2dig = 8;
8'b0000100: tube2dig = 9;
endcase
endfunction
initial begin
clr = 0;clk=0;
sign =0;
#2 clr = 1;
#2 clr =0;
#146 sign = 1; #4;
#146 sign = 0; #4;
#200 $stop;
end
always begin
#5 clk =~ clk;
end
always @(posedge clk) begin
// 这里延时一秒是由于 out 是reg 类型, 当变化的瞬间, out 输出的是上一时刻的,所以要延时一下
#1 $display("sign:%d -- out:%d -- the num of continuous low:%d%d -- the num of continuous high:%d%d",
sign, out, tube2dig(tuben1), tube2dig(tuben2), tube2dig(tubep1), tube2dig(tubep2));
end
endmodule
效果图
文本输出
波形图整体
变化细节
最后
以上就是专一啤酒为你收集整理的EDA设计(verilog)—— 连续数检测电路的全部内容,希望文章能够帮你解决EDA设计(verilog)—— 连续数检测电路所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复