GithubHelp home page GithubHelp logo

Comments (6)

andrewray avatar andrewray commented on July 23, 2024 1

Right. This is a limitation of the Hardcaml simulator. We only support the usual synchronous logic design pattern which implies only using one edge of the clock. It's actually probably not that hard for us to support both edges except

  • Simulation performance would effectively halve, without some real effort
  • I generally don't like playing games with clocks except when absolutely necessary - simple single clock, single edge semantics make the implementation process very much easier

I still expect we can build something reasonable for a register file though.

open! Import
open Signal
open Hardcaml_waveterm

module I = struct
  type 'a t =
    { clock : 'a
    ; write_enable : 'a
    ; write_data : 'a [@bits 8]
    ; write_address : 'a [@bits 5]
    ; read_address_0 : 'a [@bits 5]
    ; read_address_1 : 'a [@bits 5]
    }
  [@@deriving sexp_of, hardcaml]
end

module O = struct
  type 'a t =
    { read_data_0 : 'a [@bits 8]
    ; read_data_1 : 'a [@bits 8]
    }
  [@@deriving sexp_of, hardcaml]
end

let reg_file
      { I.clock; write_enable; write_data; write_address; read_address_0; read_address_1 }
  =
  let q =
    multiport_memory
      32
      ~write_ports:[| { write_clock = clock; write_address; write_data; write_enable } |]
      ~read_addresses:[| read_address_0; read_address_1 |]
  in
  { O.read_data_0 =
      mux2 (write_enable &: (write_address ==: read_address_0)) write_data q.(0)
  ; read_data_1 =
      mux2 (write_enable &: (write_address ==: read_address_1)) write_data q.(1)
  }
;;

module Sim = Cyclesim.With_interface (I) (O)

let%expect_test "" =
  let sim = Sim.create reg_file in
  let waves, sim = Waveform.create sim in
  let inputs = Cyclesim.inputs sim in
  let step write_address write_data read_address_0 read_address_1 =
    inputs.write_address := Bits.of_int ~width:5 write_address;
    inputs.write_data := Bits.of_int ~width:8 write_data;
    inputs.write_enable := Bits.vdd;
    inputs.read_address_0 := Bits.of_int ~width:5 read_address_0;
    inputs.read_address_1 := Bits.of_int ~width:5 read_address_1;
    Cyclesim.cycle sim;
    inputs.write_enable := Bits.vdd
  in
  step 10 100 0 0;
  step 11 101 10 11;
  step 12 102 12 11;
  Cyclesim.cycle sim;
  Waveform.print waves ~display_height:25;
  [%expect
    {|
    ┌Signals────────┐┌Waves──────────────────────────────────────────────┐
    │clock          ││┌───┐   ┌───┐   ┌───┐   ┌───┐   ┌───┐   ┌───┐   ┌──│
    │               ││    └───┘   └───┘   └───┘   └───┘   └───┘   └───┘  │
    │               ││────────┬───────┬───────────────                   │
    │read_address_0 ││ 00     │0A     │0C                                │
    │               ││────────┴───────┴───────────────                   │
    │               ││────────┬───────────────────────                   │
    │read_address_1 ││ 00     │0B                                        │
    │               ││────────┴───────────────────────                   │
    │               ││────────┬───────┬───────────────                   │
    │write_address  ││ 0A     │0B     │0C                                │
    │               ││────────┴───────┴───────────────                   │
    │               ││────────┬───────┬───────────────                   │
    │write_data     ││ 64     │65     │66                                │
    │               ││────────┴───────┴───────────────                   │
    │write_enable   ││────────────────────────────────                   │
    │               ││                                                   │
    │               ││────────┬───────┬───────────────                   │
    │read_data_0    ││ 00     │64     │66                                │
    │               ││────────┴───────┴───────────────                   │
    │               ││────────┬───────────────────────                   │
    │read_data_1    ││ 00     │65                                        │
    │               ││────────┴───────────────────────                   │
    │               ││                                                   │
    └───────────────┘└───────────────────────────────────────────────────┘ |}]
