【原创】Verilog实现常见数据结构计划(一)顺序线性表

本文介绍了如何使用Verilog实现顺序线性表的插入操作,包括顺序表的概念、插入步骤以及相关Verilog代码实现。通过状态机设计,实现了在指定位置插入数据并更新线性表长度的功能。同时提供了testbench进行仿真验证,验证了在地址5处成功插入数据123,表长增加。顺序表插入操作的时间复杂度为O(n),在硬件实现时可能会消耗较多资源。
摘要由CSDN通过智能技术生成

引言

数据结构不仅仅是一门软件基础课,同时也是一门硬件基础课,可以说是数字设计的入门课。数据结构里详细介绍了链表、队列、堆栈、哈希等基本底层操作,这些底层操作在一些高级语言中都被封装好了,但是在做硬件设计时都是需要考虑的,如内存分配和存储问题,在硬件设计中是没有编译器来帮我们完成的,都需要一步一步设计来完成。
这里将分几期来演示使用Verilog实现常用数据结构的操作,本文实现Verilog实现顺序线性表的插入操作


一、顺序线性表

顺序表的定义是用一段地址连续的存储单元一次存储线性表的数据元素,而C语言中的数组本质上也是存储器中的一片地址连续的空间,所以顺序表在C中常用一个一维数组实现(这里指非变长数组)。
描述顺序表需要三个属性:存储空间的首地址线性表实时长度存储空间最大容量。

顺序表的 i 位置插入数据的步骤:
1. 从最后一个数据向前遍历到第 i 个位置,将它们一个一个向后移动 1 位。
2. 将要插入的数据插进 i 处。
3. 表长自增 1 。

二、顺序线性表插入操作模块设计

Verilog代码如下:

`timescale	1ns/1ns

module	seqlist(
	input				clk,
	input				rst_n,

	// 顺序表插入操作
	input		[9:0]	wr_addr,	// 插入地址
	input		[7:0]	wr_data,	// 插入数据
	input				wr_valid,	// 数据有效信号
	output	reg			wr_done,	// 插入完成信号
	
	// 顺序表读取操作
	input		[9:0]	rd_addr,	// 读地址
	input				rd_valid,	// 读使能
	output	reg	[7:0]	rd_data,	// 读数据
	output		[9:0]	list_length	// 线性表长度
);


// parameter define
localparam	IDLE 	= 2'b01;
localparam	SHIFT 	= 2'b10;


reg	[7:0] dram	[1023:0];	// 内存空间
reg	[9:0] cnt;				// 移位计数器
reg	[9:0] length;			// 线性表长度
reg	[1:0] state;			// 状态寄存器


assign list_length = length;

// 内存读取
always @(*) begin
	if( rd_valid == 1'b1 )
		rd_data <= dram[rd_addr];
	else
		rd_data <= rd_data;
end


// 顺序表插入操作
integer	i;
always @(posedge clk or negedge rst_n) begin
	if (!rst_n) begin
		wr_done <= 1'b0;
		cnt <= 10'd256 - 1'b1;
		length <= 10'd256;		// 线性表初始长度为256
		state <= IDLE;
		// 内存空间初始化
		for( i=0; i<1024; i=i+1 )
			dram[i] <= i[7:0];
	end
	else begin
		case( state )
			IDLE : begin 
				wr_done <= 1'b0;
				cnt <= length - 1'b1;
				if( wr_valid == 1'b1 )
					state <= SHIFT;
				else
					state <= IDLE;
			end

			SHIFT : begin
				if( cnt >= wr_addr ) begin
					cnt <= cnt - 1'b1;
					dram[cnt+1] <= dram[cnt];
				end
				else begin
					length <= length + 1'b1;
					dram[wr_addr] <= wr_data;
					state <= IDLE;
					wr_done <= 1'b1;
				end
			end

			default : ;
		endcase
	end
end


endmodule

此模块定义了两个状态:空闲态(IDLE) 和 移位状态(SHIFT)。
1. 在空闲态中,等待插入操作启动信号 wr_valid 有效。此信号有效后进入移位状态。
2. 在移位状态中, 从最后一个数据向前遍历到第 wr_addr 个位置,将它们一个一个向后移动 1 位。wr_addr 就是要插入数据的地址。等待移位过程结束后将待插入数据插入地址 wr_addr 中,表长加一,并且拉高状态信号 wr_done 一个时钟周期,表示插入操作完成。

三、testbench设计

testbench代码如下:

`timescale	1ns/1ns

