Routine to measure read sector time (DMA/FDC Programming)

GFA, ASM, STOS, ...

Moderators: simonsunnyboy, Mug UK, Zorro 2, Moderator Team

Lautreamont
Obsessive compulsive Atari behavior
Obsessive compulsive Atari behavior
Posts: 103
Joined: Fri Jan 27, 2006 9:11 pm
Location: Friceland

Postby Lautreamont » Fri Feb 23, 2007 9:03 pm

Code: Select all

volatile BYTE* ptr = DMA_HIGH;
Uint32 p;
p = ((*ptr & 0xFF) << 16) | ((*(ptr+2) & 0xFF) << 8 ) | ((*(ptr+4) & 0xFF));

ijor wrote:I think Lautreamont wasn't talking about p being 8 bit, but about ptr being a pointer to an 8-bit variable. Again, I think it doesn't matter.

Yes I was talking of that part :

Code: Select all

... = *ptr << 16 ...

The ANSI C tells not to shift of a length that's longer than the one of your variable.

The safe way is more or less this one:

Code: Select all

volatile BYTE* ptr = DMA_HIGH;
Uint32 p;
p = *ptr, p <<= 8;
p |= ptr[2], p <<= 8;
p |= ptr[4];


(Also, I find a bit strange that you take one byte every two bytes to make p, but I don't know what your code does).

ijor
Hardware Guru
Hardware Guru
Posts: 3907
Joined: Sat May 29, 2004 7:52 pm
Contact:

Postby ijor » Fri Feb 23, 2007 9:53 pm

(Jean, please forgive the off-topic :)

Lautreamont wrote:Yes I was talking of that part :

Code: Select all

... = *ptr << 16 ...

The ANSI C tells not to shift of a length that's longer than the one of your variable.


Interesting, I didn't know that. But it seems, you are only partially right and that expression should (might) still be safe in this regard.

The ANSI C standard states: "A7.8 Shift Operators...The type of the result is that of the promoted left operand." (bolding is mine).

This means that the left operand of the shift will still be promoted to int, but not to bigger types (not even when the right operand is bigger than int).

Furthermore, he is not actually using the shift alone. He is ANDing first. And logical operators are not subject to the "right operand doesn't promote" rule.

So the actual result on the worst case depends on what is the int size (that in turn depend on the compiler and compiler switches). If int is 16-bit, then the shift perhaps might be unsafe, it is safe if it is 32-bit.

(Also, I find a bit strange that you take one byte every two bytes to make p, but I don't know what your code does).


This is because how the I/O registers are mapped.

User avatar
DrCoolZic
Fuji Shaped Bastard
Fuji Shaped Bastard
Posts: 2258
Joined: Mon Oct 03, 2005 7:03 pm
Location: France
Contact:

Postby DrCoolZic » Sat Feb 24, 2007 9:00 pm

ijor wrote:

Code: Select all

p = ((*ptr & 0xFF) << 16) | ((*(ptr+2) & 0xFF) << 8 ) | ((*(ptr+4) & 0xFF));

What it is not safe however, it is the order of the OR operations. The compiler is free to reorder all the three reads. This is in theory, all compilers I've seen will not make any reorder.

Anyway, what is wrong here is the design. You can't read the DMA address like that when DMA is running. This is not an atomic operation, and the hardware doesn't guarantee that you are not reading across two different DMA transfers.


Yes you are right in both case. I do not remember why this piece of code has been published but it is totally out of context.

As you mention the most important is the fact that it is not an atomic operation (if it was "atomized" the order would not matter) and therefore it must be used in a certain context. For example:
1) Either the DMA is not running (this gives the reading an atomic semantic)
2) you do not care if you get an "temporary wrong result" as long as you can predict that the result will be for example less than the real current.

The problem (if it was aproblem) would be easy to fix by surrounding the reading with a stop and start of the DMA around the reading.

In fact as you know the DMA address is obvioulsly always an increasing value and I only use it in "multiple read sector" to check in a loop that the current DMA address is equal or above the target address. Therefore even if have the bad luck to read an erroneously (half before half after increment) lower address it should not matter as I will catch it in the next loop (this is a tight loop that only test the INTRQ and the DMA address and therefore not extremely time critical).

But to get back to the heart of the thread (i.e. timing measurement) I do not use this function as it is too much time consuming. Remember that I have been able to reduce the loop time to less than 15usec. For that matter I had to write in assembly and part of the optimization is to only read one piece of the DMA address that is realy needed (if you see what I mean...). In this context everything is perfectly predictable (if you disable the interrupts) and only occuring about every 500usec.

As far as the discussion about the line of C code, I prefer not enter the debate: As I mentionned the target p is long (and I mean p not *ptr) and for me the compiler does what it is expected to do ... (but I may be wrong ...). For info I have participated in the USA to the early working group that have defined C++ 2.0 (pre ANSI) and I know that this kind of discussion can go on forever .... but it is fun!

What realy bother me is the problem pointed by leonard. He is perfectly right and the volatile qualifier make sense. What is strange is that it works without and even more strange that it does not work with. It is possible to say that the Lattice C compiler does not perform too much optimization and therefore works without the volatile. But it does not make sense that it does not work when using it ?!?
I still do not fully understand all the subtilities of the 68K ASM but the code change performed by the compiler just by adding the volatile kw does not make any sense to me (this extra AND ?????).

