Compare commits

..

12 Commits

8 changed files with 222 additions and 82 deletions

32
README.md Normal file
View File

@@ -0,0 +1,32 @@
# CacheSim
CacheSim is an Elm-based website to simulate various cache hierarchy configurations. It supports
fully associative, direct mapped, and n-way set associative caches.
## Building and Hosting
CacheSim is entirely a front-end project. As long as the HTML, CSS, and generated JavaScript
are hosted somewhere, it will work.
CacheSim is built using Elm and Sass. As such, you will need the compilers for the two
languages. On Arch Linux, this means `sassc` for Sass, and `elm-platform-bin` (from AUR).
On other platforms, you can use npm:
```
npm install -g elm sass
```
Now, to build the project, you can use the following commands:
```
mkdir -p static/js
mkdir -p static/css
sassc static/scss/style.scss static/css/style.css
elm make src/Main.elm --output=static/js/elm.js --optimize
```
Replace `sassc` with `sass` in the third command if you installed Sass from npm.
Now, the `index.html` file and everything in the `static` folder can be safely copied
into any directory exposed by a web server. I use my university `public_html` folder.
Below are the _SFTP commands_ I use to upload a fresh copy of the project:
```
put index.html public_html/ECE472/index.html
put -r static public_html/ECE472/static
```

25
elm.json Normal file
View File

