Skip to content

Commit 55d5c60

Browse files
committed
test(conformance): hold the model to both appendices of the manuals
Appendix A of the Hardware Manual prints the address bus and the read write line for every cycle of every addressing mode; all 27 tables are now recorded and driven. Appendix B of the Programming Manual gives each instruction its opcode, length, timing and flags; all 151 opcodes are recorded and checked against the table, the cycle counts and 257 flag absolutes. Four places where the pages and the part disagree are recorded with the evidence: the address of the discarded indexed read, written two ways in one appendix; the branch table, whose address rows sit a row lower than they run; the stack sums, which stop at the edge of page one; and two rows printed without the page-crossing mark their neighbours carry. Also ignores virtualenvs, which were failing the format gate on vendored files.
1 parent cba7f28 commit 55d5c60

9 files changed

Lines changed: 3719 additions & 2 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ build/
99
.DS_Store
1010
.mypy_cache/
1111
docs/
12+
.venv/
13+
venv/
14+
uv.lock

.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ node_modules/
44
.coverage
55
.mypy_cache/
66
.ruff_cache/
7+
.venv/
8+
venv/

README.md

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ Pure Python, standard library only. The release tooling is the sole `node_module
123123

124124
### Read from the datasheets
125125

126-
Every hardware fact this project relies on is in [`conformance/hardware.json`](conformance/hardware.json) with the sentence it was read from. Where a manufacturer's document and the recorded cycles disagree, [`conformance/divergences.json`](conformance/divergences.json) carries both and says what would settle it.
126+
Every hardware fact this project relies on is in [`conformance/hardware.json`](conformance/hardware.json) with the sentence it was read from, every cycle of every NMOS addressing mode is in [`conformance/addressing-cycles.json`](conformance/addressing-cycles.json) as Appendix A prints it, and all 151 documented opcodes are in [`conformance/instruction-set.json`](conformance/instruction-set.json) as Appendix B prints them. Where a manufacturer's document and the recorded cycles disagree, [`conformance/divergences.json`](conformance/divergences.json) carries both and says what would settle it.
127127

128128
</td>
129129
</tr>
@@ -232,6 +232,59 @@ cpu.a, cpu.x, cpu.y
232232

233233
`SparseMemory` holds only what has been written and hashes the address for everything else, so a test that touches a dozen bytes does not pay for sixteen megabytes to stay unclean. Both take a `seed`, so a differential run against another implementation stays comparable.
234234

235+
### The appendix, cycle by cycle
236+
237+
Appendix A of the MCS6500 Hardware Manual prints the address bus, the data bus
238+
and the read write line for every cycle of every addressing mode. It is the only
239+
manufacturer statement of NMOS bus behaviour this project has found, so all
240+
twenty-seven of its tables are in
241+
[`conformance/addressing-cycles.json`](conformance/addressing-cycles.json) with
242+
the manual's own address expressions rather than a paraphrase.
243+
[`conformance/addressing_cycles.test.py`](conformance/addressing_cycles.test.py)
244+
drives each shape and resolves those expressions against the run, so a row that
245+
stops matching names the page it came from. That check needs no suite on the
246+
machine.
247+
248+
Three of the tables say something the part does not do, and each is recorded
249+
rather than quietly followed:
250+
251+
- The discarded read of an indexed access. Four tables give its high byte as
252+
`BAH + C`; the indirect Y store two pages later gives it as `BAH`, with no
253+
carry. Only the second matches the part, and only the second explains why the
254+
other four carry a footnote saying that read has to be ignored. This is the
255+
cycle that makes an indexed store to a hardware register touch a second
256+
register one page below the one it names.
257+
- The branch table. Its two address rows sit one row lower than they run: the
258+
part drives the plain program counter on the third cycle, the partially
259+
corrected target on the fourth, and never reaches the corrected target inside
260+
the branch at all.
261+
- The stack addresses of a pull, written as plain sums. They hold everywhere
262+
except across the edge of page one, which is exactly where a deep sequence of
263+
pushes leaves the pointer.
264+
265+
### The instruction set, as the manufacturer stated it
266+
267+
Appendix B of the MCS6500 Programming Manual gives each of the fifty-six
268+
documented instructions its own page: the flags it touches, and for every
269+
addressing mode the opcode, the byte count and the cycle count. That is a
270+
hundred and fifty-one opcodes, which is the whole documented set, and all of
271+
them are in
272+
[`conformance/instruction-set.json`](conformance/instruction-set.json).
273+
274+
[`conformance/instruction_set.test.py`](conformance/instruction_set.test.py)
275+
holds three separate things to it. The opcode table this project decodes with
276+
has to name the same mnemonic, mode and length for every one. Each instruction
277+
has to take the cycles the page prints, with the extra cycle appearing exactly
278+
where the page marks a page crossing. And each of the two hundred and
279+
fifty-seven flag rules that are absolute, this one is always reset, this one is
280+
never touched, has to hold across twenty-four states.
281+
282+
Two rows are misprinted, and the record says so rather than following them.
283+
`AND (Oper), Y` and `ORA (Oper), Y` are printed without the asterisk that marks
284+
the page-crossing cycle, while the six other Group One instructions carry it and
285+
while these same two carry it on their absolute indexed rows one line above. The
286+
part takes the cycle on all eight.
287+
235288
## Conformance
236289

237290
```bash
@@ -295,7 +348,12 @@ mos65xx/
295348
conformance/
296349
fetch.py partial, sparse, pinned checkout of the suites
297350
singlestep.py runs the suite and reports what disagreed
351+
cycles.py holds every bus cycle to the suite rather than the end state
298352
suites.json which suites, which commit
353+
hardware.json what the datasheets print, fact by fact, with the sentence
354+
addressing-cycles.json every cycle of every NMOS addressing mode, as printed
355+
instruction-set.json every documented opcode, its length, timing and flags
356+
divergences.json where a document and the recorded cycles part, and why
299357
```
300358

301359
Each module has its tests beside it as `<module>.test.py`, so a module and the cases that pin its behaviour are read together.

0 commit comments

Comments
 (0)