If you're getting the expected output, that's "good progress", I would say!
I don't know much about the differences between openBSD and freeBSD. I suspect "no difference at all" with the fairly simple code you're doing, but I'm not certain. The freeBSD "handbook" has a really good reputation. I'd go by that, and worry about the differences if and when you encounter any.
I've modified your code slightly so it runs in Linux (difference is small, but important!) I get:
8X
cX
Okay if that's what you want, I guess, but I don't see the "point"... You've enlarged your variables to "dw", so they match ax (two bytes) which is good. I actually meant to suggest that you use al instead of ax - guess I didn't make that very clear.
The "default" register sizes in 32-bit code are 8-bit and 32-bit. To use ax, or any 16-bit register, there needs to be an "operand size override prefix" - the byte 66h. Nasm will provide this, so you don't need to worry about it - don't even need to know it's there. But it is, and adds some "bloat" to your code, for very little advantage. This is because Intel employed a "clever trick" (or a horrid kludge, depending on how you look at it) when they went from 16-bit to 32-bit. The opcodes are exactly the same! If the CPU is in 16-bit mode, it'll use ax, in 32-bit mode (Windows, Linux, or *BSD) it'll use eax - unless that "prefix" is present. (the same prefix will allow you to use eax in 16-bit code, if you care) Sometimes you "need" (or want) to use 16-bit registers in 32-bit code. If you don't, better to stick to al or eax.
You've also expanded your buffer, "digit", to "dw". The size of the buffer, "dbytes" is now 8. You're printing 8 characters, but most of 'em are zeros. Printing a zero - the number 0, not the character '0' - doesn't do anything, so it works "as expected"... but isn't really what you want to do (I don't think).
To start with something much simpler...
mov al, 2
add al, 2
Now al = 4, but putting it in a buffer and printing it won't do much. You seem to understand that a "number" and a "character representing that number" aren't the same - or maybe you were just lucky when you used 38h for '8'.
%include 'system.inc'
section .data
digit db 0, 0Ah
dbytes equ $ - digit
section .text
global _start
_start:
mov al, 2
add al, 2
add al '0' ; or 30h or 48 decimal - all the same
mov [digit], al
push dbytes
push digit
push stdout
sys.write
; add esp, 12
push 0
sys.exit
That's untested, but if I haven't screwed it up, it should work. Now, step it up a notch:
mov al, 21
add al, 21
Now al = 42 ("the answer"), but adding '0' to it isn't going to help! We need to print the character '4', and then the character '2'. Dividing by ten would give a quotient of 4 and a remainder of 2. We could use that - both would need to have '0' added to "convert" them to charcters. Ummm....
%include 'system.inc'
section .data
digit db 0, 0, 0Ah ; note increased size!
dbytes equ $ - digit
section .text
global _start
_start:
mov al, 21
add al, 21
mov bl, 10
mov ah, 0 ; make sure it's zero! "div" uses it!
div bl ; quotient in al = 4, remainder in ah = 2
add al '0'
mov [digit], al
add ah, '0'
mov [digit + 1], ah
push dbytes
push digit
push stdout
sys.write
; add esp, 12
push 0
sys.exit
More likely that I've screwed that one up, but it might still work.
Still not very useful - it'll only do two digits. For more "general" use, you probably want to use 32-bit registers, and extract the digits in a loop. I have an "example" I use, but it is rather specific to Linux - "expects" the length in edx and the buffer address in ecx, so it isn't too easily adapted to BSD. I'll see if I can come up with something, but not right now.
Fortunately, the "add" instruction is pretty easy, the "print" part is the hard part.
Best,
Frank