/RTE-IP

A specialized microcontroller for Runtime Enforcement, intended to be integrated into a SOC.

Primary LanguageTcl

Description

|SETUP |FLUSH | |[ ]

Registers

Instruction Pieces DATA[7:5]: opcode, and extension code typically. DATA[4]: Sometimes the highest bit of a 5-bit address. DATA[3:0]: Usually an address. HI(1): Affects Whether to select the high or low half nibble of the byte to load into DATA[3:0]. Program Flow EC(4): One-hot encoded execution cycle for the multi-cycle design. ES(?): Execution State. Exact size of this register may be to the disgression of the HDL synthesis tool. PC(8): Program Counter RA(8): Return Address. JC(3): Jump Counter. Counts down after each instruction, loading PC<=RA once it reaches 0. Resources CLKS(8, (4 or 12)): Clocks MEM(256, 8): Main memory IN(32): Set of input variables IN_INVALID(32): Indicates which clock constraints are not yet calculated on this policy state Output EDIT_D(32): Edit data NEXT_STATE(4) Static Config cfg_trans_offs(8): Address around which transition data is defined. cfg_state_offs(8): Address after which the starting addresses for each state are defined. cfg_prog_len(PROG_LEN_SIZE): Number of ticks until buffer flush cfg_tick_len(TICK_LEN_SIZE): Number of ticks between transitions cfg_clk_flags(32): Indicates which rows of memory are clocks cfg_clk_joins(8): Indicates which clocks are joined to the previous clock cfg_clk_div_imm(40): Indicates

Signals

Signals are either boolean values read from pins, or clock constraints (i.e. the boolean value returned when comparing one clock to a fixed constant). Signals are stored in a 5-bit addressed memory with two bits: {data, valid}. Now only the pin-signals are loaded in upon entry to a policy state, and their valid bits are set to 1 (which signals are from pins is configured using the cfg_clk_flags config). If the input address refers to a clock constraint, it is not loaded until it is requested. Whether or not to request a variable is determined through the valid bit in the memory. The value of the clock constraint is then determined by instructions in main memory which correspond to the signal address.

Memory Structure

Main memory is shared between Transition and Edit machines.

Clock Constraints

The start of memory is home to clock constraints (CLK0, CLK1). This is so that we can naturally map the clock constraint at MEM[x] to IN[x], where x is between 0 and 31. For these addresses only, we implement double reading of instructions, as 12-bit clock constraints span two bytes.

Clock constraints are calculated on-demand only. When a clock constraint is to be calculated, it will be calculated on the next A cycle, before returning to the same functionality as the B cycle.

Transitions

Transitions are actions defined as a next state, and a set of clock resets. State is 4 bits, and clock resets are 8 bits, one for each clock. We aim to pack the data without holes, so state will be packed two-to-a-byte, while clock resets take a byte each.

We configure the address cfg_trans_offs around which the transition information is placed. The state is packed in the increasing direction, while resets are packed in the decreasing direction. For a transition with id x, it's state will be found in cfg_trans_offs + x >> 1, while it's clock resets are found at cfg_trans_offs + ~x .

Nominal offsets

Some configuration variables are offset from their nominal values. This is so that the execution scheme is uniform.

  • State address table. Each address is the addr_nom - cfg_trans_offs
  • progam length, tick length and clock dividers are all 1 less than the nominal lengths.

State Addresses

Starting PC for each states is defined after the address cfg_state_offs. They are defined in pairs for the Transition Machine and Edit machine.

Example Memory

0:  0{op:1}{clk:2}{imm:4}  // IN[0], 4 bit clock
1:  0{op:1}{clk:2}{imm:4}  // IN[1], 4 bit clock
2:  1{op:1}{clk:2}{imm:4}  // IN[2], 12 bit clock
3:  {imm:8}
4:  1{op:1}{clk:2}{imm:4}  // IN[4], 12 bit clock
5:  {imm:8}
10: {state3:4}{state2:4}   // cfg_trans_offs
11: {state1:4}{state0:4}
6:  {reset0:8}
7:  {reset1:8}
8:  {reset2:8}
9:  {reset3:8}
12: {tra_addr0:8}          // cfg_state_offs
13: {edi_addr0:8}
14: {tra_addr1:8}
15: {edi_addr1:8}
16: {tra_addr2:8}
17: {edi_addr2:8}
...

Control unit

Datapath control signals are determined by three things:

  • Information from current instruction
  • Execution State (ES)
  • Execution Cycle (CYCLE)

