feat(L07): add addresses and memory card
This commit is contained in:
@@ -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');});
|
||||
@@ -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
+33
@@ -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]]()
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Executable
+19
@@ -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
|
||||
Reference in New Issue
Block a user