feat(L05): add ALU assembler and execute 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('L05 identity and three tasks are stable',()=>{assert.equal(source.card.number,'05');assert.equal(source.card.uuid,'aabf60d8-9071-5f77-9bbb-2fc500f05358');assert.deepEqual(source.tasks_order,['task01','task02','task03']);});
|
||||
test('ALU, encoding and writeback evidence are explicit',()=>{const text=JSON.stringify(source);for(const token of ['signed','002081b3','x3=12','writeback','VCD'])assert.match(text,new RegExp(token,'i'));for(const task of Object.values(source.tasks))assert.equal(task.assessment_criterion_ref,'RV05.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 u32(value: int) -> int: return value & 0xFFFFFFFF
|
||||
def s32(value: int) -> int: value &= 0xFFFFFFFF; return value - 0x100000000 if value & 0x80000000 else value
|
||||
|
||||
|
||||
def task01() -> None:
|
||||
a, b = 0x80000000, 1
|
||||
results = [u32(7 + 5), u32(7 - 5), 0xA5 & 0x3C, 0xA5 | 0x3C, 0xA5 ^ 0x3C,
|
||||
u32(1 << 4), 0x80000000 >> 4, u32(s32(0x80000000) >> 4), int(s32(a) < s32(b)), int(a < b)]
|
||||
assert results == [12, 2, 0x24, 0xBD, 0x99, 16, 0x08000000, 0xF8000000, 1, 0]
|
||||
print("PASS task01 ops=10 signed_lt=1 unsigned_lt=0 sra=f8000000")
|
||||
|
||||
|
||||
def task02() -> None:
|
||||
add = (2 << 20) | (1 << 15) | (3 << 7) | 0x33
|
||||
addi = (0xFFC << 20) | (6 << 15) | (5 << 7) | 0x13
|
||||
assert (add, addi) == (0x002081B3, 0xFFC30293)
|
||||
print("PASS task02 add=002081b3 addi_neg4=ffc30293")
|
||||
|
||||
|
||||
def task03() -> None:
|
||||
regs = [0] * 32
|
||||
regs[1] = 7; regs[2] = 5; regs[3] = regs[1] + regs[2]; regs[4] = regs[3] - regs[2]
|
||||
assert regs[1:5] == [7, 5, 12, 7]
|
||||
print("PASS task03 instructions=4 x1=7 x2=5 x3=12 x4=7")
|
||||
|
||||
|
||||
TASKS = {"task01_alu_core": task01, "task02_verilog_assembler": task02, "task03_execute_stage": 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,13 @@
|
||||
module tb;
|
||||
logic [31:0] operand_a, operand_b, result; logic [3:0] operation; logic zero; integer checks=0; string trace_file;
|
||||
alu_core dut (.*);
|
||||
task automatic check(input logic [3:0] op, input logic [31:0] a, b, expected);
|
||||
operation=op; operand_a=a; operand_b=b; #1; if (result !== expected) $fatal(1,"FAIL task01 op=%0d got=%h expected=%h",op,result,expected); checks+=1;
|
||||
endtask
|
||||
initial begin
|
||||
if ($value$plusargs("trace=%s",trace_file)) begin $dumpfile(trace_file); $dumpvars(0,tb); end
|
||||
check(0,7,5,12); check(1,7,5,2); check(2,'ha5,'h3c,'h24); check(3,'ha5,'h3c,'hbd); check(4,'ha5,'h3c,'h99);
|
||||
check(5,1,4,16); check(6,'h80000000,4,'h08000000); check(7,'h80000000,4,'hf8000000); check(8,'h80000000,1,1); check(9,'h80000000,1,0);
|
||||
$display("PASS task01 ops=%0d signed_lt=1 unsigned_lt=0 sra=f8000000",checks); $finish;
|
||||
end
|
||||
endmodule
|
||||
@@ -0,0 +1,10 @@
|
||||
module tb;
|
||||
logic format_i; logic [4:0] rd,rs1,rs2; logic [2:0] funct3; logic [6:0] funct7; logic signed [11:0] immediate; logic [31:0] instruction; string trace_file;
|
||||
verilog_assembler dut (.*);
|
||||
initial begin
|
||||
if ($value$plusargs("trace=%s",trace_file)) begin $dumpfile(trace_file); $dumpvars(0,tb); end
|
||||
format_i=0; rd=3; rs1=1; rs2=2; funct3=0; funct7=0; immediate=0; #1; if(instruction!==32'h002081b3)$fatal(1,"FAIL add=%h",instruction);
|
||||
format_i=1; rd=5; rs1=6; rs2=0; funct3=0; funct7=0; immediate=-4; #1; if(instruction!==32'hffc30293)$fatal(1,"FAIL addi=%h",instruction);
|
||||
$display("PASS task02 add=002081b3 addi_neg4=ffc30293"); $finish;
|
||||
end
|
||||
endmodule
|
||||
@@ -0,0 +1,15 @@
|
||||
module tb;
|
||||
logic [31:0] instruction,rs1_value,rs2_value,result; logic [4:0] rd; logic write_enable,illegal; logic [31:0] registers[0:31]; string trace_file;
|
||||
execute_stage dut (.*);
|
||||
task automatic execute_one(input logic [31:0] encoded);
|
||||
instruction=encoded; rs1_value=registers[encoded[19:15]]; rs2_value=registers[encoded[24:20]]; #1;
|
||||
if(illegal)$fatal(1,"FAIL illegal instruction=%h",encoded); if(write_enable) registers[rd]=result;
|
||||
endtask
|
||||
initial begin
|
||||
if ($value$plusargs("trace=%s",trace_file)) begin $dumpfile(trace_file); $dumpvars(0,tb); end
|
||||
for(integer i=0;i<32;i+=1)registers[i]=0;
|
||||
execute_one(32'h00700093); execute_one(32'h00500113); execute_one(32'h002081b3); execute_one(32'h40218233);
|
||||
if(registers[1]!==7||registers[2]!==5||registers[3]!==12||registers[4]!==7)$fatal(1,"FAIL task03 registers");
|
||||
$display("PASS task03 instructions=4 x1=7 x2=5 x3=12 x4=7"); $finish;
|
||||
end
|
||||
endmodule
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/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_alu_core|task02_verilog_assembler|task03_execute_stage) ;; *) 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