Implement BITWIDTH and TZCOUNT functions (#1450)

This commit is contained in:
Sylvie
2024-08-07 10:39:30 -04:00
committed by GitHub
parent 7435630d6a
commit e93190d491
10 changed files with 105 additions and 18 deletions

View File

@@ -55,6 +55,8 @@ enum RPNCommand {
RPN_HIGH = 0x70,
RPN_LOW = 0x71,
RPN_BITWIDTH = 0x72,
RPN_TZCOUNT = 0x73,
RPN_CONST = 0x80,
RPN_SYM = 0x81

View File

@@ -9,9 +9,9 @@
.Nm rgbasm
.Nd language documentation
.Sh DESCRIPTION
This is the full description of the language used by
This is the full description of the assembly language used by
.Xr rgbasm 1 .
The description of the instructions supported by the Game Boy CPU is in
For the full description of instructions in the machine language supported by the Game Boy CPU, see
.Xr gbz80 7 .
.Pp
It is advisable to have some familiarity with the Game Boy hardware before reading this document.
@@ -47,6 +47,23 @@ Instructions are assembled into Game Boy opcodes.
Multiple instructions on one line can be separated by double colons
.Ql :: .
.Pp
The available instructions are documented in
.Xr gbz80 7 .
Note that where an instruction requires an 8-bit register
.Ar r8 ,
.Nm
can interpret
.Ic HIGH Ns Pq Ar r16
as the top 8-bit register of the given
.Ar r16 ,
and
.Ic LOW Ns Pq Ar r16
as the bottom one (except for
.Ic LOW Ns Pq Ic AF ,
since
.Ic F
is not a valid register).
.Pp
All reserved keywords (directives, register names, etc.) are case-insensitive;
all identifiers (labels and other symbol names) are case-sensitive.
.Pp
@@ -324,9 +341,33 @@ with a zero constant as either operand will be constant 0, and
.Sq ||
with a non-zero constant as either operand will be constant 1, even if the other operand is non-constant.
.Pp
.Ic \&!
.Sq \&!
returns 1 if the operand was 0, and 0 otherwise.
Even a non-constant operand with any non-zero bits will return 0.
.Ss Integer functions
Besides operators, there are also some functions which have more specialized uses.
.Bl -column "BITWIDTH(n)"
.It Sy Name Ta Sy Operation
.It Fn HIGH n Ta Equivalent to Ql Ar n No & $FF .
.It Fn LOW n Ta Equivalent to Ql Po Ns Ar n No & $FF00 Pc >> 8 .
.EQ
delim $$
.EN
.It Fn BITWIDTH n Ta Returns the number of bits necessary to represent
.Ar n .
Some useful formulas:
.Ic BITWIDTH Ns ( Ar n Ns )\ \-\ 1
equals $\[lf] log sub 2 ( n ) \[rf]$,
.Ic BITWIDTH Ns Pq Ar n Ns \ \-\ 1
equals $\[lc] log sub 2 ( n ) \[rc]$, and
.No 32\ \-\ Ns Ic BITWIDTH Ns Pq Ar n
equals $roman clz ( n )$.
.It Fn TZCOUNT n Ta Returns $roman ctz ( n )$, the count of trailing zero bits at the end of the binary representation of
.Ar n .
.El
.EQ
delim off
.EN
.Ss Fixed-point expressions
Fixed-point numbers are basically normal (32-bit) integers, which count fractions instead of whole numbers.
They offer better precision than integers but limit the range of values.
@@ -345,8 +386,11 @@ Some integer operators like
and
.Sq -
don't care whether the operands are integers or fixed-point.
You can easily truncate a fixed-point number into an integer by shifting it right by 16 bits.
It follows that you can convert an integer to a fixed-point number by shifting it left.
You can easily truncate a fixed-point number into an integer by shifting it right by the number of fractional bits.
It follows that you can convert an integer to a fixed-point number by shifting it left that same amount.
.Pp
Note that the current number of fractional bits can be computed as
.Ic TZCOUNT Ns Pq 1.0 .
.Pp
The following functions are designed to operate with fixed-point numbers:
.EQ
@@ -513,8 +557,8 @@ There is also a character map stack that can be used to save and restore which c
.Sy Note :
Modifications to a character map take effect immediately from that point onward.
.Ss Other functions
There are a few other functions that do various useful things:
.Bl -column "DEF(symbol)"
There are a few other functions that do things beyond numeric or string operations:
.Bl -column "SECTION(symbol)"
.It Sy Name Ta Sy Operation
.It Fn BANK arg Ta Returns a bank number.
If
@@ -552,15 +596,10 @@ If
.Ar arg
is a section type keyword, it returns the starting address of that section type.
The result is not constant, since only RGBLINK can compute its value.
.It Fn DEF symbol Ta Returns TRUE (1) if
.It Fn DEF symbol Ta Returns 1 if
.Ar symbol
has been defined, FALSE (0) otherwise.
has been defined, 0 otherwise.
String constants are not expanded within the parentheses.
.It Fn HIGH arg Ta Returns the top 8 bits of the operand if Ar arg No is a label or constant, or the top 8-bit register if it is a 16-bit register .
.It Fn LOW arg Ta Returns the bottom 8 bits of the operand if Ar arg No is a label or constant, or the bottom 8-bit register if it is a 16-bit register Pq Cm AF No isn't a valid register for this function .
The result may be constant if
.Nm
is able to compute it.
.It Fn ISCONST arg Ta Returns 1 if Ar arg Ap s value is known by RGBASM (e.g. if it can be an argument to
.Ic IF ) ,
or 0 if only RGBLINK can compute its value.

View File

@@ -391,10 +391,14 @@ The value is then ORed with $C7
.It Li $80 Ta Integer literal; followed by the
.Cm LONG
integer.
.It Li $70 Ta Ql HIGH
.It Li $70 Ta Cm HIGH
byte.
.It Li $71 Ta Ql LOW
.It Li $71 Ta Cm LOW
byte.
.It Li $72 Ta Cm BITWIDTH
value.
.It Li $73 Ta Cm TZCOUNT
value.
.It Li $81 Ta A symbol's value; followed by the symbol's
.Cm LONG
ID.

View File

@@ -237,6 +237,9 @@ static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> ke
{"LOW", T_(OP_LOW) },
{"ISCONST", T_(OP_ISCONST) },
{"BITWIDTH", T_(OP_BITWIDTH) },
{"TZCOUNT", T_(OP_TZCOUNT) },
{"STRCMP", T_(OP_STRCMP) },
{"STRIN", T_(OP_STRIN) },
{"STRRIN", T_(OP_STRRIN) },

View File

@@ -174,6 +174,8 @@
%type <int32_t> opt_q_arg
%token OP_HIGH "HIGH" OP_LOW "LOW"
%token OP_BITWIDTH "BITWIDTH"
%token OP_TZCOUNT "TZCOUNT"
%token OP_ISCONST "ISCONST"
%token OP_STRCMP "STRCMP"
@@ -1349,6 +1351,12 @@ relocexpr_no_str:
| OP_LOW LPAREN relocexpr RPAREN {
$$.makeUnaryOp(RPN_LOW, std::move($3));
}
| OP_BITWIDTH LPAREN relocexpr RPAREN {
$$.makeUnaryOp(RPN_BITWIDTH, std::move($3));
}
| OP_TZCOUNT LPAREN relocexpr RPAREN {
$$.makeUnaryOp(RPN_TZCOUNT, std::move($3));
}
| OP_ISCONST LPAREN relocexpr RPAREN {
$$.makeNumber($3.isKnown());
}

View File

@@ -9,7 +9,7 @@
#include <string.h>
#include <string_view>
#include "helpers.hpp" // assume
#include "helpers.hpp" // assume, clz, ctz
#include "opmath.hpp"
#include "asm/output.hpp"
@@ -319,6 +319,12 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
case RPN_LOW:
data = val & 0xFF;
break;
case RPN_BITWIDTH:
data = val != 0 ? 32 - clz((uint32_t)val) : 0;
break;
case RPN_TZCOUNT:
data = val != 0 ? ctz((uint32_t)val) : 32;
break;
case RPN_LOGOR:
case RPN_LOGAND:
@@ -498,6 +504,8 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
case RPN_RST:
case RPN_HIGH:
case RPN_LOW:
case RPN_BITWIDTH:
case RPN_TZCOUNT:
case RPN_CONST:
case RPN_SYM:
fatalerror("%d is not a binary operator\n", op);

View File

@@ -8,7 +8,7 @@
#include <variant>
#include <vector>
#include "helpers.hpp" // assume
#include "helpers.hpp" // assume, clz, ctz
#include "linkdefs.hpp"
#include "opmath.hpp"
@@ -146,6 +146,15 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
value = popRPN(patch) & 0xFF;
break;
case RPN_BITWIDTH:
value = popRPN(patch);
value = value != 0 ? 32 - clz((uint32_t)value) : 0;
break;
case RPN_TZCOUNT:
value = popRPN(patch);
value = value != 0 ? ctz((uint32_t)value) : 32;
break;
case RPN_OR:
value = popRPN(patch) | popRPN(patch);
break;

View File

@@ -0,0 +1,11 @@
assert BITWIDTH(0) == 0
assert BITWIDTH(42) == 6
assert BITWIDTH(-1) == 32
assert BITWIDTH($80000000) == 32
assert TZCOUNT(0) == 32
assert TZCOUNT(42) == 1
assert TZCOUNT(-1) == 0
assert TZCOUNT($80000000) == 31
assert TZCOUNT(1.0) == 16

View File

@@ -27,3 +27,6 @@ db @ <= 7
dw @ << 1
dw @ >> 1
dw @ >>> 1
db BITWIDTH(@)
db TZCOUNT(@)

Binary file not shown.