Now that I have all the required informations on linking C and assembly (thanks to Belboz and Nyh http://www.atari-forum.com/viewtopic.ph ... c&start=15 ) I will probably adhere to the advice of Ijor to code the access to the I/O reg in ASM.

I wrote an initial version of my "timing loop" in C and it was aound 100µsec. I have optimized it to about 30-40µsec by removing all the possible "fat". Then I looked at the generated ASM code and ... nothing wrong but far less optimized that what I have been able to obtain.
I still think that it make sense to write most piece of my code in C but for accessing HW I will now stick to ASM.

Lautreamont
Obsessive compulsive Atari behavior
Obsessive compulsive Atari behavior
Posts: 103
Joined: Fri Jan 27, 2006 9:11 pm
Location: Friceland

Postby Lautreamont » Sat Feb 24, 2007 10:30 pm

ijor wrote:So the actual result on the worst case depends on what is the int size (that in turn depend on the compiler and compiler switches). If int is 16-bit, then the shift perhaps might be unsafe, it is safe if it is 32-bit.

Once again, thanks for your explanation.
I always have portability in mind when I write C code.

I have only a small experience at writing C code on the ST, and as it is told to be a 16/32 bits computer,
I'm wondering if ints are 16 bits for some compilers and 32 bits for others.

I checked the generated asm code for that line and it's ok (Pure C).

ijor
Hardware Guru
Hardware Guru
Posts: 3907
Joined: Sat May 29, 2004 7:52 pm
Contact:

Postby ijor » Sun Feb 25, 2007 1:37 am

DrCoolZic wrote:The problem (if it was aproblem) would be easy to fix by surrounding the reading with a stop and start of the DMA around the reading.


Stopping DMA is usually not possible to do it harmless. The solution (if you would need to do that) is to read the DMA address twice in a row. But as you already know, you don't need this if you are in a closed loop.

But to get back to the heart of the thread (i.e. timing measurement) I do not use this function as it is too much time consuming.


Ah, ok, I see.

What realy bother me is the problem pointed by leonard. He is perfectly right and the volatile qualifier make sense. What is strange is that it works without


It probably has to do with the way you coded. You are assigning a new value to the pointer on every function call. If you change the pointer, then not being volatile doesn't matter anymore. And besides, volatile is not relevant for non-static variables across function calls. Only a heavy optimizing compiler that would automatically inline some of those functions would be affected by not using a volatile modifier.

But it does not make sense that it does not work when using it ?!?
I still do not fully understand all the subtilities of the 68K ASM but the code change performed by the compiler just by adding the volatile kw does not make any sense to me (this extra AND ?????).


I wouldn't worry too much about the extra AND. I do would worry about the wrong stack overflow check and about the bus error exception.

ijor
Hardware Guru
Hardware Guru
Posts: 3907
Joined: Sat May 29, 2004 7:52 pm
Contact:

Postby ijor » Sun Feb 25, 2007 1:47 am

Lautreamont wrote:Once again, thanks for your explanation.


Well, I must thank you as well. Honestly, I wasn't aware about the particular way that promotion works on shifts. This means that something like the following might not work as I expected:

Code: Select all

 shortVariable << longVariable


I guess I never noted that because it is relevant only when int is smaller than long, which would happen in 16-bit mode, but not in 32-bit mode.

I have only a small experience at writing C code on the ST, and as it is told to be a 16/32 bits computer, I'm wondering if ints are 16 bits for some compilers and 32 bits for others.


Yes. Older US compilers were most (all?) 16 bits, like Megamax, Laser, Alycon. Latter Lattice compilers let you select if int is 16 or 32 bits. I don't know about Pure C.

User avatar
DrCoolZic
Fuji Shaped Bastard
Fuji Shaped Bastard
Posts: 2258
Joined: Mon Oct 03, 2005 7:03 pm
Location: France
Contact:

Postby DrCoolZic » Sun Feb 25, 2007 8:45 pm

The controversy created by the one line of code I have published pushed me to write a short pamphlet on the subject. Please make sure you do no not take that too seriously

The beauty of programming in C
Yes portability is an issue that you must always have in mind. For the one line of code that started this discussion you may wander if portability is really an issue? As this code is accessing the HW register of an Atari machine it will obviously not work on any other machine. Yet even here portability may be an issue if you want to be able to accommodate several C compilers on the Atari.

It is interesting to note that everybody has some ideas of how things work on a particular platform (as of course I do). For example on Atari which compilers treat int as short and which treat them as long? Personally I was under the assumption that on 68K platform (considered as a 32 bit processor?) any serious compilers would treat int as long? I suspect that this kind of assumptions on platform dependent features is dictated by the programmer’s background. For example I have done most of my C/C++ development on SUN UNIX and ported the code on many platforms / architecture (sun, hp, ibm, digital …). On all these UNIX platforms an int was always defined as 4 bytes long. In this kind of predictable cosy environment programmers tend to be lazy and to forget about portability issues. Therefore int has been used and abused all over the places (actually there are some good reasons for that explained latter) to the point that people where even casting pointers to int or vice-versa! (as an excuse you have to know that before ANSI C void* did not exist)! Life (specially in California) was nice and cool until some companies came with new processor architectures! Among these some famous example:
1) Some RISC architectures that were using optimized pointer to access internal register. These pointers could be one byte or 10 bits or whatever! Obviously not a good idea to cast int to this kind of pointers!!!
2) Some 64 bits architecture, like the Digital Alpha processor. The Alpha C compiler would consider an int as 4 bytes a long as 8 bytes and a pointer as 8 bytes. Therefore porting to Alpha has put a strong emphasis on badly developed code. This has been such a major problem for so many customers that Digital had to create special teams to help…
But all this is history… and I am glad to see that now people are concerned with portability upfront.

To get back to the Atari … one question is to find out if an int 2 bytes or 4 bytes? The obvious answer is that it is compiler dependant and therefore not predictable! For example I checked an old version of the v3.04 Lattice C compiler and at the time you had no choice int was 4 bytes. I also checked the latest version 6.x of the Lattice C compiler it also uses 32 bits for int by default but as mentioned by Ijor you can change this by defining _SHORTINT which gives you 2 bytes int. I have also checked the Pure C compiler and to my surprise it seems like the default is 16 bits and apparently it is the same (16 bits) for Borland Turbo C.

