1.相邻点累加
相邻点累加,顾名思义就是将相邻的点相加求和,本次实验使用的是相邻点十六位累加。其中的data_in为输入信号,也就是要累加的点。sys_in为采样时钟。在输入位考虑到会有正数和负数,所以需要首先转换成补码。然后由于十六位二进制数相加,所以我们需要对其升位,将原来的八位二进制升到十二位防止溢出。相应的波形图如下:
代码编写如下:
//2023.4.14 Bread
//相邻点累加
`timescale 1ns/1ps
module sigma_16P(
clk,
rst,
data_in,
syn_in,
data_out,
syn_out
);
input clk;
input rst;
input[7:0] data_in;//采样信号
input syn_in;//采样时钟
output[11:0] data_out;//累加结果输出
output syn_out;//累加结果同步脉冲
reg syn_in_n1;//syn_in反向延时
wire syn_pulse;//采样时钟上升沿识别脉冲
assign syn_pulse=syn_in&syn_in_n1;
reg[3:0] con_syn;//采样时钟循环计数器
wire[7:0] comp_8;//补码
wire[11:0] d_12;//升位结果
assign comp_8=data_in[7]?{data_in[7],~data_in[6:0]+1}:data_in;//补码运算;
assign d_12={comp_8[7],comp_8[7],comp_8[7],comp_8[7],comp_8};
reg[11:0] sigma;//累加运算
reg[11:0] data_out;
reg syn_out;
always@(posedge clk or negedge rst)
if(~rst)begin
syn_in_n1<=0;con_syn<=0;sigma<=0;data_out<=0;syn_out<=0;
end
else begin
syn_in_n1<=~syn_in;
if(syn_pulse)begin
con_syn<=con_syn+1;
end
if(syn_pulse)begin
if(con_syn==15)begin
sigma<=d_12;
data_out<=sigma;
syn_out<=1;
end
else begin
sigma<=sigma+d_12;
end
end
else begin
syn_out<=0;
end
end
endmodule
值得注意的是,我们在这里因为没有开发板,所以对时钟频率,采样频率包括分频都是使用的人为的值,如果有开发板的话,应按照开发板的时钟频率严格选取采样频率!
在代码中,使用采样时钟进行采样,每当采样时钟上升沿到来时,syn_pulse会产生一个脉冲,同时con_syn会进行计数,算作一个点输入。当计数到15时候(0-15)data_out将结果输出来。同时在采样时钟这边进行一个十六分频,即当输出数据的时候(采样时钟经过十六个周期),由syn_out产生一个脉冲。
testbench如下:
//----testbench----
module sigma_16P_tb;
reg clk,rst;
reg[7:0] data_in;
reg syn_in;
wire[11:0] data_out;
wire syn_out;
sigma_16P sigma_16P(
.clk(clk),
.rst(rst),
.data_in(data_in),
.syn_in(syn_in),
.data_out(data_out),
.syn_out(syn_out)
);
initial begin
clk<=0;rst<=0;data_in=1;syn_in<=0;
#17 rst<=1;
#25000 $stop;
end
always #5 clk<=~clk;
always #100 syn_in<=~syn_in;
endmodule
最后进行ModelSIM仿真,为了方便观察,我们将输入置为+1。
结果如下:
这里可以看到,第一次输出的是15而不是16,这是为什么呢?从代码中不难看出,我们设定的是将复位信号在17ns后置为1,所以第一个con_syn开始计数的时候,我们是没有采样脉冲的,没有采样脉冲就意味着第一个点的值是不会加到sigma的,所以第一次实际只累加了15个点的值,在后面就一直是16了。
2.简单状态机
状态机简写为 FSM(Finite State Machine),也称为同步有限状态机,我们一般简称为状态机,之所以说“同步”是因为状态机中所有的状态跳转都是在时钟的作用下进行的,而“有限”则是说状态的个数是有限的。状态机的每一个状态代表一个事件,从执行当前事件到执行另一事件我们称之为状态的跳转或状态的转移,我们需要做的就是执行该事件然后跳转到一下时间,这样我们的系统就“活”了,可以正常的运转起来了。状态机通过控制各个状态的跳转来控制 流程,使得整个代码看上去更加清晰易懂,在控制复杂流程的时候,状态机优势明显。
本文设计的是一个最简单的状态机,他可以实现上升,下降和保持之间状态的跳转。
上图所示是最简单的三角波发生器,在此基础上我们可以多加入一个保持态,将其转换为梯形。
代码如下:
//2023.5.2 Bread
//简单状态机:三角波发生器
`timescale 1ns/10ps
module tri_gen(
clk,
rst,
d_out
);
input clk;
input rst;
output[8:0] d_out;
reg[1:0] state;//主状态机寄存器;
reg[8:0] d_out;
reg[7:0] con;//计数器,记录保持周期个数
always@(posedge clk or negedge rst)
if(~rst)begin
state<=0;d_out<=0;con<=0;
end
else begin
case(state)
0://上升
begin
d_out<=d_out+1;
if(d_out==299)begin
state<=1;
end
end
1://保持
begin
if(con==200)begin
state<=2;
con<=0;
end
else begin
con<=con+1;
end
end
2://下降
begin
d_out<=d_out-1;
if(d_out==1)begin
state<=3;
end
end
3://保持
begin
if(con==200)begin
state<=0;
con<=0;
end
else begin
con<=con+1;
end
end
endcase
end
endmodule
从代码中我们可以看出,该状态机有四种状态,严格来说只有三种:上升、下降和保持。在编写代码的过程中我们要注意代码的运行顺序,从而选择正确的参数。好比我在下降的状态中不小心写成d_out==0了,在仿真的时候就发现了一个异常的脉冲。经过检查原来是下降的时候溢出了,值变为-1了,这里是需要特别注意的。
testbench如下:
//-----testbench-----
module tri_gen_tb;
reg clk;
reg rst;
wire[8:0] d_out;
tri_gen U1(
.clk(clk),
.rst(rst),
.d_out(d_out)
);
initial begin
clk<=0;rst<=0;
#17 rst<=1;
#80000 $stop;
end
always #5 clk<=~clk;
endmodule
ModelSIM仿真如下:
在观察波形的时候,因为三角波信号是模拟信号,所以我们需要改变一下显示的格式:
然后就可以看见一个个的梯形波了,如果想单纯实现三角波,可以将保持态去掉。但是这里需要注意一个问题:状态机的位数,比如只需要2个状态 简单的就用一位就可以表示了,如果状态多了的话就要留意了!