FPGA之道(51)数据的存储

前言

第一次见到这么来描述数据存储的书,感觉学了这两年的FPGA白学了,下面内容节选自《FPGA之道》,一起看看作者对于数据存储设计的智慧与经验。

数据的存储

为什么需要数据存储

数据存储功能是实现时序逻辑的基础,并且对于FPGA芯片来说,甚至连组合逻辑也是通过数据存储来实现的(因为FPGA中的组合逻辑是用查找表资源实现的,详见【数据存储的载体】章节),因此对于FPGA设计来说其重要性与必要性不言而喻,而对于FPGA开发者来说,能够灵活的掌握数据存储的使用方法也是一项必备技能。
数据存储功能虽然表面上看起来并没有什么高深之处,但是实际操作起来,难度还是非常大的,因为随着FPGA芯片的集成度越来越高、FPGA设计的复杂度越来越大、业界对FPGA处理速度的期望越来越高,对于FPGA开发者使用数据存储的能力要求也越来越高。因此,在这样一个大形势下,数据存储的要求早已经不是仅仅能够将数据存下来、读出去如此这般简单,而是要求数据存储系统的吞吐量更大、反应速度更快、正确性更高、消耗的资源更少等等。因此为了让数据存储不成为整个FPGA设计的短板,必须要重视对数据存储掌控与使用。

数据存储的载体

提到数据存储,数据到底存到哪里去,是首先应该搞清楚的问题。事实上,具有记忆功能的任何东西,都可以用来做数据存储之用。在本章节,将为大家介绍FPGA开发者可以利用的数据存储的载体。

FPGA芯片内部的载体

先来看看FPGA芯片内部,都有哪些资源可以用来做数据存储的载体。

触发器

触发器,即FPGA逻辑资源块中的Flip-flop,它一般只在时钟的有效边沿到来时更新自己的状态(也有电平敏感型的触发器),而其他时间则会记忆并保持这个状态,因此触发器具有记忆功能,是一种最常用、最被人们熟知的数据存储载体资源。
触发器载体的特点是:一个Flip-flop的记忆力仅为1个bit,而由于规模的不同,一片FPGA芯片中的Flip-flop数量也从几千、几万甚至到几百万不等。

查找表

查找表,即FPGA逻辑资源块中的LUT,它一般用来实现组合逻辑,但是,其实它也是具有记忆功能的。首先,当用来实现组合逻辑时,LUT将会在程序上电的最开始,记住组合逻辑的真值表关系,从而在后续的FPGA工作中能够给出正确的功能响应;其次,FPGA的配置是基于SRAM的,即上电时的配置会在掉电后全部丢掉,而LUT中的内容是可配置的,因此每个LUT其实也就相当于一个小SRAM。由此可见,LUT具有记忆功能,并且它既然能够在每次上电的时候被配置成为不同的内容,那么也应该可以在上电期间被重新配置并保持,因此,LUT的记忆性也并不是单次的,所以在FPGA中LUT也是数据存储的一大利器,只不过当我们在FPGA中实现组合逻辑时,可能并没有意识到自己也是在使用数据存储功能罢了。这也是为什么可以毫不夸张的说,整个FPGA设计实际上最终实现出来几乎(时钟处理单元、DSP等资源不属于数据存储的范畴)就是一个大的数据存储体系。
LUT载体的特点是:若一个LUT的输入端口数为n,那么其记忆力应该为bits,而FPGA芯片中的LUT通常为3~6输入的。一般来说,一片FPGA芯片中的LUT数量和Flip-flop数量是相当的,因此LUT也被称为分布式存储。但需要注意的是,虽然绝大多数的LUT都是可以用来作为数据存储的,但并不是所有LUT都具有被当做小SRAM来使用的特征的。当然,还有些更高级的LUT,它们甚至还可以实现移位寄存器的功能。

块存储

块存储,即FPGA中的BLOCK RAM资源,简称BRAM,它实际上就是在FPGA芯片中嵌入了一些小型的存储器,自然也就是实现数据存储功能的主力军之一。这些BRAM往往也都比较灵活,每一片BRAM,都可以被配置为多种位宽和深度的组合,例如一个4Kbits容量的BRAM,可以配置成为位宽为1bit、深度为4k,也可以配置成为位宽为4bits、深度为1k,等等。
BRAM载体的特点是:一块BRAM的记忆力通常在1kbits、甚至几十kbits以上,而由于规模的不同,一片FPGA芯片中的BRAM数量也从十几、几十甚至到几百不等。

