diff --git a/hw/ip/kmac/data/kmac.hjson b/hw/ip/kmac/data/kmac.hjson index 6dc620514fd63..fc7653490ff0a 100644 --- a/hw/ip/kmac/data/kmac.hjson +++ b/hw/ip/kmac/data/kmac.hjson @@ -133,7 +133,7 @@ } { name: "NumAppIntf" type: "int" - default: "3" + default: "4" desc: "Number of application interfaces" local: "false" expose: "true" @@ -144,7 +144,7 @@ ''' type: "kmac_pkg::app_config_t" unpacked_dimensions: "[KmacNumAppIntf]" - default: "'{kmac_pkg::AppCfgKeyMgr, kmac_pkg::AppCfgLcCtrl, kmac_pkg::AppCfgRomCtrl}" + default: "'{kmac_pkg::AppCfgKeyMgr, kmac_pkg::AppCfgLcCtrl, kmac_pkg::AppCfgRomCtrl, kmac_pkg::AppCfgOtbn}" local: "false" expose: "true" } @@ -222,6 +222,7 @@ act: "rsp" package: "kmac_pkg" width: "NumAppIntf" + default: "kmac_pkg::APP_REQ_DEFAULT" } { struct: "edn" type: "req_rsp" diff --git a/hw/ip/kmac/doc/kmac-data-path.svg b/hw/ip/kmac/doc/kmac-data-path.svg new file mode 100644 index 0000000000000..4665719d86974 --- /dev/null +++ b/hw/ip/kmac/doc/kmac-data-path.svg @@ -0,0 +1,4 @@ + + + +
App arbiter
App 0
App 1
App ...
App N
Encoded output length
FSM
SW message
FIFO & packer
SW CMD
Hashing engine
App interface
Message FIFO
Data
Control
\ No newline at end of file diff --git a/hw/ip/kmac/doc/theory_of_operation.md b/hw/ip/kmac/doc/theory_of_operation.md index 11ec123caf3ae..fb386e9ee1eca 100644 --- a/hw/ip/kmac/doc/theory_of_operation.md +++ b/hw/ip/kmac/doc/theory_of_operation.md @@ -6,23 +6,32 @@ The above figure shows the KMAC/SHA3 HWIP block diagram. The KMAC has register interfaces for SW to configure the module, initiate the hashing process, and acquire the result digest from the STATE memory region. -It also has an interface to the KeyMgr to get the secret key (masked). -The IP has N x [application interfaces](#application-interface), which allows other HWIPs to request any pre-defined hashing operations. +It also has a sideload interface to the KeyMgr to get a secret key for KMAC operation. +The key is always Boolean masked with two shares. +The IP has N x [application interfaces](#application-interface), which allows other HWIPs to request hashing operations. +An application interface can either be static where the hashing operation is predefined at compile-time or it can be dynamic where the application can select the hashing mode at runtime. -As similar with HMAC, KMAC HWIP also has a message FIFO (MSG_FIFO) whose depth was determined based on a few criteria such as the register interface width, and its latency, the latency of hashing algorithm (Keccak). +Similar to HMAC, the KMAC HWIP also has a message FIFO (MSG_FIFO) whose depth was determined based on criteria such as the register interface width, its latency, and the latency of the hashing algorithm (Keccak). Based on the given criteria, the MSG_FIFO depth was determined to store the incoming message while the SHA3 core is in computation. -The MSG_FIFO has a packer in front. -It packs any partial writes into the size of internal datapath (64bit) and stores in MSG_FIFO. -It frees the software from having to align the messages. -It also doesn't need the message length information. +To support partial writes from SW and an app interface, the MSG_FIFO has a packer in front which packs writes to the size of the internal datapath (64bit). +This frees the software from having to align the messages and it also simplifies the app interface when the message length must be appended (for KMAC operation). +Note that this FIFO is bypassed if the application interface is configured to send the message in shares. The fed messages go into the KMAC core regardless of KMAC enabled or not. The KMAC core forwards the messages to SHA3 core in case KMAC hash functionality is disabled. -KMAC core prepends the encoded secret key as described in the SHA3 Derived Functions specification. +When performing a KMAC operation, the KMAC core prepends the encoded secret key as described in the SHA3 Derived Functions specification. It is expected that the software writes the encoded output length at the end of the message. For hashing operations triggered by an IP through the application interface, the encoded output length is appended inside the AppIntf module in the KMAC HWIP. +There are two ways for a key to be supplied to the KMAC core. +One way is the sideload interface that is connected to the key manager. +The other is to pass the key in two shares with registers ([`KEY_SHARE0`](registers.md#key_share0) and [`KEY_SHARE1`](registers.md#key_share1)). + +The software can set [`CFG_SHADOWED.sideload`](registers.md#cfg_shadowed) to use the sideloaded key for the SW and app-initiated KMAC operations. +The key manager always provides the sideloaded key in two-share masked form regardless of the compile-time Verilog parameter `EnMasking`. +If `EnMasking` is false, the KMAC converts the shared key to the unmasked form before the key is used. + The SHA3 core is the main Keccak processing module. It supports SHA3 hashing functions, SHAKE128, SHAKE256 extended output functions, and also cSHAKE128, cSHAKE256 functions in order to support KMAC operation. To support multiple hashing functions, it has the padding logic inside. @@ -38,13 +47,13 @@ If desired, the masking can be disabled and the internal state width can be redu ### Keccak Round -A Keccak round implements the Keccak_f function described in the SHA3 specification. -Keccak round logic in KMAC/SHA3 HWIP not only supports 1600 bit internal states but also all possible values {50, 100, 200, 400, 800, 1600} based on a parameter `Width`. -If masking is disabled via compile-time Verilog parameter `EnMasking`, also 25 can be selected as state width. +In the KMAC HWIP the Keccak round module is instantiated with a 1600 bit internal state. +Note, this Keccak round implementation not only supports 1600 bit internal states but also all possible values {50, 100, 200, 400, 800, 1600} based on a compile-time Verilog parameter `Width`. +If the compile-time Verilog parameter `EnMasking` is false, disabling masking, then the state width can also be set to 25. + Keccak permutations in the specification allow arbitrary number of rounds. This module, however, supports Keccak_f which always runs `12 + 2*L` rounds, where $$ L = log_2 {( {Width \over 25} )} $$ . For instance, 200 bits of internal state run 18 rounds. -KMAC/SHA3 instantiates the Keccak round module with 1600 bit. ![](../doc/keccak-round.svg) @@ -133,6 +142,10 @@ The message FIFO receives incoming message bitstream regardless of its byte posi Then it packs the partial message bytes into the internal 64 bit data width. After packing the data, the logic stores the data into the FIFO until the internal KMAC/SHA3 engine consumes the data. +This FIFO does not support the handling of masked input messages. +For app interfaces providing masked input messages, the FIFO is bypassed. +If the FIFO is bypassed, there is no packer and therefore these apps must always provide full 64-bit messages (except the last message). + #### FIFO Depth calculation The depth of the message FIFO is chosen to cover the throughput of the software or other producers such as DMA engine. @@ -169,78 +182,515 @@ Refer to [Preventing potential deadlocks in EDN mode](programmers_guide.md#preve #### Masking -The message FIFO does not generate the masked message data. -Incoming message bitstream is not sensitive to the leakage. -If the `EnMasking` parameter is set and [`CFG_SHADOWED.msg_mask`](registers.md#cfg_shadowed) is enabled, the message is masked upon loading into the Keccak core using the internal entropy generator. -The secret key, however, is stored as masked form always. +The hashing engine supports a fully masked operation if the `EnMasking` compile-time Verilog parameter is set. +The software, however, can only push unmasked messages into the hashing engine. +The app interfaces operate on either unmasked or masked data, depending on their parameterization. + +For all cases, if `EnMasking` is set and [`CFG_SHADOWED.msg_mask`](registers.md#cfg_shadowed) is true, the message is masked (or re-masked) upon loading into the Keccak core using the internal entropy generator. +The secret key is always stored/used in masked form. -If the `EnMasking` parameter is not set, the masking is disabled. -Then, the software has to provide the key in unmasked form by default. +If `EnMasking` is not set, masking is disabled and the software has to provide the key in unmasked form. Any write operations to [`KEY_SHARE1_0`](registers.md#key_share1) - [`KEY_SHARE1_15`](registers.md#key_share1) are ignored. -If the `EnMasking` parameter is not set and the `SwKeyMasked` parameter is set, software has to provide the key in masked form. +If `EnMasking` is not set and the `SwKeyMasked` compile-time Verilog parameter is set, software has to provide the key in masked form. Internally, the design then unmasks the key by XORing the two key shares together when loading the key into the engine. This is useful when software interface compatibility between the masked and unmasked configuration is desirable. -If the `EnMasking` parameter is set, the `SwKeyMasked` parameter has no effect: Software always provides the key in two shares. +If `EnMasking` is set, `SwKeyMasked` has no effect: Software must always provide the key in two shares. ### Keccak State Access After the Keccak round completes the KMAC/SHA3 operation, the contents of the Keccak state contain the digest value. The software can access the 1600 bit of the Keccak state directly through the window of the KMAC/SHA3 register. -If the compile-time parameter masking feature is enabled, the upper 256B of the window is the second share of the Keccak state. -If not, the upper address space is zero value. -The software reads both of the Keccak state shares and XORed in the software to get the unmasked digest value if masking feature is set. +If `EnMasking` is set, the upper 256B of the window is the second share of the Keccak state. +The software can read both of the Keccak state shares and can recover the plain, unmasked digest value by XORing the two shares. +If `EnMasking` is not set, the upper half of the window reads as zero. The Keccak state is valid after the sponge absorbing process is completed. While in an idle state or in the sponge absorbing stage, the value is zero. This ensures that the logic does not expose the secret key XORed with the keccak_f results of the prefix to the software. In addition to that, the KMAC/SHA3 blocks the software access to the Keccak state when it processes the request from KeyMgr for Key Derivation Function (KDF). -### Application Interface - -![](../doc/application-interface.svg) - -KMAC/SHA3 HWIP has an option to receive the secret key from the KeyMgr via sideload key interface. -The software should set [`CFG_SHADOWED.sideload`](registers.md#cfg_shadowed) to use the KeyMgr sideloaded key for the SW-initiated KMAC operation. -`keymgr_pkg::hw_key_t` defines the structure of the sideloaded key. -KeyMgr provides the sideloaded key in two-share masked form regardless of the compile-time parameter `EnMasking`. -If `EnMasking` is not defined, the KMAC merges the shared key to the unmasked form before uses the key. - -The IP has N number of the application interface. The apps connected to the KMAC IP may initiate the SHA3/cSHAKE/KMAC hashing operation via the application interface `kmac_pkg::app_{req|rsp}_t`. -The type of the hashing operation is determined in the compile-time parameter `kmac_pkg::AppCfg`. - -| Index | App | Algorithm | Prefix -|:-----:|:--------:|:---------:|------------ -| 0 | KeyMgr | KMAC | CSR prefix -| 1 | LC_CTRL | cSHAKE128 | "LC_CTRL" -| 2 | ROM_CTRL | cSHAKE256 | "ROM_CTRL" - -In the current version of IP, the IP has three application interfaces, which are KeyMgr, LC_CTRL, and ROM_CTRL. -KeyMgr uses the KMAC operation with CSR prefix value. -LC_CTRL and ROM_CTRL use the cSHAKE operation with the compile-time parameter prefixes. - -The app sends 64-bit data (`MsgWidth`) in a beat with the message strobe signal. -The state machine inside the AppIntf logic starts when it receives the first valid data from any of the AppIntf. -The AppIntf module chooses the winner based on the fixed priority. -Then it forwards the selected App to the next stage. -Because this logic sees the first valid data as an initiator, the Apps cannot run the hashing operation with an empty message. -After the logic switches to accept the message bitstream from the selected App, if the hashing operation is KMAC, the logic forces the sideloaded key to be used as a secret. -Also it ignores the command issued from the software. -Instead it generates the commands and sends them to the KMAC core. - -The last beat of the App data moves the state machine to append the encoded output length if the hashing operation is KMAC. -The output length is the digest width, which is 256 bit always. -It means that the logic appends `0x020100` (little-endian) to the end of the message. -The output data from this logic goes to MSG_FIFO. -Because the MSG_FIFO handles un-aligned data inside, KeyMgr interface logic sends the encoded output length value in a separate beat. - -After the encoded output length is pushed to the KMAC core, the interface logic issues a Process command to run the hashing logic. - -After hashing operation is completed, KMAC does not raise a `kmac_done` interrupt; rather it triggers the `done` status in the App response channel. -The result digest always comes in two shares. -If the `EnMasking` parameter is not set, the second share is always zero. +### Application Interfaces + +The IP has a number of instances of an application interface. +Each of these interfaces can be either static or dynamic, which is defined at compile time. +A static interface has the hashing operation defined as a compile-time parameter in its `kmac_pkg::AppCfg` struct and only a fixed digest length is returned. +A dynamic interface can specify the hashing operation at runtime and supports XOF operation (eXtendable Output Function), so an unlimited digest size can be retrieved. + +In the current version of this IP, there are the following application interfaces implemented: + +| Index | App | Type | Algorithm | Prefix for cSHAKE / KMAC | +|-------|----------|---------|-----------|------------| +| 0 | KeyMgr | Static | KMAC | "KMAC" | +| 1 | LC_CTRL | Static | cSHAKE128 | "LC_CTRL" | +| 2 | ROM_CTRL | Static | cSHAKE256 | "ROM_CTRL" | +| 3 | OTBN | Dynamic | Dynamic | "KMAC" for KMAC mode, otherwise the prefix is taken from the `PREFIX` CSRs. | + +#### Interface channels +The interface operates with two channels, each with a valid/ready handshake. +The request channel is used by the apps to initiate and control a session and also to send the message. +Once a digest is computed, the KMAC sends it back over the response channel. +The signals of the channels are described below. + +| Channel | Signal | Description | +|----------|--------------|-------------| +| Request | `req_valid` | The valid signal of the request channel. | +| Request | `data_s0` | The first share of the message data. | +| Request | `data_s1` | The second share of the message data. | +| Request | `strb` | The byte-level strobe for the message. Either all-ones, all-zeros (empty message) or a LSB-aligned contiguous mask. | +| Request | `req_last` | A flag to signal the end of the message or session. | +| Request | `req_ready` | The ready signal of the request channel. | +| Response | `rsp_valid` | The valid signal of the response channel. | +| Response | `digest_s0` | First share of the digest data. | +| Response | `digest_s1` | Second share of the digest data. | +| Response | `error` | A flag which is set if there was an error and the receiver should discard the digest. | +| Response | `rsp_finish` | A flag which is set to indicate that it is the last response of the session. | +| Response | `rsp_ready` | The ready signal of the response channel. | + +#### Configuration +The type and functionality of an interface are configured by a struct of type `app_config_t`. +The configuration options are listed in the following table. +For a more detailed description see the type definition. +Any parameter marked as 'Session' can be configured by a dynamic interface at runtime for each hashing session. +For static interfaces, these parameters are also fixed at compile time. +Note that the output length for a KMAC operation is the same as the `digest_sx` signal width (`AppDigestW`). + +| Parameter | Validity | Description | +|-----------------|----------|-------------| +| `if_type` | Static | Selects the type of the interface. Either `static` or `dynamic`. | +| `masked` | Static | Defines whether the message comes in shares or not. If `EnMasking` is enabled and `masked` is 1, both shares are forwarded as are. If `EnMasking` is disabled and `masked` is 1, the shares are XORed. If `masked` is 0, `data_s1` is ignored. | +| `prefix` | Static | A compile-time defined prefix used for cSHAKE or KMAC operations. See `prefix_mode` for when this value is used. | +| `en_unsup_comb` | Static | If 1, non-standard combinations of `mode` and `kstrength` are supported for this interface. Otherwise a non-standard combination will result in a service rejected error. | +| `prefix_mode` | Static | The `prefix_mode` determines whether to take the prefix from the CSR or use the hardcoded prefix. For static interfaces, if `prefix_mode` is 1, the `prefix` will be used for both cSHAKE and KMAC operations. If 0, the CSR value is used. For dynamic interfaces, `prefix_mode` has no effect. Independently of the value, if the `mode` is cSHAKE, the CSR prefix is used. If the mode is KMAC, the compile-time value is used. | +| `mode` | Session | The hashing mode which is performed. | +| `kstrength` | Session | The strength of the selected `mode`. Not to be confused with the output length of a hashing operation. | +| `en_xof` | Session | If 1, the app interface will automatically trigger a RUN command once it has pushed the full rate on the response channel. If 0, no squeeze can be performed at all. Usually enabled for SHAKE and cSHAKE and disabled for SHA3 and KMAC. Has no effect on static interfaces. | + +The session configuration is sent as the first message request and the configuration values are read from `data_s0` as defined by the struct `app_ses_config_t`. + +In addition to the static and session configuration, SW must configure the KMAC HWIP before an app uses its interface. +The relevant CSRs / Fields are: +- `Prefix` + - See `prefix_mode` configuration for when this CSR is relevant. +- `CFG_SHADOWED.entropy_ready` +- `CFG_SHADOWED.entropy_mode` +- `CFG_SHADOWED.entropy_fast_process` +- `CFG_SHADOWED.sideload` +- `CFG_SHADOWED.msg_mask` + - Whether masking is performed or not. + Must be set if `masked` is set in the configuration. + +See the KMAC [register description](./registers.md) for more details. +These configuration values are left to the SW as setting these requires system state knowledge, i.e., whether entropy is available or not. + +#### Message and digest datapath +The image below depicts the message data path and its related control signals. +![](../doc/kmac-data-path.svg) + +The compile-time parameter `EnMasking` and the static parameter `masked` control whether the message FIFO is used or bypassed. +The FIFO is used unless `EnMasking` and `masked` are both set in which case the FIFO is bypassed. + +For both types of interface, messages other than the last are sent using the full width of the `data_s0` / `data_s1` width. +See the operation principle section for more details about the last message. + +Although the message requests make full use of the `data_s0` / `data_s1` signal width (`MsgWidth`), the returned digest size depends on the interface's `if_type`. +A static interface response uses the full width of the `digest_s0` / `digest_s1` signals (`AppDigestW`) which allows it to transfer the full digest in one response beat. +For dynamic interfaces, the response carries smaller digest parts (`DynAppDigestW`) and only the upper bits of the `digest_s0` / `digest_s1` are invalid. +This width is chosen so that it divides the response width for most supported mode and strength combinations, especially for XOF operation. +This allows the interface not to use a strobe signal. +Doing so simplifies response handing in the receiver and it also reduces the area required if the interface is pipelined. + +The number of digest responses can be computed from the selected mode and strength. +For SHA3, the number of responses is `Strength / DynAppDigestW = Strength / 64`. +Note, for SHA3-224 this does not divide properly. +As such, the interface sends back 4 responses where the last one contains some bits which must be ignored. + +For SHAKE, cSHAKE and KMAC the standard defines the `StateWidth` to be 1600 bits (Same as SHA3) and these algorithms produce `StateWidth - 2 * Strength` bits of digest per squeeze. +A dynamic app interface then returns this digest data in `(StateWidth - 2 * Strength) / DynAppDigestW` responses. +For example, when performing a SHAKE128 operation, the interface sends `(1600 - 2*128) / 64 = 21` response beats before it triggers a RUN command. + +Digest parts are always returned in two shares. +The `masked` parameter affects only the message path. +If `EnMasking` is not active, the second share is set to `'0`. + +#### Operation principle +The app interface follows the same command order (START, PROCESS, RUN, DONE) as software but commands are implicitly send with message requests. +An FSM inside the app interface controls the hashing operation. +Its state diagram is shown below and the following text explains how an app can use the interface. +The transitions into error states from an invalid key or a hashing engine error (SHA3 error) are not drawn: see error handling section. + +Any app starts a session by placing its first request. +More than one app can have a session request active at a particular time. +The interface uses a fixed priority to arbitrate between multiple outstanding requests. + +The sequence of requests for a static app is just its sequence of message words. +A dynamic app sends the message words after sending a request carrying its desired session configuration. +When starting a session for a dynamic app, the interface checks the requested configuration. +For both types of interfaces, if the hashing operation is KMAC, the interface also checks that the `entropy_ready` bit is 1. + +If the configuration is not valid, a service rejected error is raised, see error handling section. +If the configuration is valid, the message requests are forwarded to the hashing engine. +This engine then starts absorbing, depending on the hashing mode, the key and the prefix data. +Afterwards it starts absorbing messages from the app interface. + +An app must send the full message split up into message requests which make use of the full width. +This means `strb` must be all ones. +An exception is the last message part, which can be less than the full width of the interface. +Sending this last message part is closely related to how the message phase is ended. +There are two ways for an app to terminate the message phase. +In both cases there must be at most one request which has `req_last` asserted. + +- Termination on the last data beat + - The app sets `req_last` on the beat that carries the final data bytes. + - The strobe on this beat may be partial or zero. + Its value must always be contiguous and lsb-aligned. + - A strobe of zero means no bytes are valid, representing an empty message. +- Termination with an explicit empty message + - The app sends all data beats without ever setting `req_last`. + - The last beat carrying data is allowed to have a partial strobe which must be contiguous and lsb-aligned (a strobe of zero would be the same as the first option). + - The app then sends a single empty message (`req_last = 1`, `strb = '0`) to explicitly ending the message phase. + - Once a partial beat has been sent without `req_last` asserted, no further data beats are permitted. + +In any case, a message request with `req_last = 1` causes the state machine to end the message phase. +It then starts to append the encoded output length if the hashing mode is KMAC. +This encoded output length is hard coded to `AppDigestW` which is also the size of the `digest_s0` / `digest_s1` signals. + +After the message is completely pushed into the KMAC core (or after the encoded output length if in KMAC mode), the interface logic issues a PROCESS command to run the hashing logic. + +Once the hashing operation has completed, the app interface starts to send a digest on the response channel. +Unlike as when SW initiates a hashing operation, the KMAC HW IP does not produce a `kmac_done` interrupt at this point. + +For a static app, one full digest is sent (handshaked) and the app interface returns back to its idle state. + +For a dynamic app, the interface starts to push the full rate of the hashing operation in `DynAppDigestW` sized responses. +The app can exert back pressure on the response channel to control how fast it consumes the digest data. +If `en_xof` is false, the operation is complete once the full digest has been sent. +The interface will just wait for a termination request. +If `en_xof` is true, the interface automatically sends a RUN command to the hashing engine after sending the first full digest. +It then waits until the new digest is available and begins to push responses again. +After each full digest is sent, the interface will send another RUN command to the hashing engine and repeat. + +When the app has received the desired amount of responses, it should send another "message" request with the `req_last` signal asserted. +This termination request tells the interface to stop sending digest responses and it will issue a DONE command to the hashing engine. +One final finish response (`rsp_finish=1`) is sent to the app to acknowledge the end of the session. +Once the app has sent the termination request it must make sure to drain the (pipelined) response channel until the finish response is received. +Once the interface has sent the finish response, it will return to its idle state, ready to serve the next app request. + +When the app receives the finish response, it must check it for errors. +The reason is that there could have been an error in the last digest but this was not reported immediately to satisfy to the valid locked-in property (valid locked-in means that once the valid is asserted, the data signals may not change until the handshake happened). + +In case `en_xof` is disabled, once the first full digest is sent, the interface will just wait for a termination request and not trigger any RUN commands. + +```mermaid +stateDiagram-v2 +[*] --> StIdle + +StIdle --> StAppCfg: app selected +StIdle --> StSw: Start command + +StSw --> StIdle: Done command + +StAppCfg --> StAppMsg: valid config + +StAppMsg --> StAppProcess: Last message handshaked && !KMAC +StAppMsg --> StAppOutLen: Last message handshaked && KMAC +StErrorKeyNotValid --> StErrorAwaitMsg: if APP + +StAppCfg --> StErrorAwaitMsg: invalid config +StAppOutLen --> StAppProcess: KMAC output length appended + +StAppProcess --> StAppWait + +StAppWait --> StAppPushDigest: Digest available + +StAppPushDigest --> StAppWait: DYN && digest pushed && en_xof +StAppPushDigest --> StAppFinish: STATIC && first digest part pushed +StAppPushDigest --> StAppFinish: DYN && termination request + +StAppFinish --> StIdle: finish rsp sent || STATIC + +StErrorKeyNotValid --> StErrorAwaitSw: if SW error + +StErrorAwaitMsg --> StErrorNotify: last message part received +StErrorNotify --> StErrorAwaitTermination: DYN && error rsp sent +StErrorNotify --> StErrorFinish: STATIC && error rsp sent +StErrorAwaitTermination --> StErrorFinish: termination req + +StErrorFinish --> StIdle: (finish rsp sent || STATIC) && ServiceRejected && !SHA3 error +StErrorFinish --> StErrorAwaitSw: (finish rsp sent || STATIC) && (!ServiceRejected || SHA3 error) + +StErrorAwaitSw --> StErrorAwaitAbsorb: err_processed + +StErrorAwaitAbsorb --> StIdle: absorbed + +StAppPushDigest --> StErrorPush: DYN && SHA3 error + +StErrorPush --> StAppFinish: termination req + +``` + +#### Example operation + +For a SHAKE operation via a dynamic interface instance the interactions look like shown in the waves below. +First, the app sends a request with the configuration. +Once the interface has accepted the request, the app starts sending message parts until in cycle 4 the last message part is sent. +KMAC HWIP then starts processing the data and once finished it starts to send back digest data (states AppProcess and AppWait). +This happens in cycle 7 below (in reality, it takes around 100 cycles). +Once the app has received 2 digest parts, it deasserts `rsp_ready` (cycle 9) and sends the session end request (cycle 10). +The app then must drain the response channel (cycle 10) and must wait for the finish response to arrive which is sent in cycle 11. + +```wavejson +{ + signal: [ + {name: 'App state', wave: '2.22.222...22', data: ["Idle","AppCfg","AppMsg","AppProcess","AppWait","AppPushDigest","AppFinish","Idle"]}, + {}, + ['Request', + {name: 'req_valid', wave: '01...0....10.'}, + {name: 'data_s0', wave: 'x2.22x.......', data: ["config"]}, + {name: 'data_s1', wave: 'x..22x.......'}, + {name: 'strb', wave: 'x..22x.......', data: ["","0xFF","0x03"]}, + {name: 'req_last', wave: 'x0..1x....1x.'}, + {name: 'req_ready', wave: '0.1..0....10.'}, + ], + {}, + ['Response', + {name: 'rsp_valid', wave: '0......1....0'}, + {name: 'digest_s0', wave: 'x......222.x.'}, + {name: 'digest_s1', wave: 'x......222.x.'}, + {name: 'error', wave: 'x......0....x'}, + {name: 'rsp_finish', wave: 'x......0...1x'}, + {name: 'rsp_ready', wave: '1........01.0'}, + ], + ], + edge: [], + foot:{ + tock:0 + }, + config:{hscale:2}, +} +``` + +If the app requires more digest parts than a complete rate (in this example 3), a RUN command is automatically triggered by the interface. +Note, this example starts when the last message is received and the app stalls the response in cycle 4 for one cycle. + +```wavejson +{ + signal: [ + {name: 'App state', wave: '2222...2.2..2', data: ["AppMsg","AppProcess","AppWait","AppPushDigest","AppWait","AppPushDigest","AppWait"]}, + {}, + ['Request', + {name: 'req_valid', wave: '10...........'}, + {name: 'data_s0', wave: '2x...........', data: [""]}, + {name: 'data_s1', wave: '2x...........'}, + {name: 'strb', wave: '2x...........', data: ["0x03"]}, + {name: 'req_last', wave: '1x...........'}, + {name: 'req_ready', wave: '10...........'}, + ], + {}, + ['Response', + {name: 'rsp_valid', wave: '0..1...0.1..0'}, + {name: 'digest_s0', wave: 'x..22.2x.222x'}, + {name: 'digest_s1', wave: 'x..22.2x.222x'}, + {name: 'error', wave: 'x..0...x.0..x'}, + {name: 'rsp_finish', wave: 'x..0...x.0..x'}, + {name: 'rsp_ready', wave: '1...01.......'}, + ], + ], + edge: [], + foot:{ + tock:0 + }, + config:{hscale:2}, +} +``` + +#### Error handling + +The following errors can occur when an app is active: + +- Terminal state error +- Service rejected error +- Key invalid error +- SHA3 engine command error + +The handling of these errors is described below. + +Note, the `WaitTimerExpired` error is not handled by an app interface. +The reason is that it is planned to rework when the entropy engine places EDN requests such that this error cannot occur during a hashing operation. + +##### Terminal state error +This error occurs if an FSM in the interface entered its terminal error state because one of the following is true: +- The escalate_i signal is asserted. +- The FSM itself entered an invalid state. + +The terminal error state leads to a fatal alert which will result in a chip reset. +As such, this error case does not need to end the app session gracefully. + +##### Service rejected error +This error occurs when the configuration is invalid (only for a dynamic interface) or a KMAC operation is requested when the entropy is not ready. +If the app interface rejects an application request, it enters `StErrorAwaitMsg` and the messages from the application are still accepted but directly discarded. +After such an error, no data is sent to the hashing engine until the next session. +After the last message request, the app interface then immediately sends a response with the error flag set. +The data values in this response have no meaning. +A static interface then directly returns into the Idle state without waiting for SW to set the `error_processed` bit. +A dynamic interface waits until a termination request is received and answers with a finish acknowledgment response. +If any other request is sent, the interface will deadlock as it only accepts termination requests in this state. +This finish response has the error bit reset (= 0) indicating that another operation is possible. +After sending the finish response, the interface also immediately returns to the Idle state. +The reason for immediately returning to Idle is to support the case where an app tries to use KMAC before SW is loaded. +If in this case the entropy is not ready yet the app can try again at a later point in time. + +The diagram below shows an example for a dynamic interface (note the response backpressure cycles, these are optional). + +```wavejson +{ + signal: [ + {name: 'App state', wave: '222..2.2.2.2', data: ["Idle","AppCfg","ErrorAwaitMsg","ErrorNotify","ErrorAwaitTermination","ErrorFinish","Idle"]}, + {}, + ['Request', + {name: 'req_valid', wave: '1....0..10..'}, + {name: 'data_s0', wave: '2..22x......'}, + {name: 'data_s1', wave: '2..22x......'}, + {name: 'strb', wave: '2...2x......', data: ["0xFF","0x03"]}, + {name: 'req_last', wave: '0...1x..1x..'}, + {name: 'req_ready', wave: '0.1..0.1.0..'}, + ], + {}, + ['Response', + {name: 'rsp_valid', wave: '0....1.0.1.0'}, + {name: 'digest_s0', wave: 'x....2.x....'}, + {name: 'digest_s1', wave: 'x....2.x....'}, + {name: 'error', wave: 'x1.....x.0.x'}, + {name: 'rsp_finish', wave: 'x......x.1.0'}, + {name: 'rsp_ready', wave: '0.....10..10'}, + ], + ], + edge: [], + foot:{ + tock:0 + }, + config:{hscale:2}, +} +``` + +#### Key invalid error +This error occurs if the sideloaded key is used but the key is invalid. +The sideloaded key is considered as used when either SW has full control over the KMAC or for an app session from the start of the message absorption phase (`StAppMsg`) until the digest is valid (the processing has finished, `StAppPushDigest`). + +There are three cases to consider: +- The key is already invalid when starting the operation because SW didn't configure the KeyMgr correctly. +- The key gets invalidated before the interface sent the PROCESS command. +- The key gets invalidated after the interface sent the PROCESS command. + +In case 1 & 2 the interface transitions into the `StErrorAwaitMsg` state as soon as the key is detected as invalid (when entering the message phase). +The app interface then no longer forwards message requests to the hashing engine. +Similar to the service rejected error, the interface still accepts all the message requests but discards the data. +Once the last message has arrived, a static app interface sends an error response (garbage digest with the `error` signal set). +A dynamic interface also sends an immediate error response but then waits until a termination request arrives. +The termination request is then answered with a finish response which has the error flag set (= 1). +Both interface types then wait for SW to acknowledge the error by writing to the `error_processed` bit. +Once SW has cleared the error, the interface then triggers a PROCESS command to bring the hashing engine back into the idle state. + +Case 3: If the key gets invalidated after the interface has sent the PROCESS command (i.e., if in `StAppProcess` or `StAppWait`) the message is already complete and the PROCESS command may not be issued again. +The app interface still sends the error response and a dynamic interface waits for the termination request which it responds to with a finish response which has the error flag set (= 1). +Finally, both types of interfaces wait for the SW acknowledgement. +Once acknowledged, the interface waits until the already issued PROCESS command has finished and finally returns to Idle. + +Note, the acknowledge of the software can also happen before the app accepted the response. +In case the KMAC is controlled by SW, the app related states are skipped. + +The following wave shows an example (case 2) where the key invalid error occurs in cycle 4. + +```wavejson +{ + signal: [ + {name: 'App state', wave: '2222.22.22.2.2', data: ["Idle","AppCfg","AppMsg","ErrorAwaitMsg","ErrorNotify","ErrorAwaitTermination","ErrorFinish","ErrorAwaitSw","ErrorWaitAbsorbed","Idle"]}, + {}, + ['Request', + {name: 'req_valid', wave: '1....0.10.....'}, + {name: 'data_s0', wave: '2..22x........'}, + {name: 'data_s1', wave: '2..22x........'}, + {name: 'strb', wave: '2...2x........', data: ["0xFF","0x03"]}, + {name: 'req_last', wave: '0...1x.10.....'}, + {name: 'req_ready', wave: '0.1..01.0.....'}, + ], + {}, + ['Response', + {name: 'rsp_valid', wave: '0....10.10....'}, + {name: 'digest_s0', wave: 'x....2x.......'}, + {name: 'digest_s1', wave: 'x....2x.......'}, + {name: 'error', wave: 'x..1..x.1x....'}, + {name: 'rsp_finish', wave: 'x....0x.10....'}, + {name: 'rsp_ready', wave: '0....10.10....'}, + ], + {}, + {name: 'error_processed_i', wave: '0.........10..'} + ], + edge: [], + foot:{ + tock:0 + }, + config:{hscale:2}, +} +``` + +#### SHA3 engine internal error +This error arises if an invalid command sequence is sent to the hashing engine or one of these control signals is manipulated. +Usually this error cannot occur during an app session. +However, if the control signals are faulted, this error occurs and any digest value should be considered as invalid. + +This error must be handled in two cases, namely: +- The error occurs in the message phase. +- The error occurs after the complete message is received. + +The first case is simple and is handled the same way as a key invalid error. +Once the error occurs, the message data is voided. +As soon as the complete message is received the hashing engine is brought back to idle by issuing a process and done command. +There is only one error response sent and a dynamic interface waits for the termination request. +It then waits for SW to acknowledge the error. + +If the error occurs after the complete message is received, the behavior depends on the interface type. +A static interface continues to process the message and simply sets the error flag for the digest response. +Once the digest response is sent it then waits for SW to acknowledge the error. + +A dynamic interface begins to continuously send error responses once the message is processed. +It continues to send error responses until a termination request arrives. +The interface then sends a finish response with the error flag set (= 1) and returns back to idle without waiting for SW to process the error. +The finish response must have set the error flag so that errors occurred during the last digest handshake are still propagated. + +The following wave shows an example for a dynamic interface where the error occurs in cycle 4 / after the complete message is received. +```wavejson +{ + signal: [ + {name: 'App state', wave: '2222.2..22', data: ["AppMsg","AppProcess","AppWait","AppPushDigest","ErrorPush","AppFinish","Idle"]}, + {}, + ['Request', + {name: 'req_valid', wave: '10.....10.'}, + {name: 'data_s0', wave: '2x........', data: [""]}, + {name: 'data_s1', wave: '2x........'}, + {name: 'strb', wave: '2x........', data: ["0x03"]}, + {name: 'req_last', wave: '1x.....1x.'}, + {name: 'req_ready', wave: '10.....10.'}, + ], + {}, + ['Response', + {name: 'rsp_valid', wave: '0..1.....0'}, + {name: 'digest_s0', wave: 'x..22x..2x'}, + {name: 'digest_s1', wave: 'x..22x..2x'}, + {name: 'error', wave: 'x..0.1...x'}, + {name: 'rsp_finish', wave: 'x..0....1x'}, + {name: 'rsp_ready', wave: '1.........'}, + ], + ], + edge: [], + foot:{ + tock:0 + }, + config:{hscale:2}, +} +``` ### Entropy Generator diff --git a/hw/ip/kmac/lint/kmac.waiver b/hw/ip/kmac/lint/kmac.waiver index 4c65096b0058c..794bf5c94b9eb 100644 --- a/hw/ip/kmac/lint/kmac.waiver +++ b/hw/ip/kmac/lint/kmac.waiver @@ -26,19 +26,3 @@ waive -rules {TAG_OVERLAP} -location {kmac_app.sv} \ waive -rules {INTEGER} -location {kmac_entropy.sv} \ -regexp {'i' of type int used as a non-constant value} \ -comment "int i is compared with the storage_idx" - -waive -rules {CASE_SEL_EXPR} -location {kmac_app.sv} \ - -comment "not a problem, just a suggested alternate implementation" - -waive -rules TWO_STATE_TYPE -location {kmac_pkg.sv} \ - -regexp {'PrefixMode' is of two state type} \ - -comment "Enum bit is used as a generate selection. OK to be two state" -waive -rules TWO_STATE_TYPE -location {kmac_pkg.sv} \ - -regexp {'app_mode_e' is of two state type} \ - -comment "Enum bit is used in ifs only. OK to be two state" -waive -rules TWO_STATE_TYPE -location {kmac_pkg.sv} \ - -regexp {'Mode' is of two state type} \ - -comment "Enum bit is used in ifs only. OK to be two state" -waive -rules TWO_STATE_TYPE -location {kmac_pkg.sv} \ - -regexp {'app_config_t' is of type struct which contains two state type field} \ - -comment "Struct fields waived separately. OK to be two state" diff --git a/hw/ip/kmac/rtl/kmac.sv b/hw/ip/kmac/rtl/kmac.sv index eb719248da31e..ea9e562f792b9 100644 --- a/hw/ip/kmac/rtl/kmac.sv +++ b/hw/ip/kmac/rtl/kmac.sv @@ -29,8 +29,9 @@ module kmac // Accept SW message when idle and before receiving a START command. Useful for SCA only. parameter bit SecIdleAcceptSwMsg = 1'b0, - parameter int unsigned NumAppIntf = 3, - parameter app_config_t AppCfg[NumAppIntf] = '{AppCfgKeyMgr, AppCfgLcCtrl, AppCfgRomCtrl}, + parameter int unsigned NumAppIntf = 4, + parameter app_config_t AppCfg[NumAppIntf] = '{AppCfgKeyMgr, AppCfgLcCtrl, + AppCfgRomCtrl, AppCfgOtbn}, parameter lfsr_perm_t RndCnstLfsrPerm = RndCnstLfsrPermDefault, parameter lfsr_seed_t RndCnstLfsrSeed = RndCnstLfsrSeedDefault, @@ -153,7 +154,7 @@ module kmac // SHA3 core control signals and its response. // Sequence: start --> process(multiple) --> get absorbed event --> {run -->} done - logic sha3_start, sha3_run, unused_sha3_squeeze; + logic sha3_start, sha3_run, sha3_squeezing; prim_mubi_pkg::mubi4_t sha3_done; prim_mubi_pkg::mubi4_t sha3_done_d; prim_mubi_pkg::mubi4_t sha3_absorbed; @@ -240,6 +241,7 @@ module kmac logic [kmac_pkg::MsgWidth-1:0] mux2fifo_data[Share]; logic [kmac_pkg::MsgWidth-1:0] mux2fifo_strb; logic mux2fifo_ready; + logic mux2fifo_bypass; // KMAC to SHA3 core logic msg_valid ; @@ -299,6 +301,7 @@ module kmac logic entropy_ready; entropy_mode_e entropy_mode; + logic reg_entropy_fast_process; logic entropy_fast_process; prim_mubi_pkg::mubi4_t entropy_configured; @@ -540,7 +543,7 @@ module kmac assign entropy_ready = reg2hw.cfg_shadowed.entropy_ready.q & reg2hw.cfg_shadowed.entropy_ready.qe; assign entropy_mode = entropy_mode_e'(reg2hw.cfg_shadowed.entropy_mode.q); - assign entropy_fast_process = reg2hw.cfg_shadowed.entropy_fast_process.q; + assign reg_entropy_fast_process = reg2hw.cfg_shadowed.entropy_fast_process.q; // msg_mask_en turns on the message LFSR when KMAC is enabled. assign cfg_msg_mask = reg2hw.cfg_shadowed.msg_mask.q; @@ -717,10 +720,12 @@ module kmac logic counter_error, sha3_count_error, key_index_error; logic msgfifo_counter_error; logic kmac_entropy_hash_counter_error; + logic kmac_app_counter_error; assign counter_error = sha3_count_error | kmac_entropy_hash_counter_error | key_index_error - | msgfifo_counter_error; + | msgfifo_counter_error + | kmac_app_counter_error; assign msgfifo_counter_error = msgfifo_err.valid; @@ -957,7 +962,7 @@ module kmac .lc_escalate_en_i (lc_escalate_en[2]), .absorbed_o (sha3_absorbed), - .squeezing_o (unused_sha3_squeeze), + .squeezing_o (sha3_squeezing), .block_processed_o (sha3_block_processed), @@ -1076,10 +1081,11 @@ module kmac .key_valid_o (key_valid), // to MSG_FIFO - .kmac_valid_o (mux2fifo_valid), - .kmac_data_o (mux2fifo_data), - .kmac_strb_o (mux2fifo_strb), - .kmac_ready_i (mux2fifo_ready), + .kmac_valid_o (mux2fifo_valid), + .kmac_data_o (mux2fifo_data), + .kmac_strb_o (mux2fifo_strb), + .kmac_ready_i (mux2fifo_ready), + .kmac_bypass_fifo_o(mux2fifo_bypass), // to KMAC Core .kmac_en_o (app_kmac_en), @@ -1100,11 +1106,15 @@ module kmac // Configuration: Sideloaded Key .keymgr_key_en_i (reg2hw.cfg_shadowed.sideload.q), - .absorbed_i (sha3_absorbed), // from SHA3 - .absorbed_o (app_absorbed), // to SW + .absorbed_i (sha3_absorbed), // from SHA3 + .squeezing_i(sha3_squeezing), // from SHA3 + .absorbed_o (app_absorbed), // to SW .app_active_o(app_active), + .entropy_fast_process_i(reg_entropy_fast_process), + .entropy_fast_process_o(entropy_fast_process), + .error_i (sha3_err.valid), .err_processed_i (err_processed), @@ -1122,8 +1132,8 @@ module kmac // Error report .error_o (app_err), - .sparse_fsm_error_o (kmac_app_state_error) - + .sparse_fsm_error_o (kmac_app_state_error), + .counter_error_o (kmac_app_counter_error) ); // Message FIFO @@ -1139,7 +1149,7 @@ module kmac .fifo_data_i (mux2fifo_data), .fifo_strb_i (mux2fifo_strb), .fifo_ready_o (mux2fifo_ready), - .fifo_bypass_i('0), + .fifo_bypass_i(mux2fifo_bypass), .msg_valid_o (msgfifo_valid), .msg_data_o (msgfifo_data), @@ -1161,7 +1171,7 @@ module kmac logic [sha3_pkg::StateW-1:0] reg_state_tl [Share]; always_comb begin for (int i = 0 ; i < Share; i++) begin - reg_state_tl[i] = reg_state_valid ? reg_state[i] : 'b0; + reg_state_tl[i] = reg_state_valid ? reg_state[i] : '0; end end @@ -1550,6 +1560,8 @@ module kmac alert_tx_o[1]) `ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(KeyIndexCountCheck_A, u_kmac_core.u_key_index_count, alert_tx_o[1]) + `ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(AppDigestCountCheck_A, u_app_intf.u_digest_part_counter, + alert_tx_o[1]) // Sparse FSM state error `ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(KmacCoreFsmCheck_A, u_kmac_core.u_state_regs, alert_tx_o[1]) diff --git a/hw/ip/kmac/rtl/kmac_app.sv b/hw/ip/kmac/rtl/kmac_app.sv index 54303868ee25f..6567f966e7c53 100644 --- a/hw/ip/kmac/rtl/kmac_app.sv +++ b/hw/ip/kmac/rtl/kmac_app.sv @@ -3,6 +3,14 @@ // SPDX-License-Identifier: Apache-2.0 // // KMAC Application interface +// +// This module implements the app interface which arbitrates between the SW interface and up to +// NumAppIntf hardware app interfaces. While a session is active (either an app or SW), other +// requests are stalled. +// +// There are two kind of app interfaces: Static interfaces which have a configuration defined at +// compile-time and only a one-shot digest can be retrieved. A dynamic interface can send the +// desired hashing configuration at run time and supports XOF operation. `include "prim_assert.sv" @@ -13,8 +21,9 @@ module kmac_app parameter bit EnMasking = 1'b0, localparam int Share = (EnMasking) ? 2 : 1, // derived parameter parameter bit SecIdleAcceptSwMsg = 1'b0, - parameter int unsigned NumAppIntf = 3, - parameter app_config_t AppCfg[NumAppIntf] = '{AppCfgKeyMgr, AppCfgLcCtrl, AppCfgRomCtrl} + parameter int unsigned NumAppIntf = 4, + parameter app_config_t AppCfg[NumAppIntf] = '{AppCfgKeyMgr, AppCfgLcCtrl, + AppCfgRomCtrl, AppCfgOtbn} ) ( input clk_i, input rst_ni, @@ -54,9 +63,10 @@ module kmac_app output logic [MsgWidth-1:0] kmac_data_o[Share], // This strobe is on bit level for the packer. The FIFO will then convert it again to byte level. output logic [MsgWidth-1:0] kmac_strb_o, - input kmac_ready_i, + input logic kmac_ready_i, + output logic kmac_bypass_fifo_o, - // KMAC Core + // Signal to KMAC core that a KMAC operation is ongoing. output logic kmac_en_o, // To Sha3 Core @@ -64,29 +74,28 @@ module kmac_app output sha3_pkg::sha3_mode_e sha3_mode_o, output sha3_pkg::keccak_strength_e keccak_strength_o, - // STATE from SHA3 Core + // The current keccak state from SHA3 Core input keccak_state_valid_i, input [sha3_pkg::StateW-1:0] keccak_state_i[Share], - // to STATE TL-window if Application is not active, the incoming state goes to - // register if kdf_en is set, the state value goes to application and the - // output to the register is all zero. + // The keccak state is exposed to the STATE TL-window if no application is active. If a key from + // the KeyMgr is used, the capacity region is zeroed. If an app is active the state reads as + // zero. output logic reg_state_valid_o, output logic [sha3_pkg::StateW-1:0] reg_state_o[Share], - // Controls for SW and CmdApp operations whether to take the key from the KeyMgr sideload - // interface or registers. For KMAC operations initiated by an app interface, we always take the - // sideloaded key. + // Controls for SW whether to take the key from the KeyMgr sideload interface or registers. For + // KMAC operations initiated by an app interface, we always take the sideloaded key. // If 1, the key for KMAC is taken from the KeyMgr sideload interface. // If 0, the key is taken from the registers. input logic keymgr_key_en_i, - // Commands // Command from software input kmac_cmd_e sw_cmd_i, - // from SHA3 + // Status signals from SHA3 core input prim_mubi_pkg::mubi4_t absorbed_i, + input logic squeezing_i, // to KMAC output kmac_cmd_e cmd_o, @@ -97,33 +106,40 @@ module kmac_app // To status output logic app_active_o, - // Status - // - entropy_ready_i: Entropy configured by SW. It is used to check if App - // is OK to request. + // The entropy_fast_process bit is forced to 0 when recovering an error state. + input logic entropy_fast_process_i, + output logic entropy_fast_process_o, + + // Entropy must be ready before a KMAC operation can be performed. input prim_mubi_pkg::mubi4_t entropy_ready_i, // Error input - // This error comes from KMAC/SHA3 engine. - // KeyMgr interface delivers the error signal to KeyMgr to drop the current op - // and re-initiate. - // If error happens, regardless of SW-initiated or KeyMgr-initiated, the error - // is reported to the ERR_CODE so that SW can look into. + // This error comes from KMAC/SHA3 engine and is pulsed if a wrong command sequence is detected + // or kept high as long as any control signal has an invalid value (e.g. if the done mubi signal + // is attacked to enforce a known digest value). + // This error is reported to the ERR_CODE regardless whether app or SW is active. input error_i, - // SW sets err_processed bit in CTRL then the logic goes to Idle + // Pulsed when SW acknowledges error by writing to the err_processed bit. Lets the interface + // return to idle after errors. input err_processed_i, output prim_mubi_pkg::mubi4_t clear_after_error_o, - // error_o value is pushed to Error FIFO at KMAC/SHA3 top and reported to SW + // Reports error and its type if an FIFO or FSM error occurred. output kmac_pkg::err_t error_o, // Life cycle input lc_ctrl_pkg::lc_tx_t lc_escalate_en_i, - output logic sparse_fsm_error_o + // Fatal errors + output logic sparse_fsm_error_o, + output logic counter_error_o ); + // Create a lint error to reduce the risk of accidentally enabling this feature. + `ASSERT_STATIC_LINT_ERROR(KmacSecIdleAcceptSwMsgNonDefault, SecIdleAcceptSwMsg == 0) + import sha3_pkg::KeccakBitCapacity; import sha3_pkg::L128; import sha3_pkg::L224; @@ -135,7 +151,6 @@ module kmac_app // Definitions // ///////////////// - // Digest width is same to the key width `keymgr_pkg::KeyWidth`. localparam int KeyMgrKeyW = $bits(keymgr_key_i.key[0]); localparam key_len_e KeyLengths [5] = '{Key128, Key192, Key256, Key384, Key512}; @@ -150,6 +165,7 @@ module kmac_app (AppDigestW == 256) ? 2 : (AppDigestW == 384) ? 3 : (AppDigestW == 512) ? 4 : 0 ; + // Key length is always the same as the full app digest width. localparam key_len_e SideloadedKey = KeyLengths[SelKeySize]; // Define right_encode(outlen) value here @@ -184,16 +200,15 @@ module kmac_app logic keymgr_key_used; - // app_rsp_t signals - // The state machine controls mux selection, which controls the ready signal - // the other responses are controlled in separate logic. So define the signals - // here and merge them to the response. - logic app_data_ready, fsm_data_ready; - logic app_digest_done, fsm_digest_done_q, fsm_digest_done_d; - logic [AppDigestW-1:0] app_digest [2]; + // Response channel signals + // The state machine controls the datapath mux selection and handles the requests and responses. + app_rsp_t app_rsp; + logic app_data_ready, app_req_ready, app_error_req_ready; + logic app_digest_valid; + logic app_finish_rsp_valid, app_error_rsp_valid; + logic app_finish_rsp_is_error; + logic [AppDigestW-1:0] app_digest[2]; - // One more slot for value NumAppIntf. It is the value when no app intf is - // chosen. localparam int unsigned AppIdxW = $clog2(NumAppIntf); // app_id indicates, which app interface was chosen. various logic use this @@ -211,76 +226,33 @@ module kmac_app app_mux_sel_e mux_sel_buf_err_check; app_mux_sel_e mux_sel_buf_kmac; - // Error checking logic - + // Tracking and error signals kmac_pkg::err_t fsm_err, mux_err; + logic err_sha3_during_app_set, err_sha3_during_app_d, err_sha3_during_app_q; - logic service_rejected_error; - logic service_rejected_error_set, service_rejected_error_clr; - logic err_during_sw_d, err_during_sw_q; - + ///////////////////////////// + // Application arbitration // + ///////////////////////////// + // Latch the selected app ID when FSM grants a request. Per default select ID 0. The FSM and + // datapath ensure that no data is leaked if interface is not active. always_ff @(posedge clk_i or negedge rst_ni) begin - if (!rst_ni) service_rejected_error <= 1'b 0; - else if (service_rejected_error_set) service_rejected_error <= 1'b 1; - else if (service_rejected_error_clr) service_rejected_error <= 1'b 0; - end - - //////////////////////////// - // Application Mux/ Demux // - //////////////////////////// - - - // Processing return data. - // sends to only selected app intf. - // clear digest right after done to not leak info to other interface - always_comb begin - for (int unsigned i = 0 ; i < NumAppIntf ; i++) begin - if (i == app_id) begin - app_o[i] = '{ - ready: app_data_ready | fsm_data_ready, - done: app_digest_done | fsm_digest_done_q, - digest_share0: app_digest[0], - digest_share1: app_digest[1], - // if fsm asserts done, should be an error case. - error: error_i | fsm_digest_done_q | sparse_fsm_error_o - | service_rejected_error - }; - end else begin - app_o[i] = '{ - ready: 1'b 0, - done: 1'b 0, - digest_share0: '0, - digest_share1: '0, - error: 1'b 0 - }; - end - end // for {i, NumAppIntf, i++} - end // always_comb - - // app_id latch - always_ff @(posedge clk_i or negedge rst_ni) begin - if (!rst_ni) app_id <= AppIdxW'(0) ; // Do not select any + if (!rst_ni) app_id <= AppIdxW'(0); else if (clr_appid) app_id <= AppIdxW'(0); else if (set_appid) app_id <= app_id_d; end - // app_id selection as of now, app_id uses Priority. The assumption is that - // the request normally does not collide. (ROM_CTRL activates very early - // stage at the boot sequence) - // - // If this assumption is not true, consider RR arbiter. - - // Prep for arbiter - logic [NumAppIntf-1:0] app_reqs; - logic [NumAppIntf-1:0] unused_app_gnts; + // The arbitration uses a fixed priority. The assumption is that the requests normally do no + // collide. If this assumption doesn't hold anymore, consider RR arbiter. + logic [NumAppIntf-1:0] app_req_valids; logic [$clog2(NumAppIntf)-1:0] arb_idx; logic arb_valid; logic arb_ready; + // Pick all request valid signals from the interfaces always_comb begin - app_reqs = '0; - for (int unsigned i = 0 ; i < NumAppIntf ; i++) begin - app_reqs[i] = app_i[i].valid; + app_req_valids = '0; + for (int unsigned i = 0; i < NumAppIntf; i++) begin + app_req_valids[i] = app_i[i].req_valid; end end @@ -292,9 +264,9 @@ module kmac_app .clk_i, .rst_ni, - .req_i (app_reqs), + .req_i (app_req_valids), .data_i ('{default:'0}), - .gnt_o (unused_app_gnts), + .gnt_o (), // not used .idx_o (arb_idx), .valid_o (arb_valid), @@ -305,21 +277,215 @@ module kmac_app assign app_id_d = AppIdxW'(arb_idx); assign arb_ready = set_appid; + ////////////////////////// + // App config selection // + ////////////////////////// + // Select the new configuration from either the compile-time defined configuration or from the + // supplied session configuration. This operates on the non-latched app ID so the configuration + // can be latched at the same time the ID is. + app_config_t app_cfg; + app_ses_config_t app_ses_cfg_raw; + app_ses_config_t app_ses_cfg_pending; + app_ses_config_t app_ses_cfg_d; + app_ses_config_t app_ses_cfg_q; + + // Note, the parsed value of prefix_mode is not actually used but just here to have a complete + // type. It is overwritten below. + assign app_ses_cfg_raw = app_ses_config_t'(app_i[arb_idx].data_s0[$bits(app_ses_config_t)-1:0]); + + always_comb begin + app_ses_cfg_pending.mode = AppCfg[arb_idx].session_cfg.mode; + app_ses_cfg_pending.kstrength = AppCfg[arb_idx].session_cfg.kstrength; + app_ses_cfg_pending.en_xof = AppCfg[arb_idx].session_cfg.en_xof; + app_ses_cfg_pending.prefix_mode = AppCfg[arb_idx].session_cfg.prefix_mode; + + // Overrule configuration with configuration supplied by dynamic interface. + if (AppCfg[arb_idx].if_type == AppDynamic) begin + app_ses_cfg_pending = app_ses_cfg_raw; + + // For KMAC always use prefix defined at compile time. This saves the prefix check. + // Otherwise use prefix from CSR. + app_ses_cfg_pending.prefix_mode = app_ses_cfg_pending.mode == AppKMAC ? 1'b1 : 1'b0; + end + end + + // KMAC en / SHA3 mode / Strength / configuration latching. + logic kmac_en_d, kmac_en_q; + sha3_pkg::sha3_mode_e sha3_mode_d, sha3_mode_q; + sha3_pkg::keccak_strength_e keccak_strength_d, keccak_strength_q; + + always_comb begin + app_ses_cfg_d = app_ses_cfg_q; + kmac_en_d = kmac_en_q; + sha3_mode_d = sha3_mode_q; + keccak_strength_d = keccak_strength_q; + + if (clr_appid) begin + // When an app finishes, latch values from CSRs. + app_ses_cfg_d = AppSesCfgDefault; + kmac_en_d = reg_kmac_en_i; + sha3_mode_d = reg_sha3_mode_i; + keccak_strength_d = reg_keccak_strength_i; + end else if (set_appid) begin + // When an app starts, latch its configuration. + app_ses_cfg_d = app_ses_cfg_pending; + kmac_en_d = app_ses_cfg_pending.mode == AppKMAC ? 1'b 1 : 1'b 0; + // KMAC is based upon CShake + sha3_mode_d = app_ses_cfg_pending.mode == AppSHA3 ? sha3_pkg::Sha3 : + app_ses_cfg_pending.mode == AppShake ? sha3_pkg::Shake : sha3_pkg::CShake; + keccak_strength_d = app_ses_cfg_pending.kstrength; + end else if (st == StIdle) begin + // In idle always propagate the latest CSR values as there is no latch trigger when SW starts + // a hashing operation. + app_ses_cfg_d = AppSesCfgDefault; + kmac_en_d = reg_kmac_en_i; + sha3_mode_d = reg_sha3_mode_i; + keccak_strength_d = reg_keccak_strength_i; + end + end + always_ff @(posedge clk_i or negedge rst_ni) begin - if (!rst_ni) fsm_digest_done_q <= 1'b 0; - else fsm_digest_done_q <= fsm_digest_done_d; + if (!rst_ni) begin + app_ses_cfg_q <= AppSesCfgDefault; + kmac_en_q <= 1'b 0; + sha3_mode_q <= sha3_pkg::Sha3; + keccak_strength_q <= sha3_pkg::L256; + end else begin + app_ses_cfg_q <= app_ses_cfg_d; + kmac_en_q <= kmac_en_d; + sha3_mode_q <= sha3_mode_d; + keccak_strength_q <= keccak_strength_d; + end + end + + assign kmac_en_o = kmac_en_q; + assign sha3_mode_o = sha3_mode_q; + assign keccak_strength_o = keccak_strength_q; + + // Force the entropy_fast_process bit to 0 when recovering from error. + logic disable_entropy_fast_process; + assign entropy_fast_process_o = disable_entropy_fast_process ? 1'b0 : entropy_fast_process_i; + + // Construct the complete configuration for the active app from the static and latched + // session configuration. + always_comb begin + app_cfg = AppCfg[app_id]; + app_cfg.session_cfg = app_ses_cfg_q; + end + + //////////////////////// + // App config checker // + //////////////////////// + logic valid_app_sha3_strength; + logic valid_app_shake_strength; + logic valid_app_kmac_cfg; + logic valid_app_mode_strength_raw; + logic valid_app_mode_strength; + logic valid_app_cfg; + + assign valid_app_sha3_strength = app_cfg.session_cfg.kstrength inside {sha3_pkg::L224, + sha3_pkg::L256, + sha3_pkg::L384, + sha3_pkg::L512}; + + assign valid_app_shake_strength = app_cfg.session_cfg.kstrength inside {sha3_pkg::L128, + sha3_pkg::L256}; + + assign valid_app_mode_strength_raw = + app_cfg.session_cfg.mode == AppSHA3 ? valid_app_sha3_strength : + app_cfg.session_cfg.mode inside {AppShake, AppCShake, AppKMAC} ? valid_app_shake_strength : + 1'b0; + + // Ignore the mode and strength check if app allows unsupported combinations. + assign valid_app_mode_strength = valid_app_mode_strength_raw || app_cfg.en_unsup_comb; + + assign valid_app_kmac_cfg = prim_mubi_pkg::mubi4_test_true_strict(entropy_ready_i); + + assign valid_app_cfg = valid_app_mode_strength && + (app_cfg.session_cfg.mode == AppKMAC ? valid_app_kmac_cfg : 1'b1); + + // A compile-time defined configuration must always result in a valid mode-strength + // configuration. + `ASSERT(ConfigAlwaysValidIfStatic_A, + app_active_o && app_cfg.if_type == AppStatic |-> valid_app_mode_strength) + + ///////////////////////////// + // Application Mux / Demux // + ///////////////////////////// + // The active app's request and response channel is muxed here from / to local signals. + app_req_t app_req; + assign app_req = app_i[app_id]; + + // There are three ready sources which can accept a request. The app_data_ready active in the + // message phase and is driven by the FIFO. The app_req_ready is controlled by the FSM to accept + // the control requests (start/config, process, and termination). The app_error_req_ready is used + // to accept messages and control requests during the error handling. + // Similarly, there are three response generators. One drives digest responses, one the finish + // response and one the error responses. + assign app_rsp = '{ + req_ready: app_data_ready | app_error_req_ready | app_req_ready, + rsp_valid: app_digest_valid | app_finish_rsp_valid | app_error_rsp_valid, + digest_s0: app_digest[0], + digest_s1: app_digest[1], + error: app_error_rsp_valid | app_finish_rsp_is_error | + // Must use _d as error is relevant in cycle the digest is returned. + (err_sha3_during_app_d & app_cfg.if_type == AppStatic), + rsp_finish: app_finish_rsp_valid + }; + + // The three response generators may never be active at the same time because otherwise responses + // would collide. + `ASSERT(AppOnlyOneRspSourceActive_A, + $onehot0({app_digest_valid, app_finish_rsp_valid, app_error_rsp_valid})) + + always_comb begin + for (int unsigned i = 0; i < NumAppIntf; i++) begin + if (i == app_id) begin + app_o[i] = app_rsp; + end else begin + app_o[i] = APP_RSP_DEFAULT; + end + end end ///////// // FSM // ///////// + logic clear_app_trackers; + + logic any_request_outstanding; + logic last_req_pending; + logic process_cmd_pending; + logic last_req_hs; + + logic last_msg_part_received_d, last_msg_part_received_q; + logic last_msg_part_received_set; + + logic process_cmd_sent_d, process_cmd_sent_q; + logic process_cmd_sent_set; + + logic pending_digest_rsp_d, pending_digest_rsp_q; + logic pending_error_rsp_d, pending_error_rsp_q; + + logic digest_parts_available; + logic squeeze_again; + logic app_push_digest; + logic clear_digest_pusher; + + logic service_rejected_error_d, service_rejected_error_q; + logic service_rejected_error_set; + + logic err_key_used_but_invalid; + logic err_key_used_but_invalid_set; + logic err_key_used_but_invalid_clear; + logic err_key_used_but_invalid_d, err_key_used_but_invalid_q; + logic err_during_sw_d, err_during_sw_q; + logic err_during_sw_set; + logic err_processed_d, err_processed_q; // State register `PRIM_FLOP_SPARSE_FSM(u_state_regs, st_d, st, st_e, StIdle) - // Create a lint error to reduce the risk of accidentally enabling this feature. - `ASSERT_STATIC_LINT_ERROR(KmacSecIdleAcceptSwMsgNonDefault, SecIdleAcceptSwMsg == 0) - // Next State & output logic // SEC_CM: FSM.SPARSE always_comb begin @@ -331,77 +497,102 @@ module kmac_app set_appid = 1'b 0; clr_appid = 1'b 0; - // Commands + // Command towards the KMAC core cmd_o = CmdNone; // Software output absorbed_o = prim_mubi_pkg::MuBi4False; - // Error - fsm_err = '{valid: 1'b 0, code: ErrNone, info: '0}; - sparse_fsm_error_o = 1'b 0; + // Bypass FIFO if masked app is active. + kmac_bypass_fifo_o = 1'b0; + + // Ready signal to handshake requests for dynamic interfaces. + app_req_ready = 1'b0; - clear_after_error_o = prim_mubi_pkg::MuBi4False; + // Control signals for the digest pusher. + app_push_digest = 1'b0; + clear_digest_pusher = 1'b0; - service_rejected_error_set = 1'b 0; - service_rejected_error_clr = 1'b 0; + // Finish response + app_finish_rsp_valid = 1'b0; + app_finish_rsp_is_error = 1'b0; - // If error happens, FSM asserts data ready but discard incoming msg - fsm_data_ready = 1'b 0; - fsm_digest_done_d = 1'b 0; + // Error tracking and handling + fsm_err = '{valid: 1'b0, code: ErrNone, info: '0}; + sparse_fsm_error_o = 1'b0; + clear_after_error_o = prim_mubi_pkg::MuBi4False; + service_rejected_error_set = 1'b0; + err_sha3_during_app_set = 1'b0; + err_key_used_but_invalid_clear = 1'b0; + process_cmd_sent_set = 1'b0; + last_msg_part_received_set = 1'b0; + err_during_sw_set = 1'b0; + clear_app_trackers = 1'b0; + app_error_req_ready = 1'b0; + app_error_rsp_valid = 1'b0; + + disable_entropy_fast_process = 1'b0; unique case (st) StIdle: begin + clear_app_trackers = 1'b1; + if (arb_valid) begin + // An app was chosen, latch the ID. st_d = StAppCfg; - - // choose app_id - set_appid = 1'b 1; + set_appid = 1'b1; end else if (sw_cmd_i == CmdStart) begin - st_d = StSw; // Software initiates the sequence + st_d = StSw; cmd_o = CmdStart; - end else begin - st_d = StIdle; end end StAppCfg: begin - if (AppCfg[app_id].Mode == AppKMAC && - prim_mubi_pkg::mubi4_test_false_strict(entropy_ready_i)) begin - // Check if the entropy is not configured but it is needed in - // `AppCfg[app_id]` (KMAC mode). - // - // SW is not properly configured, report and not request Hashing - // Return the app with errors - st_d = StError; - - service_rejected_error_set = 1'b 1; - + if (!valid_app_cfg) begin + // Either the entropy source was not marked as ready for a KMAC operation, or the App + // configuration is invalid, or the configuration in the registers supplied by SW is + // invalid. In this error case we still go to the "message absorb" phase but no data is + // forwarded to the actual SHA3 core. This simplifies the error handling on the + // application side. + st_d = StErrorAwaitMsg; + service_rejected_error_set = 1'b1; end else begin - // As Cfg is stable now, it sends cmd - st_d = StAppMsg; - - // App initiates the data + // The configuration is valid and also latched. We can now send the start command and + // begin to absorb the message. + st_d = StAppMsg; cmd_o = CmdStart; end + // Handshake the configuration request also if there is an error. This is required as an + // application interface still must send the message. + // For static interfaces we stall the first request as it already contains data. + app_req_ready = app_cfg.if_type == AppDynamic; end StAppMsg: begin - mux_sel = SelApp; - if (app_i[app_id].valid && app_o[app_id].ready && app_i[app_id].last) begin - if (AppCfg[app_id].Mode == AppKMAC) begin + mux_sel = SelApp; + kmac_bypass_fifo_o = app_cfg.masked; + + // Accept any process command immediately and do not forward the request to the message + // FIFO. + app_req_ready = process_cmd_pending; + last_msg_part_received_set = last_req_hs; + + if (last_req_hs) begin + if (app_cfg.session_cfg.mode == AppKMAC) begin st_d = StAppOutLen; end else begin st_d = StAppProcess; end - end else begin - st_d = StAppMsg; + end else if (err_sha3_during_app_q) begin + // Handshaking last message part has priority as this error is also checked afterwards. + st_d = StErrorAwaitMsg; end end StAppOutLen: begin - mux_sel = SelOutLen; + mux_sel = SelOutLen; + kmac_bypass_fifo_o = app_cfg.masked; if (kmac_valid_o && kmac_ready_i) begin st_d = StAppProcess; @@ -411,19 +602,83 @@ module kmac_app end StAppProcess: begin - cmd_o = CmdProcess; - st_d = StAppWait; + cmd_o = CmdProcess; + st_d = StAppWait; + // Bypass must be stable until process command has been sent. + kmac_bypass_fifo_o = app_cfg.masked; + process_cmd_sent_set = 1'b1; end StAppWait: begin - if (prim_mubi_pkg::mubi4_test_true_strict(absorbed_i)) begin - // Send digest to KeyMgr and complete the op - st_d = StIdle; - cmd_o = CmdDone; + // absorbed_i is pulsed once when the first process command ends. squeezing_i is high once + // the processing or subsequent squeezes have finished. This is only relevant for dynamic + // interfaces. + if (prim_mubi_pkg::mubi4_test_true_strict(absorbed_i) || + (squeezing_i && app_cfg.if_type == AppDynamic)) begin + st_d = StAppPushDigest; + clear_digest_pusher = 1'b1; + end + end - clr_appid = 1'b 1; + StAppPushDigest: begin + // Static interface: + // - Return full digest in one response and return to idle (via finish). + // Dynamic interface: + // - Push the available digest / rate in parts. + // - For SHAKE and CSHAKE, if digest / rate is fully pushed, trigger a squeeze. + // - End the session if a termination request arrives. + app_push_digest = 1'b1; + + if (app_cfg.if_type == AppStatic) begin + if (app_rsp.rsp_valid && app_req.rsp_ready) begin + // Must be on _d signal to capture the error in the same cycle. + st_d = err_sha3_during_app_d ? StErrorAwaitSw : StAppFinish; + end end else begin - st_d = StAppWait; + // Ending a session by handshaking the request takes priority over 1) handling a SHA3 + // error as it is checked for when sending the response and 2) squeezing to avoid + // starting an obsolete squeeze operation. + if (last_req_pending) begin + st_d = StAppFinish; + app_req_ready = 1'b1; + end else if (err_sha3_during_app_q) begin + st_d = StErrorPush; + end else if (squeeze_again && !pending_digest_rsp_d) begin + // Trigger a squeeze if there should be sent more digest parts. Ensure that there + // is no pending response which would be 'killed' when changing the state. + cmd_o = CmdManualRun; + st_d = StAppWait; + end + end + end + + StAppFinish: begin + if (app_cfg.if_type == AppStatic) begin + // Immediately end the session for static interfaces. + st_d = StIdle; + cmd_o = CmdDone; + clr_appid = 1'b1; + end else begin + // Await handshake of pending response from last cycle(s) (digest or error response). + // Otherwise the finish response would violate the valid locked-in principle. + app_push_digest = pending_digest_rsp_q; + app_error_rsp_valid = pending_error_rsp_q; + if (!pending_digest_rsp_q && !pending_error_rsp_q) begin + // We now can send the finish response. Send again the error flag to cover the case the + // error occurred whilst the last digest response was pending. + app_finish_rsp_valid = 1'b1; + app_finish_rsp_is_error = err_sha3_during_app_q; + + // Once the finish response is handshaked the session can be terminated. + if (app_finish_rsp_valid && app_req.rsp_ready) begin + st_d = StIdle; + cmd_o = CmdDone; + clr_appid = 1'b1; + if (err_sha3_during_app_q) begin + clear_after_error_o = prim_mubi_pkg::MuBi4True; + end + end + end end end @@ -440,102 +695,108 @@ module kmac_app end end - StKeyMgrErrKeyNotValid: begin - st_d = StError; + StErrorKeyNotValid: begin + // Signal the error to SW and start the error recovery. This state won't accept any + // requests, so we cannot miss the last message request. + st_d = StErrorAwaitMsg; + + // Clear the error as we now start handling it. + err_key_used_but_invalid_clear = 1'b1; - // As mux_sel is not set to SelApp, app_data_ready is still 0. - // This logic won't accept the requests from the selected App. + // Report the error to SW. fsm_err.valid = 1'b 1; fsm_err.code = ErrKeyNotValid; fsm_err.info = 24'(app_id); end - StError: begin - // In this state, the state machine flush out the request - st_d = StError; + StErrorAwaitMsg: begin + if (err_during_sw_q) begin + // Only wait for SW to ack the error. + st_d = StErrorAwaitSw; + end else begin + // Track if the last message arrives. + last_msg_part_received_set = last_req_hs; + + // Once the last message arrived, send error response in next cycle. We must use the _q + // signal to avoid 1) a timing loop with the ready and 2) handshaking a pending + // termination request in case we enter this error state after the message has already + // been received. + if (last_msg_part_received_q) begin + st_d = StErrorNotify; + end else begin + // Continue to absorb data or a process command on the app interface. + app_error_req_ready = 1'b1; + end + end + end - // Absorb data on the app interface. - fsm_data_ready = ~err_during_sw_q; + StErrorNotify: begin + // Send error response back to app. Once response is handshaked, wait for termination + // request. Static interfaces don't wait for a termination request and skip this state. + app_error_rsp_valid = 1'b1; - // Next step depends on two conditions: - // 1) Error being processed by SW - // 2) Last data provided from the app interface (so that the app interface is completely) - // drained. If the error occurred during a SW operation, the app interface is not - // involved, so this condition gets skipped. - unique case ({err_processed_i, - (app_i[app_id].valid && app_i[app_id].last) || err_during_sw_q}) - 2'b00: begin - // Error not processed by SW and not last data from app interface -> keep current state. - st_d = StError; - end - 2'b01: begin - // Error not processed by SW but last data from app interface: - // 1. Send garbage digest to the app interface (in the next cycle) to complete the - // transaction. - fsm_digest_done_d = ~err_during_sw_q; - if (service_rejected_error) begin - // 2.a) Service was rejected because an app interface tried to configure KMAC while no - // entropy was available. It is assumed that SW is not loaded yet, so don't wait for - // SW to process the error. The last data from the app interface has now arrived, but - // we don't need to wait for the SHA3 core to have absorbed it because the data never - // entered the SHA3 core: the request from the app interface was terminated during the - // configuration phase. - st_d = StErrorServiceRejected; - end else begin - // 2.b) If service was not rejected, wait for SW to process the error. - st_d = StErrorAwaitSw; - end - end - 2'b10: begin - // Error processed by SW but not last data from app interface -> wait for app interface. - st_d = StErrorAwaitApp; - end - 2'b11: begin - // Error processed by SW and last data from app interface: - // Send garbage digest to the app interface (in the next cycle) to complete the - // transaction. - fsm_digest_done_d = ~err_during_sw_q; - // Flush the message FIFO and let the SHA3 engine compute a digest (which won't be used - // but serves to bring the SHA3 engine back to the idle state). - cmd_o = CmdProcess; - st_d = StErrorWaitAbsorbed; - end - default: st_d = StError; - endcase + if (app_rsp.rsp_valid && app_req.rsp_ready) begin + st_d = app_cfg.if_type == AppDynamic ? StErrorAwaitTermination : StErrorFinish; + end end - StErrorAwaitSw: begin - // Just wait for SW to process the error. - if (err_processed_i) begin - // Flush the message FIFO and let the SHA3 engine compute a digest (which won't be used - // but serves to bring the SHA3 engine back to the idle state). - cmd_o = CmdProcess; - st_d = StErrorWaitAbsorbed; + StErrorAwaitTermination: begin + // A state entered only for dynamic interfaces. Wait for the app to send a termination + // request. + if (app_req.req_valid && app_req.req_last) begin + st_d = StErrorFinish; + app_req_ready = 1'b1; end end - StErrorAwaitApp: begin - // Keep absorbing data on the app interface until the last data. - fsm_data_ready = 1'b1; - if (app_i[app_id].valid && app_i[app_id].last) begin - // Send garbage digest to the app interface (in the next cycle) to complete the - // transaction. - fsm_digest_done_d = 1'b1; + StErrorFinish: begin + // Send finish response (dynamic) or exit immediately (static). + // For service-rejected errors, return to idle without SW interaction. + // For other errors, wait for SW to acknowledge the error. + + // A SHA3 error can occur after the service rejected error is latched. Thus only send an + // error-free finish response if the only error case is a service rejected error. + app_finish_rsp_valid = app_cfg.if_type == AppDynamic; + app_finish_rsp_is_error = !service_rejected_error_q || err_sha3_during_app_q; + + if ((app_finish_rsp_valid && app_req.rsp_ready) || (app_cfg.if_type == AppStatic)) begin + if (!app_finish_rsp_is_error) begin + clr_appid = 1'b1; + clear_after_error_o = prim_mubi_pkg::MuBi4True; + st_d = StIdle; + end else begin + st_d = StErrorAwaitSw; + end + end + end + + StErrorAwaitSw: begin + // Wait for SW to have processed the error. + if (err_processed_q) begin // Flush the message FIFO and let the SHA3 engine compute a digest (which won't be used - // but serves to bring the SHA3 engine back to the idle state). - cmd_o = CmdProcess; - st_d = StErrorWaitAbsorbed; + // but serves to bring the SHA3 engine back to the idle state). Skip the command if + // error occurred after regular PROCESS command has been sent and the processing is + // already ongoing or has already finished. Note, the process command is not tracked + // during SW operation to have the same behaviour as the KMAC before the extended app + // interface was added. + st_d = StErrorAwaitAbsorbed; + + if (!process_cmd_sent_q) begin + cmd_o = CmdProcess; + end end end - StErrorWaitAbsorbed: begin - if (prim_mubi_pkg::mubi4_test_true_strict(absorbed_i)) begin + StErrorAwaitAbsorbed: begin + // Wait until the hashing engine has finished computing the garbage digest. Use the + // squeezing_i signal to cover the case when the engine finishes before we wait for it. + + if (squeezing_i) begin // Clear internal variables, send done command, and return to idle. - clr_appid = 1'b1; + clr_appid = 1'b1; clear_after_error_o = prim_mubi_pkg::MuBi4True; - service_rejected_error_clr = 1'b1; - cmd_o = CmdDone; - st_d = StIdle; + cmd_o = CmdDone; + st_d = StIdle; // If error originated from SW, report 'absorbed' to SW. if (err_during_sw_q) begin absorbed_o = prim_mubi_pkg::MuBi4True; @@ -543,12 +804,22 @@ module kmac_app end end - StErrorServiceRejected: begin - // Clear internal variables and return to idle. - clr_appid = 1'b1; - clear_after_error_o = prim_mubi_pkg::MuBi4True; - service_rejected_error_clr = 1'b1; - st_d = StIdle; + StErrorPush: begin + // State for dynamic interfaces only. + // An error occurred while pushing digest parts. Keep sending error responses until the + // app sends a termination request. Then close the session normally. + + // Continue sending pending response to adhere to valid locked-in principle. + if (pending_digest_rsp_q) begin + app_push_digest = 1'b1; + end else begin + // Now continuously send error responses until termination request arrives. + app_error_rsp_valid = 1'b1; + if (app_req.req_valid && app_req.req_last) begin + app_req_ready = 1'b1; + st_d = StAppFinish; + end + end end StTerminalError: begin @@ -566,89 +837,195 @@ module kmac_app end endcase + // Error out if the key is used but it is invalid. Do not transition if: + // - A request is outstanding (valid request but no handshake in this cycle). Otherwise a valid + // could be dropped which could bring the hashing engine in a non recoverable state. + // - When the key error handling starts. Otherwise the FSM would get trapped. + // Note, the key error is cleared when the error handling starts. + if (err_key_used_but_invalid && !any_request_outstanding && st != StErrorKeyNotValid) begin + st_d = StErrorKeyNotValid; + // Track if error occurred during SW. + err_during_sw_set = st == StSw; + end + + // Track whether a hashing engine error occurred. + err_sha3_during_app_set = + error_i && + (st inside {StAppCfg, StAppMsg, StAppOutLen, StAppProcess, StAppWait, StAppPushDigest}); + + // Enforce the entropy_fast_process bit is set to 0 for the duration of the error handling. + // This ensures the hashing updates the PRNG and hence the masking is fully enabled. This + // in turn ensures that no improperly masked hashing operation can be triggered by an + // adversary. + disable_entropy_fast_process = + st inside {StErrorKeyNotValid, StErrorAwaitMsg, StErrorNotify, StErrorAwaitTermination, + StErrorFinish, StErrorAwaitSw, StErrorAwaitAbsorbed}; + // SEC_CM: FSM.GLOBAL_ESC, FSM.LOCAL_ESC - // Unconditionally jump into the terminal error state - // if the life cycle controller triggers an escalation. + // Unconditionally jump into the terminal error state if the life cycle controller triggers an + // escalation. if (lc_ctrl_pkg::lc_tx_test_true_loose(lc_escalate_en_i)) begin st_d = StTerminalError; end - - // Handle errors outside the terminal error state. - if (st_d != StTerminalError) begin - // Key from keymgr is used but not valid, so abort into the invalid key error state. - if (keymgr_key_used && !keymgr_key_i.valid) begin - st_d = StKeyMgrErrKeyNotValid; - end - end end + assign any_request_outstanding = app_req.req_valid && !app_rsp.req_ready; + + assign last_req_pending = app_req.req_valid && app_req.req_last; + // An empty message request issues a process command and does not forward any message data. + assign process_cmd_pending = last_req_pending && app_req.strb == '0; + assign last_req_hs = last_req_pending && app_rsp.req_ready; + + // Track when last message part is handshaked. Note, this also tracks process commands. + assign last_msg_part_received_d = + clear_app_trackers ? 1'b0 : // clear + last_msg_part_received_set ? 1'b1 : // set + last_msg_part_received_q; // hold + + assign process_cmd_sent_d = + clear_app_trackers ? 1'b0 : // clear + process_cmd_sent_set ? 1'b1 : // set + process_cmd_sent_q; // hold + + // Track when SW acknowledges the error. Only set flag if this bit is relevant for the app + // interface, i.e., a hashing operation is ongoing (this includes SW- and app-triggered + // operations). + assign err_processed_d = + clear_app_trackers ? 1'b0 : // clear + (err_processed_i && (st != StIdle)) ? 1'b1 : // set + err_processed_q; // hold + // Track errors occurring in SW mode. - assign err_during_sw_d = - (mux_sel == SelSw) && (st_d inside {StError, StKeyMgrErrKeyNotValid}) ? 1'b1 : // set - (st_d == StIdle) ? 1'b0 : // clear - err_during_sw_q; // hold + assign err_during_sw_d = clear_app_trackers ? 1'b0 : // clear + err_during_sw_set ? 1'b1 : // set + err_during_sw_q; // hold + + // Track hashing engine errors + assign err_sha3_during_app_d = clear_app_trackers ? 1'b0 : // clear + err_sha3_during_app_set ? 1'b1 : // set + err_sha3_during_app_q; // hold + + // Track key invalid errors + assign err_key_used_but_invalid_d = clear_app_trackers ? 1'b0 : // clear + err_key_used_but_invalid_clear ? 1'b0 : // clear when handled + err_key_used_but_invalid_set ? 1'b1 : // set + err_key_used_but_invalid_q; // hold + + // Factor out the clearing as it would affect the state transition logic but we want all + // state dependent transition logic inside the FSM. + assign err_key_used_but_invalid = err_key_used_but_invalid_set || err_key_used_but_invalid_q; + + // Track service rejected errors + assign service_rejected_error_d = clear_app_trackers ? 1'b0 : // clear + service_rejected_error_set ? 1'b1 : // set + service_rejected_error_q; // hold always_ff @(posedge clk_i or negedge rst_ni) begin if (!rst_ni) begin - err_during_sw_q <= 1'b0; + last_msg_part_received_q <= 1'b0; + process_cmd_sent_q <= 1'b0; + pending_digest_rsp_q <= 1'b0; + pending_error_rsp_q <= 1'b0; + service_rejected_error_q <= 1'b0; + err_processed_q <= 1'b0; + err_during_sw_q <= 1'b0; + err_sha3_during_app_q <= 1'b0; + err_key_used_but_invalid_q <= 1'b0; end else begin - err_during_sw_q <= err_during_sw_d; + last_msg_part_received_q <= last_msg_part_received_d; + process_cmd_sent_q <= process_cmd_sent_d; + pending_digest_rsp_q <= pending_digest_rsp_d; + pending_error_rsp_q <= pending_error_rsp_d; + service_rejected_error_q <= service_rejected_error_d; + err_processed_q <= err_processed_d; + err_during_sw_q <= err_during_sw_d; + err_sha3_during_app_q <= err_sha3_during_app_d; + err_key_used_but_invalid_q <= err_key_used_but_invalid_d; end end + // Check that dynamic app only states are never entered for a static app. + `ASSERT(InvalidStateForStaticApp_A, + st inside {StErrorPush, StErrorAwaitTermination} |-> (app_cfg.if_type == AppDynamic), + clk_i, rst_ni) + ////////////// // Datapath // ////////////// - // Encoded output length + // Encoded output length for the KMAC operation. The length is based upon the full app interface + // response width. assign encoded_outlen = EncodedOutLen[SelDigSize]; assign encoded_outlen_strb = EncodedOutLenStrb[SelKeySize]; // Data mux - // This is the main part of the KeyMgr interface logic. - // The FSM selects KeyMgr interface in a cycle after it receives the first - // valid data from KeyMgr. The ready signal to the KeyMgr data interface - // represents the MSG_FIFO ready, only when it is in StKeyMgrMsg state. - // After KeyMgr sends last beat, the kmac interface (to MSG_FIFO) is switched - // to OutLen. OutLen is pre-defined values. See `EncodeOutLen` parameter above. + // This is the main datapath part of the app interface logic. + // Once the FSM selected an interface and checked its configuration the message FIFO interface is + // exposed to the interface. For a KMAC operation, as soon as the app has sent the last message + // the FSM pushes the encoded output length into the message FIFO. This is a predefined value, + // see `EncodeOutLen` parameter above. + // + // The data is forwarded depending on the masking parameters: + // EnMasking = 1: + // - For static and dynamic interface: + // - Masked = 0: Forward share 0. Set share 1 to '0. + // - Masked = 1: Forward both shares. + // EnMasking = 0 + // - For static and dynamic interface: + // - Masked = 0: Forward share 0. Ignore share 1. + // - Masked = 1: Forward XOR of both shares. always_comb begin app_data_ready = 1'b 0; + + // Always accept a SW request even if SW is not active to avoid deadlocking the software. If + // SW is not active, this request is ignored and will trigger an error (handled outside). sw_ready_o = 1'b 1; kmac_valid_o = 1'b 0; - kmac_data_o = '{default:'0}; - kmac_strb_o = '0; + kmac_data_o = '{default: '0}; + kmac_strb_o = '0; unique case (mux_sel_buf_kmac) SelApp: begin - // app_id is valid at this time - kmac_valid_o = app_i[app_id].valid; - kmac_data_o[0] = app_i[app_id].data; - // Expand strb to bits. prim_packer inside MSG_FIFO accepts the bit masks - for (int i = 0 ; i < $bits(app_i[app_id].strb) ; i++) begin - kmac_strb_o[8*i+:8] = {8{app_i[app_id].strb[i]}}; + // Forward message only if it contains data. An empty message / process command is not + // forwarded. + kmac_valid_o = app_req.req_valid && !process_cmd_pending; + if (EnMasking) begin + kmac_data_o[0] = app_req.data_s0; + kmac_data_o[1] = app_cfg.masked ? app_req.data_s1 : '0; + end else begin + // If the interface is masked but the KMAC core not, then we unmask the input data. + kmac_data_o[0] = app_cfg.masked ? app_req.data_s0 ^ app_req.data_s1 : app_req.data_s0; + end + // Expand byte strobe to bits for the prim_packer inside MSG_FIFO. + for (int i = 0; i < MsgStrbW; i++) begin + kmac_strb_o[8 * i +: 8] = {8{app_req.strb[i]}}; end app_data_ready = kmac_ready_i; end SelOutLen: begin - // Write encoded output length value - kmac_valid_o = 1'b 1; // always write + // Write encoded output length value as unmasked data (share 1 = '0). + kmac_valid_o = 1'b1; // always write kmac_data_o[0] = MsgWidth'(encoded_outlen); kmac_strb_o = MsgWidth'(encoded_outlen_strb); end SelSw: begin + // SW supports only one share kmac_valid_o = sw_valid_i; - kmac_data_o[0] = sw_data_i ; - kmac_strb_o = sw_strb_i ; - sw_ready_o = kmac_ready_i ; + kmac_data_o[0] = sw_data_i; + for (int i = 1; i < Share; i++) begin + kmac_data_o[i] = '0; + end + kmac_strb_o = sw_strb_i; + sw_ready_o = kmac_ready_i; end default: begin // Incl. SelNone kmac_valid_o = 1'b 0; - kmac_data_o = '{default:'0}; - kmac_strb_o = '0; + kmac_data_o = '{default: '0}; + kmac_strb_o = '0; end endcase @@ -718,19 +1095,20 @@ module kmac_app ); // Keccak state Demux - // Keccak state --> Register output is enabled when state is in StSw + // The state is only exposed to the registers if SW is active. always_comb begin - reg_state_valid = 1'b 0; - reg_state_o = '{default:'0}; + reg_state_valid = 1'b0; + reg_state_o = '{default: '0}; + if ((mux_sel_buf_output == SelSw) && lc_ctrl_pkg::lc_tx_test_false_strict(lc_escalate_en_i)) begin reg_state_valid = keccak_state_valid_i; - reg_state_o = keccak_state_i; - // If key is sideloaded and KMAC is SW initiated - // hide the capacity from SW by zeroing (see #17508) + reg_state_o = keccak_state_i; + // If key is sideloaded and KMAC is SW initiated hide the capacity from SW by zeroing. + // See https://github.com/lowRISC/opentitan/issues/17508. if (keymgr_key_en_i) begin for (int i = 0; i < Share; i++) begin - unique case (reg_keccak_strength_i) + unique case (keccak_strength_q) L128: reg_state_o[i][sha3_pkg::StateW-1-:KeccakBitCapacity[L128]] = '0; L224: reg_state_o[i][sha3_pkg::StateW-1-:KeccakBitCapacity[L224]] = '0; L256: reg_state_o[i][sha3_pkg::StateW-1-:KeccakBitCapacity[L256]] = '0; @@ -743,26 +1121,148 @@ module kmac_app end end - // Keccak state --> App interface + /////////////////// + // Digest pusher // + /////////////////// + // The maximal number of digest chunks is defined by the operation with the largest rate. The + // bit-width of the rate for any XOF algorithm can be computed with: StateW - 2 * Strength. + // And as SHA3, SHAKE, cSHAKE and KMAC have always a state width of 1600, the largest rate has + // SHAKE-128 / cSHAKE-128 / KMAC-128 (the lowest strength). + localparam int MaxNumDigestParts = (sha3_pkg::StateW - 2 * 128) / DynAppDigestW; + localparam int DigestCntW = $clog2(MaxNumDigestParts); + logic [DigestCntW-1:0] digest_top; + logic [DigestCntW-1:0] current_digest_idx; + + // Expose only the relevant part of the digest depending on the mode and strength. In case an + // invalid mode or strength is detected, we set the digest_top to the smallest value. always_comb begin - app_digest_done = 1'b 0; - app_digest = '{default:'0}; - if (st == StAppWait && prim_mubi_pkg::mubi4_test_true_strict(absorbed_i) && - lc_ctrl_pkg::lc_tx_test_false_strict(lc_escalate_en_i)) begin - // SHA3 engine has calculated the hash. Return the data to KeyMgr - app_digest_done = 1'b 1; + unique case (sha3_mode_q) + sha3_pkg::Sha3: begin + unique case (keccak_strength_q) + sha3_pkg::L128: digest_top = DigestCntW'(128 / DynAppDigestW); + // 224 is not a multiple of 64. Round up the number of digest parts. Note, due to this + // rounding the interface exposes bits from the rate which are not part of the hash. + sha3_pkg::L224: digest_top = DigestCntW'(4); + sha3_pkg::L256: digest_top = DigestCntW'(256 / DynAppDigestW); + sha3_pkg::L384: digest_top = DigestCntW'(384 / DynAppDigestW); + sha3_pkg::L512: digest_top = DigestCntW'(512 / DynAppDigestW); + default: digest_top = DigestCntW'(128 / DynAppDigestW); + endcase + end + sha3_pkg::Shake, + sha3_pkg::CShake: begin + // Expose the full rate for SHAKE, cSHAKE and KMAC. It is save to expose the full rate of + // KMAC even if it exceeds the encoded output length. + unique case (keccak_strength_q) + sha3_pkg::L128: digest_top = DigestCntW'((sha3_pkg::StateW - 2 * 128) / DynAppDigestW); + sha3_pkg::L224: digest_top = DigestCntW'((sha3_pkg::StateW - 2 * 224) / DynAppDigestW); + sha3_pkg::L256: digest_top = DigestCntW'((sha3_pkg::StateW - 2 * 256) / DynAppDigestW); + sha3_pkg::L384: digest_top = DigestCntW'((sha3_pkg::StateW - 2 * 384) / DynAppDigestW); + sha3_pkg::L512: digest_top = DigestCntW'((sha3_pkg::StateW - 2 * 512) / DynAppDigestW); + default: digest_top = DigestCntW'((sha3_pkg::StateW - 2 * 512) / DynAppDigestW); + endcase + end + default: digest_top = DigestCntW'(128 / DynAppDigestW); + endcase + end - // digest has always 2 entries. If !EnMasking, second is tied to 0. + // Limit the response width to 64 bits because its the GCD for all modes except SHA-224. + `ASSERT_INIT(DynAppDigestWIs64Bit_A, DynAppDigestW == 64) + // Ensure all counter top computations divide properly as the response channel does not carry any + // valid-strobe information. + `ASSERT_INIT(DigestTopDividesSha3L128_A, 128 % DynAppDigestW == 0) + // An exception is SHA3-224, where we fix the number of digest parts to 4, so the full hash and + // some additional rate bits are sent. + `ASSERT_INIT(DigestTopDividesSha3L256_A, 256 % DynAppDigestW == 0) + `ASSERT_INIT(DigestTopDividesSha3L384_A, 384 % DynAppDigestW == 0) + `ASSERT_INIT(DigestTopDividesSha3L512_A, 512 % DynAppDigestW == 0) + + `ASSERT_INIT(DigestTopDividesKmacAppDigestW_A, AppDigestW % DynAppDigestW == 0) + + `ASSERT_INIT(DigestTopDividesShakeL128_A, (sha3_pkg::StateW - 2 * 128) % DynAppDigestW == 0) + `ASSERT_INIT(DigestTopDividesShakeL224_A, (sha3_pkg::StateW - 2 * 224) % DynAppDigestW == 0) + `ASSERT_INIT(DigestTopDividesShakeL256_A, (sha3_pkg::StateW - 2 * 256) % DynAppDigestW == 0) + `ASSERT_INIT(DigestTopDividesShakeL384_A, (sha3_pkg::StateW - 2 * 384) % DynAppDigestW == 0) + `ASSERT_INIT(DigestTopDividesShakeL512_A, (sha3_pkg::StateW - 2 * 512) % DynAppDigestW == 0) + + assign digest_parts_available = current_digest_idx < digest_top; + + // Send parts of the digest if we are pushing. + // Gate with digest_parts_available to prevent sending capacity-region bits when + // current_digest_idx has already reached digest_top (the rate boundary). + assign app_digest_valid = app_push_digest && digest_parts_available && + lc_ctrl_pkg::lc_tx_test_false_strict(lc_escalate_en_i); + + // Only allow a squeeze if the app allows it. + logic squeeze_again_allowed; + assign squeeze_again_allowed = app_cfg.if_type == AppDynamic && app_cfg.session_cfg.en_xof; + + // Request a squeeze once we are out of digest parts. + assign squeeze_again = squeeze_again_allowed && !digest_parts_available; + + logic digest_part_pushed; + assign digest_part_pushed = app_digest_valid && app_req.rsp_ready; + + // Register if there is a digest response pending. If one is pending, any following response + // (finish or error) must wait as otherwise data is changed which would violate the valid + // locked-in principle. + assign pending_digest_rsp_d = app_digest_valid && !digest_part_pushed; + + // Similarly, register if there is a pending error response. + logic error_rsp_pushed; + assign error_rsp_pushed = app_error_rsp_valid && app_req.rsp_ready; + assign pending_error_rsp_d = app_error_rsp_valid && !error_rsp_pushed; + + logic [DynAppDigestW-1:0] digest_part[Share]; + for (genvar i = 0; i < Share; i++) begin : g_digest_mux + assign digest_part[i] = keccak_state_i[i][DynAppDigestW * current_digest_idx +: DynAppDigestW]; + end + + always_comb begin + app_digest = '{default:'0}; + if (app_digest_valid) begin + // Digest has always 2 entries. If !EnMasking, second is tied to 0. for (int i = 0 ; i < Share ; i++) begin // Return the portion of state. - app_digest[i] = keccak_state_i[i][AppDigestW-1:0]; + if (app_cfg.if_type == AppStatic) begin + app_digest[i] = keccak_state_i[i][AppDigestW-1:0]; + end else if (app_cfg.if_type == AppDynamic) begin + // Only expose a sub part. For SHA3-224, we set the capacity bits of the last response to + // '0. See digest_top calculation above. + app_digest[i][DynAppDigestW-1:0] = digest_part[i]; + if (sha3_mode_q == sha3_pkg::Sha3 && keccak_strength_q == sha3_pkg::L224 && + current_digest_idx == digest_top - 1) begin + app_digest[i][DynAppDigestW-1:DynAppDigestW/2] = '0; + end + end end end end + // SEC_CM CTR.REDUN + prim_count #( + .Width(DigestCntW), + .PossibleActions(prim_count_pkg::Clr | prim_count_pkg::Incr) + ) u_digest_part_counter ( + .clk_i, + .rst_ni, + .clr_i (clear_digest_pusher), + .set_i (1'b0), + .set_cnt_i ('0), + .incr_en_i (1'b1), + .decr_en_i (1'b0), + .step_i (DigestCntW'(1)), + .commit_i (digest_part_pushed || clear_digest_pusher), + .cnt_o (current_digest_idx), + .cnt_after_commit_o(), + .err_o (counter_error_o) + ); + + ////////////////// + // Key handling // + ////////////////// // Secret Key Mux - // Prepare merged key if EnMasking is not set. // Combine share keys into unpacked array for logic below to assign easily. // SEC_CM: KEY.SIDELOAD @@ -780,12 +1280,13 @@ module kmac_app end end - // Sideloaded key manage: Keep use sideloaded key for KMAC AppIntf until the - // hashing operation is finished. + // Sideloaded key expose control + assign err_key_used_but_invalid_set = keymgr_key_used && !keymgr_key_i.valid; + always_comb begin keymgr_key_used = 1'b0; - key_len_o = reg_key_len_i; - for (int i = 0 ; i < Share; i++) begin + key_len_o = reg_key_len_i; + for (int i = 0; i < Share; i++) begin key_data_o[i] = reg_key_data_i[i]; end // The key is considered invalid in all cases that are not listed below (which includes idle and @@ -793,34 +1294,40 @@ module kmac_app key_valid_o = 1'b0; unique case (st) - StAppCfg, StAppMsg, StAppOutLen, StAppProcess, StAppWait: begin - // Key from keymgr is actually used if the current HW app interface does *keyed* MAC. - keymgr_key_used = AppCfg[app_id].Mode == AppKMAC; - key_len_o = SideloadedKey; - for (int i = 0 ; i < Share; i++) begin + StAppMsg, StAppOutLen, StAppProcess, StAppWait: begin + // The key from KeyMgr is used if the current HW app interface does *keyed* MAC. This + // module does not know the precise timing related to when the kmac_core module uses the + // key during processing. To be on the safe side, we check that the key remains valid + // directly after latching the configuration and throughout the entire processing until the + // digest is received. The key is used by the kmac_core also after the kmac_app finished + // sending the message. So we must check the key until we receive the digest because we + // don't know here when the hashing engine starts the processing. + keymgr_key_used = app_cfg.session_cfg.mode == AppKMAC; + key_len_o = SideloadedKey; + for (int i = 0; i < Share; i++) begin key_data_o[i] = keymgr_key[i]; end // Key is valid if the current HW app interface does *keyed* MAC and the key provided by - // keymgr is valid. + // KeyMgr is valid. key_valid_o = keymgr_key_used && keymgr_key_i.valid; end StSw: begin if (keymgr_key_en_i) begin - // Key from keymgr is actually used if *keyed* MAC is enabled. - keymgr_key_used = kmac_en_o; - key_len_o = SideloadedKey; - for (int i = 0 ; i < Share; i++) begin + // Key from KeyMgr is actually used if *keyed* MAC is enabled. + keymgr_key_used = kmac_en_q; + key_len_o = SideloadedKey; + for (int i = 0; i < Share; i++) begin key_data_o[i] = keymgr_key[i]; end end // Key is valid if SW does *keyed* MAC and ... - if (kmac_en_o) begin + if (kmac_en_q) begin if (!keymgr_key_en_i) begin // ... it uses the key from kmac's CSR, or ... key_valid_o = 1'b1; end else begin - // ... it uses the key provided by keymgr and that one is valid. + // ... it uses the key provided by KeyMgr and that one is valid. key_valid_o = keymgr_key_i.valid; end end @@ -831,22 +1338,16 @@ module kmac_app end // Prefix Demux - // For SW, always prefix register. - // For App intf, check PrefixMode cfg and if 1, use Prefix cfg. + // For SW, always take the prefix from the register. For apps chose depending on configuration. always_comb begin sha3_prefix_o = '0; unique case (st) - StAppCfg, StAppMsg, StAppOutLen, StAppProcess, StAppWait: begin - // Check app intf cfg - for (int unsigned i = 0 ; i < NumAppIntf ; i++) begin - if (app_id == i) begin - if (AppCfg[i].PrefixMode == 1'b 0) begin - sha3_prefix_o = reg_prefix_i; - end else begin - sha3_prefix_o = AppCfg[i].Prefix; - end - end + StAppCfg, StAppMsg, StAppOutLen, StAppProcess, StAppWait, StAppPushDigest: begin + if (app_cfg.session_cfg.prefix_mode == 1'b0) begin + sha3_prefix_o = reg_prefix_i; + end else begin + sha3_prefix_o = app_cfg.prefix; end end @@ -860,33 +1361,9 @@ module kmac_app endcase end - // KMAC en / SHA3 mode / Strength - // by default, it uses reg cfg. When app intf reqs come, it uses AppCfg. - always_ff @(posedge clk_i or negedge rst_ni) begin - if (!rst_ni) begin - kmac_en_o <= 1'b 0; - sha3_mode_o <= sha3_pkg::Sha3; - keccak_strength_o <= sha3_pkg::L256; - end else if (clr_appid) begin - // As App completed, latch reg value - kmac_en_o <= reg_kmac_en_i; - sha3_mode_o <= reg_sha3_mode_i; - keccak_strength_o <= reg_keccak_strength_i; - end else if (set_appid) begin - kmac_en_o <= AppCfg[arb_idx].Mode == AppKMAC ? 1'b 1 : 1'b 0; - sha3_mode_o <= AppCfg[arb_idx].Mode == AppSHA3 - ? sha3_pkg::Sha3 : sha3_pkg::CShake; - keccak_strength_o <= AppCfg[arb_idx].KeccakStrength ; - end else if (st == StIdle) begin - kmac_en_o <= reg_kmac_en_i; - sha3_mode_o <= reg_sha3_mode_i; - keccak_strength_o <= reg_keccak_strength_i; - end - end - - // Status - assign app_active_o = (st inside {StAppCfg, StAppMsg, StAppOutLen, - StAppProcess, StAppWait}); + // Status - exclude error states so SW still can write messages during error recovery. + assign app_active_o = (st inside {StAppCfg, StAppMsg, StAppOutLen, StAppProcess, StAppWait, + StAppPushDigest, StAppFinish}); // Error Reporting ========================================================== always_comb begin @@ -908,6 +1385,6 @@ module kmac_app // Issue(#13655): Having a coverage that sideload keylen and CSR keylen are // different. `COVER(AppIntfUseDifferentSizeKey_C, - (st == StAppCfg && kmac_en_o) |-> reg_key_len_i != SideloadedKey) + (st == StAppCfg && kmac_en_q) |-> reg_key_len_i != SideloadedKey) endmodule diff --git a/hw/ip/kmac/rtl/kmac_pkg.sv b/hw/ip/kmac/rtl/kmac_pkg.sv index 10a03442ab4f3..99aeacb552e4e 100644 --- a/hw/ip/kmac/rtl/kmac_pkg.sv +++ b/hw/ip/kmac/rtl/kmac_pkg.sv @@ -190,21 +190,19 @@ package kmac_pkg; // Application interface // /////////////////////////// - // Application Algorithm - // Each interface can choose algorithms among SHA3, cSHAKE, KMAC - typedef enum bit [1:0] { - // SHA3 mode doer not nees any additional information. - // Prefix will be tied to all zero and not used. - AppSHA3 = 0, - - // In CShake/ KMAC mode, the Prefix can be determined by the compile-time - // parameter or through CSRs. - AppCShake = 1, - - // In KMAC mode, the secret key always comes from sideload. - // KMAC mode needs uniformly distributed entropy. The request will be - // silently discarded in Reset state. - AppKMAC = 2 + // Application interface type. Either use a compile-time defined configuration or the app + // provides a hashing configuration at runtime. + typedef enum logic { + AppStatic = 1'b0, + AppDynamic = 1'b1 + } app_type_e; + + // The possible hashing operation for an interface. + typedef enum logic [1:0] { + AppSHA3 = 2'b00, + AppShake = 2'b01, + AppCShake = 2'b10, + AppKMAC = 2'b11 } app_mode_e; // Predefined encoded_string @@ -217,43 +215,114 @@ package kmac_pkg; parameter int unsigned NSPrefixW = sha3_pkg::NSRegisterSize*8; typedef struct packed { - app_mode_e Mode; - - sha3_pkg::keccak_strength_e KeccakStrength; - - // PrefixMode determines the origin value of Prefix that is used in KMAC - // and cSHAKE operations. - // Choose **0** for CSRs (!!PREFIX), or **1** to use `Prefix` parameter - // below. - bit PrefixMode; + // PrefixMode determines whether to take the prefix from the CSR or use the hardcoded prefix. + // For static interfaces, if PrefixMode is 1, the Prefix will be used for both cSHAKE and + // KMAC operations. If 0, the CSR value is used. + // For dynamic interfaces, PrefixMode has no direct effect. The prefix is selected depending + // on the selected mode. + // If the mode is cSHAKE, it is always the CSR prefix used. + // If the mode is KMAC, it is always the compile-time value used. + logic prefix_mode; + + // The hashing mode which is performed. + app_mode_e mode; + + // The strength of the selected mode. + sha3_pkg::keccak_strength_e kstrength; + + // If 1, the app interface will automatically trigger a RUN command once it has pushed the + // full rate on the response channel. If 0, no squeeze can be performed at all and only the + // first rate is pushed. Usually enabled for SHAKE and cSHAKE and disabled for SHA3 and KMAC. + // Has no effect on static interfaces. + logic en_xof; + } app_ses_config_t; + + // Per default use an invalid configuration. + parameter app_ses_config_t AppSesCfgDefault = '0; - // If `PrefixMode` is 1'b 1, then this `Prefix` value will be used in - // cSHAKE or KMAC operation. - logic [NSPrefixW-1:0] Prefix; + typedef struct packed { + ///////////////////////////////// + // Compile-time configurations // + ///////////////////////////////// + + // Whether the interface is static or dynamic. For static interfaces all configs below are fix. + // For dynamic interfaces, certain config values are sent as first message part. + app_type_e if_type; + + // Specify whether the input comes in shares. If set, message FIFO is bypassed. This parameter + // is only relevant if the EnMasking parameter is set. + logic masked; + + // A compile-time defined prefix used for cSHAKE or KMAC operations. See PrefixMode when this + // value is used. + logic [NSPrefixW-1:0] prefix; + + // If set, non-standard combinations of mode and strength are supported for this dynamic + // interface. Otherwise a non-standard combination will result in a service rejected error. + logic en_unsup_comb; + + /////////////////////////// + // Session configuration // + /////////////////////////// + // These configurations can be overwritten by a dynamic interface instance at runtime but are + // fix for static interfaces. + app_ses_config_t session_cfg; } app_config_t; parameter app_config_t AppCfgKeyMgr = '{ - Mode: AppKMAC, // KeyMgr uses KMAC operation - KeccakStrength: sha3_pkg::L256, - PrefixMode: 1'b1, // Use prefix parameter + if_type: AppStatic, + masked: 1'b0, // {fname: encoded_string("KMAC"), custom_str: encoded_string("")} - Prefix: NSPrefixW'({EncodedStringEmpty, EncodedStringKMAC}) + prefix: NSPrefixW'({EncodedStringEmpty, EncodedStringKMAC}), + en_unsup_comb: 1'b0, + session_cfg: '{ + prefix_mode: 1'b1, + mode: AppKMAC, + kstrength: sha3_pkg::L256, + en_xof: 1'b0 + } }; parameter app_config_t AppCfgLcCtrl= '{ - Mode: AppCShake, - KeccakStrength: sha3_pkg::L128, - PrefixMode: 1'b1, // Use prefix parameter + if_type: AppStatic, + masked: 1'b0, // {fname: encode_string(""), custom_str: encode_string("LC_CTRL")} - Prefix: NSPrefixW'({EncodedStringLcCtrl, EncodedStringEmpty}) + prefix: NSPrefixW'({EncodedStringLcCtrl, EncodedStringEmpty}), + en_unsup_comb: 1'b0, + session_cfg: '{ + prefix_mode: 1'b1, + mode: AppCShake, + kstrength: sha3_pkg::L128, + en_xof: 1'b0 + } }; parameter app_config_t AppCfgRomCtrl = '{ - Mode: AppCShake, - KeccakStrength: sha3_pkg::L256, - PrefixMode: 1'b1, // Use prefix parameter + if_type: AppStatic, + masked: 1'b0, // {fname: encode_string(""), custom_str: encode_string("ROM_CTRL")} - Prefix: NSPrefixW'({EncodedStringRomCtrl, EncodedStringEmpty}) + prefix: NSPrefixW'({EncodedStringRomCtrl, EncodedStringEmpty}), + en_unsup_comb: 1'b0, + session_cfg: '{ + prefix_mode: 1'b1, + mode: AppCShake, + kstrength: sha3_pkg::L256, + en_xof: 1'b0 + } + }; + + parameter app_config_t AppCfgOtbn = '{ + if_type: AppDynamic, + masked: 1'b1, + // Fixed "KMAC" prefix for KMAC operation. + prefix: NSPrefixW'({EncodedStringEmpty, EncodedStringKMAC}), + en_unsup_comb: 1'b0, + session_cfg: '{ + prefix_mode: 1'b0, + mode: AppShake, + kstrength: sha3_pkg::L256, + en_xof: 1'b1 + } }; // Exporting the app internal mux selection enum into the package. So that DV @@ -284,115 +353,110 @@ package kmac_pkg; SelSw = 5'b01111 } app_mux_sel_e ; -// Encoding generated with: - // $ ./util/design/sparse-fsm-encode.py -d 3 -m 14 -n 10 \ - // -s 2454278799 --language=sv + // Encoding generated at commit 77aa3fcc58 using Python 3.10.19 with: + // $ ./util/design/sparse-fsm-encode.py --language=sv \ + // --seed 3362063275 --distance 3 --states 18 --bits 10 // // Hamming distance histogram: // // 0: -- // 1: -- // 2: -- - // 3: |||||||||| (14.29%) - // 4: |||||||||||||||||||| (27.47%) - // 5: ||||||||||||| (18.68%) - // 6: |||||||||||||||| (21.98%) - // 7: |||||||| (10.99%) - // 8: |||| (6.59%) - // 9: -- + // 3: |||||||||| (13.07%) + // 4: |||||||||||||||||||| (26.14%) + // 5: ||||||||||||||||||| (24.84%) + // 6: |||||||||||||| (18.30%) + // 7: |||||||| (10.46%) + // 8: |||| (5.23%) + // 9: | (1.96%) // 10: -- // // Minimum Hamming distance: 3 - // Maximum Hamming distance: 8 + // Maximum Hamming distance: 9 // Minimum Hamming weight: 3 - // Maximum Hamming weight: 8 + // Maximum Hamming weight: 7 // localparam int AppStateWidth = 10; typedef enum logic [AppStateWidth-1:0] { - StIdle = 10'b1010111110, - - // Application operation. - // - // if start request comes from an App first, until the operation ends by the - // requested App, all operations are granted to the specific App. SW - // requests and other Apps requests will be ignored. - // - // App interface does not have control signals. When first data valid occurs - // from an App, this logic asserts the start command to the downstream. When - // last beat pulse comes, this logic asserts the process to downstream - // (after the transaction is accepted regardless of partial writes or not) - // When absorbed by SHA3 core, the logic sends digest to the requested App - // and right next cycle, it triggers done command to downstream. - - // In StAppCfg state, it latches the cfg from AppCfg parameter to determine - // the kmac_mode, sha3_mode, keccak strength. - StAppCfg = 10'b1010101101, - - StAppMsg = 10'b1110001011, - - // In StKeyOutLen, this module pushes encoded outlen to the MSG_FIFO. - // Assume the length is 256 bit, the data will be 48'h 02_0100 - StAppOutLen = 10'b1010011000, - StAppProcess = 10'b1110110010, - StAppWait = 10'b1001010000, + StIdle = 10'b0110100101, + + // In StAppCfg state, it latches the cfg from AppCfg parameter to determine the kmac_mode, + // sha3_mode, and keccak strength. + // In StAppOutLen, the app interface pushes encoded output length into the core. + StAppCfg = 10'b1000001010, + StAppMsg = 10'b1011100000, + StAppOutLen = 10'b0011101110, + StAppProcess = 10'b0100111111, + StAppWait = 10'b0100010001, + StAppPushDigest = 10'b1100100011, + StAppFinish = 10'b1001110010, // SW Controlled // If start request comes from SW first, until the operation ends, all - // requests from KeyMgr will be discarded. - StSw = 10'b0010111011, + // requests from apps will be stalled. + StSw = 10'b1101000010, - // Error KeyNotValid - // When KeyMgr operates, the secret key is not ready yet. - StKeyMgrErrKeyNotValid = 10'b0111011111, + // Error KeyNotValid triggers if key is used but it is not valid at the time. + StErrorKeyNotValid = 10'b0000001111, - StError = 10'b1110010111, - StErrorAwaitSw = 10'b0110001100, - StErrorAwaitApp = 10'b1011100000, - StErrorWaitAbsorbed = 10'b0010100100, - StErrorServiceRejected = 10'b1101000111, + StErrorAwaitMsg = 10'b1100100100, + StErrorNotify = 10'b0111100010, + StErrorAwaitTermination = 10'b0101101000, + StErrorFinish = 10'b0011111101, + StErrorAwaitSw = 10'b0100001100, + StErrorAwaitAbsorbed = 10'b1110001111, + StErrorPush = 10'b0010110100, // This state is used for terminal errors - StTerminalError = 10'b0101110110 + StTerminalError = 10'b1101111001 } st_e; // MsgWidth : 64 // MsgStrbW : 8 parameter int unsigned AppDigestW = 384; parameter int unsigned AppKeyW = 256; + // Width of one digest chunk returned per rsp_valid pulse in AppDynamic mode. + parameter int unsigned DynAppDigestW = MsgWidth; typedef struct packed { - logic valid; - logic [MsgWidth-1:0] data; + logic req_valid; + logic [MsgWidth-1:0] data_s0; + logic [MsgWidth-1:0] data_s1; logic [MsgStrbW-1:0] strb; - logic last; + logic req_last; + logic rsp_ready; } app_req_t; typedef struct packed { - logic ready; - logic done; - logic [AppDigestW-1:0] digest_share0; - logic [AppDigestW-1:0] digest_share1; - // Error is valid when done is high. If any error occurs during KDF, KMAC + logic req_ready; + logic rsp_valid; + logic [AppDigestW-1:0] digest_s0; + logic [AppDigestW-1:0] digest_s1; + // Error is valid when rsp_valid is high. If any error occurs during KDF, KMAC // returns the garbage digest data with error. The KeyMgr discards the // digest and may re-initiate the process. logic error; + logic rsp_finish; } app_rsp_t; parameter app_req_t APP_REQ_DEFAULT = '{ - valid: 1'b 0, - data: '0, - strb: '0, - last: 1'b 0 + req_valid: 1'b0, + data_s0: '0, + data_s1: '0, + strb: '0, + req_last: 1'b0, + rsp_ready: 1'b0 }; + parameter app_rsp_t APP_RSP_DEFAULT = '{ - ready: 1'b1, - done: 1'b1, - digest_share0: AppDigestW'(32'hDEADBEEF), - digest_share1: AppDigestW'(32'hFACEBEEF), - error: 1'b1 + req_ready: 1'b0, + rsp_valid: 1'b0, + digest_s0: '0, + digest_s1: '0, + error: 1'b0, + rsp_finish: 1'b0 }; - //////////////////// // Error Handling // //////////////////// diff --git a/hw/top_darjeeling/data/autogen/top_darjeeling.gen.hjson b/hw/top_darjeeling/data/autogen/top_darjeeling.gen.hjson index 540e189814d79..1651d6854ab60 100644 --- a/hw/top_darjeeling/data/autogen/top_darjeeling.gen.hjson +++ b/hw/top_darjeeling/data/autogen/top_darjeeling.gen.hjson @@ -5141,13 +5141,13 @@ desc: Number of application interfaces param_type: int unpacked_dimensions: null - default: 3 + default: 4 local: false expose: true name_top: KmacNumAppIntf } + default: kmac_pkg::APP_REQ_DEFAULT inst_name: kmac - default: "" end_idx: -1 top_type: one-to-N top_signame: kmac_app @@ -23725,13 +23725,13 @@ desc: Number of application interfaces param_type: int unpacked_dimensions: null - default: 3 + default: 4 local: false expose: true name_top: KmacNumAppIntf } + default: kmac_pkg::APP_REQ_DEFAULT inst_name: kmac - default: "" end_idx: -1 top_type: one-to-N top_signame: kmac_app @@ -30449,7 +30449,7 @@ desc: Number of application interfaces param_type: int unpacked_dimensions: null - default: 3 + default: 4 local: false expose: true name_top: KmacNumAppIntf @@ -30458,7 +30458,7 @@ end_idx: -1 act: rsp suffix: req - default: "'{kmac_pkg::APP_REQ_DEFAULT}" + default: kmac_pkg::APP_REQ_DEFAULT } { package: kmac_pkg @@ -30470,7 +30470,7 @@ desc: Number of application interfaces param_type: int unpacked_dimensions: null - default: 3 + default: 4 local: false expose: true name_top: KmacNumAppIntf diff --git a/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson b/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson index d60506117ae9e..2e703029691be 100644 --- a/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson +++ b/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson @@ -7227,7 +7227,7 @@ name: NumAppIntf desc: Number of application interfaces type: int - default: 3 + default: 4 local: "false" expose: "true" name_top: KmacNumAppIntf @@ -7241,7 +7241,7 @@ ''' type: kmac_pkg::app_config_t unpacked_dimensions: "[KmacNumAppIntf]" - default: "'{kmac_pkg::AppCfgKeyMgr, kmac_pkg::AppCfgLcCtrl, kmac_pkg::AppCfgRomCtrl}" + default: "'{kmac_pkg::AppCfgKeyMgr, kmac_pkg::AppCfgLcCtrl, kmac_pkg::AppCfgRomCtrl, kmac_pkg::AppCfgOtbn}" local: "false" expose: "true" name_top: KmacAppCfg @@ -7273,15 +7273,15 @@ desc: Number of application interfaces param_type: int unpacked_dimensions: null - default: 3 + default: 4 local: false expose: true name_top: KmacNumAppIntf } + default: kmac_pkg::APP_REQ_DEFAULT inst_name: kmac - default: "" - end_idx: -1 - top_type: one-to-N + end_idx: 3 + top_type: partial-one-to-N top_signame: kmac_app index: -1 } @@ -23506,15 +23506,15 @@ desc: Number of application interfaces param_type: int unpacked_dimensions: null - default: 3 + default: 4 local: false expose: true name_top: KmacNumAppIntf } + default: kmac_pkg::APP_REQ_DEFAULT inst_name: kmac - default: "" - end_idx: -1 - top_type: one-to-N + end_idx: 3 + top_type: partial-one-to-N top_signame: kmac_app index: -1 } @@ -27330,16 +27330,16 @@ desc: Number of application interfaces param_type: int unpacked_dimensions: null - default: 3 + default: 4 local: false expose: true name_top: KmacNumAppIntf } type: req_rsp - end_idx: -1 + end_idx: 3 act: rsp suffix: req - default: "'{kmac_pkg::APP_REQ_DEFAULT}" + default: kmac_pkg::APP_REQ_DEFAULT } { package: kmac_pkg @@ -27351,13 +27351,13 @@ desc: Number of application interfaces param_type: int unpacked_dimensions: null - default: 3 + default: 4 local: false expose: true name_top: KmacNumAppIntf } type: req_rsp - end_idx: -1 + end_idx: 3 act: rsp suffix: rsp default: "" diff --git a/hw/top_earlgrey/rtl/autogen/top_earlgrey.sv b/hw/top_earlgrey/rtl/autogen/top_earlgrey.sv index 83a947b676560..6b2380558679d 100644 --- a/hw/top_earlgrey/rtl/autogen/top_earlgrey.sv +++ b/hw/top_earlgrey/rtl/autogen/top_earlgrey.sv @@ -87,9 +87,9 @@ module top_earlgrey #( parameter bit KmacSwKeyMasked = 0, parameter int SecKmacCmdDelay = 0, parameter bit SecKmacIdleAcceptSwMsg = 0, - parameter int KmacNumAppIntf = 3, + parameter int KmacNumAppIntf = 4, parameter kmac_pkg::app_config_t KmacAppCfg[KmacNumAppIntf] = - '{kmac_pkg::AppCfgKeyMgr, kmac_pkg::AppCfgLcCtrl, kmac_pkg::AppCfgRomCtrl}, + '{kmac_pkg::AppCfgKeyMgr, kmac_pkg::AppCfgLcCtrl, kmac_pkg::AppCfgRomCtrl, kmac_pkg::AppCfgOtbn}, // parameters for otbn parameter bit OtbnStub = 0, parameter otbn_pkg::regfile_e OtbnRegFile = otbn_pkg::RegFileFF, @@ -819,10 +819,13 @@ module top_earlgrey #( // Define partial inter-module tie-off otp_ctrl_pkg::sram_otp_key_rsp_t unused_otp_ctrl_sram_otp_key_rsp3; + kmac_pkg::app_rsp_t unused_kmac_app_rsp3; // Assign partial inter-module tie-off assign unused_otp_ctrl_sram_otp_key_rsp3 = otp_ctrl_sram_otp_key_rsp[3]; + assign unused_kmac_app_rsp3 = kmac_app_rsp[3]; assign otp_ctrl_sram_otp_key_req[3] = '0; + assign kmac_app_req[3] = kmac_pkg::APP_REQ_DEFAULT; // OTP HW_CFG* Broadcast signals. // TODO(#6713): The actual struct breakout and mapping currently needs to