[电路设计] 寄存器按位优化 - 让大叔的红杏再出一次墙(最新版在31楼)

[复制链接]
16874|55
 楼主| X-Hawk 发表于 2011-4-30 01:39 | 显示全部楼层 |阅读模式
本帖最后由 hotpower 于 2011-6-25 00:01 编辑

更新1:4楼的头文件更新,新增对IAR的支持。
更新2:19楼新的写法,更简洁。
更新3:22楼仿Lee老师的c++连续复制写法,简易且不容易出错。
           UART0s.UA_FSR()
                 .BIF(1)
                 .FEF(1)
                 .PEF(1);
更新4:24楼发布新版,
           支持在gcc底下使用,在gcc编译器下也达到了很好的优化效果。
           重新整理了代码,让发布的头文件更具有可读性。
更新5:31楼发布新版。
           寄存器定义,在gcc下用变量代替宏定义。这样调试时有可能感应出变量的值(又是向Lee老师学的,哈哈。。)。
        05/14做小更新,减少了一些重复定义。因为无法解决重复链接的问题,Keil下变量还是用宏代替,调试时无法感应变量的值。


===========================================

一、要做什么(背景介绍)
首先定义两个类型
typedef unsigned int           _reg32_t;
typedef volatile unsigned int  reg32_t;

菜农老师的红杏这么写的:
1. 定义每个寄存器中的每位
  1.   typedef struct {
  2.     ...
  3.     reg32_t PEF             : 1;
  4.     reg32_t FEF             : 1;
  5.     reg32_t BIF             : 1;
  6.     ...
  7.   } uart0_ua_fsr_bits_t;
2. 用union定义寄存器
  1.   typedef union {
  2.     uart0_ua_fsr_bits_t       b;
  3.     reg32_t                   v;
  4.   } uart0_ua_fsr_t;
3. 将寄存器分组

  1. typedef struct {
  2.   ...
  3.   /*0x40050010*/ uart0_ua_mcr_t                 UA_MCR;
  4.   /*0x40050014*/ uart0_ua_msr_t                 UA_MSR;
  5.   /*0x40050018*/ uart0_ua_fsr_t                 UA_FSR;
  6.   ...
  7. } uart0_t;
  8. #define UART0s      (*(uart0_t *)0x40050000)
(以上跟菜农老师的代码略有不同,意思应该没有表达错。)
于是要访问fsr寄存器,可以按位访问(写法一)
  1.       UART0s.UA_FSR.b.BIF = 1;
  2.       UART0s.UA_FSR.b.FEF = 1;
  3.       UART0s.UA_FSR.b.PEF = 1;
也可以按完整32位值访问(写法二)
  1.       unsigned int v = UART0s.UA_FSR.v;
  2.       v |= 0x00000070;
  3.       UART0s.UA_FSR.v = v;
两个代码效果是一样的,但是效率却完全不一样,
在Keil O2优化下,写法一花费了13条指令,写法二只花费了5条。

第二个虽然很优化,写起来却不那么痛快,
需要仔细对比每个bit所在的位置。

那么有没办法,做到如写法一,又有写法二的效率呢。
答案是肯定的,我们来玩魔术,让大叔的红杏再出一次墙。

 楼主| X-Hawk 发表于 2011-4-30 02:17 | 显示全部楼层
本帖最后由 hotpower 于 2011-5-4 21:09 编辑

三、友情提醒
1. 用到下划线_表示的bit位,最后要触发实际写动作,需要加一个 REG_COMMIT 操作。
2. 如果实在看 REG_COMMIT 不爽,也可以在最后一个操作用 “不优化的位定义”,
   同样能达到目标,但指令数会稍多一点。
  1.       UART0s.UA_FSR._.BIF = 1;
  2.       UART0s.UA_FSR._.FEF = 1;
  3.       UART0s.UA_FSR.b.PEF = 1;
3. 不用编译器优化,可能达不到预期目标。
4. 有些寄存器的位读写次序有讲究的,请留意优化陷阱
5. 头文件不必那么累手工生成,附上javascript自动工具一枚,
   酒鬼最喜欢的语言还是js啊,
   点击《Export as optimized header》,即可生成本文格式的NUC100头文件。
   

