Last active
June 15, 2024 16:45
-
-
Save thoughtpolice/8ec923e1b3fc4bb12c11aa23b4dc53b5 to your computer and use it in GitHub Desktop.
CLaSH for the iCE40-HX8K Breakout Board helper module
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- | | |
-- 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") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
`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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 () |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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