Now let’s first review some basic definitions:

Fundamental types in C are divided into three categories: integral, floating, and void. Integral types are capable of handling whole numbers. Floating types are capable of specifying values that may have fractional parts.

Integral Types
    - char : Type char is an integral type that usually contains members of the execution character set (usually ASCII). The C compiler treats variables of type char, signed char, and unsigned char as having different types. Variables of type char are sometimes promoted to int if they are type signed char.
    - short: Type short int (or simply short) is an integral type that is larger than or equal to the size of type char, and shorter than or equal to the size of type int. Variables of type short can be declared as signed short or unsigned short. Signed short is a synonym for short.
    - int: Type int is an integral type that is larger than or equal to the size of type short int, and shorter than or equal to the size of type long. Variables of type int can be declared as signed int or unsigned int. Signed int is a synonym for int.
    - long : Type long (or long int) is an integral type that is larger than or equal to the size of type int. Variables of type long can be declared as signed long or unsigned long. Signed long is a synonym for long.

Why is int so much use?
The int and unsigned int type specifiers are widely used in C programs because they allow a particular machine to handle integer values in the most efficient way for that machine. And therefore this was highly recommended at that time. However, since the sizes of the int and unsigned int types vary, programs that depend on a specific int size may not be portable to other machines.

So what is the size of int on my machine?
The size of a signed or unsigned int item is suppose to be the standard size of an integer on a particular machine. For example, in 16-bit operating systems, the int type is usually 16 bits, or 2 bytes. In 32-bit operating systems, the int type is usually 32 bits, or 4 bytes. Thus, the int type is equivalent to either the short int or the long int type, and the unsigned int type is equivalent to either the unsigned short or the unsigned long type, depending on the target environment. The int types all represent signed values unless specified otherwise.
This is all nice but yet it does not give you any information that can be used directly in a program. To make programs more portable, you can use expressions with the sizeof operator instead of hard-coded data sizes. Another alternative is to look for maximum values in limit.h

Now what about the bitwise shift operators (<<and>>) :

Both operands of the shift operators must be of integral types. Integral promotions are performed according to the rules described in Integral Promotions. The type of the result is the same as the type of the left operand.


Integral promotion:
Variables of an integral type can be converted to another wider integral type (that is, a type that can represent a larger set of values). This widening type of conversion is called "integral promotion. In C the integral types are char, int, and long (and the short, signed, and unsigned versions of these types). C promotions are "value-preserving." That is, the value after the promotion is guaranteed to be the same as the value before the promotion.

Integral Conversion
Integral conversions are performed between integral types. Variables of an integral type can be converted to another wider integral type (that is, a type that can represent a larger set of values). This widening type of conversion is called "integral promotion". (Note that conversion can also result in an integral demotion not presented here)

It is not always easy to interpret the information from the original ANSI C definition presented in the K&R book (of course the second ANSI edition). Thanks to the ANSI C/C++ working groups a lot of things have been clarified in C++ and the resulting clarifications have been applied back to ANSI C compilers (of course this only apply to relatively more recent C compilers). You may wander but this does not apply to my old C compiler developed for Atari many years before C++ was specified? Well the good news is that in most cases the decision and clarifications where taken based on the most widely accepted interpretations that where therefore widely implemented. That means that most C compilers where doing the right things even if they did not know!!!

If we take the ANSI version of K&R we see in section A7.8 that the left operand of a shift operator is subject to an integral promotion. But what kind of integral promotion: a short, an int, or a long ??? If we now look to A6.1 and interpret stricto sensus it seems to indicate that the integral promotion is apparently limited to an int??? But in fact we know that most compilers do the correct promotion this means to a long if needed. Otherwise the code presented above would not work!

So you may fill more secure using the code from Lautreamont

Code: Select all

volatile unsigned char* ptr = DMA_HIGH;
long p;
p = *ptr, p <<= 8;
p |= ptr[2], p <<= 8;
p |= ptr[4];

which is somewhat equivalent to the code I presented before (which is admitedly ugly):

Code: Select all

volatile unsigned char* ptr = DMA_HIGH;
register long p;
p = *ptr;
p <<= 8;
(unsigned char)p = *(ptr + 2);
p <<= 8;
(unsigned char)p = *(ptr + 4);

Is this code 100% safe and portable? In practice yes and maybe more portable than

Code: Select all

p = (*ptr << 16) + (*(ptr+2) << 8) + *(ptr+4);


However if you remember that:
char <= short <= int <= long

This implies that in theory a long can be 8 bits and would not handle 24 bits quantity!!! In practice I have never seen a long to be one byte, but however it is quite possible to have long with 16 bits on 4 or 8 bit controllers.

Of course the solution is easy just use the sizeof operator and make sure that your int / long (use whatever you would like to us) is at least 24 bits … If this is not the case then you will have to develop your own library to perform 24bits long additions.

Who said C is not fun... Can you believe that when programming in java not only you do not have pointers but you don’t even have to take care of the memory management…
As we say in France: ils sont fous ces romains... (From Asterix cartoon)

Lautreamont
Obsessive compulsive Atari behavior
Obsessive compulsive Atari behavior
Posts: 103
Joined: Fri Jan 27, 2006 9:11 pm
Location: Friceland

Postby Lautreamont » Sun Feb 25, 2007 10:13 pm

Woah, you wrote a faq for a single line of C, a few lines of asm...

So much work...
Very interesting though, I'll probably print it and add it to my K&R !

We know it's not that line. Now go debug your program ! :wink:

ijor
Hardware Guru
Hardware Guru
Posts: 3907
Joined: Sat May 29, 2004 7:52 pm
Contact:

Postby ijor » Sun Feb 25, 2007 10:31 pm