用文中所描述格式创建的NUC100和M051的两个头文件。


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
john_lee 发表于 2011-4-30 02:30 | 显示全部楼层
X-Hawk高明,我也来凑个热闹:
  1. inline void uart_t::SetBaudRate(uint32_t baudrate, uint32_t clock)
  2. {
  3.     if (__builtin_constant_p(baudrate) && __builtin_constant_p(clock)) {
  4.         __typeof__(BAUD) data = { 0 };
  5.         uint32_t div;
  6.         if ((clock / baudrate) % 16 < 2) {      // Source Clock mod 16 < 2 => Using Divider X =16 (MODE#0)
  7.             div = 15;
  8.         } else {                                // Source Clock mod 16 >2 => Up 5% Error BaudRate
  9.             data.DIV_X_EN = 1;                  // Try to Set Divider X = 1 (MODE#2)
  10.             data.DIV_X_ONE = 1;
  11.             div = 0;
  12.             if (clock / baudrate - 2 > 0xffff) {// If Divider > Range
  13.                 data.DIV_X_ONE = 0;             // Try to Set Divider X up 10 (MODE#1)
  14.                 div = 8;
  15.                 if ((clock / baudrate) % (div + 1) >= 3) {
  16.                     div++;
  17.                     if ((clock / baudrate) % (div + 1) >= 3) {
  18.                         div++;
  19.                         if ((clock / baudrate) % (div + 1) >= 3) {
  20.                             div++;
  21.                             if ((clock / baudrate) % (div + 1) >= 3) {
  22.                                 div++;
  23.                                 if ((clock / baudrate) % (div + 1) >= 3) {
  24.                                     div++;
  25.                                     if ((clock / baudrate) % (div + 1) >= 3) {
  26.                                         div++;
  27.                                         if ((clock / baudrate) % (div + 1) >= 3)
  28.                                             div++;
  29.                                     }
  30.                                 }
  31.                             }
  32.                         }
  33.                     }
  34.                 }
  35.                 data.DIVIDER_X = div;
  36.             }
  37.         }
  38.         data.BRD = clock / baudrate / (div + 1) - 2;
  39.         BAUD = data;
  40.     } else
  41.         .... // 调用DrvUART的波特率初始化
  42. }
 楼主| X-Hawk 发表于 2011-5-1 23:50 | 显示全部楼层
本帖最后由 hotpower 于 2011-5-4 21:09 编辑
实际酒鬼很早就提到此问题了。
酒鬼的方法很好,就是头文件要加倍。
hotpower 发表于 2011-5-1 18:38

回菜农老师,继续改进新的写法,去掉加倍的头文件。。并且,上面的写法统统作废。。作废。。
还是用1楼的例子,这是1楼的写法:
  1. UART0s.UA_FSR.b.BIF = 1;
  2. UART0s.UA_FSR.b.FEF = 1;
  3. UART0s.UA_FSR.b.PEF = 1;
其中有个.b, 略显麻烦。

新的写法如下:
新写法一:非优化版本 - 13条汇编指令:
  1. UART0s.UA_FSR.BIF = 1;
  2. UART0s.UA_FSR.FEF = 1;
  3. UART0s.UA_FSR.PEF = 1;
新写法二:优化版本(带下划线)- 5条汇编指令:
  1. UART0s.UA_FSR_.BIF = 1;
  2. UART0s.UA_FSR_.FEF = 1;
  3. UART0s.UA_FSR_.PEF = 1;
  4. REG_COMMIT(UART0s.UA_FSR); //或 REG_COMMIT(UART0s.UA_FSR_);
新写法三:直接32位赋值(仅c++有效)- 5条汇编指令
  1. unsigned int v = UART0s.UA_FSR;
  2. v |= 0x00000070;
  3. UART0s.UA_FSR = v;
写法三只用于c++, 如果在c程序用,需要加上.v,和1楼的旧写法一样,并且Keil同样编译出5条汇编指令
  1. unsigned int v = UART0s.UA_FSR.v;
  2. v |= 0x00000070;
  3. UART0s.UA_FSR.v = v;
支持新写法的寄存器头文件下载


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
 楼主| X-Hawk 发表于 2011-5-2 11:13 | 显示全部楼层
本帖最后由 hotpower 于 2011-5-4 21:10 编辑

继续毫无节制的现宝。。。

昨天看Lee老师的写法,用流的方式复制,大受启发。
  1. uart0_baud << clear
  2. << setbit<NU_UART_BAUD_t>(NU_UART_BAUD_t::BIT_DIV_X_EN)
  3. << setbit<NU_UART_BAUD_t>(NU_UART_BAUD_t::BIT_DIV_X_ONE)
  4. << apply;
于是想到换个形式:
  1. UART0s.UA_BAUD()
  2.     .DIV_X_EN(1)
  3.     .DIV_X_ONE(1);
酒鬼只是给换个碗装,汤药还是Lee老师的汤药。

