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
@@ -135,10 +135,18 @@ assembling a program.
|
|||||||
|
|
||||||
**The memory controller is charged for what it moves**, on the same terms. Banks are
|
**The memory controller is charged for what it moves**, on the same terms. Banks are
|
||||||
separate memories, and that is what sets the rate: a move between two of them can overlap
|
separate memories, and that is what sets the rate: a move between two of them can overlap
|
||||||
its read and its write, so it settles at a byte a cycle, while a move within one bank cannot
|
its read and its write, while a move within one bank cannot and costs twice as much. A fill
|
||||||
and costs two. A fill has nothing to read and costs one. So a 256 byte block is 257 cycles
|
has nothing to read and goes at the between-banks rate. Against the ten cycles a transfer
|
||||||
between banks and 513 within one, against the ten it used to cost - which was the five port
|
used to cost - the five port writes that set it up, and nothing at all for the quarter of a
|
||||||
writes that set it up and nothing for the quarter of a kilobyte that moved.
|
kilobyte that moved.
|
||||||
|
|
||||||
|
**And the controller's path to memory is sixteen bits wide.** A transfer whose source,
|
||||||
|
destination and length are all even moves two bytes a cycle between banks and one within a
|
||||||
|
bank; anything odd falls back to a byte a cycle, because lining bytes up across word
|
||||||
|
boundaries is a second design and this is not it. So a 256 byte block is 129 cycles between
|
||||||
|
banks and 257 within one when it is aligned, and 257 and 513 when it is not. The CPU still
|
||||||
|
sees eight bits and no instruction means anything different: this is a peripheral being
|
||||||
|
faster, not a new machine.
|
||||||
|
|
||||||
The transfer stalls the program that asked for it. Whether hardware would let the two run at
|
The transfer stalls the program that asked for it. Whether hardware would let the two run at
|
||||||
once is left open, the same way pipelining is: the memories are separate, so it plausibly
|
once is left open, the same way pipelining is: the memories are separate, so it plausibly
|
||||||
|
|||||||
@@ -155,6 +155,24 @@ static int rangeWritable(uint8_t bank, uint16_t address, uint32_t count) {
|
|||||||
// What the moves below have cost since anybody last asked.
|
// What the moves below have cost since anybody last asked.
|
||||||
static unsigned long pendingCycles = 0;
|
static unsigned long pendingCycles = 0;
|
||||||
|
|
||||||
|
// ---- Sixteen bits wide, when the addresses let it be ----
|
||||||
|
//
|
||||||
|
// The controller reaches bank memory two bytes at a time, so an aligned transfer moves two
|
||||||
|
// bytes in the time a misaligned one moves one. A word is read at an even address and
|
||||||
|
// written at an even address, which is why the source, the destination AND the length must
|
||||||
|
// all be even: an odd anything would have the controller shifting bytes across word
|
||||||
|
// boundaries to line them up, and that is a second design rather than this one.
|
||||||
|
//
|
||||||
|
// Misaligned falls back to a byte a cycle, which is exactly what the machine did before it
|
||||||
|
// was widened, so nothing already written got slower.
|
||||||
|
//
|
||||||
|
// THE RULE IS VISIBLE ON PURPOSE. A program that cares can align what it moves, and a cost
|
||||||
|
// a program cannot see is a cost it cannot avoid. It is also the honest thing to model:
|
||||||
|
// hardware this shape really does behave this way.
|
||||||
|
static int wideRun(uint32_t addressesAndLength) {
|
||||||
|
return (addressesAndLength & 1u) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned long controllerTakeCycles(void) {
|
unsigned long controllerTakeCycles(void) {
|
||||||
unsigned long taken = pendingCycles;
|
unsigned long taken = pendingCycles;
|
||||||
pendingCycles = 0;
|
pendingCycles = 0;
|
||||||
@@ -175,9 +193,13 @@ static void doBlit(void) {
|
|||||||
// designing against.
|
// designing against.
|
||||||
memmove(banks[destBank].memory + destAddress,
|
memmove(banks[destBank].memory + destAddress,
|
||||||
banks[sourceBank].memory + sourceAddress, count);
|
banks[sourceBank].memory + sourceAddress, count);
|
||||||
// A byte read and a byte written. Two banks are two memories and the pair overlaps;
|
// A word read and a word written. Two banks are two memories and the pair overlaps;
|
||||||
// one bank is one memory and they do not. The odd cycle is the pipeline filling.
|
// one bank is one memory and they do not. The odd cycle is the pipeline filling.
|
||||||
pendingCycles += (sourceBank == destBank) ? 2 * count + 1 : count + 1;
|
//
|
||||||
|
// Wide when everything is even, so an aligned move between banks settles at two bytes a
|
||||||
|
// cycle and an aligned move within one at a byte a cycle - each twice what it was.
|
||||||
|
unsigned long moves = wideRun(sourceAddress | destAddress | count) ? count / 2 : count;
|
||||||
|
pendingCycles += (sourceBank == destBank) ? 2 * moves + 1 : moves + 1;
|
||||||
sourceAddress = (uint16_t)(sourceAddress + count);
|
sourceAddress = (uint16_t)(sourceAddress + count);
|
||||||
destAddress = (uint16_t)(destAddress + count);
|
destAddress = (uint16_t)(destAddress + count);
|
||||||
status = 0;
|
status = 0;
|
||||||
@@ -188,9 +210,10 @@ static void doFill(void) {
|
|||||||
if (!rangeWritable(destBank, destAddress, count)) {
|
if (!rangeWritable(destBank, destAddress, count)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// A fill has nowhere to read from, only a value, so SourceLow carries the byte and
|
// A fill has nowhere to read from, only a value, so SourceLow carries the byte and the
|
||||||
// the rest of the source registers mean nothing here.
|
// rest of the source registers mean nothing here - including for the alignment, which
|
||||||
pendingCycles += count + 1;
|
// asks only about where the bytes are going and how many there are.
|
||||||
|
pendingCycles += (wideRun(destAddress | count) ? count / 2 : count) + 1;
|
||||||
memset(banks[destBank].memory + destAddress, (int)(sourceAddress & 0xFF), count);
|
memset(banks[destBank].memory + destAddress, (int)(sourceAddress & 0xFF), count);
|
||||||
destAddress = (uint16_t)(destAddress + count);
|
destAddress = (uint16_t)(destAddress + count);
|
||||||
status = 0;
|
status = 0;
|
||||||
|
|||||||
@@ -93,9 +93,14 @@
|
|||||||
// work look like ten cycles.
|
// work look like ten cycles.
|
||||||
//
|
//
|
||||||
// Banks are separate memories, which is what decides the rate. A move between two of them
|
// Banks are separate memories, which is what decides 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 -
|
// can overlap its read and its write - fetch the next word 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
|
// while a move WITHIN one bank cannot and costs twice as much. A fill has nothing to read
|
||||||
// nothing to read and costs one whatever the banks are.
|
// and costs the same as a move between banks.
|
||||||
|
//
|
||||||
|
// AND THE PATH IS SIXTEEN BITS WIDE, so a transfer whose source, destination and length are
|
||||||
|
// all even moves two bytes a cycle between banks and one within a bank. Anything odd falls
|
||||||
|
// back to the byte a cycle this had before it was widened: lining up bytes across word
|
||||||
|
// boundaries is a second design, and this is not it. See wideRun in controller.c.
|
||||||
//
|
//
|
||||||
// Returned and cleared, so the caller adds it to whatever it is charging for. The CPU picks
|
// Returned and cleared, so the caller adds it to whatever it is charging for. The CPU picks
|
||||||
// it up after each port access, which makes the transfer a stall: the machine issues a blit
|
// it up after each port access, which makes the transfer a stall: the machine issues a blit
|
||||||
|
|||||||
@@ -604,6 +604,26 @@ Everything a transfer would touch is checked before any of it moves. A transfer
|
|||||||
|
|
||||||
Filling is worth reaching for. Clearing a page with one Fill instead of a store and a loop takes about a tenth off the running time of the segmented sieve, which spends most of its life zeroing its window.
|
Filling is worth reaching for. Clearing a page with one Fill instead of a store and a loop takes about a tenth off the running time of the segmented sieve, which spends most of its life zeroing its window.
|
||||||
|
|
||||||
|
### What A Transfer Costs:
|
||||||
|
|
||||||
|
A transfer does not wait on anything, but it is not free. The controller is charged for every byte it moves, and the program that asked stalls until it is done, so these are cycles out of that program's budget.
|
||||||
|
|
||||||
|
Two things set the rate. **Banks are separate memories**, so a move between two of them can fetch the next word while the last one is stored, and a move within a single bank cannot and costs twice as much. And **the controller's path to memory is sixteen bits wide**, so it moves two bytes at a time when the addresses allow.
|
||||||
|
|
||||||
|
They allow it when the source, the destination and the length are **all even**. 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 machine. A misaligned transfer falls back to a byte a cycle, which is what this cost before the path was widened.
|
||||||
|
|
||||||
|
| Moving 256 bytes | Aligned | Not aligned |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Between two banks | 129 | 257 |
|
||||||
|
| Within one bank | 257 | 513 |
|
||||||
|
| Fill | 129 | 257 |
|
||||||
|
|
||||||
|
The odd cycle in each is the pipeline filling. A fill has nothing to read, so it goes at the between-banks rate whatever bank it writes, and only its destination and length decide whether it can be paired - the byte it writes lives in SourceLow and is a value rather than an address.
|
||||||
|
|
||||||
|
**The rule is visible so that a program can act on it.** Aligning a buffer costs nothing and halves what moving it costs, and a cost a program cannot see is a cost it cannot avoid.
|
||||||
|
|
||||||
|
None of this changes the CPU. It still sees eight bits, a Data Pointer still addresses a byte, and no instruction means anything different than it did. What got wider is the controller's own path to the memories it moves between.
|
||||||
|
|
||||||
### Banks:
|
### Banks:
|
||||||
|
|
||||||
Memory the controller can reach is divided into banks of up to 64K each, numbered 0 to 255. Program and Data are banks like any other; being 0 and 1 is the only thing special about them.
|
Memory the controller can reach is divided into banks of up to 64K each, numbered 0 to 255. Program and Data are banks like any other; being 0 and 1 is the only thing special about them.
|
||||||
|
|||||||
+18
-5
@@ -9,7 +9,7 @@ believe them.
|
|||||||
|
|
||||||
## What The Suite Claims:
|
## What The Suite Claims:
|
||||||
|
|
||||||
The suite is not one thing. It is seven scripts making five different kinds of claim, and
|
The suite is not one thing. It is eight scripts making five different kinds of claim, and
|
||||||
knowing which claim you are relying on is the whole point of this document. A recorded
|
knowing which claim you are relying on is the whole point of this document. A recorded
|
||||||
transcript and a byte-for-byte comparison against a second implementation both print
|
transcript and a byte-for-byte comparison against a second implementation both print
|
||||||
`[ok ]`, and they are worth wildly different amounts.
|
`[ok ]`, and they are worth wildly different amounts.
|
||||||
@@ -55,6 +55,7 @@ Individual scripts can be run on their own, from anywhere:
|
|||||||
./Tests/run.sh hello waitTest Only the named ones.
|
./Tests/run.sh hello waitTest Only the named ones.
|
||||||
./Tests/run.sh --bless Record current output as expected. See below.
|
./Tests/run.sh --bless Record current output as expected. See below.
|
||||||
./Tests/disk.sh The disk tool against the format.
|
./Tests/disk.sh The disk tool against the format.
|
||||||
|
./Tests/cycles.sh What the memory controller charges.
|
||||||
./Tests/terminal.sh The things a recorded file cannot see.
|
./Tests/terminal.sh The things a recorded file cannot see.
|
||||||
./Tests/native.sh The two assemblers against each other.
|
./Tests/native.sh The two assemblers against each other.
|
||||||
./Tests/agree.sh The two filesystems against each other.
|
./Tests/agree.sh The two filesystems against each other.
|
||||||
@@ -153,10 +154,22 @@ that a prompt arrives before input is read, that a keystroke arrives without Ret
|
|||||||
the terminal is handed back however the machine dies - SIGHUP, SIGINT, SIGQUIT, SIGABRT,
|
the terminal is handed back however the machine dies - SIGHUP, SIGINT, SIGQUIT, SIGABRT,
|
||||||
SIGSEGV, SIGTERM - and that suspending and resuming leave it as they found it.
|
SIGSEGV, SIGTERM - and that suspending and resuming leave it as they found it.
|
||||||
|
|
||||||
It also asks the one question about cycles that nothing else can, since the count is
|
It also asks the one question about *waiting* that nothing else can, since the count is
|
||||||
stripped from every recorded result: whether a program on a slow disk slept through the
|
stripped from every recorded result: whether a program on a slow disk slept through the wait
|
||||||
wait or spun on it. Both print the same characters and take the same elapsed time. Only the
|
or spun on it. Both print the same characters and take the same elapsed time. Only the split
|
||||||
split between idle and bus cycles tells them apart.
|
between idle and bus cycles tells them apart.
|
||||||
|
|
||||||
|
`Tests/cycles.sh` is the other half of the same bargain, and exists because the determinism
|
||||||
|
rules below throw the cycle count away. It measures what the memory controller charges for
|
||||||
|
moving memory - which is real time out of a program's budget, and is invisible everywhere
|
||||||
|
else in this suite.
|
||||||
|
|
||||||
|
**It pins 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 the transfer and
|
||||||
|
nothing else - no instruction count, no setup, no startup - so the check survives every
|
||||||
|
change to the machine that is not a change to what a transfer costs.
|
||||||
|
|
||||||
### 5. The documents against the code
|
### 5. The documents against the code
|
||||||
|
|
||||||
|
|||||||
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
|
||||||
@@ -144,6 +144,8 @@ test: $(EMU_TARGET) $(ASM_TARGET) $(DSK_TARGET) $(LINT_TARGET) strict
|
|||||||
@echo
|
@echo
|
||||||
@./Tests/disk.sh
|
@./Tests/disk.sh
|
||||||
@echo
|
@echo
|
||||||
|
@./Tests/cycles.sh
|
||||||
|
@echo
|
||||||
@./Tests/terminal.sh
|
@./Tests/terminal.sh
|
||||||
@echo
|
@echo
|
||||||
@./Tests/native.sh
|
@./Tests/native.sh
|
||||||
@@ -183,6 +185,8 @@ sanitize:
|
|||||||
@echo
|
@echo
|
||||||
@./Tests/disk.sh
|
@./Tests/disk.sh
|
||||||
@echo
|
@echo
|
||||||
|
@./Tests/cycles.sh
|
||||||
|
@echo
|
||||||
@./Tests/terminal.sh
|
@./Tests/terminal.sh
|
||||||
@echo
|
@echo
|
||||||
@./Tests/native.sh
|
@./Tests/native.sh
|
||||||
|
|||||||
Reference in New Issue
Block a user