Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revisiting ClockSource annotations... #145

Closed
mgajda opened this issue May 21, 2016 · 8 comments
Closed

Revisiting ClockSource annotations... #145

mgajda opened this issue May 21, 2016 · 8 comments

Comments

@mgajda
Copy link

mgajda commented May 21, 2016

I have recently played with fully open source FPGA toolchain targeting Lattice iCE40 devices.
The devices are cheap, and compile-upload cycle takes about 5 seconds on my laptop, so there is a lot to gain there.

However, I noticed that the current ClockSource is incompatible with the two clock setups that I wanted to try on this device.

  1. First one would try a fully external clock - just connecting the named input pin to the system1000 source, and assert system1000_rst to be permanently 1. I currently see no way to configure it fully within CLaSH source, since there is no way to access system1000_rst (or force a bit of Verilog to the output.)
    Unless we find a pin that is permanently asserted, or add our own Verilog wrapper for topEntity.
  2. Second one would be to try the standard Lattice clock component, but configure it with given values of DIVR, DIVF, and DIVQ. That would even allow for dynamic clock scaling, if one is careful enough to avoid metastability when configuration is changed. Below is example code from Clifford, that configures Lattice clock. All it needs is an ability to give extra inputs to the ClockSource:
module clockgen(input clk, output clkout)

   // assign refclk = clk;
   SB_PLL40_CORE #(.FEEDBACK_PATH("SIMPLE"),
                   .PLLOUT_SELECT("GENCLK"),
                   .DIVR(4'b0000),
                   .DIVF(7'b0000100),
                   .DIVQ(3'b000),
                   .FILTER_RANGE(3'b001),
                   ) uut (
                          .RESETB(1'b1),
                          .BYPASS(1'b0),
                          .REFERENCECLK(clk),
                          .PLLOUTCORE(clkout),
                          );

endmodule

PS Code comes from Reddit

@christiaanb
Copy link
Member

Thanks for bringing this up. This will help along the discussion in clash-lang/clash-prelude#36, where the the use of explicit clock lines would help build PLL components like this.

@christiaanb
Copy link
Member

christiaanb commented May 24, 2016

@mgajda The following wiki page will be relevant for this ticket, I will notify you again once I've somewhat finalised its content.

edit: link added

@mgajda
Copy link
Author

mgajda commented May 28, 2016

@christiaanb Which Wiki page?

@christiaanb
Copy link
Member

@mgajda Ah, sorry, I forgot to link it. It's this wiki page: https://github.com/clash-lang/clash-prelude/wiki/Clock-and-reset-modelling

@thoughtpolice
Copy link
Contributor

Hi,

I also ran into this while trying to get CLaSH working on the iCE40-HX8K breakout board, as I wanted a fully synthesize-able result without having to hack the reset wire with a wrapper or anything like that. As an easy workaround for anyone else who stumbles here, it's relatively simple to get by with fixed clock frequencies for the PLL component of the board.

Let's say you want to tune the iCE40 PLL to be a 60mhz clock. You can ask icepll what the needed SB_PLL40_CORE parameters are:

$ icepll -i 12 -o 60

F_PLLIN:    12.000 MHz (given)
F_PLLOUT:   60.000 MHz (requested)
F_PLLOUT:   60.000 MHz (achieved)

FEEDBACK: SIMPLE
F_PFD:   12.000 MHz
F_VCO:  960.000 MHz

DIVR:  0 (4'b0000)
DIVF: 79 (7'b1001111)
DIVQ:  4 (3'b100)

FILTER_RANGE: 1 (3'b001)

Next, simple verilog wrapper:

module lattice_ice40_60mhz(input wire clk, output wire clkout, output wire locked);
  SB_PLL40_CORE #(
          .FEEDBACK_PATH("SIMPLE"),
          .PLLOUT_SELECT("GENCLK"),
          .DIVR(4'b0000),
          .DIVF(7'b1001111),
          .DIVQ(3'b100),
          .FILTER_RANGE(3'b001)
          ) uut (
             .LOCK(locked),
             .RESETB(1'b1),
             .BYPASS(1'b0),
             .REFERENCECLK(clk),
             .PLLOUTCORE(clkout)
             );
endmodule

Define a 60mhz ClockSource for the PLL:

module CLaSH.Lattice.ICE40
  ( pll60mhz
  ) where
import CLaSH.Prelude
import CLaSH.Signal.Explicit (systemClock)

pll60mhz :: String -> ClockSource
pll60mhz clkExpr = ClockSource
  { c_name  = "lattice_ice40_60mhz"
  , c_inp   = pure ("clk", clkExpr)
  , c_outp  = [("clkout", show systemClock)]
  , c_reset = Nothing
  , c_lock  = "locked"
  , c_sync  = False
  }

Note: I did not hook up the reset wire in my example as it's not needed, but considering the simplicity of defining this ClockSource and the verilog module, you can do it yourself pretty easily (also note that unlike the Altera PLL example in the tutorial, which I believe has active high reset, the iCE40 PLL is active low reset).

And that seemed to do the trick. Here's an example of a muxed blinker, with constant input signals, that will blink LED0 on the Breakout board at a 10Hz interval using the PLL at 60 megahertz (I wouldn't try the 50 or 100hz options since they'll blink too fast for your human vision to register it, but the 1Hz and 10Hz signals work fine). This is based on the Verilog tutorial example available at Nandland:

module LED1 where
import CLaSH.Prelude
import CLaSH.Lattice.ICE40

import Data.Bits
import Data.Bool
import Data.Bifunctor

type Enable = Signal Bit
type Switch = Signal Bit

--------------------------------------------------------------------------------
-- Speed utilities

data Speed
  = Speed_1HZ
  | Speed_10HZ
  | Speed_50HZ
  | Speed_100HZ

khz = (* 1000)
mhz = (* (1000*1000))

duty x = (x `div` 2) - 1

tick :: Speed -> Integer
tick Speed_1HZ   = duty (mhz 60 `div` 1)
tick Speed_10HZ  = duty (mhz 60 `div` 10)
tick Speed_50HZ  = duty (mhz 60 `div` 50)
tick Speed_100HZ = duty (mhz 60 `div` 100)

--------------------------------------------------------------------------------
-- Circuit definition

flipper :: Unsigned 32 -> Signal Bit
flipper lim = mealy fsm (low, 0) $ signal ()
  where
    fsm (s,i) () | i == lim  = ((complement s, 0), complement s)
                 | otherwise = ((s, i+1),          s) 

toggle :: Speed -> Signal Bit
toggle spd = case spd of
  Speed_1HZ   -> flipper 29999999 -- duty (mhz 60 `div` 1)
  Speed_10HZ  -> flipper 2999999  -- duty (mhz 60 `div` 10)
  Speed_50HZ  -> flipper 599999   -- duty (mhz 60 `div` 50)
  Speed_100HZ -> flipper 299999   -- duty (mhz 60 `div` 100)

blinker :: Enable -> Switch -> Switch -> Signal Bit
blinker e i1 i2 = speed .&. e where
  c0 = (i1 .==. 1) .&&. (i2 .==. 1)
  c1 = (i1 .==. 1) .&&. (i2 .==. 0)
  c2 = (i1 .==. 0) .&&. (i2 .==. 1)

  speed = mux c0 (toggle Speed_1HZ)
        $ mux c1 (toggle Speed_10HZ)
        $ mux c2 (toggle Speed_50HZ)
        $ toggle Speed_100HZ

--------------------------------------------------------------------------------
-- CLaSH exports: top level entity, and test bench

topEntity :: Signal Bit
topEntity = blinker (pure 1) (pure 1) (pure 0)

{-# ANN topEntity
  (defTop
    { t_name    = "led1"
    , t_inputs  = []
    , t_extraIn = [ ("clk", 1) ]
    , t_outputs = ["LED0"]
    , t_clocks  = [ pll60mhz "clk" ]
    }) #-}

And a simple build script:

#!/usr/bin/env bash
set -x

if [ "x$1" == "x--clean" ]; then
    rm -rf verilog *.blif *.log *.v obj *.asc *.bin
    exit 0
fi

rm -rf verilog *.blif *.log *.v obj *.asc *.bin
clash -hidir obj -odir obj --verilog LED1.hs
{
  FILES=`find verilog/ -type f -iname '*.v' | grep -v test`
  FILES+=" "
  FILES+=`find CLaSH/ -type f -iname '*.v'`
  for f in $FILES; do
    echo "read_verilog -Iverilog/LED1 $f"
  done
  echo "synth_ice40 -top led1 -blif led1.blif"
  echo "write_verilog -attr2comment led1.v"
} > synth.ys
yosys -v3 -l synth.log synth.ys; rm -rf synth.ys obj
arachne-pnr -d 8k -o led1.asc -p pins.pcf led1.blif
icepack led1.asc led1.bin
icetime -tmd hx8k led1.asc

You can upload that with iceprog -S led1.bin (for volatile SRAM, or iceprog led1.bin for non-volatile flash).

Boom! A fully open source High Level Synthesis toolchain + a fully open source RTL synthesis flow. Not bad.

You could also probably parameterize the ClockSource by DIVF, etc, and pass that onto the verilog module I guess? This would allow you to have one clock source definition which you can parameterize to multiple speeds. While that won't allow dynamic scaling or anything, it does mean you can mostly get by with a single module providing the lattice ClockSource, and a Verilog shim. You then 'only' would have to run icepll and fill in the parameters to get your desired speed.

If it were somehow possible to inline the verilog shim around SB_PLL40_CORE into the output generated by CLaSH (into some new file like LED1_Extra_Code.v or something), then this would be a pretty OK workaround for me at the moment, and could be distributed as a hackage package with no fuss.

@christiaanb
Copy link
Member

@thoughtpolice Thanks for this thorough description on how to get the iCE board working, I'll add a link to this to the FAQ.

I've started on making it possible to specify clock sources inside clash at https://github.com/clash-lang/clash-prelude/blob/explicit_clock_reset/src/CLaSH/TopEntity.hs So that is going to get us part of the way of packaging up components on hackage, now I just need to figure out how to associate a Haskell function with a Verilog/VHDL template. I can probably do that latter part with {#- ANN #-} pragma's.

@christiaanb
Copy link
Member

Primitive definitions can now included in a package to be uploaded to hackage by: 82cd318

@christiaanb
Copy link
Member

Now that clock and reset lines are explicit, clock source annotations are removed, and clock manipulating blocks like PLLs can be instantiated in the Haskell code and implemented using HDL templates.

christiaanb added a commit that referenced this issue Sep 6, 2018
Use a `reflection`-like API for implicit clocks and resets
leonschoorl pushed a commit that referenced this issue Jul 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants