Tilelink concrete spec
¤
This is a (more) concrete specification of the tilelink protocol. It specifies the low-level messages with reference to the abstract spec.
The concrete model introduces Acquire and Finish messages, requester ID's, and other fields that indicate the address range to operate on, and so on.
¤
¤
Oustanding issues:
Managers must include an up-to-date copy of the data in Grants responding to Acquires upgrading permissions unless they are certain that client has not been Probed since the Aquire was issued.
Todo
currently the spec requires all Grants to have data, even in the upgrade case.
Todo
We do not allow the same txid to be used for simultaneous transactions even if they differe in type (for example, cached and uncached). This can be relaxed if needed.
Todo
We allow a probe to be handled in the middle of a voluntary release, so that some beats are voluntary ans some involuntary. This could be made mode strict.
¤
include tilelink_abstract_spec
¤
address high and low-order bits
type tl_addrhi
type tl_addrlo
¤
construct adresses from high- and low-order bits
individual addr_cons(X:tl_addrhi,Y:tl_addrlo) : addr
axiom addr_cons(X1,Y1) = addr_cons(X2,Y2) -> X1=X2 & Y1=Y2
¤
Message formats. The message contain auxiliary fields that are used in the specification, but are not physically present. These are labeled "aux".
¤
¤
structure representing Acquire message
module acq = {
individual id_ : id # id of requester
individual addr_hi : tl_addrhi # hi order bits of address
individual word : tl_addrlo # requested word if uncached
individual own:ownership # what ownership type? (none=uncached)
individual op : otype # what operation?
individual data_ : data # what data, if any?
individual ltime_ : ltime # (aux) logical time
}
¤
Heap of acquire messages
type acq_id
instantiate acqs(X:acq_id) : acq
¤
structure representing Grant message
module gnt = {
individual id_ : id # transaction id
individual word : tl_addrlo # low-order bits of this response
individual own:ownership # what ownership type? (none = uncached)
relation relack # is this an ack of a Release?
individual data_ : data # return value, if any
individual addr_hi : tl_addrhi # (aux) hi order bits of address
individual ltime_ : ltime # (aux) logical time
}
¤
Heap of grant messages
type gnt_id
instantiate gnts(X:gnt_id) : gnt
¤
structure representing Finish message
module fns = {
individual id_ : id # transaction id
individual addr_hi : tl_addrhi # (aux) hi order bits of acquire
individual word : tl_addrlo # (aux) low-order bits of acquire
individual own:ownership # (aux) ownership or acquire
}
¤
Heap of finish messages
type fns_id
instantiate fnss(X:fns_id) : fns
¤
structure representing Release message
module rls = {
individual id_ : id # id of requester
relation voluntary # is this a voluntary release?
individual addr_hi : tl_addrhi # hi order bits of address
individual word :tl_addrlo # word being released
relation dirty # is data modified?
individual data_ : data # data, if dirty
}
¤
Heap of release messages
type rls_id
instantiate rlss(X:rls_id) : rls
¤
structure representing probe message
module prb = {
individual id_ : id # id of receiver
individual addr_hi : tl_addrhi # hi order bits of address
}
¤
Heap of probe messages
type prb_id
instantiate prbs(X:prb_id) : prb
¤
Stub actions for mixins
module interface_stubs = {
action release(a:addr, d:data, o:ownership) = {}
action grant(a:addr, d:data, o:ownership) = {}
action request(a:addr, d:data, mo:otype, lt:ltime) = {}
action response(a:addr, d:data, mo:otype, lt:ltime) = {}
}
¤
This module describes the TileLink interface. It uses mixins to specify the interaction between a client (clnt) a manager (mngr) and a reference model (ref). The funciton "side" tells us, for each component ID, which side of the interface the component is on ("client" or "manager").
¤
module tl_interface(ref,clnt,mngr,side) = {
¤
whether a line is cached
relation cached_hi(X:tl_addrhi)
¤
An Acquire has three phases: requested, accepted and finishing
These predicates keep track of whether there is an acquire in one of the three phases, for each type: exclusive, shared and uncached.
Note, this is cache protcol dependent. We might want to parameterize this on an arbitraru set of possible cache line priviledges.
relation excl_r(A:tl_addrhi) # there is a requested excl acquire for line
relation shrd_r(A:tl_addrhi) # there is a requested shrd acquire for line
relation unc_r(A:tl_addrhi,W:tl_addrlo) # there is a requested uncached acquire
relation excl_a(A:tl_addrhi) # there is an accepted excl acquire for line
relation shrd_a(A:tl_addrhi) # there is an accepted shrd acquire for line
relation unc_a(A:tl_addrhi,W:tl_addrlo) # there is an accepted uncached acquire
relation excl_f(A:tl_addrhi) # there is an finishing excl acquire for line
relation shrd_f(A:tl_addrhi) # there is an finishing shrd acquire for line
relation unc_f(A:tl_addrhi,W:tl_addrlo) # there is an finishing uncached acquire
¤
Release messages
A voluntary Release message is considered to be requested from the time it is transmitted until the time its Grant has been transmitted. This predicate tracks which addresses have releases requested (none = no priviledges).
relation none_r(A:tl_addrhi,W:tl_addrlo)
¤
Probe messages
A word of a Probe message is considered to be requested from the time the Probe is transmitted until the time the corresponding involuntary Release is transmitted. This predicate tracks the addresses that have probes requested.
relation prb_addr(H:tl_addrhi, W:tl_addrlo)
¤
granted events
A memory event E (see the reference model) has been granted if a corresponding uncached Acquire has been granted. This predicate tracks the granted events.
relation granted(E:ltime)
¤
Each Acquire has a transaction ID (txid). This map tracks the txid associated with requested acquires for a given hi address. It is a protocol invariant that there is at most one such txid.
individual addr_rid(H:tl_addrhi) : id
¤
Each Acquire has a transaction ID (txid). This map tracks the txid associated with accepted and finishing acquires for a given hi address. It is a protocol invariant that there is at most one such txid.
individual addr_id(H:tl_addrhi) : id
¤
Each voluntary Release has a transaction ID (txid). This map tracks the txid associated with voluntary Releases for a given hi address. It is a protocol invariant that there is at most one such txid.
individual addr_nid(H:tl_addrhi) : id
¤
Initial condition of interface
Nothing is requested, accepted, finished or granted.
init ~excl_r(H) & ~shrd_r(H) & ~unc_r(H,W)
& ~excl_a(H) & ~shrd_a(H) & ~unc_a(H,W)
& ~excl_f(H) & ~shrd_f(H) & ~unc_f(H,W)
& ~none_r(H,W) & ~prb_addr(H,W) & ~granted(E)
¤
specification of Acquire message
action tl_Acquire(msg:acq_id) = {
local hi:tl_addrhi, lo:tl_addrlo, txid: id, ow:ownership, lt:ltime, a:addr, d:data, o:otype {
ow := acqs(msg).own;
hi := acqs(msg).addr_hi;
lo := acqs(msg).word;
txid := acqs(msg).id_;
lt := acqs(msg).ltime_;
a := addr_cons(hi,lo);
d := acqs(msg).data_;
o := acqs(msg).op;
if acqs(msg).own = none {
¤
rules for uncached Acquires
(1) May not have a requested acquire with same hi address and word.
assert ~unc_r(hi,lo);
assert unc_r(hi,W) -> addr_rid(hi) = txid;
assert unc_a(hi,W) -> addr_id(hi) = txid;
assert (unc_r(H,W) | excl_r(H) | shrd_r(H)) & H ~= hi -> addr_rid(H) ~= txid;
assert ~(to_ser(E) & ~granted(E) &
(ref.evs(E).type_ = cas | ref.evs(lt).type_ = cas)
& ref.evs(E).id_ = ref.evs(lt).id_);
assert ~(to_ser(E) & ~granted(E)
& ref.evs(E).addr_ = ref.evs(lt).addr_
& ref.evs(E).id_ = ref.evs(lt).id_);
assert ref.prevents(E,lt) -> granted(E);
assert addr_cons(hi,lo) = ref.evs(lt).addr_
& d = ref.evs(lt).data_
& o = ref.evs(lt).type_;
assert ~cached_hi(hi);
¤
mark the acquire as requested
unc_r(hi,lo) := true;
¤
Associate the address and txid
addr_rid(hi) := txid;
¤
Perform abstract operation
call abs.request(addr_cons(hi,lo),d,o,lt)
}
else {
¤
rules for cached Acquires
(1) May not have pending acquire with same ID and ownership and with same hi addr. We can, however, have more than one ownership request in same transaction.
assert ow = excl -> ~excl_r(hi) & ~excl_a(hi);
assert ow = shrd -> ~shrd_r(hi) & ~shrd_a(hi);
assert (excl_r(hi) | shrd_r(hi)) -> addr_rid(hi) = txid;
assert (excl_a(hi) | shrd_a(hi)) -> addr_id(hi) = txid;
assert (unc_r(H,W) | excl_r(H) | shrd_r(H)) & H ~= hi -> addr_rid(H) ~= txid;
assert ~none_r(hi,W);
assert ow = excl & ~prb_addr(hi,W) -> ~excl_p(addr_cons(hi,W));
assert ow = shrd & ~prb_addr(hi,W) -> ~shrd_p(addr_cons(hi,W));
assert cached_hi(hi);
¤
mark the acquire as requested
if ow = excl {
excl_r(hi) := true
};
if ow = shrd {
shrd_r(hi) := true
};
¤
Associate the address and txid
addr_rid(hi) := txid
}
}
}
mixin tl_Acquire before mngr.tl_Acquire
¤
specification of Grant message
action tl_Grant(msg:gnt_id) = {
local hi:tl_addrhi, lo:tl_addrlo, txid: id, reqt:time, maddr:addr, ow:ownership, lt:ltime {
hi := gnts(msg).addr_hi; # this field is aux
lo := gnts(msg).word;
txid := gnts(msg).id_;
ow := gnts(msg).own;
lt := gnts(msg).ltime_;
maddr := addr_cons(hi,lo); # construct full address
if gnts(msg).relack {
¤
A release ack marks the matching Release as completed
¤
Rules for Release acks:
(1) A release ack implies a requested release of the given word.
assert none_r(hi,lo);
assert ~excl_a(hi) & ~shrd_a(hi) & ~excl_f(hi) & ~shrd_f(hi);
assert addr_nid(hi) = txid;
¤
mark the release finished
none_r(hi,lo) := false
}
else {
¤
Rules for normal Grants:
(1) A Grant must match a requested or accepted Acquire.
assert ow = none -> (unc_r(hi,lo) | unc_a(hi,lo));
assert ow = excl -> (excl_r(hi) | excl_a(hi));
assert ow = shrd -> (shrd_r(hi) | shrd_a(hi));
assert ow = none -> ~granted(lt);
assert ~prb_addr(hi,W);
assert ow = none -> maddr = ref.evs(lt).addr_ & to_ser(lt) & ref.evs(lt).serialized;
assert addr_rid(hi) = txid;
assert ~((excl_a(H) | excl_f(H) | shrd_a(H) | shrd_f(H) | unc_a(H,W) | unc_f(H,W))
& addr_id(H) = txid & H ~= hi);
assert ~((excl_a(H) | excl_f(H) | shrd_a(H) | shrd_f(H) | unc_a(H,W) | unc_f(H,W))
& addr_id(H) ~= txid & H = hi);
assert ow = none -> ~unc_a(hi,lo) & ~unc_f(hi,lo);
¤
If uncached, mark the event granted. If the request is not part of an accepted transaction, move the request to accepted.
Todo
for no, uncached acquire are only one beat, so can never be in accepted state.
if ow = none {
unc_r(hi,lo) := false;
unc_f(hi,lo) := true # for now, can't be in accepted state
}
else if ow = excl {
if ~excl_a(hi) {
excl_r(hi) := false;
excl_a(hi) := true
}
}
else if ow = shrd {
if ~shrd_a(hi) {
shrd_r(hi) := false;
shrd_a(hi) := true
}
};
¤
If we are accepting a new uncached transaction, this associates the new txid with the address for accepted transactions.
addr_id(hi) := txid;
¤
Abstract semantics of grant
if gnts(msg).own ~= none { # cached: perform an abstract grant
call abs.grant(maddr,gnts(msg).data_,ow);
if forall W:tl_addrlo. (ow=excl & excl_p(addr_cons(hi,W:tl_addrlo))) {
excl_a(hi) := false;
excl_f(hi) := true
};
if forall W:tl_addrlo. (ow=shrd & shrd_p(addr_cons(hi,W:tl_addrlo))) {
shrd_a(hi) := false;
shrd_f(hi) := true
}
}
else { # uncached: perform an abstract response
call abs.response(maddr,gnts(msg).data_,ref.evs(lt).type_,lt);
granted(gnts(msg).ltime_) := true
}
}
}
}
mixin tl_Grant before clnt.tl_Grant
¤
specification of Finish message
A finish marks all the Acquires in a transaction as completed.
action tl_Finish(msg:fns_id) = {
local hi:tl_addrhi, lo:tl_addrlo, txid: id, ow:ownership {
txid := fnss(msg).id_;
hi := fnss(msg).addr_hi; # this field is aux
lo := fnss(msg).word; # this field is aux
ow := fnss(msg).own; # this field is aux
¤
Rules for Finishes:
(1) A finished transaction must correspond to some finishing acquire.
assert ow = none -> unc_f(hi,lo);
assert ow = excl -> excl_f(hi);
assert ow = shrd -> shrd_f(hi);
assert addr_id(hi) = txid;
¤
Finish the Acquire
if ow = none {
unc_f(hi,lo) := false
};
if ow = excl {
excl_f(hi) := false
};
if ow = shrd {
shrd_f(hi) := false
}
}
}
mixin tl_Finish before mngr.tl_Finish
¤
specification of Release message
action tl_Release(msg:rls_id) = {
local hi:tl_addrhi, lo:tl_addrlo, txid: id, a:addr, d:data {
txid := rlss(msg).id_;
d := rlss(msg).data_;
hi := rlss(msg).addr_hi; # this field is aux
lo := rlss(msg).word; # this field is aux
a := addr_cons(hi,lo);
¤
Rules for all Releases:
(1) Release only cached addresses
assert cached_hi(hi);
Because a voluntary release has to be acked before an involuntary release can be issued, we can never have two matching releases pending.
assert ~none_r(hi,lo);
Todo
allow exclusive but clean release?
assert rlss(msg).dirty <-> excl_p(a);
if rlss(msg).voluntary {
¤
Rules for voluntary Releases:
(1) Must have no matching acquires requested or accepted when voluntarily releasing.
assert ~excl_r(hi) & ~excl_a(hi) & ~shrd_r(hi) & ~shrd_a(hi);
assert none_r(hi,W) -> addr_nid(hi) = txid;
assert none_r(H,L) & H ~= hi -> addr_nid(H) ~= txid;
assert excl_p(a) | shrd_p(a);
¤
Mark release as request, record txid
none_r(hi,lo) := true;
addr_nid(hi) := txid
}
else {
¤
Rules for involuntary Releases:
(1) A probe must be requested for the full address.
Note we do allow an involuntary relase with a pending Acquire.
assert prb_addr(hi,lo);
¤
Mark the probe completed
prb_addr(hi,lo) := false
};
¤
The abstract semantics of release
if excl_p(a) {
call abs.release(a,d,excl)
};
if shrd_p(a) {
call abs.release(a,d,shrd)
}
}
}
mixin tl_Release before mngr.tl_Release
¤
specification of Probe message
action tl_Probe(msg:prb_id) = {
local hi:tl_addrhi {
hi := prbs(msg).addr_hi;
¤
Rules for Probes:
(1) Probe only cached addresses
assert cached_hi(hi);
assert ~prb_addr(hi,W);
assert ~excl_a(hi) & ~excl_f(hi) & ~shrd_a(hi) & ~ shrd_f(hi);
¤
Mark the probe pending (for all words of line)
prb_addr(hi,W) := true
}
}
mixin tl_Probe before clnt.tl_Probe
¤
Instantiate the abstract interface specification
¤
instantiate abs : interface_stubs
instantiate interface(ref,abs,abs,side)
¤
All or none of a line is cached
¤
axiom cached(addr_cons(H,L)) <-> cached_hi(H)
¤
Conjectured invariants of the interface state
¤
A cached acquire must be in cached space, uncached in uncached space
conjecture (excl_r(H) | excl_a(H) | excl_f(H) |
shrd_r(H) | shrd_a(H) | shrd_f(H)) -> cached_hi(H)
conjecture (unc_r(H,L) | unc_a(H,L) | unc_f(H,L)) -> ~cached_hi(H)
conjecture ~(excl_r(H) & excl_a(H)) & ~(excl_r(H) & excl_f(H)) & ~(excl_a(H) & excl_f(H))
conjecture ~(shrd_r(H) & shrd_a(H)) & ~(shrd_r(H) & shrd_f(H)) & ~(shrd_a(H) & shrd_f(H))
conjecture ~unc_a(H,L)
conjecture excl_a(H) -> exists W. ~excl_p(addr_cons(H,W))
conjecture shrd_a(H) -> exists W. ~shrd_p(addr_cons(H,W))
conjecture none_r(H,W) -> cached_hi(H)
conjecture none_r(H,W) -> ~excl_r(H) & ~excl_a(H) & ~shrd_r(H) & ~shrd_a(H)
conjecture none_r(H,W) & A = addr_cons(H,W) -> ~excl_p(A) & ~shrd_p(A)
conjecture prb_addr(H,W) -> cached_hi(H)
conjecture excl_f(H) & ~none_r(H,W) -> excl_p(addr_cons(H,W))
conjecture shrd_f(H) & ~none_r(H,W) -> shrd_p(addr_cons(H,W))
conjecture prb_addr(H,W) -> ~excl_a(H) & ~excl_f(H) & ~shrd_a(H) & ~shrd_f(H)
conjecture excl_r(H) & ~none_r(H,W) & ~prb_addr(H,W) -> ~excl_p(addr_cons(H,W))
conjecture shrd_r(H) & ~none_r(H,W) & ~prb_addr(H,W) -> ~shrd_p(addr_cons(H,W))
conjecture granted(E) -> ref.evs(E).serialized
conjecture granted(E) -> to_ser(E)
conjecture to_ser(E2) & ref.prevents(E1,E2) -> granted(E1)
conjecture ~(to_ser(E1) & to_ser(E2) & ~granted(E1) & ~granted(E2) & E1 ~= E2 &
ref.evs(E1).id_ = ref.evs(E2).id_
& (ref.evs(E1).type_ = cas | ref.evs(E1).type_ = cas))
conjecture ~(to_ser(E1) & to_ser(E2) & ~granted(E1) & ~granted(E2) & E1 ~= E2 &
ref.evs(E1).id_ = ref.evs(E2).id_
& ref.evs(E1).addr_ = ref.evs(E2).addr_)
conjecture (excl_a(H) | shrd_a(H) | unc_a(H,W)) -> addr_id(H) = addr_rid(H)
conjecture
(excl_a(H1) | excl_f(H1) | shrd_a(H1) | shrd_f(H1) | unc_a(H1,W1) | unc_f(H1,W1))
& (excl_a(H2) | excl_f(H2) | shrd_a(H2) | shrd_f(H2) | unc_a(H2,W2) | unc_f(H2,W2))
& H1 ~= H2
-> addr_id(H1) ~= addr_id(H2)
conjecture
(excl_r(H1) | shrd_r(H1) | unc_r(H1,W1))
& (excl_r(H2) | shrd_r(H2) | unc_r(H2,W2))
& H1 ~= H2
-> addr_rid(H1) ~= addr_rid(H2)
conjecture
none_r(H1,W1) & none_r(H2,W2)
& H1 ~= H2
-> addr_nid(H1) ~= addr_nid(H2)
}
¤
Generic model of a client. This performs arbitrary client actions, except the it guarantees to use only ID's from the "client" side of the interface as defined by its parameter "side".
Todo
should be able to generate this
¤
type client_action = {ca_acquire,ca_finish,ca_perform,ca_fuse}
module tl_generic_client(mngr,ref,side) = {
action tl_Grant(msg:gnt_id) = {}
action tl_Probe(msg:prb_id) = {}
action acquire(a:acq_id) = {
call mngr.tl_Acquire(a)
}
action finish(f:fns_id) = {
call mngr.tl_Finish(f)
}
action release(f:rls_id) = {
call mngr.tl_Release(f)
}
action perform(lt:ltime, sid:id) = {
assume side(sid) = client;
call ref.perform(lt,sid)
}
action fuse(lt:ltime, lt1:ltime, sid:id) = {
assume side(sid) = client;
call ref.fuse(lt,lt1,sid)
}
action step = {
local op:client_action, lt:ltime, lt1:ltime, sid:id, a:acq_id, f:fns_id, r:rls_id {
if * {
call acquire(a)
} else if * {
call finish(f)
} else if * {
call finish(f)
} else if * {
call release(r)
} else if * {
call perform(lt,sid)
} else {
call fuse(lt,lt1,sid)
}
}
}
}
¤
Generic model of a manager. This performs arbitrary manager actions, except the it guarantees to use only ID's from the "manager" side of the interface as defined by its parameter "side".
Todo
should be able to generate this
¤
module tl_generic_manager(clnt,ref,side) = {
action tl_Acquire(msg:acq_id) = {}
action tl_Finish(msg:fns_id) = {}
action tl_Release(msg:rls_id) = {}
action grant(g:gnt_id) = {
call clnt.tl_Grant(g)
}
action probe(g:prb_id) = {
call clnt.tl_Probe(g)
}
action perform(lt:ltime, sid:id) = {
assume side(sid) = manager;
call ref.perform(lt,sid)
}
action fuse(lt:ltime, lt1:ltime, sid:id) = {
assume side(sid) = manager;
call ref.fuse(lt,lt1,sid)
}
action step = {
local lt:ltime, lt1:ltime, sid:id, g:gnt_id, p:prb_id {
if * {
call grant(g)
} else if * {
call probe(p)
} else if * {
call perform(lt,sid)
} else {
call fuse(lt,lt1,sid)
}
}
}
}