Execution State allows us to interpret bytes differently depending on the context, and get considerably more mileage out of the 1-byte format. It can be thought of as an opcode extension that resides in a register.

Instruction Tables

KEY

<= Assignment on clock edge := Alias definition {a,b,c} signal concatenation PUSH(), POP() push/pop commands on stack $0, $1 First & second elements of stack. UPPERCASE Global identifier lowercase local alias for a signal

For Edit Machine

Note: In the Code Column, * indicates that the instruction is shared between the Edit and Transition machines.

Assembler Code ES Format Description
Actions
++++++++++++++++++++++++++++++
*EDIT INIT {addr:8} Start execution.
Goto address addr.
CYCLE 0
addr:=MEM[PC]
{OP,EX,VAR}<=addr
CYCLE 1
addr:={OP,EX,VAR}
PC<=addr + TRAN_OFFS
ES<=STD
*CLK0 CLK 0{op:1}{clk:2}{imm:4} Short clock constraint.
Compares the 4-bit clock {clk, 0} to imm. op == 1 specifies 'equality', and 'less than' otherwise.
CYCLE 2
{_,op,clk,imm}:=MEM[VAR]
val:=CLKS[{clk,0}]
if op:
-> IN[VAR]<=(val==imm)
else:
-> IN[VAR]<=(val<imm)
IN_INVALID[VAR] <= 0
DO CYCLE 1
*CLK1 CLK 1{op:1}{clk:2}{imm:12} Long clock constraint.
Compares the 4-bit clock {clk, 1} to imm. op == 1 specifies 'equality', and 'less than' otherwise. This instruction reads two bytes from memory.
CYCLE 2
{_,op,clk,imm}:=MEM[VAR]
imm2:=MEM[VAR+1]
val:=CLKS[{clk,1}]
if op:
-> IN[VAR]<=(val=={imm,imm2})
else:
-> IN[VAR]<=(val<{imm,imm2})
IN_INVALID[VAR] <= 0
DO CYCLE 1
*PSH STD 01{ex:1}{var:5} Push value onto stack.
Chain ACC commands if ex. If var refers to a clock, and that clock has not been computed, go do that.
CYCLE 0
{_,var,ex}:=MEM[PC]
VAR<=var
EX<=ex
CYCLE 1
If IN_INVALID[VAR]:
-> PC_EN <= 0
-> ES <= CLK
-> PC <= VAR
else:
-> PUSH()
-> $0<=IN[VAR]
-> If EX:
---> ES<=ACC
*OP STD 10{ex:1}{pop:1}{lut:4} Binary logic operation.
Perform a binary logic operation on first two bits of stack, and push the result. Chain ACC commands if ex. If pop, consume both elements.
CYCLE 0
{_,ex,pop,lut}:=MEM[PC]
VAR<={pop, lut}
EX<=ex
CYCLE 1
{pop,lut}:=VAR
If pop:
-> POP()
else:
-> PUSH()
$0<=lut[{$1, $0}]
If EX:
-> ES<=ACC
*DO STD 11{n:2}{addr:4} Reuse previous code.
This function exists to save precious memory space.
Jump backwards by addr + n + 1, and run n+1 instructions before returning to the instruction directly after this one.
CYCLE 0
{_,n,addr}:=MEM[PC]
JC<=n+2
VAR<=(~addr)+(~n)
CYCLE 1
RA<=PC+1
PC<=PC+1+VAR
*ACC ACC {op:2}{ex:1}{var:5} Accumulate on $0.
Accumulate on the first stack value using the specified operation. The operation is AND, where the inputs are inverted depending on their bit in op. Asserting ex will continue to chain ACC comands.
CYCLE 0
{op,ex,var}:=MEM[PC]
OP<=op
VAR<=var
EX<=ex
CYCLE 1
If IN_INVALID[VAR]:
-> PC_EN <= 0
-> ES <= CLK_ACC
else:
-> $0<=(OP[0]^$0)&(OP[1]^IN[VAR])
-> If !EX:
---> ES<=STD
VIO STD 00{ex:1}{pop:1}{lut:4} ** Declare edit block**.
Commit the proceeding edits if the binary operation on the first two bits yields True. Chain ACC commands on the test value if ex. If pop, consume one element from the stack.
CYCLE 0
{ex,pop,lut}:=MEM[PC]
EX<=ex
VAR<={pop,lut}
CYCLE 1
{pop,lut}:=VAR
If pop:
-> POP()
$0<=lut[{$1,$0}]
If EX:
-> ES<=EACC
else:
-> ES <= EDIT
*ACC ACC {op:2}{ex:1}{var:5} Accumulate on $0.
Accumulate on the first stack value using the specified operation. The operation is AND, where the inputs are inverted depending on their bit in op. Asserting ex will continue to chain ACC comands.
CYCLE 0
{op,ex,var}:=MEM[PC]
OP<=op
VAR<=var
EX<=ex
CYCLE 1
If IN_INVALID[VAR]:
-> PC_EN <= 0
-> ES <= CLK_ACC
else:
-> $0<=(OP[0]^$0)&(OP[1]^IN[VAR])
-> If !EX:
---> ES<=STD
ACC E_ACC {op:2}{ex:1}{var:5} Accumulate.
Same as above, except that ending the chain returns to the EDIT state. If the accumulated value is 1, the proceeding edits will be executed.
CYCLE 0
{op,ex,var}:=MEM[PC]
OP<=op
EX<=ex
VAR<=var
CYCLE 1
If IN_INVALID[VAR]:
-> -> PC_EN <= 0
-> ES <= E_CLK_ACC
else:
-> $0<=(OP[0]^$0)&(OP[1]^IN[VAR])
-> If !EX:
---> ES<=EDIT
EDI EDIT {end:1}{val:1}{nxt:1}{var:5} Specify edit.
If the first element on the stack (written by a proceeding VIO or VIO_ACC instruction) is 1, then override the value output value of var to be val. Continue chaining these instructions by asserting nxt. End the execution of this policy state by asserting end.
CYCLE 0
{end,val,nxt,var}:=MEM[PC]
OP<={end,val}
EX<=nxt
VAR<=var
CYCLE 1
{end, val}:=OP
if $0:
-> EDIT_D[VAR]<=val
if end:
-> DO FINISH
else if !EX:
-> POP()
-> ES<=STD
FINISH CYCLE 0
CYCLE 1
if do_setup:
-> ES <= SETUP
SETUP CYCLE 0
VAR <= {NEXT_STATE, 1}
CYCLE 1
PC<=VAR + cfg_state_offs
ES<=INIT

