module mux2_1(
IN0 ,
IN1 ,
SEL ,
OUT );
parameter WL = 1; // bit wide of input and output. set 1 for conciseness here
input [WL-1:0] IN0, IN1;
input SEL;
output[WL-1:0] OUT;
reg [WL-1:0] OUT;
// code for combinational logic
always @ (IN0 or IN1 or SEL) begin
if(SEL)
OUT = IN1;
else
OUT = IN0;
end
endmodule
// endmodule top
小声:本人英语拉得很,但是我的quartus9.1不支持中文,所以所有代码注释均为英文。如有离谱之处,敬请指出。(当然代码如有问题还望不吝赐教)
仿真结果:
电路中的逻辑单元有延迟,故输出有延迟,所以时钟间隔应当大一些。我一般设置为100ns。
module mux4_1(
IN0 ,
IN1 ,
IN2 ,
IN3 , //4 input
SEL ,
OUT );
parameter WL = 1; //input bit wide
input [WL-1:0] IN0,IN1,IN2,IN3;
input [1:0] SEL; //4input need 2bits select
output [WL-1:0] OUT;
reg [WL-1:0] OUT;
always @ (IN0 or IN1 or IN2 or IN3 or SEL) begin
case(SEL)
0 : OUT = IN0;
1 : OUT = IN1;
2 : OUT = IN2;
default : OUT = IN3;
endcase
end
endmodule
注意case后面要有end。最好写完case( )end再填充内容,防止忘记。
编译报告:
与2选1的mux编译报告对比可发现4选1的mux资源用了更多的逻辑单元。
// module top, a 2x2 crossbar switch circuit
module crossbar_switch_2x2(
IN0 , // input 1
IN1 , // input 2
SEL0 , // select the output0 source
SEL1 , // select the output1 source
OUT0 , // output data 0
OUT1 ); // output data 1
parameter WL = 1;
input [WL-1:0] IN0, IN1;
input SEL0, SEL1;
output[WL-1:0] OUT0, OUT1;
reg [WL-1:0] OUT0, OUT1;
// get the OUT0
always @ (IN0 or IN1 or SEL0) begin
if(SEL0)
OUT0 = IN1;
else
OUT0 = IN0;
end
// get the OUT1
always @ (IN0 or IN1 or SEL1) begin
if(SEL1)
OUT1 = IN1;
else
OUT1 = IN0;
end
endmodule
// endmodule top
代码中不难看出这里其实是两个多路选择器的组合。
RTL Viewer:
RTL视图里也可以清晰地看到这其实只是两个多路选择器的组合。
module crossbar_switch_4x4(
IN0 ,
IN1 ,
IN2 ,
IN3 ,
SEL0 ,
SEL1 ,
SEL2 ,
SEL3 ,
OUT0 ,
OUT1 ,
OUT2 ,
OUT3 );
parameter WL = 1;
input [WL-1:0] IN0,IN1,IN2,IN3;
input [1:0] SEL0,SEL1,SEL2,SEL3;
//Each output need a independent select signals, so we need 4 sel;
//There are 4 inputs, so sel need 2 bis.
output [WL-1:0] OUT0,OUT1,OUT2,OUT3;
reg [WL-1:0] OUT0,OUT1,OUT2,OUT3;
always @ (IN0 or IN1 or IN2 or IN3 or SEL0) begin
case(SEL0)
0 : OUT0 = IN0;
1 : OUT0 = IN1;
2 : OUT0 = IN2;
default : OUT0 = IN3;
endcase
end
always @ (IN0 or IN1 or IN2 or IN3 or SEL1) begin
case(SEL1)
0 : OUT1 = IN0;
1 : OUT1 = IN1;
2 : OUT1 = IN2;
default : OUT1 = IN3;
endcase
end
always @ (IN0 or IN1 or IN2 or IN3 or SEL2) begin
case(SEL2)
0 : OUT2 = IN0;
1 : OUT2 = IN1;
2 : OUT2 = IN2;
default : OUT2 = IN3;
endcase
end
always @ (IN0 or IN1 or IN2 or IN3 or SEL3) begin
case(SEL3)
0 : OUT3 = IN0;
1 : OUT3 = IN1;
2 : OUT3 = IN2;
default : OUT3 = IN3;
endcase
end
endmodule
这里的输入输出端口比较多,本来想把input,output,sel全都使用数组表示的,但是报错了。查询语法后发现reg内存的可以只用数组表示,端口不能。用到的四个reg这里可以直接与输出端口匹配,为了防止未知的语法问题和代码复杂化,就没有采用数组表示。
RTL Viewer:
四个输出,分别用四个2bit的sel来分别选择输入信号。也就是四个4_1多路选择器的组合。
// module top, 4 input priority encoder with zero input check
module priority_encoder4_2(
IN ,
OUT );
input [3:0] IN;
output [2:0] OUT;
reg [2:0] OUT;
always @ (IN) begin
if(IN[3]) // highest priority
OUT = 3'b011;
else if(IN[2]) // second priority
OUT = 3'b010;
else if(IN[1]) // third priority
OUT = 3'b001;
else if(IN[0]) // fourth priority
OUT = 3'b000;
else // detected nothing
OUT = 3'b111; // arbitrary value which different from the values above
end
endmodule
module priority_encoder8_3(
IN ,
OUT );
input [7:0] IN;
output [3:0] OUT;
reg [2:0] OUT;
always @ (IN) begin
if(IN[7]) OUT = 4'b0111;
else if(IN[6]) OUT = 4'b0110;
else if(IN[5]) OUT = 4'b0101;
else if(IN[4]) OUT = 4'b0100;
else if(IN[3]) OUT = 4'b0011;
else if(IN[2]) OUT = 4'b0010;
else if(IN[1]) OUT = 4'b0001;
else if(IN[0]) OUT = 4'b0000;
else OUT = 4'b1111;
end
endmodule
仿真结果:
RTL Viewer:
也是依次选通,共8-1=7次。
// module top, 4 input priority encoder with zero input check
module decoder3_8(
IN ,
OUT );
input [2:0] IN;
output [7:0] OUT;
reg [7:0] OUT;
always @ (IN) begin
case(IN)
3'b000: OUT = 8'b0000_0001;
3'b001: OUT = 8'b0000_0010;
3'b010: OUT = 8'b0000_0100;
3'b011: OUT = 8'b0000_1000;
3'b100: OUT = 8'b0001_0000;
3'b101: OUT = 8'b0010_0000;
3'b110: OUT = 8'b0100_0000;
3'b111: OUT = 8'b1000_0000;
// full case don't need default. otherwise there must have default
endcase
end
endmodule
module decoder4_16(
IN ,
OUT );
input [3:0] IN;
output [15:0] OUT;
reg [15:0] OUT;
always @ (IN) begin
case(IN)
4'b0000 : OUT = 16'b0000_0000_0000_0001;
4'b0001 : OUT = 16'b0000_0000_0000_0010;
4'b0010 : OUT = 16'b0000_0000_0000_0100;
4'b0011 : OUT = 16'b0000_0000_0000_1000;
4'b0100 : OUT = 16'b0000_0000_0001_0000;
4'b0101 : OUT = 16'b0000_0000_0010_0000;
4'b0110 : OUT = 16'b0000_0000_0100_0000;
4'b0111 : OUT = 16'b0000_0000_1000_0000;
4'b1000 : OUT = 16'b0000_0001_0000_0000;
4'b1001 : OUT = 16'b0000_0010_0000_0000;
4'b1010 : OUT = 16'b0000_0100_0000_0000;
4'b1011 : OUT = 16'b0000_1000_0000_0000;
4'b1100 : OUT = 16'b0001_0000_0000_0000;
4'b1101 : OUT = 16'b0010_0000_0000_0000;
4'b1110 : OUT = 16'b0100_0000_0000_0000;
4'b1111 : OUT = 16'b1000_0000_0000_0000;
endcase
end
endmodule
编译报告:
共使用逻辑单元16个,相当于两个3_8译码器,功能上也可以由两个3_8译码器组合而成,合理。
加法器是很常用的电路,主要常用的形式包括:
输入和输出数据都是无符号的整数,常用于计数器累加和计算地址序号
module unsigned_adder1(
IN1 ,
IN2 ,
OUT );
input [3:0] IN1, IN2;
output [4:0] OUT;
reg [4:0] OUT;
always @(IN1 or IN2) begin
OUT = IN1 + IN2;
end
endmodule
仿真结果:
显然,加法器的输出并不是与输入每一时刻都对应的。输入改变时输出会有延迟,约7.5ns。
上图为0跳变至3,低位为00跳变至11。延迟为7.395ns。
可见:输出不同位的跳变不是同时的,不能做到同时改变,所以会产生过渡的输出状态。共有一个过渡状态。
上图为0跳变至2,低位为00跳变至10。延迟为7.782ns。
如果是10+00,则输出只有一位产生跳变,故没有过渡状态。但若是01+01,则会有进位的过程。先是低位翻转,因为进位低位又翻转回来,然后高位再翻转得到最后的结果。所以进位会增加过渡状态的数目,且增加了延迟时间。
注意一开始为输入4位输出5位。输出5位因为加法会产生进位,多一位储存进位。若输出设置为4为,即不储存进位,则在计算有进位的式子时会产生错误。
可见:无进位时输入与输出相匹配,有进位时输出结果比正确结果小16,即10000B。
输入改为8bit时:
图示电平翻转过程中延迟达到了10.195ns。
翻转时间增加是因为8bit的加法器组合逻辑层级更多,所以延迟更高。但主要延迟还是在输入的高低电平翻转,即图中17.4us至17.408604us的8.604ns,所以延迟增加不是特别大。
输入和输出数据都是2补码形式的有符号数,常用于数字信号处理电路。
module complement_adder1(
IN1 ,
IN2 ,
OUT );
input signed [3:0] IN1, IN2;
output signed [4:0] OUT;
reg signed [4:0] OUT;
always @ (IN1 or IN2) begin
OUT = IN1 + IN2;
end
endmodule
从组合逻辑门的层面而言,补码加法器和无符号加法器的电路逻辑是有较大区别的。代码区别不大是由于EDA工具检测到了"signed"关键字,所以才生成了补码加法器的电路逻辑。
注意补码加法器的输入数据和对应结果在时间上同样是有延迟的,这是组合逻辑的延迟。此处延迟和无符号加法器差不多。
波形图上的数据是带负号的,这是通过选择信号-属性-Radix为 ‘SIgnal Decimal / 有符号的十进制’ 来观察的结果。这说明电路中有的只是高低电平,我们可以根据自己的需要来选择它所代表的含义。
如果输出的位数和输出的位数相同,当结果不超过输出位数可以表示的时候,即同号相加最高位未产生进位借位时,结果正确。在本例中4bit输出可以表示的范围为-8~7,超过范围可以通过±16来修正得到输出结果,当然修正过的结果是错误的。
8bit输入时也与无符号加法器类似,延时略有增加。
与一般加法器的区别:
引入流水线也就是引入了时序逻辑的部分。
一层流水线指输入,输出级寄存器不算一级流水线。
module pipelined_adder1(
IN1 ,
IN2 ,
CLK ,
OUT );
input [3:0] IN1, IN2;
output [4:0] OUT;
input CLK;
reg [3:0] in1_d1R, in2_d1R;
reg [4:0] adder_out, OUT;
always @ (posedge CLK) begin // diff
in1_d1R <= IN1;
in2_d1R <= IN2;
OUT <= adder_out;
end
always @ (in1_d1R or in2_d1R) begin // combinational logic
adder_out = in1_d1R + in2_d1R;
end
endmodule
仿真波形:
细节:
延时为456.289ns-456.067ns=0.222ns,相比没有流水线的毛刺时间区别并不大。且输入与输出不在同一个时钟周期,相隔较大。
输入改为8bit时:
输出延迟略大,但可以在更高的时钟频率下运行。
高位与低位间加一级流水线。
module pipelined2_adder_8bit_input(
IN1 ,
IN2 ,
CLK ,
OUT );
input [7:0] IN1, IN2;
output [8:0] OUT;
input CLK;
reg [7:0] in1_d1R, in2_d1R;
reg [3:0] in1_high4,in2_high4,low4out;
reg [8:0] OUT;
reg low4_carry;
always @ (posedge CLK) begin // input diff
in1_d1R <= IN1;
in2_d1R <= IN2;
end
always @ (posedge CLK) begin // second assembly line
{low4_carry,low4out} <= in1_d1R[3:0] + in2_d1R[3:0];
in1_high4 <= in1_d1R[7:4];
in2_high4 <= in2_d1R[7:4];
end
always @ (posedge CLK) begin // combinational logic
OUT[8:4] = in1_high4 + in2_high4 + low4_carry;
OUT[3:0] = low4out;
end
endmodule
RTL Viewer:
除了输入输出两层流水线外,在高位和低位加法之间又加了一层流水线。
仿真波形:
因为多一层流水线导致一开始延时更大,输入到对应输出延时更长,用了更多的寄存器。
但流水线让加法器可以运行在更高的时钟频率下,提高了大量计算时的效率。
所以用不用流水线需要从资源和效率两方面进行权衡。
流水线的更多介绍请参考这里。
摘录:
第三 使用流水线的优缺点
1) 优点: 流水线缩短了在一个时钟周期内给的那个信号必须通过的通路长度,增加了数据吞吐量,从而可以提高时钟
频率,但也导致了数据的延时。举例如下:
例如:一个 2 级组合逻辑,假定每级延迟相同为 Tpd,
1.无流水线的总延迟就是 2Tpd,可以在一个时钟周期完成,但是时钟周期受限制在 2Tpd;
2.流水线:
每一级加入寄存器(延迟为 Tco)后,单级的延迟为 Tpd+Tco,每级消耗一个时钟周期,流水线需要 2 个时钟周期来获得第一个计算结果,称 为首次延迟,它要 2*( Tpd+Tco),但是执行重复操作时,只要一个时钟周期来获得最后的计算结果,称为吞吐延迟( Tpd+Tco)。可见只要 Tco 小于 Tpd,流水线就可以提高速度。 特别需要说明的是,流水线并不减小单次操作的时间,减小的是整个数据的操作时间,请大家认真体会。
2) 缺点: 功耗增加,面积增加,硬件复杂度增加,特别对于复杂逻辑如 cpu 的流水线而言,流水越深,发生需要 hold 流水线或 reset 流水线的情况时,时间损失越大。 所以使用流水线并非有利无害,大家需权衡考虑。
————————————————
版权声明:本文为CSDN博主「Times_poem」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/times_poem/article/details/52033535
先要强调的是,乘法器是一种奢侈品会消耗大量的组合电路逻辑资源,一定要慎重使用。尤其是对于芯片内部没有硬件乘法器的FPGA芯片,需要更加慎重。 为什么会消耗如此多的资源呢,请回想一下我们在草稿纸上手工计算多位数乘法的过程,我们需要做很多次的加法,用电路计算乘法也是类似的。
乘法器的代码和加法器类似,有无符号,有符号的乘法器,另外还可以添加流水线。
module complement_multiplier(
IN1 ,
IN2 ,
OUT );
input signed [3:0] IN1, IN2;
output signed [7:0] OUT;
reg signed [7:0] OUT;
always @ (IN1 or IN2) begin
OUT = IN1 * IN2;
end
endmodule
仿真波形:
补码乘法器的计算结果会扩展出1比特额外的符号位,从波形图中可以看到,最高2个比特几乎总是一样的。
乘法优化可以从乘法的计算方法上入手:
移位相加: A ∗ 3 = A ∗ 2 + A = ( A < < 1 ) + A A ∗ 15 = ( A < < 3 ) + ( A < < 2 ) + ( A < < 1 ) + A A*3=A*2+A=(A<<1)+A\\A*15=(A<<3)+(A<<2)+(A<<1)+A A∗3=A∗2+A=(A<<1)+AA∗15=(A<<3)+(A<<2)+(A<<1)+A
有时候数字电路在一个周期内并不能够完成多个变量同时相加的操作,而对于第二个式子,右侧有四位相加,可能会导致时序不满足计算要求。可以通过流水线来解决。
有些FPGA芯片内部有固化的硬件乘法器,对于这样的芯片,代码中的乘号 * 会变成芯片中的乘法器。
对于内部没有乘法器的FPGA芯片,代码中的乘号就会用逻辑单元拼接成加法器,再用加法器拼接成乘法器。
组合逻辑资源消耗量大:
可以通过选取没有硬件乘法器的FPGA芯片(本例选取Cyclone EP1C6F256C6),对比8bit的乘法器和加法器两者编译之后的资源开销来说明。
乘法器编译报告:
加法器编译报告:
加法器中逻辑单元仅用了9个,而乘法器中逻辑单元用了多达106个,可见乘法器内部组合逻辑之复杂。
计数器是最为常用的时序电路之一,计数器在数字电路里面的作用,就如C程序中的for循环一样。
最简单的计数器只有一个CLK信号和一个计数值Q信号,在实际应用中的计数器可能会出现较多的变化,例如下图所示的各种端口,完成不同的逻辑功能,列举如下:
只有CLK和OV
1
module counter4(
CLK ,
OV );// Simple counter
input CLK ;
output OV ;
reg [1:0] Q, D;
reg OV ;
parameter CNT_MAX_VAL = 3;
always @ (Q) begin // counter combinational logic
if(Q < CNT_MAX_VAL)begin
D = Q + 1'b1;
end
else begin // mayb it's sounds stupid , but u cant change one reg in which always is sentitive to it , which i just done
D = 0;
end
end
always @ (posedge CLK) begin// diff temporal logic
Q <= D;
end
always @ (Q) begin // ov combinational logic
if(Q == CNT_MAX_VAL)
OV = 1;
else
OV = 0;
end
endmodule
2
module counter4(
CLK ,
OV );
input CLK ;
output OV ;
reg [1:0] Q;
reg OV ;
parameter CNT_MAX_VAL = 3;
always @ (posedge CLK) begin// diff and combinational
if(Q < CNT_MAX_VAL)begin
Q <= Q + 1'b1;
end
else begin
Q <= 0;
end
end
always @ (Q) begin // ov combinational logic
if(Q == CNT_MAX_VAL)
OV = 1;
else
OV = 0;
end
endmodule
在第二种写法里我把diff和逻辑电路写在一起了,因为我以前就是这么写的。但我现在觉得第一种分开写的更好一些。分开写可以让计数的组合逻辑部分更清晰,也可以把控制的部分放进组合逻辑电路,更符合实际电路的划分。
根据电路要求给不同的控制信号设置优先级。本例中同步清零CLR的优先级最高,使能EN次之,LOAD最低。
优先级高的写在if嵌套的外层。嵌套向内优先级依次降低。
module counter10(RST , CLK , EN , LOAD , DATA , CLR , OV , Q);
input RST , CLK , EN , LOAD , CLR ;
input [3:0] DATA;
output OV;
output [3:0] Q;// also cntval
reg [3:0] Q , D;
reg OV;
parameter CNT_MAX_VAL = 9;
always @ (CLR or EN or LOAD or DATA or Q) begin // control and counter combinational logic
if (CLR) begin // CLR == 1 then D =0
D = 0;
end
else begin // else EN and LOAD
if (EN) begin
if (LOAD) begin
D = DATA;
end
else begin // then simple counter
if (Q < CNT_MAX_VAL) begin // Q dont reach MAXVAL then count
D = Q + 1'b1;
end
else begin // Q reach MAXVAL then D = 0
D = 0;
end
end
end
else begin // EN == 0 then D unchange
D = Q;
end
end
end
always @ (posedge CLK or posedge RST) begin // diff temporal logic
if(RST)
Q <= 0;
else
Q <= D;
end
always @ (Q) begin // ov combinational logic
if(Q == CNT_MAX_VAL)
OV = 1;
else
OV = 0;
end
endmodule
仿真波形:
观察120ns~200ns的波形:LOAD为1时将DATA的值传给Q,且传送值随DATA变化而变化。
观察280ns~320ns的波形:EN为0,所以计数停止了。而EN屏蔽了LOAD信号,所以DATA的值也没有传给Q。
观察440ns~480ns的波形:CLR同步置零, 在下一时钟上升沿将Q置为0,延迟约为7ns。CLR屏蔽了EN和LOAD信号,所以置零过程不受EN和LOAD的影响。
观察600ns~640ns的波形:因为LOAD控制信号为低电平,所以即使DATA有输入也不会传给Q。
观察740ns~780ns的波形:RST为异步置零,检测到RST为高电平时即开始置零过程,延迟约9ns。
最后看CLK、Q和OV,可以正常计数。
注意仿真时最好在最前面RST一下,把内部的寄存器清零。
有限状态机(Finite State Machine)是数字电路设计中非常常用的模块,其在EDA设计中的地位等同于C语言中的If-else语句。
关键:把要解决的问题映射到状态空间,包括:
比如现在我们要实现一个可乐售卖机的状态机电路,假设可乐3分钱1罐,机器只接受1分钱的硬币,当投入第三个硬币的时候,投出一罐可乐
该电路的状态转移逻辑和电路RTL逻辑如下图所示:
从电路的RTL结构上来看,状态机和计数器的RTL是比较相似的,但是它们的应用含义不同。
具体的状态跳转和输出逻辑如下面的表格定义。当设计状态机时,建议先把这两个表格画出来再开始写代码
状态跳转逻辑表:
当前状态 | 输入 | 次态 |
---|---|---|
ST_0_CENT | 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 | Dont care | ST_0_CENT |
输出逻辑表:
当前状态 | 输出 TINOUT |
---|---|
ST_0_CENT | 0 |
ST_1_CENT | 0 |
ST_2_CENT | 0 |
ST_3_CENT | 1 |
module state_machine_3(
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
此处的代码有一个always专门控制输出,故为moore型。
对于代码编写的规范的状态机,EDA工具可以检测出来,编译之后,可以在Tools-Netlist Viewers-State Machine Viewer 里面看到你写的状态机的状态转移图和表达式:
状态机常用于设计序列信号检测器。
比如要设计一个检测’1011’的检测器。
先画状态转移图:
状态跳转逻辑和输出逻辑在图上都已经很明了了,熟练的话可以不列表。
module detector_1011_moore(CLK , RST , EN , CENT1IN , TINOUT);
input CLK , RST , EN , CENT1IN;
output TINOUT;
parameter ST_0_CENT = 0; //suggest that define states as parameter
parameter ST_1_CENT = 1;
parameter ST_2_CENT = 2;
parameter ST_3_CENT = 3;
parameter ST_4_CENT = 4;
reg [3-1:0] stateR;
reg [3-1:0] next_state;
reg TINOUT;
always @ (CENT1IN or EN or stateR) begin // next state
if (EN == 1) 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_1_CENT ; else next_state = ST_2_CENT ; end
ST_2_CENT : begin if(CENT1IN) next_state = ST_3_CENT ; else next_state = ST_0_CENT ; end
ST_3_CENT : begin if(CENT1IN) next_state = ST_4_CENT ; else next_state = ST_2_CENT ; end
ST_4_CENT : begin if(CENT1IN) next_state = ST_0_CENT ; else next_state = ST_0_CENT ; end
endcase
end
else begin
next_state = stateR;
end
end
always @ (stateR) begin // tinout
if(stateR == ST_4_CENT)
TINOUT = 1;
else
TINOUT = 0;
end
always @ (posedge CLK or posedge RST) begin // diff
if (RST)
stateR <= 0;
else
stateR <= next_state;
end
endmodule
仿真波形:
如图,输入为1010110101100111…,其中第一个1011检测输出正确。
第二个1011时因为0被EN屏蔽了,所以没有输出。而10011中第二个0被EN屏蔽了,所以输出正常。这两处说明EN使能工作符合要求。
串行数据和并行数据之间的相互转换是在接口设计中很常见的功能。
一般而言,数据在FPGA内部都是并行传递的,当通过串行接口协议(例如SPI,I2C,I2S等)把数据从FPGA内部传送到一个外部芯片(例如一片EEPROM存储器或是一片音频DAC)时就需要用到串并转换了。其核心的电路是移位寄存器。
常用的串并转换电路有“串行-并行”和“并行-串行”两种,其RTL电路如下:
module SIPO_shift_register(
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];
always @ (posedge CLK or posedge RST) begin // temporal logic
if(RST)
shift_R[3:0] <= 0;
else
if(EN) begin // EN = 1 , begin inputting
shift_R[3:1] <= shift_R[2:0];
shift_R[0] <= IN;
end
else begin
shift_R[3:0] <= shift_R[3:0]; // EN = 0 , shift_R unchange
end
end
endmodule
module PISO_shift_register(IN , RST , LOAD , EN , CLK , OUT);
input [3:0] IN;
input RST , LOAD , EN , CLK;
output OUT;
reg [3:0] shift_R;
assign OUT = shift_R[3];
always @ (posedge CLK or posedge RST) begin // temporal logic
if (RST)
shift_R[3:0] <= 0;
else
if (EN) begin // EN = 1 , begin inputting
if (LOAD) begin // LOAD = 1 , reload input
shift_R[3:0] <= IN[3:0];
end
else begin // LOAD = 0 , serial output
shift_R[3:1] <= shift_R[2:0];
shift_R[0] <= 0;
end
end
else begin // EN = 0 , shift_R unchange
shift_R[3:0] <= shift_R[3:0];
end
end
endmodule
一开始写的时候乍一看RTL感觉很复杂,但其实理清楚逻辑就清晰了。
先看控制信号的优先级。这里的优先级RST异步置零最高,RST写在敏感信号表内。在模块内部是EN使能信号优先级仅次RST,因为EN为0的话不会改变寄存器的内容,所以先套上EN的判断。然后就是LOAD了。当LOAD为1时重新采样输入,当LOAD为0时进行移位寄存。
这样三个if的嵌套就很明了了,写起来也就很容易了。
仿真波形:
如图,输入有四位,所以四个clk时钟重新采样一次输入。
输出为1010 0010 0000 1011 1001 0100 0001 ,与输入相符。