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
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.
P : prints current pointer
Prompt on successful: .
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`)
let prompt = ['?','.']
let mode = "M"
let ptr = 0
let previousError = undefined
let P = 0
let pE = undefined
let peek = (p) => {
// TODO add open bus behaviour
try {
return sys.peek(p)
}
catch (e) {
return (p & 0xFFFFFF) >>> 16
}
}
while (1) {
print(prompt[+!previousError])
print(prompt[+!pE])
let buf = read().split(' ')
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) {
let addr = parseInt(buf[1], 16)
let addr2 = parseInt(buf[2], 16)
if (Number.isNaN(addr)) {
println((uhex(ptr, 4))+' : '+uhex(sys.peek(ptr)))
previousError = undefined
println((uhex(P, 6))+' : '+uhex(peek(P)))
pE = undefined
}
else {
let oldptr = ptr
ptr = addr
let oldP = P
P = addr
if (Number.isNaN(addr2)) {
println(uhex(sys.peek(ptr)))
previousError = undefined
println(uhex(peek(P)))
pE = undefined
}
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 {
for (let i = 0; i <= Math.abs(addr2) - Math.abs(addr); i++) {
if (i % 16 == 0 && i > 0) { println() }
if (i % 16 == 0) { print((uhex(ptr, 4))+' : ') }
print(uhex(sys.peek(ptr)) + ' ')
if (addr < 0 && addr2 < 0) { ptr-- } else { ptr++ }
if (i % 16 == 0) { print((uhex(P, 6))+' : ') }
print(uhex(peek(P)) + ' ')
if (addr < 0 && addr2 < 0) { P-- } else { P++ }
}
println()
previousError = undefined
pE = undefined
}
ptr = oldptr
P = oldP
}
}
else if ("N" == cmd) {
ptr++
println((uhex(ptr, 4))+' : '+uhex(sys.peek(ptr)))
previousError = undefined
if (P >= 0) { P++ } else { P-- }
println((uhex(P, 6))+' : '+uhex(peek(P)))
pE = undefined
}
else if ("J" == cmd) {
let addr = parseInt(buf[1], 16)
if (Number.isNaN(addr))
previousError = "Jump address unspecified"
pE = "Jump address unspecified"
else {
ptr = addr
previousError = undefined
P = addr
pE = undefined
}
}
else if ("P" == cmd) {
println(` ${P} ($${uhex(P, 6)})`)
pE = undefined
}
else if ("W" == cmd) {
let arg = buf[1]
if (arg == undefined)
previousError = "No arguments given"
pE = "No arguments given"
else if (arg.length % 2 == 1)
previousError = "Length of byte string is odd number"
pE = "Length of byte string is odd number"
else {
for (let i = 0; i < arg.length; i += 2) {
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) {
// do nothing
}
else if ('?' == cmd) {
println(previousError)
println(pE)
}
else {
previousError = "Unknown command"
pE = "Unknown command"
}
}

View File

@@ -1259,14 +1259,15 @@ const moduleCache = {}
// install other stuffs
// js source-based impl
/*var require = (absdir) => {
var require = (absdir) => {
let moduleFile = files.open(absdir)
if (!moduleFile.exists) throw Error("No such file: " + absdir)
let moduleScript = moduleFile.sread()
var intchkFunName = `tvdosSIGTERM_${generateRandomHashStr(16)}`;
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 intent = str.charCodeAt(4) // 2 for executable, 3 for shared
let addrToLoad = (str.charCodeAt(5) << 16) | (str.charCodeAt(6) << 8) | (str.charCodeAt(7))
@@ -1284,27 +1285,6 @@ let loadImgToMem = (str) => {
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) => {
if (moduleCache[ptr]) return moduleCache[ptr]
@@ -1328,6 +1308,28 @@ let requireFromMemory = (ptr) => {
moduleCache[moduleId] = 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")

View File

@@ -44,7 +44,8 @@ const EXEC_FUNS = {
"pcm": (f) => _G.shell.execute(`playpcm "${f}" -i`),
"ipf1": (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

View File

@@ -12,7 +12,7 @@ import kotlin.math.ceil
object SerialHelper {
private const val SLEEP_TIME = 4L
private const val SLEEP_TIME = 1L
fun sendMessageGetBytes(vm: VM, portNo: Int, message: ByteArray): ByteArray {
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[]{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", 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 pipvm = new VM("./assets", 4096, new TheRealWorld(), new VMProgramRom[]{PipBios.INSTANCE, PipROM.INSTANCE}, 8, watchdogs);