Xilinx-Verilog-学习笔记(15):Verilog基础语法演示(2)
一、移位寄存器
移位寄存器原理:
按照时钟节拍,一位一位移进来,起初寄存器中的值为0,lvds发送过来的数据为lvds d1。本实验采取从左侧移入的方法。
第一个时钟周期:{lvds_d1 0000_000}
第二个时钟周期:{lvds_d2 lvds_d1 000_000}
······
第八个时钟周期:{lvds_d8 lvds_d7 ···· lvds_d1}
其中,花括号为位拼接符,新移进来的1位与之前移位寄存器中的前7位进行拼接。
1、design文件
//接口区
module ex_shift_reg(
input wire lvds_clk, //lvds的时钟
input wire rst_n, //复位
input wire lvds_d, //lvds输入的数据
output reg [7:0] o_lvds_d //寄存器输入的数据
);
//变量定义区
reg [7:0] shift_reg; //移位寄存器,共8位
reg [2:0] s_cnt; //计数器
reg s_flag; //标志位
reg s_flag_dly1, s_flag_dly2; //打两拍延迟
//一位一位从左侧移入
always @(posedge lvds_clk or negedge rst_n)
if(rst_n == 1'b0)
shift_reg <= 'd0;
else
shift_reg <= {lvds_d,shift_reg[7:1]};
//计数器记移8次
always @(posedge lvds_clk or negedge rst_n)
if(rst_n == 1'b0)
s_cnt <= 'd0;
else
s_cnt <= s_cnt + 1'b1;
//移位寄存器8位被占满
always @(s_cnt)
if(s_cnt == 3'd7)
s_flag <= 1'b1;
else
s_flag <= 1'b0;
//将寄存器中的数,输出出去
always @(posedge lvds_clk or negedge rst_n)
if(rst_n == 1'b0)
o_lvds_d <= 'd0;
else if(s_flag_dly2 == 1'b1)
o_lvds_d <= shift_reg;
//打两拍
always @(posedge lvds_clk)
{s_flag_dly2,s_flag_dly1} <= {s_flag_dly1, s_flag};
endmodule
如何写全敏感列表:
条件表里的所有变量,以及赋值语句右边的所有变量。
打两拍:
除s_flag寄存器外,又额外定义了两个寄存器s_flag_dly1和s_flag_dly2。s_flag的值先移到s_flag1中,再移到s_flag2中,共需要2个时钟周期。
2、testbench文件
`timescale 1ns/1ns
module tb_ex_shift_reg;
reg lvds_clk; //lvds的时钟
reg rst_n; //复位
reg lvds_d; //lvds输入的数据
wire [7:0] o_lvds_d; //寄存器输入的数据
reg [0:0] mem1x16 [15:0]; //存储区变量,位宽为1,深度为16
reg [3:0] i_30; //取低四位
always #10 lvds_clk <= ~lvds_clk;
initial begin
lvds_clk = 0;
rst_n = 0;
lvds_d = 0;
#100
rst_n = 1;
end
initial begin
$readmemb("./data.txt",mem1x16);
end
initial begin
#100
lvds_send_d();
end
ex_shift_reg ex_shift_reg_inst(
.lvds_clk (lvds_clk), //lvds的时钟
.rst_n (rst_n), //复位
.lvds_d (lvds_d), //lvds输入的数据
.o_lvds_d (o_lvds_d) //寄存器输入的数据
);
task lvds_send_d();
integer i;
begin
for(i=0;i<255;i=i+1)
begin
@(posedge lvds_clk);
lvds_d <= mem1x16[i[3:0]];
i_30 <= i[3:0];
end
end
endtask
endmodule
mem变量的使用方法:
存储器:
有地址,有深度。
左边为存储器位宽,中间是变量名字, 右边是深度
reg [0:0] mem1x16 [15:0];
mem变量填充方式:
$readmemh $readmemb
h为十六进制填充,b为二进制填充。
$readmemb("./data.txt",mem1x16);
读取方式:
mem1x16[i[3:0]]
一次读4位。
3、仿真波形
解析:lvds_d为lvds接收进来的数据,该数据通过将外部数据读入到存储器mem1x16中,之后从存储器中一次读1位到lvds_d中,因此lvds_d的数据依次为1010_1010_1000_1000。在下一个时钟上升沿来临时,将lvds_d的值拼接到移位寄存器的最高位(见拼接示意)。移位寄存器计数器的作用是用来记是否移完8位,如果移完则移位标志位置1,表示移位寄存器中8位已满,可以输出。如图所示,当使用s_flag时,对应的shift_reg提前了2拍,所以需要推后2拍在输出。
拼接示意:
第一个时钟周期:{1 0000_000}
第二个时钟周期:{0 1000_000}
第三个时钟周期:{1 0100_000}
第四个时钟周期:{0 1010_000}
第五个时钟周期:{1 0101_000}
第六个时钟周期:{0 1010_100}
第七个时钟周期:{1 0101_010}
第八个时钟周期:{0 1010_101}
最终输出来为8’h01010101,即为8’h55
二、modelsim脚本仿真
1、Tcl语言
2、do文件
此run.do文件为上面移位寄存器的。
#退出当前仿真工程
quit -sim
#清除命令行显示信息
.main clear
#创建一个库(目录)
vlib lib
vlib ./lib/work
#映射逻辑库的名字到物理路径上
vmap work ./lib/work
#将所有东西都编译到work库里面
vlog -work work .tb_ex_shift_reg.v
vlog -work work ./../design/*.v
#优化
vsim -voptargs=+acc work.tb_ex_shift_reg
#添加波形
add wave -divider {tb_module}
add wave tb_ex_shift_reg/lvds_d
add wave tb_ex_shift_reg/o_lvds_d
add wave tb_ex_shift_reg/i_30
add wave tb_ex_shift_reg/lvds_clock
add wave tb_ex_shift_reg/rst_n
add wave -divider {module_inst}
add wave tb_ex_shift_reg/tb_ex_shift_reg_inst/*
#启动仿真
run 100us
三、mealy型状态机
这里以检测11101序列为例,状态迁移图如下图:
1、design文件
//检测11101序列
module mealy(
input wire s_clk,
input wire rst_n,
input wire A,
output reg k
);
//定义了6种状态
parameter S1 = 6'b00_0001;
parameter S2 = 6'b00_0010;
parameter S3 = 6'b00_0100;
parameter S4 = 6'b00_1000;
parameter S5 = 6'b01_0000;
parameter S6 = 6'b10_0000;
reg [5:0] Curr_st;
always @(posedge s_clk or negedge rst_n)
if(rst_n == 0)
Curr_st <= S1;
else
case(Curr_st)
S1:if(A == 1'b1)
Curr_st <= S2;
else
Curr_st <= S1;
S2:if(A == 1'b1)
Curr_st <= S3;
else
Curr_st <= S1;
S3:if(A == 1'b1)
Curr_st <= S4;
else
Curr_st <= S1;
S4:if(A == 1'b0)
Curr_st <= S5;
else
Curr_st <= S4;
S5:if(A == 1'b1)
Curr_st <= S6;
else
Curr_st <= S1;
S6:Curr_st <= S1;
default:Curr_st <= S1;
endcase
always @(posedge s_clk or negedge rst_n)
if(rst_n == 1'b0)
k <= 1'b0;
else if(Curr_st == S5 && A == 1'b1)
k <= 1'b1;
else
k <= 1'b0;
endmodule
2、testbench文件
`timescale 1ns/1ns
module tb_mealy;
reg s_clk;
reg rst_n;
reg a_in;
wire k_out;
initial begin
s_clk = 0;
rst_n = 0;
#100
rst_n = 1;
end
initial begin
#200;
rand_bit();
end
always #10 s_clk <= ~s_clk;
mealy mealy_inst(
.s_clk (s_clk),
.rst_n (rst_n),
.A (a_in),
.k (k_out)
);
task rand_bit();
integer i;
begin
for(i=0;i<255;i=i+1)
begin
@(posedge s_clk);
a_in <= {$random} % 2; //产生0,1随机数
end
end
endtask
endmodule
知识点:
$random %10:产生-10到9的随机数。
${random}%10:产生0到10的随机数。
3、do文件
quit -sim
.main clear
vlib ./lib/
vlib ./lib/work_a/
vlib ./lib/design/
vmap base_space ./lib/work_a/
vmap design ./lib/design/
vlog -work base_space ./tb_mealy.v
vlog -work design ./../design/*.v
#-t指运行仿真的时间精度是ns
#-L是链接库关键字
vsim -t ns -voptargs=+acc -L base_space -L design base_space.tb_mealy
#创建虚拟信号
virtual type {
{01 S1}
{02 S2}
{04 S3}
{08 S4}
{10 S5}
{20 S6}
} vir_new_signal
add wave -divider {tb_mealy_1}
add wave tb_mealy/*
add wave -divider {mealy}
#顶层/例化的名字/* 其中*是通配符
add wave tb_mealy/mealy_inst/*
#创建一个vir_new_signal 类型的信号,也就是把Curr_st进行类型转换
virtual function {(vir_new_signal)tb_mealy/mealy_inst/Curr_st} new_state
add wave -color blue tb_mealy/mealy_inst/new_state
run 1us
知识点:
(1)vmap将base_space映射到实际目录./lib/work_a/下,vlog在编译过程中将./tb_mealy.v编译后的结果放到了base_space下,即放到了目录./lib/work_a/下。
(2)-t ns表示以ns级进行仿真。
(3)-L base_space表示在仿真时将base_space链接进来。
(4)virtual用于创建虚拟信号,其中信号S1的独热码为6’b000001,即为01,通过S1表示它。 (vir_new_signal) 相当于一种强制类型转换。
4、仿真波形
解析:A信号随机产生0或1,从图中可以看出,new_state的状态变化为S1->S2->S3->S4->S5,这是与Curr_st对应的。当进入S5状态后,且A的值为1,那么下一个时钟沿到来后,进入S6状态。