SEQ Scripting ISA¶
Contents
Introduction¶
NVIDIA uses PDAEMON for power-management related functions, including DVFS. For this they extended the firmware, PMU, with a scripting language called seq. Scripts are uploaded through falcon data I/O.
SEQ conventions¶
Operations are represented as 32-bit opcodes, follwed by 0 or more 32-bit parameters. The opcode is encoded as follows:
- Bit 0-7: operation
- Bit 31-16: total operation length in 32-bit words (# parameters + 1)
A script ends with 0x0. In the pseudo-code in the rest of this document, the following conventions hold:
- $r3 is reserved as the script program counter, aliased pc
- op aliases *pc & 0xffff
- params aliases (*pc & 0xffff0000) >> 16
- param[] points to the first parameter, the first word after *pc
- PMU reserves 0x5c bytes on the stack for general usage, starting at sp+0x24
- scratch[] is a pointer to scratchpad memory from 0x3e0 onward.
Stack layout¶
address | Type | Alias | Description |
---|---|---|---|
0x00-0x20 | u32[9] | Callers $r[0]..$r[8] | |
0x24 | u32 | *packet.data | Pointer to data structure |
0x2a | u16 | in_words | Number of words in the program. |
0x2c | u32 | *in_end | Pointer to the end of the program |
0x30 | u32 | insn_len | Length of the currently executed instruction |
0x54 | u32 | *head_vert | &(PDISPLAY.HEAD_STAT[0].VERT)+head_off |
0x58 | u32 | head_off | Offset for current HEAD from PDISPLAY[0] |
0x5c | u32 | *in_start | Pointer to the start of the program |
0x62 | u16 | word_exit | |
0x64 | u32 | timestamp |
Scratch layout¶
Type | Name | Description |
---|---|---|
u8 | out_words | Size of the out memory section, in 32-bit units |
u24 | Unused, padding | |
u32 | *out_start | Pointer to the out memory section |
u8 | flag_eq | 1 if compare val_last == param |
u8 | flag_lt | 1 if compare val_last < param |
u16 | Unused, padding | |
u32 | val_last | Holds the register last read or written. Can be set manually |
u32 | reg_last | The value last read or written. Can be set manually |
u32 | val_ret | Holds a return value written back to sp[80] after successful execution |
Opcodes¶
XXX: Gaps are all sorts of exit routines. Not clear how the exit procedure works wrt status propagation.
Memory¶
SET last¶
Set the last register/value in scratch memory.
- Opcode:
- 0x00 0x01
- Parameters:
- 1
- Operation:
scratch[3 + (op & 1)] = param[0];
READ last register¶
Do a read of the last register and/or a register/offset given by parameter 1, and write back to the last value.
- Opcode:
- 0x0a 0x0b 0x0c
- Parameters:
- 0/1
- Operation:
reg = 0; if(op == 0xa || op == 0xc) reg += scratch->reg_last; if(op == 0xb || op == 0xc) reg += param[0]; scratch->val_last = mmrd(reg);
WRITE last register¶
Do a write to the last register and/or a register/offset given by parameter 1 of the last value.
- Opcode:
- 0x0d 0x0e 0x0f
- Parameters:
- 0/1
- Operation:
reg = 0; if(op == 0xd || op == 0xf) reg += scratch->reg_last; if(op == 0xe || op == 0xf) reg += param[0]; mmwr_seq(reg, scratch->val_last);
SET register(s)¶
For each register/value pair, this operation performs a (locked) register write. through
- Opcode:
- 0x21
- Parameters:
- 2n for n > 0
- Operation:
IRQ_DISABLE; for (i = 0; i < params; i += 2) { mmwr_unlocked(param[i],param[i+1]); } IRQ_ENABLE; scratch->reg_last = param[i-2]; scratch->val_last = param[i-1];
WRITE OUT last value¶
Write a word to the OUT memory section, offset by the first parameter. For indirect read, the parameter points to an 8-bit value describing the offset of the address to write to.
- Opcode:
- 0x22 0x23
- Parameters:
- 1
- Operation:
if (!out_start) exit(pc); idx = $param[0].u08; if (idx >= out_words.u08) exit(pc); /* Indirect */ if (op & 0x1) { idx = out_start[idx]; if (idx >= out_words.u08) exit(pc); } out_start[idx] = scratch->val_last;
WRITE OUT¶
Write a word to the OUT memory section, offset by the first parameter. For indirect read, the parameter points to an 8-bit value describing the offset of the address to write to.
- Opcode:
- 0x24 0x25
- Parameters:
- 2
- Operation:
if (!out_start) exit(pc); idx = $param[0].u08; if (idx >= out_words.u08) exit(pc); /* Indirect */ if (op & 0x1) { idx = out_start[idx]; if (idx >= out_words.u08) exit(pc); } out_start[idx] = param[1];
READ OUT last value¶
Read a word from the OUT memory section, into the val_last location. Parameter is the offset inside the out page. For indirect read, the parameter points to an 8-bit value describing the offset of the read out value.
- Opcode:
- 0x26 0x27
- Parameters:
- 1
- Operation:
if (!out_start) exit(pc); idx = $param[0].u08; if (idx >= out_words.u08) exit(pc); /* Indirect */ if (op & 0x1) { idx = out_start[idx]; if (idx >= out_words.u08) exit(pc); } scratch->val_last = out_start[idx];
READ OUT last register¶
Read a word from the OUT memory section, into the reg_last location. Parameter is the offset inside the out page. For indirect read, the parameter points to an 8-bit value describing the offset of the read out value.
- Opcode:
- 0x28 0x29
- Parameters:
- 1
- Operation:
if (!out_start) exit(pc); idx = $param[0].u08; if (idx >= out_words.u08) exit(pc); /* Indirect */ if (op & 0x1) { idx = out_start[idx]; if (idx >= out_words.u08) exit(pc); } scratch->reg_last = out_start[idx];
WRITE OUT TIMESTAMP¶
Write the current timestamp to the OUT memory section, offset by the first parameter. For indirect read, the parameter points to an 8-bit value describing the offset of the address to write to.
- Opcode:
- 0x34 0x35
- Parameters:
- 2
- Operation:
if (!out_start) exit(pc); idx = $param[0].u08; if (idx >= out_words.u08) exit(pc); /* Indirect */ if (op & 0x1) { idx = out_start[idx]; if (idx >= out_words.u08) exit(pc); } call_timer_read(&value) out_start[idx] = value;
Arithmetic¶
OR last¶
OR the last register/value in scratch memory.
- Opcode:
- 0x02 0x03
- Parameters:
- 1
- Operation:
scratch[3 + (op & 1)] |= param[0];
AND last¶
AND the last register/value in scratch memory.
- Opcode:
- 0x04 0x05
- Parameters:
- 1
- Operation:
scratch[3 + (op & 1)] &= param[0];
ADD last¶
ADD the last register/value in scratch memory.
- Opcode:
- 0x06 0x07
- Parameters:
- 1
- Operation:
scratch[3 + (op & 1)] += param[0];
SHIFT-left last¶
Shift the last register/value in scratch memory to the left, negative parameter shifts right.
- Opcode:
- 0x08 0x09
- Parameters:
- 1
- Operation:
if(param[0].s08 >= 0) { scratch[3 + (op & 1)] <<= sex($param[0].s08); break; } else { scratch[3 + (op & 1)] >>= -sex($param[0].s08); break; }
AND last value, register¶
AND the last value with value read from register.
- Opcode:
- 0x1f
- Parameters:
- 1
- Operation:
scratch->val_last &= mmrd(param[0]);
ADD OUT¶
ADD an immediate value to a value in the OUT memory region.
- Opcode:
- 0x2a
- Parameters:
- 2
- Operation:
if (!out_start) exit(pc); idx = param[0]; if (idx >= out_len) exit(pc); out_start[idx] += param[1];
OR last value, register¶
OR the last value with value read from register
- Opcode:
- 0x2c
- Parameters:
- 1
- Operation:
scratch->val_last |= mmrd(param[0]);
OR OUT last value¶
OR the contents of last_val with a value in the OUT memory region.
- Opcode:
- 0x30 0x31
- Parameters:
- 1
- Operation:
if (!out_start) exit(pc); idx = param[0]; if (idx >= out_len) exit(pc); /* Indirect */ if (op & 0x1) { idx = out_start[idx]; if (idx >= out_words.u08) exit(pc); } out_start[idx] |= scratch->val_last;
ADD last value, OUT¶
Add a value in OUT to val_last.
- Opcode:
- 0x3b 0x3c
- Parameters:
- 1
- Operation:
if (!out_start) exit(pc); idx = param[0]; if(idx >= out_len) exit(pc); /* Indirect if(!op & 0x1) { idx = out_start[idx]; if (idx >= out_words.u08) exit(pc); } val_last += out_start[idx];
AND OUT last value¶
AND the contents of last_val with a value in the OUT memory region.
- Opcode:
- 0x32 0x33
- Parameters:
- 1
- Operation:
if (!out_start) exit(pc); idx = param[0]; if (idx >= out_len) exit(pc); /* Indirect */ if (op & 0x1) { idx = out_start[idx]; if (idx >= out_words.u08) exit(pc); } out_start[idx] &= scratch->val_last;
Control flow¶
EXIT¶
Exit
- Opcode:
- 0x10..0x12 0x16 0x2f
- Parameters:
- 0/1
- Operation:
if(op == 0x16) exit(param[0].s08); else exit(-1);
COMPARE last value¶
Compare last value with a parameter. If smaller, set flag_lt. If equal, set flag_eq.
- Opcode:
- 0x17
- Parameters:
- 1
- Operation:
flag_eq = 0; flag_lt = 0; if(scratch->val_last < param[0]) flag_lt = 1; else if(scratch->val_last == param[0]) flag_eq = 1;
BRANCH EQ¶
When compare resulted in eq flag set, branch to an absolute location in the program.
- Opcode:
- 0x18
- Parameters:
- 1
- Operation:
if(flag_eq) BRANCH param[0];
BRANCH NEQ¶
When compare resulted in eq flag unset, branch to an absolute location in the program.
- Opcode:
- 0x19
- Parameters:
- 1
- Operation:
if(!flag_eq) BRANCH param[0];
BRANCH LT¶
When compare resulted in lt flag unset, branch to an absolute location in the program.
- Opcode:
- 0x1a
- Parameters:
- 1
- Operation:
if(flag_lt) BRANCH param[0];
BRANCH GT¶
When compare resulted in lt and eq flag unset, branch to an absolute location in the program.
- Opcode:
- 0x1b
- Parameters:
- 1
- Operation:
if(!flag_lt && !flag_eq) BRANCH param[0];
BRANCH¶
Branch to an absolute location in the program.
- Opcode:
- 0x1c
- Parameters:
- 1
- Operation:
target = param[0].s16; if(target >= in_words) exit(target); word_exit = $r9.s16 target &= 0xffff; target <<= 2; pc = in_start + target; if(pc >= in_end) exit(in_end);
COMPARE OUT¶
Compare word in OUT with a parameter. If smaller, set flag_lt. If equal, set flag_eq.
- Opcode:
- 0x2b
- Parameters:
- 1
- Operation:
if(!out_start) exit(pc); idx = param[0]; if(idx >= out_words.u08) exit(pc); flag_eq = 0; flag_lt = 0; if(out_start[idx] < param[1]) flag_lt = 1; else if(out_start[idx] == param[1]) flag_eq = 1;
Miscellaneous¶
WAIT¶
Waits for desired number of nanoseconds, synchronous for 0x2e.
- Opcode:
- 0x13 0x2e
- Parameters:
- 1
- Operation:
if(op == 0x2e) mmrd(0); call_timer_wait_nf(param[0]);
WAIT STATUS¶
Shifts val_ret left by 1 position, and waits until a status bit is set/unset. Sets flag_eq and the LSB of val_ret on success. The second parameter contains the timeout.The first parameter encodes the desired status.
Old blob
param[0] | Test |
---|---|
0 | UNKNOWN(0x01) |
1 | !UNKNOWN(0x01) |
2 | FB_PAUSED |
3 | !FB_PAUSED |
4 | HEAD0_VBLANK |
5 | !HEAD0_VBLANK |
6 | HEAD1_VBLANK |
7 | !HEAD1_VBLANK |
8 | HEAD0_HBLANK |
9 | !HEAD0_HBLANK |
10 | HEAD1_HBLANK |
11 | !HEAD1_HBLANK |
New blob
In newer blobs (like 337.25), bit 16 encodes negation. Bit 8:10 the status type to wait for, and where applicable bit 0 chooses the HEAD.
param[0] | Test |
---|---|
0x0 | HEAD0_VBLANK |
0x1 | HEAD1_VBLANK |
0x100 | HEAD0_HBLANK |
0x101 | HEAD1_HBLANK |
0x300 | FB_PAUSED |
0x400 | PGRAPH_IDLE |
0x10000 | !HEAD0_VBLANK |
0x10001 | !HEAD1_VBLANK |
0x10100 | !HEAD0_HBLANK |
0x10101 | !HEAD1_HBLANK |
0x10300 | !FB_PAUSED |
0x10400 | !PGRAPH_IDLE |
- Todo:
- Why isn’t flag_eq unset on failure? Find out switching point from old to new format?
- Opcode:
- 0x14
- Parameters:
- 2
- Operation OLD BLOB:
val_ret *= 2; test_params[1] = param[0] & 1; test_params[2] = I[0x7c4]; switch ((param[0] & ~1) - 2) { default: test_params[0] = 0x01; break; case 0: test_params[0] = 0x04; break; case 2: test_params[0] = 0x08; break; case 4: test_params[0] = 0x20; break; case 6: test_params[0] = 0x10; break; case 8: test_params[0] = 0x40; break; } if (call_timer_wait(&input_bittest, test_params, param[1])) { flag_eq = 1; val_ret |= 1; }
- Operation NEW BLOB:
b32 func(b32 *) *f; unk3ec[2] <<= 1; test_params[2] = 0x1f100; // 7c4 test_params[1] = (param[0] >> 16) & 0x1; switch(param[0] & 0xffff) { case 0x0: test_params[0] = 0x8; f = &input_test break; case 0x1: test_params[0] = 0x20; f = &input_test break; case 0x100: test_params[0] = 0x10; f = &input_test break; case 0x101: test_params[0] = 0x40; f = &input_test break; case 0x300: test_params[0] = 0x04; f = &input_test break; case 0x400: test_params[0] = 0x400; f = &pgraph_test; break; default: f = NULL; break; } if(f && timer_wait(f, param, timeout) != 0) { unk3e8 = 1; unk3ec[2] |= 1; }
WAIT BITMASK last¶
Shifts val_ret left by 1 position, and waits until the AND operation of the register pointed in reg_last and the first parameter equals val_last. Sets flag_eq and the LSB of val_ret on success. The first parameter encodes the bitmask to test. The second parameter contains the timeout.
- Todo:
- Why isn’t flag_eq unset on failure?
- Opcode:
- 0x15
- Parameters:
- 2
- Operation:
b32 seq_cb_wait(b32 parm) { return (mmrd(last_reg) & parm) == last_val; } val_ret *= 2; if (call_timer_wait(seq_cb_wait, param[0], param[1])) break; val_ret |= 1; flag_eq = 1;
IRQ_DISABLE¶
Disable IRQs, increment reference counter irqlock_lvl
- Opcode:
- 0x1f
- Parameters:
- 1
- Operation:
interrupt_enable_0 = interrupt_enable_1 = false; irqlock_lvl++;
IRQ_ENABLE¶
Decrement reference counter irqlock_lvl
, enable IRQs if 0.
- Opcode:
- 0x1f
- Parameters:
- 1
- Operation:
if(!irqlock_lvl--) interrupt_enable_0 = interrupt_enable_1 = true;
FB PAUSE/RESUME¶
If parameter 1, disable IRQs on PDAEMON and pause framebuffer (memory), otherwise resume FB and enable IRQs.
- Opcode:
- 0x20
- Parameters:
- 1
- Operation:
if (param[0]) { IRQ_DISABLE; /* XXX What does this bit do? */ mmwrs(0x1610, (mmrd(0x1610) & ~3) | 2); mmrd(0x1610); mmwrs(0x1314, (mmrd(0x1314) & ~0x10001) | 0x10001); /* RNN:PDAEMON.INPUT0_STATUS.FB_PAUSED */ while (!(RD(0x7c4) & 4)); mmwr_seq = &mmwr_unlocked; } else { mmwrs(0x1314, mmrd(0x1314) & ~0x10001); while (RD(0x7c4) & 4); mmwrs(0x1610, mmrd(0x1610) & ~0x33); IRQ_ENABLE; mmwr_seq = &mmwrs; }