DrCoolZic wrote:Personally I was under the assumption that on 68K platform (considered as a 32 bit processor?) any serious compilers would treat int as long?


The 68K is not a 32-bit CPU. Most 32-bit operations are more expensive than 16-bit ones. It is usually much more efficient to make int 16 bits.

So I would say that most "serious" compiler should make int != long. Later Lattice compilers made the right thing and give you the best of both worlds by letting the user choose.

If we take the ANSI version of K&R we see in section A7.8 that the left operand of a shift operator is subject to an integral promotion. But what kind of integral promotion: a short, an int, or a long ??? If we now look to A6.1 and interpret stricto sensus it seems to indicate that the integral promotion is apparently limited to an int??? But in fact we know that most compilers do the correct promotion this means to a long if needed. Otherwise the code presented above would not work!


I think that Lautremont has a point, and you are reaching the wrong conclusion.

It is very clear from the ANSI C standard that promotion should be done to int and not to long. You don't know that most compiler to what you want, you only tested one 16-bit compiler...

Let's try Turbo C for DOS ... Yep, it follows the standard strictly and promotion is done to 16-bit, not to 32-bit. So it is very possible that the ST version of Turbo C would generate the "wrong" code for you.

I guess that Pure C promotes to 32-bit because in the 68K it is rather "cheap" (not so in 16-bit x86). But it is possible that this could be considered a compiler bug.

User avatar
DrCoolZic
Fuji Shaped Bastard
Fuji Shaped Bastard
Posts: 2258
Joined: Mon Oct 03, 2005 7:03 pm
Location: France
Contact:

Postby DrCoolZic » Mon Feb 26, 2007 5:16 pm

ijor wrote:The 68K is not a 32-bit CPU. Most 32-bit operations are more expensive than 16-bit ones. It is usually much more efficient to make int 16 bits.
Yes you are perfectly right. I am glad to see that the advertizing from Motorola did not work. Remember that I have been working at Intel for many years and this is probably a bad joke!
I breifly looked at the timing in the 68K user's manual. I took the add operations: and you are right the word op are faster than long op (but a bit slower than byte op in certain cases).

It is very clear from the ANSI C standard that promotion should be done to int and not to long.
Humm if it is very clear for you, it was not so clear during the ANSI C discussions... So I checked with ANSI guys and ... yes you win
Rationale for American National Standard for Information Systems - Programming Language - C wrote:The description of shift operators in K&R suggests that shifting by a long count should force the left operand to be widened to long before being shifted. A more intuitive practice, endorsed by the Committee, is that the type of the shift count has no bearing on the type of the result.


QUIET CHANGE

Shifting by a long count no longer coerces the shifted operand to long.
So you must be intuitive as you guessed the right answer.
for more info look at http://www.lysator.liu.se/c/rat/c3.html#3-3-7

You don't know that most compiler do what you want, you only tested one 16-bit compiler...
Sorry I did not thought I suggested that? I just wanted to point that with the only compiler I tested (the Lattice C 6.x compiler) it worked as I was expecting otherwise I would not had the right result.

Let's try Turbo C for DOS ... Yep, it follows the standard strictly and promotion is done to 16-bit, not to 32-bit. So it is very possible that the ST version of Turbo C would generate the "wrong" code for you.
Actually this make sense as this is a recent compilers that must adhere to the ISO/IEC9899-1999 (the C99 version of the ANSI C). However I looked at the ASM generated by Visual C++.Net and it does not seems to follow the rules (meaning it uses 32 bit instructions on 32 registers)?

I guess that Pure C promotes to 32-bit because in the 68K it is rather "cheap" (not so in 16-bit x86). But it is possible that this could be considered a compiler bug.
Sorry I cannot answer as I have no idea about what Pure C is doing? You should ask Nyh that seems to be the expert here. Personally I have not yet been very lucky with Pure C.

I should get soon the ISO9899-1990 (C89) and the ISO9899-1999 (C99) documents and I will look if they have clarified this matter in the final document.

By the way if you are using the K&R second edition as the bible (as I do) you may be interested by the Errata for The C Programming Language, Second Edition goto http://cm.bell-labs.com/cm/cs/cbook/2ediffs.html

ijor
Hardware Guru
Hardware Guru
Posts: 3907
Joined: Sat May 29, 2004 7:52 pm
Contact:

Postby ijor » Mon Feb 26, 2007 6:30 pm

DrCoolZic wrote:I breifly looked at the timing in the 68K user's manual. I took the add operations: and you are right the word op are faster than long op (but a bit slower than byte op in certain cases).


Hmm, no word operation is slower than the corresponding byte one (Unless you consider branches as operations). May be you were looking at the 68008 table?

Humm if it is very clear for you, it was not so clear during the ANSI C discussions... So I checked with ANSI guys and ... yes you win
Rationale for American National Standard for Information Systems - Programming Language - C wrote:The description of shift operators in K&R suggests that shifting by a long count should force the left operand to be widened to long before being shifted...


You are mixing up two different issues. One issue is that the right operand on shifts doesn't promote. Another issue is that "Integral promotion" is done to int and not to long.

The first issue is not relevant here. It is not relevant because char and short types are always promoted to int disregarding the right operand type. It would be relevant in the following code:

Code: Select all

charVar << 16UL


If you try to force a promotion to long like that, by using a long type in the right operand; then it might work in K&R but not in ANSI C. But this is not our case.

Sorry I did not thought I suggested that? I just wanted to point that with the only compiler I tested (the Lattice C 6.x compiler) it worked as I was expecting otherwise I would not had the right result.


Ah, ok, sorry for the misunderstanding.

Let's try Turbo C for DOS ...
Actually this make sense as this is a recent compilers that must adhere to the ISO/IEC9899-1999 (the C99 version of the ANSI C). However I looked at the ASM generated by Visual C++.Net and it does not seems to follow the rules (meaning it uses 32 bit instructions on 32 registers)?


