INTRODUCTION:


...to two assembler-programs, written in NASM-dialect and used to translate NASM-dialect or a new dialect, which I describe at the end of this text.
The program, which translates NASM-dialect is called "ASMn" in the following text and is limited in some respect, as it needs to work only under ASMOS, assembling code only for programs running under ASMOS. It is made to assemble sourcecode with very few modifications, which is written in NASM-dialect.
If the source-compatibility is not needed, use the derivate ASMat, described at the end of this document. There are a lot of expressions, which will be translated by NASM, but not by ASMn. And there are a few things, which can be done in ASMn, but not in NASM...
A detailed synopsis you can find in the file "TSOURCE", which can be used as test source for assembling too.

ASMn:

...is a single standalone-program, assembling in a single run. The output of this program is a listing and a binary, which can be made running using the normal way in the menu-mode of ASMOS. The source can be made in edit-mode 0 or 1. The error-output is part of the listing. The line will be black-painted starting exactly at that expression or a part of an expression, which wasn't recognized by ASMn. Normally listing goes on after an error. The only exceptions are a double defined label, a negative adress for filling ("TIMES") or a too long adress in "loop"-commands.
The preprocessor stage is as simple as can be - it does not exist. As in ASMOS cut&paste is very easily done, while the copy can contain every value in a byte, including of files, data or macros in a preprocessor stage isn't needed.
As You do not need real mode code for any other purpose than to write a bootloader (which is done already...), ASMn does nothing depending on real mode or V86 (implicite "USE 32").
As the destination of code is only ASMOS, there are no command-line-options or linker-headers available (or needed). After Initialization ASMn is a point in the menu F9.
The ASMn-assembler understands very few expressions. These few are limited to only a few purposes in definitions (You can't do any arithmetics except simple adress-arithmetic!):
: + * -
A Label can be written with postfix ":" or without it as in NASM. And a command can follow in the same line.
ASMn starts assembling in a line searching for ";" first and skipping whitespace (20h and 00h). Besides these special signs every other sign is treated to be a mnemomic or the expression "ALIGN 4" (a "standard makro" in NASM). Similar to NASM and other Assemblers, ASMn finally searches for labels.
If "ALIGN 4" or "align 4 " (or any mix of upper and lower cases of the signs...) is found, ASMn fills with NOPs (=90h).
Behind a label defining a value you can use only:
DT, DQ, DD, DW, DB (in lower or upper case to define the type)
or one of the two quasi-ops "*" (similar "TIMES") or "I" (similar "INCBIN").
Any other letters will be treated as a command!
If the type is DB or db ,then instead of numbers 'text...' or "text..." can follow. And similar to NASM-style a name of a label representing an adress can follow, if the type is DD or dd.
Onlc then, when the type is DD, DQ or DT, there is a minus "-" allowed as präfix of the digits. But while the type DD can be used to define integer constants too, the types DQ and DT are oly useable to define floating point format in the familiar way. Look for more details about FPU and floating point format at the end of this text!
As "TIMES" and "INCBIN" are "types" in ASMn, there is no need for the expression "$" or "$$". If the type of an expression behind a label is "*", AMSn does filling similar to NASM. In this case there has to follow a count for repetition. This can be a single number like this:

Fillwith: * 200h D 20202020h ;...fills with 400h ASCII-spaces starting at "Fillwith" (which is a labels name here!) Only D W B allowed inside this expression!
Else you can code a simple numeric expression using names of adresses too. There is at most a single "-" between two spaces allowed to define a difference of two adresses looking like this:
Fill_till_adress: * offsetadress - 0 ..."offsetadress" has to be a number! The minus sign "-" is aequivalent to "$" as used in NASM. The expressions means:
offsetadress - (adress of "Fill_till_adress")
The result is a count of fill-bytes, defined in this example by the byte value "0". The insertion starts at the adress of "Fill_till_adress" and ends below the "offsetadress". Of course a negative result won't do, what you expected! Assembling is terminated in this case.
You must not use "*" in any other way than to fill! I.e."TIMES 100 movsb"...is impossible. In effect you can do everything, what you can do in NASM, but brackets or "$" are superfluous.
If the "type" is "I" or "i", this is similar to "INCBIN" in effect. In ASMOS you can easily cut&paste pure data. Thus you only have to define the length of the inserted data using a bytecount like that:
Inserted_bytes: I 100h x....
This expression says, that 100h bytes starting with "x" are treated to be defined values.This method allows to easily insert not only raw code, but text or tables too.
Different to NASM, segment-override-praefixes are not allowed before a command, but are to define in operands. Inside a command you can use only:
DWORD WORD BYTE SHORT NEAR FAR
And these expressions are only allowed behind the mnemonic, set between two whitespaces!
Jumps inside a segment ( not FAR ) will be translated to NEAR jumps, if the labels adress is unknown at that stage, because it is higher than that of the actual translated command. Else ASMn automatically tries to make SHORT jumps. An explicitely defined SHORT is ignored.
This makes a lot of silly error-messages superflous, which NASM outputs. The occassionally superfluous three bytes in the binary are not relevant because of opcode-prefetch...
Inside the brackets [...] of an effective adress-operand there aren't allowed:
BYTE, WORD, DWORD, NOSPLIT
And You must not use any whitespace inside the brackets!
Inside the brackets You have to express Your wishes as clear, as the CPU needs it. The CPU assembles every effective adress for indexed adressing out of four parts:
base, index, scale-factor, displacement
Thus every adress-operand for indexed adressing has to look like that:
[segmentoverride:base+index*scalefactor+displacement]
segmentoverride expressed by: ds,es,fs,gs,ss
base and index expressed by: eax,ebx,ecx,edx,esi,edi,esp,ebp
scalefactor expressed only by: 2,4,8
displacement expressed by: one name or one number and occassionally negative
You can't change the order, but You can omit every part.
There will be only one case, which might be confusing: a single Registername could adress the base- or the index-adress. But this doesn't matter! You have to mention, that every effective adress for indexed adressing is computed in the same way by the CPU (and in the same time!). The difference results in some more bytes of opcode (the displacement). This is the reason why in ASMn the first registername inside the brackets is treated as the container of the base-adress. Thus a single registername will be translated as an adress made of the baseadress only. Then the opcode is the short one without displacement-bytes. If You make use of the registers esp and ebp in indexed adressing, You need to know, that the CPU does computing in this case in a special manner.
The efforts to change already written NASM-code can be very low - i.e.my code. But you may have to change religion, if you like makros and other things, which are not recognized by ASMn. This means, that You may have to erase every expression from Your source, which isn't recognized by ASMn - and you have to re-code "TIMES"- and "INCBIN"-expressions!
If there is any confusion about expressions, look at the file "TSOURCE", where a synopsis of the commands in NASM, ASMn and ASMat is written, ready to assemble.

ASMnr:


Different to NASM, ASMn does not translate code, mixed for real and protected mode. But in a few cases (bootloadercode) mixed code is needed.
As an assembler translation program for such mixed codes would be too complicated, I made two programs, each running faster and using less memory space for the task. Mixing code is easily done under ASMOS in edit-mode 0, because binary copies can be made and pasted. Mixing needs not to be done by the translation program!
Look for the details at the original file in the sources!

ASMat:

This derivate is to prefer, if you do not need compatibility with NASM and write and use the source in ASMOS. ASMat is made to ease writing of programs and to speed up assembling.
In ASMat there is a very simple rule used to define the three dimensions of a sourcecode: comments, commands and labels. The rule is:

This is a comment, because the first sign in the line is neither a white-space nor a colon ":" :"This" is a label named "This", while the rest of the line is a comment. mov eax,ebx is a command, because there is at least one whitespace at the start of the line. As you can see, there is no ";" needed! ASMat searches for whitespaces and colons, and if there is no hit, the signs in the rest of the line are taken as comments. Empty lines (=only containing whitespace) are skipped and not listed.
If the first sign in the line is a colon ":", every sign behind is part of a labels name till the first whitespace. You can't get trouble, if you use mnemonics as names or strange signs outside ASCII-space. Only the few signs, which are mentioned here for special purpose shall not be used as the first signs of a name.
If the label names the adress of an opcode, there must follow a second whitespace! Else the signs
h t q d w b i * : are searched and an error is marked, if there is no hit! Everything behind these signs has to be expressed as described above for ASMn, but only the lower case of signs is allowed.
You have to use d instead of DD or DWORD, w instead of DW or WORD and b instead of DB or BYTE.
The signs "*" and "i" are used as described above for ASMn and you must use a colon ":" ,if you define an adress by name. String-constants between " or ' are an error. Use the i-type instead! As in ASMOS every byte value can be defined with a key-stroke, string constants are much easier defined like this:
:String i 50h this is the defined string with the length of 50h bytes Some more examples for frequently used definition:
:DWORD d FFh is a dword constant where you do not need a praefix 0, if a hexnumber is the value :word w 10 is decimal coded :Byte b 101b is binary code :adressconstant :label This is an adressconstant, which will be translated to a 32-bit-adress of the label named "label" As you can see, there is no whitespace allowed behind ":" , but needed behind the other signs h t q d w b * - This decision makes it easier to copy&paste labels into constant expressions...
The types used to define floating point format are d, q and t .
For more precision, if a value between 1 and 0 (using negative exponent) is to define, in ASMat you can use a type "h" with an exponent defining a power of 16 instead of 10 .
If the first sign in a line is a whitespace, following whitespaces will be skipped and a mnemonic is exspected. ASMat does not recognize mnemonics written in upper case! But in hexnumbers upper case is allowed (because I like it so much).
As the string "mov" is the most hit string, ASMat does not only select this command first as ASMn, but allows a short form too: The sign at the right hand side of the left shift-key can be used instead, which is not only easily hit, but shows the direction of moves too. But if nobody likes this besides me, you can use "mov" as well.
Inside a command expressions are simplified too.
In ASMat every opening square bracket "[" is replaced by a point "." Closing square brackets "]" are forbidden in every case (and superfluous). But the easier hacked point is needed to define a memory reference as the destination. Some examples:
mov d.es:eax+ebx*2-10h,10h is the same as: mov DWORD [es:eax+ebx*2-10h],10h in NASM-dialect mov .label,eax is the same as: mov [label],eax in NASM-dialect jmp .label is the same as: jmp [label] in NASM-dialect mov .es:eax+ebx*2-10h,al jmp .10h is the same as: jmp [10h] Inside jump- or call-commands only the sign f is to use as FAR in ASMn, but no other expression (i.e. SHORT !) is allowed. Some examples:
jmp f.es:eax+ebx*2-10h is the same as: jmp FAR [es:eax+ebx*2-10h] in NASM-dialect jmp f.Label is the same as: jmp FAR [Label] in NASM-dialect jmp .Label is the same as: jmp [Label] in NASM-dialect jmp Label is the same as: jmp Label in NASM-dialect If the edit-mode 0 in ASMOS is used to write the source, you can easily make comments out of commands, writing any sign in the first column after switching the cursor to up- or down-proceeding. And you can easily re-use a listing after deleting the columns containing the listed adresses and opcodes or values. Do deleting of columns with a STRGr-shifted TAB-key.

FPU:


To program the FPU and use the floating point format is special and not to do knowing only about the mnemonics. Because of this, I describe here the most important details.
Since the Intel-CPU type i486, the FPU is integrated on the same chip as the CPU. It can be programmed using opcode, but contains its own registers and can calculate parallel to the CPU. For the purpose of synchronisation and error-handling, the FPU is capable of requesting interrupts. As the FPU once was made as a separate circuit in an own case, there is an own reset and initialisation to do - different to the ALU.
The original FPU contains eight 80-bit-data-registers, organized as stack and internally adressed by a FPU-stackpointer, which is readable in bits 11,12,13 of the FPU-status-register and "grows" downward, pointing to the lowest defined element (=TOP) and stepping with predecrement.
In newer FPUs, there are new data-registers and commands available, but these ones are not recognized by may translation programs.
Besides the stacked data-registers, a control- and a status-register are part of the FPU, which can be accessed similar to those in the CPU by using special commands.
A very important part of the construction is a format of the operands, which allows representation of point, mantisse (sometimes called "significant") and exponent. This format is normalized as "IEEE 754". While significant and exponent can be handled in three different sizes, the point can "flow".
This means, that there is no binary representation of the point similar to the sign-bit. Instead of wasting a bit, the point is defined by the exponent, which is a special format too to make a signbit for the exponent superfluous. Instead of wasting a bit a "bias" is added to the real value of the exponent. Up to the value of the bias, exponents are negative.
Initialising and control of the FPU is done using three registers:
the CPU-control-register cr0 controls the FPU too and contains most important settings
the control-register of the FPU can only be accessed using the commands ;fldcw;, ;fstcw; and ;finit;
the status-register of the FPU can only be accessed using the commands ;fstsw; and ;fnstsw;
Of course the status-register is needed at runtime, which contains in bits 0-5 the FPU-flags linked to interrupt requests, if not masked. These bits show "exceptions". The interrupt-handler has to delete the according flag in the FPU, because this is not done automatically! In the bits 8,10,14 the flags are set, which are comparable to those in the status-register of the CPU .
Finally the bits 11,12,13 show the actual adress of the Stackpointer in the FPU.
Operands can be transfered from and to memory, but nor registers in the CPU. The right place is the FPU-stack.
While the adress of the stackpointer is an absolute registeradress, the adress allowed in commands is relative to TOP. Then the name of the registers is in the most assembler dialects "st" and a postfix 0...7. As this postfix adresses relative to the stackpointer, the actually adressed register has always the name "st0" and is =TOP.
As you can increment or decrement the stackpointer, you can move the relation and make the same register to be "st1" or "st7" and move TOP. Thus the baseadress =TOP of the stack can rotate through the physical registers.
The FPU uses only operands in the floating point format for extended precision. Every other format for single or double precision is internally changed!
In every case there is a limited precision of significants and a limited range of numbers because of a limited exponent. The structure of the three types of "precision" available in the FPU seems to be simple, but the change of decimal digits to dual digits isn't. The new algorithm used in ASMn and ASMat produces more precision than the known ones in "higher" languages. I use for some steps 128 bit precision. This causes a difference in the least bits between my translations and those of other programs.

THE FLOATING POINT:


If You are familiar with the use of decimal exponents, You saw already points float, but certainly without the flowerish expression for that phenomena...
You know the decimal exponents as 10=10exp2, 100=10exp2 a.s.o... and used this as a multiplier for decimal digits. At the left hand side of "exp" I wrote the significant, at the right hand side the exponent.
Then you can write the following equation: 123*10exp1=12.3*10exp2=1.23*10exp3=0.123*10exp4.....and see the point float from the right hand side to the left hand side.
If the numbers are represented as dual digits, then the point flows in the same way - more useable in a transistor circuit between transistors, which output 0V representing dual 0 or 5V representing dual 1. But now the floating point does not make a multiplication or division by factor 10, as in the decimal system, but only by factor 2 . But in both systems this is incrementing oder decrementing the exponent.
Now You can see, that the choice of an exponent is the choice, where to put the point in the significant, if the amount of bits for the significant is limited as in the FPU. But You need not worry about the right definition of the exponent during calculation, because the FPU can make the point floating! How to do this, is normalized in "IEEE 754".
By this rule every number represented by dual digits, is treated to be a dual fraction and a leading 1 before the point. Thus a decimal 1234 is a 1.234*10exp3
As the binary representation of the number has to use the power of 2 instead of 10, the multiplicator in this example has to be changed: (1,234*5exp3)*2exp3
This change allows to keep the exponent unchanged, but the mantissa has to be multiplied by a power of 5
While the exponent has to be changed to dual digit format too, the multiplicator "2exp" is part of the construction of the FPU.
As every decimal definition of constants contains a power of 5, which is a divisor, if the exponent is negative, most of the changed dual digits should be made of more bits than available to be exact. This is the reason, why you normally can't exactly define the value, which is finally used in the FPU !
Because of this I added the feature to ASMat to define floating point values using hexadecimal digits.
As the MSB of the significant is exactly defined by the exponent, there is a 1, which needs not to fill a bit. Thus the significant can be doubled by making this 1 implicite. This is, what is called "normalized". The normalized floating point format is not internally used and not externally to define, if extended precision is switched on!
If single or double precision is on, the FPU outputs results "normalized" too! Only then, when a very small number with exponent=0 is to define, the FPU makes "denormalized" results and requests for interrupt 16 (if not masked).
The problem with a sign for the exponent is solved in another way. This sign says, if it is a minus, that a significant isn't multiplied by an implicite 2 (the base of the dual system) with given exponent, but divided by this term. There is no sign-bit wasted too, but a "bias" is added to the original exponent of the operand. The bias is different due to precision. The decimal values are: single precision bias=127, double precision bias=1023, extended precision bias=16383.
There are some more values available in the binary exponent for alien results as null, infinite and others...
Besides those alien values there are cut off bits and rounding, which make results not really exact. And as the amount of bits is limited, "infinite" values can be in fact exact values, which are too big or too small, but not really infinite. The FPU allows corrections of calculations requesting an interrupt 16 in those cases.

If there is any confusion about expressions, look at the file "TSOURCE", where a synopsis of the commands is written with comments.
p.p.
This version of the file is shortened - read the file again after unzipping the source...


  • Go here back to the mainpage: www.rcfriz.de