FPGA杂记5——格雷码转换设计

在此感谢 特权同学,接下来写到的FPGA杂记基本上源于他的B站讲解视频,同时这是他的淘宝商铺链接,感兴趣的同学也可以在B站中看到他更多的一些视频学习资料,一起学习一起成长

1. 显示和监视任务

1.1 显示任务 $display 以及 $write

两者的语法结构大概都是:任务名(“可选字符串+格式”,信号列表)

  • 任务名包括$displayb(二进制)、 $displayh(十六进制)、 $displayo(八进制)以及 $write的其他任务名
  • 可选字符串+格式包括%h(16进制输出)、%c(ASCII输出)、%b(二进制输出)、%s(字符串输出)、%t(以当前时间格式输出显示)等等
  • display和write的区别在于display输出后会自动换行

%0d,加个0会使得输出的数据前面的零或者空格删掉(一般发生在输出要求位数大于实际输出位数的情况下)例如,32位的数据以十进制显示为65,用显示任务不加0的结果是:____(空格)65,但若是要求为%0d,那么输出结果就是65

1.2 监视任务$monitor

$monitor任务在声明后默认开启,只有在其运行期间调用系统任务 $monitoroff 则会关闭监视任务知道系统再次调用系统任务 $monitoron后将重新开启监视

以上两种主要在testbench中调用任务,方便在测试中验证自己的设计、逻辑功能的实现

2. 4位格雷码计数器设计

2.1 设计实现

设计目标:以1S为一个计数周期(注意不是系统时钟),计数值从0一直往上叠加,并将计数值不断转换成格雷码输出

`timescale 1ns/1ps
module vlg_design(
	input i_clk,	//时钟为50MHz,20ns
	input i_rst_n,
	output[3:0] o_gray
    );
/
//1s定时计数
localparam	TIMER_1S_MAX_CNT = 32'd1_000_000_000/20;	//50MHZ的时钟在1s内的最大周期数,1S标志位

reg[31:0] r_cnt;

always @(posedge i_clk)
	if(!i_rst_n) r_cnt <= 'b0;
	else if(r_cnt < (TIMER_1S_MAX_CNT - 1))	r_cnt <= r_cnt + 'b1;
	else r_cnt <= 'b0;

//
//1s计数器
reg[3:0] r_second;
	always @(posedge i_clk)
	if(!i_rst_n) r_second <= 'b0;
	else if(r_cnt == (TIMER_1S_MAX_CNT-1)) r_second <= r_second + 'b1;
	else ;
	
/
//r_second译码为格雷码输出
reg[3:0] r_gray;

always @(posedge i_clk)	
	if(!i_rst_n) r_gray <= 'b0;
	else begin
	case(r_second)
		4'b0000: r_gray <= 4'b0000;
		4'b0001: r_gray <= 4'b0001;
		4'b0010: r_gray <= 4'b0011;
		4'b0011: r_gray <= 4'b0010;
		4'b0100: r_gray <= 4'b0110;
		4'b0101: r_gray <= 4'b0111;
		4'b0110: r_gray <= 4'b0101;
		4'b0111: r_gray <= 4'b0100;
		4'b1000: r_gray <= 4'b1100;
		4'b1001: r_gray <= 4'b1101;
		4'b1010: r_gray <= 4'b1111;
		4'b1011: r_gray <= 4'b1110;
		4'b1100: r_gray <= 4'b1010;
		4'b1101: r_gray <= 4'b1011;
		4'b1110: r_gray <= 4'b1001;
	    4'b1111: r_gray <= 4'b1000;
		default: ;
	endcase
end

assign o_gray = r_gray;	

endmodule

看整个运行过程,各部分都会定义一个寄存器暂时储存输出结果,最后再进行赋值操作

2.2 测试脚本testbench

`timescale 1ns/1ps
module testbench_top();