FPGA芯片外部的资源

除了FPGA芯片内部的载体之外,还有很多成熟的、专门的存储芯片可供我们来选择,包括但不限于——SRAM、DRAM、SDRAM、OTPROM、EPROM、EEPROM、FLASH、FIFO甚至硬盘等等。

数据存储的形式、实现及应用场合

如果将数据存储的载体比作不同种类的木材,那么数据存储的形式就好比功能不同的实木家具,例如椅子、桌子、床等等(不好意思,最近正在买家具)。而从使用形式方面来看,可以将数据存储分为几个大类,接下来,就为大家一一介绍FPGA芯片内部各种存储形式的特征、实现载体选择以及一些合适的应用场合。

寄存器

特征简介

在【共同语言篇->数字逻辑电路基础知识->数字逻辑功能单元->时序逻辑基本单元->锁存器与寄存器】小节中,我们介绍了,寄存器其实就是边沿敏感型的触发器。由于其在每一个时钟有效边沿都能完成一次存储的更新,其内部存储的数据可以被任意读取,并且还具有异步置0、置1的功能,因此使用起来非常的灵活、方便。

实现载体

首先,FPGA芯片中的触发器,基本上都是边沿敏感型的触发器,因此实现寄存器的首选载体便是FPGA芯片中的触发器资源。
其次,LUT也可以作为寄存器的实现载体,这是因为:身为时序单元的触发器,其本质也就是在组合逻辑电路中引入恰当的反馈得到的,而LUT是用来实现组合逻辑的,因此只要按照【共同语言篇->数字逻辑电路基础知识->数字逻辑功能单元->时序逻辑基本单元】中各种触发器的电路图来配置和连接若干个LUT,便可实现寄存器所需的功能,例如在【知己知彼篇->FPGA内部资源介绍->逻辑资源块】小节中,就介绍了一种利用LUT实现主从D触发器的方法。注意,一个LUT的记忆力虽然是一个触发器的很多倍,但是要模拟一个触发器的功能,却需要多个LUT协作完成,因此除非出现触发器资源不够用了等极端情况,一般不会考虑使用LUT来作为寄存器的载体。不过有一点例外,那就是有些高级的LUT可以被配置成为移位寄存器的形式,因此当需要移位寄存器的时候,采用这些高级的LUT效果往往更好。

应用场合

寄存器的应用可以说是无处不在:
首先,几乎时序逻辑中的所有中间变量都是用寄存器来存储的。
其次,寄存器也可以用来做一些小规模的数据缓存。
第三,寄存器还具有延迟、抗干扰等特性,因此还可以用来调整数据流中不同信号间的相对位置以及去除信号毛刺等作用。
最后,多个寄存器可以形成多位寄存器以及寄存器阵列,这其中最有典型意义的便是移位寄存器,具体细节可以参考【共同语言篇->数字逻辑电路基础知识->数字逻辑功能单元->时序逻辑基本单元->锁存器与寄存器】小节。

RAM

特征简介

RAM,英文全称:Random Access Memory,翻译成中文即——随机访问存储器,即我们可以在任意时刻向任意一个RAM地址写入数据或者从任意一个RAM地址读取数据。在RAM浩瀚的存储空间中,一般同一时刻最多只能访问RAM中的一个存储单元,这点与寄存器阵列有着本质的不同。

实现载体

首先,FPGA中的BRAM资源就是一个个独立的RAM,因此它们当然是实现RAM的首选载体。
其次,如果能够将多个LUT联合起来,其记忆力也是很客观的,因此LUT也是实现RAM的载体之一。
第三,寄存器阵列的功能是大于RAM的功能,因为同一时刻我们可以访问到寄存器阵列中的所有存储单元,因此,它也可以用来作为RAM的载体,不过由于它提供的功能远远大于RAM的需求,因此其对触发器资源的消耗是非常大的,因此通常不建议用寄存器阵列来实现RAM。

应用场合

当需要一些大容量的数据缓存时,RAM通常是首选。并且RAM是所有涉及到大量数据存储的形式的本源,后续的所有存储形式都是在RAM的基础上发展起来的,并且,由于RAM的随机访问特性,我们可以基于此开发出适合自己的、自定义的、特殊的数据存取方式。
值得注意的是,RAM的功能不仅仅限于数据的存储,因为它实际上就是一个大的查找表,可以用来实现任意的、变化无穷的逻辑功能。

