Skip to content

Instantly share code, notes, and snippets.

@thoughtpolice
Last active June 15, 2024 16:45
Show Gist options
  • Save thoughtpolice/8ec923e1b3fc4bb12c11aa23b4dc53b5 to your computer and use it in GitHub Desktop.
Save thoughtpolice/8ec923e1b3fc4bb12c11aa23b4dc53b5 to your computer and use it in GitHub Desktop.
CLaSH for the iCE40-HX8K Breakout Board helper module
#!/usr/bin/env bash
set -xe
if [ "x$1" == "x--clean" ]; then
rm -rf verilog *.blif *.log *.v obj *.asc *.bin *.exe
exit 0
fi
# clean
rm -rf verilog *.blif *.log *.v obj *.asc *.bin *.exe
# generate verilog, and simulation executable
clash -Wall -hidir obj -odir obj --verilog LED1.hs
clash -Wall -hidir obj -odir obj -main-is LED1.main LED1.hs -o LED1.exe
# finish generating verilog: we need the iCE40 PLL module
clash -e 'printPllVerilog' CLaSH/Lattice/ICE40.hs > verilog/LED1/ICE40_PLL.v
# generate yosys synthesis script
{
FILES=`find verilog/ -type f -iname '*.v' | grep -v test`
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 -c 60 -tmd hx8k led1.asc
-- |
-- Module : CLaSH.Lattice.ICE40
-- Copyright : (c) Austin Seipp 2016
-- License : MIT
--
-- Maintainer : aseipp@pobox.com
-- Stability : experimental
-- Portability : portable
--
-- This module provides Phase Lock Loop (PLL) @'ClockSource'@s for the Lattice
-- iCE40 FPGA series, including the iCE40-HX8K (\"Breakout Board\") and
-- iCE40-HX1K (\"IceStick\"). This allows you to tie your @topEntity@
-- declarations to the on-board PLLs, and set them to a specific operating
-- frequency, beyond the on-board clock.
--
-- Furthermore, the Lattice iCE40 series is unique in that it supports a
-- completely open source synthesis flow, consisting of Yosys, Arachne-pnr, and
-- IceStorm for verilog synthesis, place-and-route, and board upload/design
-- timing analysis. Along with the low cost of iCE40 boards, this makes Lattice
-- FPGAs uniquely attractive and low-overhead educational purposes.
--
module CLaSH.Lattice.ICE40
( ICE40Reset -- :: *
, ICE40PLL(..) -- :: *
, ice40pll -- :: ICE40PLL -> String -> ClockSource
-- * Utilities for generating necessary accompanying verilog code
, printPllVerilog
) where
import CLaSH.Prelude
import CLaSH.Signal.Explicit (systemClock)
import Data.FileEmbed (embedStringFile)
-- | Reset wire for the iCE40 PLL. If you do not require a reset wire to be
-- hooked up to the PLL, you can simply set this to either @1'b1@ for
-- (System)Verilog or @\'1\'@ for VHDL.
--
-- Note: while other systems like Altera PLLs use active /high/ reset for these
-- signals, the iCE40 PLL uses active /low/ reset. Thus, to reset, you would
-- change the reset wire from high (1) to low (0), not low to high. This is also
-- why you should default to 1 if you don't need the reset wire, as specified
-- above.
type ICE40Reset = String
-- | iCE40 PLL speed setting. This determines the frequency of the output clock
-- generated by the PLL, after it has been wired to the on-board 12Mhz
-- oscillator.
--
-- Note that currently, the set of output clock frequencies for the PLL is fixed
-- and non-extensible by the user in any form, due to some limitations in the
-- current version of CLaSH. In the future it will hopefully be possible, in
-- combination with the @icepll@ tool (among other things), to allow
-- user-configurable frequency.
--
-- The minimum clock frequency supported by the PLL is 16mhz.
data ICE40PLL
= PLL_16MHZ
-- ^ 16mhz output (16,000,000 cycles per second). This is the /lower/ bound of
-- the PLL: it cannot reach a frequency lower than this given the input clock
-- on the iCE40.
| PLL_50MHZ
-- ^ 50mhz output (50,000,000 cycles per second).
| PLL_60MHZ
-- ^ 60mhz output (60,000,000 cycles per second).
| PLL_100MHZ
-- ^ 100mhz output (100,000,000 cycles per second).
| PLL_120MHZ
-- ^ 120mhz output (120,000,000 cycles per second).
| PLL_200MHZ
-- ^ 200mhz output (200,000,000 cycles per second).
| PLL_270MHZ
-- ^ 270mhz output (270,000,000 cycles per second). This is the /upper/ bound
-- of the PLL. Note that the iCE40 itself claims that the PLL may support
-- up-to 275 megahertz; this 5mhz gap is merely a 'red zone' providing a
-- small, comfortable gap before hitting max speed.
-- | A configurable PLL-based @'ClockSource'@ for designs intending to be
-- synthesized onto Lattice iCE40 boards. Intended to be used along with
-- @'TopEntity'@'s @t_clocks@ field.
ice40pll :: ICE40Reset -- ^ Optional reset signal as input to the PLL.
-> ICE40PLL -- ^ The speed of the resulting PLL clock.
-> String -- ^ The name of the input wire this clock is tied to.
-- Traditionally, this is the name of some wire
-- that is mapped to the onboard oscillator.
-> ClockSource -- ^ Output clock source.
ice40pll rst speed clk = common (modname speed) where
modname PLL_16MHZ = "ice40_pll16mhz"
modname PLL_50MHZ = "ice40_pll50mhz"
modname PLL_60MHZ = "ice40_pll60mhz"
modname PLL_100MHZ = "ice40_pll100mhz"
modname PLL_120MHZ = "ice40_pll120mhz"
modname PLL_200MHZ = "ice40_pll200mhz"
modname PLL_270MHZ = "ice40_pll270mhz"
common comp = ClockSource
{ c_name = comp
, c_inp = pure ("clk", clk)
, c_outp = [("clkout", show systemClock)]
, c_reset = Just ("reset", rst)
, c_lock = "locked"
, c_sync = False
}
-- | Print out extra Verilog code needed to drive the iCE40 PLL. This is
-- necessary due to some limitations in the current version of CLaSH; you should
-- run this function to generate the necessary `.v` file to include in your
-- project synthesis.
printPllVerilog :: IO ()
printPllVerilog = putStr extraVerilog
extraVerilog :: String
extraVerilog = $(embedStringFile "CLaSH/Lattice/ice40.v")
`define ICE40_PLL_TEMPLATE(name, divr, divf, divq, frange) \
module name( input wire clk \
, input wire reset \
, output wire clkout \
, output wire locked \
); \
SB_PLL40_CORE #( \
.FEEDBACK_PATH("SIMPLE"), \
.PLLOUT_SELECT("GENCLK"), \
.DIVR(divr), \
.DIVF(divf), \
.DIVQ(divq), \
.FILTER_RANGE(frange) \
) uut ( \
.LOCK(locked), \
.RESETB(reset), \
.BYPASS(1'b0), \
.REFERENCECLK(clk), \
.PLLOUTCORE(clkout) \
); \
endmodule
// you can generate a number of macro instantiations for different clock
// frequencies, via icepll in icestorm, with this godawful one-liner:
// export FREQS="16 50 60 100 120 200 270"
// for x in $FREQS; do echo -n \`ICE40_PLL_TEMPLATE\(; echo -n ice40_pll$x; echo -n "mhz, "; icepll -o $x | egrep '(DIV[RFQ]|FILTER_RANGE)' | awk '{print $3}' | cut -c 2- | rev | cut -c 2- | rev | tr '\n' ',' | rev | cut -c 2- | rev | tr '\n' ')'; echo; done
`ICE40_PLL_TEMPLATE(ice40_pll16mhz, 4'b0000,7'b1010100,3'b110,3'b001)
`ICE40_PLL_TEMPLATE(ice40_pll50mhz, 4'b0000,7'b1000010,3'b100,3'b001)
`ICE40_PLL_TEMPLATE(ice40_pll60mhz, 4'b0000,7'b1001111,3'b100,3'b001)
`ICE40_PLL_TEMPLATE(ice40_pll100mhz, 4'b0000,7'b1000010,3'b011,3'b001)
`ICE40_PLL_TEMPLATE(ice40_pll120mhz, 4'b0000,7'b1001111,3'b011,3'b001)
`ICE40_PLL_TEMPLATE(ice40_pll200mhz, 4'b0000,7'b1000010,3'b010,3'b001)
`ICE40_PLL_TEMPLATE(ice40_pll270mhz, 4'b0000,7'b0101100,3'b001,3'b001)
module LED1
( -- * Circuit definition
blinker -- :: ... -> Signal Bit
, topEntity -- :: Signal Bit
-- * Simulation driver
, main -- :: IO ()
) where
import CLaSH.Prelude
import CLaSH.Lattice.ICE40
type Enable = Signal Bit
type Switch = Signal Bit
--------------------------------------------------------------------------------
-- Speed utilities
-- | Speed of the LED blinker, specified in hertz (i.e. the number of times per
-- second we want to blink the LED).
data Speed
= Speed_1HZ -- ^ Blink once per second.
| Speed_10HZ -- ^ Blink 10 times a second.
| Speed_50HZ -- ^ Blink 50 times a second.
| Speed_100HZ -- ^ Blink 100 times a second.
khz, mhz :: Integer -> Integer
khz = (* 1000)
mhz = (* (1000*1000))
-- | Calculate the duty of a particular LED cycle; it will spend half of each
-- specified interval on, and the other half off.
--
-- We subtract one, as the register that will contain our counter to keep track
-- of the ticks starts at 0.
duty :: Integer -> Integer
duty x = (x `div` 2) - 1
-- | Utility for calculating the necessary clock tick interval at which the LED
-- signal will flip. Example: If we have a 60 megahertz clock, and want 1 blink
-- per second, then we must spend half of those 60 megahertz with the LED off,
-- and the other half on.
--
-- This function assumes the input clock is running at exactly 60 megahertz.
--
-- NOTE: This function should /NOT/ be used in synthesis; use it up-front to
-- calculate the required tick, and use that in your design. Otherwise, the
-- compiler may output wires containing the entire formula to calculate the tick
-- offset (as opposed to a simple constant, as a result of constant propogation)
-- which means you may miss timing requirements due to propogation delay.
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
-- | A \"flipper\" register. Given a 32-bit counter limit, this register will
-- increment itself every cycle, outputting either a constant 0 or a 1. When the
-- register finally hits the counter limit, it flips the output bit (from 0 to
-- 1, or 1 to 0) and resets the count back at zero. Thus, this flipper pulses
-- the output signal at a given rate: a single bit, flipped once per N cycles.
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 signal; given a speed, return an output signal that switches between
-- high and low states at the specified frequency.
toggle :: Speed -> Signal Bit
toggle spd = case spd of
Speed_1HZ -> flipper 29999999 -- tick Speed_1HZ
Speed_10HZ -> flipper 2999999 -- tick Speed_10HZ
Speed_50HZ -> flipper 599999 -- tick Speed_50HZ
Speed_100HZ -> flipper 299999 -- tick Speed_100HZ
-- | Blinker circuit. Given an enablement bit which controls whether to blink at
-- all, and two bits which together control the rate at which to blink the LED,
-- return an output signal, that will pulse the LED at the required rate. Note
-- that, if we request the LED to blink e.g. once-per-second, then it spends
-- half of that time in the off state, and the other half of that time in the on
-- state. Thus, @N@-blinks-per-second is roughly @2N@-state-changes-per-second
-- for the LED.
--
-- If the enablement signal is high (i.e. is set to 1), then the LED is enabled
-- at a particular frequency (based on the input switches). Otherwise, the LED
-- is always off.
--
-- The input switches are two single-bit signals, controlling the frequency of
-- the blink with a muxer. The LED may blink at one of four specified rates,
-- based on the input switches:
--
-- If the input signal pair is @(1, 1)@ coming off the wire, then the LED will
-- blink at a rate of 1hz (1 blink-per-second).
--
-- If the input signal pair is @(1, 0)@ coming off the wire, then the LED will
-- blink at a rate of 10hz (10 blinks-per-second).
--
-- If the input signal pair is @(0, 1)@ coming off the wire, then the LED will
-- blink at a rate of 50hz (50 blinks-per-second).
--
-- If the input signal pair is @(0, 0)@ coming off the wire, then the LED will
-- blink at a rate of 100hz (100 blinks-per-second).
--
blinker :: Enable -- ^ Enablement bit. 0 means disabled, 1 is enabled.
-> Switch -- ^ Input switch 1.
-> Switch -- ^ Input switch 2.
-> Signal Bit -- ^ Output LED signal.
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
-- | The output circuit, to be synthesized into (System)Verilog or VHDL, and
-- onto a chip.
--
-- Currently, this signal always pulses the LED at a rate of 1mhz, and always
-- has the enablement bit set. Thus, it simply takes an input clock, and an
-- output LED, and pulses the LED at a constant rate.
--
-- The clock is tied to an on-board iCE40 PLL, providing a stable 60mhz clock to
-- the circuit. The reset wire to the PLL is not used.
topEntity :: Signal Bit
topEntity = blinker (pure 1) (pure 1) (pure 1)
{-# ANN topEntity
(defTop
{ t_name = "led1"
, t_inputs = []
, t_extraIn = [ ("clk", 1) ]
, t_outputs = ["LED0"]
, t_clocks = [ ice40pll "1'b1" PLL_60MHZ "clk" ]
}) #-}
--------------------------------------------------------------------------------
-- Simulation driver: run the resulting /Haskell/ program.
main :: IO ()
main = return ()
# 12mhz on-board oscillator
set_io clk J3
# On-board LEDs
set_io LED0 B5
#set_io LED1 B4
#set_io LED2 A2
#set_io LED3 A1
#set_io LED4 C5
#set_io LED5 C4
#set_io LED6 B3
#set_io LED7 C3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment