blob: 551607a5ec693a15d40a71011f2372eec71ae670 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Serial Peripheral Interface (SPI) Trusted Platform Module (TPM)
/*
*/
module spi_tpm
import spi_device_pkg::*;
#(
parameter int unsigned CmdAddrFifoDepth = 2,
// Max Write/Read buffer size to support is 64B in TPM spec.
// But more than 4B is for future use. So, 4B is recommended.
parameter int unsigned WrFifoDepth = 4,
parameter int unsigned RdFifoDepth = 4,
// Locality determines the number of TPM_ACCESS registers.
// Other HW managed registers are shared across the locality.
// If host accesses the HW managed registers that are unsupported locality,
// the HW returns 0xFF. The SW is responsible to the other SW managed
// registers.
parameter bit EnLocality = 1,
// derived
localparam int unsigned CmdAddrPtrW = $clog2(CmdAddrFifoDepth+1),
localparam int unsigned WrFifoPtrW = $clog2(WrFifoDepth+1),
localparam int unsigned RdFifoPtrW = $clog2(RdFifoDepth+1),
localparam int unsigned NumLocality = (EnLocality) ? 5 : 1,
localparam int unsigned AccessRegSize = 8, // times Locality
localparam int unsigned IntEnRegSize = 32,
localparam int unsigned IntVectorRegSize = 8,
localparam int unsigned IntStsRegSize = 32,
localparam int unsigned IntfCapRegSize = 32,
localparam int unsigned StatusRegSize = 32,
localparam int unsigned IdRegSize = 32, // {DID, VID}
localparam int unsigned RidRegSize = 8,
localparam int unsigned ActiveLocalityBitPos = 5, // Access[5]
localparam int unsigned CmdAddrSize = 32, // Cmd 8bit + Addr 24bit
localparam int unsigned FifoRegSize = 12, // lower 12bit excluding locality
localparam int unsigned DataFifoSize = $bits(spi_byte_t),
// TPM_CAP related constants.
// - Revision: the number visible in TPM_CAP CSR. Need to update the
// revision when the SW interface is revised.
localparam logic [7:0] CapTpmRevision = 8'h 00,
// - Max Xfer Size: the supported xfer_size is visible to the SW via
// TPM_CAP CSR
localparam logic [2:0] CapMaxXferSize = 3'($clog2(WrFifoDepth))
) (
input clk_in_i,
input clk_out_i,
input sys_clk_i,
input sys_rst_ni,
input scan_rst_ni,
input prim_mubi_pkg::mubi4_t scanmode_i, // scanmode[RstTpmSel]
// SPI interface
input csb_i, // TPM needs separate CS#
input mosi_i,
output logic miso_o,
output logic miso_en_o,
// TPM Capability
output spi_device_pkg::tpm_cap_t tpm_cap_o,
// Configurations
// tpm_en to turn on the TPM function
input cfg_tpm_en_i,
// tpm_mode to switch TPM between FIFO anc CRB
input cfg_tpm_mode_i,
// hw_reg_dis to turn off return-by-HW registers
input cfg_tpm_hw_reg_dis_i,
// Disable TPM register checker. Logic won't compare addr[23:16] with 8'h
// D4 (TpmAddr).
input cfg_tpm_reg_chk_dis_i,
// tpm_invalid_locality : TPM function returns invalid (0xFF) when the
// received address is out of the Locality. If this bit is turned off, the
// logic uploads the command and address even the address is out of the max
// Locality.
input cfg_tpm_invalid_locality_i,
// Registers in SYS clock
input [NumLocality*AccessRegSize-1:0] sys_access_reg_i,
input [IntEnRegSize-1:0] sys_int_enable_reg_i,
input [IntVectorRegSize-1:0] sys_int_vector_reg_i,
input [IntStsRegSize-1:0] sys_int_status_reg_i,
input [IntfCapRegSize-1:0] sys_intf_capability_reg_i,
input [StatusRegSize-1:0] sys_status_reg_i,
input [IdRegSize-1:0] sys_id_reg_i,
input [RidRegSize-1:0] sys_rid_reg_i,
// Buffer and FIFO status
output logic sys_cmdaddr_rvalid_o,
output logic [CmdAddrSize-1:0] sys_cmdaddr_rdata_o,
input sys_cmdaddr_rready_i,
output logic sys_wrfifo_rvalid_o,
output logic [DataFifoSize-1:0] sys_wrfifo_rdata_o,
input sys_wrfifo_rready_i,
input sys_rdfifo_wvalid_i,
input [DataFifoSize-1:0] sys_rdfifo_wdata_i,
output logic sys_rdfifo_wready_o,
// TPM_STATUS
output logic sys_cmdaddr_notempty_o,
output logic sys_rdfifo_notempty_o,
output logic [RdFifoPtrW-1:0] sys_rdfifo_depth_o,
output logic [WrFifoPtrW-1:0] sys_wrfifo_depth_o
);
// Capability
assign tpm_cap_o = '{
rev: CapTpmRevision,
locality: EnLocality,
max_xfer_size: CapMaxXferSize
};
localparam int unsigned TpmRegisterSize = (AccessRegSize * NumLocality)
+ IntEnRegSize + IntVectorRegSize
+ IntStsRegSize + IntfCapRegSize
+ StatusRegSize + IdRegSize
+ RidRegSize;
localparam logic [7:0] TpmAddr = 8'h D4;
typedef enum logic [3:0] {
RegAccess = 4'h 0,
RegIntEn = 4'h 1,
RegIntVect = 4'h 2,
RegIntSts = 4'h 3,
RegIntfCap = 4'h 4,
RegSts = 4'h 5,
RegHashStart = 4'h 6,
RegId = 4'h 7,
RegRid = 4'h 8,
HwRegEnd = 4'h 9
} hw_reg_idx_e;
localparam int unsigned NumHwReg = 32'(HwRegEnd);
localparam logic [11:0] TpmReturnByHwAddr [NumHwReg] = '{
12'h 000, // 000 Access_x
12'h 008, // 00B:008 Interrupt Enable
12'h 00C, // 00C Interrupt Vector
12'h 010, // 013:010 Interrupt Status
12'h 014, // 017:014 Interface Capability
12'h 018, // 01B:018 Status_x
12'h 020, // 023:020 Hash Start
12'h F00, // F03:F00 DID_VID
12'h F04 // F04:F04 RID
};
///////////////////
// Clock & Reset //
///////////////////
logic rst_n;
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_tpm_csb_rst_scan_mux (
.clk0_i (sys_rst_ni & ~csb_i),
.clk1_i (scan_rst_ni),
.sel_i (prim_mubi_pkg::mubi4_test_true_strict(scanmode_i)),
.clk_o (rst_n)
);
// TODO: internal reset (sys_rst_ni & csb_i)
// Do we really need the csb reset for TPM?
////////////////
// Definition //
////////////////
// Configuration structure
typedef struct packed {
logic tpm_en;
logic tpm_mode;
logic hw_reg_dis;
logic tpm_reg_chk_dis;
logic invalid_locality;
} tpm_cfg_t;
typedef enum logic {
Write = 1'b 0,
Read = 1'b 1
} cmd_type_e;
typedef enum logic [3:0] {
// In Idle state, the TPM waits for the opcode from the host system. When
// Opcode (first byte) is received, the TPM configures internal datapath
// based on the opcode. opcode[7]: Read(1)/Write(0) opcode[5:0]: transfer
// size in 0-based number (e.g 'd63 == 64B)
//
// Next state:
// - StAddr: when bitcnt hits 5'h7, the state moves to StAddr state.
// - StErr??: If the xfer_size is bigger than the compile-time parameter,
// reports error.
StIdle,
// Address is 3B register offset. The logic decodes the address and
// determines if the register to be returned by HW or by SW.
//
// HW managed registers:
// - TPM_ACCESS_x
// - TPM_INT_ENABLE
// - TPM_INT_VECTOR
// - TPM_INT_STATUS
// - TPM_INTF_CAPABILITY
// - TPM_STS_x
// - TPM_DID , TPM_VID
//
// Next state:
// - StWait: If the address is not for a Return-by-HW register, or the
// hw_reg_dis configuration is set, the state machine moves to WAIT
// state to send more WAIT byte to the SB.
// - StStartByte: If the received command is a read command and the
// address is for a Return-by-HW register and the hw_reg_dis config is
// not set, the state machine does not need more WAIT state. The state
// machine directly moves to StStartByte to send `START (0x01)`.
// - StWrite: If the received command is a write command, and the write
// FIFO is empty, the state machine moves to the StWrite state
// directly. If this condition is met, the state machine sends `START`
// byte at the last byte of the address phase.
StAddr,
// In Wait state, the TPM sends 0x00 to SPI MISO line in order to hold the
// host system to send write data or to receive the read data.
//
// Next state:
// - StStartByte: From Wait state, it always moves to StStartByte when
// FIFOs are ready to be used.
StWait,
// In StartByte state, TPM sends 0x01 to SPI MISO line. When the host
// receives the data[0], it assumes the TPM device is ready to receive
// data or to send the requested data if the value is 1.
//
// Next state:
// - StReadFifo: If the received command is a read command (cmd_type ==
// Read), the state machine moves to StRead and sends the output of the
// read FIFO to the parallel-to-serial module.
// - StReadHwReg: If the received address is hw reg, and enabled hw
// return, move to ReadHwReg state to return the HW data.
// - StWrite: If the received command is a write command (cmd_type ==
// Write), the state machine moves to StWrite and stacks the incoming
// data into the write FIFO. It is assumed that the write FIFO has
// enough empty space to store the incoming transfer size.
StStartByte,
// In Read state, the module reads data from the Read FIFO and returns to
// the host system. After the transfer size amount of data is sent, it
// moves to the End state.
//
// Next state:
// - ??? : If the SB toggles more than the xfer size?
StReadFifo,
StReadHwReg,
// In Write state, the module accepts the data from MOSI and stores into
// the Write FIFO. After the transfer size amount of data is received, the
// TPM stop receiving the data and move to the End state.
//
// Next state:
// - StEnd: When the module stores the xfer_size amount of the write data
// into the write FIFO, it moves to StEnd state and waits for the CS#
// deassertion.
StWrite,
StInvalid,
// In End state, TPM waits for the CSb de-assertion. This is
// TERMINAL_STATE.
StEnd
} st_e;
st_e sck_st_q, sck_st_d;
// tpm_reg_t struct defines the Return-by-HW registers. These registers must
// be processed by the HW to return the data in at most one WAIT cycle.
//
// The TPM submodule returns WAIT by default at the last byte of the address
// phase. Then, when the module receives the addr[2], it knows the SB is
// accessing the Return-by-HW registers. It sets the `is_hw_reg` flag.
//
// When the state machine in this submodule moves from the Address state, if
// the flag is set and the `cfg_tpm_hw_reg_dis_i` is cleard, the state
// machine directly moves to `START` state as it does not need to wait
// longer.
typedef struct packed {
logic [AccessRegSize*NumLocality-1:0] access;
logic [IntEnRegSize-1:0] int_enable;
logic [IntVectorRegSize-1:0] int_vector;
logic [IntStsRegSize-1:0] int_status;
logic [IntfCapRegSize-1:0] intf_capacity;
logic [StatusRegSize-1:0] status;
logic [IdRegSize-1:0] id; // Device ID, Vendor ID
logic [RidRegSize-1:0] rid; // Revision ID
} tpm_reg_t;
///////////
// Signal//
///////////
// CS# assertion pulse signal in SCK domain
logic isck_csb_asserted_pulse, sck_csb_asserted_pulse;
tpm_cfg_t sys_tpm_cfg;
tpm_cfg_t sck_tpm_cfg;
assign sys_tpm_cfg = '{
tpm_en: cfg_tpm_en_i,
tpm_mode: cfg_tpm_mode_i,
hw_reg_dis: cfg_tpm_hw_reg_dis_i,
invalid_locality: cfg_tpm_invalid_locality_i,
tpm_reg_chk_dis: cfg_tpm_reg_chk_dis_i
};
tpm_reg_t sys_tpm_reg;
tpm_reg_t isck_tpm_reg;
logic [NumLocality-1:0] sck_active_locality; // TPM_ACCESS_x[5]
assign sys_tpm_reg = '{
access: sys_access_reg_i,
int_enable: sys_int_enable_reg_i,
int_vector: sys_int_vector_reg_i,
int_status: sys_int_status_reg_i,
intf_capacity: sys_intf_capability_reg_i,
status: sys_status_reg_i,
id: sys_id_reg_i,
rid: sys_rid_reg_i
};
// FIFOs
logic sck_cmdaddr_wvalid, sck_cmdaddr_wready;
logic [CmdAddrSize-1:0] sck_cmdaddr_wdata_q, sck_cmdaddr_wdata_d;
logic [CmdAddrPtrW-1:0] sys_cmdaddr_rdepth, sck_cmdaddr_wdepth;
logic [FifoRegSize-1:0] isck_fifoaddr; // latched from sck_cmdaddr_wdata_d
logic sck_fifoaddr_latch;
logic isck_fifoaddr_inc;
// (sys_cmdaddr_rdepth > 0)
assign sys_cmdaddr_notempty_o = |sys_cmdaddr_rdepth;
logic sck_wrfifo_wvalid, sck_wrfifo_wready;
logic [DataFifoSize-1:0] sck_wrfifo_wdata;
logic [WrFifoPtrW-1:0] sys_wrfifo_rdepth, sck_wrfifo_wdepth;
assign sys_wrfifo_depth_o = sys_wrfifo_rdepth;
// Read FIFO uses inverted SCK (clk_out_i)
logic isck_rdfifo_rvalid, isck_rdfifo_rready;
logic [DataFifoSize-1:0] isck_rdfifo_rdata;
logic [RdFifoPtrW-1:0] sys_rdfifo_wdepth, isck_rdfifo_rdepth;
assign sys_rdfifo_depth_o = sys_rdfifo_wdepth;
assign sys_rdfifo_notempty_o = |sys_rdfifo_wdepth;
// If cmdaddr_shift_en is 1, the logic stacks the incoming MOSI into cmdaddr
// register.
logic cmdaddr_shift_en;
logic [4:0] cmdaddr_bitcnt;
logic [23:0] addr; // used temporary while shifting the cmd address
// Write Data Latch Enable: Latch and generate a pulse when a byte is stacked.
logic wrdata_shift_en;
logic [2:0] wrdata_bitcnt;
logic [7:0] wrdata_q, wrdata_d;
// Read FIFO is in inverted SCK domain (clk_out)
logic sck_rddata_shift_en;
// Indicate if the received address is FIFO/CRB register. Should start with
// D4_xxxx
logic is_tpm_reg;
logic check_tpm_reg;
// If the received command falls into the return-by-HW registers, then
// `is_hw_reg` is set.
logic is_hw_reg;
hw_reg_idx_e sck_hw_reg_idx, isck_hw_reg_idx;
logic [31:0] isck_hw_reg_word;
logic [7:0] isck_hw_reg_byte;
// Set this signal when the received address bits are enough, which should
// be address[23:2] or bigger.
logic check_hw_reg;
// When the address shifted up to addr[12] (cmdaddr_bitcnt == 5'h 13), the
// module can decide if the received address is in the Locality or not. If
// it is not in the locality, the logic may return the invalid and discard
// the request.
logic latch_locality;
// bit[15:12] in the received address is the locality if the address is FIFO
// addr.
logic [3:0] locality;
// Indicate the locality is greater than or equal to NumLocality.
// with tpm_cfg.invalid_locality, the logic returns FFh if host sends read
// requests to unsupported locality.
logic invalid_locality;
// The first bit of the command (Command[7]) indicates the command direction.
logic latch_cmd_type;
cmd_type_e cmd_type;
// xfer_size: Command[5:0] is the command xfer size. currently the logic
// supports up to WrFifoDepth.
logic latch_xfer_size;
logic [5:0] xfer_size;
logic [5:0] xfer_bytes_q, xfer_bytes_d;
logic xfer_size_met;
// Output MISO
typedef enum logic [2:0] {
SelWait = 3'h 0, // 0x00
SelStart = 3'h 1, // 0x01
SelInvalid = 3'h 2, // 0xFF
SelHwReg = 3'h 3, // depends on hw_reg_idx
SelRdFifo = 3'h 4 // from RdFifo
} tpm_data_sel_e;
logic sck_p2s_valid;
logic isck_p2s_valid;
logic [7:0] isck_p2s_data;
logic isck_p2s_sent;
tpm_data_sel_e sck_data_sel, isck_data_sel;
logic isck_miso_en;
logic isck_miso;
assign miso_en_o = isck_miso_en;
assign miso_o = isck_miso;
/////////
// CDC //
/////////
// clk_in csb_asserted_pulse
prim_edge_detector #(
.Width (1),
.ResetValue (1'b 1),
.EnSync (1'b 1)
) u_sck_csb_edge (
.clk_i (clk_in_i),
.rst_ni (rst_n),
.d_i (1'b 0), // rst_n has CSb assertion
.q_sync_o (),
.q_posedge_pulse_o (),
.q_negedge_pulse_o (sck_csb_asserted_pulse)
);
// Latch configs
// clk_out csb_asserted pulse
prim_edge_detector #(
.Width (1),
.ResetValue (1'b 1),
.EnSync (1'b 1)
) u_isck_csb_edge (
.clk_i (clk_out_i),
.rst_ni (rst_n),
.d_i (1'b 0),
.q_sync_o (),
.q_posedge_pulse_o (),
.q_negedge_pulse_o (isck_csb_asserted_pulse)
);
// Configuration latched into SCK
always_ff @(posedge clk_in_i or negedge sys_rst_ni) begin
if (!sys_rst_ni) begin
sck_tpm_cfg <= '{default: '0};
end else if (sck_csb_asserted_pulse) begin
sck_tpm_cfg <= sys_tpm_cfg;
end
end
// SYS register latched into the SCK (or isck?)
always_ff @(posedge clk_in_i or negedge sys_rst_ni) begin
if (!sys_rst_ni) begin
isck_tpm_reg <= '{default: '0};
end else if (isck_csb_asserted_pulse) begin
isck_tpm_reg <= sys_tpm_reg;
end
end
// Latch activeLocality in SCK to be used in the state machine
always_ff @(posedge clk_in_i or negedge sys_rst_ni) begin
if (!sys_rst_ni) begin
sck_active_locality <= NumLocality'(0);
end else if (sck_csb_asserted_pulse) begin
for (int unsigned i = 0 ; i < NumLocality ; i++) begin
sck_active_locality[i] <=
sys_tpm_reg.access[AccessRegSize*i + ActiveLocalityBitPos];
end
end
end
// data_sel (sck -> isck)
always_ff @(posedge clk_out_i or negedge rst_n) begin
if (!rst_n) begin
isck_data_sel <= SelWait;
end else begin
isck_data_sel <= sck_data_sel;
end
end
//////////////
// Datapath //
//////////////
// command, addr latch
always_ff @(posedge clk_in_i or negedge rst_n) begin
if (!rst_n) begin
cmdaddr_bitcnt <= 5'h 0;
end else if (cmdaddr_shift_en) begin
cmdaddr_bitcnt <= cmdaddr_bitcnt + 5'h 1;
end
end
// Push to CmdAddr buffer if the command is not processed by HW.
assign sck_cmdaddr_wvalid = cmdaddr_bitcnt == 5'h 1F && !is_hw_reg;
// Control signals:
// latch_cmd_type
assign latch_cmd_type = (cmdaddr_bitcnt == 5'h 0) && (sck_st_q == StIdle);
assign check_tpm_reg = (cmdaddr_bitcnt == 5'h 0F);
assign check_hw_reg = (cmdaddr_bitcnt == 5'h 1D);
assign sck_fifoaddr_latch = (cmdaddr_bitcnt == 5'h 1F);
always_ff @(posedge clk_in_i or negedge rst_n) begin
if (!rst_n) begin
sck_cmdaddr_wdata_q <= '0;
end else if (cmdaddr_shift_en) begin
sck_cmdaddr_wdata_q <= sck_cmdaddr_wdata_d;
end
end
assign sck_cmdaddr_wdata_d = {sck_cmdaddr_wdata_q[0+:CmdAddrSize-1], mosi_i};
logic unused_cmdaddr_wdata_q;
assign unused_cmdaddr_wdata_q = sck_cmdaddr_wdata_q[CmdAddrSize-1];
// fifoaddr latch
// clk_out (iSCK)
always_ff @(posedge clk_out_i or negedge rst_n) begin
if (!rst_n) begin
isck_fifoaddr <= '0;
end else if (sck_fifoaddr_latch) begin
// TODO: latch sck_fifoaddr_latch into isck_fifoaddr_latch?
// Shall assert when sck_st_q moves away from StAddr
isck_fifoaddr <= sck_cmdaddr_wdata_d[FifoRegSize-1:0];
end else if (isck_fifoaddr_inc) begin
isck_fifoaddr <= isck_fifoaddr + 1'b 1;
end
end
`ASSERT(SckFifoAddrLatchCondition_A,
sck_fifoaddr_latch |=>
$past(sck_st_q) == StAddr && (sck_st_q inside {StWait, StStartByte}
|| invalid_locality),
clk_in_i, !rst_n)
// only fifoaddr[1:0] is used in this version.
logic unused_fifoaddr;
assign unused_fifoaddr = ^isck_fifoaddr[FifoRegSize-1:2];
// fifoaddr_inc @ iSCK :: SCK is not useful at all. SW can compute the
// address with the xfer_size and input address in CmdAddr buffer.
//
// Increase the address only when HwReg is selected (for now)
// Other cases have no usecase as of now.
assign isck_fifoaddr_inc = isck_p2s_sent && (isck_data_sel == SelHwReg);
// Write Data Latch
always_ff @(posedge clk_in_i or negedge rst_n) begin
if (!rst_n) begin
wrdata_bitcnt <= '0;
end else if (wrdata_shift_en) begin
wrdata_bitcnt <= wrdata_bitcnt + 3'h 1;
end
end
assign sck_wrfifo_wvalid = (wrdata_bitcnt == 3'h 7);
always_ff @(posedge clk_in_i or negedge rst_n) begin
if (!rst_n) begin
wrdata_q <= 8'h 0;
end else if (wrdata_shift_en) begin
wrdata_q <= wrdata_d;
end
end
assign wrdata_d = {wrdata_q[6:0], mosi_i};
assign sck_wrfifo_wdata = wrdata_d;
// Address: This is a comb logic that shows correct address value when the
// address is compared with other logics
always_comb begin
addr = 24'h 00_0000;
unique case (1'b 1)
check_tpm_reg: begin
// when checking the tpm_reg, only 8bit address is received.
// Look at the assertion TpmRegCondition_A.
addr = {sck_cmdaddr_wdata_d[7:0], 16'h 0000};
end
// locality in the TPM transaction is in addr[15:12].
// latch_locality is asserted at the 16th beat.
// Look at the assertion LocalityLatchCondition_A
latch_locality: begin
addr = {sck_cmdaddr_wdata_d[11:0], 12'h 000};
end
check_hw_reg: begin
// In Return-by-HW Reg check stage, the lower 2 bits were not arrived.
// Look at the assertion HwRegCondition_A
addr = {sck_cmdaddr_wdata_d[21:0], 2'b 00};
end
default: addr = 24'h 00_0000;
endcase
end
// when the address[16] is received, check if the address is for FIFO/CRB.
// If checker is turned off, `is_tpm_reg` becomes 1 regardless of addr
always_ff @(posedge clk_in_i or negedge rst_n) begin
if (!rst_n) begin
is_tpm_reg <= 1'b 0;
end else if (check_tpm_reg &&
(sck_tpm_cfg.tpm_reg_chk_dis || (addr[23:16] == TpmAddr))) begin
is_tpm_reg <= 1'b 1;
end
end
// Return-by-HW register check
logic is_hw_reg_d;
hw_reg_idx_e sck_hw_reg_idx_d;
always_ff @(posedge clk_in_i or negedge rst_n) begin
if (!rst_n) begin
is_hw_reg <= 1'b 0;
sck_hw_reg_idx <= RegAccess;
end else if (!sck_tpm_cfg.tpm_mode && check_hw_reg && (cmd_type == Read)
&& is_tpm_reg && !invalid_locality && !sck_tpm_cfg.hw_reg_dis) begin
// HW register is set only when the following conditions are met:
//
// 1. TPM is in FIFO mode
// 2. The command received is a Read command.
// 3. Is TPM register (starting with 0xD4_XXXX) or tpm_reg_chk_dis is set
// 4. Received locality is in the range of supported Locality.
is_hw_reg <= is_hw_reg_d;
sck_hw_reg_idx <= sck_hw_reg_idx_d;
end // if check_hw_reg
end
always_comb begin
is_hw_reg_d = 1'b 0;
sck_hw_reg_idx_d = hw_reg_idx_e'(0);
// check_hw_reg asserts when cmdaddr_bitcnt is 29.
for (int i = 0 ; i < NumHwReg; i ++) begin
if (TpmReturnByHwAddr[i][11:2] == addr[11:2]) begin
is_hw_reg_d = 1'b 1;
sck_hw_reg_idx_d = hw_reg_idx_e'(i);
end
end // for
end
// hw_reg_idx (sck -> isck)
// Remember that the logic chooses only one HwReg at a transaction.
// It does not send continuously even the transfer size is greater than the
// word boundary.
always_ff @(posedge clk_out_i or negedge rst_n) begin
if (!rst_n) isck_hw_reg_idx <= RegAccess;
else isck_hw_reg_idx <= sck_hw_reg_idx;
end
// locality store
always_ff @(posedge clk_in_i or negedge rst_n) begin
if (!rst_n) begin
locality <= '0;
invalid_locality <= 1'b 0;
end else if (latch_locality && is_tpm_reg) begin
locality <= addr[15:12];
invalid_locality <= (addr[15:12] < 4'(NumLocality)) ? 1'b 0: 1'b 1;
end
end
// cmd_type
always_ff @(posedge clk_in_i or negedge rst_n) begin
if (!rst_n) begin
cmd_type <= Write;
end else if (latch_cmd_type) begin
// latch at the very first SCK edge
cmd_type <= cmd_type_e'(sck_cmdaddr_wdata_d[0]);
end
end
// xfer_size
always_ff @(posedge clk_in_i or negedge rst_n) begin
if (!rst_n) begin
xfer_size <= 6'h 0;
end else if (latch_xfer_size) begin
xfer_size <= sck_cmdaddr_wdata_d[5:0];
end
end
// Xfer size count
always_ff @(posedge clk_in_i or negedge rst_n) begin
if (!rst_n) begin
xfer_bytes_q <= '0;
end else if ((isck_p2s_sent && sck_rddata_shift_en) ||
(sck_wrfifo_wvalid && wrdata_shift_en)) begin
xfer_bytes_q <= xfer_bytes_d;
end
end
assign xfer_bytes_d = xfer_bytes_q + 6'h 1;
assign xfer_size_met = xfer_bytes_q == xfer_size;
// Output data mux
`ASSERT_KNOWN(DataSelKnown_A, isck_data_sel, clk_out_i, !rst_n)
always_comb begin
isck_p2s_data = 8'h 00;
unique case (isck_data_sel)
SelWait: begin
isck_p2s_data = 8'h 00;
end
SelStart: begin
isck_p2s_data = 8'h 01;
end
SelInvalid: begin
isck_p2s_data = 8'h FF;
end
SelHwReg: begin
isck_p2s_data = isck_hw_reg_byte;
end
SelRdFifo: begin
isck_p2s_data = isck_rdfifo_rdata;
end
default: begin
isck_p2s_data = 8'h 00;
end
endcase
end
// HW REG mux
prim_slicer #(
.InW (32),
.OutW (8),
.IndexW (2)
) u_hw_reg_slice (
.sel_i (isck_fifoaddr[1:0]),
.data_i (isck_hw_reg_word),
.data_o (isck_hw_reg_byte)
);
`ASSERT_KNOWN(HwRegIdxKnown_A, isck_hw_reg_idx, clk_out_i, !rst_n)
always_comb begin : hw_reg_mux
isck_hw_reg_word = 32'h FFFF_FFFF;
unique case (isck_hw_reg_idx)
RegAccess: begin
for (int unsigned i = 0 ; i < NumLocality ; i++) begin
if (!invalid_locality && (4'(i) == locality)) begin
isck_hw_reg_word = { {(32-AccessRegSize){1'b1}},
isck_tpm_reg.access[AccessRegSize*i+:AccessRegSize]};
end
end
end
RegIntEn: begin
isck_hw_reg_word = isck_tpm_reg.int_enable;
end
RegIntVect: begin
isck_hw_reg_word = {24'h FFFFFF, isck_tpm_reg.int_vector};
end
RegIntSts: begin
isck_hw_reg_word = isck_tpm_reg.int_status;
end
RegIntfCap: begin
isck_hw_reg_word = isck_tpm_reg.intf_capacity;
end
RegSts: begin
// Check locality to return FFh or correct value
if (!invalid_locality && sck_active_locality[locality[2:0]]) begin
// return data
isck_hw_reg_word = isck_tpm_reg.status;
end else begin
isck_hw_reg_word = 32'h FFFF_FFFF;
end
end
RegHashStart: begin
isck_hw_reg_word = 32'h 0000_0000;
end
RegId: begin
isck_hw_reg_word = isck_tpm_reg.id;
end
RegRid: begin
isck_hw_reg_word = {24'h FFFFFF, isck_tpm_reg.rid};
end
default: begin
isck_hw_reg_word = 32'h FFFF_FFFF;
end
endcase
end : hw_reg_mux
// Parallel to Serial (Output)
//
// Parallel to Serial datapath in the TPM submodule differs from spi_p2s
// module used in the SPI_DEVICE.
//
// The logic in the TPM always works as byte granularity. The logic loops
// 8 stage. Each stage sends the bit of the input p2s_data from 7 to 0.
// Even the p2s_valid is asserted not in the byte granularity (e.g:
// asserted when bit position is 4), the logic directly sends the
// p2s_data[4] and turns on the output enable signal.
//
// The single IO characteristic of the TPM submodule simplifies the logic
// above.
//
// The p2s logic does not have the problem that spi_fwmode has. As SPI TPM
// does not support Mode 3, there's no case that the CSb de-asserted
// without the 8th negedge of SCK. So, the logic asserts the FIFO pop
// signal (`rready`) at the 8th beat.
logic [2:0] isck_p2s_bitcnt; // loop from 7 to 0
always_ff @(posedge clk_out_i or negedge rst_n) begin
if (!rst_n) begin
isck_p2s_bitcnt <= 3'h 7;
end else begin
isck_p2s_bitcnt <= isck_p2s_bitcnt - 1'b 1;
end
end
// p2s_valid & p2s_sent & p2s_data
// ~|isck_p2s_bitcnt
assign isck_p2s_sent = isck_p2s_valid && (isck_p2s_bitcnt == '0);
always_ff @(posedge clk_out_i or negedge rst_n) begin
if (!rst_n) isck_p2s_valid <= 1'b 0;
else isck_p2s_valid <= sck_p2s_valid;
end
// Decided to implement 8-to-1 mux rather than conventional shift out for
// Parallel-to-Serial. It is to support sending StartByte in StAddr phase.
// This logic affects the timing. If the logic cannot meet the timing
// requirement, the FSM must not send Start Byte at the last byte of the
// address phase and change the logic to shift out logic.
//
// The datapath can be reduced to use 3-to-1 mux. The condition of sending
// Start/Wait bit is determined at the addr[2] beat. It means, the MISO data
// comes from:
//
// - registered_data[7]
// - input p2s_data[7]
// - input p2s_data[2]
//
// The logic, however, introduces more limitation and gains little benefit.
// The logic depth is just one depth less than the original implementation.
assign isck_miso = isck_p2s_data[isck_p2s_bitcnt];
assign isck_miso_en = isck_p2s_valid;
// rvalid -> rready is OK not the opposit direction (rready -> rvalid)
assign isck_rdfifo_rready = isck_rdfifo_rvalid
&& isck_p2s_sent
&& (isck_data_sel == SelRdFifo);
///////////////////
// State Machine //
///////////////////
// Inputs
// - CFG: (tpm_en, tpm_hw_reg_dis, tpm_invalid_locality)
// - is_hw_reg
//
// Outputs
// - latch_cmd_type
// - latch_xfer_size
// - latch_locality
// - check_hw_reg
// - check_tpm_reg
// - cmdaddr_shift_en
// - wrdata_shift_en
// - p2s_valid
// - sck_data_sel
always_ff @(posedge clk_in_i or negedge rst_n) begin
if (!rst_n) begin
sck_st_q <= StIdle;
end else begin
sck_st_q <= sck_st_d;
end
end
always_comb begin : next_state
sck_st_d = sck_st_q;
// Default output values
latch_xfer_size = 1'b 0;
latch_locality = 1'b 0;
cmdaddr_shift_en = 1'b 0;
wrdata_shift_en = 1'b 0;
sck_rddata_shift_en = 1'b 0;
sck_p2s_valid = 1'b 0;
sck_data_sel = SelWait;
unique case (sck_st_q)
StIdle: begin
cmdaddr_shift_en = 1'b 1;
if (cmdaddr_bitcnt == 5'h 7) begin
if (sck_tpm_cfg.tpm_en) begin
sck_st_d = StAddr;
latch_xfer_size = 1'b 1;
end else begin
// Stop processing and move to End state.
// sck_tpm_cfg.tpm_en cannot be compared right after reset. Due
// to the absent of the SCK, the configuration cannot be
// synchronized into SCK domain at the first 3 clock cycle.
// So, the enable signal is checked when the state is about to
// move to StAddr.
sck_st_d = StEnd;
end
end // cmdaddr_bitcnt == 5'h 7
end // StIdle
StAddr: begin
// NOTE: The coding style in this state is ugly. How can we improve?
cmdaddr_shift_en = 1'b 1;
// Latch locality
if (cmdaddr_bitcnt == 5'h 13) begin
latch_locality = 1'b 1;
end
if (cmdaddr_bitcnt >= 5'h 18) begin
// Send Wait byte [18h:1Fh]
sck_p2s_valid = 1'b 1;
sck_data_sel = SelWait;
end
// Next state: if is_tpm_reg 1 && !cfg_hw_reg_dis
if (cmdaddr_bitcnt == 5'h 1F && cmd_type == Read) begin
if (!is_tpm_reg || sck_tpm_cfg.tpm_mode) begin
// If out of TPM register (not staring with 0xD4_XXXX) or
// TPM mode is CRB, always processed by SW
sck_st_d = StWait;
end else if (is_hw_reg) begin
// If read command and HW REG, then return by HW
// is_hw_reg contains (is_tpm_reg && (locality < NumLocality))
sck_st_d = StStartByte;
end else if (invalid_locality && sck_tpm_cfg.invalid_locality) begin
// The read request is out of supported Localities.
// Return FFh
sck_st_d = StInvalid;
end else begin
// Other read command sends to Wait, till SW response
sck_st_d = StWait;
end
end // cmdaddr_bitcnt == 5'h 1F
if (cmdaddr_bitcnt == 5'h 1F && cmd_type == Write) begin
if (~|sck_wrfifo_wdepth) begin
// Write command and FIFO is empty. Ready to push
// TODO: Change the state machine to send start byte at
// cmdaddr_bitcnt == 5'h 17 if write command and FIFO is
// empty, then the state can go to StWrite directly.
sck_st_d = StStartByte;
end else begin
// FIFO is not empty. Move to StWait and waits for the empty write
// fifo.
sck_st_d = StWait;
end
end // cmd_type == Write
end // StAddr
StWait: begin
sck_p2s_valid = 1'b 1;
sck_data_sel = SelWait;
// at every LSB of a byte, check the next state condition
if (isck_p2s_sent &&
(((cmd_type == Read) && |isck_rdfifo_rdepth) ||
((cmd_type == Write) && ~|sck_wrfifo_wdepth))) begin
sck_st_d = StStartByte;
end
end // StWait
StStartByte: begin
sck_p2s_valid = 1'b 1;
sck_data_sel = SelStart;
if (isck_p2s_sent) begin
// Must move to next state as StartByte is a byte
if ((cmd_type == Read) && is_hw_reg) begin
sck_st_d = StReadHwReg;
end else if (cmd_type == Read) begin
sck_st_d = StReadFifo;
end else if (cmd_type == Write) begin
sck_st_d = StWrite;
end
end
end // StStartByte
StReadFifo: begin
sck_rddata_shift_en = 1'b 1;
sck_p2s_valid = 1'b 1;
sck_data_sel = SelRdFifo;
// TODO: RdFifo rready (sck --> isck)
// TODO: check xfer_size handling
if (isck_p2s_sent && xfer_size_met) begin
sck_st_d = StEnd;
end
end // StReadFifo
StReadHwReg: begin
sck_p2s_valid = 1'b 1;
sck_data_sel = SelHwReg;
// HW Reg slice? using index
if (isck_p2s_sent && xfer_size_met) begin
sck_st_d = StEnd;
end
end // StReadHwReg
StWrite: begin
wrdata_shift_en = 1'b 1;
// Processed by the logic. Does not have to do
// TODO: check xfer_size handling
if (sck_wrfifo_wvalid && xfer_size_met) begin
sck_st_d = StEnd;
end
end // StWrite
StInvalid: begin // TERMINAL_STATE
// Send FFh
if (cmd_type == Read) begin
sck_p2s_valid = 1'b 1;
sck_data_sel = SelInvalid;
end
end // StInvalid
StEnd: begin // TERMINAL_STATE
// TODO: Check if open pull-up cancel the transaction?
// If yes, then drive 0x00 for the read command
if (cmd_type == Read) begin
sck_p2s_valid = 1'b 1;
sck_data_sel = SelWait; // drive 0x00
end
end // StEnd
default: begin
sck_st_d = StIdle;
end
endcase
end : next_state
//////////////
// Instance //
//////////////
prim_fifo_async #(
.Width (CmdAddrSize),
.Depth (CmdAddrFifoDepth),
.OutputZeroIfEmpty (1'b 1)
) u_cmdaddr_buffer (
.clk_wr_i (clk_in_i),
.rst_wr_ni (sys_rst_ni),
.wvalid_i (sck_cmdaddr_wvalid),
.wready_o (sck_cmdaddr_wready),
.wdata_i (sck_cmdaddr_wdata_d),
.wdepth_o (sck_cmdaddr_wdepth),
.clk_rd_i (sys_clk_i),
.rst_rd_ni (sys_rst_ni),
.rvalid_o (sys_cmdaddr_rvalid_o),
.rready_i (sys_cmdaddr_rready_i),
.rdata_o (sys_cmdaddr_rdata_o),
.rdepth_o (sys_cmdaddr_rdepth)
);
prim_fifo_async #(
.Width (DataFifoSize),
.Depth (WrFifoDepth),
.OutputZeroIfEmpty (1'b 1)
) u_wrfifo (
.clk_wr_i (clk_in_i),
.rst_wr_ni (sys_rst_ni),
.wvalid_i (sck_wrfifo_wvalid),
.wready_o (sck_wrfifo_wready),
.wdata_i (sck_wrfifo_wdata),
.wdepth_o (sck_wrfifo_wdepth),
.clk_rd_i (sys_clk_i),
.rst_rd_ni (sys_rst_ni),
.rvalid_o (sys_wrfifo_rvalid_o),
.rready_i (sys_wrfifo_rready_i),
.rdata_o (sys_wrfifo_rdata_o),
.rdepth_o (sys_wrfifo_rdepth)
);
// The content inside the Read FIFO needs to be flush out when a TPM
// transaction is completed (CSb deasserted). So, everytime CSb is
// deasserted --> rst_n asserted. So, reset the read FIFO.
prim_fifo_async #(
.Width (DataFifoSize),
.Depth (RdFifoDepth),
.OutputZeroIfEmpty (1'b 1)
) u_rdfifo (
.clk_wr_i (sys_clk_i),
.rst_wr_ni (rst_n), // Need synchronizer?
.wvalid_i (sys_rdfifo_wvalid_i),
.wready_o (sys_rdfifo_wready_o),
.wdata_i (sys_rdfifo_wdata_i),
.wdepth_o (sys_rdfifo_wdepth),
.clk_rd_i (clk_out_i),
.rst_rd_ni (rst_n),
.rvalid_o (isck_rdfifo_rvalid),
.rready_i (isck_rdfifo_rready),
.rdata_o (isck_rdfifo_rdata),
.rdepth_o (isck_rdfifo_rdepth)
);
// Logic Not Used
logic unused_logic;
assign unused_logic = ^{ sck_cmdaddr_wready,
sck_cmdaddr_wdepth,
sck_wrfifo_wready
};
///////////////
// Assertion //
///////////////
// Parameters
`ASSERT_INIT(CmdPowerof2_A, CmdAddrFifoDepth == 2**$clog2(CmdAddrFifoDepth))
`ASSERT_INIT(RdPowerof2_A, RdFifoDepth == 2**$clog2(RdFifoDepth))
// Write FIFO: should be in the range of TPM spec supported (4B, 8B, 32B, 64B)
// Read FIFO can have more flexible size as SW can push more bytes in
// a transaction.
`ASSERT_INIT(WrDepthSpec_A, WrFifoDepth inside {4, 8, 32, 64})
`ASSERT_INIT(DataFifoLessThan64_A, RdFifoDepth <= 64)
`ASSERT_INIT(TpmRegSizeMatch_A, TpmRegisterSize == $bits(tpm_reg_t))
// CMDADDR buffer should be available, if not, at least error to be propagated
`ASSERT(CmdAddrAvailable_A,
sck_cmdaddr_wvalid |-> sck_cmdaddr_wready,
clk_in_i, !rst_n)
// When a byte is being pushed to WrFifo, the FIFO should have a space
`ASSERT(WrFifoAvailable_A,
sck_wrfifo_wvalid |-> sck_wrfifo_wready,
clk_in_i, !rst_n)
// If the command and the address have been shifted, the Locality, command
// type should be matched with the shifted register.
`ASSERT(CmdAddrInfo_A,
$fell(cmdaddr_shift_en) && !csb_i && sck_tpm_cfg.tpm_en |->
(locality == sck_cmdaddr_wdata_q[15:12]) &&
(cmd_type == sck_cmdaddr_wdata_q[31]),
clk_in_i, !rst_n)
// The condition of tpm_reg check
`ASSERT(TpmRegCondition_A,
check_tpm_reg |-> (cmdaddr_bitcnt == 5'h F),
clk_in_i, !rst_n)
// when latch_locality, the address should have 16 bits received.
`ASSERT(LocalityLatchCondition_A,
latch_locality |-> (cmdaddr_bitcnt == 5'h 13),
clk_in_i, !rst_n)
// when check_hw_reg is set, the address should have a word size
`ASSERT(HwRegCondition_A,
check_hw_reg |-> (cmdaddr_bitcnt == 5'h 1D),
clk_in_i, !rst_n)
// If is_hw_reg set, then it should be FIFO reg and within locality
`ASSERT(HwRegCondition2_a,
$rose(is_hw_reg) |->
is_tpm_reg && !invalid_locality && !sck_tpm_cfg.hw_reg_dis,
clk_in_i, !rst_n)
// If module returns data in StAddr, the cmdaddr_bitcount should be the last
// byte
`ASSERT(CmdAddrBitCntInAddrSt_A,
(sck_st_q == StAddr) && sck_p2s_valid |-> (cmdaddr_bitcnt inside {[23:31]}),
clk_in_i, !rst_n)
endmodule : spi_tpm