module	seqlist_tb;

// input signal
reg			clk;
reg			rst_n;

reg	 [9:0]	wr_addr;
reg  [7:0]	wr_data;
reg			wr_valid;

reg	 [9:0]	rd_addr;
reg			rd_valid;

// output signal
wire		wr_done;
wire [7:0]	rd_data;
wire [9:0]	list_length;


// reg define
reg	[3:0] tb_state;


// 复位
initial begin
	rst_n = 1;
	#50	 rst_n = 0;
	#100 rst_n = 1;
end


// 生成时钟
initial begin
	clk = 0;
	forever #5 clk = ~clk;
end


// 信号初始化 打开dat文件
integer handle;
initial begin
	wr_addr = 10'd0;
	wr_data = 8'd0;
	wr_valid = 0;
	rd_addr = 10'd0;
	rd_valid = 0;
	tb_state = 4'b0001;
	// 打开dat文件
	handle  =  $fopen("C:/Users/xumingwei/Desktop/seqlist/sim/data_out.dat");
	$fdisplay(handle,"数据   地址   表长\n");
end


// 激励文件状态机
always @(posedge clk or negedge rst_n) begin
	if( !rst_n ) begin
		wr_addr <= 10'd0;
		wr_data <= 8'd0;
		wr_valid <= 0;
		rd_addr <= 10'd0;
		rd_valid <= 0;
		tb_state <= 4'b0001;
	end
	else begin
		wr_valid <= 1'b0;
		rd_valid <= 1'b0;
		case( tb_state )
		// 向顺序表中插入数据
			4'b0001 : begin
				wr_addr <= 10'd5;
				wr_data <= 8'd123;
				wr_valid <= 1'b1;
				tb_state <= 4'b0010;
			end

		// 等待数据插入完毕
			4'b0010 : begin
				if( wr_done == 1'b1 ) begin
					tb_state <= 4'b0100;
					rd_valid <= 1;		// 读出地址0的数据
				end
				else
					tb_state <= 4'b0010;
			end

		// 读出并打印内存空间的数据
			4'b0100 : begin
				if( rd_addr < 10'd1023 ) begin
					rd_addr <= rd_addr + 1'b1;
					rd_valid <= 1;
					// 将内存空间中的数据写入dat文件中
					$fdisplay(handle,"%d   %d   %d",rd_data,rd_addr,list_length);
				end
				else if( rd_addr == 10'd1023 ) begin
					tb_state <= 4'b1000;
					rd_addr <= 10'd0;
					$fdisplay(handle,"%d   %d   %d",rd_data,rd_addr,list_length);
				end
			end

			4'b1000 : ;		// 仿真停止 

			default : ;   

		endcase
	end	
end


// top例化
seqlist  tb_seqlist(
	.clk		( clk ),
	.rst_n		( rst_n ),

	.wr_addr	( wr_addr ),	
	.wr_data	( wr_data ),	
	.wr_valid	( wr_valid ),	
	.wr_done	( wr_done ),	

	.rd_addr	( rd_addr ),	
	.rd_valid	( rd_valid ),	
	.rd_data	( rd_data ),
	.list_length( list_length )
);


endmodule

在 testbench 中操作顺序表模块向表的地址 10'd5 中插入数据 8'd123 ,并将插入后的整个内存空间中的数据打印至文件 data_out.dat 中,便于直观地观察实验结果。

四、仿真结果

编写自动化仿真脚本,在Modelsim中启动仿真,可见写入后的 data_out.dat 文件如下图所示。在地址 5 的位置成功插入了数据 123 ,且表长自增为257(初始表长设为256)

在仿真波形中也可以看到,当 rd_valid (读存储器使能)拉高后,顺序读出存储空间中的新数据,可见在地址 5 处插入了数据 123 。


顺序表的插入和删除需要移动大量数据,此时时间复杂度是O(n)。即使在硬件中可以并行移动所有数据,但也会消耗大量硬件资源,尤其当存储空间很大时。
由此看来,无论硬件软件,顺序表的缺点都是一样的,不适合进行插入和删除操作,会浪费大量时间或硬件面积。 

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值