Move i2c to clash cores#2584
Conversation
e1989c6 to
8553ddc
Compare
|
|
||
| {-# ANN system Synthesize { t_name = "system", t_inputs = [], t_output = PortName "" } #-} | ||
| system = system0 systemClockGen resetGen | ||
| {-# ANN testBench Synthesize { t_name = "testBench", t_inputs = [], t_output = PortName "" } #-} |
There was a problem hiding this comment.
As discussed, some comments:
testBenchis a magic name; I think it's better to give it a different name. TheSynthesizeannotation probably masks the special machinery.- The record syntax with a space between the constructor and the
{is confusing, it looks like function application. How many arguments doesANNtake? :-) - We didn't document this yet, but we did discuss steering people away from the constructor and to using
defSyninstead. I think mainly so we can add fields without existing use sites suddenly having undefined field contents.
There was a problem hiding this comment.
Ah forget about the defSyn that doesn't work... <sad trombone>
fb61cae to
2efc4a0
Compare
bde9655 to
f08ba82
Compare
6af2276 to
d663a1a
Compare
bfa702d to
e9b217b
Compare
0ccc4ac to
41776f8
Compare
Also remove `Synthesize` annotations from `i2c` core and internals.
…binatorial Co-authored-by: lucas@qbaylogic.com
Hide the print statements of the I2C unittest behind debug flags. This debug flag is currently always False. TODO: Expose the debug flag of the I2C unittest
Since the I2C core is subject to changes in the near future, we've moved it to an `Experimental` submodule.
41776f8 to
59cb3c7
Compare
christiaanb
left a comment
There was a problem hiding this comment.
Aside from commit messages stating that specific changes were made, they should explain why the changes were made.
| , clashTestGroup "crc32" | ||
| [ runTest "CRC32" def | ||
| ] | ||
| , clashTestGroup "i2c" |
There was a problem hiding this comment.
The I2C test is:
- The only test in the clash testsuite that has multiple synthesize annotations
- Tests
SimIOin a common user scenario.
So I would like it if this test wasn't removed. And given that the I2C core in clash cores has a new API, perhaps we can either just:
- Keep the old domain-monomorphic code in the
examplesdirectory - Or move the old domain-monomorphic code to a test directory
So that we still have a test with multiple Synthesize annotations.
There was a problem hiding this comment.
- Sounds like the best option to me!
There was a problem hiding this comment.
Also agree with 2, we can just put the old code in a test directory.
| , _coreTxd :: Bit -- coreTxd register | ||
| , _shiftsr :: Bool -- shift sr | ||
| , _ld :: Bool -- load values in to sr | ||
| , _i2cOpAck :: Bool -- host cmd acknowlegde register |
There was a problem hiding this comment.
Why is the command ack made combinational? The commit message doesn't state why this change was made.
| -> do display "CONFaddrAck" | ||
| pure s { i2cConfStateM = CONFreg | ||
| , i2cWrite = False | ||
| -> if rxAck then do |
There was a problem hiding this comment.
Are these changes made because the ack made combinational in the another commit? Perhaps mention that in the commit message then.
|
|
||
| type ConfI = (Bool,Bool,Bool,Bool,Bool) | ||
| type ConfO = (Bool,Maybe I2COperation,Bool,Bool) | ||
| data ConfI = ConfI { i2cRst :: Bool |
There was a problem hiding this comment.
The commit message just says "refactor", perhaps explain what the refactor does.
| configT s0 ConfI{i2cRst=rst,i2cEna=ena,i2cCmdAck=cmdAck,i2cRxAck=rxAck,i2cAl=al} = do | ||
| s <- readReg s0 | ||
| let ConfS confStateM claim op lutIndex fault = s | ||
| let ConfS confStateM claim op lutIndex fault debug = s |
There was a problem hiding this comment.
Why are debug messages made conditional? Perhaps that could be mentioned in the commit message.
There was a problem hiding this comment.
Because otherwise they flood the terminal when you run clash-cores:unittests :)
| -- | Core for I2C communication. Returns the output enable signals for SCL en SDA | ||
| -- These signals assume that when they are `high`, they pull down SCL and SDA respectively. | ||
| -- For 2-wire I2C, you can use BiSignals (`Clash.Signal.Bidirectional.BiSignalIn` and `Clash.Signal.Bidirectional.BiSignalOut`) | ||
| -- | ||
| -- === __Example__ | ||
| -- | ||
| -- @ | ||
| -- i2cComp clk rst ena sclIn sdaIn = (sclOut, sdaOut) | ||
| -- where | ||
| -- sclOut = writeToBiSignal sclIn (mux sclOe (pure $ Just 0) (pure Nothing)) | ||
| -- sdaOut = writeToBiSignal sdaIn (mux sdaOe (pure $ Just 0) (pure Nothing)) | ||
| -- (sclOe, sdaOe) = unbundle i2cO | ||
| -- i2cIn = bundle (readFromBiSignal sclIn, readFromBiSignal sdaIn) | ||
| -- (dout,i2cOpAck,busy,al,ackWrite,i2cOut) = i2c clk arst rst ena clkCnt claimBus i2cOp ackRead i2cI | ||
| -- @ |
There was a problem hiding this comment.
The outputs for SCL and SDA are Maybe Bit, neither the haddock comment, nor the code example seem to get this rights.
There was a problem hiding this comment.
(I did not have a chance to actually look at this code and the changes; and I don't have time anymore to do so now. So I'm making a bunch of assumptions and next week I'll see if I was off base)
SCL and SDA are open-drain I/O pins: they are both input and output, and they are either driven low or high-impedance. I assume the Maybe Bit therefore has two states: Nothing (Hi-Z) and Just 0 (drive low). And I suppose this encoding was chosen because in an FPGA, that's how you would drive the tri-state used to implement an open-drain pin on the FPGA. Specifically, the trivial way to implement open-drain with a tri-state is to have the input from the FPGA tied to ground and put your data on the gate: gated when high, outputting when low.
So this Maybe Bit is purely pragmatic: it encodes how you drive a tristate, but it only has two states. It's also not entirely practical; we have two tri-state primitives, one for the ICE40 and one for the ECP5, and one of those uses an enable signal to gate, and the other a Signal Bool to gate; neither actually uses Maybe to gate.
So I'd like to propose we change the SCL and SDA outputs to just be Bit. And this would then encode the two possible states: 1 = Hi-Z, 0 = drive low, just like I²C defines it afaik.
And related but not for this PR, I'd like to harmonise our two tri-state primitives, and define wrappers that can be used to implement open-drain and open-source, where the data inputs to those wrappers are just Bit and they wrap the underlying tri-state primitives in the logical way (data input tied to rail, gate input tied to data, either inverted or direct). But that's for later; it just clarifies how I see the future for using this I²C core with I/O buffer primitives.
There was a problem hiding this comment.
So this Maybe Bit is purely pragmatic: it encodes how you drive a tristate, but it only has two states
Indeed! It's to show that it drives a tristate.
It's also not entirely practical; we have two tri-state primitives, one for the ICE40 and one for the ECP5, and one of those uses an enable signal to gate, and the other a Signal Bool to gate; neither actually uses Maybe to gate.
I have never seen these primitives to be honest, the choice was made to be compatible with BiSignals. at least Vivado simply infers the tristate buffer when you use BiSignal. We don't use any explicit primitives in the Bittide implementations.
Personally I prefer the Maybe Bit over `Bit.
And related but not for this PR, I'd like to harmonise our two tri-state primitives, and define wrappers that can be used to implement open-drain and open-source, where the data inputs to those wrappers are just Bit and they wrap the underlying tri-state primitives in the logical way (data input tied to rail, gate input tied to data, either inverted or direct). But that's for later; it just clarifies how I see the future for using this I²C core with I/O buffer primitives.
Personally I don't find it intuitive to make the behavior depend on the outside world, whats the downside of using Maybe Bit to you?
There was a problem hiding this comment.
Maybe Bit can do the wrong thing. You could output Just 1 which is wrong for i2c. I would like to propose a third option. Have a unit data type newtype Low = Low or something like that and output Maybe Low. If you want you can even make a custom BitPack so that it still implemented as 2 bits.
There was a problem hiding this comment.
Indeed the core should never output Just 1, and it never does. I don't see a problem in using Maybe Bit as tristrate drive type for i2c. It intuitively hints to the user that its mean to be a tristate driver
There was a problem hiding this comment.
at least Vivado simply infers the tristate buffer when you use BiSignal.
Oh that's good to know! I just immediately thought of those two primitives.
Thinking about it a bit more, a wrapper for a BiSignal is less pretty in API than I initially imagined it could be. But still I'm leaning towards it being the right way. It would also hide a bit of the complexity of BiSignal for this use.
We could add this to the Prelude:
openDrainIO :: BiSignalIn ds dom (BitSize a) -> Signal dom a -> (BiSignalOut ds dom (BitSize a), Signal dom a)
openSourceIO :: BiSignalIn ds dom (BitSize a) -> Signal dom a -> (BiSignalOut ds dom (BitSize a), Signal dom a)and people wouldn't have to do all the readFromBiSignal and writeToBiSignal themselves.
Personally I don't find it intuitive to make the behavior depend on the outside world, whats the downside of using
Maybe Bitto you?
It's semantically wrong. It is a two-valued signal, not a three-valued one. If you want to force people towards a specific implementation, you should perhaps create a new type OpenDrain or something like that. So what's above becomes:
openDrainIO :: BiSignalIn ds dom (BitSize a) -> Signal dom (OpenDrain a) -> (BiSignalOut ds dom (BitSize a), Signal dom a)(that's my counter-suggestion to Rowan's Low. I think it is more obvious in its intention.)
It intuitively hints to the user that its mean to be a tristate driver
In an FPGA, that is the usual implementation, yes, since it's there anyway. But it's not supposed to be a tri-state driver in my view. It's supposed to be an open-drain driver. But in an FPGA, every I/O pin is connected to a tri-state driver, and that's what you use. A standard totem-pole output is a tri-state that is never Hi-Z, an open-drain output is a tri-state that is never driven high, and an open-source output is a tri-state that is never driven low. Three types of outputs, all of them having two states. Only the true tri-state output has three states.
In an ASIC, you would never use a tri-state to implement an open drain, it would be wastage of die space on a staggering scale, relatively speaking.
There was a problem hiding this comment.
I don't intend to codify the BiSignal in the I²C stuff; people are free to choose their pin driver. But I think you either use platform-specific primitives or the BiSignal stuff. As I said, I don't mean to include these Prelude extensions as part of this PR, it's just how I envision it being used, which supports my argument in favour of just Bit.
Currently the i2c core in the
examplesfolder can not be used without copying the files or having a local clone of the reposity. This pull request moves the core over toclash-coresand aims to making it properly documented.TODO:
There are a few things which I'd like added to this PR:
I've created an issue for the changes which I think are needed before moving the I2C core out of its Experimental submodule.