ROM

特征简介

ROM,英文全称:Read-Only Memory,翻译成中文即——只读存储器。它是一种预先设定好存储内容,然后只允许进行读操作的存储结构。

实现载体

从ROM的特征介绍,我们可以看出,ROM其实就是RAM功能的一个子集,因此,触发器、查找表、块存储也都可以是ROM的实现载体。

应用场合

当FPGA设计的某些算法中,需要用到篇幅较大,且规律不明显的常数表时,ROM是首选方案。

FIFO

特征简介

FIFO,英文全称:First Input First Output,翻译成中文即——先进先出队列。我们可以以在车站排队买票为例来理解FIFO的行为:初始时,售票窗口没人,对应FIFO为空;过了一阵售票窗口有人排队,大家将按照先来后到的顺序排好,对应FIFO中数据存储的顺序;开始售票时,最先来的人最先购票,并且之后离开窗口,不再参与排队,对应FIFO的先进先出特性;当人很多时,售票大厅站满了人,再也容不下更多的人排队时,对应FIFO为满。由于FIFO具有先进先出的特性,所以操作FIFO的时候,我们端口中并不需要专门的地址输入端口。

实现载体

现实世界中,不存在一个容量可随数据元素多少而改变的硬件结构,因此FPGA中的FIFO大小其实都是固定的。即使在C语言中,我们可以通过指针以及节点的动态申请和释放的形式来实现好似容量可变的FIFO,但是这也仅仅是高级语言对固定大小内存硬件空间的抽象划分罢了。所以,真正的FIFO,并不是通过动态调整自己的存储空间来实现先进先出特性的,而是在RAM的基础上,通过适当控制读、写地址的变化,来营造一个先进先出的氛围。因此,从FIFO中读出的数据仍然存储在FIFO的空间当中,之所以我们再也访问不到,只不过是因为在写操作对它进行覆盖前,读地址是不可能再指向它了而已。
由此可见,触发器、查找表、块存储也都是FIFO的实现载体,同时,为了实现先进先出的功能,还必须使用一些触发器和查找表来组成RAM的读写控制逻辑。
最后,请注意,FIFO与移位寄存器有着本质的不同,移位寄存器虽然也是先进先出的,但它并不是一个队列,而更像是一个隧道!队列的长短会随着排队人数的多少而变化,如果你前面的人多,你出队列需要等待的时间就长,如果你前面没有人,那么你无须等待就可以出队列;但隧道的长短却与等待通过的人数没有任何关系,无论你是第一个进入隧道的,还是最后一个进入隧道的,你出隧道所需要的时间都完全相同,因为隧道带给我们的是一个固定的延迟。除此以外,FIFO的写入和读出操作是分开的,只要条件允许,一段时间内可以只进行读或者只进行写操作;但移位寄存器的读、写操作是紧密联系的,如果写入一个数据,那么它必然也会同时吐出一个数据,反之亦然。而且移位寄存器中的数据一旦读出,就真的消失了(这里特指串行移位寄存器)。综上所述,移位寄存器并不能成为FIFO的实现载体。

应用场合

受到先进先出性质的约束,FIFO的应用范围远没有RAM广泛,但是在FPGA设计中,FIFO的人气指数却要远远高于RAM,这其中最主要的原因就在于绝大部分的数据传递都是遵循先进先出特性的,而且直接使用成熟的FIFO模块远比使用RAM再配合编写读、写控制逻辑要来得简便得多。

STACK

特征简介

STACK,即是堆栈的意思。与FIFO的先进先出特性恰恰相反,STACK遵循的是后进先出的原则。因此STACK的特征可以用汉诺塔的游戏原理来理解,如果你实在不知道汉诺塔是怎么回事,那么冰糖葫芦总见过吧,最后串进去的山楂球将会是第一个被你吃掉的。

实现载体

除了读取数据的位置相差甚远外,STACK和FIFO的其他性质都是一样的,因此它也是基于RAM的,所以触发器、查找表、块存储也都是STACK的实现载体,同时,为了实现后进先出的功能,还必须使用一些触发器和查找表来组成RAM的读写控制逻辑。

应用场合

