some primitive MONitor thing

This commit is contained in:
minjaesong
2025-04-29 20:44:33 +09:00
parent a7df32378c
commit 8f6a82984e
7 changed files with 301 additions and 57 deletions

View File

@@ -0,0 +1,200 @@
object Random {
fun uniformRand(low: Int, high: Int) = (Math.random() * (high + 1)).toInt()
fun triangularRand(low: Float, high: Float): Float {
val a = (Math.random() + Math.random()) / 2.0
return ((high - low) * a + low).toFloat()
}
fun gaussianRand(avg: Float, stddev: Float): Float {
// Box-Muller transform to generate random numbers with standard normal distribution
// This implementation uses the polar form for better efficiency
// We need two uniform random values between 0 and 1
val random = kotlin.random.Random
// Using the polar form of the Box-Muller transformation
var u: Double
var v: Double
var s: Double
do {
// Generate two uniform random numbers between -1 and 1
u = Math.random() * 2 - 1
v = Math.random() * 2 - 1
// Calculate sum of squares
s = u * u + v * v
} while (s >= 1 || s == 0.0)
// Calculate polar transformation
val multiplier = kotlin.math.sqrt(-2.0 * kotlin.math.ln(s) / s)
// Transform to the desired mean and standard deviation
// We only use one of the two generated values here
return (avg + stddev * u * multiplier).toFloat()
}
}
sealed class SeekSimulator {
abstract fun computeSeekTime(currentSector: Int, targetSector: Int): Float
class Tape(
val totalSectors: Int,
val tapeLengthMeters: Float = 200f,
val baseSeekTime: Float = 0.5f, // seconds base inertia
val tapeSpeedMetersPerSec: Float = 2.0f, // normal speed
) : SeekSimulator() {
override fun computeSeekTime(currentSector: Int, targetSector: Int): Float {
val posCurrent = (currentSector.toFloat() / totalSectors) * tapeLengthMeters
val posTarget = (targetSector.toFloat() / totalSectors) * tapeLengthMeters
val distance = kotlin.math.abs(posTarget - posCurrent)
// Inject random tape jitter
val effectiveSpeed = tapeSpeedMetersPerSec * Random.triangularRand(0.9f, 1.1f)
return baseSeekTime + (distance / effectiveSpeed)
}
}
class Disc(
val totalTracks: Int,
val armSeekBaseTime: Float = 0.005f, // fast seek, seconds
val armSeekMultiplier: Float = 0.002f, // slower for bigger jumps
val rotationLatencyAvg: Float = 0.008f, // seconds (half-rotation average)
) : SeekSimulator() {
override fun computeSeekTime(currentSector: Int, targetSector: Int): Float {
val cylCurrent = sectorToTrack(currentSector)
val cylTarget = sectorToTrack(targetSector)
val deltaTracks = kotlin.math.abs(cylTarget - cylCurrent)
val armSeek = armSeekBaseTime + (armSeekMultiplier * kotlin.math.sqrt(deltaTracks.toFloat()))
val rotationLatency = rotationLatencyAvg//Random.gaussianRand(rotationLatencyAvg, rotationLatencyAvg * 0.2f)
return armSeek + rotationLatency
}
private fun sectorToTrack(sector: Int): Int {
// Simplistic assumption: sector layout maps 1:1 to track at this level
return sector % totalTracks
}
}
class Drum(
val rpm: Float = 3000f
) : SeekSimulator() {
override fun computeSeekTime(currentSector: Int, targetSector: Int): Float {
val degreesPerSector = 360.0f / 10000.0f // Assume 10k sectors per drum circumference
val angleCurrent = currentSector * degreesPerSector
val angleTarget = targetSector * degreesPerSector
val deltaAngle = kotlin.math.abs(angleTarget - angleCurrent) % 360f
val rotationLatencySeconds = (deltaAngle / 360f) * (60f / rpm)
// Add a little mechanical jitter
val jitteredLatency = rotationLatencySeconds * Random.triangularRand(0.95f, 1.05f)
return jitteredLatency
}
}
}
class SeekLatencySampler(
val simulator: SeekSimulator,
val totalSectors: Int,
val sampleCount: Int = 10000
) {
data class Sample(val fromSector: Int, val toSector: Int, val latency: Float)
val samples = mutableListOf<Sample>()
fun runSampling() {
samples.clear()
var lastSector = Random.uniformRand(0, totalSectors - 1)
repeat(sampleCount) {
val nextSector = Random.uniformRand(0, totalSectors - 1)
val latency = simulator.computeSeekTime(lastSector, nextSector)
samples.add(Sample(lastSector, nextSector, latency))
lastSector = nextSector
}
}
fun analyzeAndPrint() {
if (samples.isEmpty()) {
println("No samples generated. Run runSampling() first.")
return
}
val latencies = samples.map { it.latency }
val minLatency = latencies.minOrNull() ?: 0f
val maxLatency = latencies.maxOrNull() ?: 0f
val avgLatency = latencies.average().toFloat()
val stddevLatency = kotlin.math.sqrt(latencies.map { (it - avgLatency).let { diff -> diff * diff } }.average()).toFloat()
println("=== Seek Latency Stats ===")
println("Samples: $sampleCount")
println("Min: ${"%.4f".format(minLatency)} s")
println("Max: ${"%.4f".format(maxLatency)} s")
println("Avg: ${"%.4f".format(avgLatency)} s")
println("Stddev: ${"%.4f".format(stddevLatency)} s")
printSimpleHistogram(latencies)
}
private fun printSimpleHistogram(latencies: List<Float>, bins: Int = 30) {
val min = latencies.minOrNull() ?: return
val max = latencies.maxOrNull() ?: return
val binSize = (max - min) / bins
val histogram = IntArray(bins) { 0 }
latencies.forEach { latency ->
val bin = kotlin.math.min(((latency - min) / binSize).toInt(), bins - 1)
histogram[bin]++
}
println("--- Latency Distribution ---")
histogram.forEachIndexed { index, count ->
val lower = min + binSize * index
val upper = lower + binSize
val bar = "#".repeat(count / (sampleCount / 200)) // Scale bar length
println("${"%.4f".format(lower)} - ${"%.4f".format(upper)} s: $bar")
}
}
}
fun main() {
val tapeSimulator = SeekSimulator.Tape(
totalSectors = 100000,
tapeLengthMeters = 200f,
baseSeekTime = 0.2f,
tapeSpeedMetersPerSec = 5.0f
)
val discSimulator = SeekSimulator.Disc(
totalTracks = 3810,
armSeekBaseTime = 0.005f,
armSeekMultiplier = 0.002f,
rotationLatencyAvg = 0.008f
)
val drumSimulator = SeekSimulator.Drum(
rpm = 3000f
)
listOf(tapeSimulator, discSimulator, drumSimulator).forEach { sim ->
SeekLatencySampler(
simulator = sim,
totalSectors = 100000,
sampleCount = 5000
).also {
it.runSampling()
it.analyzeAndPrint()
}
}
}

