在计算机系统中,每32bit我们称之为一个word(字),由4个字节组成。这个Project要求把内存中的字左旋转一个字节。举个例子,我们先假设内存就是连续的数组。数组0号位置M[0]存储着32'h01234567,h代表16进制表示。这里面的每个数字对应4个bit,所以一共就是32个bit。我们需要将最重要的字节(权重最高的字节)左旋转到权重最低的字节位置。如32'h01234567->32'h23456701。因为是以字节为单位,所以是01一共8比特即一字节发生了移动。
然后介绍了一下模块的接口,黄色的这个byte_rotation就是我们要设计的模块,而内存模型由testbench提供(也就是纯粹的仿真模型,不用考虑实际电路是怎么样的,只要实现功能就可以了)。Project的意思是用状态机实现,默认位于IDLE
状态,等待开始。根据message_addr
这个变量所提供的地址去访问数据,按之前介绍的规则进行字节旋转,然后根据output_addr
这个变量提供的地址去写回数据。message_addr
和output_addr
都是字地址,我这里解释一下。在现代的数字系统当中,地址与地址之间基本都是按字对齐的。如果之前学过C语言应该知道,一个int类型的变量就占据32bit,也就是一个字。这样的一个变量占的地址空间为4byte,也就是0x0->0x4。如下图所示,大家可以简单理解数据存储的组织方式如下,类似于一个一个的抽屉,每个抽屉的间隔为0x4,每个抽屉内部可以存32bit/4byte/1word的数据。
回到正题,size
用来代表字的数量,在完成运算以后将done信号置为1。
明白了需求,我们整理一下端口:
- 首先是复位信号和时钟信号
- 然后是用户所提供的配置和命令信号(下达命令的接口)以及自身完成运算的标志信号
- 最后是和Memory模型的接口信号
我们要完成的就是黄色的这个模块。
然后介绍了一下Memory Model。实际的芯片设计中内存模型都是由Foundry厂提供的,FPGA开发的话也需要使用FPGA开发厂商提供的IP库。自己写的话大部分情况是不可综合的或者综合成寄存器(本人不太清楚能不能通过写RTL的方式,综合出很好的内存模型,大部分公司应该不会这么做)。在这里是自己进行仿真而已,所以简单的写个Testbench即可。这里定义内存模型有读和写两种操作:
- 对于读操作,设置
mem_addr
为一定的值,然后将mem_we
设置为0(此处的we应该为write enable,设置为0即不写,则为读),下一个时钟周期,会从内存模型读数据到mem_read_data
信号。 - 对于写操作,设置mem_addr 为一定的值,然后将mem_we 设置为1,会将数据写到你希望写的地址。
每个周期都可以发起一次读或者写操作,但是对于读操作而言,必须等待到下一个时钟周期,数据才会读到mem_read_data
信号上。
需要注意,如果你将mem_addr
和mem_we
放到always_ff逻辑块中,编译器会生成触发器,这也就意味着地址和写使能信号下一个时钟周期才会发生变化。
此外,大家再看一下上面的时序图,在第0个时钟周期,发起读请求,下一个时钟周期读数据才会发生变化。这里也是说明不是一发起读请求,数据马上就有了。对于写操作,则不需要管这么多,每一拍是每一拍的操作(这一拍写进去了,你下一拍才可以读,此时已经安全写到内存模型了),此处也不涉及同时读写,因此比较简单。
然后我们看下这段代码,这段代码是错误的。大家想一想为什么?在S0状态的时候,我们发起了一个读请求,同时跳转到S1状态。然而到了S1状态,mem_addr和mem_we
才刚刚变化(这就是上面所说的,如果你将mem_addr 和mem_we 放到always_ff逻辑块中,编译器会生成触发器,这也就意味着地址和写使能信号下一个时钟周期才会发生变化。),因此还需要一个时钟周期。才能读回数据!
因此需要像上图这样,增加一个状态。在这个时钟周期,memory才能看到地址变化成了100。
此外我们还可以采用流水线操作。大家仔细看,在S1状态的时候地址和写使能信号已经发生变化了。因此下一个时钟周期会读回相应的值。也就是S2状态,mem_read_data的值已经对应了地址100的值。并且此时地址和写使能信号也已经是S1状态赋值的101地址,因此再过一个时钟周期,就又可以读回地址101的值。(那么如果是连续读多个数据呢?留给读者自己思考)
然后建议大家用function函数去实现字节旋转,字节旋转这个功能其实特别简单,就是之前说的位操作+拼接操作实现即可。
大家可以自己实现一下这个Project2。下节课给大家讲解Project2的几个示例代码。同时讲解一下Project3 SHA256。