From 954f077fa1f9fd4741b57b2d27eb11d632583a40 Mon Sep 17 00:00:00 2001 From: Martijn Bastiaan Date: Tue, 5 Mar 2024 15:15:01 +0100 Subject: [PATCH] Add `elf-to-hex` Writes out an ELF file into 2x4 files readable by Verilog's `readmemh`. This is useful when implementing your memories in Verilog. --- clash-vexriscv-sim/app/ElfToHex.hs | 88 +++++++++++++++++++++ clash-vexriscv-sim/clash-vexriscv-sim.cabal | 13 +++ 2 files changed, 101 insertions(+) create mode 100644 clash-vexriscv-sim/app/ElfToHex.hs diff --git a/clash-vexriscv-sim/app/ElfToHex.hs b/clash-vexriscv-sim/app/ElfToHex.hs new file mode 100644 index 0000000..00bd3b3 --- /dev/null +++ b/clash-vexriscv-sim/app/ElfToHex.hs @@ -0,0 +1,88 @@ +-- | Convert an ELF file to a set of @.mem@ files, suitable for use with +-- Verilog simulators. Use in combination with @readmemh@ in Verilog. +module Main where + +import Prelude + +import Control.Concurrent.Async (mapConcurrently_) +import Control.Monad (forM_) +import Data.IntMap (IntMap, findWithDefault) +import Data.Tuple.Extra (uncurry3) +import System.Environment (getArgs) +import System.Exit (die) +import System.IO (withFile, IOMode(WriteMode), hPutStrLn) +import Text.Printf (printf) +import Text.Read (readMaybe) +import Utils.ReadElf (readElfFromMemory) + +import qualified Data.ByteString as BS +import qualified Clash.Prelude as C + +iMEM_START :: Int +iMEM_START = 0x20000000 + +dMEM_START :: Int +dMEM_START = 0x40000000 + +-- | Like 'zipWithM_', but execute concurrently +zipWithConcurrently_ :: (a -> b -> IO ()) -> [a] -> [b] -> IO () +zipWithConcurrently_ f xs ys = mapConcurrently_ (uncurry f) (zip xs ys) + +-- | Like 'zipWith3M_', but execute concurrently +zipWith3Concurrently_ :: (a -> b -> c -> IO ()) -> [a] -> [b] -> [c] -> IO () +zipWith3Concurrently_ f xs ys zs = mapConcurrently_ (uncurry3 f) (zip3 xs ys zs) + +-- | Convenience function to get data from a memory map. If a memory address is +-- not found, return 0. +getData :: Num a => IntMap a -> Int -> a +getData mem addr = findWithDefault 0 addr mem + +-- | Generate the addresses for the four memory banks, given the total size in +-- bytes, and the start address. +getAddrs :: Int -> Int -> ([Int], [Int], [Int], [Int]) +getAddrs size start = + ( [start + 0, start + 4 .. start + size - 1] + , [start + 1, start + 5 .. start + size - 1] + , [start + 2, start + 6 .. start + size - 1] + , [start + 3, start + 7 .. start + size - 1] ) + +-- | Write a single @.mem@ file +writeByteMem :: IntMap (C.BitVector 8) -> FilePath -> [Int] -> IO () +writeByteMem mem path addrs = do + putStrLn $ "Writing " <> path + withFile path WriteMode $ \h -> + forM_ addrs $ \addr -> do + hPutStrLn h (toHex (getData mem addr)) + +-- | Write four @.mem@ files +writeMem :: Int -> IntMap (C.BitVector 8) -> FilePath -> Int -> IO () +writeMem size mem prefix start = do + let (addrs0, addrs1, addrs2, addrs3) = getAddrs size start + + zipWithConcurrently_ + (writeByteMem mem) + [prefix <> show n <> ".mem" | n <- [(0::Int)..]] + [addrs0, addrs1, addrs2, addrs3] + +-- | Print a byte as a hex string of the form 0x00. +toHex :: C.BitVector 8 -> String +toHex = printf "0x%02x" . toInteger + +main :: IO () +main = do + let usage = "Usage: elf-to-hex " + + (sizeStr, elfFile) <- getArgs >>= \case + [sizeStr, elfFile] -> pure (sizeStr, elfFile) + args -> die $ usage <> "\n\n" <> "Expected 2 arguments, but got " <> show (length args) + + size <- case readMaybe @Int sizeStr of + Just x -> pure x + Nothing -> die $ usage <> "\n\n" <> " must be an integer, but got: " <> sizeStr + (_addr, iMem, dMem) <- readElfFromMemory <$> BS.readFile elfFile + + zipWith3Concurrently_ + (writeMem size) + [iMem, dMem] + ["imem", "dmem"] + [iMEM_START, dMEM_START] diff --git a/clash-vexriscv-sim/clash-vexriscv-sim.cabal b/clash-vexriscv-sim/clash-vexriscv-sim.cabal index ee6504f..9a028ad 100644 --- a/clash-vexriscv-sim/clash-vexriscv-sim.cabal +++ b/clash-vexriscv-sim/clash-vexriscv-sim.cabal @@ -115,6 +115,19 @@ executable clash-vexriscv-bin containers, directory, +executable elf-to-hex + import: common-options + main-is: ElfToHex.hs + hs-source-dirs: app + default-language: Haskell2010 + ghc-options: -threaded -rtsopts -with-rtsopts=-N + build-depends: + async, + base, + bytestring, + clash-vexriscv-sim, + extra, + test-suite unittests import: common-options default-language: Haskell2010