For Transition Machine

Assembler
Code
ES Format Description
Actions
++++++++++++++++++++++++++++++
*TRAN INIT {addr:8} Start execution.
Goto address addr.
CYCLE 0
addr:=MEM[PC]
{OP,EX,VAR}<=addr
CYCLE 1
addr:={OP,EX,VAR}
PC<=addr
ES<=STD
*CLK0 CLK 0{op:1}{clk:2}{imm:4} Short clock constraint.
Compares the 4-bit clock {clk, 0} to imm. op == 1 specifies 'equality', and 'less than' otherwise.
CYCLE 2
{_,op,clk,imm}:=MEM[VAR]
val:=CLKS[{clk,1}]
if op:
-> IN[VAR]<=(val==imm)
else:
-> IN[VAR]<=(val<imm)
IN_INVALID[VAR] <= 0
DO CYCLE 1
*CLK1 CLK 1{op:1}{clk:2}{imm:12} Long clock constraint.
Compares the 4-bit clock {clk, 1} to imm. op == 1 specifies 'equality', and 'less than' otherwise. This instruction reads two bytes from memory.
CYCLE 2
{_,op,clk,imm}:=MEM[VAR]
imm2:=MEM[VAR+1]
val:=CLKS[{clk,1}]
if op:
-> IN[VAR]<=(val=={imm,imm2})
else:
-> IN[VAR]<=(val<{imm,imm2})
IN_INVALID[VAR] <= 0
DO CYCLE 1
*PSH STD 01{ex:1}{var:5} Push value onto stack.
Chain ACC commands if ex. If var refers to a clock, and that clock has not been computed, go do that.
CYCLE 0
{_,var,ex}:=MEM[PC]
VAR<=var
EX<=ex
CYCLE 1
If IN_INVALID[VAR]:
-> PC_EN <= 0
-> ES <= CLK
else:
-> PUSH()
-> $0<=IN[VAR]
-> If EX:
---> ES<=ACC
*OP STD 10{ex:1}{pop:1}{lut:4} Binary logic operation.
Perform a binary logic operation on first two bits of stack, and push the result. Chain ACC commands if ex. If pop, consume both elements.
CYCLE 0
{_,ex,pop,lut}:=MEM[PC]
VAR<={pop, lut}
EX<=ex
CYCLE 1
{pop,lut}:=VAR
If pop:
-> PUSH()
else:
-> POP()
$0<=lut[{$1, $0}]
If EX:
-> ES<=ACC
*DO STD 11{n:2}{addr:4} Reuse previous code.
This function exists to save precious memory space.
Jump backwards by addr + n + 1, and run n+1 instructions before returning to the instruction directly after this one.
CYCLE 0
{_,n,addr}:=MEM[PC]
JC<=n+2
VAR<=(~addr)+(~n)
CYCLE 1
RA<=PC+1
PC<=PC+1+VAR
*ACC ACC {op:2}{ex:1}{var:5} Accumulate on $0.
Accumulate on the first stack value using the specified operation. The operation is AND, where the inputs are inverted depending on their bit in op. Asserting ex will continue to chain ACC comands.
CYCLE 0
{op,ex,var}:=MEM[PC]
OP<=op
VAR<=var
EX<=ex
CYCLE 1
If IN_INVALID[VAR]:
-> PC_EN <= 0
-> ES <= CLK_ACC
else:
-> $0<=(OP[0]^$0)&(OP[1]^IN[VAR])
-> If !EX:
---> ES<=STD
NXT1 STD 000{addr:5} Unconditional transition.
Perform transition specfied by addr. Transitions are specified in instruction memory around traOffset. states are 4 bit and stored in front of traOffset, two per byte. Clock resets are 1 byte and are stored behind traOffset.
CYCLE 0
{_, addr}:=MEM[PC]
VAR<=addr
JC<=2
CYCLE 1
HI<=VAR[0]
RA<=cfg_trans_offs + ~(VAR[4:0]>>1)
PC<=cfg_trans_offs + VAR[4:0]
ES<=TRA
NXT2 STD 001{up:1}{len:4} Declare transition jump table.
Construct a jump table using the first len bits of the stack as the index. We pack two stack id's per byte, so we specify the 5th bit using up.
CYCLE 0
{_,up,len}:=MEM[PC]
VAR<={up,len}
CYCLE 1
{_,len}:=VAR
val:=stack[4:1]
PC<=PC + 1 + val
ES<=NXT
HI<=$0
TAB NXT {hi:4}{lo:4} Transition jump table row
Rows of the NXT jump table.
Set up state to perform the transition with the id specified by either hi or lo, selecting based on the HI register. Following this, execute TRA and RST.
CYCLE 0
{hi,lo}:=MEM[PC]
if HI:
-> VAR[3:0]<=hi
else:
-> VAR[3:0]<=lo
JC<=2
CYCLE 1
HI<=VAR[0]
RA<=cfg_trans_offs + ~(VAR[4:0]>>1)
PC<=cfg_trans_offs + VAR[4:0]
ES<=RST
TRA TRA {hi:4}{lo:4} Transition state data declaration.
Set the next state to the nibble specified by the HI register.
CYCLE 0
{hi,lo}:=MEM[PC]
if HI:
-> VAR[3:0]<=hi
else:
-> VAR[3:0]<=lo
CYCLE 1
NEXT_STATE<=VAR[3:0]
ES<=FINISH
RST RST {rst:8} Transition reset data declaration.
Reset the clocks according to the asserted bits in rst.
CYCLE 0
rst:=MEM[PC]
VAR<=rst
CYCLE 1
rst:=VAR
apply rst
ES<=TRA
FINISH CYCLE 0
CYCLE 1
if do_setup:
-> ES <= SETUP
SETUP CYCLE 0
VAR <= {NEXT_STATE, 0}
CYCLE 1
PC<=VAR + cfg_state_offs
ES<=INIT

Assumed state behaviour

State Description Actions
++++++++++++++++++++++++++++++++++++++++
SETUP0 VAR[E]<={NEXT_STATE,0}

SETUP1(1) Load inputs. Setup initial states for both machines. Load Initial ES, kicking off the machine. PC[E]<=VAR[E] + cfg_state_offs
ES[E]<=INIT

VAR[T]<={NEXT_STATE,1}
SETUP2(0) PC[T]<=VAR[T] + cfg_state_offs
ES[T]<=INIT
CYCLE 0(1) if JC != 0:
-> JC<=JC-1
CYCLE 1 (1) if !extraCycle:
-> PC<=PC+1
->
-> if JC==1:
---> PC <= RA
else:
-> CYCLE<=CYCLE 2
CYCLE 2 CYCLE <= CYCLE 1
FINSH Wait until trigger condition if t>1

PC PC<=cfg_trans_offs + ~addr PC<=cfg_trans_offs + ~VAR