feat(L01): add number systems and storage card
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
import assert from 'node:assert/strict'; import { access, readFile } from 'node:fs/promises'; import test from 'node:test';
|
||||
const source=JSON.parse(await readFile(new URL('../json/card_source.json',import.meta.url),'utf8'));
|
||||
test('L01 identity and three tasks are stable',()=>{assert.equal(source.card.series,'rv32i-asm');assert.equal(source.card.number,'01');assert.equal(source.card.uuid,'d132962b-7deb-5605-8567-d5d35f688230');assert.deepEqual(source.tasks_order,['task01','task02','task03']);});
|
||||
test('number, extension and storage evidence are explicit',()=>{const text=JSON.stringify(source);for(const token of ['10101101','sign extension','little-endian','78,56,34,12','VCD'])assert.match(text,new RegExp(token,'i'));});
|
||||
test('all task sources exist',async()=>{for(const name of ['task01_positional_encoding','task02_extension_shifts','task03_little_endian_memory'])await access(new URL(`../src/tasks/${name}.sv`,import.meta.url));});
|
||||
@@ -0,0 +1,67 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtemp, rm } from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
import { CardStateDatabase } from '../scripts/lib/card_state_db.mjs';
|
||||
|
||||
test('card state survives closing and reopening SQLite', async () => {
|
||||
const directory = await mkdtemp(path.join(os.tmpdir(), 'stem-card-state-'));
|
||||
const file = path.join(directory, 'state.sqlite3');
|
||||
const state = {
|
||||
schema: 'stem-card-navigation.v2',
|
||||
revision: 7,
|
||||
selected_at: '2026-07-17T12:00:00.000Z',
|
||||
snapshot_ref: 'task04.alloc5.cursor'
|
||||
};
|
||||
|
||||
try {
|
||||
const first = new CardStateDatabase(file);
|
||||
first.write('navigation', state);
|
||||
first.close();
|
||||
|
||||
const second = new CardStateDatabase(file);
|
||||
assert.deepEqual(second.read('navigation'), state);
|
||||
assert.equal(second.read('missing'), null);
|
||||
second.close();
|
||||
} finally {
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('audit log is append-only, ordered and keeps navigation timestamps', async () => {
|
||||
const directory = await mkdtemp(path.join(os.tmpdir(), 'stem-card-audit-'));
|
||||
const file = path.join(directory, 'state.sqlite3');
|
||||
try {
|
||||
const database = new CardStateDatabase(file);
|
||||
const navigation = {
|
||||
task: { id: 'task04' },
|
||||
block: { id: 'allocator-flow' },
|
||||
phase: { id: 'alloc5' },
|
||||
step: { id: 'alloc5-commit' },
|
||||
snapshot_ref: 'task04.alloc5.commit'
|
||||
};
|
||||
const first = database.appendEvent({
|
||||
eventType: 'navigation.activate',
|
||||
actor: 'nvim',
|
||||
navigation,
|
||||
occurredAt: '2026-07-17T12:00:00.000Z',
|
||||
payload: { source: 'F2' }
|
||||
});
|
||||
const second = database.appendEvent({
|
||||
eventType: 'checkpoint.ready',
|
||||
actor: 'nvim',
|
||||
navigation,
|
||||
occurredAt: '2026-07-17T12:00:01.000Z'
|
||||
});
|
||||
assert.equal(second.id, first.id + 1);
|
||||
assert.deepEqual(
|
||||
database.listEvents({ afterId: first.id, limit: 10 }).map(event => event.event_type),
|
||||
['checkpoint.ready']
|
||||
);
|
||||
assert.equal(database.listEvents()[0].snapshot_ref, navigation.snapshot_ref);
|
||||
database.close();
|
||||
} finally {
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Independent reference models for L01; no RTL source is imported."""
|
||||
import sys
|
||||
|
||||
|
||||
def task01():
|
||||
value = 173
|
||||
bits = format(value, "08b")
|
||||
weighted = sum(((value >> bit) & 1) << bit for bit in range(8))
|
||||
assert bits == "10101101" and format(value, "02X") == "AD" and weighted == value
|
||||
print("PASS task01 value=173 binary=10101101 hex=AD weighted=173")
|
||||
|
||||
|
||||
def task02():
|
||||
value = 0x80
|
||||
signed = value - 0x100 if value & 0x80 else value
|
||||
zext = value
|
||||
sext = signed
|
||||
lsr1 = value >> 1
|
||||
asr1 = signed >> 1
|
||||
assert (zext, sext, lsr1, asr1) == (128, -128, 64, -64)
|
||||
print("PASS task02 input=0x80 zext=128 sext=-128 lsr1=64 asr1=-64")
|
||||
|
||||
|
||||
def task03():
|
||||
word = 0x12345678
|
||||
memory = [(word >> (8 * index)) & 0xFF for index in range(4)]
|
||||
roundtrip = sum(byte << (8 * index) for index, byte in enumerate(memory))
|
||||
assert memory == [0x78, 0x56, 0x34, 0x12] and roundtrip == word
|
||||
print("PASS task03 word=0x12345678 bytes=78,56,34,12 endian=little roundtrip=1")
|
||||
|
||||
|
||||
TASKS = {"task01_positional_encoding": task01, "task02_extension_shifts": task02, "task03_little_endian_memory": task03}
|
||||
if len(sys.argv) != 2 or sys.argv[1] not in TASKS:
|
||||
raise SystemExit(f"usage: {sys.argv[0]} {'|'.join(TASKS)}")
|
||||
TASKS[sys.argv[1]]()
|
||||
@@ -0,0 +1,10 @@
|
||||
module tb;
|
||||
logic [7:0] value, bits; logic [3:0] hex_high, hex_low; logic [8:0] weighted_sum; string trace_file;
|
||||
positional_encoding dut(.*);
|
||||
initial begin
|
||||
if ($value$plusargs("trace=%s", trace_file)) begin $dumpfile(trace_file); $dumpvars(0, tb); end
|
||||
value = 8'hAD; #1;
|
||||
if (bits !== 8'b10101101 || hex_high !== 4'hA || hex_low !== 4'hD || weighted_sum !== 9'd173) $fatal(1, "encoding mismatch");
|
||||
$display("PASS task01 value=173 binary=10101101 hex=AD weighted=173"); $finish;
|
||||
end
|
||||
endmodule
|
||||
@@ -0,0 +1,10 @@
|
||||
module tb;
|
||||
logic [7:0] value; logic [2:0] shift; logic [31:0] zero_extended, sign_extended, logical_right, arithmetic_right; string trace_file;
|
||||
extension_shifts dut(.*);
|
||||
initial begin
|
||||
if ($value$plusargs("trace=%s", trace_file)) begin $dumpfile(trace_file); $dumpvars(0, tb); end
|
||||
value = 8'h80; shift = 3'd1; #1;
|
||||
if (zero_extended !== 32'd128 || $signed(sign_extended) !== -128 || logical_right !== 32'd64 || $signed(arithmetic_right) !== -64) $fatal(1, "extension/shift mismatch");
|
||||
$display("PASS task02 input=0x80 zext=128 sext=-128 lsr1=64 asr1=-64"); $finish;
|
||||
end
|
||||
endmodule
|
||||
@@ -0,0 +1,11 @@
|
||||
module tb;
|
||||
logic clk=0, reset, write_enable; logic [2:0] address; logic [31:0] write_word, read_word; logic [7:0] byte0, byte1, byte2, byte3; string trace_file;
|
||||
little_endian_memory dut(.*); always #5 clk = ~clk;
|
||||
initial begin
|
||||
if ($value$plusargs("trace=%s", trace_file)) begin $dumpfile(trace_file); $dumpvars(0, tb); end
|
||||
reset=1; write_enable=0; address=0; write_word=0; @(posedge clk); #1; reset=0;
|
||||
write_word=32'h12345678; write_enable=1; @(posedge clk); #1; write_enable=0;
|
||||
if ({byte3,byte2,byte1,byte0} !== 32'h12345678 || read_word !== 32'h12345678) $fatal(1, "little-endian mismatch");
|
||||
$display("PASS task03 word=0x12345678 bytes=78,56,34,12 endian=little roundtrip=1"); $finish;
|
||||
end
|
||||
endmodule
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"; mode=test
|
||||
if [[ "${1:-}" == --build-only || "${1:-}" == --trace ]]; then mode="${1#--}"; shift; fi
|
||||
task="${1:?task required}"
|
||||
case "$task" in task01_positional_encoding|task02_extension_shifts|task03_little_endian_memory) ;; *) exit 2 ;; esac
|
||||
state="${STEM_STATE_DIR:-$root/.stem/instances/local}"; profile="${STEM_PROFILE:-hazard3-sim}"; target="${STEM_TARGET:-hazard3-baremetal}"; build_dir="$state/build/rtl/$task"; artifact_dir="$root/.stem/artifacts/$profile/$target/$task"; binary="$build_dir/sim"
|
||||
mkdir -p "$build_dir" "$artifact_dir"
|
||||
args=(--binary --timing --assert -Wall -Wno-fatal -Wno-DECLFILENAME -Wno-BLKSEQ -Wno-UNUSEDSIGNAL -Wno-WIDTH --top-module tb --Mdir "$build_dir/obj" -o "$binary" "$root/src/tasks/$task.sv" "$root/tests/${task}_tb.sv")
|
||||
[[ "$mode" == trace ]] && args+=(--trace)
|
||||
verilator "${args[@]}" >/dev/null; cp -f "$root/src/tasks/$task.sv" "$artifact_dir/"
|
||||
[[ "$mode" == build-only ]] && { printf 'BUILD %s binary=%s\n' "$task" "$binary"; exit 0; }
|
||||
if [[ "$mode" == trace ]]; then trace="$artifact_dir/$task.vcd"; "$binary" "+trace=$trace"; test -s "$trace"; printf 'TRACE %s vcd=%s\n' "$task" "$trace"; else "$binary"; fi
|
||||
Reference in New Issue
Block a user