I was talking about the old 16-bit real-mode Turbo C for DOS. It is not a recent compiler at all (copyright date is 1988). Testing with modern Visual C++ is not useful because it is a 32-bit compiler. So it will promote to 32 bits because int is 32 bits. Exactly the same that it happens with Lattice C because you are using the int=32 bits setting.

If you test Lattice C selecting int size as 16 bits ... let's see ... then it performs like Turbo C, it promotes to int (16 bits) and not to long.

By the way if you are using the K&R second edition as the bible (as I do) you may be interested by the Errata for The C Programming Language, Second Edition goto http://cm.bell-labs.com/cm/cs/cbook/2ediffs.html


Interesting, thanks for the link. Btw, after Lautremont raised the issue about shifts, I realized that it still has an error that was never corrected, not even in that errata.

I you look at the appendix C (changes from the first edition) it claims: "The type of a shift expression is that of the left operand". But as described in the main chapter, it is incorrect, it is the type of the promoted left operand.

User avatar
DrCoolZic
Fuji Shaped Bastard
Fuji Shaped Bastard
Posts: 2258
Joined: Mon Oct 03, 2005 7:03 pm
Location: France
Contact:

Postby DrCoolZic » Mon Feb 26, 2007 7:04 pm

ijor wrote:You are mixing up two different issues. One issue is that the right operand on shifts doesn't promote. Another issue is that "Integral promotion" is done to int and not to long.

I do not agree. For one both operands gets promoted.
ISO wrote:6.5.7 Bitwise shift operators
Syntax
1 shift-expression:
additive-expression
shift-expression <<additive>> additive-expression
Constraints
2 Each of the operands shall have integer type.
Semantics
3 The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand. If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.

But this is not the point.
Rationale for American National Standard for Information Systems - Programming Language - C wrote:The description of shift operators in K&R suggests that shifting by a long count should force the left operand to be widened to long before being shifted.
To me this means that if the shifting of the left operand by the right operand does not fit (they talk about a long count not a long type) then it gets promoted.
But that has nothing to do with the type of the right operand. Having a long right operand make no sense. Even if you use a long left operand you can at most shift it by 32 and this fit very well even in a char! Who want to shift by billions?
[code]charVar << 16UL
Why would you write such a thing ????

If you try to force a promotion to long like that, by using a long type in the right operand; then it might work in K&R but not in ANSI C. But this is not our case.
This is of course absolutely not what I meant. Again I am talking about promoting the left operand so it can hold the result independently of the type of the right operand.

I you look at the appendix C (changes from the first edition) it claims: "The type of a shift expression is that of the left operand". But as described in the main chapter, it is incorrect, it is the type of the promoted left operand.
As you can see above this has been corrected in C99.
Last edited by DrCoolZic on Mon Feb 26, 2007 8:30 pm, edited 1 time in total.

User avatar
DrCoolZic
Fuji Shaped Bastard
Fuji Shaped Bastard
Posts: 2258
Joined: Mon Oct 03, 2005 7:03 pm
Location: France
Contact:

Postby DrCoolZic » Mon Feb 26, 2007 8:13 pm

Lautreamont wrote:Woah, you wrote a faq for a single line of C, a few lines of asm...

So much work...
Very interesting though, I'll probably print it and add it to my K&R !

We know it's not that line. Now go debug your program ! :wink:

I have started to code in C more than 20 years ago and, as you can see, I am still learning!
I have tried many languages but for me C++ is still the best programming language for most applications. But I do not want to open another debate here...
It was refreshing to get back to C when I decided to write some applications for the Atari, but C++ is definitively my favorite. Both languages have a lot in common but once you have somewhat masterized the OO programming going back to procedural programming seems odd.

As far as my program/libraries goes it is in relatively good shape with Lattice C compiler but still needs a lot of testing and more features.
You were talking about porting issues and one of my goal was to get a library with Pure C but so far I am totally unsuccessful.
While we were discussing about the int type on different compilers I wrote these lines:

Code: Select all

   fprintf(stdout, "Sizeof a char is %d\n", sizeof(char));
   fprintf(stdout, "Sizeof a short is %d\n", sizeof(short));
   fprintf(stdout, "Sizeof an int is %d\n", sizeof(int));
   fprintf(stdout, "Sizeof a long is %d\n", sizeof(long));
   fprintf(stdout, "Sizeof a pointer %d\n", sizeof(void*));
Even this is not working with Pure C. First bus error?!? Changed the project file and now the program runs but returns 0 for all the type???
Of course I must do something stupid but I still need to investigate.

User avatar
DrCoolZic
Fuji Shaped Bastard
Fuji Shaped Bastard
Posts: 2258
Joined: Mon Oct 03, 2005 7:03 pm
Location: France
Contact:

Postby DrCoolZic » Mon Feb 26, 2007 8:28 pm

Ijor,
I have a question for you.
I am trying to read the counter register in the DMA (set the mode to $90) and read, but I am getting totally erroneous (if not random) numbers.
Say that I load the DMA counter with 1 for transfering 512 bytes and even before starting the transfer (i.e. before sending the command to the FDC) I read back the value and I get constant value (I mean always the same)but never 1???
I found this by "accident" because I wanted to verify that the count went down to 0 after the transfer, and to my surprise it was a different value? This is why I tested before after (and even during - I know this is bad idea) and always got strange result. By the way in Steem the emulation is better than the HW as it always return the right expected value.
I was so surprised that I thought of a bug in the DMA of my STE. So I tested on my old STF and I got the same problem???
Are you aware of this problem?
I say problem but in fact it is not really a problem as reading the count is useless but it is strange.
For info Atari only specify the last three bits of the status and they seems to work as described (I even tested the DRQ and it works), but the other 13 bits gets also changed and the result do not look random. But I have not yet been able to correlate the values to something useful?
Have you any idea on these bits?