//参数定义
`define CLK_PERIORD		20		//时钟周期设置为20ns(50MHz)	


//接口申明
reg clk;
reg rst_n;
wire[3:0] o_gray;

	
//对被测试的设计进行例化
vlg_design		uut_vlg_design(
	.i_clk(clk),
	.i_rst_n(rst_n),
	.o_gray(o_gray)
    );	
	

//复位和时钟产生
//时钟和复位初始化、复位产生
initial begin
	clk <= 0;
	rst_n <= 0;
	#1000;
	rst_n <= 1;
end
	
//时钟产生
always #(`CLK_PERIORD/2) clk = ~clk;	


//测试激励产生
integer i;
initial begin
	@(posedge rst_n);	//等待复位完成
	$monitor("o_gray is %b at %0dns",o_gray,$time);
	
	$stop;
end


endmodule

2.3 测试结果

图片

3. 基于查找表的8位格雷码转换

3.1 查找表LUT

查找表是相当于预先存储好结果的数据表,免去了运算过程,从另外一种角度来说相当于ROM。我们接下来利用IP的ROM加入查找表,通过VIVADO创建出ROM的初始化COE文件用于存放LUT的数据

3.2 ROM初始化COE文件

COE文件用于对ROM作初始化赋值,具体格式如下

	memory_initialization_radix = 位数(数据格式);
	memory_initialization_vector = 初始化数据;	//查找表数据

COE文件制作以及使用ROM流程:

  • 打开vivado工程文件点击IP Catalog选择生成ROM(以Block Memory Generator为例)
  • 将打开的ROM的Memory Type改成Single Port ROM
  • Port A Options里面将位宽与数据个数改成相应的数据,同时使能always
  • Other Options中导入COE文件,这里由于没有COE文件可以在线创建一个即可
  • 然后点击OK会弹出一个generate的窗口直接生成即可
  • 回到vivado界面的Sources里面的IP Sources中查看你所配置IP的Instantiation Template中的原语 .veo 文件,并将原语用于你所设计的verilog代码中

这里要注意一下,只要在IP中勾选了Primitives Output Register,那么就意味着你会延时两个时钟周期输出数据,在Summary中也可查看
IP-Summary

3.3 设计代码.v

`timescale 1ns/1ps
module vlg_design(
	input i_clk,	
	input i_rst_n,
	input i_en,
	input[7:0] i_data,
	output o_vld,	//有效信号
	output[7:0] o_gray
    );

//
//o_vld是i_en两个时钟周期的延时
reg[1:0] r_vld;

always@(posedge i_clk)
	if(!i_rst_n) r_vld <= 'b00;
	else r_vld <= {r_vld[0],i_en};
	
assign o_vld = r_vld[1];

//IP ROM_LUT
blk_mem_gen_0 uut_blk_mem_gen_0 (
  .clka(i_clk),    // input wire clka
  .addra(i_data),  // input wire [7 : 0] addra
  .douta(o_gray)  // output wire [7 : 0] douta
);
endmodule

3.4 测试脚本

`timescale 1ns/1ps
module testbench_top();

//参数定义
`define CLK_PERIORD		20		//时钟周期设置为20ns(50MHz)	


//接口申明
reg clk;
reg rst_n;
reg i_en;
reg[7:0] i_data;
wire o_vld;
wire[7:0] o_gray;

	
//对被测试的设计进行例化
vlg_design		uut_vlg_design(
	.i_clk(clk),
	.i_rst_n(rst_n),
	.i_en(i_en),
	.i_data(i_data),
	.o_vld(o_vld),
	.o_gray(o_gray)
    );	
	

//复位和时钟产生
//时钟和复位初始化、复位产生
initial begin
	clk <= 0;
	rst_n <= 0;
	#1000;
	rst_n <= 1;
end
//时钟产生
always #(`CLK_PERIORD/2) clk = ~clk;	


