【原创】Verilog实现常见数据结构计划(二)栈

一、栈的 C 语言实现

1.1 顺序栈的结构定义

data为栈中每个元素的数据值;top为栈顶的指针,在计算机体系中也称作 sp 指针

/* 数据结构定义 */
typedef struct
{
    int data[MAXSIZE];  // 栈数据空间
    int top;            // 栈顶指针
}SqStack;   // 顺序栈

1.2 顺序栈的初始化

在栈中,我们定义 sp 指针初始指向-1 ,表示这是一个空栈,而当 sp 指向 0 时则代表栈中存有一个数据。

// 栈初始化
void Stack_Init(SqStack *S)
{
    S->top = -1;
    for (unsigned int i = 0; i < MAXSIZE; i++)
        S->data[i] = 0;
}

1.3 顺序栈的写入

向栈中写入数据又称压栈入栈,在将新数据压栈之前需要判断栈是否已经写满,方法是当 sp 指针指向栈空间的最顶端时则认为栈已满。若栈未满,则将 sp 指针自增以指向下一个存储单元,然后将数据存入这个存储单元。

// 压栈操作
char Push(SqStack *S, int e)
{
    if( S->top == MAXSIZE - 1 )
    {
        return -1;          // 栈满
    }

    S->top++;               // 栈顶自增
    S->data[S->top] = e;    // 数据e压栈
    return 1;
}

1.4 顺序栈的读出

从栈中读出数据又称弹栈出栈,在执行读数据操作之前需要判断栈是否已经读空,方法是当 sp 指针指回 -1 时认为栈已经读空。若栈未空,则先读出当前 sp 指针指向空间的数据,然后将 sp 指针自减,指向上一个存储单元。

// 弹栈操作
char Pop(SqStack *S, int *e)
{   
    if( S->top == -1 )
    {
        return -1;          // 栈空
    }

    *e = S->data[S->top];   // 弹出的数据赋值给e
    S->top--;               // 栈顶自减
    return 1;
}

二、栈的 Verilog 实现

堆栈是现代计算机设备不可或缺的组成部分之一,局部变量、跳转语句、函数跳转的现场保存等,都需要使用堆栈(栈)来实现。目前主流的计算机及嵌入式处理器大都采用内存实现堆栈的方式,即在主存中开辟一块空间来保存堆栈数据。然而在一些低端嵌入式微控制器,例如 PIC 单片机中则会使用硬件堆栈,因为它们的内存不够大而且对堆栈大小的需求也不高。

基于上述的软件堆栈原理,本节使用 Verilog HDL 实现硬件同步堆栈并给出仿真测试。

2.1 Verilog 实现代码

以下为栈缓存的 verilog 实现代码。

① sp 指针在空栈时为 -1 (补码形式),并且约束其在栈顶和栈底的自增条件。
② 栈的判满和判空使用两个一段状态机实现。
③ 参数WRITE_GUARD可配置此栈缓存为写满保护无写满保护模式。写满保护模式即是当栈满后,再将新数据压栈时不会将栈顶的旧数据冲刷掉。WRITE_GUARD = "ON"时打开写满保护、WRITE_GUARD = "OFF"时关闭写满保护。

//**********************************************
//COPYRIGHT(c)2021, XXX University
//All rights reserved.
//
//File name     : Sync_Stack.v
//Module name   : Sync_Stack	
//Full name     :
//Author 		: XXX
//Email 		: 
//
//Version 		:
//This File is  Created on 2021-09-24 13:27:27
//------------------Discription-----------------
//
//----------------------------------------------
//-------------Modification history-------------
//Last Modified by 	: XXX
//Last Modified time: 2021-09-24 13:27:27
//Discription 	:
//----------------------------------------------
//TIMESCALE
`timescale 1ns/1ns
//DEFINES
//`include ".v"
//----------------------------------------------