这个形式的编译效果,相当于以下伪代码
  1.   unsigned int v = UART0s.UA_BAUD;
  2.   v |= BIT_DIV_X_EN | BIT_DIV_X_ONE;
  3.   UART0s.UA_BAUD = v;
共6条asm指令。

或者,另一个形式,UA_BAUD先赋个初值0的
  1. UART0s.UA_BAUD(0)
  2.     .DIV_X_EN(1)
  3.     .DIV_X_ONE(1);
这个形式的编译效果,相当于以下伪代码
  1.   unsigned int v;
  2.   v = BIT_DIV_X_EN | BIT_DIV_X_ONE;
  3.   UART0s.UA_BAUD = v;
少一次读取动作,共4条asm指令。



这个新的代码方式,实现了按语句优化。
在这个连续的函数调用语句内,
只读/写寄存器一次,中间多次对位赋值,都可以实现完全的优化。

如果要实现1楼的赋值功能(13条指令):
  1. UART0s.UA_FSR.b.BIF = 1;
  2. UART0s.UA_FSR.b.FEF = 1;
  3. UART0s.UA_FSR.b.PEF = 1;
只需要写
  1. UART0s.UA_FSR()
  2.     .BIF(1)
  3.     .FEF(1)
  4.     .PEF(1);
编译出来共计5条asm指令。

效果和19楼的写法二、写法三一样,但是更简洁、更不容易写错。


注:
1. 支持上述功能,需要c++, 并用新的头文件。
2. 编译器需要开优化,测试的优化级别
     Keil 4.10: -O3 + Optimize for Time
     IAR 6: High, blanced

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
 楼主| X-Hawk 发表于 2011-5-12 14:12 | 显示全部楼层
本帖最后由 X-Hawk 于 2011-5-14 23:00 编辑

学习李老师的办法,将gcc下宏定义改为变量。
这样gcc的程序在调试时,也能感应到变量内容了。

  1. #define USBs        (*((usb_t *)    0x40060000))
改为
  1. extern usb_t       USBs;
  2. __asm__ (".global USBs    \n.weak USBs   \n,set USBs,        0x40060000");
更新版头文件下载(NUC100和M051系列):


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
hotpower 发表于 2011-4-30 01:56 | 显示全部楼层
且听下回分解???
 楼主| X-Hawk 发表于 2011-4-30 02:06 | 显示全部楼层
本帖最后由 hotpower 于 2011-5-4 21:08 编辑

二、如何做(从volatile下手)

其实也很简单,让编译器的优化为我们做事情。
上面写法一有14条指令,乃是因为每一个bit,都用reg32_t修饰,具有volatile属性。
我们将volatile去掉即可。
重新定义一个下划线版本,不带volatile的
  1.   typedef struct {
  2.     ...
  3.     _reg32_t PEF             : 1;
  4.     _reg32_t FEF             : 1;
  5.     _reg32_t BIF             : 1;
  6.     ...
  7.   } _uart0_ua_fsr_bits_t;
并将这些位加入寄存器中:
  1.   typedef union {
  2.     _uart0_ua_fsr_bits_t      _; //可优化的位定义
  3.     uart0_ua_fsr_bits_t       b; //不优化的位定义
  4.     reg32_t                   v;
  5.   } uart0_ua_fsr_t;
定义一个宏,将寄存器的“可优化的值”赋值给“不优化的值”,该宏可保证触发一次实际的写动作。
  1.   #define REG_COMMIT(reg) do{(reg).v = *(_reg32_t *)&(reg);} while(0)
我们代码这么写
  1.       UART0s.UA_FSR._.BIF = 1;
  2.       UART0s.UA_FSR._.FEF = 1;
  3.       UART0s.UA_FSR._.PEF = 1;
  4.       REG_COMMIT(UART0s.UA_FSR);
以上代码,经Keil O2编译,只要5条指令,和上面写法二的效果一样。
但是代码更清晰了,完美实现了目标。

 楼主| X-Hawk 发表于 2011-4-30 02:24 | 显示全部楼层
2# hotpower

嗯,分解完了,感谢大叔支持!
hotpower 发表于 2011-4-30 02:39 | 显示全部楼层
俺主要是担心被优化了。
一般编译器认为一个变量不经过读写一个轮回,就可能视为无用变量被优化掉了。
再当一个变量被连续赋予一个相同值时优化也可能发生。
故考虑了还是加入volatile属性稳妥些,这样不管任何优化级别,都不会出现莫名其妙的错误。
等李老师来讨论。看来是否一个将volatile属性去掉。
 楼主| X-Hawk 发表于 2011-4-30 02:47 | 显示全部楼层
