makedivide 1.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,72 @@
1
+ # This workflow will upload a Python Package to PyPI when a release is created
2
+ # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
3
+
4
+ # This workflow uses actions that are not certified by GitHub.
5
+ # They are provided by a third-party and are governed by
6
+ # separate terms of service, privacy policy, and support
7
+ # documentation.
8
+
9
+ name: Upload Python Package
10
+
11
+ on:
12
+ workflow_dispatch:
13
+ types: [published]
14
+ release:
15
+ types: [published]
16
+
17
+ permissions:
18
+ contents: read
19
+
20
+ jobs:
21
+ release-build:
22
+ runs-on: ubuntu-latest
23
+
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+
27
+ - uses: actions/setup-python@v5
28
+ with:
29
+ python-version: "3.x"
30
+
31
+ - name: Build release distributions
32
+ run: |
33
+ # NOTE: put your own distribution build steps here.
34
+ python -m pip install build
35
+ python -m build
36
+
37
+ - name: Upload distributions
38
+ uses: actions/upload-artifact@v4
39
+ with:
40
+ name: release-dists
41
+ path: dist/
42
+
43
+ pypi-publish:
44
+ runs-on: ubuntu-latest
45
+ needs:
46
+ - release-build
47
+ permissions:
48
+ # IMPORTANT: this permission is mandatory for trusted publishing
49
+ id-token: write
50
+
51
+ # Dedicated environments with protections for publishing are strongly recommended.
52
+ # For more information, see: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules
53
+ environment:
54
+ name: pypi
55
+ # OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status:
56
+ # url: https://pypi.org/p/YOURPROJECT
57
+ #
58
+ # ALTERNATIVE: if your GitHub Release name is the PyPI project version string
59
+ # ALTERNATIVE: exactly, uncomment the following line instead:
60
+ # url: https://pypi.org/project/YOURPROJECT/${{ github.event.release.name }}
61
+
62
+ steps:
63
+ - name: Retrieve release distributions
64
+ uses: actions/download-artifact@v4
65
+ with:
66
+ name: release-dists
67
+ path: dist/
68
+
69
+ - name: Publish release distributions to PyPI
70
+ uses: pypa/gh-action-pypi-publish@release/v1
71
+ with:
72
+ packages-dir: dist/
@@ -0,0 +1,3 @@
1
+ [FORMAT]
2
+ indent-string=\t
3
+ max-line-length=132
@@ -0,0 +1,24 @@
1
+ BSD 2-Clause License
2
+
3
+ Copyright (c) 2026, msearle5
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,138 @@
1
+ Metadata-Version: 2.5
2
+ Name: makedivide
3
+ Version: 1.0.2
4
+ Summary: Generate 6502 code to divide 8 x 8 bit integers
5
+ Author-email: Mike Searle <msearle5@btinternet.com>
6
+ License-Expression: BSD-2-Clause
7
+ License-File: LICENSE
8
+ Keywords: 6502,assembler,compiler,division
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.7
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.15
21
+ Classifier: Topic :: Software Development :: Build Tools
22
+ Requires-Python: >=3.7
23
+ Requires-Dist: py65emu
24
+ Requires-Dist: tqdm
25
+ Description-Content-Type: text/markdown
26
+
27
+
28
+ # makedivide
29
+
30
+ A Python app which makes 6502 assembler code to compute 8x8 bit division. There are millions of possible ways to do it, with trade-offs of size against speed. Speed can be be measured in various different ways - the mean of different groups of denominators, median and worst-case. It includes code to test them all by emulation (using [py65emu](https://github.com/docmarionum1/py65emu)), and rank the best.
31
+
32
+ ## How it works
33
+
34
+ Any denominator can have a custom routine to divide by that number - these are reasonably fast, but the more that are used the bigger the code is. To reduce the size of the custom denominators, powers of two are used (for example, the divide-by-34 routine is a divide by 2 which then falls into the divide-by-17), and optionally inlining (the ends of these routines are often identical and can be shared, but this adds a little time). In a few cases, factoring can be used (divide by 3 and then by 3 or 5).
35
+
36
+ When a custom routine isn't present, a fallback method must be used. The main ways are repeated subtraction (which works best when the denominator is large and so the subtraction doesn't have to be repeated many times), and shifting (like long division: slow, but compact and the speed doesn't vary between denominators). It can make sense to use both, switching between them based on the denominator. Repeated subtraction can be performed in a loop (small but slower) or unrolled for more speed at the cost of size. It's also possible to have a special case for denominators over 128 - these can only have a result of 0 or 1, making it reduce to a simple comparison.
37
+
38
+ ## Resource requirements
39
+
40
+ All variations of the divider (even when the inputs or output are in registers) require 2 bytes of zero page. The code can be between 21 and 700+ bytes, though the largest ones (over 400 bytes) are rarely worth using. Pick what fits.
41
+
42
+ ## I just want the 6502 code!
43
+
44
+ Check out **dividers.asm**! This contains dividers of all sizes - those picked as best for their size according to the score of ((mean x 2) + ((mean for denominators <= 64) x 3) + ((mean for denominators <= 16) x 4) + (median x 0.5) + (worst case x 1)) in which all these figures are actually ranks (positions in a list of results sorted by that statistic), not the raw figure itself.
45
+ To use one, find zero-page addresses for denominator and numerator and call the entry point (*note that this is not necessarily at the top!*) with numerator in that memory location, denominator in X and take the result in A.
46
+ Note that many of these will require page alignment (starting at $xx00), and many will also need to run from RAM, not ROM (as they use self-modifying code). Both limitations are mentioned in the leading comment to each routine.
47
+
48
+ ## I just want the 6502 code, but with a different stat mix.
49
+
50
+ A list of all results produced by the full test (~5MB compressed, 330MB when uncompressed) is at **full-list.txt.xz**. Makedivide can parse this and emit stats again, using a different mix function, e.g.:
51
+
52
+ makedivide.py -t -O full-list.txt -s -E -m 1 2 3 4 5 -o new-list.txt
53
+
54
+ (where you can substitute your weights (mean, mean to 64, mean to 16, median and worst-case) for "1 2 3 4 5")
55
+ This will produce new-list.txt in the same format as the original full-list.txt.
56
+ At the top of this file will be a list of entries, like this:
57
+
58
+ | Parameters |Size| Simulated Statistics | Emulated Statistics | Command Line
59
+ |5|0|0|N|N|N|N|N|0|Y|Y| 70 |45.8|71.2|109.9|34|370.0|47.7|73.4|112.6|36|372.0|-c5 -f0 -H -M
60
+
61
+ Pick one that looks good, take the command line from the rightmost field and feed it back to makedivide:
62
+
63
+ makedivide.py -o out.asm -c5 -f0 -H -M
64
+
65
+ The list of top (according to **--mix 2 3 4 0.5 1** weights) scoring parameters is in **top-list.txt**, and there is also a script **mkd.py** used to output **dividers.asm**.
66
+
67
+ ## Making a single divider routine
68
+
69
+ A simple example:
70
+
71
+ makedivide.py -o out.asm
72
+
73
+ produces a divider from default parameters, and writes it to the file **out.asm**.
74
+
75
+ An example of changing the parameters:
76
+
77
+ makedivide.py -c 4 -I -o out.asm
78
+
79
+ produces a divider with less (4) custom routines but with inlining switched on - resulting in a 57 byte routine rather than the default 193.
80
+
81
+ ## Running multiple variations
82
+
83
+ It's possible to run multiple sets of parameters. As well as testing that the code produced is always correct (passing all 256x256 possible inputs into the emulator and checking that the result is as expected), this allows statistics to be obtained which can be used to rank the parameters and obtain the fastest for each size. As the brute-force approach of trying every possible combination takes some hours, there are various methods to cut down the amount of work done.
84
+
85
+ The default if no additional parameters are given (just **--test**) is to run all possible combinations. This can be reduced to a fixed subset (~100x less) by **--quick**, and to a smaller subset (~40x less again) by **--quickest**. Alternatively a random subset of parameters can be tried (**--random 1000** will generate 1000 random sets of parameters - and print a seed which can be used with **--random-seed \<seed\>** to repeat the same set again.). You can also restrict which parameters are acceptable with some of the same options described below for a single case ("**Making a single divider routine, in more detail**") - either turning an option on or off, in all cases.
86
+
87
+ The **--assemble** parameter alone will attempt to assemble using ACME, but by itself will ignore the result unless assembly errors occur. If the **--stats** parameter is added, it will be simulated (this is a guess and not cycle accurate, but much faster than emulation). If the **--emulate** parameter is used, it will be emulated - by default, for all possible cases. However a smaller set can be used with the **--fast-stats** option, and it is also possible to select a random subset of cases with **--random-stats \<number of cases\>** - this also respects **--random-seed**.
88
+
89
+ If either simulation or emulation is done, and there is no assembly or emulation error, there will be stats in the output. (If both, then there will be two sets. If neither, there will just be the size.) This gives means (for all denominators, for denominators <=64 and <=16, median and worst case.) There will also be lists at the top of the output file showing the best for each of these metrics by size, from smallest to fastest. There will also be a combined score obtained by taking the sum of positions in each table, which can optionally be individually scaled (with **--mix**).
90
+
91
+ It's also possible to re-use results from a previous output file (such as full-list.txt) with **--old-file \<filename\>**, which is useful for trying a different mixed score. In this case to obtain simulated and/or emulated statistics in the new output you will also have to pass **--stats** and/or **--emulate** (even though this doesn't involve actually running the emulator again.)
92
+
93
+ By default, errors (failure to assemble, or the emulator producing the wrong result) are logged to the report file (set by **--report**), without exiting. To exit at the first error, use **--one-error**. To include all cases (not just failing ones) in the report **--verbose** can be used (*this can produce very large files if not used with a small subset of cases, though.*)
94
+
95
+ Typical speeds to complete a run are - all with pypy (CPython works, but at least when using --emulator is likely to be slower):
96
+
97
+ | Command | Speed |
98
+ | ----------------------------------------- | ----- |
99
+ | --emulate --stats --quickest --fast-stats | 0.8s |
100
+ | --emulate --stats --quickest | 11s |
101
+ | --emulate --stats --quick --fast-stats | 9.7s |
102
+ | --emulate --stats --random 2000 | 36s |
103
+ | --emulate --stats --fast-stats | 16m |
104
+ | --emulate --stats | 7h40m |
105
+ | --emulate --stats --old-file | 1m50s |
106
+
107
+ ## The runtime environment
108
+
109
+ Two named zero page locations are required, which can be changed by (**--denominator \<denominator\>** and **--numerator \<numerator\>**). By default, on entry the numerator is in the numerator zero page location and the denominator is in the X register, while the result is returned in A. However other conventions can be used with the **--denominator-from** (denominator source), **-numerator-from** (numerator source) and **-result-to** (result destination) parameters. This can reduce performance, though (generally not by much if at all for the inputs, but changing the result destination requires a wrapper subroutine which adds ~15 cycles).
110
+
111
+ ## Handling division by zero
112
+
113
+ The default approach is to treat it the same as dividing by 1. If you want it to signal an error, **-divide-by-zero \<handler\>** will jump to **\<handler\>**. Alternatively, if you also pass **-error-vector** then it will treat the address as a pointer and jump through it.
114
+
115
+ ## Assembly formatting
116
+
117
+ The code produced can be formatted in various ways to suit different assemblers. The defaults are intended for ACME: comments are prefixed by **;**, labels have no prefix, instructions are prefixed by a tab, and literal bytes are prefixed by **!byte** but all of these can be changed with the **--comment**, **--label**, **--instruction** and **--equb** arguments. Additionally, all labels (internal, as well as the entry point **_entry**_ have a prefix, set by **-prefix**.
118
+
119
+ ## Making a single divider routine, in more detail
120
+
121
+ Which denominators get a custom routine is defined by **--max-custom** (the highest denominator to use a custom routine), **--max-full** (the maximum denominator to use a custom routine that is not just a prefix to another one) and **--skip-custom** which allows one of these to be removed. Many of the custom routines share trailing sequences with others, so it is possible to combine them, and this is the default behaviour. It can be disabled with **--inlining** though, gaining speed at the cost of size. If **--factoring** is given and no custom /9 or /15 is present, then custom routines built from /3 /3 or /3 /5 will be used (these are slower but smaller than the custom routines, while still being faster than the generic case.)
122
+
123
+ The behaviour for denominators outside this is controlled by **--max-shifting** - above this denominator, a shifting divider is used, below it repeated subtraction is used. (Pass 256 to disable it and always use subtraction, pass 0 to disable subtraction and always use shifting.) The special case for denominators >= 128 is controlled by **--high-bit** to enable it, and **--early-high-bit** to perform the check for these high-bit-set values before checking whether the value is in the table of custom routines. Doing so makes high-bit set values faster, but others slower. It's a win on average if your input is close to being randomly distributed (rather than biased towards smaller values). The subtraction loop can be replaced by unrolled code - faster, but it can get very large - with **--unroll**.
124
+
125
+ How a custom routine is selected and dispatched is controlled by **--self-modifying** which allows the code to modify itself - faster and smaller, but it won't be able to run from ROM - and **--half-table** which uses a table of 8-bit low bytes with the high byte being common to all of them. This is faster and smaller, but can't be used for all parameters (those with more than 256 bytes of table-called routines) and requires that the resulting code is page aligned. Alternatively for very small jump tables, the table can be skipped entirely with **--choice-tree** (replacing it with compare-and-branch code).
126
+
127
+ ## Some special cases
128
+
129
+ Code generation for 65C02 (rather than the NMOS 6502) can be selected with **--with-65c02**. It won't make much difference though, and it will prevent emulation as the emulator doesn't support the 65C02.
130
+
131
+ A routine to divide by a constant can be extracted with **--known-denominator**.
132
+
133
+ When running a potentially slow multiple-parameter mode, a progress bar is displayed using [tqdm](https://github.com/tqdm/tqdm). This can be turned off with **--no-progress**.
134
+
135
+ ## Where the division routine are from
136
+
137
+ Most are from [this thread](https://forums.nesdev.org/viewtopic.php?f=2&t=11336), however I have also used a superoptimizer (**supero.c**, **supero-run** to run multiple copies of it with different conditions, **cycler.py** to annotate the results with speed, **supero-annotated.txt** the end result of this.)
138
+
@@ -0,0 +1,112 @@
1
+
2
+ # makedivide
3
+
4
+ A Python app which makes 6502 assembler code to compute 8x8 bit division. There are millions of possible ways to do it, with trade-offs of size against speed. Speed can be be measured in various different ways - the mean of different groups of denominators, median and worst-case. It includes code to test them all by emulation (using [py65emu](https://github.com/docmarionum1/py65emu)), and rank the best.
5
+
6
+ ## How it works
7
+
8
+ Any denominator can have a custom routine to divide by that number - these are reasonably fast, but the more that are used the bigger the code is. To reduce the size of the custom denominators, powers of two are used (for example, the divide-by-34 routine is a divide by 2 which then falls into the divide-by-17), and optionally inlining (the ends of these routines are often identical and can be shared, but this adds a little time). In a few cases, factoring can be used (divide by 3 and then by 3 or 5).
9
+
10
+ When a custom routine isn't present, a fallback method must be used. The main ways are repeated subtraction (which works best when the denominator is large and so the subtraction doesn't have to be repeated many times), and shifting (like long division: slow, but compact and the speed doesn't vary between denominators). It can make sense to use both, switching between them based on the denominator. Repeated subtraction can be performed in a loop (small but slower) or unrolled for more speed at the cost of size. It's also possible to have a special case for denominators over 128 - these can only have a result of 0 or 1, making it reduce to a simple comparison.
11
+
12
+ ## Resource requirements
13
+
14
+ All variations of the divider (even when the inputs or output are in registers) require 2 bytes of zero page. The code can be between 21 and 700+ bytes, though the largest ones (over 400 bytes) are rarely worth using. Pick what fits.
15
+
16
+ ## I just want the 6502 code!
17
+
18
+ Check out **dividers.asm**! This contains dividers of all sizes - those picked as best for their size according to the score of ((mean x 2) + ((mean for denominators <= 64) x 3) + ((mean for denominators <= 16) x 4) + (median x 0.5) + (worst case x 1)) in which all these figures are actually ranks (positions in a list of results sorted by that statistic), not the raw figure itself.
19
+ To use one, find zero-page addresses for denominator and numerator and call the entry point (*note that this is not necessarily at the top!*) with numerator in that memory location, denominator in X and take the result in A.
20
+ Note that many of these will require page alignment (starting at $xx00), and many will also need to run from RAM, not ROM (as they use self-modifying code). Both limitations are mentioned in the leading comment to each routine.
21
+
22
+ ## I just want the 6502 code, but with a different stat mix.
23
+
24
+ A list of all results produced by the full test (~5MB compressed, 330MB when uncompressed) is at **full-list.txt.xz**. Makedivide can parse this and emit stats again, using a different mix function, e.g.:
25
+
26
+ makedivide.py -t -O full-list.txt -s -E -m 1 2 3 4 5 -o new-list.txt
27
+
28
+ (where you can substitute your weights (mean, mean to 64, mean to 16, median and worst-case) for "1 2 3 4 5")
29
+ This will produce new-list.txt in the same format as the original full-list.txt.
30
+ At the top of this file will be a list of entries, like this:
31
+
32
+ | Parameters |Size| Simulated Statistics | Emulated Statistics | Command Line
33
+ |5|0|0|N|N|N|N|N|0|Y|Y| 70 |45.8|71.2|109.9|34|370.0|47.7|73.4|112.6|36|372.0|-c5 -f0 -H -M
34
+
35
+ Pick one that looks good, take the command line from the rightmost field and feed it back to makedivide:
36
+
37
+ makedivide.py -o out.asm -c5 -f0 -H -M
38
+
39
+ The list of top (according to **--mix 2 3 4 0.5 1** weights) scoring parameters is in **top-list.txt**, and there is also a script **mkd.py** used to output **dividers.asm**.
40
+
41
+ ## Making a single divider routine
42
+
43
+ A simple example:
44
+
45
+ makedivide.py -o out.asm
46
+
47
+ produces a divider from default parameters, and writes it to the file **out.asm**.
48
+
49
+ An example of changing the parameters:
50
+
51
+ makedivide.py -c 4 -I -o out.asm
52
+
53
+ produces a divider with less (4) custom routines but with inlining switched on - resulting in a 57 byte routine rather than the default 193.
54
+
55
+ ## Running multiple variations
56
+
57
+ It's possible to run multiple sets of parameters. As well as testing that the code produced is always correct (passing all 256x256 possible inputs into the emulator and checking that the result is as expected), this allows statistics to be obtained which can be used to rank the parameters and obtain the fastest for each size. As the brute-force approach of trying every possible combination takes some hours, there are various methods to cut down the amount of work done.
58
+
59
+ The default if no additional parameters are given (just **--test**) is to run all possible combinations. This can be reduced to a fixed subset (~100x less) by **--quick**, and to a smaller subset (~40x less again) by **--quickest**. Alternatively a random subset of parameters can be tried (**--random 1000** will generate 1000 random sets of parameters - and print a seed which can be used with **--random-seed \<seed\>** to repeat the same set again.). You can also restrict which parameters are acceptable with some of the same options described below for a single case ("**Making a single divider routine, in more detail**") - either turning an option on or off, in all cases.
60
+
61
+ The **--assemble** parameter alone will attempt to assemble using ACME, but by itself will ignore the result unless assembly errors occur. If the **--stats** parameter is added, it will be simulated (this is a guess and not cycle accurate, but much faster than emulation). If the **--emulate** parameter is used, it will be emulated - by default, for all possible cases. However a smaller set can be used with the **--fast-stats** option, and it is also possible to select a random subset of cases with **--random-stats \<number of cases\>** - this also respects **--random-seed**.
62
+
63
+ If either simulation or emulation is done, and there is no assembly or emulation error, there will be stats in the output. (If both, then there will be two sets. If neither, there will just be the size.) This gives means (for all denominators, for denominators <=64 and <=16, median and worst case.) There will also be lists at the top of the output file showing the best for each of these metrics by size, from smallest to fastest. There will also be a combined score obtained by taking the sum of positions in each table, which can optionally be individually scaled (with **--mix**).
64
+
65
+ It's also possible to re-use results from a previous output file (such as full-list.txt) with **--old-file \<filename\>**, which is useful for trying a different mixed score. In this case to obtain simulated and/or emulated statistics in the new output you will also have to pass **--stats** and/or **--emulate** (even though this doesn't involve actually running the emulator again.)
66
+
67
+ By default, errors (failure to assemble, or the emulator producing the wrong result) are logged to the report file (set by **--report**), without exiting. To exit at the first error, use **--one-error**. To include all cases (not just failing ones) in the report **--verbose** can be used (*this can produce very large files if not used with a small subset of cases, though.*)
68
+
69
+ Typical speeds to complete a run are - all with pypy (CPython works, but at least when using --emulator is likely to be slower):
70
+
71
+ | Command | Speed |
72
+ | ----------------------------------------- | ----- |
73
+ | --emulate --stats --quickest --fast-stats | 0.8s |
74
+ | --emulate --stats --quickest | 11s |
75
+ | --emulate --stats --quick --fast-stats | 9.7s |
76
+ | --emulate --stats --random 2000 | 36s |
77
+ | --emulate --stats --fast-stats | 16m |
78
+ | --emulate --stats | 7h40m |
79
+ | --emulate --stats --old-file | 1m50s |
80
+
81
+ ## The runtime environment
82
+
83
+ Two named zero page locations are required, which can be changed by (**--denominator \<denominator\>** and **--numerator \<numerator\>**). By default, on entry the numerator is in the numerator zero page location and the denominator is in the X register, while the result is returned in A. However other conventions can be used with the **--denominator-from** (denominator source), **-numerator-from** (numerator source) and **-result-to** (result destination) parameters. This can reduce performance, though (generally not by much if at all for the inputs, but changing the result destination requires a wrapper subroutine which adds ~15 cycles).
84
+
85
+ ## Handling division by zero
86
+
87
+ The default approach is to treat it the same as dividing by 1. If you want it to signal an error, **-divide-by-zero \<handler\>** will jump to **\<handler\>**. Alternatively, if you also pass **-error-vector** then it will treat the address as a pointer and jump through it.
88
+
89
+ ## Assembly formatting
90
+
91
+ The code produced can be formatted in various ways to suit different assemblers. The defaults are intended for ACME: comments are prefixed by **;**, labels have no prefix, instructions are prefixed by a tab, and literal bytes are prefixed by **!byte** but all of these can be changed with the **--comment**, **--label**, **--instruction** and **--equb** arguments. Additionally, all labels (internal, as well as the entry point **_entry**_ have a prefix, set by **-prefix**.
92
+
93
+ ## Making a single divider routine, in more detail
94
+
95
+ Which denominators get a custom routine is defined by **--max-custom** (the highest denominator to use a custom routine), **--max-full** (the maximum denominator to use a custom routine that is not just a prefix to another one) and **--skip-custom** which allows one of these to be removed. Many of the custom routines share trailing sequences with others, so it is possible to combine them, and this is the default behaviour. It can be disabled with **--inlining** though, gaining speed at the cost of size. If **--factoring** is given and no custom /9 or /15 is present, then custom routines built from /3 /3 or /3 /5 will be used (these are slower but smaller than the custom routines, while still being faster than the generic case.)
96
+
97
+ The behaviour for denominators outside this is controlled by **--max-shifting** - above this denominator, a shifting divider is used, below it repeated subtraction is used. (Pass 256 to disable it and always use subtraction, pass 0 to disable subtraction and always use shifting.) The special case for denominators >= 128 is controlled by **--high-bit** to enable it, and **--early-high-bit** to perform the check for these high-bit-set values before checking whether the value is in the table of custom routines. Doing so makes high-bit set values faster, but others slower. It's a win on average if your input is close to being randomly distributed (rather than biased towards smaller values). The subtraction loop can be replaced by unrolled code - faster, but it can get very large - with **--unroll**.
98
+
99
+ How a custom routine is selected and dispatched is controlled by **--self-modifying** which allows the code to modify itself - faster and smaller, but it won't be able to run from ROM - and **--half-table** which uses a table of 8-bit low bytes with the high byte being common to all of them. This is faster and smaller, but can't be used for all parameters (those with more than 256 bytes of table-called routines) and requires that the resulting code is page aligned. Alternatively for very small jump tables, the table can be skipped entirely with **--choice-tree** (replacing it with compare-and-branch code).
100
+
101
+ ## Some special cases
102
+
103
+ Code generation for 65C02 (rather than the NMOS 6502) can be selected with **--with-65c02**. It won't make much difference though, and it will prevent emulation as the emulator doesn't support the 65C02.
104
+
105
+ A routine to divide by a constant can be extracted with **--known-denominator**.
106
+
107
+ When running a potentially slow multiple-parameter mode, a progress bar is displayed using [tqdm](https://github.com/tqdm/tqdm). This can be turned off with **--no-progress**.
108
+
109
+ ## Where the division routine are from
110
+
111
+ Most are from [this thread](https://forums.nesdev.org/viewtopic.php?f=2&t=11336), however I have also used a superoptimizer (**supero.c**, **supero-run** to run multiple copies of it with different conditions, **cycler.py** to annotate the results with speed, **supero-annotated.txt** the end result of this.)
112
+
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/python3
2
+ infn = "supero-SEC-output"
3
+ srcfn = "sourcet"
4
+ tempfn = "/tmp/annotate"
5
+ outfn = "supero-SEC-output-annotated"
6
+ srcoutfn = "source-annotated"
7
+ outlines = []
8
+
9
+ def annotate(infn, outfn, wlast):
10
+ outlines = []
11
+ last=""
12
+ with open(infn,"r") as infile:
13
+ with open(outfn,"w") as outfile:
14
+ text = infile.read()
15
+ lines = text.splitlines()
16
+ for line in lines:
17
+ if (len(line) >= 2) and line[-2] == ';':
18
+ ncycs = 0
19
+ nbytes = 0
20
+ words = line.split()
21
+ for word in words:
22
+ if word == "lsr;" or word == "ror;" or word == "clc;" or word == "sec;":
23
+ ncycs += 2
24
+ nbytes += 1
25
+ elif word == "adc" or word == "sbc":
26
+ ncycs += 3
27
+ nbytes += 2
28
+ elif word[0] == "{" or word == "rts;":
29
+ pass
30
+ elif word[0] == "#":
31
+ ncycs -= 1
32
+ else:
33
+ print(word)
34
+ print("huh?")
35
+ if wlast:
36
+ outlines.append(f"{ncycs} cycles, {nbytes} bytes: {line}")
37
+ else:
38
+ outlines.append(f"{last} {ncycs} cycles, {nbytes} bytes: {line}")
39
+ else:
40
+ last = line
41
+ if wlast:
42
+ outlines.append(line)
43
+ outfile.write("\n".join(outlines))
44
+
45
+ def source2super(infn, outfn):
46
+ outlines = []
47
+ words = []
48
+ with open(infn,"r") as infile:
49
+ with open(outfn,"w") as outfile:
50
+ text = infile.read()
51
+ lines = text.splitlines()
52
+ for line in lines:
53
+ sline = line.strip()
54
+ if len(sline) == 0:
55
+ words.append("")
56
+ outlines.append("; ".join(words))
57
+ words=[]
58
+ if "label" in sline or "comment" in sline or "insn" not in sline:
59
+ outlines.append(line)
60
+ else:
61
+ start = line.index("}")
62
+ end = line.rindex('"')
63
+ word = line[start+1:end]
64
+ words.append(word)
65
+ outfile.write("\n".join(outlines))
66
+
67
+ for i in range(4):
68
+ for j in range(4):
69
+ inn = f"supero-output-dir/supero-out-{j}-{i}.txt"
70
+ outn = f"supero-output-dir/annotated-{j}-{i}.txt"
71
+ annotate(inn, outn, False)
72
+ source2super(srcfn, tempfn)
73
+ annotate(tempfn, srcoutfn, True)