/*** 硬件堆栈 LIFO ***/ 
module Sync_Stack #(
	parameter	WRITE_GUARD =   "ON" ,	// 模式配置:“ON”开启栈顶写保护; “OFF”关闭写保护
	parameter	DATA_WIDTH	=	32   ,	// 数据位宽
	parameter	STACK_WIDTH =	8 	  	// log2(堆栈大小)
)
(
	input							sclk	,	// 同步时钟
	input							rst_n	,	// 低复位

	input							wr_en	,	// 写使能
	input		[DATA_WIDTH-1:0]	din		,	// 写数据
	output reg						full	,	// 写满标志

	input							rd_en	,	// 读使能
	output reg	[DATA_WIDTH-1:0]	dout	,	// 读数据
	output reg						empty 		// 读空标志
);


localparam	STACK_DEPTH = 	2**STACK_WIDTH;


reg		[DATA_WIDTH-1:0]	syc_ram	[STACK_DEPTH-1:0];	// RAM
reg		[STACK_WIDTH:0]	sp;								// SP指针
wire	[STACK_WIDTH:0]	index = $signed(sp) + 1;		 // ram元素索引


// RAM读写控制
generate
	// 堆栈写满保护模式
	if( WRITE_GUARD == "ON" ) begin
		reg	guard_flag;	// 栈顶写满保护标志
		always @(posedge sclk or negedge rst_n) begin : wr_rd
			integer i;
			if ( rst_n == 1'b0 ) begin
				for( i=0; i<STACK_DEPTH; i=i+1 )
					syc_ram[i] <= 'd0;
				dout <= 'd0;
				guard_flag <= 1'b0;
			end
			else begin
				if( wr_en & rd_en ) begin
					dout <= din;
					guard_flag <= 1'b0;
				end
				else if( wr_en && (index < STACK_DEPTH) ) begin
					if( index < (STACK_DEPTH - 1) ) begin
						syc_ram[index] <= din;
					end
					else begin
						if( guard_flag == 1'b0 ) begin
							syc_ram[index] <= din;
							guard_flag <= 1'b1;
						end
					end
				end
				else if( rd_en ) begin
					dout <= syc_ram[index];
					guard_flag <= 1'b0;
				end
			end
		end
	end
	// 无写满保护模式
	else begin
		always @(posedge sclk or negedge rst_n) begin : wr_rd
			integer i;
			if ( rst_n == 1'b0 ) begin
				for( i=0; i<STACK_DEPTH; i=i+1 )
					syc_ram[i] <= 'd0;
				dout <= 'd0;
			end
			else begin
				if( wr_en & rd_en ) begin
					dout <= din;
				end
				else if( wr_en && (index < STACK_DEPTH) ) begin
					syc_ram[index] <= din;
				end
				else if( rd_en ) begin
					dout <= syc_ram[index];
				end
			end
		end
	end
endgenerate


// SP指针控制
always @(posedge sclk or negedge rst_n) begin
	if ( rst_n == 1'b0 ) begin
		sp <= -1;
	end
	else begin
		if( wr_en & rd_en ) begin
			sp <= sp;
		end
		else if( wr_en && (index < STACK_DEPTH - 1) ) begin
			sp <= sp + 1'b1;
		end
		else if( rd_en && (index > 'd0) ) begin
			sp <= sp - 1'b1;
		end
		else begin
			sp <= sp;
		end
	end
end


// 栈满判断状态机
reg	full_state;
always @(posedge sclk or negedge rst_n) begin
	if ( rst_n == 1'b0 ) begin
		full_state <= 1'b0;
		full  <= 1'b0;
	end
	else begin
		case( full_state )
			// 未满状态
			1'b0 : begin
				if( wr_en && (index == STACK_DEPTH - 1) ) begin
					full  <= 1'b1;
					full_state <= 1'b1;
				end
				else begin
					full  <= 1'b0;
					full_state <= 1'b0;
				end
			end

			// 栈满状态
			1'b1 : begin
				if( rd_en ) begin
					full  <= 1'b0;
					full_state <= 1'b0;
				end
				else begin
					full  <= 1'b1;
					full_state <= 1'b1;
				end
			end
		endcase
	end
end


// 栈空判断状态机
reg empty_state;
always @(posedge sclk or negedge rst_n) begin
	if ( rst_n == 1'b0 ) begin
		empty_state <= 1'b1;
		empty <= 1'b1;
	end
	else begin
		case( empty_state )
			// 未空状态
			1'b0 : begin
				if( rd_en && (index == 'd0) ) begin
					empty  <= 1'b1;
					empty_state <= 1'b1;
				end
				else begin
					empty  <= 1'b0;
					empty_state <= 1'b0;
				end
			end

			// 栈空状态
			1'b1 : begin
				if( wr_en ) begin
					empty  <= 1'b0;
					empty_state <= 1'b0;
				end
				else begin
					empty  <= 1'b1;
					empty_state <= 1'b1;
				end
			end
		endcase
	end
end


endmodule

2.2 仿真测试

Testbench 代码如下所示。

//**********************************************
//COPYRIGHT(c)2021, XXX University
//All rights reserved.
//
//File name     : testbench.v
//Module name   : testbench	
//Full name     :
//Author 		: XXX
//Email 		: 
//
//Version 		:
//This File is  Created on 2021-09-24 13:27:08
//------------------Discription-----------------
//
//----------------------------------------------
//-------------Modification history-------------
//Last Modified by 	: XXX
//Last Modified time: 2021-09-24 13:27:08
//Discription 	:
//----------------------------------------------
//TIMESCALE
`timescale 1ns/1ns
//DEFINES
//`include ".v"
//----------------------------------------------


`define DATA_WIDTH		32
`define STACK_WIDTH		4

module testbench;

reg							sclk   ;
reg							rst_n  ;
  
reg							wr_en  ;
reg		[`DATA_WIDTH-1:0]	din    ;
wire						full   ;
  
reg							rd_en  ;
wire 	[`DATA_WIDTH-1:0]	dout   ;
wire						empty  ;


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


// Main
initial begin
	rst_n = 0;
	wr_en = 0;
	din = 0;
	rd_en = 0;
	#50
	rst_n = 1;

	#50
	repeat( 2**`STACK_WIDTH + 1 ) begin
		@(posedge sclk) begin
			wr_en <= 1'b1;
			din   <= din + 1;
		end
	end

	@(posedge sclk)
		wr_en <= 1'b0;


	repeat( 2**`STACK_WIDTH + 1 ) begin
		@(posedge sclk) begin
			rd_en <= 1'b1;
		end
	end

	@(posedge sclk)
		rd_en <= 1'b0;




	#50
	repeat( 2**`STACK_WIDTH/2 ) begin
		@(posedge sclk) begin
			wr_en <= 1'b1;
			din   <= din + 1;
		end
	end

	@(posedge sclk)
		wr_en <= 1'b0;

	#50
	repeat( 2**`STACK_WIDTH/2 ) begin
		@(posedge sclk) begin
			wr_en <= 1'b1;
			din   <= din + 1;
		end
	end

	@(posedge sclk)
		wr_en <= 1'b0;


	repeat( 2**`STACK_WIDTH/2 ) begin
		@(posedge sclk) begin
			rd_en <= 1'b1;
		end
	end

	@(posedge sclk)
		rd_en <= 1'b0;

	#50
	repeat( 2**`STACK_WIDTH/2 ) begin
		@(posedge sclk) begin
			rd_en <= 1'b1;
		end
	end

	@(posedge sclk)
		rd_en <= 1'b0;


	#100
	$stop;
end



// 同步堆栈例化
Sync_Stack #(
		.WRITE_GUARD("ON"),
		.DATA_WIDTH (`DATA_WIDTH),
		.STACK_WIDTH(`STACK_WIDTH)
	) U_Sync_Stack (
		.sclk  (sclk),
		.rst_n (rst_n),

		.wr_en (wr_en),
		.din   (din),
		.full  (full),

		.rd_en (rd_en),
		.dout  (dout),
		.empty (empty)
	);


endmodule

开启写满保护模式的栈缓存读写波形如下。测试中栈深度为 16 ,对其写 17 次,可见第 17 个数据 “32’d17” 并没有写入栈中。随后对其读 17 次,可以看到之前写入的数据都按 LIFO 顺序被读了出来。
在这里插入图片描述

关闭写满保护模式的波形如下。测试激励同上,可以看到读取时原来的数据 “32’d16” 的位置被 “32’d17” 覆盖了。
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值