非整数倍数据位宽转换8to12
所谓非整数倍,就是利用一个cnt去周期性决定寄存器里怎么输出,这个cnt的值,是最小公倍数
寄存器就正常的寄存,怎么输入怎么寄存
`timescale 1ns/1ns
module width_8to12(
input clk ,
input rst_n ,
input valid_in ,
input [7:0] data_in ,
output reg valid_out,
output reg [11:0] data_out
);
reg[7:0]data_lock;
reg[1:0]valid_cnt;
always@(posedge clk,negedge rst_n)begin
if(!rst_n)
data_lock<=0;
else if(valid_in)
data_lock<=data_in;
end
always@(posedge clk,negedge rst_n)begin
if(!rst_n)
valid_cnt<=0;
else if(valid_in)begin
if(valid_cnt==2'd2)
valid_cnt<=0;
else
valid_cnt<=valid_cnt+1;
end
end
always@(posedge clk,negedge rst_n)begin
if(!rst_n)
valid_out<=0;
else if(valid_in&&valid_cnt==1)
valid_out<=1;
else if(valid_in&&valid_cnt==2)
valid_out<=1;
else
valid_out<=0;
end
always@(posedge clk,negedge rst_n)begin
if(!rst_n)
data_out<=0;
else if(valid_in&&valid_cnt==1)
data_out<={data_lock,data_in[7:4]};
else if(valid_in&&valid_cnt==2)
data_out<={data_lock[3:0],data_in};
end
endmodule
为什么不需要2个8bit的寄存器进行数据缓存:
根据时序图, data_out是在两个数据输入之后的下一个时钟周期产生输出,如果采用两个寄存器缓存两个数据,那么第二个数据还没缓存进寄存器后就要输出数据,这样不能实现满足时序要求的数据输出。
根据时序图,数据是在第二个数据到来之后输出,当仅有一个数据到来时,不产生输出,所以内部设计一个计数器(valid_cnt),用来指示数据接收状态。当检测到valid_in拉高时,valid_cnt加1,valid_cnt在0-2之间循环,valid_cnt复位值是0。当valid_cnt是1或2,且valid_in为高时,输出数据,valid_out拉高。
当valid_cnt==1且valid_in为高时,data_out <= {data_lock, data_in[7:4]};当valid_cnt==2且valid_in为高时,data_out <= {data_lock[3:0], data_in}。
就是说,复位的时候,寄存器里一个也没有,所以就不能输出,然后存了一个后,就是1,再来就可以输出,这时输出,就输出存的8个以及新输入的前4个,并且进到2,表示此时寄存器里存的是第二个输入的,而且前半个已经输出过了,该输出后四个,所以此时要输出,就是输出寄存器的后四个以及新输入的全部,这时候寄存器内部实际上也就相当于空了
也就是说valid_cnt决定输出信号,怎么处理寄存器里的信号
电路图
就是时序逻辑就是要用触发器,<=最直接的就是D触发器
整数倍数据位宽转换8to16
`timescale 1ns/1ns
module width_8to16(
input clk ,
input rst_n ,
input valid_in ,
input [7:0] data_in ,
output reg valid_out,
output reg [15:0] data_out
);
reg[0:0]cnt;
reg[7:0]ram;
always@(posedge clk,negedge rst_n)begin
if(!rst_n)
cnt<=0;
else if(valid_in)
cnt<=(cnt)?0:1;
else
cnt<=cnt;
end
always@(posedge clk,negedge rst_n)begin
if(!rst_n)
ram<=0;
else if(valid_in)
ram<=data_in;
else
ram<=ram;
end
always@(posedge clk,negedge rst_n)begin
if(!rst_n)begin
data_out<=0;
valid_out<=0;
end
else if(cnt&&valid_in)begin
data_out<={ram,data_in};
valid_out<=1;
end
else begin
data_out<=data_out;
valid_out<=0;
end
end
endmodule
注意有if情况遗漏时,自动生成锁存器锁存之前的状态
电路图
QN代表反态,即取反
空线代表不利用相应的信号,即不做后续的输出
简单的逻辑就是生成一个简单的D触发器
这里的D信号,就是控制触发器现态Q的信号,做一些处理,就是二路选择器,然后上面是一个与门;上面的没有是因为锁存的是8位的信号,不是0或1
时钟分频(偶数
`timescale 1ns/1ns
module even_div
(
input wire rst ,
input wire clk_in,
output wire clk_out2,
output wire clk_out4,
output wire clk_out8
);
//*************code***********//
reg clk2,clk4,clk8;
always@(posedge clk_in,negedge rst)begin
if(!rst)
clk2<=0;
else
clk2<=~clk2;
end
always@(posedge clk2,negedge rst)begin
if(!rst)
clk4<=0;
else
clk4<=~clk4;
end
always@(posedge clk4,negedge rst)begin
if(!rst)
clk8<=0;
else
clk8<=~clk8;
end
assign clk_out8=clk8;
assign clk_out4=clk4;
assign clk_out2=clk2;
//*************code***********//
endmodule
D触发器
D触发器构建电路图,就是<=右边的为输入的D信号,左边的就是输出的Q现态信号
D触发器接口,有控制触发器触发的时钟信号,以及D信号,就是等式右侧的信号,输出的就是转换后的信号
占空比50%的奇数分频
`timescale 1ns/1ns
module odo_div_or
#(parameter N = 7)
(
input wire rst ,
input wire clk_in,
output wire clk_out7
);
reg [3:0] cnt ;
always @(posedge clk_in or negedge rst) begin
if (!rst) begin
cnt <= 'b0 ;
end
else if (cnt == N-1) begin
cnt <= 'b0 ;
end
else begin
cnt <= cnt + 1'b1 ;
end
end
reg clkp ;
always @(posedge clk_in or negedge rst) begin
if (!rst) begin
clkp <= 1'b0 ;
end
else if (cnt == (N>>1)) begin
clkp <= 1 ;
end
else if (cnt == N-1) begin
clkp <= 0 ;
end
end
reg clkn ;
always @(negedge clk_in or negedge rst) begin
if (!rst) begin
clkn <= 1'b0 ;
end
else if (cnt == (N>>1) ) begin
clkn <= 1 ;
end
else if (cnt == N-1) begin
clkn <= 0 ;
end
end
assign clk_out7 = clkp | clkn ;
endmodule
对于奇数分频电路,主要难点在于50%占空比的实现。单触发沿在奇数分频中是没有办法实现50%占空比的,因此需要考虑使用双边沿加组合逻辑实现50%占空比
clkout7的翻转第一次是在上升沿,第二次是在下降沿。
任意小数分频
请设计一个可以实现任意小数分频的时钟分频器,比如说8.7分频的时钟信号
注意rst为低电平复位
提示:
其实本质上是一个简单的数学问题,即如何使用最小公倍数得到时钟周期的分别频比。
设小数为nn,此处以8.7倍分频的时钟周期为例。
首先,由于不能在硬件上进行小数的运算(比如2.1个时钟这种是不现实的,也不存在3.3个寄存器),小数分频不能做到分频后每个时钟周期都是源时钟的nn倍,也无法实现占空比为1/2,因此,考虑小数分频,其实现方式应当为53个clkout时钟周期是10个clkin时钟周期的8.7倍。
其实和非整数倍数据转位宽差不多,
非整数倍数据转位宽,是用一个计数器,去记录当前是输出到第几个阶段了
用一个寄存器,然后记录当前输入的信号,最后就依据计数器的信号,做出相应的输出,结合此时输出的信号;
不同的点在于,非整数倍的数据位宽转换,每个时钟周期内都会输出信号
而小数分频器,是一定时间内为1,一定时间内为0
分频器,是把信号频率变慢,原来10秒内跳10次,现在只跳1次
思路就是逼近模拟,在大的周期上表现的是小数分频,小的周期里就用奇数分频以及偶数分频逼近,然后一个计数器去记录大的周期,一个计数器就是实现每个小的周期上的分频;那个大的周期上的,决定此时小的周期上是用什么分频器,是奇数还是偶数;然后用一个FLAG标志,去记录当前是用奇数分频器还是偶数分频器,这个FLAG在中间某一点以及整个大周期结束时去进行更换,中间某一点通过计算得到
`timescale 1ns/1ns
module div_M_N(
input wire clk_in,
input wire rst,
output wire clk_out
);
parameter M_N = 8'd87;
parameter c89 = 8'd24; // 8/9时钟切换点
parameter div_e = 5'd8; //偶数周期
parameter div_o = 5'd9; //奇数周期
//*************code***********//
reg [3:0] clk_cnt;
reg [6:0] cyc_cnt;
reg div_flag;
reg clk_out_r;
always@(posedge clk_in or negedge rst) begin
if(~rst)
clk_cnt <= 0;
else if(~div_flag)
clk_cnt <= clk_cnt==(div_e-1)? 0: clk_cnt+1;
else
clk_cnt <= clk_cnt==(div_o-1)? 0: clk_cnt+1;
end
always@(posedge clk_in or negedge rst) begin
if(~rst)
cyc_cnt <= 0;
else
cyc_cnt <= cyc_cnt==(M_N-1)? 0: cyc_cnt+1;
end
always@(posedge clk_in or negedge rst) begin
if(~rst)
div_flag <= 0;
else
div_flag <= cyc_cnt==(M_N-1)||cyc_cnt==(c89-1)? ~div_flag: div_flag;
end
always@(posedge clk_in or negedge rst) begin
if(~rst)
clk_out_r <= 0;
else if(~div_flag)
clk_out_r <= clk_cnt<=((div_e>>2)+1);
else
clk_out_r <= clk_cnt<=((div_o>>2)+1);
end
assign clk_out = clk_out_r;
//*************code***********//
endmodule
在电路图当中,画触发器,就是先看时钟信号是什么,接上去,然后看复位信号是什么,接上去;再就是对应D信号,选触发器,对于<=,就是用D触发器,左侧就是输出的Q现态信号,右侧就是输入的D信号,遇到三目表达式或者else if 选择,就是选择器,选择出一个信号接到D信号上
无占空比要去的奇数分频
所谓占空比,就是在一个周期内0和1所占的比例。
以小数分频器上题为例,要实现小数分频器,在大周期里,flag在周期末以及中间某一点去更新,中间某一点就决定了什么时候反转,也决定了flag的占空比
同理在分频器中,输出分频信号什么时候反转,就是对应中间某一点,但是一定会在周期末进行反转,即cnt==分频数时。
选取中间点时,占空比是百分之五十
`timescale 1ns/1ns
module odd_div (
input wire rst ,
input wire clk_in,
output wire clk_out5
);
//*************code***********//
reg[2:0]cnt;
reg clko;
always@(posedge clk_in,negedge rst)begin
if(!rst)
cnt<=0;
else
cnt<=cnt==4?0:cnt+1;
end
always@(posedge clk_in,negedge rst)begin
if(!rst)
clko<=0;
else
clko<=cnt<=1;
end
assign clk_out5=clko;
//*************code***********//
endmodule
自动贩售机1
状态机
`timescale 1ns/1ns
module seller1(
input wire clk ,
input wire rst ,
input wire d1 ,
input wire d2 ,
input wire d3 ,
output reg out1,
output reg [1:0]out2
);
parameter s0=0,s1=1,s2=2,s3=3,s4=4,s5=5,s6=6;
reg[2:0]cs;
reg[2:0]ns;
wire[2:0]is;
assign is={d1,d2,d3};//将输入的控制信号并在一起
always@(posedge clk,negedge rst)begin
if(!rst)
cs<=s0;
else
cs<=ns;
end
always@(*)begin
case(cs)
s0:begin
case(is)
3'b100:ns=s1;
3'b010:ns=s2;
3'b001:ns=s4;
default:ns=ns;
endcase
end
s1:begin
case(is)
3'b100:ns=s2;
3'b010:ns=s3;
3'b001:ns=s5;
default:ns=ns;
endcase
end
s2:begin
case(is)
3'b100:ns=s3;
3'b010:ns=s4;
3'b001:ns=s6;
default:ns=ns;
endcase
end
default:begin
ns=s0;
end
endcase
end
always@(posedge clk,negedge rst)begin
if(!rst)begin
out1<=0;
out2<=0;
end
else begin
case(ns)
s3:begin
out1<=1;
out2<=0;
end
s4:begin
out1<=1;
out2<=1;
end
s5:begin
out1<=1;
out2<=2;
end
s6:begin
out1<=1;
out2<=3;
end
default:begin
out1<=0;
out2<=0;
end
endcase
end
end
endmodule
自动贩售机2
sel为0买第一种饮料,为1买第二种,s3时就可以买了,不过只能买第一种
一旦能买,次态就只能是0,表示这一次付款的终止,然后找零在输出去执行
输出,只能是MEALY机,即输出与当前状态以及输入信号有关,不能是MOORE机,否则状态太多
是MEALY机就是输出一方面取决于现态,一方面取决于输入信号,这里就是SEL信号,或者可以写一个二段式,
用MOORE机的话,判断输出用次态,则不能确定到底是要买什么,需要SEL以及现态共同确定一个状态才行
`timescale 1ns/1ns
module seller2(
input wire clk ,
input wire rst ,
input wire d1 ,
input wire d2 ,
input wire sel ,
output reg out1,
output reg out2,
output reg out3
);
//*************code***********//
parameter S0=0, S0_5=1, S1=2, S1_5=3, S2=4, S2_5=5, S3=6;
reg[2:0] state, nstate;
always@(posedge clk or negedge rst) begin
if(~rst)
state <= 0;
else
state <= nstate;
end
always@(*) begin
case(state)
S0 : nstate = d1? S0_5:
d2? S1:
nstate;
S0_5 : nstate = d1? S1:
d2? S1_5:
nstate;
S1 : nstate = d1? S1_5:
d2? S2:
nstate;
S1_5 : nstate = ~sel? S0:
d1? S2:
d2? S2_5:
nstate;
S2 : nstate = ~sel? S0:
d1? S2_5:
d2? S3:
nstate;
default: nstate = S0;
endcase
end
always@(*) begin
if(~rst) begin
{out1, out2, out3} = 3'b000;
end
else begin
case(state)
S0, S0_5, S1: {out1, out2, out3} = 0;
S1_5 : {out1, out2, out3} = ~sel? 3'b100: 3'b000;
S2 : {out1, out2, out3} = ~sel? 3'b101: 3'b000;
S2_5 : {out1, out2, out3} = ~sel? 3'b101: 3'b010;
S3 : {out1, out2, out3} = ~sel? 3'b101: 3'b011;
default : {out1, out2, out3} = 3'b000;
endcase
end
end
//*************code***********//
endmodule
游戏机计费程序
`timescale 1ns/1ns
module game_count
(
input rst_n, //异位复位信号,低电平有效
input clk, //时钟信号
input [9:0]money,
input set,
input boost,
output reg[9:0]remain,
output reg yellow,
output reg red
);
always@(posedge clk or negedge rst_n) begin
if(~rst_n) begin
yellow <= 0;
red <= 0;
end
else begin
yellow <= remain<10&&remain;
red <= boost? remain<2: remain<1;
end
end
always@(posedge clk or negedge rst_n) begin
if(~rst_n)
remain <= 0;
else if(boost)
remain <= set ? remain+money:
remain<2? remain:
remain-2;
else
remain <= set ? remain+money:
remain<1? remain:
remain-1;
end
endmodule
`timescale 1ns/1ns
module game_count
(
input rst_n, //异位复位信号,低电平有效
input clk, //时钟信号
input [9:0]money,
input set,
input boost,
output reg[9:0]remain,
output reg yellow,
output reg red
);
always@(posedge clk or negedge rst_n)begin
if(~rst_n)
remain <= 0;
else begin
if(boost==0)
remain <= set==1? remain+money: remain-1;
else if(boost==1)
remain <= set==1? remain+money: remain-2;
end
end
always@(posedge clk or negedge rst_n)begin
if(~rst_n)begin
yellow <= 0;
red <= 0;
end
else begin
if(remain < 1)begin
yellow <= 0;
red <= 1;
end
else if(remain < 10)begin
yellow <= 1;
red <= 0;
end
else begin
yellow <= 0;
red <= 0;
end
end
end
endmodule