STACK的思想多用于软件编程的子程序调用中,FPGA中存储数据时比较少碰到,但是当你的算法什么时候需要一个数据存储的后进先出特性时,别犹豫,非它莫属了!

寄存器的HDL描述与用法

HDL描述

下面的HDL代码所描述的就是一个端口较为完整的寄存器:

-- VHDL example	
signal Q : std_logic;
process(clk)
begin
	if(R = '1')then
		Q <= '0';
	elsif(S = '1')then
		Q <= '1';
	elsif(clk'event and clk = '1')then
		Q <= D;
	end if;
end process; 

// Verilog example
reg Q;
always@(posedge R, posedge S, posedge clk)
begin
	if(R == 1'b1)
	begin
		Q <= 1'b0;
	end
	else if(S == 1'b1)
	begin
		Q <= 1'b1;
	end
	else
	begin
		Q <= D;
	end
end

从上例代码可以看出,它描述的实际是一个敏感时钟上升沿的且具有高电平有效的异步置0、置1信号的边沿D触发器,需要注意的一点是,如果该段代码所使用的目标FPGA芯片上的触发器并不符合代码所描述的所有性质,那么编译器将会引入额外的资源来实现。

HDL用法

虽然我们可以将上例的代码封装成为模块,然后在HDL代码中通过实例化的方式来调用使用,但是通常来说,这样做会略有画蛇添足之嫌,因为寄存器是HDL语法中两个最为基本的描述元素之一(另一个是线网),所以,只要确保声明正确、用在敏感时钟边沿的程序块中且赋值方式选择恰当,编译器实现出来的就会是一个寄存器。因此,寄存器的HDL描述同样也是寄存器的HDL用法。不过在实际使用中,并不一定要用到寄存器的所有异步置位端口,并且还有可能需要添加同步复位端口,不过我们仅需要根据需求进行HDL代码描述即可,而不需要费心思考虑具体的实现问题,因为那是编译器的工作。

单口RAM的HDL描述与用法

RAM并不是HDL语法所支持的基本描述元素,因此若要使用之,必先创造之!创造一个具有RAM功能的模块有两种方法:一、使用现成的IP核生成工具来生成;二、自己使用HDL语法来描述。
通常来说,IP核的性能会较我们自己编写的模块更好一些,但是了解一下如何用HDL来描述RAM会更有助于我们理解RAM的功能行为,并且有些时候,使用自己描述的RAM模块也是有优势的,例如可增强代码的可移植性。那么本章节将重点讨论单口RAM的HDL描述与用法。

HDL描述

单口RAM,即仅有一套操作接口的RAM,这套接口包括:一个数据写入总线、一个数据读出总线、一个地址总线和一些相关的读写控制信号。由于仅有一套操作接口,所以单口RAM的读、写操作可能存在着冲突,那么根据单口RAM在发生冲突时所采用的仲裁机制,又可以将单口RAM分为Write first、Read first、No change三种;按照单口RAM的读取时序,又可将其分为异步读、伪同步读、真同步读三种;除此以外,是否使用使能信号也会造成RAM行为的不同。那么接下来,就为大家介绍一些具有相关特点的单口RAM的HDL描述。

No change

No change,即是一种读、写互不干扰的模式,它的HDL描述范例如下:

-- VHDL example
type ram_type is array (255 downto 0) of std_logic_vector (7 downto 0);
signal RAM: ram_type;
process (clk)
begin
	if clk'event and clk = '1' then
		if en = '1' then
			if we = '1' then
				RAM(conv_integer(addr)) <= dIn;
			else
				dOut <= RAM(conv_integer(addr)) ;
			end if;
		end if;
	end if;
end process;

// Verilog example
reg [7:0] RAM [255:0];
always@(posedge clk)
begin	
	if (en)
	begin
		if (we)
		begin
			RAM[addr] <= dIn;
		end
		else
		begin
			dOut <= RAM[addr];
		end
	end
end

通过上例可以看出,no change模式下的RAM是需要操作使能信号en的。当en有效时,如果we等于1,表示此次进行的是写操作;如果we等于0,表示此次进行的是读操作。由此可见、该模式下读、写操作其实是分开的,不可能同时发生,因此写操作对数据输出端口dOut没有任何影响,故该类型的RAM的工作模式称为no change。

Read first

Read first,即冲突时读优先的操作模式,它的HDL描述范例如下:

-- VHDL example
type ram_type is array (255 downto 0) of std_logic_vector (7 downto 0);
signal RAM: ram_type;
process (clk)
begin
	if clk'event and clk = '1' then
		if en = '1' then
			if we = '1' then
				RAM(conv_integer(addr)) <= dIn;
			end if;
			dOut <= RAM(conv_integer(addr)) ;
		end if;
	end if;
end process;

// Verilog example
reg [7:0] RAM [255:0];
always@(posedge clk)
begin	
	if (en)
	begin
		if (we)
		begin
			RAM[addr] <=  dIn;
		end
		dOut <= RAM[addr];
	end
end

通过上例可以看出,当en有效时,RAM的读操作便开启,但是如果此时we信号也有效,那么RAM会针对当前写入数据的地址同时进行读、写操作从而产生冲突,而针对此引入的仲裁机制是dOut输出该地址的旧数据,因此虽然读、写操作是同时的,但是输出结果与no change模式下先读再写是一样的,故该类型的RAM的工作模式称为read first。

Write first

Write first,即冲突时写优先的操作模式,它的HDL描述范例如下:

-- VHDL example
type ram_type is array (255 downto 0) of std_logic_vector (7 downto 0);
signal RAM: ram_type;
process (clk)
begin
	if clk'event and clk = '1' then
		if en = '1' then
			if we = '1' then
				RAM(conv_integer(addr)) <= dIn;
				dOut <= dIn;
			else
				dOut <= RAM(conv_integer(addr)) ;
			end if;
		end if;
	end if;
end process;

// Verilog example
reg [7:0] RAM [255:0];
always@(posedge clk)
begin	
	if (en)
	begin
		if (we)
		begin
			RAM[addr] <= dIn;
			dOut <= dIn;
		end
		else
		begin
			dOut <= RAM[addr];
		end
	end
end

通过上例可以看出,当en有效时,无论we信号有效与否,RAM都会进行读操作,因此当we信号有效时,RAM会针对当前写入数据的地址同时进行读、写操作从而产生冲突,而针对此引入的仲裁机制是dOut输出待写入的数据dIn,也即该地址的新数据,因此虽然读、写操作是同时的,但是输出结果与no change模式下先写再读是一样的,故该类型的RAM的工作模式称为write first。

异步读

异步读,即采用异步的方式从RAM中读取数据的操作模式,它的HDL描述范例如下:

-- VHDL example
type ram_type is array (255 downto 0) of std_logic_vector (7 downto 0);
signal RAM: ram_type;
process (clk)
begin
	if clk'event and clk = '1' then
		if we = '1' then
			RAM(conv_integer(addr)) <= dIn;
		end if;
	end if;
end process;
dOut <= RAM(conv_integer(addr)) ;

// Verilog example
reg [7:0] RAM [255:0];
always@(posedge clk)
begin	
	if (we)
	begin
		RAM[addr] <= dIn;
	end				
end
assign dOut = RAM[addr];

通过上例可以看出,异步读模式下的RAM是不需要操作使能信号en的,当we信号有效时,将进行RAM的写操作,而无论在任何时候,dOut都会根据addr来对RAM进行异步读取。因此,当发生读、写冲突时,dOut会先输出旧值,当写操作执行完后,便会输出新值,故由于读取是异步的,所以此时的RAM输出是与仲裁方案无关的。

伪同步读

伪同步读,即是采用实则为异步但表现上却好似为同步的方式从RAM中读取数据的操作模式,它的HDL描述范例如下:

-- VHDL example
type ram_type is array (255 downto 0) of std_logic_vector (7 downto 0);
signal RAM: ram_type;
process (clk)
begin
	if clk'event and clk = '1' then
		if we = '1' then
			RAM(conv_integer(addr)) <= dIn;
		end if;
		dOut <= RAM(conv_integer(addr)) ;
	end if;
end process;

// Verilog example
reg [7:0] RAM [255:0];
always@(posedge clk)
begin	
	if (we)
	begin
		RAM[addr] <= dIn;
	end			
	dOut <= RAM[addr];			
end

通过上例可以看出,伪同步读模式下,dOut的变化虽然是与时钟有效沿同步地,但是RAM的地址信号却仍然是异步的,故称为伪同步模式。

真同步读

真同步读,即采用真正的同步方式从RAM中读取数据的操作模式,它的HDL描述范例如下:

-- VHDL example
type ram_type is array (255 downto 0) of std_logic_vector (7 downto 0);
signal RAM: ram_type;
process (clk)
begin
	if clk'event and clk = '1' then
		if we = '1' then
			RAM(conv_integer(addr)) <= dIn;
		end if;
		addrLock <= addr;
	end if;
end process;	
dOut <= RAM(conv_integer(addrLock)) ;	

// Verilog example
reg [7:0] RAM [255:0];
always@(posedge clk)
begin	
	if (we)
	begin
		RAM[addr] <= dIn;
	end			
	addrLock <= addr;			
end
assign	dOut = RAM[addrLock];

通过上例可以看出,真同步读模式下,RAM的读取地址信号是真正经过同步的,故称为真同步模式。

单口RAM的用法

无论是IP核,还是自己编写的HDL代码,单口RAM都可以用实例化的形式来调用使用,当然了,如果是自己用HDL代码描述的RAM,也可以在此基础上直接展开功能代码编写。
由于单口RAM有很多种,因此请根据所选用单口RAM的特性,采用相应的访问方式。但是有一点请注意,由于单口RAM只有一个地址总线,除非读取和写入恰恰总是需要针对同一地址进行操作,否则它们不能同时进行。因此,在绝大多数情况下,单口RAM在写入的时候不能完成期望的读取,而在读取的时候不能完成期望的写入,所以有效的读取和写入往往是时分复用RAM的,这样将会造成RAM数据吞吐量的减半。而根据单口RAM的这个特性,可以将单口RAM的操作大致分为几种模式:

零存零取模式

该模式适用于对RAM的读、写操作情况均是串行的、随机的、离散的、不规律的。例如程序在PC机上运行时,由于其串行执行的思路,所以同一时刻仅能对其数据空间进行读、写操作中的一种,并且代码的赋值操作一般比较杂散,因此也是无规律、随机、离散的。

零存整取模式

该模式适用于RAM的写操作是离散的、不定长的,但是读操作要求是连续的、定长的,因此当RAM中缓存了一定数量的内容后,才可以开始读操作,并且要有机制能够确保读操作期间不会再出现写请求。

整存零取模式

该模式适用于RAM的读操作是离散的、不定长的,但是写操作要求是连续的、定长的,因此当RAM中连续的写入一批内容后,才可以开始离散的读操作,并且要有机制能够确保写操作期间不会出现读请求。

整存整取模式

该模式适用于RAM的读、写操作均要求是连续的、定长的,但前提是必须有机制来确保批量的读、写操作是串行发生且时间上没有交叉的。该模式也是吞吐量最大的一种单口RAM操作模式。

长期存储模式

该模式适用于RAM中的内容会被全部更新一次,然后就被长期的、多次的使用的情况。例如在某些通信算法中,用来存储每隔一段时间就更新的密钥解密参数表等。

双口RAM的HDL描述与用法

双口RAM是一个具有两套完全独立的时钟线、数据线、地址线和读写控制线,并允许这两套独立的操作端口同时对该RAM进行随机性的访问,因此双口RAM通常又被称为共享式RAM。
双口RAM与单口RAM一样,可以通过现成的IP核生成工具生成,也可以通过自己编写HDL代码来实现。通常来说,IP核的性能会较我们自己编写的模块更好一些,但是了解一下如何用HDL来描述会更有助于我们理解双口RAM的功能行为。

HDL描述

双口RAM的每一套端口都是独立的,因此单独针对某一套端口来说,它的操作方式和单口RAM完全相同,因此单口RAM所具有的各个特点双口RAM全都具有,因此如果细分起来,双端口RAM的种类将会非常之多,所以这里将选出两种比较典型的双口RAM为大家进行介绍,更多的情况请大家参考单端口RAM的各个特征去自行分析。一种无使能控制端口、异步读的双口RAM的HDL描述范例,参考如下:

全双口异步读RAM描述

全双口异步读RAM,即两套独立端口都采用异步读的方式操作RAM,并且每一套端口都可以对RAM进行读、写操作。它的HDL描述范例如下:

-- VHDL example
type ram_type is array (255 downto 0) of std_logic_vector (7 downto 0);
signal RAM: ram_type;
process (clkA)
begin
	if clkA'event and clkA = '1' then
		if weA = '1' then
			RAM(conv_integer(
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李锐博恩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值