TheJach.com

Jach's personal blog

(Largely containing a mind-dump to myselves: past, present, and future)
Current favorite quote: "Supposedly smart people are weirdly ignorant of Bayes' Rule." William B Vogt, 2010

re-frame: A software FPGA

Ever since ClojureScript was announced I've been keeping tabs on it here and there to see what sorts of interesting frameworks people come up with. I'm pretty picky when it comes to UI development (not because I'm particularly good at it, just because I'm picky in general) and something needs to do quite a bit to convince me it's worth using over plain HTML+CSS+JavaScript. (Not a fan of Backbone, but I did like Angular.)

Anyway, re-frame has been on my radar for a while as "the pinnacle alternative to Om". Om is really neat, but I've never tried it enough to feel like I wanted to try more of it. I've known about reagent for a while (that re-frame is built on) and instantly loved its usage of Hiccup to specify markup. Hiccup is so straightforward and simple I still haven't fully read its spec, though I should do so because it's kind of disturbing to me that if I have a "component" (function) that returns something like [:h1 fn-arg] then I can insert that as a child element to something else either with [my-fn fn-arg] or (my-fn fn-arg)...

Anyway again, last weekend I finally sat down and read all the through re-frame's manifesto in its README. And afterwards I thought: wow, this reminds me a lot of FPGA programming. I decided to 'redo' an FPGA assignment I had in college, using re-frame. This evening I cleaned up what I made and wrote this up.

I haven't done much FPGA programming, but the basic idea is you write up in Verilog (or VHDL or MyHDL) a program that specifies the hardware layout programmatically so you don't have to build it for real with a breadboard or ASIC. You can define wire connections and input/output signals and thus build whatever circuit you like, including CPUs that can then execute compiled C programs. You have real electrical parallelism by default (which you wrangle by typically flowing a clock signal through everything, and make things react to changes in the clock signal...). If you want to check out why FPGAs are awesome, read this and if you want to make your own toy one there's this.

The first real FPGA program I made for school was wiring 4 switches on our physical FPGA to act as a binary number representation, and to output that number onto the FPGA's seven-segment display, 0-16 (with 10-16 shown with hex letters). Once that was working, we were to see the benefits of Verilog's module system by making it work for another 4 switches and a 2nd seven-seg display. Here's the source in Verilog:


module lab3(SW, LEDR, HEX7, HEX6);

input [17:0] SW;
output [17:0] LEDR;
output [6:0] HEX7;
output [6:0] HEX6;

assign LEDR[17:0] = SW[17:0];

sev_seg(HEX7[6:0], SW[17:14]);
sev_seg(HEX6[6:0], SW[13:10]);

endmodule


/**
* Actual module for controlling the logic of some seven seg.
* @param disp - the 7-seg array, disp[0] is segment A and so on.
* @param numb - the 4-bit number determining the output.
*/
module sev_seg(output [6:0] disp, input [0:3] numb);

// logic derived from k-maps
assign
disp[0] = !numb[0] & !numb[1] & !numb[2] & numb[3] | !numb[0] & numb[1] & !numb[2] & !numb[3] | numb[0] & numb[1] & !numb[2] & numb[3] | numb[0] & !numb[1] & numb[2] & numb[3],

disp[1] = numb[1] & numb[2] & !numb[3] | numb[0] & numb[2] & numb[3] | numb[0] & numb[1] & !numb[3] | !numb[0] & numb[1] & !numb[2] & numb[3],

disp[2] = numb[0] & numb[1] & !numb[3] | numb[0] & numb[1] & numb[2] | !numb[0] & !numb[1] & numb[2] & !numb[3],

disp[3] = numb[1] & numb[2] & numb[3] | !numb[0] & !numb[1] & !numb[2] & numb[3] | !numb[0] & numb[1] & !numb[2] & !numb[3] | numb[0] & !numb[1] & numb[2] & !numb[3],

disp[4] = !numb[0] & numb[3] | !numb[0] & numb[1] & !numb[2] | !numb[1] & !numb[2] & numb[3],

