Machine Problem 6, due at the end of Nov 13
Part of
the homework for CS:2630, Fall 2023
|
Top Level Specification: You must provide a callable function, FTOINT that takes an IEEE 32-bit floating point parameter in R3 and returns a 32-bit two's complement integer holding the value of that argument.
The conversion to integer should truncate toward zero. That is, the fractional part of the parameter should be simply discarded, with no attempt to round to the nearest integer.
For most purposes, your code will be equivalent to (albeit harder to write) this C code:
int ftoint( float f ) { return (int)f; }
If the floating point value is larger than can be represented in 32 bits, the value returned should be 8000000016.
Your submission must not include a main program.
The symbol
Your code must conform to the standard Hawk calling sequence conventions.
We will link it with our main program to test it, and our program will assume
that you obey the calling conventions.
The submitted version of your code should not include an S directive.
Discussion: Curiously, there is no need to use the Hawk floating point
coprocessor in your FTOINT code. The floating point coprocessor
does integer to floating conversion, but not floating to integer.
You must write your own main program to test your code. You may well want to
use the floating-point coprocessor to do this. A bare minimum test program
might be equivalent to this C code:
If you use the above code, beware of several things: First, it was written
without regard to the limited precision of type float. We're using
32-bit IEEE format numbers, these only support 24 bits of precision. As a
result, there is no exact representation for 1,000,000,000 in that system.
It is a legal 32-bit integer, but convert it to float and back, and you'll
get something similar that may be off by quite a bit.
Doing this test on the Hawk will involve using the floating-point coprocessor,
since you need it to do integer to floating conversion (unless you want to
double your work load and write your own).
The above test does not cover NaNs (not-a-number values), infinite values,
fractions or floating-point values that are out of range. You'll probably
want to test these.
Grading:
5 points. Correct output is worth 2.5 points. The remaining credit will be
offered only for those who have reasonably correct output.
Code containing assembly errors will not earn any credit.
Your program must be written in SMAL Hawk assembly language, and it it must
be formatted so that it does not trigger warnings when you run
The final 2.5 points of the score will be based on program organization.
Stylistically clean code is important; that is why half the credit is reserved
for style, but only if the code works. Bad indenting of the assembly code
itself will be penalized up to 1 point from the 2.5 style points.
The use of indenting to improve comment readability is equally important.
Excessive or inadequate white space within
and between lines will be penalized, again, up to 1 point. Excessive or
inadequate comments will be judged similarly.
Comments must show clear evidence of planning. Clear comments in a notation
similar to C are very valuable. If the code does not do what the comments
say, that will be penalized.
Control structures in your code that cannot be expressed in a readable
high-level language style of commentary will be penalized. Comments that
do not reflect the actual control structure of your code will be penalized.
When you edit your code, don't forget to update the comments.
Function headers are important.
Do not deviate from the standard Hawk calling sequence rules.
Document your activation records! Document
the registers you use (but note: everyone should know that R1 and
R3 are locally available between calls to functions.
Do not put off commenting your code! If you ask for help with your code and
it is not properly commented, you will be told to write good comments first!
This is because the exercise of writing good comments frequently helps you find
out what your code really does, and even when it does not, the comments tell us
what you thought your code was supposed to do.
If you base your code on examples from the notes, code distributed in previous
course offerings, code distributed with lecture notes, or any source other
than your own brain, say so! Cite your sources! Failure to cite sources
will be penalized.
Submission:
To submit your work, it must be in a file named mp6.a in your
current directory on the CLAS Linux system, and you must know your section
number. In the following, what you type is shown in bold face. Begin at
the linux command-line prompt:
Note: The ~dwjones must be verbatim, do not substitute another name.
Also, use your section number (one of 0A01 to 0A05).
The submission script will copy your code and, after the copy is made, it
will output a message saying "successful submission."
You may resubmit as many times as you want;
each time you resubmit, your previous submission of that file will be replaced,
so we will only see your final submission.
In the event of insurmountable trouble, do not delete
or edit files you have submitted until the graded work is returned.
The operating system record of the last edit
time and date and the backups maintained by the operating system are
proof of what you did and when, and they allow us to investigate what happened.
until your graded work is returned. This way, the time stamp marking the
time of last edit is a trustworthy indicator of whether you did the work on
time.
I submitted what I’m working on, with hopes that you can see what I am
referring to with this. My comments outline my logic.
I looked at the code, and my immediate reaction is that you need to do some
little experiments, single stepping through small code fragments. For example,
your question about TRUNC can be answered by an experiment that takes
less time to run than it takes to e-mail me ang get a reply. Here's my
experimental code:
I assembled it. There's no need to link, since it doesn't use the operating
system, so I just ran the assembled object file under the Hawk emulator and
used the emulator's s command (single step) to run one instruction
at time, looking at how R8 changes. Here's what I got:
In looking at your code, I see several places where similar trivial experments
would help you understand your code.
At one point in your code, you made an interesting optimization. A C
programmer might have written this to clear the sign bit:
Note that the LIW macro is really LIL plus ORIS and
note that LIL is a long instruction. You did something like this
instead:
That almost works, and in fact it's on the road to a good optimization.
The problem is, you used SR a sign-preserving shift. What you
should have written, and in fact, the recommended way to clean off bits from
the top of a word, is something like this:
Afterword:
What was that in C about
*(int *)&x?
C has a design flaw. If I write (int)x where x is a
floating point number, it means, do the computations needed to truncate
x to the nearest integer and give me an integer result. That is, it
forces a change of representation as well as a change of type.
In contrast, if x is a non numeric type, and I write (int)x,
it means: Take the bit pattern used to represent x and with no change
in represention, pretend it's an integer.
What we wanted in our code above was to use an integer operation (logical and)
on the representation of a floating point value. To do that in C, we first
need to convince the compiler to change the type label on the value without
changing its representation. So, we do something awkward:
&x — make a pointer to the variable x.
(int *)&x — pretend it's a pointer to an integer.
*(int *)&x — get the value of that integer.
Ugly, but if the compiler does any optimization, no pointer will ever be
generated or followed and instead it will simply use the floating point
value as if it was an integer.
I resorted (on paper) to converting the mantissa into a integer
(multiplying by 2^23). I'm not sure if this software can handle that sort of
number though. I then have written down to add 1*2^23 if the value should be
normalized. Then I multiply by 2^exp. Next I would divide back by 2^23.
I have the sense that this number would be too big for the hawk to handle.
First, you are right. All of the Hawk monitor arithmetic support is limited
to 32 bits, as are the Hawk's shift instructions. It is entirely possible to
write higher precision integer multiply and divide routines, or to write higher
precision shift instructions, but there's nothing built in.
But note: You said multiply or divide by powers of two. You never need to
use any multiplication or division to solve this problem. It's all done by
shifting.
Section 10.3 and Section 11.2 of the Hawk manual discuss instructions that
can be used to make 64-bit precision left and right shifts, but you don't need
those tricks for this problem because it can all be done in 32 bits. If you
do some algebra on all the multiplies and divides you discussed above, you
will discover that your large constand multiplier and divisors cancel out!
I noticed in mp6 that you are expecting the students to check for
infinity and NaN. What should the subroutine return in these cases?
The assignment says:
"If the floating point value is larger than can be represented in 32 bits, the
value returned should be 8000000016."
Infinite values cannot be represented in 32 bits, so this obviously applies
to them. NaN values seem to fit naturally in the same class.
Several students noted that you can't write SL R3,R4 to shift
R3 left a number of places specified by R4. What you can do
is either:
a) Write a for loop repeatedly doing SL R3,1 the right number
of times or
b) Look at the final question on Midterm II and use that algorithm to
efficiently solve the problem.
Debugging the shift counts in this code is messy, and I found it much easier
to dig around in the logic working in C instead of assembly language. The
challenge is that C doesn't pin down the size of types int and
long int. As an afterthought, the header file stdint.h was
introduced to define well defined fixed size integer types.
Design by afterthoughts always seems to produce ugly
results, and that is certainly the case here. stdint.h includes two
relevant types, int32_t for exactly 32-bit signed integers, and
uint32_t for exactly 32-bit unsigned integers. Both of these are
relevant here.
Actually, the precision of type float is equally undefined, but it
is almost always IEEE 32-bit float, and I've tested that. It is true on our
machine.
To help you experiment with these, here is the skeleton of my C solution to
this problem:
Of course, after debugging the logic, I translated it to assembly language,
where objects of type float, uint32_t and int32_t
are all just values of words or registers.
At first, I was going to use the AND instruction, but I ended up using shift
operations. Am I correct in thinking that it does the exact same thing?
You can frequently use shifting instead of anding with a mask. That was the
motivation behind some of last week's quiz/homework questions. So suppose
you have a variable a that is a 32-bit unsigned integer
(type uint32_t in C). You could write the expression:
or you could get the same result using
Programmers frequently refer to the first approach, with and, as masking off
or masking out
the unwanted bits, while they refer to the second approach as shifting off
or shifting out
the unwanted bits. It is quite common to want to do some shifting anyway, as
for example:
When this is the case, it is frequently faster to write:
Good compilers tend to have lots of special cases in them to recognize cases
where masking can be converted to shifting, so high level language programmers
generally just write code with masking, and let the compiler substitute
shifting out the unwanted bits. Assembly language programmers frequently
go directly to shifting off unneeded bits.
I have included a very simple main program loading R3 with the
floating point value of 48 so you can easily run my program to see how it
is functioning. When I comment out the main program, leaving only
INT FTOINT, the activation record for FTOINT and
everything below the FTOINT: line, I get this error ...
I quote the assignment:
Your submission must not include a main program.
The symbol FTOINT
must be declared in your code as having an internal definition (that is, your
code must contain a line saying INT FTOINT.
That means that your file must not contain a line saying
INT MAIN nor a line saing S MAIN.
While you may use a test program in the same source file during development,
we will link our test program with the object file produced by assembling your
code. Our test script works like this, where mp5.a is your code
and mp5test.o is our main program to test your code:
You should really test your code by assembling it and linking it to your own
test program.
int main() {
int i;
for (i = 1000000000; i > 1; i = i/10) {
if (i != ftoint( (float)i )) {
printf( "fails for i = %d\n", i );
}
if (-i != ftoint( (float)-i )) {
printf( "fails for i = -%d\n", i );
}
}
}
[HawkID@fastx?? ~]$ ~dwjones/format mp5.a
[HawkID@fastx?? ~]$ ~dwjones/submit 0A0X mp6.a
Discussion
Nov. 4, 6:29 PM
USE "hawk.h"
LIS R8,-1 ; a value to truncate
TRUNC R8,4 ; truncate it to see the effect
abs = (*(int *)&x) & 0x80000000;
LIW R3,#80000000
AND R3,R4
SL R3,1
SR R3,1 ;<---- small important mistake
SL R3,1
SRU R3,1 ;<---- an unsigned shift
Nov. 4, 9:58 PM
Nov. 7, 9:27 AM
Nov. 10, 8:27 AM
Nov. 10, 6:45 PM
int32_t ftoint( float f ) {
uint32_t fasint = *((uint32_t*)&f);
int32_t retval; /* the return value */
/* pick apart the number */
uint32_t s = fasint & (uint32_t)0x80000000; /* 1 bit sign */
uint32_t exp = fasint & (uint32_t)0x7F800000; /* 8 bit exponent */
uint32_t mant = fasint & (uint32_t)0x007FFFFF; /* 23 bit mantissa */
/* your solution here */
return retval;
}
Nov. 12, 3:17 PM
a & 0x00FFFF00
((a << 8) >> 16) << 8
(a & 0x00FFFF00) >> 8
(a << 8) >> 16
Nov. 13, 9:30 AM
smal mp6.a
hawklink mp6.o mp6test.o
hawk link.o