View File

@@ -9,6 +9,7 @@ M (hex addr) : shows a byte on the address
N : Increment the memory pointer, then show the byte on the address N : Increment the memory pointer, then show the byte on the address
W (hex string) : write given byte string to the memory starting from the current pointer. If the length of the W (hex string) : write given byte string to the memory starting from the current pointer. If the length of the
hex string is odd, error will be thrown. The pointer will auto-increment. hex string is odd, error will be thrown. The pointer will auto-increment.
P : prints current pointer
Prompt on successful: . Prompt on successful: .
Prompt on error: ? Prompt on error: ?
@@ -16,86 +17,125 @@ Prompt on error: ?
*/ */
let uhex = (i, len) => { return (i >>> 0).toString(16).toUpperCase().padStart(len||2, '0').slice(-(len||2)) } let uhex = (i, t) => (i >>> 0).toString(16).toUpperCase().padStart(t||2, '0').slice(-(t||2))
sys.sleep(256)
println(`/MONITOR/ ${sys.maxmem()} BYTES SYSTEM`) println(`/MONITOR/ ${sys.maxmem()} BYTES SYSTEM`)
let prompt = ['?','.'] let prompt = ['?','.']
let mode = "M" let P = 0
let ptr = 0 let pE = undefined
let previousError = undefined
let peek = (p) => {
// TODO add open bus behaviour
try {
return sys.peek(p)
}
catch (e) {
return (p & 0xFFFFFF) >>> 16
}
}
while (1) { while (1) {
print(prompt[+!previousError]) print(prompt[+!pE])
let buf = read().split(' ') let buf = read().split(' ')
let cmd = buf[0].toUpperCase() let cmd = buf[0].toUpperCase()
let putNinc = b => { if (P >= 0) { sys.poke(P++, b) } else { sys.poke(P--, b) } }
let getNinc = () => { if (P >= 0) { return sys.peek(P++) } else { return sys.poke(P--) } }
if ("M" == cmd) { if ("M" == cmd) {
let addr = parseInt(buf[1], 16) let addr = parseInt(buf[1], 16)
let addr2 = parseInt(buf[2], 16) let addr2 = parseInt(buf[2], 16)
if (Number.isNaN(addr)) { if (Number.isNaN(addr)) {
println((uhex(ptr, 4))+' : '+uhex(sys.peek(ptr))) println((uhex(P, 6))+' : '+uhex(peek(P)))
previousError = undefined pE = undefined
} }
else { else {
let oldptr = ptr let oldP = P
ptr = addr P = addr
if (Number.isNaN(addr2)) { if (Number.isNaN(addr2)) {
println(uhex(sys.peek(ptr))) println(uhex(peek(P)))
previousError = undefined pE = undefined
} }
else if (Math.abs(addr2) <= Math.abs(addr)) else if (Math.abs(addr2) <= Math.abs(addr))
previousError = "Range error: end is greater than start" pE = "Range error: end is greater than start"
else { else {
for (let i = 0; i <= Math.abs(addr2) - Math.abs(addr); i++) { for (let i = 0; i <= Math.abs(addr2) - Math.abs(addr); i++) {
if (i % 16 == 0 && i > 0) { println() } if (i % 16 == 0 && i > 0) { println() }
if (i % 16 == 0) { print((uhex(ptr, 4))+' : ') } if (i % 16 == 0) { print((uhex(P, 6))+' : ') }
print(uhex(sys.peek(ptr)) + ' ') print(uhex(peek(P)) + ' ')
if (addr < 0 && addr2 < 0) { ptr-- } else { ptr++ } if (addr < 0 && addr2 < 0) { P-- } else { P++ }
} }
println() println()
previousError = undefined pE = undefined
} }
ptr = oldptr P = oldP
} }
} }
else if ("N" == cmd) { else if ("N" == cmd) {
ptr++ if (P >= 0) { P++ } else { P-- }
println((uhex(ptr, 4))+' : '+uhex(sys.peek(ptr))) println((uhex(P, 6))+' : '+uhex(peek(P)))
previousError = undefined pE = undefined
} }
else if ("J" == cmd) { else if ("J" == cmd) {
let addr = parseInt(buf[1], 16) let addr = parseInt(buf[1], 16)
if (Number.isNaN(addr)) if (Number.isNaN(addr))
previousError = "Jump address unspecified" pE = "Jump address unspecified"
else { else {
ptr = addr P = addr
previousError = undefined pE = undefined
} }
} }
else if ("P" == cmd) {
println(` ${P} ($${uhex(P, 6)})`)
pE = undefined
}
else if ("W" == cmd) { else if ("W" == cmd) {
let arg = buf[1] let arg = buf[1]
if (arg == undefined) if (arg == undefined)
previousError = "No arguments given" pE = "No arguments given"
else if (arg.length % 2 == 1) else if (arg.length % 2 == 1)
previousError = "Length of byte string is odd number" pE = "Length of byte string is odd number"
else { else {
for (let i = 0; i < arg.length; i += 2) { for (let i = 0; i < arg.length; i += 2) {
let b = parseInt(arg.charAt(i)+arg.charAt(i+1), 16) let b = parseInt(arg.charAt(i)+arg.charAt(i+1), 16)
sys.poke(ptr++, b) putNinc(b)
} }
previousError = undefined pE = undefined
} }
} }
else if ("R" == cmd) {
// parse and run CUM image
// 0xA5 [payload size in 24 bit] [payload]
let hdr = sys.peek(P)
if (hdr != 0xA5) {
pE = "Image is not executable"
}
else {
// try {
let src = sys.toObjectCode(P)
pE = new Function(src)()
// }
// catch (e) {
// pE = e
// serial.printerr(e)
// }
}
}
else if ('' == cmd) { else if ('' == cmd) {
// do nothing // do nothing
} }
else if ('?' == cmd) { else if ('?' == cmd) {
println(previousError) println(pE)
} }
else { else {
previousError = "Unknown command" pE = "Unknown command"
} }
} }