disp[5] = !numb[0] & !numb[1] & numb[3] | !numb[0] & !numb[1] & numb[2] | !numb[0] & numb[2] & numb[3] | numb[0] & numb[1] & !numb[2] & numb[3],

disp[6] = !numb[0] & !numb[1] & !numb[2] | !numb[0] & numb[1] & numb[2] & numb[3] | numb[0] & numb[1] & !numb[2] & !numb[3];

endmodule



That's it. Really? What's SW, LEDR, etc. then? Where are those defined? Those are all defined in a 432 line CSV file that maps these custom friendlier names to physical PINs. I also have a 741 line .pin file that has similar things in it but also specifies power and ground and voltage levels... It says the pin file is an output file. Anyway through some combination of these with Altera's Quartus 2 IDE, I was able to get the above Verilog to compile and load onto the physical FPGA. (You can see what the FPGA looks like here, notice the row of switches and 8 seven seg displays in the bottom left.)

So SW maps to the switches, LEDR maps to some LEDs on the board, HEXes map to the sev segs. Note that in Verilog the program is literally just assigning the pins corresponding to the LEDs to be the same pins as the switches (so changing one changes the other, good luck changing an LED!) and then also assigns those pins to be equal to the seven segment pins after a logic mapping has happened. Verilog provides logic primitives for you, so you don't have to go off and make your own 'and', 'or', and 'not' components in verilog too, but you could. The logic mapping was derived from k-maps I did ages ago and I don't have the work handy, just Trust Me. Later on in this post I'll show my Clojure version which has a comment that clears things up a bit with the indexing and what true or false mean. (Namely, LEDs are active-low.)

A seven segment display is made up of, you guessed it, seven segments that map to a pin. The particular ordering is as seen in this ascii diagram:


0
+---------+
+ +
| |
5| |1
| |
+ 6 +
+---------+
+ +
| |
4| |2
| |
+ +
+---------+
3


Going back to re-frame, I spent a night making the following demo:








Pretty basic, but it taught me that re-frame might be worth delving into more, and that the ClojureScript way with figwheel and such of building web apps is awesome. The "wiring" logic ended up being about as simple as the Verilog logic, and not overly verbose either despite not being able to quite as easily say "assign elements 10-12 of this vector to elements 3-5 of that vector.", i.e. assign a[3:5] = b[10:12]. I mean it's not impossible to get a nice syntax, but it takes thinking. Maybe it's because I don't know Clojure that well despite loving it, but it took me several minutes to come up with this:


