packages feed

libriscv (empty) → 0.1.0.0

raw patch · 33 files changed

+2608/−0 lines, 33 filesdep +arraydep +basedep +bv

Dependencies added: array, base, bv, bytestring, containers, exceptions, extra, file-embed, filepath, freer-simple, libriscv, melf, optparse-applicative, parameterized-utils, tasty, tasty-hunit, template-haskell, transformers, yaml

Files

+ LICENSE.txt view
@@ -0,0 +1,19 @@+Copyright (c) 2022-2023 Group of Computer Architecture, University of Bremen <riscv@systemc-verification.org>++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ app/Main.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeApplications #-}++module Main where++import Control.Monad (when)+import Control.Monad.Freer+import Data.BitVector+import Data.IORef (newIORef)+import Data.Word (Word32)+import LibRISCV (RegIdx (SP), align)+import LibRISCV.CmdLine+import LibRISCV.Effects.Decoding.Default.Interpreter (+    defaultDecoding,+ )+import LibRISCV.Effects.Expressions.Default.Interpreter (defaultEval, evalE)+import qualified LibRISCV.Effects.Expressions.Expr as E+import LibRISCV.Effects.Logging.Default.Interpreter (+    defaultLogging,+    noLogging,+ )+import LibRISCV.Effects.Operations.Default.Interpreter (+    defaultInstructions,+    dumpState,+    getMem,+    mkArchState,+ )+import LibRISCV.Effects.Operations.Default.Machine.Memory (storeByteString)+import LibRISCV.Loader+import LibRISCV.Semantics (buildAST, writeRegister)+import Options.Applicative++main' :: BasicArgs -> IO ()+main' (BasicArgs memAddr memSize trace putReg fp) = do+    state <- mkArchState memAddr memSize++    elf <- readElf fp+    loadElf elf $ storeByteString fromIntegral (getMem state)+    entry <- startAddr elf++    instRef <- newIORef (0 :: Word32)+    let+        initalSP = align (memAddr + memSize - 1)+        evalEnv = ((== 1), evalE)+        interpreter =+            interpretM (defaultInstructions state)+                . interpretM (defaultEval evalEnv)+                . interpretM (defaultDecoding @BV instRef)+                . interpretM (if trace then defaultLogging else noLogging)+    runM $ interpreter $ do+        writeRegister (bitVec 32 $ fromEnum SP) (E.FromInt 32 $ fromIntegral initalSP)+        buildAST @32 (bitVec 32 entry)++    when putReg $+        dumpState state++main :: IO ()+main = main' =<< execParser opts+  where+    opts =+        info+            (basicArgs <**> helper)+            ( fullDesc+                <> progDesc "Concrete execution of RV32I machine code"+            )
+ data/instr_dict.yaml view
@@ -0,0 +1,478 @@+add:+  encoding: 0000000----------000-----0110011+  extension:+  - rv_i+  mask: '0xfe00707f'+  match: '0x33'+  variable_fields:+  - rd+  - rs1+  - rs2+addi:+  encoding: '-----------------000-----0010011'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x13'+  variable_fields:+  - rd+  - rs1+  - imm12+and:+  encoding: 0000000----------111-----0110011+  extension:+  - rv_i+  mask: '0xfe00707f'+  match: '0x7033'+  variable_fields:+  - rd+  - rs1+  - rs2+andi:+  encoding: '-----------------111-----0010011'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x7013'+  variable_fields:+  - rd+  - rs1+  - imm12+auipc:+  encoding: '-------------------------0010111'+  extension:+  - rv_i+  mask: '0x7f'+  match: '0x17'+  variable_fields:+  - rd+  - imm20+beq:+  encoding: '-----------------000-----1100011'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x63'+  variable_fields:+  - bimm12hi+  - rs1+  - rs2+  - bimm12lo+bge:+  encoding: '-----------------101-----1100011'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x5063'+  variable_fields:+  - bimm12hi+  - rs1+  - rs2+  - bimm12lo+bgeu:+  encoding: '-----------------111-----1100011'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x7063'+  variable_fields:+  - bimm12hi+  - rs1+  - rs2+  - bimm12lo+blt:+  encoding: '-----------------100-----1100011'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x4063'+  variable_fields:+  - bimm12hi+  - rs1+  - rs2+  - bimm12lo+bltu:+  encoding: '-----------------110-----1100011'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x6063'+  variable_fields:+  - bimm12hi+  - rs1+  - rs2+  - bimm12lo+bne:+  encoding: '-----------------001-----1100011'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x1063'+  variable_fields:+  - bimm12hi+  - rs1+  - rs2+  - bimm12lo+ebreak:+  encoding: '00000000000100000000000001110011'+  extension:+  - rv_i+  mask: '0xffffffff'+  match: '0x100073'+  variable_fields: []+ecall:+  encoding: '00000000000000000000000001110011'+  extension:+  - rv_i+  mask: '0xffffffff'+  match: '0x73'+  variable_fields: []+fence:+  encoding: '-----------------000-----0001111'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0xf'+  variable_fields: []+jal:+  encoding: '-------------------------1101111'+  extension:+  - rv_i+  mask: '0x7f'+  match: '0x6f'+  variable_fields:+  - rd+  - jimm20+jalr:+  encoding: '-----------------000-----1100111'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x67'+  variable_fields:+  - rd+  - rs1+  - imm12+lb:+  encoding: '-----------------000-----0000011'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x3'+  variable_fields:+  - rd+  - rs1+  - imm12+lbu:+  encoding: '-----------------100-----0000011'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x4003'+  variable_fields:+  - rd+  - rs1+  - imm12+lh:+  encoding: '-----------------001-----0000011'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x1003'+  variable_fields:+  - rd+  - rs1+  - imm12+lhu:+  encoding: '-----------------101-----0000011'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x5003'+  variable_fields:+  - rd+  - rs1+  - imm12+lui:+  encoding: '-------------------------0110111'+  extension:+  - rv_i+  mask: '0x7f'+  match: '0x37'+  variable_fields:+  - rd+  - imm20+lw:+  encoding: '-----------------010-----0000011'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x2003'+  variable_fields:+  - rd+  - rs1+  - imm12+or:+  encoding: 0000000----------110-----0110011+  extension:+  - rv_i+  mask: '0xfe00707f'+  match: '0x6033'+  variable_fields:+  - rd+  - rs1+  - rs2+ori:+  encoding: '-----------------110-----0010011'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x6013'+  variable_fields:+  - rd+  - rs1+  - imm12+sb:+  encoding: '-----------------000-----0100011'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x23'+  variable_fields:+  - imm12hi+  - rs1+  - rs2+  - imm12lo+sh:+  encoding: '-----------------001-----0100011'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x1023'+  variable_fields:+  - imm12hi+  - rs1+  - rs2+  - imm12lo+sll:+  encoding: 0000000----------001-----0110011+  extension:+  - rv_i+  mask: '0xfe00707f'+  match: '0x1033'+  variable_fields:+  - rd+  - rs1+  - rs2+slli:+  encoding: 0000000----------001-----0010011+  extension:+  - rv32_i+  mask: '0xfe00707f'+  match: '0x1013'+  variable_fields:+  - rd+  - rs1+  - shamtw+slt:+  encoding: 0000000----------010-----0110011+  extension:+  - rv_i+  mask: '0xfe00707f'+  match: '0x2033'+  variable_fields:+  - rd+  - rs1+  - rs2+slti:+  encoding: '-----------------010-----0010011'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x2013'+  variable_fields:+  - rd+  - rs1+  - imm12+sltiu:+  encoding: '-----------------011-----0010011'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x3013'+  variable_fields:+  - rd+  - rs1+  - imm12+sltu:+  encoding: 0000000----------011-----0110011+  extension:+  - rv_i+  mask: '0xfe00707f'+  match: '0x3033'+  variable_fields:+  - rd+  - rs1+  - rs2+sra:+  encoding: 0100000----------101-----0110011+  extension:+  - rv_i+  mask: '0xfe00707f'+  match: '0x40005033'+  variable_fields:+  - rd+  - rs1+  - rs2+srai:+  encoding: 0100000----------101-----0010011+  extension:+  - rv32_i+  mask: '0xfe00707f'+  match: '0x40005013'+  variable_fields:+  - rd+  - rs1+  - shamtw+srl:+  encoding: 0000000----------101-----0110011+  extension:+  - rv_i+  mask: '0xfe00707f'+  match: '0x5033'+  variable_fields:+  - rd+  - rs1+  - rs2+srli:+  encoding: 0000000----------101-----0010011+  extension:+  - rv32_i+  mask: '0xfe00707f'+  match: '0x5013'+  variable_fields:+  - rd+  - rs1+  - shamtw+sub:+  encoding: 0100000----------000-----0110011+  extension:+  - rv_i+  mask: '0xfe00707f'+  match: '0x40000033'+  variable_fields:+  - rd+  - rs1+  - rs2+sw:+  encoding: '-----------------010-----0100011'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x2023'+  variable_fields:+  - imm12hi+  - rs1+  - rs2+  - imm12lo+xor:+  encoding: 0000000----------100-----0110011+  extension:+  - rv_i+  mask: '0xfe00707f'+  match: '0x4033'+  variable_fields:+  - rd+  - rs1+  - rs2+xori:+  encoding: '-----------------100-----0010011'+  extension:+  - rv_i+  mask: '0x707f'+  match: '0x4013'+  variable_fields:+  - rd+  - rs1+  - imm12++div:+  encoding: 0000001----------100-----0110011+  extension:+  - rv_m+  mask: '0xfe00707f'+  match: '0x2004033'+  variable_fields:+  - rd+  - rs1+  - rs2+divu:+  encoding: 0000001----------101-----0110011+  extension:+  - rv_m+  mask: '0xfe00707f'+  match: '0x2005033'+  variable_fields:+  - rd+  - rs1+  - rs2+mul:+  encoding: 0000001----------000-----0110011+  extension:+  - rv_m+  mask: '0xfe00707f'+  match: '0x2000033'+  variable_fields:+  - rd+  - rs1+  - rs2+mulh:+  encoding: 0000001----------001-----0110011+  extension:+  - rv_m+  mask: '0xfe00707f'+  match: '0x2001033'+  variable_fields:+  - rd+  - rs1+  - rs2+mulhsu:+  encoding: 0000001----------010-----0110011+  extension:+  - rv_m+  mask: '0xfe00707f'+  match: '0x2002033'+  variable_fields:+  - rd+  - rs1+  - rs2+mulhu:+  encoding: 0000001----------011-----0110011+  extension:+  - rv_m+  mask: '0xfe00707f'+  match: '0x2003033'+  variable_fields:+  - rd+  - rs1+  - rs2+rem:+  encoding: 0000001----------110-----0110011+  extension:+  - rv_m+  mask: '0xfe00707f'+  match: '0x2006033'+  variable_fields:+  - rd+  - rs1+  - rs2+remu:+  encoding: 0000001----------111-----0110011+  extension:+  - rv_m+  mask: '0xfe00707f'+  match: '0x2007033'+  variable_fields:+  - rd+  - rs1+  - rs2
+ internal/LibRISCV/Internal/Decoder/Generator.hs view
@@ -0,0 +1,200 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TupleSections #-}++module LibRISCV.Internal.Decoder.Generator where++import Control.Monad (forM)+import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.Bits ((.&.))+import Data.Char (toUpper)+import Data.FileEmbed (makeRelativeToProject)+import Data.List (nub)+import qualified Data.Map.Strict as M+import Data.Maybe (fromJust, isJust)+import Data.Word (Word32)+import Data.Yaml+import Language.Haskell.TH hiding (match)+import LibRISCV.Internal.Decoder.YamlParser++readAndParse :: (MonadIO m) => FilePath -> m [(String, InstructionFields)]+readAndParse filePath = M.toList <$> liftIO (decodeFileThrow filePath)++generateConsts :: [(String, InstructionFields)] -> String -> (InstructionFields -> String) -> Q [Dec]+generateConsts entries suffix f = do+    forM entries $ \(instructionName, fields) -> do+        let+            nameD = mkName $ instructionName <> "_" <> suffix+            valE = read $ f fields+        pure $+            ValD+                (VarP nameD)+                (NormalB $ LitE $ IntegerL valE)+                []++generateExtType :: [(String, InstructionFields)] -> Q [Dec]+generateExtType entries = do+    let+        byExts =+            M.fromListWith (<>)+                . map (\(x, y) -> (x, [y]))+                . concatMap+                    ( \(inst, fs) ->+                        map (,inst) (extension fs)+                    )+                $ entries+        superType =+            DataD+                []+                (mkName "InstructionType")+                []+                Nothing+                ( NormalC (mkName "InvalidInstruction") []+                    : map+                        ( \ex ->+                            NormalC+                                (mkName $ map toUpper ex)+                                [(Bang NoSourceUnpackedness NoSourceStrictness, ConT (mkName $ map toUpper ex))]+                        )+                        (M.keys byExts)+                )+                [DerivClause Nothing [ConT ''Eq, ConT ''Show]]+    extTypes <- forM (M.toList byExts) $ \(ext, instructions) -> do+        pure $+            DataD+                []+                (mkName $ map toUpper ext)+                []+                Nothing+                (map (\instr -> NormalC (mkName $ map toUpper instr) []) instructions)+                [DerivClause Nothing [ConT ''Eq, ConT ''Show]]++    pure $ extTypes <> [superType]++generateExtDecodeFns :: [(String, InstructionFields)] -> Q [Dec]+generateExtDecodeFns entries = do+    let+        byExts =+            M.fromListWith (<>)+                . map (\(x, y) -> (x, [y]))+                . concatMap+                    ( \(inst, fs) ->+                        map (,inst) (extension fs)+                    )+                $ entries++    sigs <- forM (M.toList byExts) $ \(ex, _) ->+        pure $+            SigD+                (mkName $ "decode_" <> map toUpper ex)+                ( AppT+                    ( AppT+                        ArrowT+                        (ConT ''Word32)+                    )+                    ( AppT+                        (ConT ''Maybe)+                        (ConT $ mkName $ map toUpper ex)+                    )+                )+    defs <- forM (M.toList byExts) $ \(ex, instrs) ->+        pure $+            FunD+                (mkName $ "decode_" <> map toUpper ex)+                [ Clause+                    [VarP (mkName "w")]+                    ( GuardedB $+                        map+                            ( \instr ->+                                ( NormalG $+                                    InfixE+                                        ( Just+                                            ( InfixE+                                                (Just (VarE $ mkName "w"))+                                                (VarE '(.&.))+                                                (Just (VarE $ mkName $ instr <> "_mask"))+                                            )+                                        )+                                        (VarE '(==))+                                        (Just (VarE $ mkName $ instr <> "_match"))+                                , AppE (ConE 'Just) $ ConE $ mkName $ map toUpper instr+                                )+                            )+                            instrs+                            <> [(NormalG (ConE 'True), ConE 'Nothing)]+                    )+                    []+                ]+    pure $ sigs <> defs++generateDecodeFn :: [(String, InstructionFields)] -> Q [Dec]+generateDecodeFn entries = do+    let+        exts =+            map (map toUpper)+                . nub+                . concatMap (extension . snd)+                $ entries+        sig =+            SigD+                (mkName "decode")+                ( AppT+                    ( AppT+                        ArrowT+                        (ConT ''Word32)+                    )+                    (ConT $ mkName "InstructionType")+                )+        def =+            FunD+                (mkName "decode")+                [ Clause+                    [VarP $ mkName "w"]+                    ( GuardedB $+                        map+                            ( \ex ->+                                ( NormalG $+                                    AppE+                                        (VarE 'isJust)+                                        ( AppE+                                            (VarE $ mkName $ "decode_" <> ex)+                                            (VarE $ mkName "w")+                                        )+                                , AppE+                                    (ConE $ mkName ex)+                                    ( AppE+                                        (VarE 'fromJust)+                                        ( AppE+                                            (VarE $ mkName $ "decode_" <> ex)+                                            (VarE $ mkName "w")+                                        )+                                    )+                                )+                            )+                            exts+                            <> [(NormalG (ConE 'True), ConE $ mkName "InvalidInstruction")]+                    )+                    []+                ]+    pure [def, sig]++generateMasks :: [(String, InstructionFields)] -> Q [Dec]+generateMasks entries = generateConsts entries "mask" mask++generateMatches :: [(String, InstructionFields)] -> Q [Dec]+generateMatches entries = generateConsts entries "match" match++generateAll :: [[(String, InstructionFields)] -> Q [Dec]] -> FilePath -> Q [Dec]+generateAll seqs path = do+    entries <- liftIO $ readAndParse path+    concat <$> mapM (\f -> f entries) seqs++generateDefaultDecoder :: Q [Dec]+generateDefaultDecoder =+    makeRelativeToProject "data/instr_dict.yaml"+        >>= generateAll+            [ generateMasks+            , generateMatches+            , generateExtType+            , generateExtDecodeFns+            , generateDecodeFn+            ]
+ internal/LibRISCV/Internal/Decoder/Instruction.hs view
@@ -0,0 +1,61 @@+module LibRISCV.Internal.Decoder.Instruction where++import Data.Bits+import Data.Word++-- Convert to an unsigned word (in two's complement) to a signed number.+fromTwoscomp :: Word32 -> Word32 -> Word32+fromTwoscomp numBits n = fromIntegral $ -(n .&. mask) + (n .&. complement mask)+  where+    mask :: Word32+    mask = 2 ^ (numBits - 1)++-- Extract a bit field from a RISC-V instruction word.+instrField :: Int -> Int -> Word32 -> Word32+instrField start end w = mask start (end + 1) .&. shiftR w start+  where+    -- Create a 32-bit bit mask in the range [start,end-1].+    mask :: Int -> Int -> Word32+    mask start end = complement $ shift (maxBound :: Word32) (end - start)++------------------------------------------------------------------------++immI :: Word32 -> Word32+immI = fromTwoscomp 12 . instrField 20 31++immS :: Word32 -> Word32+immS i =+    fromTwoscomp 12 $+        fromIntegral $+            (instrField 25 31 i `shift` 5) .|. instrField 07 11 i++immU :: Word32 -> Word32+immU i = instrField 12 31 i `shiftL` 12++{- FOURMOLU_DISABLE -}+immB :: Word32 -> Word32+immB i = fromTwoscomp 13 $+         (instrField 31 31 i `shift` 12)+     .|. (instrField 07 07 i `shift` 11)+     .|. (instrField 25 30 i `shift` 05)+     .|. (instrField 08 11 i `shift` 01)++immJ :: Word32 -> Word32+immJ i = fromTwoscomp 21 $+         (instrField 31 31 i `shift` 20)+     .|. (instrField 12 19 i `shift` 12)+     .|. (instrField 20 20 i `shift` 11)+     .|. (instrField 21 30 i `shift` 1)+{- FOURMOLU_ENABLE -}++mkShamt :: Word32 -> Word32+mkShamt = instrField 20 25++mkRs1 :: Word32 -> Word32+mkRs1 = instrField 15 19++mkRs2 :: Word32 -> Word32+mkRs2 = instrField 20 24++mkRd :: Word32 -> Word32+mkRd = instrField 7 11
+ internal/LibRISCV/Internal/Decoder/Opcodes.hs view
@@ -0,0 +1,9 @@+{-# LANGUAGE TemplateHaskell #-}+-- generateDefaultDecoder doesn't generate signatures.+{-# OPTIONS_GHC -Wno-missing-signatures #-}++module LibRISCV.Internal.Decoder.Opcodes where++import LibRISCV.Internal.Decoder.Generator++generateDefaultDecoder
+ internal/LibRISCV/Internal/Decoder/YamlParser.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE DeriveGeneric #-}++module LibRISCV.Internal.Decoder.YamlParser where++import qualified Data.Map.Strict as M+import Data.Yaml (FromJSON)+import GHC.Generics (Generic)++data InstructionFields = InstructionFields+    { extension :: [String]+    , mask :: String+    , match :: String+    }+    deriving (Eq, Show, Generic)++instance FromJSON InstructionFields++type InstructionEntries = M.Map String InstructionFields
+ lib/LibRISCV.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE MultiParamTypeClasses #-}++module LibRISCV where++import Data.Ix+import Data.Word++-- | Representation of a 32-bit addresses for RV32.+type Address = Word32++-- | Align an 'Address' on the next word boundary.+align :: Address -> Address+align addr = addr - addr `mod` 4++-- | Type to represent an index for the register file.+--+-- >>> toEnum 12 :: RegIdx+-- A2+-- >>> toEnum 31 :: RegIdx+-- T6+-- >>> fromEnum (minBound :: RegIdx)+-- 0+{- FOURMOLU_DISABLE -}+data RegIdx = Zero | RA | SP | GP | TP | T0 | T1 | T2 | FP+    | S1 | A0 | A1 | A2 | A3 | A4 | A5 | A6 | A7 | S2 | S3+    | S4 | S5 | S6 | S7 | S8 | S9 | S10 | S11 | T3 | T4+    | T5 | T6 deriving (Ord, Eq, Ix, Bounded, Show, Enum, Read)+{- FOURMOLU_ENABLE -}
+ lib/LibRISCV/CmdLine.hs view
@@ -0,0 +1,51 @@+-- | Implementation of common command-line arguments using <https://hackage.haskell.org/package/optparse-applicative  optparse-applicative>.+module LibRISCV.CmdLine where++import Data.Word+import Options.Applicative++-- | t'BasicArgs' can be combined/extended with additional parsers using+-- the '<*>' applicative operator provided by "Options.Applicative".+data BasicArgs = BasicArgs+    { memAddr :: Word32+    -- ^ Start address of the general-purpose memory.+    , memSize :: Word32+    -- ^ Size of the memory in bytes.+    , trace :: Bool+    -- ^ Whether to enable instruction tracing.+    , putRegs :: Bool+    -- ^ Whether to print all register values at the end.+    , file :: String+    -- ^ Path to ELf file which should be executed.+    }++-- TODO: Rename memStart to memSize.++-- | "Options.Applicative" parser for t'BasicArgs'.+basicArgs :: Parser BasicArgs+basicArgs =+    BasicArgs+        <$> option+            auto+            ( long "memory-start"+                <> short 'm'+                <> value 0x10000+            )+        <*> option+            auto+            ( long "memory-size"+                <> short 's'+                <> value (1024 * 1024 * 1) -- 1 MB RAM+                <> help "Size of the memory region"+            )+        <*> switch+            ( long "trace"+                <> short 't'+                <> help "Whether to trace all executed instructions"+            )+        <*> switch+            ( long "registers"+                <> short 'r'+                <> help "Whether to print all register values"+            )+        <*> argument str (metavar "FILE")
+ lib/LibRISCV/Effects/Decoding/Default/Interpreter.hs view
@@ -0,0 +1,54 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeOperators #-}++-- | Provides the default (concrete) interpretation for the decoding effect. This+-- implementation assumes that the 'Decodable' type class can be implemented for+-- the underlying type parameter, that is, it must be possible to convert the+-- type to a fixed-width concrete integer.+module LibRISCV.Effects.Decoding.Default.Interpreter where++import Control.Monad.Freer (type (~>))+import Control.Monad.IO.Class (MonadIO (liftIO))+import Data.BitVector (BV, bitVec)+import Data.Data (Proxy (..))+import Data.IORef (IORef, readIORef, writeIORef)+import Data.Word (Word32)+import LibRISCV.Effects.Decoding.Language (Decoding (..))+import LibRISCV.Internal.Decoder.Instruction (immB, immI, immJ, immS, immU, mkRd, mkRs1, mkRs2, mkShamt)+import LibRISCV.Internal.Decoder.Opcodes (decode)++-- | Decoder state used to implement the stateful 'SetInstr' constructor of the 'Decoding' effect.+type DecoderState = IORef Word32++-- | Type class used to perform conversion from/to a fixed-with concrete integer.+class Decodable a where+    fromWord :: Word32 -> a+    -- ^ Convert from a fixed-with integer to the underlying value type of the interpreter.++    toWord :: a -> Word32+    -- ^ Convert from the underlying value type to a fixed-with integer.++instance Decodable BV where+    fromWord = bitVec 32+    toWord = fromIntegral++-- | Concrete implementation of the decoding effect.+defaultDecoding :: forall v m. (Decodable v, MonadIO m) => DecoderState -> Decoding v ~> m+defaultDecoding instRef =+    let+        decodeAndConvert f = fmap (fromWord . f) . readIORef+     in+        liftIO . \case+            SetInstr v -> writeIORef instRef $ toWord v+            WithInstrType Proxy f -> f . decode <$> readIORef instRef+            DecodeRD -> decodeAndConvert mkRd instRef+            DecodeRS1 -> decodeAndConvert mkRs1 instRef+            DecodeRS2 -> decodeAndConvert mkRs2 instRef+            DecodeImmI -> decodeAndConvert immI instRef+            DecodeImmS -> decodeAndConvert immS instRef+            DecodeImmB -> decodeAndConvert immB instRef+            DecodeImmU -> decodeAndConvert immU instRef+            DecodeImmJ -> decodeAndConvert immJ instRef+            DecodeShamt -> decodeAndConvert mkShamt instRef
+ lib/LibRISCV/Effects/Decoding/Language.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE TemplateHaskell #-}++-- | Implements an effect for instruction decoding. Specifically, the effect+-- is only used to describe obtaining of instruction operands. The instruction+-- opcode is determined seperatly using code generated from the existing+-- <https://github.com/riscv/riscv-opcodes riscv-opcodes> tool. The decoding+-- effect is stateful and operates on a current instruction word specified via+-- 'setInstr', thereby avoiding the need to pass the current instruction word+-- to every decoding function.+module LibRISCV.Effects.Decoding.Language where++import Control.Monad.Freer.TH (makeEffect)+import Data.Data (Proxy)+import LibRISCV.Internal.Decoder.Opcodes (InstructionType)++data Decoding v r where+    DecodeRS1 :: Decoding v v+    DecodeRS2 :: Decoding v v+    DecodeRD :: Decoding v v+    DecodeImmI :: Decoding v v+    DecodeImmS :: Decoding v v+    DecodeImmB :: Decoding v v+    DecodeImmU :: Decoding v v+    DecodeImmJ :: Decoding v v+    DecodeShamt :: Decoding v v+    SetInstr :: v -> Decoding v ()+    -- TODO: WithInstrType is only used to determine which Haskell module+    -- provides the semantics for a given instruction. Can we eliminate+    -- the need to do this somehow?+    WithInstrType :: Proxy v -> (InstructionType -> b) -> Decoding v b++makeEffect ''Decoding
+ lib/LibRISCV/Effects/Expressions/Default/Interpreter.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeOperators #-}++-- | Provides the default (concrete) evaluation of the expression abstraction.+module LibRISCV.Effects.Expressions.Default.Interpreter where++import Control.Monad.Freer (type (~>))+import Control.Monad.IO.Class (MonadIO)+import Data.BitVector (BV, Bits (unsafeShiftL, unsafeShiftR, xor, (.&.), (.|.)), bitVec, extract, signExtend, zeroExtend)+import Data.Bool (bool)+import Data.Int (Int32)+import Data.Word (Word8)+import LibRISCV.Effects.Expressions.Expr (Expr (..))+import LibRISCV.Effects.Expressions.Language (ExprEval (..))++-- | Evaluate an 'Expr' abstraction which encapsulates a concrete 'BV'.+evalE :: Expr BV -> BV+evalE (FromImm a) = a+evalE (FromInt n i) = bitVec n i+evalE (ZExt n e) = zeroExtend n (evalE e)+evalE (SExt n e) = signExtend n (evalE e)+evalE (Extract start len e) = extract (start + (len - 1)) start (evalE e)+evalE (Add e1 e2) = evalE e1 + evalE e2+evalE (Sub e1 e2) =+    fromIntegral $+        (fromIntegral (evalE e1) :: Int32) - fromIntegral (evalE e2)+evalE (Eq e1 e2) = bool 0 1 $ (fromIntegral (evalE e1) :: Int32) == fromIntegral (evalE e2)+evalE (Slt e1 e2) = bool 0 1 $ (fromIntegral (evalE e1) :: Int32) < fromIntegral (evalE e2)+evalE (Sge e1 e2) = bool 0 1 $ (fromIntegral (evalE e1) :: Int32) >= fromIntegral (evalE e2)+evalE (Ult e1 e2) = bool 0 1 $ evalE e1 < evalE e2+evalE (Uge e1 e2) = bool 0 1 $ evalE e1 >= evalE e2+evalE (And e1 e2) = evalE e1 .&. evalE e2+evalE (Or e1 e2) = evalE e1 .|. evalE e2+evalE (Xor e1 e2) = evalE e1 `xor` evalE e2+evalE (LShl e1 e2) = evalE e1 `unsafeShiftL` fromIntegral (fromIntegral (evalE e2) :: Word8)+evalE (LShr e1 e2) = evalE e1 `unsafeShiftR` fromIntegral (fromIntegral (evalE e2) :: Word8)+evalE (AShr e1 e2) = fromIntegral $ (fromIntegral (evalE e1) :: Int32) `unsafeShiftR` fromIntegral (fromIntegral (evalE e2) :: Word8)+evalE (Mul e1 e2) = evalE e1 * evalE e2+evalE (SDiv e1 e2) = fromIntegral $ fromIntegral @_ @Int32 (evalE e1) `quot` fromIntegral (evalE e2)+evalE (SRem e1 e2) = fromIntegral $ fromIntegral @_ @Int32 (evalE e1) `rem` fromIntegral (evalE e2)+evalE (UDiv e1 e2) = evalE e1 `quot` evalE e2+evalE (URem e1 e2) = evalE e1 `rem` evalE e2++-- | Concrete implementation of the 'ExprEval' effect.+defaultEval :: (MonadIO m) => (v -> Bool, Expr v -> v) -> ExprEval v ~> m+defaultEval (pred, evalE) =+    pure . \case+        Eval e -> evalE e+        IsTrue e -> pred $ evalE e+        IsFalse e -> not . pred $ evalE e
+ lib/LibRISCV/Effects/Expressions/Expr.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE TemplateHaskell #-}+-- generateImmediates doesn't generate signatures.+{-# OPTIONS_GHC -Wno-missing-signatures #-}++-- | Defines the expression abstraction to express arithmetic and logic+-- operations within the formal description of RISC-V instructions. The+-- abstraction is just a non-monadic algebraic data type called 'Expr'.+-- In addition to the the data type definition, this module also provides+-- several smart constructors for utilzing the expression lanuage, these+-- are generated using template-haskell.+module LibRISCV.Effects.Expressions.Expr (+    module LibRISCV.Effects.Expressions.Type,+    module LibRISCV.Effects.Expressions.Expr,+) where++import Data.Function (on)+import LibRISCV.Effects.Expressions.Generator+import LibRISCV.Effects.Expressions.Type++-- | Extract shamt value from an expression (lower 5 bits).+regShamt :: Int -> Expr a -> Expr a+regShamt w a = a `And` FromInt w 0x1f++------------------------------------------------------------------------++zextImm :: Int -> a -> Expr a+zextImm w = ZExt w . FromImm++sextImm :: Int -> a -> Expr a+sextImm w = SExt w . FromImm++generateImmediates
+ lib/LibRISCV/Effects/Expressions/Generator.hs view
@@ -0,0 +1,66 @@+-- This module generates smart constructors for the LibRISCV expressions+-- language through template-haskell. These constructors are syntactic sugar+-- intended to ease usage of the expression language.+module LibRISCV.Effects.Expressions.Generator (generateImmediates) where++import Data.Char (toLower)+import Language.Haskell.TH++binOps :: [String]+binOps =+    [ "Add"+    , "Sub"+    , "Eq"+    , "Slt"+    , "Sge"+    , "Ult"+    , "Uge"+    , "And"+    , "Or"+    , "Xor"+    , "LShl"+    , "LShr"+    , "AShr"+    , "Mul"+    , "UDiv"+    , "SDiv"+    , "URem"+    , "SRem"+    ]++------------------------------------------------------------------------++foldcase :: String -> String+foldcase = map toLower++genImm :: String -> Q Dec+genImm operator = do+    let name = mkName $ foldcase operator++    let consName = mkName operator+    let immName = mkName "FromImm"+    rval <- newName "rval"+    lval <- newName "lval"++    let body =+            AppE+                (AppE (ConE consName) (AppE (ConE immName) (VarE lval)))+                (AppE (ConE immName) (VarE rval))+    return $ FunD name [Clause [VarP lval, VarP rval] (NormalB body) []]++genImmRval :: String -> Q Dec+genImmRval operator = do+    let name = mkName $ foldcase operator ++ "Imm"++    let onName = mkName "on"+    let consName = mkName operator+    let immName = mkName "FromImm"++    let body = AppE (AppE (VarE onName) (ConE consName)) (ConE immName)+    return $ FunD name [Clause [] (NormalB body) []]++generateImmediates :: Q [Dec]+generateImmediates = do+    l1 <- mapM genImmRval binOps+    l2 <- mapM genImm binOps+    pure (l1 ++ l2)
+ lib/LibRISCV/Effects/Expressions/Language.hs view
@@ -0,0 +1,70 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeOperators #-}++-- | Implements an effect for /evaluation/ of the 'Expr' abstraction.+module LibRISCV.Effects.Expressions.Language (+    ExprEval (..),+    eval,+    defaultEval,+    ifExprM,+    whenExprM,+    unlessExprM,+) where++import Control.Monad.Freer (Eff, Member, type (~>))+import Control.Monad.Freer.TH (makeEffect)+import Control.Monad.IO.Class (MonadIO)+import LibRISCV.Effects.Expressions.Expr (Expr)++-- TODO: Reevaluate if we really need the distinction between IsTrue / IsFalse+-- for symbolic execution or if this is just nice to have for debugging.+data ExprEval v r where+    IsTrue :: Expr v -> ExprEval v Bool+    IsFalse :: Expr v -> ExprEval v Bool+    Eval :: Expr v -> ExprEval v v++makeEffect ''ExprEval++defaultEval :: (MonadIO m) => (v -> Bool, Expr v -> v) -> ExprEval v ~> m+defaultEval (pred, evalE) = \case+    Eval e -> pure $ evalE e+    IsTrue e -> pure $ pred $ evalE e+    IsFalse e -> pure $ not . pred $ evalE e++------------------------------------------------------------------------++-- Higher-level abstraction for making use of IsTrue / IsFalse without+-- having to export the isTrue / isFalse smart constructor. The standard+-- constructor is still exported as it will need to be interpreted.++condExprM ::+    forall v r v'.+    (Member (ExprEval v) r) =>+    (Expr v -> Eff r Bool) ->+    Expr v ->+    Eff r v' ->+    Eff r v' ->+    Eff r v'+condExprM p b t f = do+    b <- p b+    if b then t else f++-- | Like 'Control.Monad.Extra.ifM' but with internal expression evaluation.+ifExprM :: forall v r v'. (Member (ExprEval v) r) => Expr v -> Eff r v' -> Eff r v' -> Eff r v'+ifExprM = (condExprM isTrue)++-- | Like 'Control.Monad.Extra.whenM' but with internal expression evaluation.+whenExprM :: forall v r. (Member (ExprEval v) r) => Expr v -> Eff r () -> Eff r ()+whenExprM b t = (condExprM isTrue) b t (pure ())++-- | Like 'Control.Monad.Extra.unlessM' but with internal expression evaluation.+unlessExprM :: forall v r. (Member (ExprEval v) r) => Expr v -> Eff r () -> Eff r ()+unlessExprM b t = (condExprM isFalse) b t (pure ())
+ lib/LibRISCV/Effects/Expressions/Type.hs view
@@ -0,0 +1,38 @@+module LibRISCV.Effects.Expressions.Type where++data Expr a+    = -- | Create a new 'Expr' from a given immediate value.+      FromImm a+    | -- | Create an expression from a concrete 'Integer', treating it as a fixed-width two's+      -- complement value of the size (in bits) specified by the first argument to the constructor.+      FromInt Int Integer+    | -- | Zero extend an expression by adding the given amount of bits to it, for example,+      -- an 8-bit value can be zero-extended to 32-bit by passing 24 as the first argument.+      ZExt Int (Expr a)+    | -- | Same as 'ZExt' but performs sign-extension instead of zero-extension.+      SExt Int (Expr a)+    | -- | Extract a specified amount of bits from an expression. The first argument specifies+      -- the first bit which should be extracted, the second specifies the amount of bits to extract.+      Extract Int Int (Expr a)+    | Add (Expr a) (Expr a)+    | Sub (Expr a) (Expr a)+    | Eq (Expr a) (Expr a)+    | Slt (Expr a) (Expr a)+    | Sge (Expr a) (Expr a)+    | Ult (Expr a) (Expr a)+    | Uge (Expr a) (Expr a)+    | And (Expr a) (Expr a)+    | Or (Expr a) (Expr a)+    | Xor (Expr a) (Expr a)+    | LShl (Expr a) (Expr a)+    | LShr (Expr a) (Expr a)+    | AShr (Expr a) (Expr a)+    | Mul (Expr a) (Expr a)+    | UDiv (Expr a) (Expr a)+    | SDiv (Expr a) (Expr a)+    | URem (Expr a) (Expr a)+    | SRem (Expr a) (Expr a)++-- XXX: For binary operations, consider creation of a smart constructor which+-- allows passing an immediate value of type `a` as a second parameter. See+-- LibRISCV.Effects.Expressions.Generator for more information.
+ lib/LibRISCV/Effects/Logging/Default/Interpreter.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE TypeOperators #-}++-- | Provides the default interpretation for the logging effect.+module LibRISCV.Effects.Logging.Default.Interpreter where++import Control.Monad.Freer (type (~>))+import Control.Monad.IO.Class (MonadIO (..))+import LibRISCV.Effects.Logging.Language (+    LogInstructionFetch (..),+ )++-- | The default effectful logging interpreter which writes the+-- 'LibRISCV.Internal.Decoder.Opcodes.InstructionType' of a fetched instruction+-- to standard output, before executing it. This is particularly useful for+-- debugging but is quite verbose in the common case.+defaultLogging :: (MonadIO m) => LogInstructionFetch ~> m+defaultLogging =+    liftIO . \case+        LogFetched inst -> print inst++-- | A stub implementation of an effectful interpreter which ignores any logging+-- effects entirely. Should be used when no debugging output is desired.+noLogging :: (Monad m) => LogInstructionFetch ~> m+noLogging = \case+    LogFetched _ -> pure ()
+ lib/LibRISCV/Effects/Logging/Language.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeOperators #-}++-- | Implements the logging effect for instruction tracing. This effect+-- is primarly intended to be used for debugging.+module LibRISCV.Effects.Logging.Language where++import Control.Monad.Freer.TH (makeEffect)+import LibRISCV.Internal.Decoder.Opcodes (InstructionType)++data LogInstructionFetch r where+    -- TODO: Log program counter (i.e. instruction address) too.+    LogFetched :: InstructionType -> LogInstructionFetch ()++makeEffect ''LogInstructionFetch
+ lib/LibRISCV/Effects/Operations/Default/Interpreter.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeOperators #-}++-- | Implements the default (concrete) interpreter for the 'Operations' effect.+module LibRISCV.Effects.Operations.Default.Interpreter (+    ArchState (..),+    mkArchState,+    dumpState,+    defaultInstructions,+) where++import Control.Monad.Freer (type (~>))+import Control.Monad.IO.Class (MonadIO (..))+import Data.Array.IO (IOUArray)+import Data.BitVector (BV, bitVec)+import Data.Int (Int32)+import Data.Word (Word16, Word32, Word8)+import LibRISCV (Address)+import qualified LibRISCV.Effects.Operations.Default.Machine.Memory as MEM+import qualified LibRISCV.Effects.Operations.Default.Machine.Register as REG+import LibRISCV.Effects.Operations.Language (+    Operations (..),+    Size (Byte, Half, Word),+ )+import Numeric (showHex)++-- | Representation of the concrete architectural state of the interpreter.+data ArchState = ArchState+    { getReg :: REG.RegisterFile IOUArray Int32+    -- ^ Register file implementation of the architectural state.+    , getMem :: MEM.Memory IOUArray Word8+    -- ^ Memory implementation of the architectural state.+    }++-- | Create a new t'ArchState' based on a memory start address and a memory size.+mkArchState :: Address -> Word32 -> IO ArchState+mkArchState memStart memSize = do+    reg <- REG.mkRegFile 0+    mem <- MEM.mkMemory memStart memSize+    pure $ ArchState reg mem++-- | Write a textual representation of the t'ArchState' to standard output.+dumpState :: ArchState -> IO ()+dumpState ArchState{getReg = r} =+    REG.dumpRegs (showHex . fromIntegral @Int32 @Word32) r >>= putStr++-- | Implements concrete interpretation of the 'Operations' effect based on a 'BV' value representation.+defaultInstructions :: (MonadIO m) => ArchState -> Operations BV ~> m+defaultInstructions (ArchState regFile mem) =+    liftIO . \case+        ReadRegister idx -> bitVec 32 <$> REG.readRegister regFile (toEnum $ fromIntegral idx)+        WriteRegister idx reg -> REG.writeRegister regFile (toEnum $ fromIntegral idx) (fromIntegral reg)+        Load size addr -> case size of+            Byte -> bitVec 8 <$> MEM.loadByte mem (fromIntegral addr)+            Half -> bitVec 16 <$> (MEM.loadHalf mem (fromIntegral addr) :: IO Word16)+            Word -> bitVec 32 <$> MEM.loadWord @_ @_ @BV mem (fromIntegral addr)+        Store size addr w -> case size of+            Byte -> MEM.storeByte mem (fromIntegral addr) (fromIntegral w)+            Half -> MEM.storeHalf mem (fromIntegral addr) w+            Word -> MEM.storeWord mem (fromIntegral addr) w+        WritePC w -> REG.writePC regFile (fromIntegral w)+        ReadPC -> bitVec 32 <$> REG.readPC regFile+        Exception pc msg -> error $ "[0x" ++ showHex pc "" ++ "] " ++ msg+        Ecall pc -> putStrLn $ "ecall at 0x" ++ showHex pc ""+        Ebreak pc -> putStrLn $ "ebreak at 0x" ++ showHex pc ""
+ lib/LibRISCV/Effects/Operations/Default/Machine/Memory.hs view
@@ -0,0 +1,161 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TupleSections #-}++-- | Provides an implementatio of a byte-addressable memory, intended for internal+-- usage in interpreters for the 'LibRISCV.Effects.Operations.Operations' effect.+module LibRISCV.Effects.Operations.Default.Machine.Memory (+    Memory,+    HalfStorage (..),+    WordStorage (..),+    mkMemory,+    memSize,+    loadByte,+    loadHalf,+    loadWord,+    storeByte,+    storeHalf,+    storeWord,+    storeByteString,+    mkWord,+    mkBytes,+) where++import Data.Array.IO (+    MArray (getBounds, newArray_),+    readArray,+    writeArray,+ )+import Data.BitVector (BV, bitVec)+import Data.Bits (Bits (shift, shiftR, (.&.), (.|.)))+import qualified Data.ByteString.Lazy as BSL+import Data.Int ()+import Data.Word (Word16, Word32, Word8)+import LibRISCV++-- | Since the memory is byte-addressable it requires converting values of a+-- larger size to bytes. This type class is responsible for a conversion of+-- 16-bit values (halfs). That is, it converts halfs to bytes (and vice versa)+-- in little endian.+class HalfStorage halfType byteType where+    -- | Convert a list of two bytes to a single half.+    toHalf :: [byteType] -> halfType++    -- | Convert a single half to a list of two bytes.+    halfToBytes :: halfType -> [byteType]++-- | Similar to 'HalfStorage' but handles conversion of 32-bit values (words).+class WordStorage wordType byteType where+    -- | Convert a list of four bytes to a single word.+    toWord :: [byteType] -> wordType++    -- | Convert a single word to a list of four bytes.+    wordToBytes :: wordType -> [byteType]++instance WordStorage Word32 Word8 where+    toWord = mkWord+    wordToBytes = mkBytes++instance HalfStorage Word16 Word8 where+    toHalf = fromIntegral . mkWord+    halfToBytes = mkBytes . fromIntegral++instance WordStorage BV Word8 where+    toWord = (bitVec 32 :: Word32 -> BV) . fromIntegral . mkWord+    wordToBytes = mkBytes . fromIntegral++instance HalfStorage BV Word8 where+    toHalf = bitVec 16 . mkWord+    halfToBytes = mkBytes . fromIntegral++-- | Converts a list of bytes to a 'Word32' in little endian.+mkWord :: [Word8] -> Word32+mkWord bytes =+    foldl+        (\x (byte, idx) -> (fromIntegral byte `shift` (idx * 8)) .|. x)+        0+        $ zip bytes [0 .. length bytes - 1]++-- | Split a 32-bit word into four octets in little endian.+mkBytes :: Word32 -> [Word8]+mkBytes w = map (\off -> fromIntegral $ shiftR w off .&. 0xff) offs+  where+    offs = take 4 $ iterate (+ 8) 0++------------------------------------------------------------------------++-- | Representation of a byte-addressable memory. The type is parameterized+-- over an array implementation (such as 'Data.Array.IO.IOUArray') and a+-- generic value type (used to represent instruction operands).+data Memory t a = Memory+    { memStart :: Address+    , memBytes :: t Address a+    }++-- | Create a new memory of the given size starting at the given address.+mkMemory :: (MArray t a IO) => Address -> Word32 -> IO (Memory t a)+mkMemory addr size = fmap (Memory addr) (newArray_ (0, size - 1))++-- Translate global address to a memory-local address.+toMemAddr :: Memory t a -> Address -> Address+toMemAddr mem addr = addr - (memStart mem)++-- | Returns the size of the memory in bytes.+memSize :: (MArray t a IO) => Memory t a -> IO Word32+memSize = fmap ((+ 1) . snd) . getBounds . memBytes++------------------------------------------------------------------------++-- TODO: Only provide load/store, remove all wrapper functions. Could+-- use the 'Size' type from the operations language for this purpose too.++-- | Load a single byte from memory at the given address.+loadByte :: (MArray t a IO) => Memory t a -> Address -> IO a+loadByte mem = readArray (memBytes mem) . toMemAddr mem++load :: (MArray t a IO) => ([a] -> b) -> Word32 -> Memory t a -> Address -> IO b+load proc bytesize mem addr =+    proc+        <$> mapM (\off -> loadByte mem $ addr + off) [0 .. (bytesize - 1)]++-- | Load a half (16-bit) from memory at the given address.+loadHalf :: (MArray t a IO, HalfStorage b a) => Memory t a -> Address -> IO b+loadHalf = load toHalf 2++-- | Load a word (32-bit) from memory at the given address.+loadWord :: (MArray t a IO, WordStorage b a) => Memory t a -> Address -> IO b+loadWord = load toWord 4++-- | Store a single byte in memory.+storeByte :: (MArray t a IO) => Memory t a -> Address -> a -> IO ()+storeByte mem addr = writeArray (memBytes mem) $ toMemAddr mem addr++store :: (MArray t a IO) => (b -> [a]) -> Word32 -> Memory t a -> Address -> b -> IO ()+store proc bytesize mem addr =+    mapM_ (\(off, val) -> storeByte mem (addr + off) val)+        . zip [0 .. (bytesize - 1)]+        . proc++-- | Store a half (16-bit) in memory.+storeHalf :: (MArray t a IO, HalfStorage b a) => Memory t a -> Address -> b -> IO ()+storeHalf = store halfToBytes 2++-- | Store a word (32-bit) in memory.+storeWord :: (MArray t a IO, WordStorage b a) => Memory t a -> Address -> b -> IO ()+storeWord = store wordToBytes 4++-- | Write a 'BSL.ByteString' to memory in little endian byteorder. Expects a+-- function to convert single bytes ('Word8') to the chosen value+-- representation, a t'Memory', as well the 'Address' where the string should be+-- stored and the 'BSL.ByteString' itself.+storeByteString ::+    (MArray t a IO) =>+    (Word8 -> a) ->+    Memory t a ->+    Address ->+    BSL.ByteString ->+    IO ()+storeByteString convert mem addr bs =+    mapM_ (\(off, val) -> storeByte mem (addr + off) (convert val)) $+        zip [0 ..] $+            BSL.unpack bs
+ lib/LibRISCV/Effects/Operations/Default/Machine/Register.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE FlexibleContexts #-}++-- | Provides a polymorphic implementation of a register file. This module is+-- intended to be used internally by interpreters for the+-- 'LibRISCV.Effects.Operations.Operations' effect. This register file+-- implementation also provides facilities for storing a concrete program+-- counter.+module LibRISCV.Effects.Operations.Default.Machine.Register where++import Control.Monad (unless)+import Data.Array.IO (+    MArray (newArray),+    getElems,+    readArray,+    writeArray,+ )+import Data.IORef (IORef, newIORef, readIORef, writeIORef)+import Data.Word (Word32)+import LibRISCV++-- | Register file addressed by 'RegIdx'. The type is parameterized over an array+-- implementation (such as 'Data.Array.IO.IOUArray') and a generic value type+-- (used to represent instruction operands).+data RegisterFile t a = RegisterFile+    { pc :: IORef Word32+    -- ^ The current program counter (always concrete in this implementation).+    , regs :: t RegIdx a+    -- ^ The underlying array to store the register values.+    }++-- | Create a new register file, initializing all registers with the given default+-- value. This value /must/ represent the zero value in the chosen value type.+mkRegFile :: (MArray t a IO) => a -> IO (RegisterFile t a)+mkRegFile defValue = RegisterFile <$> newIORef 0 <*> newArray (minBound, maxBound) defValue++-- | Dump the current register file state as a 'String'.+dumpRegs :: (MArray t a IO) => (a -> ShowS) -> RegisterFile t a -> IO String+dumpRegs sh =+    fmap+        ( foldr (\(a, v) s -> show a ++ "\t= 0x" ++ sh v "\n" ++ s) ""+            . zip [(minBound :: RegIdx) .. maxBound]+        )+        . getElems+        . regs++------------------------------------------------------------------------++-- | Read register value at given register index. For the 'Zero' register index, the+-- zero/default value (as passed to 'mkRegFile' is always returned.+readRegister :: (MArray t a IO) => RegisterFile t a -> RegIdx -> IO a+readRegister = readArray . regs++-- | Write register at given register index. Writes to the 'Zero' register are ignored.+writeRegister :: (MArray t a IO) => RegisterFile t a -> RegIdx -> a -> IO ()+writeRegister RegisterFile{regs = r} idx val = unless (idx == Zero) $ writeArray r idx val++------------------------------------------------------------------------++-- | Returs the current program counter value.+readPC :: RegisterFile t a -> IO Word32+readPC = readIORef . pc++-- | Write a new program counter value.+writePC :: RegisterFile t a -> Word32 -> IO ()+writePC = writeIORef . pc
+ lib/LibRISCV/Effects/Operations/Language.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE TemplateHaskell #-}++-- | Implements an effect for interactions with the architectural state, upon+-- which instructions are executed (register file, memory, program counter, etc.).+module LibRISCV.Effects.Operations.Language where++import Control.Monad.Freer.TH (makeEffect)++-- | Abstraction for expressing a 8-, 16-, or 32-bit size.+data Size = Byte | Half | Word+    deriving (Eq, Show)++-- | Returns the size in bits (either 8, 16, or 32).+bitSize :: Size -> Int+bitSize Byte = 8+bitSize Half = 16+bitSize Word = 32++data Operations v r where+    ReadRegister :: v -> Operations v v+    WriteRegister :: v -> v -> Operations v ()+    Load :: Size -> v -> Operations v v+    Store :: Size -> v -> v -> Operations v ()+    WritePC :: v -> Operations v ()+    ReadPC :: Operations v v+    Exception :: v -> String -> Operations v ()+    Ecall :: v -> Operations v ()+    Ebreak :: v -> Operations v ()++makeEffect ''Operations
+ lib/LibRISCV/Loader.hs view
@@ -0,0 +1,87 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}++-- | An implementation of an /Executable Loadable Format/ (ELF) loader. The module+-- is responsible for loading instructions into a provided memory implementation+-- and obtaining the entry point for the executable.+module LibRISCV.Loader (readElf, LoadFunc, loadElf, startAddr) where++import Control.Monad.Catch (MonadCatch)+import Data.Bits ()+import qualified Data.ByteString.Lazy as BSL+import Data.Elf (+    Elf (..),+    ElfListXX (..),+    ElfNodeType (..),+    ElfSectionData (ElfSectionData),+    ElfXX (+        ElfSection,+        ElfSegment,+        ehEntry,+        epAddMemSize,+        epData,+        epType,+        esAddr,+        esData+    ),+    elfFindHeader,+    parseElf,+ )+import Data.Elf.Constants+import Data.Elf.Headers (+    SingElfClass (SELFCLASS32, SELFCLASS64),+    SingElfClassI,+    withSingElfClassI,+ )+import Data.Elf.PrettyPrint (readFileLazy)+import Data.Int (Int64)+import Data.Word (Word32)+import LibRISCV+import System.FilePath ()++-- Filter all ELF segments with type PT_LOAD.+loadableSegments :: ElfListXX a -> [ElfXX 'Segment a]+loadableSegments (ElfListCons v@(ElfSegment{..}) l) =+    if epType == PT_LOAD+        then v : loadableSegments l+        else loadableSegments l+loadableSegments (ElfListCons _ l) = loadableSegments l+loadableSegments ElfListNull = []++-- Copy data from ElfSection to memory at the given absolute address.+copyData :: (Monad m, SingElfClassI a) => ElfListXX a -> Int64 -> LoadFunc m -> m ()+copyData ElfListNull _ _ = pure ()+copyData (ElfListCons (ElfSection{esData = ElfSectionData textData, ..}) xs) zeros f = do+    f (fromIntegral esAddr) $ BSL.append textData (BSL.replicate zeros 0)+    copyData xs zeros f+copyData (ElfListCons _ xs) zeros f = copyData xs zeros f++-- Load an ElfSegment into memory at the given address.+loadSegment :: (Monad m, SingElfClassI a) => LoadFunc m -> ElfXX 'Segment a -> m ()+loadSegment loadFunc ElfSegment{..} =+    copyData epData (fromIntegral epAddMemSize) loadFunc++------------------------------------------------------------------------++-- | Load a 'BSL.ByteString' into memory at a given address.+type LoadFunc m = Address -> BSL.ByteString -> m ()++-- | Load all loadable segments of an ELF file into memory. An addition to the+-- 'Data.Elf.Elf' file, it requires an implementation of a 'LoadFunc' which is+-- responsible for converting a 'BSL.ByteString' to the internal value+-- representation.+loadElf :: (Monad m) => Elf -> LoadFunc m -> m ()+loadElf (Elf classS elfs) loadFunc = withSingElfClassI classS $ do+    let loadable = loadableSegments elfs+    mapM_ (loadSegment loadFunc) loadable++-- | Read an ELF from a given 'FilePath'.+readElf :: FilePath -> IO Elf+readElf path = readFileLazy path >>= parseElf++-- | Return the entry point from the ELF header.+startAddr :: (MonadCatch m) => Elf -> m Word32+startAddr (Elf SELFCLASS32 elfs) = ehEntry <$> elfFindHeader elfs+startAddr (Elf SELFCLASS64 _) = error "64-bit executables not supported"
+ lib/LibRISCV/Semantics.hs view
@@ -0,0 +1,13 @@+-- | Functions for interacting with the free monad abstraction used to formally+-- describe the semantics of RISC-V instructions.+module LibRISCV.Semantics (+    LibRISCV.Semantics.Default.buildAST,+    LibRISCV.Semantics.Utils.writeRegister,+    LibRISCV.Semantics.Utils.readRegister,+    LibRISCV.Semantics.Utils.load,+    LibRISCV.Semantics.Utils.store,+    LibRISCV.Semantics.Utils.writePC,+) where++import LibRISCV.Semantics.Default+import LibRISCV.Semantics.Utils
+ lib/LibRISCV/Semantics/Default.hs view
@@ -0,0 +1,97 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}++module LibRISCV.Semantics.Default where++import Control.Monad.Freer+import Data.Data (Proxy (..))+import Data.Parameterized.NatRepr+import GHC.TypeLits+import LibRISCV.Effects.Decoding.Language+import LibRISCV.Effects.Expressions.Expr+import LibRISCV.Effects.Expressions.Language+import LibRISCV.Effects.Logging.Language (LogInstructionFetch, logFetched)+import LibRISCV.Effects.Operations.Language hiding (load, readRegister, store, writePC, writeRegister)+import LibRISCV.Internal.Decoder.Opcodes+import qualified LibRISCV.Semantics.RV32_I.Default as RV32_I+import qualified LibRISCV.Semantics.RV_I.Default as RV_I+import qualified LibRISCV.Semantics.RV_M.Default as RV_M+import LibRISCV.Semantics.Utils++------------------------------------------------------------------------++-- We require type annotations here to workaround a limitation of+-- GHC type inference in conjunction with freer-simple. Alternatively,+-- we could use a proxy type.+--+-- See: https://github.com/lexi-lambda/freer-simple/issues/7++instrSemantics ::+    forall v r.+    ( Member (Operations v) r+    , Member LogInstructionFetch r+    , Member (Decoding v) r+    , Member (ExprEval v) r+    ) =>+    Int ->+    v ->+    Eff r ()+instrSemantics width pc = do+    ty <- withInstrType @v Proxy id+    logFetched ty+    case ty of+        RV_I inst -> RV_I.instrSemantics width pc inst >> buildInstruction @v width+        RV32_I inst -> RV32_I.instrSemantics @v inst >> buildInstruction @v width+        RV_M inst -> RV_M.instrSemantics @v width inst >> buildInstruction @v width+        InvalidInstruction -> pure ()++buildInstruction ::+    forall v r.+    ( Member (Operations v) r+    , Member LogInstructionFetch r+    , Member (Decoding v) r+    , Member (ExprEval v) r+    ) =>+    Int ->+    Eff r ()+buildInstruction width = do+    -- fetch instruction at current PC+    pc <- readPC @v+    instrWord <- load @v Word $ FromImm pc+    setInstr instrWord++    -- Increment PC before execute', allows setting PC to to+    -- different values in execute' for jumps and branches.+    writePC $ FromImm pc `Add` FromInt width 4+    instrSemantics width pc++-- | Obtain the free monad AST for a program loaded into memory, e.g. through+-- the provided ELF 'LibRISCV.Loader' implementation. The function takes one+-- argument  which corresponds to an address in memory at which program+-- execution will start. An instruction word will be loaded from this address,+-- decoded, and executed.+buildAST ::+    forall w v r.+    ( KnownNat w+    , Member (Operations v) r+    , Member LogInstructionFetch r+    , Member (Decoding v) r+    , Member (ExprEval v) r+    ) =>+    v ->+    Eff r ()+buildAST entry =+    let+        !width = fromIntegral (intValue (knownNat :: NatRepr w))+     in+        writePC (FromImm entry) >> buildInstruction @v width
+ lib/LibRISCV/Semantics/RV32_I/Default.hs view
@@ -0,0 +1,38 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}++module LibRISCV.Semantics.RV32_I.Default where++import Control.Monad.Freer+import LibRISCV.Effects.Decoding.Language (Decoding, decodeShamt)+import LibRISCV.Effects.Expressions.Expr+import LibRISCV.Effects.Expressions.Language (ExprEval)+import LibRISCV.Effects.Logging.Language (LogInstructionFetch)+import LibRISCV.Effects.Operations.Language (Operations (..))+import LibRISCV.Internal.Decoder.Opcodes (RV32_I (..))+import LibRISCV.Semantics.Utils++instrSemantics :: forall v r. (Member (Operations v) r, Member LogInstructionFetch r, Member (Decoding v) r, Member (ExprEval v) r) => RV32_I -> Eff r ()+instrSemantics = \case+    SLLI -> do+        (r1, rd, _) <- decodeAndReadIType @v+        shamt <- decodeShamt+        writeRegister rd $ r1 `lshlImm` shamt+    SRLI -> do+        (r1, rd, _) <- decodeAndReadIType @v+        shamt <- decodeShamt+        writeRegister rd $ r1 `lshrImm` shamt+    SRAI -> do+        (r1, rd, _) <- decodeAndReadIType @v+        shamt <- decodeShamt+        writeRegister rd $ r1 `ashrImm` shamt
+ lib/LibRISCV/Semantics/RV_I/Default.hs view
@@ -0,0 +1,212 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}++module LibRISCV.Semantics.RV_I.Default where++import Control.Monad.Freer+import LibRISCV.Effects.Decoding.Language (Decoding)+import LibRISCV.Effects.Expressions.Expr+import LibRISCV.Effects.Expressions.Language (ExprEval, unlessExprM, whenExprM)+import LibRISCV.Effects.Logging.Language (LogInstructionFetch)+import LibRISCV.Effects.Operations.Language (Operations (..), Size (..), ebreak, ecall, exception, readPC)+import LibRISCV.Internal.Decoder.Opcodes (RV_I (..))+import LibRISCV.Semantics.Utils++instrSemantics ::+    forall v r.+    ( Member (Operations v) r+    , Member LogInstructionFetch r+    , Member (Decoding v) r+    , Member (ExprEval v) r+    ) =>+    Int ->+    v ->+    RV_I ->+    Eff r ()+instrSemantics width pc =+    let+        fromUInt :: Integer -> Expr v+        fromUInt = FromInt width++        -- False if a given address is not aligned at the four-byte boundary.+        isMisaligned :: Expr v -> Expr v+        isMisaligned addr = (addr `And` fromUInt 0x3) `Uge` fromUInt 1+     in+        \case+            ADDI -> do+                (r1, rd, imm) <- decodeAndReadIType @v+                writeRegister rd $ r1 `addImm` imm+            SLTI -> do+                (r1, rd, imm) <- decodeAndReadIType @v+                let cond = r1 `slt` imm+                writeRegister rd cond+            SLTIU -> do+                (r1, rd, imm) <- decodeAndReadIType @v+                let cond = r1 `ult` imm+                writeRegister rd cond+            ANDI -> do+                (r1, rd, imm) <- decodeAndReadIType @v+                writeRegister rd $ r1 `andImm` imm+            ORI -> do+                (r1, rd, imm) <- decodeAndReadIType @v+                writeRegister rd $ r1 `orImm` imm+            XORI -> do+                (r1, rd, imm) <- decodeAndReadIType @v+                writeRegister rd $ r1 `xorImm` imm+            LUI -> do+                (rd, imm) <- decodeUType @v+                writeRegister rd $ FromImm imm+            AUIPC -> do+                (rd, imm) <- decodeUType+                writeRegister rd $ pc `addImm` imm+            ADD -> do+                (r1, r2, rd) <- decodeAndReadRType @v+                writeRegister rd $ r1 `addImm` r2+            SLT -> do+                (r1, r2, rd) <- decodeAndReadRType+                let cond = r1 `slt` r2 :: Expr v+                writeRegister rd cond+            SLTU -> do+                (r1, r2, rd) <- decodeAndReadRType+                let cond = r1 `ult` r2 :: Expr v+                writeRegister rd cond+            AND -> do+                (r1, r2, rd) <- decodeAndReadRType @v+                writeRegister rd $ r1 `andImm` r2+            OR -> do+                (r1, r2, rd) <- decodeAndReadRType @v+                writeRegister rd $ r1 `orImm` r2+            XOR -> do+                (r1, r2, rd) <- decodeAndReadRType @v+                writeRegister rd $ r1 `xorImm` r2+            SLL -> do+                (r1, r2, rd) <- decodeAndReadRType @v+                writeRegister rd $ FromImm r1 `LShl` regShamt width (FromImm r2)+            SRL -> do+                (r1, r2, rd) <- decodeAndReadRType @v+                writeRegister rd $ FromImm r1 `LShr` regShamt width (FromImm r2)+            SUB -> do+                (r1, r2, rd) <- decodeAndReadRType @v+                writeRegister rd $ r1 `sub` r2+            SRA -> do+                (r1, r2, rd) <- decodeAndReadRType @v+                writeRegister rd $ FromImm r1 `AShr` regShamt width (FromImm r2)+            JAL -> do+                nextInstr <- readPC+                (rd, imm) <- decodeJType++                let newPC = pc `addImm` imm+                writePC newPC+                whenExprM (isMisaligned newPC) $+                    exception pc "misaligned PC"+                writeRegister rd (FromImm nextInstr)+            JALR -> do+                nextInstr <- readPC+                (r1, rd, imm) <- decodeAndReadIType++                let newPC = (r1 `addImm` imm) `And` fromUInt 0xfffffffe+                writePC newPC+                whenExprM (isMisaligned newPC) $+                    exception pc "misaligned PC"+                writeRegister rd $ FromImm nextInstr+            LB -> do+                (r1, rd, imm) <- decodeAndReadIType @v+                byte <- load Byte $ r1 `addImm` imm+                -- TODO: Alignment handling+                writeRegister rd $ sextImm 24 byte+            LBU -> do+                (r1, rd, imm) <- decodeAndReadIType @v+                -- TODO: Alignment handling+                byte <- load Byte $ r1 `addImm` imm+                writeRegister rd $ zextImm 24 byte+            LH -> do+                (r1, rd, imm) <- decodeAndReadIType @v+                -- TODO: Alignment handling+                half <- load Half $ r1 `addImm` imm+                writeRegister rd $ sextImm 16 half+            LHU -> do+                (r1, rd, imm) <- decodeAndReadIType @v+                -- TODO: Alignment handling+                half <- load Half $ r1 `addImm` imm+                writeRegister rd $ zextImm 16 half+            LW -> do+                (r1, rd, imm) <- decodeAndReadIType @v++                -- TODO: Alignment handling+                word <- load Word $ r1 `addImm` imm+                writeRegister rd $ FromImm word+            SB -> do+                (r1, r2, imm) <- decodeAndReadSType @v++                -- TODO: Alignment handling+                store Byte (r1 `addImm` imm) $ FromImm r2+            SH -> do+                (r1, r2, imm) <- decodeAndReadSType @v++                -- TODO: Alignment handling+                store Half (r1 `addImm` imm) $ FromImm r2+            SW -> do+                (r1, r2, imm) <- decodeAndReadSType @v+                -- TODO: Alignment handling+                store Word (r1 `addImm` imm) $ FromImm r2+            BEQ -> do+                (r1, r2, imm) <- decodeAndReadBType++                let addr = pc `add` imm+                whenExprM (r1 `eq` r2) $ do+                    writePC addr+                    whenExprM (isMisaligned addr) $+                        exception pc "misaligned PC"+            BNE -> do+                (r1, r2, imm) <- decodeAndReadBType++                let addr = pc `add` imm+                unlessExprM (r1 `eq` r2) $ do+                    writePC addr+                    whenExprM (isMisaligned addr) $+                        exception pc "misaligned PC"+            BLT -> do+                (r1, r2, imm) <- decodeAndReadBType++                let addr = pc `add` imm+                whenExprM (r1 `slt` r2) $ do+                    writePC addr+                    whenExprM (isMisaligned addr) $+                        exception pc "misaligned PC"+            BLTU -> do+                (r1, r2, imm) <- decodeAndReadBType++                let addr = pc `add` imm+                whenExprM (r1 `ult` r2) $ do+                    writePC @v $ addr+                    whenExprM (isMisaligned addr) $+                        exception pc "misaligned PC"+            BGE -> do+                (r1, r2, imm) <- decodeAndReadBType++                let addr = pc `add` imm+                whenExprM (r1 `sge` r2) $ do+                    writePC addr+                    whenExprM (isMisaligned addr) $+                        exception pc "misaligned PC"+            BGEU -> do+                (r1, r2, imm) <- decodeAndReadBType++                let addr = pc `add` imm+                whenExprM (r1 `uge` r2) $ do+                    writePC addr+                    whenExprM (isMisaligned addr) $+                        exception pc "misaligned PC"+            FENCE -> pure () -- XXX: ignore for now+            ECALL -> ecall pc+            EBREAK -> ebreak pc
+ lib/LibRISCV/Semantics/RV_M/Default.hs view
@@ -0,0 +1,112 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}++module LibRISCV.Semantics.RV_M.Default where++import Control.Monad.Freer+import Data.Function (on)+import LibRISCV.Effects.Decoding.Language (Decoding)+import LibRISCV.Effects.Expressions.Expr+import LibRISCV.Effects.Expressions.Language (ExprEval, ifExprM)+import LibRISCV.Effects.Logging.Language (LogInstructionFetch)+import LibRISCV.Effects.Operations.Language (Operations (..))+import LibRISCV.Internal.Decoder.Opcodes (RV_M (..))+import LibRISCV.Semantics.Utils++instrSemantics ::+    forall v r.+    ( Member (Operations v) r+    , Member LogInstructionFetch r+    , Member (Decoding v) r+    , Member (ExprEval v) r+    ) =>+    Int ->+    RV_M ->+    Eff r ()+instrSemantics width =+    let+        fromUInt :: Integer -> Expr v+        fromUInt = FromInt width++        immEqInt :: v -> Integer -> Expr v+        immEqInt imm int = FromImm imm `Eq` fromUInt int++        mask1 :: Expr v+        mask1 = FromInt width (2 ^ width - 1)++        extract32 :: Int -> Expr v -> Expr v+        extract32 = flip Extract 32++        mostNegative :: Integer+        mostNegative = 2 ^ (width - 1)++        -- Signed division overflow occurs when the most-negative integer is divided by -1.+        sdivOverflow :: v -> v -> Expr v+        sdivOverflow n divisor = (n `immEqInt` mostNegative) `And` (divisor `immEqInt` (-1))+     in+        \case+            MUL -> do+                (r1, r2, rd) <- decodeAndReadRType+                let+                    multRes = (Mul `on` sextImm 32) r1 r2+                    res = extract32 0 multRes+                writeRegister rd res+            MULH -> do+                (r1, r2, rd) <- decodeAndReadRType+                let+                    multRes = (Mul `on` sextImm 32) r1 r2+                    res = extract32 32 multRes+                writeRegister rd res+            MULHU -> do+                (r1, r2, rd) <- decodeAndReadRType+                let+                    multRes = (Mul `on` zextImm 32) r1 r2+                    res = extract32 32 multRes+                writeRegister rd res+            MULHSU -> do+                (r1, r2, rd) <- decodeAndReadRType+                let+                    multRes = sextImm 32 r1 `Mul` zextImm 32 r2+                    res = extract32 32 multRes+                writeRegister rd res+            DIV -> do+                (r1, r2, rd) <- decodeAndReadRType+                ifExprM+                    (r2 `immEqInt` 0)+                    do writeRegister rd mask1+                    do+                        ifExprM+                            (sdivOverflow r1 r2)+                            do writeRegister rd $ FromImm r1+                            do writeRegister rd $ r1 `sdiv` r2+            DIVU -> do+                (r1, r2, rd) <- decodeAndReadRType+                ifExprM+                    (r2 `immEqInt` 0)+                    do writeRegister rd mask1+                    do writeRegister rd $ r1 `udiv` r2+            REM -> do+                (r1, r2, rd) <- decodeAndReadRType+                ifExprM+                    (r2 `immEqInt` 0)+                    do writeRegister rd $ FromImm r1+                    do+                        ifExprM+                            (sdivOverflow r1 r2)+                            do writeRegister rd $ fromUInt 0+                            do writeRegister rd $ r1 `srem` r2+            REMU -> do+                (r1, r2, rd) <- decodeAndReadRType+                ifExprM+                    (r2 `immEqInt` 0)+                    do writeRegister rd $ FromImm r1+                    do writeRegister rd $ r1 `urem` r2
+ lib/LibRISCV/Semantics/Utils.hs view
@@ -0,0 +1,75 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}++module LibRISCV.Semantics.Utils where++import Control.Applicative+import Control.Monad.Freer+import LibRISCV.Effects.Decoding.Language+import LibRISCV.Effects.Expressions.Expr+import LibRISCV.Effects.Expressions.Language+import LibRISCV.Effects.Operations.Language+import qualified LibRISCV.Effects.Operations.Language as Op++-- TODO add newTypes for type safety+-- decode and read register+decodeAndReadIType :: forall v r. (Member (Decoding v) r, Member (Operations v) r) => Eff r (v, v, v)+decodeAndReadIType = liftA3 (,,) (decodeRS1 >>= Op.readRegister) decodeRD decodeImmI++-- decode and read register+decodeAndReadBType :: forall v r. (Member (Decoding v) r, Member (Operations v) r) => Eff r (v, v, v)+decodeAndReadBType = liftA3 (,,) (decodeRS1 >>= Op.readRegister) (decodeRS2 >>= Op.readRegister) decodeImmB++-- decode and read register+decodeAndReadSType :: forall v r. (Member (Decoding v) r, Member (Operations v) r) => Eff r (v, v, v)+decodeAndReadSType = liftA3 (,,) (decodeRS1 >>= Op.readRegister) (decodeRS2 >>= Op.readRegister) decodeImmS++-- decode and read register+decodeAndReadRType :: forall v r. (Member (Decoding v) r, Member (Operations v) r) => Eff r (v, v, v)+decodeAndReadRType = liftA3 (,,) (decodeRS1 >>= Op.readRegister) (decodeRS2 >>= Op.readRegister) decodeRD++-- decode and read register+decodeJType :: forall v r. (Member (Decoding v) r, Member (Operations v) r) => Eff r (v, v)+decodeJType = liftA2 (,) decodeRD decodeImmJ++decodeUType :: forall v r. (Member (Decoding v) r, Member (Operations v) r) => Eff r (v, v)+decodeUType = liftA2 (,) decodeRD decodeImmU++-- | Write to a register in the register file. The function takes a register index and+-- a value which should be written to the register (represented as an 'Expr'). This function+-- is primarly useful to initialize special registers, e.g. setting the stack pointer to a+-- meaningful value at the very beginning of the free monad AST.+writeRegister :: forall v r. (Member (ExprEval v) r, Member (Operations v) r) => v -> Expr v -> Eff r ()+writeRegister reg e = eval e >>= Op.writeRegister reg++-- | Obtain the current value for a register in the register file. The functions takes a register+-- index (encapsulated in an 'Expr') and returns the value of this register.+readRegister :: forall v r. (Member (ExprEval v) r, Member (Operations v) r) => Expr v -> Eff r v+readRegister e = eval e >>= Op.readRegister++-- | Change the current value of the /Program Counter/ (PC). The new value is the only function+-- argument and is represented as an 'Expr'.+writePC :: forall v r. (Member (ExprEval v) r, Member (Operations v) r) => Expr v -> Eff r ()+writePC e = eval e >>= Op.writePC++-- | Load a fixed-size value from memory. The function takes two arguments: The 'Size' of the+-- value to load and the start address of the value in memory (represented as an 'Expr').+load :: forall v r. (Member (ExprEval v) r, Member (Operations v) r) => Size -> Expr v -> Eff r v+load s e = eval e >>= Op.load s++-- | Store a fixed-size value in memory. The arguments are: The 'Size' of the value, the start+-- address where the value should be stored, and the value itself. The latter two are encapuslated+-- in the 'Expr' abstraction.+store :: forall v r. (Member (ExprEval v) r, Member (Operations v) r) => Size -> Expr v -> Expr v -> Eff r ()+store s r e = do+    reg <- eval r+    v <- eval e+    Op.store s reg v
+ libriscv.cabal view
@@ -0,0 +1,141 @@+cabal-version:      2.4+name:               libriscv+version:            0.1.0.0++synopsis:           A versatile, flexible and executable formal model for the RISC-V architecture.+description:+  A formal model for the <https://riscv.org/ RISC-V> /Instruction Set Architecture/ (ISA).+  Contrary to other Haskell RISC-V ISA models, like <https://github.com/GaloisInc/grift GRIFT>+  or <https://github.com/mit-plv/riscv-semantics riscv-semantics>, LibRISCV is specifically+  tailored to the creation of custom ISA interpreters. To this end, it is designed for+  flexibility, allowing a versatile representation of instruction operands. For example,+  instruction operands can be <https://en.wikipedia.org/wiki/Satisfiability_modulo_theories SMT>+  expressions for <https://doi.org/10.48550/arXiv.2404.04132 symbolic execution> of binary code.+  .+  LibRISCV abstractly describes instruction semantics using an /Embedded Domain Specific Language/+  (EDSL) with <https://doi.org/10.1145/2887747.2804319 free(r) monads>. This Haskell library+  is intended to build custom interpreters for this free monad. The entry point for this purpose is+  the 'LibRISCV.Semantics.buildAST' function which obtains the free monad AST based on an entry address.+  The entry address can be obtained from a provided ELF loader implementation, this "Loader" module+  is also responsible for loading binary instructions into a provided memory implementation. Refer to+  provided example interpreters in the <https://github.com/agra-uni-bremen/libriscv GitHub repository>+  for practical usage instruction. More detailed information on LibRISCV and its concepts is also+  available in a <https://doi.org/10.1007/978-3-031-38938-2_2 TFP'23 publication>.++homepage:           https://github.com/agra-uni-bremen/libriscv+bug-reports:        https://github.com/agra-uni-bremen/libriscv/issues+license:            MIT+license-file:       LICENSE.txt+author:             Sören Tempel, Tobias Brandt, and Christoph Lüth+maintainer:         Group of Computer Architecture <riscv@informatik.uni-bremen.de>+copyright:          (c) 2022-2024 University of Bremen+data-files:         data/instr_dict.yaml+category:           Formal Languages++source-repository head+    type: git+    location: https://github.com/agra-uni-bremen/libriscv.git++common opts+    ghc-options:    -Wall -Wno-name-shadowing++library libriscv-internal+    import:          opts++    exposed-modules: LibRISCV.Internal.Decoder.Generator+                   , LibRISCV.Internal.Decoder.Opcodes+                   , LibRISCV.Internal.Decoder.Instruction+                   , LibRISCV.Internal.Decoder.YamlParser++    build-depends:+        base+        , yaml                  >= 0.11.8.0 && <0.12+        , containers            >= 0.6.5.1 && <0.7+        , file-embed            >= 0.0.10 && <0.1+        , template-haskell+        , bv++    hs-source-dirs:   internal+    default-language: Haskell2010++library+    import:          opts++    exposed-modules: LibRISCV+                   , LibRISCV.Loader+                   , LibRISCV.CmdLine+                   , LibRISCV.Effects.Decoding.Language+                   , LibRISCV.Effects.Decoding.Default.Interpreter+                   , LibRISCV.Effects.Operations.Language+                   , LibRISCV.Effects.Operations.Default.Interpreter+                   , LibRISCV.Effects.Operations.Default.Machine.Register+                   , LibRISCV.Effects.Operations.Default.Machine.Memory+                   , LibRISCV.Effects.Logging.Language+                   , LibRISCV.Effects.Logging.Default.Interpreter+                   , LibRISCV.Effects.Expressions.Language+                   , LibRISCV.Effects.Expressions.Default.Interpreter+                   , LibRISCV.Effects.Expressions.Expr+                   , LibRISCV.Semantics+    other-modules: LibRISCV.Effects.Expressions.Type+                   , LibRISCV.Effects.Expressions.Generator+                   , LibRISCV.Semantics.Default+                   , LibRISCV.Semantics.Utils+                   , LibRISCV.Semantics.RV_I.Default+                   , LibRISCV.Semantics.RV32_I.Default+                   , LibRISCV.Semantics.RV_M.Default++    -- LANGUAGE extensions used by modules in this package.+    -- other-extensions:+    build-depends:+        libriscv-internal+        , base                  >= 4.15.0.0 && <4.20.0.0+        , array                ^>= 0.5.4.0+        , filepath             ^>= 1.4.2.1+        , bytestring            >= 0.10.10 && <0.12+        , exceptions           ^>= 0.10.4+        , melf                 ^>= 1.3.0+        , freer-simple         ^>= 1.2.1.2+        , transformers          >= 0.5.6.0 && <0.7+        , optparse-applicative  >= 0.16.1 && <0.19+        , bv                   ^>= 0.5+        , parameterized-utils  ^>= 2.1.6.0+        , extra                 >= 1.7.0 && <1.8+        , template-haskell      >= 2.18.0 && <2.22++    hs-source-dirs:   lib+    default-language: Haskell2010++executable riscv-tiny+    import:               opts+    main-is:              Main.hs+    hs-source-dirs:       app+    default-language:     Haskell2010+    build-depends:+        base+        , libriscv+        , freer-simple+        , optparse-applicative+        , bv++test-suite test+    import: opts+    default-language:+      Haskell2010+    type:+      exitcode-stdio-1.0+    hs-source-dirs: test+    main-is: Main.hs++    other-modules:+      DecoderTest+      ArchStateTest++    build-depends:+      libriscv-internal+      , base+      , libriscv+      , array+      , bytestring+      , bv+      , tasty        >= 1.4.2.3+      , tasty-hunit  >= 0.10.0.3
+ test/ArchStateTest.hs view
@@ -0,0 +1,90 @@+module ArchStateTest where++import Data.Array.IO (IOUArray)+import qualified Data.ByteString.Lazy as BSL+import Data.Word+import LibRISCV (RegIdx (..))+import LibRISCV.Effects.Operations.Default.Machine.Memory+import LibRISCV.Effects.Operations.Default.Machine.Register+import Test.Tasty+import Test.Tasty.HUnit++mkReg :: IO (RegisterFile IOUArray Word32)+mkReg = mkRegFile 0++registerTests :: TestTree+registerTests =+    testGroup+        "RegisterFile Tests"+        [ testCase "Read and write general-puropose register" $ do+            r <- mkReg+            writeRegister r A1 23+            regVal <- readRegister r A1+            assertEqual "" 23 regVal+        , testCase "Write zero register" $ do+            r <- mkReg+            writeRegister r Zero 42+            regVal <- readRegister r Zero+            assertEqual "" 0 regVal+        , testCase "Read and write program counter" $ do+            r <- mkReg+            initVal <- readPC r+            assertEqual "PC is zero initially" 0 initVal++            writePC r 1337+            regVal <- readPC r+            assertEqual "PC can be written" 1337 regVal++            writePC r 0+            regVal <- readPC r+            assertEqual "Zero can be written to PC" 0 regVal+        ]++memoryTests :: TestTree+memoryTests =+    testGroup+        "Memory Tests"+        [ testCase "Create memory and extract its size" $ do+            m <- mkMemory 0x0 512 :: IO (Memory IOUArray Word8)+            memSize m >>= assertEqual "" 512+        , testCase "Store and load byte" $ do+            m <- mkMemory 0x0 512 :: IO (Memory IOUArray Word8)+            storeByte m 0x4 0xab+            loadByte m 0x04 >>= assertEqual "" 0xab+        , testCase "Read uninitialized memory" $ do+            m <- mkMemory 0x0 256 :: IO (Memory IOUArray Word8)++            -- We don't really care what this evaluates to. This returns+            -- undefined values which is fine as long as the memory doesn't+            -- error on an uninitialized memory accesses.+            (loadWord m 128 :: IO Word32) >> pure ()+        , testCase "StoreWord in between" $ do+            m <- mkMemory 0x0 12 :: IO (Memory IOUArray Word8)+            storeWord m 0 (0xffffffff :: Word32)+            storeWord m 4 (0xffffffff :: Word32)+            storeWord m 8 (0xffffffff :: Word32)+            storeWord m 0x2 (0x12345678 :: Word32)+            (loadWord m 0 :: IO Word32) >>= assertEqual "1st word" 0x5678ffff+            (loadWord m 4 :: IO Word32) >>= assertEqual "2nd word" 0xffff1234+            (loadWord m 8 :: IO Word32) >>= assertEqual "3rd word" 0xffffffff+        , testCase "Store and load word" $ do+            m <- mkMemory 0x0 256 :: IO (Memory IOUArray Word8)+            storeWord m 8 (0xdeadbeef :: Word32)+            (loadWord m 8 :: IO Word32) >>= assertEqual "Load entire word" 0xdeadbeef+            loadByte m 8 >>= assertEqual "Load 1st byte" 0xef+            loadByte m 9 >>= assertEqual "Load 2nd byte" 0xbe+            loadByte m 11 >>= assertEqual "Load 4th byte" 0xde+        , testCase "Write ByteString in little endian byteorder" $ do+            m <- mkMemory 0x0 32 :: IO (Memory IOUArray Word8)+            let bs = BSL.pack [0xde, 0xad, 0xbe, 0xef]++            storeByteString fromIntegral m 0x0 bs+            (loadWord m 0x0 :: IO Word32) >>= assertEqual "" 0xefbeadde+        , testCase "Write ByteString with multiple bytes" $ do+            m <- mkMemory 0x0 8 :: IO (Memory IOUArray Word8)+            let bs = BSL.pack [0xde, 0xad, 0xbe, 0xef, 0x12, 0x23, 0x34, 0xff]++            storeByteString fromIntegral m 0x0 bs+            (loadWord m 0x0 :: IO Word32) >>= assertEqual "" 0xefbeadde+            (loadWord m 0x4 :: IO Word32) >>= assertEqual "" 0xff342312+        ]
+ test/DecoderTest.hs view
@@ -0,0 +1,75 @@+module DecoderTest where++import Data.Int+import LibRISCV+import LibRISCV.Internal.Decoder.Instruction+import LibRISCV.Internal.Decoder.Opcodes+import Test.Tasty+import Test.Tasty.HUnit++decoderTests :: TestTree+decoderTests =+    testGroup+        "Decoder Tests"+        [ testCase "Decode ADD instruction" $ do+            let inst = 0x00a605b3++            assertEqual "opcode" (RV_I ADD) $ decode inst+            assertEqual "rd" A1 $ toEnum $ fromIntegral $ mkRd inst+            assertEqual "rs1" A2 $ toEnum $ fromIntegral $ mkRs1 inst+            assertEqual "rs2" A0 $ toEnum $ fromIntegral $ mkRs2 inst+        , testCase "Decode ADDI instruction" $ do+            let inst = 0x02a30293++            assertEqual "opcode" (RV_I ADDI) $ decode inst+            assertEqual "rd" T0 $ toEnum $ fromIntegral $ mkRd inst+            assertEqual "rs1" T1 $ toEnum $ fromIntegral $ mkRs1 inst+            assertEqual "immI" 42 $ immI inst+        , testCase "Decode LW instruction" $ do+            let inst = 0xffc52503++            assertEqual "opcode" (RV_I LW) $ decode inst+            assertEqual "rd" A0 $ toEnum $ fromIntegral $ mkRd inst+            assertEqual "rs1" A0 $ toEnum $ fromIntegral $ mkRs1 inst+            assertEqual "immI" (-4) (fromIntegral (immI inst) :: Int32)+        , testCase "Decode AUIPC instruction" $ do+            let inst = 0x00000297++            assertEqual "opcode" (RV_I AUIPC) $ decode inst+            assertEqual "rd" T0 (toEnum $ fromIntegral (mkRd inst))+            assertEqual "immU" 0 (immU inst)+        , testCase "Decode SW instruction" $ do+            let inst = 0x00102423++            assertEqual "opcode" (RV_I SW) $ decode inst+            assertEqual "rs1" Zero (toEnum $ fromIntegral (mkRs1 inst))+            assertEqual "rs2" RA (toEnum $ fromIntegral (mkRs2 inst))+            assertEqual "immS" 8 $ immS inst+        , testCase "Decode JAL instruction" $ do+            let inst = 0xffdff06f++            assertEqual "opcode" (RV_I JAL) $ decode inst+            assertEqual "rd" Zero (toEnum $ fromIntegral (mkRd inst))+            assertEqual "immJ" (-4) (fromIntegral (immJ inst) :: Int32)+        , testCase "Decode BLT instruction" $ do+            let inst = 0x00b54263++            assertEqual "opcode" (RV_I BLT) $ decode inst+            assertEqual "rs1" A0 (toEnum $ fromIntegral (mkRs1 inst))+            assertEqual "rs2" A1 (toEnum $ fromIntegral (mkRs2 inst))+            assertEqual "immB" 4 $ immB inst+        , testCase "Decode SLLI instruction" $ do+            let inst = 0x00129593++            assertEqual "opcode" (RV32_I SLLI) $ decode inst+            assertEqual "rd" A1 (toEnum $ fromIntegral (mkRd inst))+            assertEqual "rs1" T0 (toEnum $ fromIntegral (mkRs1 inst))+            assertEqual "shamt" 01 $ mkShamt inst+        , testCase "Decode SLLI instruction" $ do+            let inst = 0x01f2d693++            assertEqual "opcode" (RV32_I SRLI) $ decode inst+            assertEqual "rd" A3 (toEnum $ fromIntegral (mkRd inst))+            assertEqual "rs1" T0 (toEnum $ fromIntegral (mkRs1 inst))+            assertEqual "shamt" 31 $ mkShamt inst+        ]
+ test/Main.hs view
@@ -0,0 +1,15 @@+import ArchStateTest+import DecoderTest+import Test.Tasty++main :: IO ()+main = defaultMain tests++tests :: TestTree+tests =+    testGroup+        "Tests"+        [ decoderTests+        , registerTests+        , memoryTests+        ]