Skip to content
This repository has been archived by the owner on Dec 13, 2022. It is now read-only.

express device_implements_state_machine using only run1, without runUntilResp (on top of #959) #960

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

samuelgruetter
Copy link
Contributor

I'm trying to work top-down from device_implements_state_machine towards Cava Hmac, and the first too-high-level item that I encountered was that the read/write steps in device_implements_state_machine talk about several device steps at once (because they're using runUntilResp).
I wrote an alternative device_implements_state_machine that only uses run1, but not runUntilResp.
That should bring the interface closer to the structure of Cava proofs, hopefully.
Unfortunately, the proofs for CavaIncrementDevice.v break badly, and I haven't fixed them yet, so this is still a draft, but I'm posting it here because I'd be interested to know what @fshaked thinks about the changes I made in MMIOToCava.v, and whether they could simplify what you're working on?

not compatible any more with newer OpenTitan version
without using runUntilResp, so that each condition talks
only about one cycle, which should be closer to Cava specs
@fshaked
Copy link
Contributor

fshaked commented Nov 6, 2021

I haven't really looked at this PR yet but I noticed yesterday that runUntilResp is behaving wrong with respect to the TL protocol. The function should only send the h2d packet with a_valid=1 until it gets a d2h packet with a_ready=1, and then it should keep sending h2d packets with a_valid=0 and d_ready=1, until it gets back a d2h packet with d_valid=1. Also note that the tlul_adapter_reg implementation sends the a_ready=1 signal in the d2h packet prior to the h2d with a_valid=1. So runUntilResp must start by sending a nop_h2d (i.e. a_valid=0), check the a_ready in the returned packet, to determine if the next h2d packet will be received, and only then send the valid h2d packet.

Also, I'm redoing CavaIncrementDevice.v anyway so don't worry about breaking it.

Copy link
Contributor

@fshaked fshaked left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely helpful. I'm having trouble with maxRespDelay in my rewriting of the IncrWait device, which this refactoring might solve.

Comment on lines +67 to +79
a_valid h2d = true ->
a_opcode h2d = Get ->
a_size h2d = N.of_nat log2_nbytes ->
a_address h2d = word_to_N (state_machine.reg_addr r) ->
d_ready h2d = true ->
device.run1 sL h2d = (sL', d2h) ->
if d_valid d2h then
exists sH',
device_state_related sH' sL' /\
state_machine.read_step (2 ^ log2_nbytes) sH r (N_to_word (d_data d2h)) sH'
else
device_state_related sH sL' /\
(device.maxRespDelay sL' h2d < device.maxRespDelay sL h2d)%nat;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect in general (the original code also has the same issue).
TLDR: you never check a_ready d2h=1, and you might be sending multiple valid Get requests, instead of just one.

The process of reading from a register is broken down to two sub-tasks:

  1. Host sends a 'Get' command on channel A (h2d packet); and
  2. the device reply with an ack, that includes the value of the register, on channel D (d2h packet).

When you send an h2d packet on channel A you have to check that a_ready from the previous d2h packet is set to 1, only then you can send your h2d packet with a_valid=1, and you must send it only once (otherwise the other side might consider it as multiple messages).
After you sent the h2d packet on channel A you need to wait (i.e. send h2d packets with a_valid=0 and d_ready=1) until you get back a d2h packet with d_valid=1.

we will get an ack response and the device will end up in a state corresponding to a
high-level state reached after a high-level write, but not necessarily in the state
we used to show that sH accepts writes *)
state_machine_write_to_device_write_or_later: forall log2_nbytes r v sH sL sL' h2d d2h,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar issue as with state_machine_read_to_device_read_or_later.

@@ -186,7 +187,11 @@ Section WithParameters.
device.run1 s i := Semantics.step incr s (i, tt);
device.addr_range_start := INCR_BASE_ADDR;
device.addr_range_pastend := INCR_END_ADDR;
device.maxRespDelay := 1;
device.maxRespDelay '((istate, (val, tl_d2h)), tlul_state) h2d :=
(* if the value register was requested, and we're in state Busy1, it will take one
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is relevant, but the SW semantics allows reading the value register only when in the DONE state.

@samuelgruetter
Copy link
Contributor Author

I added one more file, Bedrock2StateMachineToCavaHmac.v, where I wrote some invariant for hmac_top, but then, when I started to think about what update_repr for hmac_top should look like, taking into account response delays that I would not want to specify deterministically, I noticed that I first should understand TLUL better, so I sketched a tlul_step that's supposed to specify the TLUL interaction at a high level. I'm not sure if that definition tlul_step will show up as-is anywhere in the final specs and proofs, but I'd still be interested in @fshaked's feedback on whether that looks about right?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants