mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
Implement BITWIDTH and TZCOUNT functions (#1450)
This commit is contained in:
@@ -55,6 +55,8 @@ enum RPNCommand {
|
||||
|
||||
RPN_HIGH = 0x70,
|
||||
RPN_LOW = 0x71,
|
||||
RPN_BITWIDTH = 0x72,
|
||||
RPN_TZCOUNT = 0x73,
|
||||
|
||||
RPN_CONST = 0x80,
|
||||
RPN_SYM = 0x81
|
||||
|
||||
67
man/rgbasm.5
67
man/rgbasm.5
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) },
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
11
test/asm/bit-functions.asm
Normal file
11
test/asm/bit-functions.asm
Normal 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
|
||||
@@ -27,3 +27,6 @@ db @ <= 7
|
||||
dw @ << 1
|
||||
dw @ >> 1
|
||||
dw @ >>> 1
|
||||
|
||||
db BITWIDTH(@)
|
||||
db TZCOUNT(@)
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user