[i3c] Output driver abstraction, top-level fixes - #31136
Conversation
andreaskurth
left a comment
There was a problem hiding this comment.
Thanks @alees24, good to see ifdef inside the package replaced by a proper file split for the different driver variants, and the drv_state_t helpers should make the transceiver code considerably easier to follow.
The approach seems sensible to me, but I have a few questions/suggestions.
I also checked the RSTACT fix, the EXT_IBI_QUEUE_EN correction and the new clamp limits against HCI Table 139, Table 44 and Table 94 respectively, and they all look right.
|
|
||
| .enable_i (ctrl_enabled), | ||
| // PIO Queue enable; reading is prohibited when disabled (HCI Table 49). | ||
| .enable_i (reg2hw_i.pio_control.enable.q), |
There was a problem hiding this comment.
This looks like it will collide with #30998, which removes the enable_i port from i3c_hci_ibi altogether and drops this connection. Was the intention to keep the port after all, and should that PR be updated to match (or this change dropped)?
If enable_i does stay: ibi_read comes from i3c_queues, which has no enable of its own, so a read of IBI_PORT while PIO_CONTROL.ENABLE is 0 still pops the FIFO but doesn't advance rd_words_left. That would desynchronise the status/data interleaving until a software reset. Would it be safer to gate the read itself (or report it via hciq_access_err) rather than only the counter?
There was a problem hiding this comment.
No, it's intended to go as annotated in that later PR; this one has been sitting for a bit longer.
| |-----------------------------------------------------------------------------------------------|-------------|---------------|--------|-----------| | ||
| | Primary and Secondary Controller functionality; implies Target functionality and a single bus | 1 | 1 | 1 | - | | ||
| | Primary Controller and Target; these may be on a single bus or separated buses | 1 | 0 | 1 | Yes | | ||
| | Primary controller only | 1 | 0 | 0 | Yes | |
There was a problem hiding this comment.
Are "Primary controller only" and "Target only" (line 16) really available yet? Target doesn't currently gate any logic: i3c_target is instantiated unconditionally and targ_enabled comes only from TARG_CONTROL.EN, so a Target=0 build still contains a fully working target that software can enable, while TARG_STATUS.PRESENT reads 0. That seems to contradict the note added at i3c_core.sv:1592 ("neither can presently be unconfigured, since all instances require all functionality").
Would it be clearer to mark these two rows - as well until the parameter actually gates the logic?
There was a problem hiding this comment.
I think that should wait until a later review of the documentation. The parameters do not, in the visible RTL, do very much at present, you're correct. I've a local branch which started to scope out the work involved because even in the first deployment I suspect that it will not be the case that both instances require all functionality; proper use of the parameters may occur before first release.
| @@ -0,0 +1,18 @@ | |||
| # Design Parameters | |||
There was a problem hiding this comment.
This file isn't listed in SUMMARY.md and nothing links to it, so it won't appear on the docs site. The link checker only validates on-disk paths, so it won't flag the omission either. Could it be added alongside Integration Notes?
There was a problem hiding this comment.
OK. Since the other blocks have tended to follow a template, I've not regarded those links as being so 'free form.'
| Two alternative sets of driver outputs are available in the IP block: | ||
|
|
||
| ### Output Driver Style 1 | ||
| ### Output Driver Style 1 (`i3c_io_pkg.sv`) |
There was a problem hiding this comment.
Should these be the post-move paths, i.e. rtl/io_sep/i3c_io_pkg.sv here and rtl/io_vod/i3c_io_pkg.sv at line 91? i3c_io_vod_pkg.sv no longer exists.
While touching the headings - would it be worth naming the styles rather than numbering them, e.g. "Separated Push-Pull and Open Drain Enables" and "Virtual Open Drain"? "Style 1"/"Style 2" doesn't convey anything on its own, and the surrounding text (and DrvSeparatedEn) already uses the descriptive terms.
There was a problem hiding this comment.
Thanks for the first response about pathnames; it looks as though that change may hold, but it was far from certain that it would be accepted when I tentatively made it.
I'll keep Style 1/2 - referenced in many places - for now, because I expect all of this still to change. I was aware that numbers are less than helpful. There's an ongoing discussion about even what EG2 requires.
| - `scl_en` - Enable for the Serial CLock (SCL) output signal; its state depends upon whether the IP block is the Active Controller. | ||
| - `scl` - SCL output signal, gated by `scl_en`. | ||
|
|
||
| **In the design source, `i3c_io_pkg.sv` and `i3c_io_vod_pkg.sv` both offer an implementation of the package `i3c_io_pkg`, so only _one_ of these files shall be included in the build.** |
There was a problem hiding this comment.
Same here: these are now rtl/io_sep/i3c_io_pkg.sv and rtl/io_vod/i3c_io_pkg.sv.
| assign hw2reg_o.queue_status_level.ibi_buffer_lvl.d = ibi_buffer_lvl; | ||
|
|
||
| // These two fields are specified as _entries_ and not DWORDs; Commands are _two_ DWORDs. | ||
| assign hw2reg_o.queue_status_level.response_buffer_lvl.d = fifo_state[FIFO_RspQ].used; |
There was a problem hiding this comment.
Did the TODO removed above ("These values are restricted to 8 bits by the HCI; we have 10 bit used/avail values") not apply to these fields too? used/avail are BufAddrW+1 = 11 bits, and RESPONSE_BUFFER_LVL, CMD_QUEUE_FREE_LVL, RX_BUF_LVL and TX_BUF_FREE_LVL (lines 2086-2087) are all 8 bits, so they still wrap for exactly the misconfiguration the new comment guards against. Should they be clamped the same way?
There was a problem hiding this comment.
I've started something here, haven't I? This is probably another case of 'software should get what it requests' if misconfiguring things, and assertions in the software should catch misconfiguration. I've introduced a function that clamps the values to the maximum representable levels, but only because this is inexpensive for 2^n.
There was a problem hiding this comment.
| @@ -20,6 +23,7 @@ module i3c_core | |||
| parameter int unsigned ClkFreq = 50_000_000, // IP clock frequency, Hz. | |||
| parameter bit PrimaryCtrl = 1'b1, | |||
| parameter bit SecondaryCtrl = 1'b1, | |||
There was a problem hiding this comment.
i3c.sv now defaults SecondaryCtrl to 1'b0; should this one match? As it stands the core's standalone default (1,1,1) is the row design_parameters.md marks as not yet available.
There was a problem hiding this comment.
I should probably stop supplying default parameter values in submodules; they are, of course, overridden in the parent, although in this case the 'i3c.sv' parent is supposed to be optional, and OT-specific.
| // Controller and Target 'present' indications. | ||
| // Note: neither can presently be unconfigured, since all instances require all functionality. | ||
| assign hw2reg_o.ctrl_status.d = PrimaryCtrl | SecondaryCtrl; | ||
| assign hw2reg_o.targ_status.present.d = Target | SecondaryCtrl | !PrimaryCtrl; |
There was a problem hiding this comment.
Now that Target is explicit, are the SecondaryCtrl | !PrimaryCtrl terms still needed? Per the new table both of those cases already imply Target=1, and the !PrimaryCtrl term makes an all-zero configuration report a target as present.
| package i3c_io_pkg; | ||
| import i3c_pkg::*; | ||
|
|
||
| // Single driver enable signal. |
There was a problem hiding this comment.
This comment looks copy-pasted from the VOD variant - this is the package with separate pp_en/od_en enables.
| - rtl/i3c_controller_pkg.sv | ||
| - rtl/i3c_timing_pkg.sv | ||
| - rtl/i3c_io_pkg.sv | ||
| - rtl/io_sep/i3c_io_pkg.sv |
There was a problem hiding this comment.
The PR description mentions "leaving the build to choose the appropriate RTL design file", but this is hard-coded and there's no fileset or target for the VOD variant, so choosing still means editing this file. Would a second fileset (e.g. files_rtl_io_vod) selected by a target be worth adding? Happy for that to be a tracked follow-up rather than done here.
There was a problem hiding this comment.
Well the description meant builds generally, not just OT EG2; I'm anticipating that EG2 will have its OT-specific '.core' files set for what it needs, in the same way that the hjson cannot support all possible I3C IP configurations, just the one that OT EG2 uses.
etterli
left a comment
There was a problem hiding this comment.
Thanks for the refactoring. The VOD now looks like we are almost CIO compatible :)
The problem is that the combined signal struct is not nicely supported by the framework, there needs always to be a data and enable signal. This is what we now have for the VOD case but the signals are grouped to one struct at the IP boundary. This raises the following question to me:
How does topgen know which signal of the struct has to be connected to which pad/pinmux port? AFAIU, this would require manual changes in the top RTL file to correctly wire the data and enable signals. And these changes would depend on the chosen IO mode.
Even if we find a solution, having both IO options is not easily supported in the hjson. We cannot parametrise parts of the hjson.
I've created a CIO compatible proposal on https://github.com/etterli/opentitan/tree/i3c-io. This keeps the combined i3c_targ_bus_drv_t structs but adds the required wiring such that only CIOs can be used. The arbitration logic there is now probably no longer required when the IP is used in the VOD mode/setting.
Just for my understanding, do we see any use case where we require the separated IO scheme? I'm not against having multiple options but just want to understand the context.
| Two alternative sets of driver outputs are available in the IP block: | ||
|
|
||
| ### Output Driver Style 1 | ||
| ### Output Driver Style 1 (`i3c_io_pkg.sv`) |
There was a problem hiding this comment.
NIT: Both packages have the same filename. Just the folder is named differently.
There was a problem hiding this comment.
Yes, thanks. Updated. I expected to be slapped down in CI for having subfolders, and I shall not be surprised if down the line something in our tooling gets upset. It seems to be without precedent.
| **In the design source, `i3c_io_pkg.sv` and `i3c_io_vod_pkg.sv` both offer an implementation of the package `i3c_io_pkg`, so only _one_ of these files shall be included in the build.** | ||
|
|
There was a problem hiding this comment.
Both packages have the same filename. Just the folder is named differently.
| } drv_state_t; | ||
|
|
||
| // Return the appropriate driver state for Open Drain signaling. | ||
| // - pull up is enabled, driver is enabled and may pull down. |
There was a problem hiding this comment.
why must the pull up be activated when the IO should be open_drain? Is this controller specific?
General I3C question: Isn't it intended that the bus pull ups are on the PCB? Is this in the responsibility of the active controller?
There was a problem hiding this comment.
High-keepers are always on, but pull-ups need to be switched on/off. In open drain mode, the pull-up is required to produce the 'high' state of the bus; otherwise the signal voltage is defined. In push-pull mode, having the pull-up enabled, whilst it may not break function, it would at least be creating a resistance against which the NMOS driver (pulling low) would have to fight, and increase the power consumption.
Even with external pull-ups, the Active Controller is still in charge of them. Ditto for the high-keepers. Although the high-keepers are usually left enabled, they may prove inadequate for a given physical bus, so the I3C Basic specification recommends that it be possible to enable/disable them.
There was a problem hiding this comment.
I see, makes sense. Thanks.
andreaskurth
left a comment
There was a problem hiding this comment.
Thanks @alees24, you fixed many points I raised, and I'm OK with the ones you deferred or declined.
Two things on the new clamping code, otherwise this LGTM.
| for (genvar t = 0; t < NumTargets; t++) begin : gen_vt_queue_lvl | ||
| assign hw2reg_o.targ_tx_queue_status_level[t].tx_desc_free_lvl.d = | ||
| fifo_state[FIFO_TxDTarg0 + t].avail; | ||
| 12'(fifo_lvl_clamp(fifo_state[FIFO_TxDTarg0 + t].avail, 12)); |
There was a problem hiding this comment.
TARG_TX_QUEUE_STATUS_LEVEL.TX_DESC_FREE_LVL is bits 23:16, i.e. 8 bits (i3c_reg_pkg.sv:2607-2609), so the 12-bit clamp and cast here get truncated to 8 anyway and the field still wraps above 255. Should this be 8'(fifo_lvl_clamp(..., 8))?
Its sibling tx_buf_free_lvl on the next line genuinely is 12 bits, which is presumably how the two came to be paired. The other 12-bit uses look fine (no-ops, since used/avail are 11 bits, but harmless as a precaution).
There was a problem hiding this comment.
Thanks; actually this has prompted to me to check a bit more carefully - the TTI probably isn't yet complete since there have never been any proper requirements and nothing uses it fully - and I discovered that the rx_desc_lvl field was inappropriately set to 12 bits.
For data Tx and Rx from/to the Virtual Targets, 2^12 should be more than enough for the data words, and 2^8 should be more than ample for the transfer descriptors. For IBI and Async Events I stick to 2^12 because these have inline descriptors rather than separated queues for descriptors and data.
| assign hw2reg_o.queue_status_level.ibi_buffer_lvl.d = ibi_buffer_lvl; | ||
|
|
||
| // These two fields are specified as _entries_ and not DWORDs; Commands are _two_ DWORDs. | ||
| assign hw2reg_o.queue_status_level.response_buffer_lvl.d = fifo_state[FIFO_RspQ].used; |
There was a problem hiding this comment.
Yes, |
andreaskurth
left a comment
There was a problem hiding this comment.
Thanks @alees24 for addressing my feedback. The new fifo_lvl_clamp function seems to return one bit fewer than it takes; pls see below.
| // Convenience function that clamps the supplied quantity to the specified number of bits for | ||
| // presentation via an HCI register field. This is just an aid to software in the event that | ||
| // FIFO properties have been misconfigured; it should lead to better behavior than wrapping would. | ||
| function automatic bit [DepthW-1:0] fifo_lvl_clamp(input [DepthW:0] entries, input int n); |
There was a problem hiding this comment.
The return type is one bit narrower than the argument: entries is [DepthW:0] (11 bits, 0-1024) but the result is [DepthW-1:0], so the maximum value wraps rather than passing through.
used/avail do reach 1024, since fifo_size = (max - min) + 1 is 1024 for min=0, max=1023, so a queue given the whole message buffer would report 0 when completely full (used) or 0 free when completely empty (avail). It affects the four 12-bit callers - async_evt_lvl, targ_buf_status_level.rx_buf_lvl, ibi_free_lvl and tx_buf_free_lvl; the 5- and 8-bit ones are safe because the guard catches those values first.
| function automatic bit [DepthW-1:0] fifo_lvl_clamp(input [DepthW:0] entries, input int n); | |
| function automatic bit [DepthW:0] fifo_lvl_clamp(input [DepthW:0] entries, input int n); |
One related detail, in case it looks like the mask should do the work instead: ('b1 << n) - 'b1 is evaluated at the width of the enclosing DepthW'() cast, so for n >= DepthW it saturates at 1023 rather than 2**n - 1. That is unreachable today (the guard is false there), but it is why widening the return type seems the cleaner fix.
There was a problem hiding this comment.
Och, branch-juggling again; the version that you're seeing is not correct. I briefly confused myself thinking about the values as byte counts rather than DWORDs. It was meant to be [DepthW:0] as you suggest, although in fact it does not matter. The 12-bit fields I've created in the register API are that width to leave a bit of headroom for larger buffers, but nothing is intended to support a buffer that large, and something else would surely break first. The 12-bit fields therefore don't require use of fifo_lvl_clamp() so I've dropped that complication.
In fact no queue can in practice be allocated the entire message buffer, so even 1024 entries does not need to be accommodated; as a though exercise, I suppose one could conceivably create a Target that does nothing more than send IBIs, but anything that actually requires Read/Write Transfers must have a descriptor queue alongside the data buffer.
Re the last point, about expression width, rest assured that I am aware of that.
Introduce Target switch to cover all intended roles. Document in design_parameters.md which roles are currently supported, i.e. available in this first version. Indicate the presence of Controller and/or Target logic. Introduce Virtual Open Drain driver implementation, and tidy up the selection of driver style, leaving the build to choose the appropriate RTL design file. Correct the spelling of RSTACT. Signed-off-by: Adrian Lees <a.lees@lowrisc.org>
andreaskurth
left a comment
There was a problem hiding this comment.
LGTM, thank you @alees24!
Introduce Target switch to cover all intended roles. Document in design_parameters.md which roles are currently supported, i.e. available in this first version.
Indicate the presence of Controller and/or Target logic.
Introduce Virtual Open Drain driver implementation, and tidy up the selection of driver style, leaving the build to choose the appropriate RTL design file.
Correct the spelling of RSTACT.
Update: migrated files because, as I thought may happen, linting doesn't like the mismatch between filename and package name.
Update 2: CI seems happy with the restructuring, squashed and pushed again.