本帖最后由 hotpower 于 2011-5-13 00:38 编辑

李老师这个好像能自动算出波特率的设定值,很不错!

 楼主| X-Hawk 发表于 2011-4-30 02:50 | 显示全部楼层
本帖最后由 hotpower 于 2011-5-13 00:39 编辑

7# hotpower
没错了。这个在同时读写多个位域时比较有用。
要实际写进去,
最后还是要调用一个加了volatile的语句,如
REG_COMMIT(UART0s.UA_FSR);
或:
UART0s.UA_FSR.b.PEF = 1;



 楼主| X-Hawk 发表于 2011-4-30 03:09 | 显示全部楼层
本帖最后由 hotpower 于 2011-5-13 00:39 编辑

只要假想,.b 形式的位操作,真正对实际设备读写。

而下划线 ._ 形式的位操作,是从临时空间上读写,
临时空间的结果,只有REG_COMMIT之后,或写过 .b形式的操作,
才会真正写到寄存器上。

这样就不会用错。

例如,官方BSP中DrvFMC操作flash的一段,

  1. FMC->ISPCMD.FCTRL = 2;
  2. FMC->ISPCMD.FCEN = 0;
  3. FMC->ISPCMD.FOEN = 1;
  4. FMC->ISPADR = u32addr;
  5. FMC->ISPTRG.ISPGO = 1;
  6. __ISB();
  7. while (FMC->ISPTRG.ISPGO);
如果改成新的头文件,更优化的写法,则写成

  1. FMCs.ISPCMD._.FCTRL = 2;
  2. FMCs.ISPCMD._.FCEN = 0;
  3. FMCs.ISPCMD._.FOEN = 1;
  4. REG_COMMIT(FMCs.ISPCMD);
  5. FMCs.ISPADR = u32addr;

  6. //此处用下划线形式,FMCs.ISPTRG._.ISPGO, 可能会无法工作
  7. //因为下划线形式有可能只写到“临时空间”
  8. FMCs.ISPTRG.b.ISPGO = 1;  

  9. __ISB();
  10. while (FMCs.ISPTRG.b.ISPGO);







murex 发表于 2011-4-30 07:50 | 显示全部楼层
学习受教了
Swallow_0322 发表于 2011-4-30 07:55 | 显示全部楼层
流口水!:P
john_lee 发表于 2011-4-30 09:32 | 显示全部楼层
我写的那个函数,基本上就是根据新唐的函数 BaudRateCalculator() 稍微修改一下,如果参数 baudrate 和 clock 都是常数,就可以利用编译器的 Constant folding(常量叠算) 优化,中间所有的计算全部在编译时完成,最后生成仅两三条汇编指令。

对波特率寄存器的赋值,使用了一个缓冲数据:
  1. __typeof__(BAUD) data = { 0 };
这个值不会分配在 RAM 中,甚至不在 CPU 寄存器中,编译器只是将它作为常量叠算的暂存值,中间所有的操作,如:
  1. data.DIV_X_EN = 1;
  2. data.DIV_X_ONE = 1;
等,都是对这个缓冲进行操作。最后真正的赋值在:
  1. BAUD = data;
缓冲数据 data 表现为一个常量(立即数),与 0x30000066 之类没有什么分别。
 楼主| X-Hawk 发表于 2011-4-30 09:52 | 显示全部楼层
本帖最后由 hotpower 于 2011-5-13 00:39 编辑

13# john_lee
这个非常强悍!
昨晚我在狐疑,这个__builtin_constant_p究竟是什么。
经李老师这么一说,再加goolge, 终于明白了。

hotpower 发表于 2011-4-30 10:28 | 显示全部楼层
哈哈,都是夜猫子了~~~
总之用中间变量缓冲,只从文字池中取一次硬件结构指针地址,在此变量中折腾最后再写入必然优化。
所以当时就考虑了Regs和Bits两种写法。
weshiluwei6 发表于 2011-4-30 11:10 | 显示全部楼层
太厉害了你们
dong_abc 发表于 2011-5-1 12:01 | 显示全部楼层
来到火星了~~~
hotpower 发表于 2011-5-1 18:38 | 显示全部楼层
实际酒鬼很早就提到此问题了。
酒鬼的方法很好,就是头文件要加倍。
hotpower 发表于 2011-5-2 07:13 | 显示全部楼层
这次很好!
在任何优化级别都行吗?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

3

主题

380

帖子

3

粉丝
快速回复 在线客服 返回列表 返回顶部