;;

from hardcaml.

andrewray avatar andrewray commented on July 23, 2024 1

I will update the simulator documentation - this is not said explicitly enough there.

from hardcaml.

andrewray avatar andrewray commented on July 23, 2024 1

Closing this out. There is some explicit new documentation for the simulator features and restrictions which will appear soon.

from hardcaml.

andrewray avatar andrewray commented on July 23, 2024

There are 2 types of memory in Hardcaml - asynchronously read, and synchronously read.

The former include the memory and (newer) multiport_memory primitives.

The synchronous ones are ram_wbr and functions in the Ram module. The notion of read-before-write and write-before-read only exists in the synchronous case.

By synchronous we mean it takes 1 clock cycle to read the data.

I dont think this issue is caused by the simulator, and I suspect the function you want exists as

Ram.create
      ~collision_mode:Write_before_read ~size:32 
       ~write_ports:[| one_write_port |] ~read_ports:[| two_read_ports |]

That said you say:

However, in our Hardcaml simulator tests, the written data is only read on the next cycle.

That has to be the case for a synchronous read ram (there is a register in the read path - without that you are not describing a RAM that can be implemented on the FPGA). If what you want is an asynchronous read ram, but with write before read you will need to construct that by detecting this case outside the memory ie

let q = async_memory_instantiation .... data_in in
let q = mux2 (memory_is_writing &: write_address ==:. read_address) data_in q in

from hardcaml.

askvortsov1 avatar askvortsov1 commented on July 23, 2024

Looking back over our implementation again, I forgot to mention that we made a tweak to ram_wbr:

let delay_address_to_falling clock address = 
  let spec = Reg_spec.create ~clock:clock () in
  let spec_on_falling = Reg_spec.override ~clock_edge:Edge.Falling spec in
  reg ~enable:vdd spec_on_falling address

let regfile rs rt clock write_enable write_address write_data =
  let write_port =
    { write_clock = clock; write_address; write_enable; write_data }
  in
  let number_of_regs = 32 in
  let delay_address = delay_address_to_falling clock in
  let regs =
    multiport_memory number_of_regs ~write_ports:[|write_port|] ~read_addresses:[| delay_address rs; delay_address rt |]
  in
  (Array.get regs 0, Array.get regs 1)

In particular, instead of using reg_empty, we configured our read delay register spec to run on falling clock cycles. As a result, the write should occur during the first half of a clock cycle, and the read during the second. The RTL generator represented this correctly:

...
always @(negedge _12) begin
        _31 <= _27;
    end
    always @(posedge _12) begin
        if (_6)
            _36[_10] <= _8;
    end
    assign _12 = clock;
    assign _32 = _23[4:0];
    always @(negedge _12) begin
        _35 <= _32;
    end
...

And Vivado simulation confirmed that it can read/write as expected in the same clock cycle.

from hardcaml.

askvortsov1 avatar askvortsov1 commented on July 23, 2024

Right. This is a limitation of the Hardcaml simulator. We only support the usual synchronous logic design pattern which implies only using one edge of the clock. It's actually probably not that hard for us to support both edges except

  • Simulation performance would effectively halve, without some real effort
  • I generally don't like playing games with clocks except when absolutely necessary - simple single clock, single edge semantics make the implementation process very much easier

Makes sense, thanks for the clarification! Would it make sense to add a comment discouraging falling edge use to edge.mli?

{ O.read_data_0 =
mux2 (write_enable &: (write_address ==: read_address_0)) write_data q.(0)
; read_data_1 =
mux2 (write_enable &: (write_address ==: read_address_1)) write_data q.(1)
}

We initially planned to explicitly forward writeback => decode as a workaround. This is much neater, thanks!

from hardcaml.

Related Issues (9)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.