SEQ Scripting ISA

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.

Opcode Params Description
0x00 1 SET last value
0x01 1 SET last register
0x02 1 OR last value
0x03 1 OR last register
0x04 1 AND last value
0x05 1 AND last register
0x06 1 ADD last value
0x07 1 ADD last register
0x08 1 SHIFT last value
0x09 1 SHIFT last register
0x0a 0 READ last register
0x0b 1 READ last register
0x0c 1 READ last register
0x0d 0 WRITE last register
0x0e 1 WRITE last register
0x0f 1 WRITE last register
0x10 0 EXIT
0x11 0 EXIT
0x12 0 EXIT
0x13 1 WAIT
0x14 2 WAIT STATUS
0x15 2 WAIT BITMASK last
0x16 1 EXIT
0x17 1 COMPARE last value
0x18 1 BRANCH EQ
0x19 1 BRANCH NEQ
0x1a 1 BRANCH LT
0x1b 1 BRANCH GT
0x1c 1 BRANCH
0x1d 0 IRQ_DISABLE
0x1e 0 IRQ_ENABLE
0x1f 1 AND last value, register
0x20 1 FB PAUSE/RESUME
0x21 2n SET register(s)
0x22 1 WRITE OUT last value
0x23 1 WRITE OUT indirect last value
0x24 2 WRITE OUT
0x25 2 WRITE OUT indirect
0x26 1 READ OUT last value
0x27 1 READ OUT indirect last value
0x28 1 READ OUT last register
0x29 1 READ OUT indirect last register
0x2a 2 ADD OUT
0x2b 1 COMPARE OUT
0x2c 1 OR last value, register
0x2d 2 XXX: Display-related
0x2e 1 WAIT
0x2f 0 EXIT
0x30 1 OR OUT last value
0x31 1 OR OUT indirect last value
0x32 1 AND OUT last value
0x33 1 AND OUT indirect last value
0x34 1 WRITE OUT TIMESTAMP
0x35 1 WRITE OUT TIMESTAMP indirect
0x38 0 NOP
0x3b 1 ADD last value, OUT
0x3c 1 ADD last value, OUT indirect
other 0 EXIT

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;
}