How to write to a blockRam?

As of right now I have a readOnly blockRam that stores a matrix of all 0s and the simulation works, but I would like to also be able to write to it. I know I should probably replace (pure Nothing) by something, but I do not know what. I have tried giving bram two params (rdAddr and wrAddr) and made sample data for wrAddr, but this did not work while simulating. Why is this and how do I make it so I can write to my blockRam at any point?

type Sub = Vec 27 Value
type Matrix = Vec 16 Sub

acc :: Acc
acc = repeat (repeat 0)

bram rdAddr = blockRam acc rdAddr (pure Nothing)

read_addr :: [Int]
read_addr = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]

sim_bram :: [Acc_sub]
sim_bram = simulate @System bram read_addr 

If you check the documentation, it gives a description of the third parameter
https://hackage.haskell.org/package/clash-prelude-1.8.1/docs/Clash-Prelude.html#v:blockRam

There it says

(write address w, value to write)

So if you wanted to write the value 3 to address 12, you’d have to pass Just (12, 3) in that place (as a signal, of course).

This API lets you write and read at the same time, so you could pass this write argument directly through to the blockRam you’re using.

A more complex example, but here it shows how those signals can be used. This only allows somebody to either read or to write

data BlockRamOp addr a = Read addr | Write addr a
  deriving (NFDataX, Generic)

bram ::
  (HiddenClockResetEnable dom, Enum addr) =>
  Signal dom (BlockRamOp addr Acc_sub) ->
  Signal dom Acc_sub
bram op = blockRam acc readSignal writeSignal
 where
  (readSignal, writeSignal) = unbundle (splitOp <$> op)
  splitOp (Read addr) = (addr, Nothing)
  splitOp (Write addr val) = (addr, Just (addr, val))

This code takes the signal of operations and “pulls it apart” into two signals, one for the read address argument and one for the write (address, value) argument.

Does this help make it more clear?

2 Likes

Yes that helps a lot! Thank you so much! One more question though. How would I write a value attained in the program to the block ram? I cant seem to get my input “lifted” into the signal domain

If you say you want to lift your input into the signal domain. Does that mean that you are trying to write a constant into the blockram? Or the result of a single calculation?

Could you tell us more about the types / structure of your circuit or the circuit you are trying to make?

I am trying to write the result of a calculation into the blockram. The matrix should be updated repeatedly to hold new values, calculated using the old values. One update will change one element of one subvector (type Sub, a vector of 27 Value’s). The blockram works when using simulate, but what if I want to write/read only one value?

type Value = Signed 8
type Sub = Vec 27 Value
type Matrix = Vec 16 Sub

acc :: Acc
acc = repeat (repeat 0)

I tried doing this, but it gives me an error:
Couldn’t match expected type: Signal dom (BlockRamOp addr0 Acc_sub) with actual type: BlockRamOp addr1 (Vec 27 a3). I get what it means but dont know how i should fix it.

new_sub_vec :: Acc_sub
new_sub_vec = iterate d27 (+1) 0

write_to_bram = bram (Write 0 new_sub_vec)

Hope that helps

So the compiler is telling you:

I expect a Signal in the clock domain dom of type (BlockRamOp addr0 Acc_sub)
But you are giving me:
a (BlockRamOp addr0 Acc_sub)

The latter is a singular value, while a Signal is essentially a stream of values changing over time (in clock cycles).

You could use the pure function that can turn any value a into a Signal dom a, but if you would hook that up to the block ram, you would be writing the same value to the same location at every clock cycle, which is not very useful.

You said that you want to write the result of your calculation to the blockram, so I expect you have some function with one input a - > b , or multiple inputs a -> b -> c etc that you want to apply to some input.

Let’s for now assume you want to simply apply a -> b to the input of your circuit and store the result. and let’s assume that for now your circuit receive a new value every clock cycle, then your input looks like: Signal dom a.

Here the Signal dom means that you get a new value for every clock cycle of the clock in domain dom. The a is the type of your input value.

If we want to apply our function and simply write the result to address 0, we could do that as follows:

myCircuit ::
  (HiddenClockResetEnable dom, Num b) =>
  -- | Initial content of the blockram, containing `n` values of type `b`
  Vec 16 b ->
  -- | Input signal with values of type `a`
  Signal dom a ->
  -- | Output signal with values of type `b`
  Signal dom b
myCircuit bramInit a = blockRamOut
 where
  -- Apply the function `f` to the values in our input signal,
  -- this creates a combinatorial circuit.
  functionResult = fmap f a

  -- The blockram expects `Signal dom (Maybe (addr, b))` as input,
  -- so we have to tell it that we want to write our `b` to some address
  blockRamIn = fmap (\b -> Just (0 :: Index 16, b)) functionResult

  -- Connect the signal that writes to the blockram to the blockram.
  -- Continuously read the value at address 0 from the blockram.
  blockRamOut = blockRam bramInit (pure 0) blockRamIn

Note that I hardcoded the size of the blockram to 16 elements for this example and the first arguments sets the initial contents of the blockram.

Be careful, the address comes first: Just (12, 3)

1 Like