ijor
Hardware Guru
Hardware Guru
Posts: 3907
Joined: Sat May 29, 2004 7:52 pm
Contact:

Postby ijor » Mon Feb 26, 2007 8:32 pm

Hi Jean,

I will reply to the previous message later (it is going far off topic), so I will not if you prefer.

DrCoolZic wrote:You were talking about porting issues and one of my goal was to get a library with Pure C but so far I am totally unsuccessful.
While we were discussing about the int type on different compilers I wrote these lines:

Code: Select all

   fprintf(stdout, "Sizeof a char is %d\n", sizeof(char));
   fprintf(stdout, "Sizeof a short is %d\n", sizeof(short));
   fprintf(stdout, "Sizeof an int is %d\n", sizeof(int));
   fprintf(stdout, "Sizeof a long is %d\n", sizeof(long));
   fprintf(stdout, "Sizeof a pointer %d\n", sizeof(void*));
Even this is not working with Pure C. First bus error?!? Changed the project file and now the program runs but returns 0 for all the type???
Of course I must do something stupid but I still need to investigate.


It's not stupid, it is just not portable. Believe it or not, the above code is not portable because the type of "sizeof" is not guaranteed to be int. As a matter of fact, in most 16-bit implementations is long. So cast all those sizeof's to int.

ijor
Hardware Guru
Hardware Guru
Posts: 3907
Joined: Sat May 29, 2004 7:52 pm
Contact:

Postby ijor » Mon Feb 26, 2007 8:40 pm

DrCoolZic wrote:Are you aware of this problem?
I say problem but in fact it is not really a problem as reading the count is useless but it is strange.
For info Atari only specify the last three bits of the status and they seems to work as described (I even tested the DRQ and it works), but the other 13 bits gets also changed and the result do not look random. But I have not yet been able to correlate the values to something useful?
Have you any idea on these bits?


I'm not sure what are you asking.

You can't (well, you can, but you shouldn't) mess with the "DMA mode" ($8606) register while DMA is running.

Yes, the other bits of "DMA status" are unused. I will need to check my notes, I don't remember by heart if they were totally random or not. It is possible that they are "undefined" but deterministic. That is, they could depend on previous values. Again, would need to check my notes.

User avatar
DrCoolZic
Fuji Shaped Bastard
Fuji Shaped Bastard
Posts: 2258
Joined: Mon Oct 03, 2005 7:03 pm
Location: France
Contact:

Postby DrCoolZic » Mon Feb 26, 2007 8:43 pm

For the shift operator, I think we have pretty much beaten up the subject and I think i is time to shut off or we can go on forever.

sizeof if i remember correctly returns a size_t type but I thought this would be printed correctly with %d (it does in many implementation). I will give a try.

Lautreamont
Obsessive compulsive Atari behavior
Obsessive compulsive Atari behavior
Posts: 103
Joined: Fri Jan 27, 2006 9:11 pm
Location: Friceland

Postby Lautreamont » Mon Feb 26, 2007 8:47 pm

DrCoolZic wrote:While we were discussing about the int type on different compilers I wrote these lines:

Code: Select all

   fprintf(stdout, "Sizeof a char is %d\n", sizeof(char));
   fprintf(stdout, "Sizeof a short is %d\n", sizeof(short));
   fprintf(stdout, "Sizeof an int is %d\n", sizeof(int));
   fprintf(stdout, "Sizeof a long is %d\n", sizeof(long));
   fprintf(stdout, "Sizeof a pointer %d\n", sizeof(void*));
Even this is not working with Pure C. First bus error?!? Changed the project file and now the program runs but returns 0 for all the type???
Of course I must do something stupid but I still need to investigate.


Add an extra "l" ("%ld") :).

User avatar
DrCoolZic
Fuji Shaped Bastard
Fuji Shaped Bastard
Posts: 2258
Joined: Mon Oct 03, 2005 7:03 pm
Location: France
Contact:

Postby DrCoolZic » Mon Feb 26, 2007 8:48 pm

ijor wrote:You can't (well, you can, but you shouldn't) mess with the "DMA mode" ($8606) register while DMA is running.

Ok lets say it is not running. Just write to the countr register, then read it back. In my case return value is different???
The written value seems to be taken correctly so if you set it to 1 the DMA will stop after 512 bytes, but again the read value is always wrong for me???
The scenario I was describing is the following:
DMA stoped, write 1 in count rgister, start DMA and FDC read command. At the endstop DMA, read count register and returned value is not 0.

ijor
Hardware Guru
Hardware Guru
Posts: 3907
Joined: Sat May 29, 2004 7:52 pm
Contact:

Postby ijor » Mon Feb 26, 2007 9:05 pm

DrCoolZic wrote:For the shift operator, I think we have pretty much beaten up the subject and I think i is time to shut off or we can go on forever.


Ok, issue locked :)

sizeof if i remember correctly returns a size_t type but I thought this would be printed correctly with %d (it does in many implementation). I will give a try.


It would print correctly when size_t == int, which is the case in most 32-bit implementations. But in most 16 implementations size_t != int.

Lautreamont wrote:Add an extra "l" ("%ld") :).


This would fix in in this case, and will still work in most cases. But more portable would be to cast the result of sizeof to int. Because, conceivable, in some implementations size_t might be != long.

ijor
Hardware Guru
Hardware Guru
Posts: 3907
Joined: Sat May 29, 2004 7:52 pm
Contact:

Postby ijor » Mon Feb 26, 2007 9:20 pm

DrCoolZic wrote:DMA stoped, write 1 in count rgister, start DMA and FDC read command. At the endstop DMA, read count register and returned value is not 0.


The count register seems to be write only. Again, I would need to check my notes. But there a few minor aspects of the DMA logic that I don't have a complete understanding.