(defmacro slice-in [out out-start out-end
in in-start in-end]
`(apply assoc ~out
(flatten (map #(vector %1 (nth ~in %2))
(range ~out-start (inc ~out-end))
(range ~in-start (inc ~in-end))))))

(def a (vec (range 20))
(def b (vec (range 20 40))
(slice-in a 3 5 b 10 12)
; [0 1 2 30 31 32 6 7 8 9 10 11 12 13 14 15 16 17 18 19]


And it's lacking count-down support too, though that could be added fairly easily. So back to the demo... In re-frame, you basically have 4 files:


  1. db.cljs -- defines what stateful entities exist in your application and initial values for each, can of course have new entities added/removed but I found it simple to just define them all up front.

  2. handlers.cljs -- registered signal names so that when something dispatches that signal, we can respond and update state as needed with arbitrary transformations.

  3. subs.cljs -- registered subscriptions that provide views into the underlying state and allow users of that state to automatically react with the new state when the state changes. Can be views over single elements in the DB or over groups of elements.

  4. views.cljs -- where the layout structure and visible look of the page lives, components get defined with other components and each component can rely on subscriptions defined in subs as state variables, and each component can dispatch other signals to changes the state for instance if the user clicks something



Thinking about these from an FPGA perspective:


  • views.cljs -- the physical FPGA. Actual bits of hardware like LEDs are wired (subscribe) to various pins and if those pins change the LEDs will change. Some hardware is interactive like switches, switches subscribe to their own pin and can dispatch a switch event that should change the pin state but may also affect any other pins depending on the switch pin.

  • subs.cljs -- defines which pins in the DB will 'flow' their state to dependencies. By default in an FPGA, all pins flow themselves via electricity. CLJS gives the advantage of being able to have an arbitrary grouping of pins flow as a single input to some module.

  • handlers.cljs - initiates any possible flows by physically updating the underlying hardware, the little tiny elf whose job it is to make a high-voltage pin change to a low-voltage pin. Also acts like a "main" verilog module whose job it is to set up the various pins to flow into other pins with input/output relations.

  • db.cljs -- somewhat like the pins file, defines what names we're using for things that can change.



Am I really stretching things with this analogy to FPGAs? Maybe, maybe... Here's a dump of those 4 files, plus the corresponding calc.cljs file that determines the number-to-pin logic. Make up your own mind, feel free to discuss or question in the comments.


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; calc.cljs
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(ns sevseg.calc)

(defn calc
"Expects a 4-item vector of booleans
representing a big-endian binary number.
Returns a 7-item map with keys prefix + num
corresponding to which segments of the display
should be lit up. e.g. If prefix is :segA,
you will get {:segA0 false, :segA1 true, ...}
Calculated from k-maps long ago, however note that
the initial calculations assume active-low
(i.e. if false, light should be on)
so in the final reduce stage a not is wrapped around each value."
[prefix [a3 a2 a1 a0]]
(let [calcs
[(or
(and (not a3) (not a2) (not a1) a0)
(and (not a3) a2 (not a1) (not a0))
(and a3 a2 (not a1) a0)
(and a3 (not a2) a1 a0))
(or
(and a2 a1 (not a0))
(and a3 a1 a0)
(and a3 a2 (not a0))
(and (not a3) a2 (not a1) a0))
(or
(and a3 a2 (not a0))
(and a3 a2 a1)
(and (not a3) (not a2) a1 (not a0)))
(or
(and a2 a1 a0)
(and (not a3) (not a2) (not a1) a0)
(and (not a3) a2 (not a1) (not a0))
(and a3 (not a2) a1 (not a0)))
(or
(and (not a3) a0)
(and (not a3) a2 (not a1))
(and (not a2) (not a1) a0))
(or
(and (not a3) (not a2) a0)
(and (not a3) (not a2) a1)
(and (not a3) a1 a0)
(and a3 a2 (not a1) a0))
(or
(and (not a3) (not a2) (not a1))
(and (not a3) a2 a1 a0)
(and a3 a2 (not a1) (not a0)))]
out (reduce #(assoc %1
(keyword (str (name prefix) %2)) (not (nth calcs %2)))
{} (range 7))]
out))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; db.cljs
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(ns sevseg.db)

(def default-db
{
; switches
:s0 false
:s1 false
:s2 false
:s3 false

:s4 false
:s5 false
:s6 false
:s7 false

; seven seg display 1
; (initially display 0)
:segA0 true
:segA1 true
:segA2 true
:segA3 true
:segA4 true
:segA5 true
:segA6 false

; seven seg display 2
; (initially display 0)
:segB0 true
:segB1 true
:segB2 true
:segB3 true
:segB4 true
:segB5 true
:segB6 false

})

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; subs.cljs
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(ns sevseg.subs
(:require-macros [reagent.ratom :refer [reaction]])
(:require [re-frame.core :as re-frame]))

; Register :s0-s7 as individual switch reactions
(doseq [switch-num (range 8)]
(let [sid (keyword (str "s" switch-num))]
(re-frame/register-sub
sid
(fn [db]
(reaction (sid @db))))))

; Register segA0-6 and segB0-6 as individual reactions
(doseq [seg-prefix ["segA" "segB"]]
(doseq [seg-num (range 7)]
(let [seg-id (keyword (str seg-prefix seg-num))]
(re-frame/register-sub
seg-id
(fn [db]
(reaction (seg-id @db)))))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; handlers.cljs
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(ns sevseg.handlers
(:require [re-frame.core :as re-frame]
[sevseg.db :as db]
[sevseg.calc]))

(re-frame/register-handler
:initialize-db
(fn [_ _]
db/default-db))

(def switch-bank-1 (juxt :s0 :s1 :s2 :s3))
(def switch-bank-2 (juxt :s4 :s5 :s6 :s7))

(defn determine-bank [sid]
"Maps s0-s3 to bank 1, s4-s5 to bank 2."
(condp contains? (js/parseInt (last (str sid)))
(set (range 4)) switch-bank-1
(set (range 4 8)) switch-bank-2
(throw (str "Bad sid " sid))))

(defn determine-seg [bank]
"Maps bank 1 to segment A, bank 2 to segment B."
(condp = bank
switch-bank-1 :segA
switch-bank-2 :segB
(throw (str "Bad bank " bank))))

; toggles the switch pin value state on itself.
; also immediately propogate that change to
; everything else that depends on the state,
; namely the segments.
(re-frame/register-handler
:toggle-switch
(fn [db [_ sid]]
(let [db (update-in db [sid] not) ; change switch state itself
bank (determine-bank sid)
seg (determine-seg bank)
switch-bank-vals (bank db)]
(merge db ; update seven seg states
(sevseg.calc/calc seg switch-bank-vals)))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; views.cljs
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(ns sevseg.views
(:require [re-frame.core :as re-frame]
[re-com.core :as rc]
[reagent.core :as reagent]
[goog.string :as gstring]))


(defn switch [sid]
"The FPGA 'switch' component is just a checkbox with a db id of sid.
When user clicks the switch, dispatch the toggling."
(let [ticked? (re-frame/subscribe [sid])]
(fn []
[rc/checkbox
:model ticked?
:on-change #(re-frame/dispatch [:toggle-switch sid])])))

(def empty-box ; needed to sepaarate the lines on the sev-seg component.
[rc/box :width "50px" :child (gstring/unescapeEntities " ")])

(def box-style {:border "1px solid lightgray"
:border-radius "4px"
:padding "5px"})

(defn mk-line [visible?]
"Convenience function to create a red line that changes its visible
state depending on visible?"
[rc/line :size "3px" :color "red" :style {:visibility (if visible? "visible" "hidden")}])

(defn sev-seg [seg-prefix]
"Creates a seven segment display component wired to the segments
:[seg-prefix]0 through :[seg-prefix]6.
The correspondence of the pins to the output is as follows:
0
+---------+
+ +
| |
5| |1
| |
+ 6 +
+---------+
+ +
| |
4| |2
| |
+ +
+---------+
3
"
(let [segments (reduce #(conj %1 (re-frame/subscribe
[(keyword (str (name seg-prefix) %2))]))
[] (range 7))
[a0 a1 a2 a3 a4 a5 a6] segments]
(fn []
[rc/v-box
:gap "10px"
:style box-style
:children [[mk-line @a0]
[rc/h-box
:gap "10px"
:children [[mk-line @a5] empty-box [mk-line @a1]]]
(mk-line @a6)
[rc/h-box
:gap "10px"
:children [[mk-line @a4] empty-box [mk-line @a2]]]
[mk-line @a3]]])))

(defn switches [n nums]
"Component of the word 'Switch bank n'
containing nums individual switches."
[rc/v-box
:style box-style
:children [[rc/box :child (str "Switch bank " n)]
[rc/h-box
:gap "10px"
:children (for [n nums] [switch (keyword (str "s" n))])]]])

(defn main-panel []
"Constructs the page. Page consists of
a header, two switch banks, and two seven segment displays."
(fn []
[rc/v-box
:gap "5px"
:children [[rc/box :child [:h1 "Seven Segment Display Control"]] ; header
[rc/h-box ; switches container
:children [[switches 1 (range 4)]
[switches 2 (range 4 8)]]]
[rc/h-box ; sev-segs container
:gap "10px"
:children [[sev-seg :segA]
[sev-seg :segB]]]]
]))



Posted on 2015-10-17 by Jach

Tags: clojure, demo, FPGA, programming

Permalink: https://www.thejach.com/view/id/312

Trackback URL: https://www.thejach.com/view/2015/10/re-frame_a_software_fpga

Back to the top

Back to the first comment

Comment using the form below

(Only if you want to be notified of further responses, never displayed.)

Your Comment:

LaTeX allowed in comments, use $$\$\$...\$\$$$ to wrap inline and $$[math]...[/math]$$ to wrap blocks.