EDA设计(verilog)—— 连续数检测电路

题目描述:设计一个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

效果图

文本输出
在这里插入图片描述

波形图整体
在这里插入图片描述
变化细节
在这里插入图片描述

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个可能的实现方式: ``` module channel_mux ( input [63:0] channel_data, // 输入的64个通道据 input [63:0] vld, // 输入的64个有效信号 output reg [63:0] output_data // 输出的一路据 ); reg [5:0] sel; // 6选择器,用于选择当前有效通道 reg [63:0] sel_data; // 当前有效通道的据 always @* begin sel = 0; sel_data = 0; // 搜索所有通道,找到第一个有效通道 for (int i = 0; i < 64; i = i + 1) begin if (vld[i]) begin sel = i; sel_data = channel_data[i]; break; end end end always @(posedge clk) begin output_data <= sel_data; end endmodule ``` 该模块使用两个输入端口:`channel_data` 和 `vld`,以及一个输出端口 `output_data`。`channel_data` 是一个 64 输入端口,其中每个 8 对应一个通道的据。`vld` 是一个 64 输入端口,其中每个对应一个通道的有效信号。`output_data` 是一个 64 的输出端口,对应 64 个周期中选中的某个通道的据。 在模块内部,使用一个 6 的选择器 `sel`,以及一个 64 的临时变量 `sel_data`,用于选择当前有效通道,并存储该通道的据。在每个时钟上升沿触发的 always 块中,将 `sel_data` 的值赋给 `output_data`,以输出选中的据。 模块的实现方式是:首先搜索所有通道,找到第一个有效通道,并将其对应的据存储到 `sel_data` 中。如果在 64 个周期内都没有有效通道,则 `sel_data` 的值为 0。 需要注意的是,该实现方式假设在任何时刻只有一个通道是有效的。如果在同一时刻有多个通道是有效的,则只会选择第一个被搜索到的有效通道。如果需要支持同时有多个通道是有效的情况,需要对该实现方式进行修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值