I checked the FPGA published code some time ago. But, at least at that time, the DMA logic there was completely wrong. So it seems that the author has an even worse understanding of the topic.

Some things are obviously clear. But the exact interaction between the 3 components, DMA, MMU & GLUE is not completely clear to me for the DMA aspects. It is clear that some registers are not in the DMA chip, obviously the DMA address is in MMU. And it is also clear that some registers are shared/shadowed by at least 2 chips.

I would say that DMA is one of the most complex topics of the ST chipset.

In anycase, and disregarding the hardware behaviour, you are not supposed to read the sector counter (DMA stopped or not). Atari warned about that in some of the notes. You can read the DMA address if you want to know how much bytes were transferred, which is more precise than the sector counter (even if you could read it).

User avatar
DrCoolZic
Fuji Shaped Bastard
Fuji Shaped Bastard
Posts: 2258
Joined: Mon Oct 03, 2005 7:03 pm
Location: France
Contact:

Postby DrCoolZic » Tue Feb 27, 2007 11:36 am

ijor wrote:The count register seems to be write only. Again, I would need to check my notes. But there a few minor aspects of the DMA logic that I don't have a complete understanding.

You are right in the sense that the count register does not return what you expect. However as I mentioned the value returned is not random but "stable". What I mean is that this value is somewhat unpredictable but if you read it several time it will always be the same value. It is a bit like the upper bits of the status register they are undocumented but not random.

I checked the FPGA published code some time ago. But, at least at that time, the DMA logic there was completely wrong. So it seems that the author has an even worse understanding of the topic.
Which FPGA did you checked? There at least two descriptions floating around (plus a very promising coming out soon?). The work done by Wolfgang Foerster at http://www.experiment-s.de seems excelent. I am in contact with him about the FDC. He did an excelent work in this area and as I mentionned in another thread I have helped him fixed few things in the VHDL model of the WD1772 DPLL.

I would say that DMA is one of the most complex topics of the ST chipset.
I agree and as you probably remember we already had discussion on the subject about memory access...

In anycase, and disregarding the hardware behaviour, you are not supposed to read the sector counter (DMA stopped or not). Atari warned about that in some of the notes.

You are right in one of the doc from Atari it says write only.
Engineering HW Specification ... - Atari 1986 wrote: (section 2.3) DMA Sector Count Register (word access write only, reset: all zeros)

Also checked:
    Application Notes on the ASCI - Atari 1985
    Atari ASCI/DMA Integration Guide - Atari 1991 (excellent)
    Atari ST/STe/MSTe/TT/F030 Hardware Register Listing - 1/22/94 By Dan Hollis
They are all relatively unclear. One of the problem is that the same address is shared between FDC/HDC register access and cout register access. Therefore these address are declared r/w which is true when accessing FDC/HDC register ...

You can read the DMA address if you want to know how much bytes were transferred, which is more precise than the sector counter (even if you could read it).
In fact I have never been interested in reading the count register as the information is not much useful as you mention. It is just that during debug of the libraty I was checking all registers of all chips for information and was surprise by the content of the count register.

While we are talking about the DMA chip. There is something that surprise me in
Engineering HW Specification ... - Atari 1986 wrote: 841017G Select the Sector Count Register before testing the DMA Status Register Error bit.
This does not make sense to me as the status is not multiplexed ? Don't know why they advice this ?


During experimentation with the DMA I found an interesting information that is actually mentioned in some of the document above but relatively unclear. I would like to know if you have found the same answer.
As you know the DMA has internally FIFOS. During read it is clear that the transfer of the FIFO is done when one of them is full. But what about when DMA is in writing mode? Well of course a FIFO is filled as soon as it is empty. But the question is when is that "trigerred".
I found this again by reading the HW registers for debug purpose and was surprise to find that the address register of the DMA was incremented by 32 bytes (of course corresponding to the filling of the 2 FIFOS) immediately even before stating any FDC write operation. If you think about it, it makes sense: the DMA logic is totally independent of the FDC/HDC and data must be ready for the DC immediately. This is important if you read carefully the way the FDC works: one byte must be provided immediately to the FDC in write operation...

So here is the quiz: Do you know what trigger the DMA FIFO write operation?
I want to know if you have the same answer as this influence the order of the operand passed to the DMA. It is true that even without knowing the answer if you just follow the order used in flops.s it works.

User avatar
DrCoolZic
Fuji Shaped Bastard
Fuji Shaped Bastard
Posts: 2258
Joined: Mon Oct 03, 2005 7:03 pm
Location: France
Contact:

Postby DrCoolZic » Tue Feb 27, 2007 1:44 pm

OK
Here is my latest version of the famous routine!!!

Code: Select all

/*   DMA reading is non atomic operation and internal value may change while reading...
   As we read bytes with high, middle, low order the risk is to get a return value that
   is lower than the real current value. As address counter is monodically increasing
   it is possible to check for that in the calling routine */
UBYTE* dma_get_address(void) {
   UBYTE* ptr = DMA_HIGH;
   union { long l; UBYTE b[4]; } a;
   a.b[0] = 0;
   a.b[1] = *ptr;
   a.b[2] = *(ptr+2);
   a.b[3] = *(ptr+4);
   return (UBYTE*)a.l;
}
This routine should somewhat portable as long as pointer is a long! But the main goal was to reduce the size / execution time of the assembly - See the attached jpeg. Note that it is possible to do better by directly coding in ASM but the latest version is not too bad.
Note that the disasembler try to be too smart you should read 4 instead of _fdc_opt!
Question for the 68K ASM warriors.
1) Why link a6,#-8 and not link a6,#-4 ?
2) Why movea.l 8(a6), a0 + move.l a0,d0 instead of move.l 8(a6),d0 ?
2) Want to try to beat the C compiler and post your hand optimized version?
You do not have the required permissions to view the files attached to this post.

