COSIM — Simulator Manual
Computer Organization and Embedded Systems
COSIM — Computer Organization Simulator: reference manual
COSIM runs the generic RISC-style assembly language of Hamacher, Vranesic, Zaky and Manjikian, Computer Organization and Embedded Systems, 6th ed. (Chapters 2–3, also used in Chapters 5, 6 and 8), with the keyboard/display/timer interfaces of Chapter 3, a five-step datapath view (Ch. 5), a 5-stage pipeline timing model (Ch. 6) and a cache model (Ch. 8). The core is plain JavaScript (ES modules, no dependencies) and runs in the browser and in Node.
simulator/core/— simulator core (this manual, sections 1–9 and 12)simulator/targets/— other instruction sets (“targets”, section 14); the book’s ISA is the default targetcosimsimulator/cli.mjs— command-line interface (section 10)simulator/examples/*.asm— the book’s programs (section 11)simulator/tasks/— lab tasks, format intasks/README.mdnode --test simulator/test— test suitesimulator/cosim.bundle.js— the whole simulator (core, UI, examples, tasks) as one classic script, loaded byindex.html. Because of it the simulator also works whenindex.htmlis opened directly from disk (file://), where browsers block ES modules. It is generated: after editingcore/,targets/,ui/,tasks/orexamples/, runnode simulator/build.mjs(uses esbuild vianpx;--checktells whether it is stale). The build also regeneratestargets/manifest.js(the list of targets and task files).
Things that are not in the book but added by COSIM are marked (extension).
1. Registers
| Register | Use |
|---|---|
R0 |
always 0 (writes are ignored) — §2.4.2, so Move = Add Rd, R0, … and absolute mode = X(R0) |
R1 – R26, R30 |
general purpose |
R27 = SP |
stack pointer (§2.6); initialised to the top of RAM, 0x00100000 (first push stores at 0x000FFFFC) |
R28 = FP |
frame pointer (§2.7.3) |
R29 = IRA |
interrupt return address (Example 5.5, lecture 06) — written by the processor on every interrupt; do not use it for data when interrupts are enabled |
R31 = LINK = LINK_reg |
link register: Call saves the return address here (§2.7, Fig. 5.17, lectures 05/06) |
PC, IR |
program counter, instruction register |
Register names are case-insensitive (r5, sp, Link_reg). The aliases follow the Nios II convention (sp = r27, fp = r28, ea = r29, ra = r31), which is the processor closest to the book’s generic ISA (Appendix B).
Processor control registers (Fig. 3.7), accessed only with MoveControl:
| Name | Bits |
|---|---|
PS |
b0 = IE (interrupt enable) |
IPS |
copy of PS saved at interrupt entry |
IENABLE |
b1 KBD, b2 DISP, b3 TIM (book); b4 BTN, b5 PORT (extension) |
IPENDING |
same bits, read-only, shows the active requests; b0 = software interrupt in progress (extension) |
2. Memory
- Byte-addressable, 32-bit words, little-endian (the byte at the word address is the least significant byte, as in the Python interpreters of lectures 02/03; Fig. 2.3b).
Load/Storeneed word-aligned addresses (multiple of 4) — otherwise a misaligned access exception stops the program.LoadByte/StoreBytework at any address.- RAM:
0x00000000–0x000FFFFF(1 MB, configmemorySize). Accessing an address that is neither RAM nor an I/O register stops the program with an error. - Memory-mapped I/O (section 6) occupies
0x4000–0x406Fand0xFFFFFFF0–0xFFFFFFF7. Device registers take priority over RAM; the assembler warns if a program places code or data in0x4000–0x40FF. - Uninitialised memory reads as 0.
RESERVEdoes not initialise memory (§2.5.1).
3. Instructions
Operand order: Operation Rdst, Rsrc1, Rsrc2|#value; Load Rdst, source; Store Rsrc, destination (§2.3.4). Mnemonics are case-insensitive.
| Instruction | Effect | Notes |
|---|---|---|
Load Rd, addr |
Rd ← [EA] (word) | EA = word address |
Store Rs, addr |
[EA] ← [Rs] | |
LoadByte Rd, addr |
Rd ← byte [EA], upper 24 bits cleared | §2.8.2 |
StoreByte Rs, addr |
byte [EA] ← [Rs]7–0 | |
Add Rd, Ri, Rj / Add Rd, Ri, #V |
Rd ← [Ri] + [Rj] / V | V sign-extended |
Subtract Rd, Ri, Rj / #V |
Rd ← [Ri] − [Rj] / V | V sign-extended |
Multiply Rd, Ri, Rj / #V |
Rd ← low 32 bits of [Ri] × [Rj] / V | §2.8.3, Fig. 2.35 uses #10 |
Divide Rk, Ri, Rj |
Rk ← [Rj] / [Ri] | the book’s operand order (§2.8.3)! signed, truncated, remainder lost; [Ri] = 0 → divide-by-zero exception; no immediate form |
And, Or, Xor Rd, Ri, Rj / #V |
bitwise | V zero-extended (§2.8.1, §5.3.4) |
Not Rd, Ri |
Rd ← NOT [Ri] | |
OrHigh Rd, Ri, #V |
Rd ← [Ri] OR (V × 2¹⁶) | §2.9 |
LShiftL, LShiftR, AShiftR Rd, Ri, Rj / #n |
shift [Ri] by n | n = 0…31; a register count uses its low 5 bits. AShiftL = LShiftL |
RotateL, RotateR Rd, Ri, Rj / #n |
rotate | (RotateLC/RotateRC need the C flag — not available, no condition codes) |
Branch LOC |
PC ← LOC | |
Branch_if_[Ri]cond[Rj] LOC |
if [Ri] cond [Rj] then PC ← LOC | cond: > = ≠ < ≥ ≤, also != <> >= <= ==; signed comparison |
Branch_if_[Ri]cond0 LOC |
compare with 0 | = Branch_if_[Ri]cond[R0] |
BGT, BEQ, BNE, BLT, BGE, BLE Ri, Rj, LOC |
same as Branch_if | Fig. 2.13 mnemonics; Branch_greater_than also accepted |
Call LOC |
LINK ← [PC]; PC ← LOC | [PC] = address after the Call |
Call_register Ri |
LINK ← [PC]; PC ← [Ri] | Fig. 5.17 (also Call_Register) |
Return |
PC ← [LINK] | |
MoveControl Rd, CR / MoveControl CR, Rs |
copy between a GPR and PS, IPS, IENABLE, IPENDING | §3.2.4 |
Return-from-interrupt |
PC ← [IRA]; PS ← [IPS] | also Return_from_interrupt, RFI |
Software-interrupt |
as an accepted interrupt: IRA ← [PC], IPS ← [PS], IE ← 0, PC ← ILOC | §4.6 “Trap or Software-interrupt”; also Trap. Not maskable; IPENDING b0 = 1 until Return-from-interrupt |
Nop |
nothing | §6.4.2 |
Halt |
stop the simulation | (extension) |
Pseudo-instructions (expanded by the assembler, shown in the listing):
| Pseudo | Assembled as |
|---|---|
Move Rd, Rs |
Add Rd, Rs, R0 |
Move Rd, #V |
Add Rd, R0, #V if −32768 ≤ V ≤ 32767; Or Rd, R0, #V if V ≤ 0xFFFF; otherwise OrHigh Rd, R0, #hi (+ Or Rd, Rd, #lo if lo ≠ 0) — two words, with a note (§2.9) |
MoveByte Rd, #V |
Or Rd, R0, #V (V = 0…255; used in Fig. 3.4) |
MoveImmediateAddress Rd, LOC |
OrHigh Rd, R0, #HI(LOC) ; Or Rd, Rd, #LO(LOC) (always two words, §2.9) |
Clear Rd |
Add Rd, R0, R0 |
| aliases | LD ST CLR MOV ADD SUB MUL BR (Fig. 2.13); ADDI R2, R3, 5 (§2.5, # optional) |
Not available (CISC, §2.10): two-operand arithmetic, memory operands in arithmetic, condition codes (Branch>0, Compare, TestBit, SetBit, …). The assembler explains this when such an instruction is used.
Addressing modes (Table 2.1)
| Mode | Syntax | EA | Encoding |
|---|---|---|---|
| Immediate | #V |
operand = V | I format, 16 bits |
| Register | Ri |
— | |
| Absolute | LOC |
LOC | LOC(R0): LOC must fit a sign-extended 16-bit value: 0…0x7FFF or 0xFFFF8000…0xFFFFFFFF (§2.4.1). Larger addresses: error with the hint to use MoveImmediateAddress |
| Indirect | (Ri) |
[Ri] | 0(Ri) |
| Index | X(Ri) |
[Ri] + X | X signed 16 bits; X may be a label (TABLE(R5)) or negative (-4(FP)) |
| Base with index | (Ri,Rj), X(Ri,Rj) |
[Ri] + [Rj] + X | R format, X = 11-bit signed (−1024…1023) |
| Autoincrement | (Ri)+ |
[Ri], then Ri += size | CISC, only with the cisc option (§2.10.1) |
| Autodecrement | -(Ri) |
Ri −= size, then [Ri] | CISC, only with the cisc option |
Branch and Call targets are PC-relative: offset = target − (address of the branch + 4), i.e. relative to the updated PC (§2.13, Fig. 5.15).
Machine encoding (Fig. 2.32 / Fig. 5.12)
R format | 31-27 Rsrc1 | 26-22 Rsrc2 | 21-17 Rdst | 16-6 0 (or X) | 5-0 OP |
I format | 31-27 Rsrc | 26-22 Rdst | 21-6 16-bit immediate | 5-0 OP |
J format | 31-6 26-bit Call offset | 5-0 OP |
Load: Rsrc = base/index register, Rdst = destination. Store: Rsrc = base, the “Rdst” field holds the register to store (Fig. 5.14: RB ← [R6]). Conditional branch: Ri in 31-27, Rj in 26-22, offset in the immediate field. Return holds LINK in 31-27, Return-from-interrupt holds IRA in 31-27 (Fig. 5.18). MoveControl holds the control register number (PS 0, IPS 1, IENABLE 2, IPENDING 3) in the immediate field.
OP codes (the book gives none; Add/Subtract/And/Or/Load/Store/Branch/Return equal the lecture 05 demo assembler, so BGT R2, R0, LOOP at 128 in Fig. 2.12 is 0x103FFB31):
| OP | Instruction | OP | Instruction | OP | Instruction |
|---|---|---|---|---|---|
| 0x01 | Add (R) | 0x11 | Add #V | 0x20 | Load X(Ri) |
| 0x02 | Subtract (R) | 0x12 | Subtract #V | 0x21 | Store X(Ri) |
| 0x03 | And (R) | 0x13 | And #V | 0x22 | LoadByte X(Ri) |
| 0x04 | Or (R) | 0x14 | Or #V | 0x23 | StoreByte X(Ri) |
| 0x05 | Xor (R) | 0x15 | Xor #V | 0x24–0x27 | Load/Store/LoadByte/StoreByte X(Ri,Rj) (R) |
| 0x06 | Multiply (R) | 0x16 | Multiply #V | 0x28–0x2B | … (Ri)+ |
| 0x07 | Divide (R) | 0x18 | LShiftL #n | 0x2C–0x2F | … −(Ri) |
| 0x08 | LShiftL (R) | 0x19 | LShiftR #n | 0x30 | Branch |
| 0x09 | LShiftR (R) | 0x1A | AShiftR #n | 0x31–0x36 | Branch_if > = ≠ < ≥ ≤ |
| 0x0A | AShiftR (R) | 0x1B | RotateL #n | 0x37 | Call (J) |
| 0x0B | RotateL (R) | 0x1C | RotateR #n | 0x38 | Return |
| 0x0C | RotateR (R) | 0x1D | OrHigh | 0x39 | Return-from-interrupt |
| 0x0D | Not (R) | 0x3A / 0x3B | MoveControl Rd,CR / CR,Rs | ||
| 0x0E | Call_register (R) | 0x3C | Software-interrupt | ||
| 0x00 | illegal | 0x3E / 0x3F | Nop / Halt |
The processor executes the encoded words (it decodes what is in memory), so a program that overwrites its own code or jumps into data behaves like real hardware.
4. Assembly language
LABEL: Operation operand, operand, … ; comment
NAME EQU value
- Labels end with
:and may stand alone on a line (they then label the next statement). Names: letters, digits,_,.; case-sensitive (the assembler suggests the right spelling when only the case differs). Register names cannot be labels. - Comments:
;or//to the end of the line. Text after the last operand without a comment character is also treated as a comment, so programs copied from the book (which has a separate comment column) assemble; a note reports how many lines did this. - Characters copied from the PDF (−, –, ’, “) are normalised.
- Numbers (§2.5.3):
93,0x5D,$5D,%01011101,0b01011101,'A','\n'(escapes\n \r \t \0 \\ \' \" \xHH), negative-5. - Expressions:
+ - * /, unary-and~, parentheses, labels,HI(e)/LO(e)(high/low 16 bits) (extension), e.g.LIST+4,#HI(TABLE).
Directives (§2.5.1)
| Directive | Meaning |
|---|---|
ORIGIN addr |
following code/data start at addr (before the first ORIGIN: address 0) |
NAME EQU value |
define a symbol (also NAME: EQU value); forward references allowed |
DATAWORD v1, v2, … |
32-bit words (values may be labels) |
DATABYTE v1, "text", … |
bytes; strings allowed (strings are an extension) (Fig. 3.12) |
DATASTRING "text" |
bytes of the string, no automatic terminator — write "text\0" or "text\r" (extension) |
RESERVE n |
n bytes, not initialised |
ALIGN n |
align the location counter to a multiple of n (extension) |
END [START] |
end of the source; the optional operand is the start address |
Instructions and DATAWORD are automatically aligned to a word boundary.
Start address: the END operand, else the label START, else the first instruction.
Predefined symbols (extension): all device register names of section 6 (KBD_DATA, DISP_STATUS, TIM_INIT, SEVEN, LEDS, …) and CR = 0x0D, LF = 0x0A. A program may redefine them (Fig. 3.18 does SEVEN EQU 0x4030).
Error messages give the line, what is wrong and a hint, for example:
Error on line 2: Add needs three operands (destination, source 1, source 2) but has two.
2 | Add R3, R5
Hint: RISC-style instructions are three-address instructions (§2.3.4). Did you mean Add R3, R3, R5?
All errors of a program are reported at once. Warnings (code in the I/O area, text after END) and notes (Move expanded to two words) do not stop assembly.
Run settings in the source (extension): comment lines of the form ; @cosim key=value … set machine options for the CLI and the UI, e.g.
; @cosim input="Hello\n" timerTicksPerInstruction=1000000
; @cosim cache="size=32,block=4,assoc=1" pipeline="forwarding=off" maxSteps=50000
5. Program execution
stepexecutes one instruction;runexecutes until the program stops.- The program stops normally when it executes
Halt, when execution runs past the last instruction (the book’s programs end with “next instruction”), or atX: Branch Xwhile interrupts are disabled. - It stops with an error on: illegal instruction, misaligned word access, access to an unmapped address, divide by zero, a jump to an address that holds no assembled instruction (e.g. a wrong
Returnaddress), an interrupt without an ISR at ILOC. The message names the source line and gives a hint. - It pauses at breakpoints and when it is waiting for keyboard input (it has polled KBD_STATUS 200 times with KIN = 0 and no queued characters, without storing anything in between); it continues after input.
- The CLI stops after 1 000 000 instructions (endless loop?) — option
--max-steps. - Cycle count: 1 per instruction; with a cache, plus the miss stall cycles; with the pipeline model, the pipeline’s cycle count.
- Step back: any number of instructions can be undone (checkpoints every 500 instructions plus deterministic re-execution, including typed keys and switch changes).
6. I/O devices and memory map
All device registers can be accessed with LoadByte/StoreByte and with Load/Store. A byte access reads/writes one byte of the 32-bit register (little-endian).
| Address | Name | Bits | Source |
|---|---|---|---|
| 0x4000 | KBD_DATA |
received character (reading clears KIN) | Fig. 3.3a |
| 0x4004 | KBD_STATUS |
b1 KIN (character available), b0 KIRQ (= KIN ∧ KIE) | Fig. 3.3a |
| 0x4008 | KBD_CONT |
b1 KIE (interrupt enable) | Fig. 3.3a |
| 0x4010 | DISP_DATA |
character to display (writing clears DOUT) | Fig. 3.3b |
| 0x4014 | DISP_STATUS |
b2 DOUT (ready), b0 DIRQ (= DOUT ∧ DIE) | Fig. 3.3b |
| 0x4018 | DISP_CONT |
b2 DIE | Fig. 3.3b |
| 0x4020 | TIM_STATUS |
b2 TON (running), b1 ZERO, b0 TIRQ; reading clears ZERO and TIRQ | Fig. 3.14 |
| 0x4024 | TIM_CONT |
b3 UP, b2 FREE, b1 RUN, b0 TIE | Fig. 3.14 |
| 0x4028 | TIM_INIT |
initial count (writing also loads the counter) | Fig. 3.14 |
| 0x402C | TIM_COUNT |
current count (read/write) | Fig. 3.14 |
| 0x4030 | SEVEN = HEX0 |
7-segment display: a = b6, b = b5, … g = b0 (Fig. 3.17), b7 = decimal point (ext.) | Example 3.6 |
| 0x4031–0x4035 | HEX1–HEX5 |
five more 7-segment displays (a word store to 0x4030 sets HEX0–HEX3) | (extension) |
| 0x4040 | LEDS |
10 LEDs (b9–b0), read/write | (extension) |
| 0x4050 | SWITCHES |
10 slide switches (b9–b0), read only | (extension) |
| 0x4060 | BTN_DATA |
4 push buttons, 1 = pressed, read only | (extension) |
| 0x4064 | BTN_EDGE |
edge capture: bit set when a button is pressed; write 1 to clear a bit | (extension) |
| 0x4068 | BTN_CONT |
interrupt mask, request = EDGE ∧ CONT → IPENDING b4 | (extension) |
| 0xFFFFFFF0–F7 | PAIN PAOUT PADIR PBIN PBOUT PBDIR PSTAT PCONT |
Ch. 10 parallel port, byte registers, PADIR/PBDIR 1 = output, direct pin mode only (PSTAT reads 0, no handshake) | Fig. 10.6 (minimal) |
The bit positions of Fig. 3.3 were checked against the figure in the PDF (KIN and KIE in b1, DOUT and DIE in b2, KIRQ/DIRQ in b0) and agree with the programs (And R4, R4, #2 tests KIN, #4 tests DOUT, Fig. 3.4).
Keyboard: characters typed (UI input box, CLI --input, typeInput()) are queued. When KIN = 0 the next character is loaded into KBD_DATA and KIN is set, keyboardDelay instructions (default 0) after the previous one was read. The Enter key / "\n" is delivered as CR (0x0D), which the book’s programs test for.
Display: writing DISP_DATA appends the character to the terminal and clears DOUT; DOUT returns to 1 after displayDelay instructions (default 20), so polling loops are meaningful. A character written while DOUT = 0 is lost and a warning is shown (config displayStrict: false accepts it). CR (and LF) start a new line, backspace deletes.
Timer (Example 3.5): the counter changes by timerTicksPerInstruction (default 1) per executed instruction — the book’s 100-MHz clock would need 10⁹ instructions for 10 s, so examples set e.g. ; @cosim timerTicksPerInstruction=1000000. With RUN = 1 it counts down (UP = 0) or up (UP = 1). When it reaches 0 it sets ZERO, and TIRQ if TIE = 1; with FREE = 1 it reloads TIM_INIT and continues, otherwise it stops (TON = RUN = 0). Starting a down-counter whose count is 0 reloads TIM_INIT first.
7. Interrupts and exceptions (§3.2, Figs. 3.7–3.10, Example 5.5)
- A device raises a request (KIRQ, DIRQ, TIRQ, button edge); the request appears in IPENDING.
- At the end of each instruction, if PS.IE = 1 and (IPENDING ∧ IENABLE) ≠ 0 the processor
- saves the return address (the address of the next instruction) in IRA = R29,
- copies PS to IPS and clears PS.IE,
- loads PC ← ILOC.
- The interrupt handler at ILOC finds the device in IPENDING (Fig. 3.9) and services it; reading KBD_DATA / writing DISP_DATA / reading TIM_STATUS removes the request.
Return-from-interruptdoes PC ← [IRA], PS ← [IPS].
ILOC is the address of the label ILOC if the program defines one (all book examples do), otherwise 0x20 (config iloc overrides). There are no vectored interrupts: one handler polls IPENDING, as in Fig. 3.9. For nested interrupts the handler must save IRA and IPS (MoveControl Ri, IPS) on the stack before setting IE (§3.2.2). The handler must save every register it uses, including LINK if it calls subroutines (Fig. 3.9).
Exceptions (§3.2.6: illegal OP code, divide by zero, misaligned access, bad address) stop the simulation with a message instead of jumping to an exception handler.
8. Datapath view (Ch. 5)
For each executed instruction COSIM shows the five steps of Fig. 5.4 with the book’s register transfers (Figs. 5.11, 5.13–5.17) and the contents of PC, IR, RA, RB, RZ, RM, RY and PC-Temp after each step:
Load R6, 1000(R9)
1 Fetch Memory address ← [PC], Read memory, IR ← Memory data, PC ← [PC] + 4
2 Decode Decode instruction, RA ← [R9]
3 Compute RZ ← [RA] + Immediate value X
4 Memory Memory address ← [RZ], Read memory, RY ← Memory data
5 Write R6 ← [RY]
As in lecture 06: both register-file ports are always read in step 2 (RA ← [IR31-27], RB ← [IR26-22]), RM ← [RB] in step 3, and inter-stage registers keep their values when a stage does nothing. Multiplexer settings (MuxB, MuxY, MuxC, MuxPC, MuxINC, Extend) are included. Base-with-index stores need a third register read (noted in the text).
9. Pipeline model (Ch. 6) and cache model (Ch. 8)
Pipeline
A timing model over the dynamic instruction trace (the functional result never depends on it). Stages F D C M W; an instruction enters a stage when the previous one has left it.
| Option | Values (default first) | Book |
|---|---|---|
forwarding |
true / false |
§6.4.1 |
branchStage |
'decode' (penalty 1, Fig. 6.10) / 'compute' (penalty 2, Fig. 6.9) |
§6.6.1 |
delaySlot |
false / true — must match the CPU option delaySlot (the instruction after a branch is then always executed; Call saves PC + 8) |
§6.6.2 |
prediction |
'not-taken', 'backward-taken' (static, decided in Decode), '1bit', '2bit' (Fig. 6.12a/b, initial state LNT) |
§6.6.3 |
btb |
true — dynamic predictors predict in the Fetch stage with a branch target buffer (correct prediction = no penalty) |
§6.6.4 |
memoryStalls |
true — cache-miss cycles stall F/M (Fig. 6.7) |
§6.5 |
tableLimit |
200 — rows kept for the timing diagram |
Timing rules: without forwarding a dependent instruction reads its operands in Decode in the cycle after the producer’s Write (enters Compute at W + 2, Fig. 6.3); with forwarding it enters Compute after the producer’s Compute (from RZ, Fig. 6.4) or one cycle later after a Load (from RY, Fig. 6.8). A branch decided in Decode that needs a register produced by the previous instruction waits for it (forwarding into the comparator, as in lecture 07, Exercise 7.2). Unconditional branches, Call and Return are treated like taken branches. An accepted interrupt flushes the pipeline (approximation).
Output: total cycles, CPI, stall cycles by cause (data, load-use, branch, memory, fetch), branch statistics, and a timing table (F D C M W; lower case f d c m = stalled; X = discarded wrong-path fetch). It reproduces Figs. 6.3, 6.4, 6.6, 6.8 and the 8 cycles per iteration of the list-sum loop (lecture 07).
Cache
| Option | Default | Notes |
|---|---|---|
size |
1024 | bytes, power of 2 |
blockSize |
16 | bytes, power of 2, ≥ 4 |
assoc |
1 | 1 = direct mapped, n = n-way, 'full' = fully associative |
replacement |
'lru' |
'lru', 'fifo', 'random' (seeded, deterministic) |
writePolicy |
'write-back' |
or 'write-through' |
writeAllocate |
true for write-back, false for write-through | |
hitTime, missPenalty |
1, 10 | cycles (AMAT and stalls) |
Address split: tag | set index | byte offset. Statistics: accesses, reads, writes, hits, misses, hit rate, write-backs, AMAT = hit time + miss rate × miss penalty. Stall cycles per access: miss penalty on a miss, plus one more miss penalty for writing back a dirty victim; write-through writes go to a write buffer (no stall). Data are always kept up to date in memory (the cache view reads the block’s words from memory). I/O registers are not cached. A separate instruction cache (icache) is optional.
The §8.6.3 example (Figs. 8.19–8.23) is reproduced with a 32-byte cache of 4-byte blocks (examples/sec8-6-3-cache-example.asm): read hits direct 2, associative 8, 4-way set-associative 4.
10. Command-line interface
node simulator/cli.mjs prog.asm [options]
| Option | |
|---|---|
--input "text" |
keyboard input (\n → CR, escapes \r \t \xHH) |
--max-steps N |
instruction limit (default 1 000 000) |
--regs |
all registers (default: non-zero ones) |
--mem LABEL[:count[:b]] |
memory dump in words, or bytes with :b; LABEL or address; repeatable |
--listing, --symbols |
assembly listing (address, machine code, source), symbol table |
--trace [N] |
the first N executed instructions with register/memory changes |
--datapath [N] |
five-step view of the first N instructions |
--pipeline [spec], --pipeline-table [N] |
e.g. forwarding=on,branch=decode,predict=2bit,delay=off,btb=on |
--cache [spec], --icache [spec] |
e.g. size=1024,block=16,assoc=2,policy=lru,write=wb,penalty=10 (size=1k ok; assoc=full; write=wt) |
--cisc, --delay-slot |
CISC addressing modes; delayed branching |
--switches N, --buttons 0,2 |
lab-board inputs |
--timer-ticks N, --display-delay N, --keyboard-delay N |
device timing |
--task ID [--tasks-dir DIR] |
check the program against a lab task |
--json, --quiet |
machine-readable summary; only output and errors |
--target ID, --list-targets |
simulate another instruction set (section 14) / list them |
--set ID=VALUE |
other targets: board input before the run (switch/button 1/0, slider value); repeatable |
prog.hex |
Intel HEX file, for targets that load binaries (--target …) |
Exit codes: 0 finished (or task passed), 1 runtime error (or task failed), 2 assembly error / bad usage, 3 instruction limit reached.
11. Example programs (simulator/examples/)
examples/index.js exports examples = [{id, file, chapter, title}] (ids such as fig2-8 for index.html?example=fig2-8).
| File | Book | Result |
|---|---|---|
fig2-08-listsum.asm |
Fig. 2.8/2.12 list sum | SUM = 150 |
fig2-11-marks.asm |
Figs. 2.10/2.11 index mode | SUM1..3 |
fig2-13-mnemonics.asm |
Fig. 2.13 (LD/CLR/MOV/BGT) | SUM = 24 |
fig2-17-subroutine-regs.asm |
Fig. 2.17 | SUM = 100 |
fig2-18-subroutine-stack.asm |
Figs. 2.18/2.19 | SUM = 15 |
fig2-21-nested.asm |
Figs. 2.21/2.22 nested frames (book addresses 2000/2100/3000) | RESULT = 17 |
fig2-24-bcd-pack.asm |
Fig. 2.24 | PACKED = 0x47 |
fig2-27-dot-product.asm |
Fig. 2.27 | DOTPROD = 12 |
fig2-30-string-search.asm |
Figs. 2.29/2.30 | RESULT = T + 4 |
fig2-33-string-length.asm |
Example 2.1 | LENGTH = 12 |
fig2-34-smallest.asm |
Example 2.2 (ORIGIN 400/1000) | SMALL = 1 |
fig2-35-decimal-to-binary.asm |
Example 2.3 | BINARY = 1987 |
fig2-36-add-columns.asm |
Example 2.4 | column 2 = 4, 12, 20 |
fig3-04-echo-polling.asm |
Fig. 3.4 polling echo | echo of the input line |
fig3-08-keyboard-interrupt.asm |
Fig. 3.8 | interrupt-driven input, polled echo |
fig3-09-interrupt-handler.asm |
Fig. 3.9 (ISRs completed) | text printed by DIRQ, line read by KIRQ |
fig3-12-hex-display.asm |
Fig. 3.12 | 2F80A1C3 |
fig3-15-timer-interrupt.asm |
Fig. 3.15 (DISPLAY completed) | 1 2 3 |
fig3-18-seven-segment.asm |
Fig. 3.18 | HEX0 shows 7 |
fig6-03-data-dependency.asm, fig6-08-load-use.asm |
Figs. 6.3/6.4, 6.8 | 9 / 8 cycles |
ex6-loop-scheduling.asm |
lecture 07, Exercise 7.2 | 8 cycles / iteration |
sec8-6-3-cache-example.asm |
§8.6.3, Fig. 8.20 | read hits 2 / 8 / 4 |
ex-factorial-recursive.asm, ex-switches-leds.asm, ex-timer-polling.asm, ex-cisc-autoincrement.asm |
COSIM examples |
Where the PDF text lost a symbol (e.g. Branch_if_[R8]=[R9] NOMATCH in Fig. 2.30 and Branch_if_[R3]=[R2] RTRN "Return if not CR" in Fig. 3.8, which must be ≠), the examples use the condition the comments require, with a note in the file header. The CISC programs (Figs. 2.26, 2.28, 2.31, 3.5, 3.10, 3.11, 3.13, 3.16, 3.19) use condition codes and memory operands and are not runnable.
12. JavaScript API
import { createMachine, assemble, formatMessage } from './core/index.js';
const m = createMachine({ displayDelay: 10, pipeline: { forwarding: true } });
const asm = m.load(sourceText); // assemble + load + reset
if (!asm.ok) showErrors(asm.errors); // [{line, message, hint, severity, source}]
m.typeInput('Hello\n');
function tick() { // non-blocking run loop for a UI
const r = m.run(5000); // at most 5000 instructions
render();
if (r.status === 'running') requestAnimationFrame(tick);
}
tick();Module core/index.js (facade)
| Export | |
|---|---|
createMachine(config) / new Machine(config) |
a simulated computer (below) |
assemble(src, {cisc, origin, predefined, symbols}) |
assembly result (below) |
formatMessage(msg), formatListing(asm) |
text for errors and the listing |
sourceSettings(src) |
{config, input, maxSteps} from ; @cosim lines |
runProgram(src, {config, input, maxSteps}) |
assemble, load and run → {machine, asm, result} |
datapathSteps(record), previewNext(machine), formatDatapath(d) |
Ch. 5 view |
PipelineModel, simulatePipeline(trace, opts), formatTimingTable(rows), rowCells(row), parsePipelineSpec(s) |
Ch. 6 |
Cache, simulateCache(addrs, cfg), parseCacheSpec(s) |
Ch. 8 |
checkTask(task, src), runTest(task, test, src), resolveAddress(spec, asm), findTask(lists, id) |
lab checker |
isa |
instruction set: decode(word), disassemble(word, pc, labelOf), encR/encI/encJ, parseRegister, regDisplayName, REG, CONTROL_REGS, INSTRUCTION_REFERENCE (table for help pages), OPCODES |
IO_SYMBOLS, sevenSegToChar(bits), DEFAULT_CONFIG, VERSION |
Assembly result
{ ok, errors, warnings, image, symbols, symbolInfo, predefined, listing, addrToLine, lineToAddr, codeAddrs, statements, entry, iloc, lines }
errors/warnings:{line, message, hint, severity: 'error'|'warning'|'info', source}(sorted by line).listing: per source line{line, addr, words: [machine words], bytes: [data bytes], source, kind: 'code'|'data'|'dir'|'equ'|'label'|'end', size}.symbols:{LOOP: 112, …}(labels and EQU);symbolInfo[name] = {value, line, type}.addrToLine(Map address → line, for highlighting the current line),lineToAddr(Map line → address, for breakpoints in the gutter),codeAddrs(Set of instruction addresses).
Machine configuration (DEFAULT_CONFIG)
memorySize (0x100000), stackTop (= memorySize), cisc (false), delaySlot (false), iloc (null = label ILOC or 0x20), strictFetch (true), selfLoopHalts (true), stopOnInputWait (true), inputWaitPolls (200), keyboardDelay (0), displayDelay (20), displayStrict (true), newlineAsCR (true), timerTicksPerInstruction (1), traceLimit (10000), checkpointInterval (500), maxCheckpoints (400), cache (null | cache config), icache (null), pipeline (null | pipeline options).
Machine methods and properties
| Method | |
|---|---|
load(srcOrAsm) → asm |
assemble (if a string), load the image, reset; check asm.ok |
reset() |
reload the image, clear registers/devices/statistics (switch positions are kept) |
step() → {status, record, message} |
one instruction (plus interrupt entry) |
run(maxSteps = 1e6, {ignoreBreakpoints}) → {status, steps, message} |
run a chunk; status 'running' = limit reached, call again |
stepBack(n = 1) → bool, canStepBack |
undo instructions |
status, statusMessage, canRun |
'empty', 'ready', 'running', 'breakpoint', 'waiting', 'halted', 'error'; errorInfo = {kind, pc, line, message, hint} |
pc, ir, steps, cycles, getStats() |
counters; getStats() → {instructions, cycles, cpi, interrupts, dcache, icache, pipeline} |
getRegister(r), getRegisters(), setRegister(r, v) |
r = 0…31, 'R5', 'SP', 'PC', 'PS' … (unsigned values) |
getControl(name), getControls(), setControl(name, v) |
PS, IPS, IENABLE, IPENDING |
readMemory(addr, 1|4), dumpMemory(addr, len) |
debugger reads, no device side effects (null if unmapped) |
writeMemory(addr, value, 1|4) |
debugger write |
symbol(name), lineOf(addr), asm |
symbol lookup, source line of an address, the loaded assembly result |
addBreakpoint(where), removeBreakpoint, toggleBreakpoint, clearBreakpoints(), breakpoints |
where = address, label, or {line} |
getTrace(n) |
last n trace records (below) |
typeInput(text) |
keyboard input |
setSwitch(i, on), setSwitches(mask), pressButton(i), releaseButton(i), setPortPins('A'|'B', v) |
lab board |
getDeviceState() |
{terminal, keyboard:{DATA,KIN,KIE,KIRQ,queue,queueLength}, display:{DOUT,DIE,DIRQ,text,bytes,lost}, timer:{COUNT,INIT,TON,ZERO,TIRQ,TIE,RUN,FREE,UP,events}, sevenseg:{digits:[6], chars}, leds:{value,bits}, switches:{value,bits}, buttons:{pressed,edge,mask,IRQ}, parallel:{…}, ipending} |
output |
terminal text |
messages |
runtime warnings/errors {level, text, step, pc, line} (e.g. lost display character) |
snapshot(), restore(s) |
complete deep-copied state |
dcache, icache, pipeline |
model objects or null; setModels({cache, icache, pipeline}) replaces them and re-executes up to the current instruction so statistics are complete |
on(event, fn) → unsubscribe |
events 'step' (record), 'run' ({status, steps}), 'message', 'change', 'reset' |
All external inputs (typed keys, switches, buttons, register/memory edits) are recorded with the instruction count, so step back and setModels replay them exactly.
Trace record
{ step, pc, word, line, cls, name,
text, // disassembly, e.g. "Add R4, R0, #208"
src, // source statement, e.g. "Move R4, #NUM1"
reads: [regs], writes: [{r, old, v}],
mem: {addr, size, value, write, io} | null,
branch: {conditional, taken, target, call?, ret?, rfi?, swi?} | null,
ctrl: {reg, old, v} | null, // control-register write
interrupt: {pending, returnAddress, iloc, software?} | null, // accepted after this instruction
dp: {RA, RB, RZ, RM, RY, PCTemp}, dpPrev: {...}, // datapath registers after / before
memStall, fetchStall, nextPc, fault? }Datapath
datapathSteps(record) → {text, machineText, pc, word, cls, fields, signals, steps: [{n, name, rtn, regs: {PC, IR, RA, RB, RZ, RM, RY, PCTemp}, mem?, write?, immediate?}]}. previewNext(machine) gives the same for the instruction at PC without changing the state.
Pipeline
m.pipeline.stats() → {cycles, instructions, cpi, stalls: {data, loadUse, branch, memory, fetch, interrupt}, branches: {total, taken, mispredicted, penaltyCycles}, options}; m.pipeline.table() → rows {n, step, pc, text, F, D, C, M, Mend, W, stall, penalty, prediction, taken} or {discarded: true, F}; rowCells(row) → {cycle: 'F'|'D'|'d'|…}.
Cache
m.dcache.stats() → {accesses, reads, writes, hits, misses, readMisses, writeMisses, writeBacks, memoryWrites, stallCycles, hitRate, missRate, amat}; geometry() → {size, blockSize, blocks, sets, ways, tagBits, indexBits, offsetBits, mapping, …}; contents(addr => m.readMemory(addr)) → sets[set][way] = {valid, dirty, tag, blockAddr, lruRank, data: [words]}; split(addr) → {tag, index, offset}; log = last 64 accesses.
Lab tasks
Task files tasks/example.js, tasks/lab1.js … lab8.js export tasks = [{id, lab, title, description, template, solution?, config, maxSteps, tests, hints}]. Each test has setup (memory by label, registers, input, switches, buttons), optional timed actions, and expect (memory, registers, output exact/regex/contains, leds, sevenSeg, halted, maxInstructions, cacheHitRate, pipelineCycles, custom check). Full description: simulator/tasks/README.md. checkTask(task, source) → {id, title, pass, passed, total, assembled, errors, tests: [{name, pass, messages, steps, status, output}]}.
13. Decisions where the book leaves a choice
| Topic | Decision |
|---|---|
| R0 | hard-wired 0 (§2.4.2) |
| SP, FP, IRA, LINK | R27, R28, R29, R31 (Nios II convention; LINK = R31 as in lectures 05/06) |
| Endianness | little-endian (lectures 02/03) |
| Return address of interrupts | general-purpose register IRA (Example 5.5, lecture 06), not the stack |
| ILOC | label ILOC, else 0x20; no vectored interrupts |
| KIRQ / DIRQ bit | b0 (Fig. 3.3 in the PDF) |
| Immediates | 16 bits; sign-extended for Add/Subtract/Multiply and addresses, zero-extended for And/Or/Xor (§2.8.1, §5.3.4) |
| Divide | Rk ← [Rj] / [Ri] as written in §2.8.3, remainder discarded |
| Branch offsets | 16-bit, relative to PC + 4; Call offset 26-bit, relative to PC + 4 |
| OP codes | own table (section 3), consistent with lecture 05 where it defines them |
Unconditional Branch |
I format with Ri = Rj = R0 (as lecture 05) |
| (Ri,Rj) / X(Ri,Rj) | R format with an 11-bit X (Table 2.1 lists the mode for RISC) |
| Autoincrement/decrement | CISC only, behind the cisc option |
| Condition codes, RotateLC/RC | not implemented (CISC) |
| End of a book program (“next instruction”) | running past the last instruction ends the program normally |
14. Targets (other instruction sets)
COSIM can simulate other instruction sets and microcontrollers besides the book’s ISA. Each one is a target in simulator/targets/<dir>/; the book’s ISA is the target cosim and is the default, so every ?task= / ?example= link, task file and CLI command above works unchanged.
| Target | |
|---|---|
cosim |
the book’s RISC-style ISA (sections 1–13), with the Datapath, Pipeline and Cache tabs |
toy |
TOY, a nine-instruction 8-bit accumulator machine — the documented template for adding an ISA (targets/_template/README.md) |
avr-atmega328p, avr-attiny85, avr-atmega324pb |
Microchip AVR (Arduino Uno / ATtiny85 / ATmega324PB Xplained Pro), when targets/avr/ is installed (targets/avr/README.md) |
node simulator/cli.mjs --list-targets prints the installed targets.
Choosing a target. In the browser: the menu at the top left, or index.html?target=<id> (remembered for the next visit); ?task=<id> switches to the task’s target. CLI: --target <id>, else the task’s target (--task), else a source line ; @cosim target=<id>, else cosim.
What changes with the target. The program menu lists that target’s examples and tasks; the editor highlights its syntax; the Registers tab shows its register groups and flags (click a flag to toggle it); the Memory tab has a memory-space selector (program memory is shown one word per row with its disassembly); the I/O tab shows the target’s board (LEDs, push buttons that are held while the mouse button is down, switches, sliders, 7-segment displays, a terminal, a pin table) and a tree of the device state; the status bar shows the simulated time when the target has a clock. Datapath, Pipeline and Cache exist only for cosim. Targets that load binaries show Load .hex…: the editor then shows the disassembly, which can be stepped and breakpointed like a source program. Help (?) contains the target’s instruction reference and a link to its documentation.
Lab tasks may name a target (target: 'avr-atmega328p'); the checker then uses that target’s machine and the generic expectations (registers, flags, memory, terminal output, board state, cycle limits, cycle-timed input actions) — see tasks/README.md.
Adding an ISA. The interface every target implements (descriptor, assembly result, machine, UI description, checker, CLI, build) is specified in targets/target-api.md. Copy targets/_template/, change the descriptor, implement assembler and machine, then run node simulator/build.mjs and node --test simulator/test — the conformance suite (test/conformance.mjs) checks every registered target automatically. JavaScript use:
import { getTarget, listTargets } from './targets/index.js';
import { checkTask } from './targets/checker.js';
const t = getTarget('toy');
const m = t.createMachine(t.defaultConfig);
m.load(sourceText); // → AsmResult, as for cosim
m.run(100000);
m.getRegisterList(); m.getFlags(); m.getBoardState(); m.output;