feat(L09): add load and store semantics card

This commit is contained in:
user
2026-07-21 17:26:16 +02:00
commit 4f5fd8f64e
42 changed files with 7289 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('L09 identity and three tasks are stable',()=>{assert.equal(source.card.number,'09');assert.equal(source.card.uuid,'f1efc445-93bd-5c0b-b531-52fc5a9ba943');assert.deepEqual(source.tasks_order,['task01','task02','task03']);});
test('byte lanes, endian, extension and waveform evidence are explicit',()=>{const text=JSON.stringify(source);for(const token of ['strobe','little-endian','signed','fault','VCD'])assert.match(text,new RegExp(token,'i'));for(const task of Object.values(source.tasks))assert.equal(task.assessment_criterion_ref,'RV09.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 });
}
});
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env python3
import sys
def task01():
assert (1 << 3) == 0b1000 and (0b11 << 2) == 0b1100
assert ((0xAA << 24) & 0xFFFFFFFF) == 0xAA000000
assert ((0xBEEF << 16) & 0xFFFFFFFF) == 0xBEEF0000
print("PASS task01 sb_strobe=1000 sb_data=aa000000 sh_strobe=1100 sh_data=beef0000 sw_strobe=1111 fault=1")
def sign_extend(value, bits):
return (value | (~0 << bits)) & 0xFFFFFFFF if value & (1 << (bits - 1)) else value
def task02():
word = 0x80FF7F01
assert sign_extend((word >> 24) & 0xFF, 8) == 0xFFFFFF80
assert ((word >> 24) & 0xFF) == 0x80
assert sign_extend((word >> 16) & 0xFFFF, 16) == 0xFFFF80FF
print("PASS task02 lb=ffffff80 lbu=00000080 lh=ffff80ff lhu=000080ff")
def task03():
word = 0x11223344
word = (word & ~(0xFF << 8)) | (0xAA << 8)
assert word == 0x1122AA44 and sign_extend(0xAA, 8) == 0xFFFFFFAA
print("PASS task03 word=1122aa44 lb=ffffffaa lbu=000000aa fault=1")
TASKS = {"task01_store_lanes": task01, "task02_load_extend": task02, "task03_load_store_unit": 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]]()
+21
View File
@@ -0,0 +1,21 @@
module tb;
logic [1:0] address_low;
logic [2:0] funct3;
logic [31:0] store_value, bus_write_data;
logic [3:0] write_strobe;
logic fault;
string trace_file;
store_lanes dut (.*);
initial begin
if ($value$plusargs("trace=%s", trace_file)) begin $dumpfile(trace_file); $dumpvars(0, tb); end
address_low=3; funct3=0; store_value=32'haa; #1;
if (write_strobe!==4'b1000 || bus_write_data!==32'haa000000 || fault) $fatal(1,"FAIL SB");
address_low=2; funct3=1; store_value=32'hbeef; #1;
if (write_strobe!==4'b1100 || bus_write_data!==32'hbeef0000 || fault) $fatal(1,"FAIL SH");
address_low=0; funct3=2; store_value=32'h12345678; #1;
if (write_strobe!==4'b1111 || bus_write_data!==store_value || fault) $fatal(1,"FAIL SW");
address_low=1; funct3=1; #1; if (!fault || write_strobe!==0) $fatal(1,"FAIL misaligned");
$display("PASS task01 sb_strobe=1000 sb_data=aa000000 sh_strobe=1100 sh_data=beef0000 sw_strobe=1111 fault=1");
$finish;
end
endmodule
+18
View File
@@ -0,0 +1,18 @@
module tb;
logic [31:0] memory_word, read_data;
logic [1:0] address_low;
logic [2:0] funct3;
logic fault;
string trace_file;
load_extend dut (.*);
initial begin
if ($value$plusargs("trace=%s", trace_file)) begin $dumpfile(trace_file); $dumpvars(0, tb); end
memory_word=32'h80ff7f01; address_low=3; funct3=0; #1; if(read_data!==32'hffffff80||fault)$fatal(1,"FAIL LB %h",read_data);
funct3=4; #1; if(read_data!==32'h80||fault)$fatal(1,"FAIL LBU %h",read_data);
address_low=2; funct3=1; #1; if(read_data!==32'hffff80ff||fault)$fatal(1,"FAIL LH %h",read_data);
funct3=5; #1; if(read_data!==32'h80ff||fault)$fatal(1,"FAIL LHU %h",read_data);
address_low=0; funct3=2; #1; if(read_data!==memory_word||fault)$fatal(1,"FAIL LW %h",read_data);
$display("PASS task02 lb=ffffff80 lbu=00000080 lh=ffff80ff lhu=000080ff");
$finish;
end
endmodule
+24
View File
@@ -0,0 +1,24 @@
module tb;
logic clk=0, request=0, write_enable=0;
logic [31:0] address=0, write_data=0, read_data;
logic [2:0] funct3=0;
logic fault;
string trace_file;
load_store_unit dut (.*);
always #1 clk=~clk;
task automatic store(input logic [31:0] addr, input logic [2:0] size, input logic [31:0] data);
@(negedge clk); request=1; write_enable=1; address=addr; funct3=size; write_data=data;
@(posedge clk); #1;
endtask
initial begin
if ($value$plusargs("trace=%s", trace_file)) begin $dumpfile(trace_file); $dumpvars(0, tb); end
store(0,2,32'h11223344); store(1,0,32'haa);
@(negedge clk); write_enable=0; address=0; funct3=2; #1;
if(read_data!==32'h1122aa44||fault)$fatal(1,"FAIL word=%h",read_data);
address=1; funct3=0; #1; if(read_data!==32'hffffffaa||fault)$fatal(1,"FAIL LB=%h",read_data);
funct3=4; #1; if(read_data!==32'haa||fault)$fatal(1,"FAIL LBU=%h",read_data);
address=2; funct3=2; #1; if(!fault||read_data!==0)$fatal(1,"FAIL fault=%b data=%h",fault,read_data);
$display("PASS task03 word=1122aa44 lb=ffffffaa lbu=000000aa 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_store_lanes|task02_load_extend|task03_load_store_unit) ;; *) 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