Charge the memory controller for the memory it moves
A blit cost ten cycles, which were the five port writes that set it up. The quarter of a kilobyte that moved cost nothing, and no hardware moves a quarter of a kilobyte for nothing. BANKS ARE SEPARATE MEMORIES, AND THAT IS WHAT SETS THE RATE. A move between two of them can overlap its read and its write - fetch the next byte while the last one is stored - so it settles at a byte a cycle. A move within one bank cannot, and costs two. A fill has nothing to read and costs one whatever the banks are. The odd cycle on each is the pipeline filling. That is not a modelling choice so much as a reading of the structure the machine already has: a Program to Data blit is inherently twice the rate of a Data to Data one, and it is legible why. Measured: 256 bytes is 297 cycles across banks and 518 within one, both including the instructions that ask for it. WHAT IT TAUGHT, which was not what I expected. Charging for movement costs the native assembler 0.4 per cent and costs directory work 13.4. The assembler reads a block and then thinks about it for a long time, so the move is amortised into nothing; the filesystem reads a block in order to look at it and does nothing else in between. So the case for a blitter that runs alongside the CPU is weaker than it sounds. Concurrency pays when there is other work to do during the transfer, and the place that spends its time moving memory is exactly the place with nothing else to do - it blits a block precisely so that it can read it. What that workload wants is a FASTER controller, not a concurrent one: a wider data path halves the wait, and the machine is waiting either way. Video is the case that would still want concurrency, since a frame can be moved while the next one is worked out. That is an argument about software nobody has written yet, and it is now an argument with numbers on the other side of it. The byte at a time port is charged too, for the byte it moves beyond reaching the port. Nothing polls CTRL_STATUS, so the transfer stalls whoever asked for it, which is the conservative reading and the one the software already assumes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
co-authored by
Claude Opus 5
parent
f1e5cc46f6
commit
e0cf0a9a25
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "cpu.h"
|
||||
#include "io.h"
|
||||
#include "controller.h"
|
||||
#include "../Assembler/assembly.h" // For the vector table layout, which both tools share.
|
||||
|
||||
uint16_t shiftRegister;
|
||||
@@ -38,11 +39,16 @@ static inline void writeData(CPURegisters *cpu, uint16_t at, uint8_t value) {
|
||||
static inline void portOut(CPURegisters *cpu, uint8_t value, uint8_t port) {
|
||||
cpu->busCycles++;
|
||||
OutputHandler(value, port);
|
||||
// And whatever memory that made the controller move. The machine waits for it, which
|
||||
// is the conservative reading: a blit stalls the program that asked for one.
|
||||
cpu->busCycles += controllerTakeCycles();
|
||||
}
|
||||
|
||||
static inline uint8_t portIn(CPURegisters *cpu, uint8_t port) {
|
||||
cpu->busCycles++;
|
||||
return InputHandler(port);
|
||||
uint8_t value = InputHandler(port);
|
||||
cpu->busCycles += controllerTakeCycles();
|
||||
return value;
|
||||
}
|
||||
|
||||
static uint16_t readVector(const uint8_t *programMemory, uint16_t base, uint8_t index) {
|
||||
|
||||
Reference in New Issue
Block a user