feat(L07): add addresses and memory card

This commit is contained in:
user
2026-07-21 17:14:02 +02:00
commit 529e720f5d
42 changed files with 7220 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
import assert from 'node:assert/strict'; import { 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('L07 identity and three tasks are stable',()=>{assert.equal(source.card.number,'07');assert.equal(source.card.uuid,'4edb5d8a-fc39-5f96-b462-cb421586ef68');assert.deepEqual(source.tasks_order,['task01','task02','task03']);});
test('address, memory, alignment and waveform evidence are explicit',()=>{const text=JSON.stringify(source);for(const token of ['AUIPC','address','RAM','fault','VCD'])assert.match(text,new RegExp(token,'i'));for(const task of Object.values(source.tasks))assert.equal(task.assessment_criterion_ref,'RV07.KW01');});
+67
View File
@@ -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 });
}
});
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
import sys
def task01():
pc, imm20 = 0x100, 0x12345
upper = imm20 << 12
assert upper == 0x12345000 and (pc + upper) & 0xFFFFFFFF == 0x12345100
print("PASS task01 lui=12345000 auipc=12345100")
def task02():
ram = [0] * 16
ram[0], ram[1] = 0x11223344, 0xAABBCCDD
before = list(ram)
misaligned = (2 & 3) != 0
assert ram[0] == 0x11223344 and ram[1] == 0xAABBCCDD and misaligned and ram == before
print("PASS task02 word0=11223344 word1=aabbccdd misaligned=1")
def task03():
base, immediate = 0x20, 12
address = (base + immediate) & 0xFFFFFFFF
ram = {address: 0xDEADBEEF}
fault = ((base + 2) & 3) != 0
assert address == 0x2C and ram[address] == 0xDEADBEEF and fault
print("PASS task03 address=0000002c load=deadbeef fault=1")
TASKS = {"task01_upper_immediate": task01, "task02_word_ram": task02, "task03_address_memory_path": 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]]()
+16
View File
@@ -0,0 +1,16 @@
module tb;
logic [31:0] pc, result;
logic [19:0] imm20;
logic auipc;
string trace_file;
upper_immediate dut (.*);
initial begin
if ($value$plusargs("trace=%s", trace_file)) begin $dumpfile(trace_file); $dumpvars(0, tb); end
pc = 32'h100; imm20 = 20'h12345; auipc = 0; #1;
if (result !== 32'h12345000) $fatal(1, "FAIL lui=%h", result);
auipc = 1; #1;
if (result !== 32'h12345100) $fatal(1, "FAIL auipc=%h", result);
$display("PASS task01 lui=12345000 auipc=12345100");
$finish;
end
endmodule
+25
View File
@@ -0,0 +1,25 @@
module tb;
logic clk = 0, request = 0, write_enable = 0;
logic [31:0] byte_address = 0, write_data = 0, read_data;
logic aligned, fault;
string trace_file;
word_ram dut (.*);
always #1 clk = ~clk;
task automatic write_word(input logic [31:0] address, input logic [31:0] data);
@(negedge clk); request=1; write_enable=1; byte_address=address; write_data=data;
@(posedge clk); #1;
endtask
initial begin
if ($value$plusargs("trace=%s", trace_file)) begin $dumpfile(trace_file); $dumpvars(0, tb); end
write_word(0, 32'h11223344); write_word(4, 32'haabbccdd);
@(negedge clk); write_enable=0; byte_address=0; #1;
if (read_data !== 32'h11223344 || !aligned || fault) $fatal(1, "FAIL word0=%h", read_data);
byte_address=4; #1; if (read_data !== 32'haabbccdd) $fatal(1, "FAIL word1=%h", read_data);
byte_address=2; write_enable=1; write_data=32'hffffffff; #1;
if (!fault || aligned) $fatal(1, "FAIL misaligned flags");
@(posedge clk); #1; write_enable=0; byte_address=0; #1;
if (read_data !== 32'h11223344) $fatal(1, "FAIL misaligned modified memory");
$display("PASS task02 word0=11223344 word1=aabbccdd misaligned=1");
$finish;
end
endmodule
+19
View File
@@ -0,0 +1,19 @@
module tb;
logic clk = 0, request = 0, write_enable = 0;
logic [31:0] base = 0, write_data = 0, effective_address, read_data;
logic signed [31:0] immediate = 0;
logic fault;
string trace_file;
address_memory_path dut (.*);
always #1 clk = ~clk;
initial begin
if ($value$plusargs("trace=%s", trace_file)) begin $dumpfile(trace_file); $dumpvars(0, tb); end
@(negedge clk); request=1; write_enable=1; base=32'h20; immediate=12; write_data=32'hdeadbeef;
@(posedge clk); #1;
if (effective_address !== 32'h2c || fault) $fatal(1, "FAIL address=%h fault=%b", effective_address, fault);
write_enable=0; #1; if (read_data !== 32'hdeadbeef) $fatal(1, "FAIL load=%h", read_data);
immediate=2; #1; if (!fault || read_data !== 0) $fatal(1, "FAIL fault=%b data=%h", fault, read_data);
$display("PASS task03 address=0000002c load=deadbeef fault=1");
$finish;
end
endmodule
+19
View File
@@ -0,0 +1,19 @@
#!/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 name is required}"
case "$task" in task01_upper_immediate|task02_word_ram|task03_address_memory_path) ;; *) printf 'Unknown RTL task: %s\n' "$task" >&2; 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"
verilator --binary --timing --assert --trace -Wall -Wno-fatal -Wno-DECLFILENAME -Wno-BLKSEQ -Wno-UNUSEDSIGNAL --top-module tb --Mdir "$build_dir/obj" -o "$binary" "$root/src/tasks/$task.sv" "$root/tests/${task}_tb.sv" >/dev/null
cp -f "$root/src/tasks/$task.sv" "$artifact_dir/"
[[ "$mode" == build-only ]] && { printf 'BUILD %s binary=%s\n' "$task" "$binary"; exit 0; }
trace_file="$artifact_dir/$task.vcd"
if [[ "$mode" == trace ]]; then "$binary" "+trace=$trace_file"; test -s "$trace_file"; printf 'TRACE %s vcd=%s\n' "$task" "$trace_file"; else "$binary"; fi