View File

@@ -1259,14 +1259,15 @@ const moduleCache = {}
// install other stuffs // install other stuffs
// js source-based impl // js source-based impl
/*var require = (absdir) => { var require = (absdir) => {
let moduleFile = files.open(absdir) let moduleFile = files.open(absdir)
if (!moduleFile.exists) throw Error("No such file: " + absdir) if (!moduleFile.exists) throw Error("No such file: " + absdir)
let moduleScript = moduleFile.sread() let moduleScript = moduleFile.sread()
var intchkFunName = `tvdosSIGTERM_${generateRandomHashStr(16)}`; var intchkFunName = `tvdosSIGTERM_${generateRandomHashStr(16)}`;
return eval(`var ${intchkFunName} = function(){ ${checkTerm} };let exports = {}; ${injectIntChk(moduleScript, intchkFunName)}; Object.freeze(exports)`) return eval(`var ${intchkFunName} = function(){ ${checkTerm} };let exports = {}; ${injectIntChk(moduleScript, intchkFunName)}; Object.freeze(exports)`)
}*/ }
// COCC-based impl
// COCC-based impl helper functions
let loadImgToMem = (str) => { let loadImgToMem = (str) => {
let intent = str.charCodeAt(4) // 2 for executable, 3 for shared let intent = str.charCodeAt(4) // 2 for executable, 3 for shared
let addrToLoad = (str.charCodeAt(5) << 16) | (str.charCodeAt(6) << 8) | (str.charCodeAt(7)) let addrToLoad = (str.charCodeAt(5) << 16) | (str.charCodeAt(6) << 8) | (str.charCodeAt(7))
@@ -1284,27 +1285,6 @@ let loadImgToMem = (str) => {
return addrToLoad return addrToLoad
} }
var require = (absdir) => {
let moduleFile = files.open(absdir)
if (!moduleFile.exists) throw Error("No such file: " + absdir)
let moduleScript = moduleFile.sread()
let moduleName = absdir.split("\\").last().substringBeforeLast(".")
// load the "string" into memory
let ptr = loadImgToMem(moduleScript)
let intent = sys.peek(ptr + 4)
// if it's a shared library, put it into the global table
if (3 == intent) {
// create the table if it's not there
if (!_G.SO)
_G.SO = {}
_G.SO[moduleName] = ptr
}
requireFromMemory(ptr)
}
let requireFromMemory = (ptr) => { let requireFromMemory = (ptr) => {
if (moduleCache[ptr]) return moduleCache[ptr] if (moduleCache[ptr]) return moduleCache[ptr]
@@ -1328,6 +1308,28 @@ let requireFromMemory = (ptr) => {
moduleCache[moduleId] = moduleRunner moduleCache[moduleId] = moduleRunner
return moduleRunner return moduleRunner
} }
// COCC-based impl
/*var require = (absdir) => {
let moduleFile = files.open(absdir)
if (!moduleFile.exists) throw Error("No such file: " + absdir)
let moduleScript = moduleFile.sread()
let moduleName = absdir.split("\\").last().substringBeforeLast(".")
// load the "string" into memory
let ptr = loadImgToMem(moduleScript)
let intent = sys.peek(ptr + 4)
// if it's a shared library, put it into the global table
if (3 == intent) {
// create the table if it's not there
if (!_G.SO)
_G.SO = {}
_G.SO[moduleName] = ptr
}
requireFromMemory(ptr)
}*/
var GL = require("A:/tvdos/include/gl.mjs") var GL = require("A:/tvdos/include/gl.mjs")

View File

@@ -44,7 +44,8 @@ const EXEC_FUNS = {
"pcm": (f) => _G.shell.execute(`playpcm "${f}" -i`), "pcm": (f) => _G.shell.execute(`playpcm "${f}" -i`),
"ipf1": (f) => _G.shell.execute(`decodeipf "${f}" -i`), "ipf1": (f) => _G.shell.execute(`decodeipf "${f}" -i`),
"ipf2": (f) => _G.shell.execute(`decodeipf "${f}" -i`), "ipf2": (f) => _G.shell.execute(`decodeipf "${f}" -i`),
"bas": (f) => _G.shell.execute(`basic "${f}"`) "bas": (f) => _G.shell.execute(`basic "${f}"`),
"txt": (f) => _G.shell.execute(`less "${f}"`)
} }
let windowMode = 0 // 0 == left, 1 == right let windowMode = 0 // 0 == left, 1 == right

View File

@@ -12,7 +12,7 @@ import kotlin.math.ceil
object SerialHelper { object SerialHelper {
private const val SLEEP_TIME = 4L private const val SLEEP_TIME = 1L
fun sendMessageGetBytes(vm: VM, portNo: Int, message: ByteArray): ByteArray { fun sendMessageGetBytes(vm: VM, portNo: Int, message: ByteArray): ByteArray {
sendMessage(vm, portNo, message) sendMessage(vm, portNo, message)

File diff suppressed because one or more lines are too long

View File

@@ -43,7 +43,8 @@ public class AppLoader {
// VM vm = new VM("./assets", 64 << 10, new TheRealWorld(), new VMProgramRom[]{OEMBios.INSTANCE, BasicRom.INSTANCE}, 8, watchdogs); // VM vm = new VM("./assets", 64 << 10, new TheRealWorld(), new VMProgramRom[]{OEMBios.INSTANCE, BasicRom.INSTANCE}, 8, watchdogs);
// VM vm = new VM("./assets", 64 << 10, new TheRealWorld(), new VMProgramRom[]{TandemBios.INSTANCE, BasicRom.INSTANCE}, 2, watchdogs); // VM vm = new VM("./assets", 64 << 10, new TheRealWorld(), new VMProgramRom[]{TandemBios.INSTANCE, BasicRom.INSTANCE}, 2, watchdogs);
// VM vm = new VM("./assets", 128 << 10, new TheRealWorld(), new VMProgramRom[]{BasicBios.INSTANCE, WPBios.INSTANCE}, 2, watchdogs); // VM vm = new VM("./assets", 128 << 10, new TheRealWorld(), new VMProgramRom[]{BasicBios.INSTANCE, WPBios.INSTANCE}, 2, watchdogs);
VM vm = new VM("./assets", 8192 << 10, new TheRealWorld(), new VMProgramRom[]{TsvmBios.INSTANCE}, 8, watchdogs); // VM vm = new VM("./assets", 8192 << 10, new TheRealWorld(), new VMProgramRom[]{TsvmBios.INSTANCE}, 8, watchdogs);
VM vm = new VM("./assets", 8192 << 10, new TheRealWorld(), new VMProgramRom[]{Mon.INSTANCE}, 8, watchdogs);
// VM vm = new VM("./assets", 8192 << 10, new TheRealWorld(), new VMProgramRom[]{OpenBios.INSTANCE}, 8, watchdogs); // VM vm = new VM("./assets", 8192 << 10, new TheRealWorld(), new VMProgramRom[]{OpenBios.INSTANCE}, 8, watchdogs);
// VM pipvm = new VM("./assets", 4096, new TheRealWorld(), new VMProgramRom[]{PipBios.INSTANCE, PipROM.INSTANCE}, 8, watchdogs); // VM pipvm = new VM("./assets", 4096, new TheRealWorld(), new VMProgramRom[]{PipBios.INSTANCE, PipROM.INSTANCE}, 8, watchdogs);