之前,说明了,当外部需要增加寄存器的时候,需要更改总线互联模块。在总线互联模块中对于读数据和使能信号是每一个寄存器都有一个连线和总线互联模块相连的,如果寄存器比较少,似乎是没有什么问题。但是如果外接的寄存器有几千,几万个呢?那这一块的电路可真是不敢想象了,基本上做不出来,因为要做出来的话,面积太大了,你想象一下,一个多路选择器,有几千几万个输入,这怎么实现?
所以真正的SOC,肯定不能像我们之前说的那样设计,一个寄存器一个寄存器的考虑,而是要考虑整体。我们知道,SOC要驱动的其实是一个模块,比如串口模块,SDRAM模块,SPI模块等等。但是要驱动模块的话,是要去控制模块的寄存器。所以这个时候,使能信号不考虑给模块的每个寄存器,只考虑给模块,同样,读数据也是一样的,不考虑只给模块的每个寄存器,只考虑模块。按照这个思路下来,那使能信号电路和读数据电路是不是就要好设计了一些,因为一个SOC,模块就那么多,总线互联译码设计就比较容易了。那么怎么对对应的寄存器操作了,这个就交给模块自己来做就行了。
总线互联模块通过CPU发出的地址,判断该地址是属于哪个模块的,然后将对应模块的使能信号使能,将读数据选择连接到模块的读取数据线。模块检测有模块使能信号时,再根据CPU发出的地址信号,判断是对自己的哪一个寄存器进行操作,然后再进行操作。这样,就简化了总线互联的设计。
通过上面这个图,就明白上面所说的意思。这个时候总线互联模块,不关心外部有多少寄存器,只关心有几个模块,然后将地址给连续的分配这些模块,这样,每个模块就会分到一部分连续的地址。当总线互联模块检测到CPU的地址是处于哪个模块地址区域时,就将对应的模块使能信号给使能,把模块的读数据给选择连接到CPU的读数据上。
假设有8个模块,总线是32位的。地址就是从0x00000000到0xffffffff。然后将4G的地址空间平均分给每个模块,那么每个模块就会分配到512M的地址空间。比如对于第一个模块,地址从0x00000000-1fffffff,那么这个模块的基地址就是0x0,第二个模块,地址从0x2000_0000-0x2fff_ffff,那么这个模块的基地址就是0x2000_000。
当CPU发出的地址是0x00856840时,处于第一个模块的地址区域,所以第一个模块的使能信号就会有效,访问第一个模块的偏移是0x856840寄存器。
当CPU发出的地址是0x20856840时,处于第二个模块的地址区域,所以第二个模块的使能信号就会有效,访问第二个模块的偏移是0x856840寄存器。
我们再来看看STM32的存储器映射
可以看出,STM32也是按照以上的方式设计的。比如对于USART1,地址从0x40013800到0x40013bff。地址范围大小是1K字节大小。但是是不是这个模块就有256个寄存器了(32位)?我们来看看USART的寄存器地址映射。
从上图,发现,好像只有7个寄存器了,但是地址空间不是有256了,那不是有249个寄存器的空间浪费了。对,确实是浪费了,这是没有办法的事,因为你要简化互联总线对地址译码的那一块电路,所以是会存在很多寄存器地址被浪费掉了。这就是鱼和熊掌不可兼得。
对于处理器来说,模块的基地址都是固定的,模块的地址集合也是固定的。没有什么好研究的,但是对于一个SOC来说,模块的地址集合是要我们自己分配的了。
使用vivado软件,新建zynq处理器,然后再添加一个GPIO外设IP。原理图如下所示
1是ZYNQ处理器,2是总线互联模块,3是GPIO模块。这个时候,查看外设模块地址分配
可以看出,这个GPIO的外设地址分配从0x41200000-0x4120ffff。大小是64K字节。这个64K就是此时这个外设的地址范围。当然,我们是可以更改这个大小的。
但是更改的时候,发现选择的值似乎是有规律的,如果你把这些值转成二进制就可以看出来了,就是不管是什么值,转化的二进制,只有最高位为1,其他都是为0。而且你去把STM32的模块的寄存器的地址范围都计算一下的话,会发现地址范围大小的规律和VIVADO里面地址范围大小规律是一样的,只有最高位是为1,其他都是为0。这样的话,就可以大大简化互联总线的译码电路设计。
那如果我们改改Offest_Address的值了,就会出现下面的错误。说明模块的起始地址也不是可以随意设的,而是要符合要求的。不过,还好对于ZYNQ开发,VIVADO软件会自动给我们生成起始地址。
但是如果是我们自己要设计一个SOC系统的话,就要考虑模块的地址怎么分配了,因为这关系到你的互联总线译码设计是否可简单。所以对SOC地址分配也不是简单的事了。