|SETUP |FLUSH | |[ ]
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 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.
Main memory is shared between Transition and Edit machines.
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 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
.
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.
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.
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}
...
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.
<=
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
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 0addr:=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 0VAR <= {NEXT_STATE, 1} CYCLE 1 PC<=VAR + cfg_state_offs ES<=INIT |
Assembler Code |
ES | Format | Description |
Actions ++++++++++++++++++++++++++++++ |
---|---|---|---|---|
*TRAN | INIT | {addr:8} |
Start execution. Goto address addr . |
CYCLE 0addr:=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 0rst:=MEM[PC] VAR<=rst CYCLE 1 rst:=VAR apply rst ES<=TRA |
FINISH | CYCLE 0 CYCLE 1 if do_setup :-> ES <= SETUP |
|||
SETUP | CYCLE 0VAR <= {NEXT_STATE, 0} CYCLE 1 PC<=VAR + cfg_state_offs ES<=INIT |
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