User avatar
Nyh
Atari God
Atari God
Posts: 1496
Joined: Tue Oct 12, 2004 2:25 pm
Location: Netherlands

Postby Nyh » Tue Feb 27, 2007 3:02 pm

Mopping up and commenting on some recent subjects in this thread...

DrCoolZic wrote:However the lib works great with the Lattice C compiler, but fails immediately with the Pure C compiler??? It gives a bus error on the first access to a HW register???

Pure C should work OK. I have never seen it optimizing pointer access.

DrCoolZic wrote:For the bus violation with Pure C here is a picture of where I am getting stuck. (Did not try volatile on Pure C).

I am completely stuck too. I don't understand how you got the output from the Pure C compiler. I tried to duplicate your code and it compiled fine as expected. The extra AND does not make sense IMNSHO.

Code: Select all

#define DMA_MODE (volatile unsigned int *)0xff8606L
#define DMA_DATA (volatile unsigned int *)0xff8604L

void fdc_set_reg(unsigned int regnum, unsigned int value)
{
  volatile unsigned int* wptr;
 
  wptr=DMA_MODE;
  *wptr=regnum;
  wptr=DMA_DATA;
  *wptr=value;
}


See also attached picture.

ijor wrote:Interesting, I didn't know that. But it seems, you are only partially right and that expression should (might) still be safe in this regard.

The ANSI C standard states: "A7.8 Shift Operators...The type of the result is that of the promoted left operand." (bolding is mine).

This means that the left operand of the shift will still be promoted to int, but not to bigger types (not even when the right operand is bigger than int).

Furthermore, he is not actually using the shift alone. He is ANDing first. And logical operators are not subject to the "right operand doesn't promote" rule.


I don't like to be dependent on compiler quirks in cases like this. I usually take the safe way. Most of my code contain too much () and {} just because I want to be on the safe side.

I would have used something like this ((unsigned long)*ptr)<<16, also do an explicit cast to a long before the shift.

It is true the result of a shift over the number of bits of a type or more is not defined in the C standard because the result is processor dependent. The 68000 will happily shift over up to 63 bits so there is no problem in this case.

DrCoolZic wrote:However if you remember that:
char <= short <= int <= long

This implies that in theory a long can be 8 bits and would not handle 24 bits quantity!!! In practice I have never seen a long to be one byte, but however it is quite possible to have long with 16 bits on 4 or 8 bit controllers.

No! According to the standard a char is at least 8 bits, an int at least 16 and a long at least 32 bits.

ijor wrote:The 68K is not a 32-bit CPU. Most 32-bit operations are more expensive than 16-bit ones. It is usually much more efficient to make int 16 bits.

The 68K has a 16 bit ALU. So it makes sense to make the int 16 bit.

DrCoolZic wrote:While we were discussing about the int type on different compilers I wrote these lines:
Code:
fprintf(stdout, "Sizeof a char is %d\n", sizeof(char));
fprintf(stdout, "Sizeof a short is %d\n", sizeof(short));
fprintf(stdout, "Sizeof an int is %d\n", sizeof(int));
fprintf(stdout, "Sizeof a long is %d\n", sizeof(long));
fprintf(stdout, "Sizeof a pointer %d\n", sizeof(void*));
Even this is not working with Pure C. First bus error?!? Changed the project file and now the program runs but returns 0 for all the type???
Of course I must do something stupid but I still need to investigate.

I don't know about the bus error. Must be you project file. But then you are treating the result of siseof() as an int which isn't correct, the result of sizeof() is size_t. You assume size_t to be int by using %d. When size_t is 32 bits and a int is 16 bits you get as answer 0 on a big endian system. 32 bits are pushed on the stack, the first 16 are used as the int value so you get all zeros. Cast the result of sizeof() to an int and your problem is solved.

Hans Wessels
You do not have the required permissions to view the files attached to this post.

User avatar
Nyh
Atari God
Atari God
Posts: 1496
Joined: Tue Oct 12, 2004 2:25 pm
Location: Netherlands

Postby Nyh » Tue Feb 27, 2007 3:30 pm

DrCoolZic wrote:OK
Here is my latest version of the famous routine!!!
Note that the disasembler try to be too smart you should read 4 instead of _fdc_opt!
Question for the 68K ASM warriors.
1) Why link a6,#-8 and not link a6,#-4 ?
2) Why movea.l 8(a6), a0 + move.l a0,d0 instead of move.l 8(a6),d0 ?
3) Want to try to beat the C compiler and post your hand optimized version?

1) Because Lattice C is a stupid compiler, why use stack frames?
2) Because Lattice C is a stupid compiler
3) Just use Pure C:

Code: Select all

unsigned char* dma_get_address(void) {      // subq.w  #4,A7
   unsigned char* ptr = DMA_HIGH;           // movea.l #$00FF8609,A0
   union { long l; unsigned char b[4]; } a;
   a.b[0] = 0;                              // clr.b   (A7)
   a.b[1] = *ptr;                           // move.b  (A0),$0001(A7)
   a.b[2] = *(ptr+2);                       // move.b  $0002(A0),$0002(A7)
   a.b[3] = *(ptr+4);;                      // move.b  $0004(A0),$0003(A7)
   return (unsigned char*)a.l;              // move.l  (A7),A0
                                            // addq.w  #4,A7
                                            // rts
}


By hand I can do just a bit better:

Code: Select all

subq.w #4,A7
move.l sp,a0
move.l #$00FF8609,a1
clr.b  (a0)+
move.b (a1),(a0)+
move.b 2(a1),(a0)+
move.b 4(a1),(a0)+
move.l (sp)+,a0
rts


Hans Wessels


Social Media

     

Return to “Coding”

Who is online

Users browsing this forum: No registered users and 4 guests