@@ -0,0 +1,25 @@
{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.0",
"dependencies": {
"direct": {
"elm/browser": "1.0.1",
"elm/core": "1.0.2",
"elm/html": "1.0.0",
"elm/parser": "1.1.0"
},
"indirect": {
"elm/json": "1.1.3",
"elm/time": "1.0.0",
"elm/url": "1.0.0",
"elm/virtual-dom": "1.0.2"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}

View File

@@ -2,15 +2,21 @@ module CacheSim.AccessView exposing (..)
import CacheSim.Cache exposing (..)
import CacheSim.Hierarchy exposing (..)
type AccessPosition = Down Int | Up Int | Done
type alias AccessView = (List (AccessEffect Cache), AccessPosition)
type AccessPosition = Down Int | Up Int | Preview | End
type alias AccessView =
{ initialState : CacheHierarchy
, blockAddr : BlockAddr
, accessEffects : List (AccessEffect Cache)
, position : AccessPosition
}
accessPositionForward : Int -> AccessPosition -> AccessPosition
accessPositionForward depth av =
case av of
Down n -> if n == depth - 1 then Up n else Down (n + 1)
Up n -> if n == 0 then Done else Up (n - 1)
Done -> Done
Up n -> if n == 0 then Preview else Up (n - 1)
Preview -> End
End -> End
accessPositionBack : Int -> AccessPosition -> AccessPosition
accessPositionBack depth av =
@@ -18,36 +24,43 @@ accessPositionBack depth av =
Down 0 -> Down 0
Down n -> Down (n-1)
Up n -> if n == (depth - 1) then Down n else Up (n + 1)
Done -> Up 0
Preview -> Up 0
End -> Preview
accessPositionDone : AccessPosition -> Bool
accessPositionDone av =
case av of
Done -> True
End -> True
_ -> False
accessViewForward : AccessView -> AccessView
accessViewForward (l, ap) = (l, accessPositionForward (List.length l) ap)
accessViewForward av = { av | position = accessPositionForward (List.length av.accessEffects) av.position }
accessViewBack : AccessView -> AccessView
accessViewBack (l, ap) = (l, accessPositionBack (List.length l) ap)
accessViewBack av = { av | position = accessPositionBack (List.length av.accessEffects) av.position }
accessViewDone : AccessView -> Bool
accessViewDone (_, ap) = accessPositionDone ap
accessViewDone av = accessPositionDone av.position
effectiveCacheHierarchy : CacheHierarchy -> AccessView -> CacheHierarchy
effectiveCacheHierarchy c (l, ap) =
finalCacheHierarchy : AccessView -> CacheHierarchy
finalCacheHierarchy av =
List.map .output av.accessEffects ++ List.drop (List.length av.accessEffects) av.initialState
effectiveCacheHierarchy : AccessView -> CacheHierarchy
effectiveCacheHierarchy av =
let
finalContents = List.map .output l
unaccessed = List.drop (List.length l) c
finalContents = List.map .output av.accessEffects
unaccessed = List.drop (List.length av.accessEffects) av.initialState
notDone =
case ap of
Done -> []
Down _ -> List.take (List.length l) c
Up n -> List.take n c
case av.position of
Preview -> []
End -> []
Down _ -> List.take (List.length av.accessEffects) av.initialState
Up n -> List.take n av.initialState
done =
case ap of
Done -> finalContents
case av.position of
Preview -> finalContents
End -> finalContents
Down _ -> []
Up n -> List.drop n finalContents
in

View File

@@ -6,7 +6,7 @@ import CacheSim.AccessView exposing (..)
type alias Model =
{ rawHierarchy : RawCacheModelHierarchy
, hierarchy : Maybe CacheHierarchy
, accessView : Maybe AccessView
, accessView : Maybe (List AccessView)
, accessInput : String
}
type alias Flags = ()
@@ -15,7 +15,8 @@ type Msg
| CreateRawModel
| DeleteRawModel Int
| UseHierarchy (Maybe CacheModelHierarchy)
| Access Int
| Access (List Int)
| ChangeAccessInput String
| AccessViewCancel
| AccessViewForward
| AccessViewBack

View File

@@ -38,11 +38,32 @@ updateUseHierarchy cmh m =
in
(newModel, cmd)
updateAccess : Int -> Model -> (Model, Cmd Msg)
updateAccess i m =
updateAccess : List Int -> Model -> (Model, Cmd Msg)
updateAccess li m =
let
accessResult = Maybe.andThen (Result.toMaybe << accessCacheHierarchy i) m.hierarchy
newModel = { m | accessView = Maybe.map (\ar -> (ar, Down 0)) accessResult }
process c xs =
case xs of
[] -> Ok []
(i::t) ->
case accessCacheHierarchy (i // 4) c of
Ok av ->
let
newView = { blockAddr = i, accessEffects = av, position = Down 0, initialState = c }
in
Result.map ((::) newView)
<| process (finalCacheHierarchy newView) t
Err s -> Err s
accessResult = Maybe.andThen (\h -> Result.toMaybe <| process h li) m.hierarchy
newModel = { m | accessView = accessResult }
cmd = Cmd.none
in
(newModel, cmd)
updateAccessViewCancel : Model -> (Model, Cmd Msg)
updateAccessViewCancel m =
let
newModel = { m | accessView = Nothing }
cmd = Cmd.none
in
(newModel, cmd)
@@ -50,13 +71,16 @@ updateAccess i m =
updateAccessViewForward : Model -> (Model, Cmd Msg)
updateAccessViewForward m =
let
afterStep = Maybe.map accessViewForward m.accessView
replaceHierarchy avs h = List.map .output avs ++ List.drop (List.length avs) h
(newHierarchy, newAccessView) =
afterStep = Maybe.map (intMapUpdate 0 accessViewForward) m.accessView
newAccessView =
case afterStep of
Just (avs, Done) -> (Maybe.map (replaceHierarchy avs) m.hierarchy, Nothing)
as_ -> (m.hierarchy, as_)
newModel = { m | accessView = newAccessView, hierarchy = newHierarchy }
Just (result::xs) ->
case (result.position, xs) of
(End, []) -> Nothing
(End, nxs) -> Just nxs
_ -> Just (result::xs)
as_ -> as_
newModel = { m | accessView = newAccessView }
cmd = Cmd.none
in
(newModel, cmd)
@@ -64,7 +88,7 @@ updateAccessViewForward m =
updateAccessViewBack : Model -> (Model, Cmd Msg)
updateAccessViewBack m =
let
afterStep = Maybe.map accessViewBack m.accessView
afterStep = Maybe.map (intMapUpdate 0 accessViewBack) m.accessView
newModel = { m | accessView = afterStep }
cmd = Cmd.none
in

View File

@@ -4,44 +4,48 @@ import CacheSim.Model exposing (..)
import CacheSim.Cache exposing (..)
import CacheSim.AccessView exposing (..)
import CacheSim.Hierarchy exposing (..)
import Parser exposing ((|.))
import Html exposing (Html, Attribute, input, text, div, label, span, h2, h3, table, tr, td, th, p, h1)
import Html.Attributes exposing (type_, class, value, for, classList, disabled, colspan, hidden)
import Html.Events exposing (onInput, onClick)
-- Button components, powered by Bootstrap
basicButton : List (Attribute Msg) -> String -> Html Msg
basicButton attrs s = input ([ type_ "button", value s, class "btn", class "btn-info" ] ++ attrs) []
button : List (Attribute Msg) -> String -> Html Msg
button attrs s = input ([ type_ "button", value s, class "btn"] ++ attrs) []
disabledButton : String -> Html Msg
disabledButton = basicButton [ disabled True ]
basicButton : Msg -> String -> Html Msg
basicButton msg s = button [ onClick msg ] s
advancedButton : List (Attribute Msg) -> String -> Msg -> Html Msg
advancedButton attrs s m = basicButton (attrs ++ [ onClick m ]) s
button : String -> Msg -> Html Msg
button s m = basicButton [ onClick m ] s
disabledButton : List (Attribute Msg) -> String -> Html Msg
disabledButton attrs = button (attrs ++ [ disabled True ])
dangerButton : String -> Msg -> Html Msg
dangerButton = advancedButton [ class "btn-danger" ]
dangerButton s m = button [ onClick m, class "btn-danger" ] s
infoButton : String -> Msg -> Html Msg
infoButton s m = button [ onClick m, class "btn-info" ] s
primaryButton : String -> Msg -> Html Msg
primaryButton = advancedButton [ class "btn-primary" ]
primaryButton s m = button [ onClick m, class "btn-primary" ] s
secondaryButton : String -> Msg -> Html Msg
secondaryButton = advancedButton [ class "btn-secondary" ]
secondaryButton s m = button [ onClick m, class "btn-secondary" ] s
maybeButton : Maybe a -> String -> (a -> Msg) -> Html Msg
maybeButton m s f =
maybeButton : Maybe a -> List (Attribute Msg) -> String -> (a -> Msg) -> Html Msg
maybeButton m attrs s f =
case m of
Just v -> button s (f v)
_ -> disabledButton s
Just v -> button (attrs ++ [ onClick (f v) ]) s
_ -> disabledButton attrs s
resultButton : Result e a -> String -> (a -> Msg) -> Html Msg
resultButton : Result e a -> List (Attribute Msg) -> String -> (a -> Msg) -> Html Msg
resultButton = maybeButton << Result.toMaybe
-- Button wrapper (button group)
buttonWrapper : List (Html Msg) -> Html Msg
buttonWrapper = div [ classList [("btn-group", True), ("mb-3", True) ] ]
buttonWrapper = div [ classList [("btn-group", True), ("mb-3", True), ("mr-3", True) ] ]
buttonToolbar : List (List (Html Msg)) -> Html Msg
buttonToolbar ll = div [ class "btn-toolbar" ] <| List.map buttonWrapper ll
-- Input with a label
labeledInput : String -> String -> (String -> Msg) -> Html Msg
@@ -77,7 +81,7 @@ viewRawCacheModel level rcm =
deleteButton = dangerButton "Delete" (DeleteRawModel level)
params = div []
[ labeledInput "Block size" rcm.blockSize (wrapUpdate updateBlockSize)
[ labeledInput "Block size (words)" rcm.blockSize (wrapUpdate updateBlockSize)
, labeledInput "Set count" rcm.setCount (wrapUpdate updateSetCount)
, labeledInput "Set size" rcm.setSize (wrapUpdate updateSetSize)
]
@@ -95,13 +99,17 @@ viewRawCacheModelHierarchy rcmh =
<| List.indexedMap viewRawCacheModel rcmh
translationResult = Result.andThen validateCacheModelHierarchy
<| translateRawCacheModelHierarchy rcmh
errorHtml =
checkedResult =
case translationResult of
Ok h -> if h == [] then Err "Please specify at least one cache level." else Ok h
Err e -> Err e
errorHtml =
case checkedResult of
Ok _ -> viewError True ""
Err e -> viewError False e
newButton = button "Add level" CreateRawModel
useButton = resultButton translationResult "Use hierarchy" (UseHierarchy << Just)
newButton = infoButton "Add level" CreateRawModel
useButton = resultButton checkedResult [ class "btn-info" ] "Start simulation" (UseHierarchy << Just)
in
div []
[ h2 [] [ text "Cache hierarchy" ]
@@ -129,7 +137,10 @@ viewCache level (cm, cs) =
slotHtml s =
case s of
Empty -> td [] [ text "" ]
Used l a -> td [] [ text <| String.fromInt a ]
Used l a -> td [] [ text <|
(String.fromInt (a * cm.blockSize * 4)) ++
"-" ++
(String.fromInt ((a + 1) * cm.blockSize * 4 - 1)) ]
in
List.map slotHtml set
cacheRow = List.concat <| List.map setRow cs
@@ -158,23 +169,21 @@ viewCacheHierarchy ch =
viewAccessView : Model -> AccessView -> Html Msg
viewAccessView m av =
let
currentCache = Maybe.withDefault [] m.hierarchy
in
div []
[ h2 [] [ text "Access Simulation" ]
, buttonWrapper
[ primaryButton "Back" AccessViewBack
, primaryButton "Forward" AccessViewForward
]
, h3 [] [ text "Access event log" ]
, viewAccessLog av
, h3 [] [ text "Current cache state" ]
, viewCacheHierarchy <| effectiveCacheHierarchy currentCache av
div []
[ h2 [] [ text "Access Simulation" ]
, p [] [ text ("Simulating access of address " ++ String.fromInt av.blockAddr) ]
, buttonToolbar
[ [ infoButton "Back" AccessViewBack, infoButton "Forward" AccessViewForward ]
, [ dangerButton "Stop" AccessViewCancel ]
]
, h3 [] [ text "Access event log" ]
, viewAccessLog av
, h3 [] [ text "Current cache state" ]
, viewCacheHierarchy <| effectiveCacheHierarchy av
]
viewAccessLog : AccessView -> Html Msg
viewAccessLog (aes, ap) =
viewAccessLog av =
let
resultSpan r =
span [ classList [ ("badge", True), ("badge-success", r == Hit), ("badge-danger", r == Miss) ] ]
@@ -184,27 +193,50 @@ viewAccessLog (aes, ap) =
, resultSpan ae.result
]
upEvent n ae = div [ class "event" ]
[ text <| "Updated L" ++ String.fromInt (List.length aes - n)
[ text <| "Updated L" ++ String.fromInt (List.length av.accessEffects - n)
]
events =
case ap of
Done -> []
Down n -> List.indexedMap downEvent <| List.take (n + 1) aes
Up n -> List.indexedMap downEvent aes ++
(List.indexedMap upEvent <| List.drop n aes)
case av.position of
Preview -> List.indexedMap downEvent av.accessEffects ++
(List.indexedMap upEvent av.accessEffects) ++
[ div [ class "event" ] [ text "Access complete. Viewing final cache state." ] ]
End -> []
Down n -> List.indexedMap downEvent <| List.take (n + 1) av.accessEffects
Up n -> List.indexedMap downEvent av.accessEffects ++
(List.indexedMap upEvent <| List.drop n av.accessEffects)
in
div [] events
viewAccessInput : Model -> Html Msg
viewAccessInput m =
let
accessButton = maybeButton (String.toInt m.accessInput) "Access address" Access
editHierarchyButton = button "Edit hierarchy" (UseHierarchy Nothing)
parser =
Parser.sequence
{ start = ""
, end = ""
, separator = ","
, spaces = Parser.spaces
, item = Parser.int
, trailing = Parser.Optional
}
parseErrorToString _ = "Unable to parse input. Please enter a sequence of numbers separated by commas."
parseResult = Parser.run (parser |. Parser.end) m.accessInput
checkedResult =
case parseResult of
Ok is -> if is == [] then Err "Please enter at least one number." else Ok is
Err e -> Err <| parseErrorToString e
accessButton = resultButton checkedResult [ class "btn-info" ] "Access address" Access
errorHtml =
case checkedResult of
Ok _ -> viewError True ""
Err e -> viewError False e
editHierarchyButton = button [ onClick (UseHierarchy Nothing), class "btn-dark" ] "Edit hierarchy"
in
div []
[ h2 [] [ text "Run access simulation" ]
, labeledInput "Access address" m.accessInput ChangeAccessInput
, labeledInput "Access byte address" m.accessInput ChangeAccessInput
, buttonWrapper [ accessButton, editHierarchyButton ]
, errorHtml
]
viewDescription : Html Msg
@@ -218,7 +250,7 @@ viewDescription =
, p []
[ text <| "To use the simulator, first create a fitting cache configuration by using the \"Add level\" button,"
++ " as well as the settings provided by each individual cache level. When the cache is correctly specified, and"
++ " no warnings appear, click \"Use hierarchy\" to load the specified hierarchy and begin simulating. To simulate,"
++ " no warnings appear, click \"Start simulation\" to load the specified hierarchy and begin simulating. To simulate,"
++ " type a block address into the \"Access address\" field, and click \"Access\". This will bring forward the simulation"
++ " view, which will allow you to step through the steps of accessing a cache."
]
@@ -241,7 +273,15 @@ viewBase m =
Maybe.withDefault []
<| Maybe.map (List.singleton << viewCacheHierarchy) <| m.hierarchy
Just _ -> []
accessView = Maybe.withDefault [] <| Maybe.map (List.singleton << viewAccessView m) <| m.accessView
accessView = Maybe.withDefault [] <| Maybe.map (List.singleton << viewAccessView m) <| Maybe.andThen (List.head) <| m.accessView
remainingAccessView =
case Maybe.map (\l -> List.length l - 1) m.accessView of
Just n -> if n <= 0 then [] else
[ div [ class "alert", class "alert-info" ] [ text <|
"Simulating more than one access. " ++ (String.fromInt n) ++
" addresses in queue." ]
]
_ -> []
in
div [ class "container" ]
<| [ viewDescription] ++ rawView ++ accessInputView ++ accessView ++ cacheView
<| [ viewDescription] ++ rawView ++ accessInputView ++ remainingAccessView ++ accessView ++ cacheView

View File

@@ -35,6 +35,7 @@ update msg m =
UseHierarchy cmh -> updateUseHierarchy cmh m
Access i -> updateAccess i m
ChangeAccessInput s -> ({ m | accessInput = s }, Cmd.none)
AccessViewCancel -> updateAccessViewCancel m
AccessViewForward -> updateAccessViewForward m
AccessViewBack -> updateAccessViewBack m

View File

@@ -1,3 +1,7 @@
.btn-group {
width: min-content;
}
.btn:focus {
box-shadow: none;
}