Widen the memory controller's path to sixteen bits
The controller now reaches bank memory two bytes at a time, so a transfer whose source, destination and length are all even moves two bytes a cycle between banks and one within a bank - twice what each was. A 256 byte block between banks falls from 257 cycles to 129. Alignment is required all three ways because a word is read at an even address and written at an even address; an odd anything would mean shifting bytes across word boundaries to line them up, which is a different design. A misaligned transfer falls back to the byte a cycle it cost before, so nothing already written got slower. THE CPU DOES NOT CHANGE. It still sees eight bits, a Data Pointer still addresses a byte, and no instruction means anything different. This is a peripheral getting faster, which is why it is worth doing now rather than after more is built on top of it. The rule is deliberately visible rather than smoothed over: aligning a buffer costs nothing and halves what moving it costs, and a cost a program cannot see is a cost it cannot avoid. Tests/cycles.sh is new, and is the test the Test Manual has always said this kind of change would need - run.sh strips the cycle count from every recorded result, so nothing else in the suite can see any of this. It pins the RATE rather than a total: each case runs twice from programs whose instructions are identical but for the byte written to the Command port, once asking for the transfer and once for GuardOff, which costs nothing beyond the port write. The difference is the transfer and nothing else. Verified by disabling the widening, which failed exactly the three aligned cases and left the five misaligned ones passing. The Programming Manual gains a section saying what a transfer costs, which it never said at all - it only promised a transfer does not wait, which is a different claim and could be read as promising it is free. 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
c3c2451afe
commit
4c3eac8d9c
Executable
+144
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env bash
|
||||
# Checks what the memory controller charges for moving memory.
|
||||
#
|
||||
# EVERY OTHER TEST HERE IS BLIND TO THIS. run.sh strips the cycle count out of every
|
||||
# recorded result on purpose, because a number that moves whenever anything changes turns
|
||||
# real differences into a crowd of meaningless ones. The Test Manual says the other half of
|
||||
# that bargain out loud: anything that genuinely wants to measure cycles has to say so in a
|
||||
# test of its own. This is that test.
|
||||
#
|
||||
# What it pins is the RATE rather than a total. Each case runs twice, from programs whose
|
||||
# instructions are identical except for the byte written to the Command port: once asking
|
||||
# for the transfer, and once asking for GuardOff, which lowers a fence that was never raised
|
||||
# and costs nothing beyond the port write. The difference between the two runs is therefore
|
||||
# the transfer and nothing else - no instruction count, no setup, no startup.
|
||||
#
|
||||
# Written by Anachronaut
|
||||
|
||||
set -u
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
BUILD="$ROOT/Tests/build/cycles"
|
||||
ASM="$ROOT/Assembler"
|
||||
EMU="$ROOT/SplitBit"
|
||||
|
||||
for tool in "$ASM" "$EMU"; do
|
||||
[ -x "$tool" ] || { echo "$(basename "$tool") is not built."; exit 1; }
|
||||
done
|
||||
|
||||
rm -rf "$BUILD"; mkdir -p "$BUILD"
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
FAILED_NAMES=()
|
||||
|
||||
GREEN=$'\033[32m'; RED=$'\033[31m'; RESET=$'\033[0m'
|
||||
[ -t 1 ] || { GREEN=""; RED=""; RESET=""; }
|
||||
|
||||
# A program that sets the controller up and then writes one byte to the Command port.
|
||||
# The eight registers are written the same way every time, so two programs built from this
|
||||
# differ by exactly one immediate.
|
||||
program() {
|
||||
# program <sourceBank> <srcHigh> <srcLow> <destBank> <dstHigh> <dstLow> <lenHigh> <lenLow> <command>
|
||||
cat <<ASM
|
||||
#Program
|
||||
start:
|
||||
INIA $1
|
||||
OUTA 0xE0
|
||||
INIA $2
|
||||
OUTA 0xE1
|
||||
INIA $3
|
||||
OUTA 0xE2
|
||||
INIA $4
|
||||
OUTA 0xE3
|
||||
INIA $5
|
||||
OUTA 0xE4
|
||||
INIA $6
|
||||
OUTA 0xE5
|
||||
INIA $7
|
||||
OUTA 0xE6
|
||||
INIA $8
|
||||
OUTA 0xE7
|
||||
INIA $9
|
||||
OUTA 0xE8
|
||||
HALT
|
||||
#Vectors
|
||||
Boot start
|
||||
ASM
|
||||
}
|
||||
|
||||
# Runs one program and says how many cycles the machine used.
|
||||
cycles() {
|
||||
local name="$1"; shift
|
||||
program "$@" > "$BUILD/$name.asm"
|
||||
"$ASM" "$BUILD/$name.asm" -o "$BUILD/$name.bin" >/dev/null 2>&1 || {
|
||||
echo " could not assemble $name"; return 1; }
|
||||
"$EMU" --fast "$BUILD/$name.bin" 2>&1 | grep -oE 'after [0-9]+ cycles' | grep -oE '[0-9]+'
|
||||
}
|
||||
|
||||
# The cost of one transfer: the same program asking for it, less the same program asking
|
||||
# for GuardOff instead. 0x01 is Blit, 0x02 is Fill, 0x11 is GuardOff.
|
||||
charged() {
|
||||
# charged <name> <command> <sourceBank..lenLow>
|
||||
local name="$1" command="$2"; shift 2
|
||||
local withIt withoutIt
|
||||
withIt="$(cycles "$name" "$@" "$command")" || return 1
|
||||
withoutIt="$(cycles "$name-idle" "$@" 0x11)" || return 1
|
||||
[ -n "$withIt" ] && [ -n "$withoutIt" ] || { echo " no cycle count for $name"; return 1; }
|
||||
echo $((withIt - withoutIt))
|
||||
}
|
||||
|
||||
check() {
|
||||
# check <name> <expected> <command> <registers...>
|
||||
local name="$1" expected="$2"; shift 2
|
||||
local got
|
||||
got="$(charged "$name" "$@")" || {
|
||||
FAIL=$((FAIL + 1)); FAILED_NAMES+=("$name")
|
||||
printf " [%sFAIL%s] %-34s could not measure it\n" "$RED" "$RESET" "$name"
|
||||
return
|
||||
}
|
||||
if [ "$got" = "$expected" ]; then
|
||||
PASS=$((PASS + 1))
|
||||
printf " [%sok %s] %-34s %s cycles\n" "$GREEN" "$RESET" "$name" "$got"
|
||||
else
|
||||
FAIL=$((FAIL + 1)); FAILED_NAMES+=("$name")
|
||||
printf " [%sFAIL%s] %-34s %s cycles, expected %s\n" "$RED" "$RESET" "$name" "$got" "$expected"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "Checking what the memory controller charges."
|
||||
|
||||
# ---- Between two banks ----
|
||||
#
|
||||
# Data Memory to Program Memory, well above where the program itself sits and well below
|
||||
# the vector table. 256 bytes: 128 words and the cycle the pipeline takes to fill.
|
||||
check "256 bytes between banks" 129 0x01 0d1 0x10 0x00 0d0 0x80 0x00 0x01 0x00
|
||||
# The same move with an odd source. Nothing about it can be paired, so it runs at the byte
|
||||
# a cycle the machine had before the path was widened.
|
||||
check "and again from an odd address" 257 0x01 0d1 0x10 0x01 0d0 0x80 0x00 0x01 0x00
|
||||
# An odd destination is just as disqualifying, and so is an odd length: all three have to
|
||||
# line up or none of it does.
|
||||
check "an odd destination is the same" 257 0x01 0d1 0x10 0x00 0d0 0x80 0x01 0x01 0x00
|
||||
check "so is an odd length" 256 0x01 0d1 0x10 0x00 0d0 0x80 0x00 0x00 0xFF
|
||||
|
||||
# ---- Within one bank ----
|
||||
#
|
||||
# One memory cannot overlap its own read and its own write, so it costs twice what the same
|
||||
# move between two banks costs - widened or not.
|
||||
check "256 bytes within one bank" 257 0x01 0d1 0x10 0x00 0d1 0x20 0x00 0x01 0x00
|
||||
check "and misaligned, within one" 513 0x01 0d1 0x10 0x01 0d1 0x20 0x00 0x01 0x00
|
||||
|
||||
# ---- Filling ----
|
||||
#
|
||||
# Nothing to read, so it goes at the between-banks rate whatever the banks are. SourceLow
|
||||
# carries the byte rather than an address, which is why only the destination and the length
|
||||
# decide whether it can be paired.
|
||||
check "256 bytes filled" 129 0x02 0d0 0x00 0xAA 0d1 0x30 0x00 0x01 0x00
|
||||
check "and filled at an odd address" 257 0x02 0d0 0x00 0xAA 0d1 0x30 0x01 0x01 0x00
|
||||
|
||||
echo
|
||||
if [ "$FAIL" -eq 0 ]; then
|
||||
echo "All $PASS controller cost checks passed."
|
||||
exit 0
|
||||
fi
|
||||
echo "$PASS passed, $FAIL failed: ${FAILED_NAMES[*]}"
|
||||
exit 1
|
||||
Reference in New Issue
Block a user