//测试激励产生
initial begin
	i_en <= 'b0;
	i_data <= 'b0;
	
	@(posedge rst_n);	//等待复位完成
	@(posedge clk);
	i_en <= 'b1;
	i_data <= 'b0;
	@(posedge clk);
	repeat(255) begin	//循环叠加256次
		i_data <= i_data + 1;
		@(posedge clk);
	end
	
	i_en <= 'b0;
	i_data <= 'b0;
	#1000;
	
	$stop;
end

//
//实时显示
always@(posedge clk) begin
	if(o_vld) $display("%b",o_gray);
	else ; 
end

endmodule

仿真结果

4. 格雷码转换的快速算法

4.1 算法介绍

  • 二进制码(自然数)的最高位是MSB,最低位是0
  • 二进制码的MSB位直接赋值给格雷码的MSB位
  • 对于次高位(MSB-1)位到最低位0,使用二进制码相邻位异或的方式获取格雷码
    • 若二进制码字的第i位与第i+1位相同,则第 i 位的格雷码为0
    • 若二进制码字的第i位与第i+1位不同,则第 i 位的格雷码为1

举个例子:4位的二进制码1010转换格雷码(不通过LUT,直接通过RTL实现)

位数操作按位输出格雷码
Bit3取MSB位1
Bit21 xor 01
Bit10 xor 11
Bit01 xor 01

最终输出结果:1111

4.2 模块设计

`timescale 1ns/1ps
module vlg_design #
	(parameter MSB = 7)		//参数定义
	(
	input i_clk,
	input i_rst_n,
	input i_en,
	input[MSB:0] i_data,
	output reg o_vld,
	output reg[MSB:0] o_gray
    );

always @(posedge i_clk)	
	if(!i_rst_n) o_vld <= 'b0;
	else o_vld <= i_en;
	

//快速转换设计
always @(posedge i_clk)
	o_gray[MSB] <= i_data[MSB];
	
genvar i;
generate
	for(i=MSB-1;i>=0;i=i-1) begin
		always @(posedge i_clk) begin
			o_gray[i] <= i_data[i] ^ i_data[i+1];
		end
	end
endgenerate

endmodule

4.3 测试脚本

`timescale 1ns/1ps
module testbench_top();

//参数定义
`define CLK_PERIORD		10		//时钟周期设置为10ns(100MHz)	
parameter GRAY_MSB = 7;			//定义参数


//接口申明
reg clk;
reg rst_n;
reg i_en;
reg[GRAY_MSB:0] i_data;
wire o_vld;
wire[GRAY_MSB:0] o_gray;

	
//对被测试的设计进行例化
vlg_design 
	#(.MSB(GRAY_MSB))
	uut_vlg_design(
	.i_clk(clk),
	.i_rst_n(rst_n),
	.i_en(i_en),
	.i_data(i_data),
	.o_vld(o_vld),
	.o_gray(o_gray)
    );	
	

//复位和时钟产生
//时钟和复位初始化、复位产生
initial begin
	clk <= 0;
	rst_n <= 0;
	#1000;
	rst_n <= 1;
end
//时钟产生
always #(`CLK_PERIORD/2) clk = ~clk;	


//测试激励产生
initial begin
	i_en <= 'b0;
	i_data <= 'b0;
	$display("The value of GRAY_MSB is %0d",GRAY_MSB);		
	@(posedge rst_n);		//等待复位完成
	
	@(posedge clk);
	i_en <= 'b1;
	i_data <= 'b0;
	
	repeat(2**(GRAY_MSB+1)-1) begin			//一共有(MSB+1)位,相当于计数值最大为2^(MSB+1)-1
		@(posedge clk);
		i_en <= 'b1;
		i_data <= i_data + 1;
	end
	@(posedge clk);
	i_en <='b0;
	
	$stop;
end

always @(posedge clk)
	if(o_vld) $display("%b",o_gray);
	else ;

endmodule

4.4 测试结果

脚本显示(modelsim):
仿真测试波形:
波形

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值