Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add JTAG support to the core
Browse files Browse the repository at this point in the history
hydrolarus authored and martijnbastiaan committed Mar 5, 2024
1 parent 14620c8 commit 3a57205
Showing 35 changed files with 3,823 additions and 11,566 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -65,3 +65,6 @@ log
vivado_*
tight_setup_hold_pins.txt
.Xil

# Verilator debug output
simulation_dump.vcd
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -13,5 +13,6 @@ opt-level = "z"
[workspace]
members = [
"clash-vexriscv-sim/test-programs",
"debug-test",
]
resolver = "2"
1 change: 0 additions & 1 deletion cabal.project
Original file line number Diff line number Diff line change
@@ -7,7 +7,6 @@ packages:
clash-vexriscv-sim/

write-ghc-environment-files: always

tests: True


88 changes: 88 additions & 0 deletions clash-vexriscv-sim/app/ElfToHex.hs
Original file line number Diff line number Diff line change
@@ -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 <size> <elf-file>"

(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" <> "<size> 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]
11 changes: 7 additions & 4 deletions clash-vexriscv-sim/app/HdlTest.hs
Original file line number Diff line number Diff line change
@@ -10,10 +10,13 @@ import VexRiscv
circuit ::
"CLK" ::: Clock System ->
"RST" ::: Reset System ->
"INPUT" ::: Signal System Input ->
"OUTPUT" ::: Signal System Output
circuit clk rst input =
withClockResetEnable clk rst enableGen vexRiscv input
"CPU_COMB_INPUT" ::: Signal System CpuIn ->
"JTAG_IN_" ::: Signal System JtagIn ->
"" :::
( "CPU_OUTPUT" ::: Signal System CpuOut
, "JTAG_OUT_" ::: Signal System JtagOut)
circuit clk rst input jtagIn =
vexRiscv clk rst input jtagIn

makeTopEntity 'circuit

62 changes: 46 additions & 16 deletions clash-vexriscv-sim/app/VexRiscvSimulation.hs
Original file line number Diff line number Diff line change
@@ -5,23 +5,26 @@
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE GADTs #-}

{-# OPTIONS_GHC -fconstraint-solver-iterations=10 #-}

import Clash.Prelude

import Protocols.Wishbone
import VexRiscv (Output(iBusWbM2S, dBusWbM2S))
import VexRiscv (CpuOut(iBusWbM2S, dBusWbM2S))

import qualified Data.List as L

import Control.Monad (forM_, when)
import Data.Char (chr)
import Data.Maybe (catMaybes)
import Data.Maybe (catMaybes, fromMaybe)
import System.Environment (getArgs)
import System.IO (putChar, hFlush, stdout)
import Text.Printf (printf)


import Utils.ProgramLoad (loadProgram)
import Utils.Cpu (cpu)
import System.Exit (exitFailure)

--------------------------------------
--
@@ -59,9 +62,9 @@ debugConfig =
--
{-
InspectBusses
50
0
(Just 300)
0
(Just 100)
True
True
-- -}
@@ -77,37 +80,58 @@ main = do
withClockResetEnable @System clockGen resetGen enableGen $
loadProgram @System elfFile

let cpuOut@(unbundle -> (_circuit, writes, iBus, dBus)) =
let cpuOut@(unbundle -> (_circuit, writes, _iBus, _dBus)) =
withClockResetEnable @System clockGen (resetGenN (SNat @2)) enableGen $
bundle (cpu iMem dMem)
let (circ, writes1, iBus, dBus) = cpu (Just 7894) iMem dMem
dBus' = register Nothing dBus
in bundle (circ, writes1, iBus, dBus')

case debugConfig of
RunCharacterDevice ->
forM_ (sample_lazy @System (bundle (dBus, iBus, writes))) $ \(dS2M, iS2M, write) -> do
when (err dS2M) $
putStrLn "D-bus ERR reply"
forM_ (sample_lazy @System (bundle (register @System (unpack 0) cpuOut, cpuOut))) $
\((_out, write, dS2M0, iS2M0), (out1, _write, _dS2M, _iS2M)) -> do
let
iS2M = fromMaybe emptyWishboneS2M iS2M0
dS2M = fromMaybe emptyWishboneS2M dS2M0

when (err dS2M) $ do
let dBusM2S = dBusWbM2S out1
let dAddr = toInteger (addr dBusM2S) -- `shiftL` 2
-- printf "D-bus ERR reply % 8X (% 8X)\n" (toInteger $ dAddr `shiftL` 2) (toInteger dAddr)
-- exitFailure
pure ()

when (err iS2M) $ do
let iBusM2S = iBusWbM2S out1
let iAddr = toInteger (addr iBusM2S) -- `shiftL` 2
-- printf "I-bus ERR reply % 8X (% 8X)\n" (toInteger $ iAddr `shiftL` 2) (toInteger iAddr)
-- printf "%s\n" (showX iBusM2S)
-- exitFailure
pure ()

when (err iS2M) $
putStrLn "I-bus ERR reply"

case write of
Just (address, value) | address == 0x0000_1000 -> do
let (_ :: BitVector 24, b :: BitVector 8) = unpack value
putChar $ chr (fromEnum b)
hFlush stdout
pure ()
_ -> pure ()
-- performPrintsToStdout 0x0000_1000 (sample_lazy $ bitCoerce <$> writes)
InspectBusses initCycles uninteresting interesting iEnabled dEnabled -> do

let skipTotal = initCycles + uninteresting

let sampled = case interesting of
Nothing -> L.zip [0 ..] $ sample_lazy @System cpuOut
Just nInteresting ->
let total = initCycles + uninteresting + nInteresting in L.zip [0 ..] $ L.take total $ sample_lazy @System cpuOut
let total = initCycles + uninteresting + nInteresting in
L.zip [0 ..] $ L.take total $ sample_lazy @System cpuOut

forM_ sampled $ \(i, (out, _, iBusS2M, dBusS2M)) -> do
let doPrint = i >= skipTotal
forM_ sampled $ \(i, (out, _, iBusS2M0, dBusS2M0)) -> do
let
iBusS2M = fromMaybe emptyWishboneS2M iBusS2M0
dBusS2M = fromMaybe emptyWishboneS2M dBusS2M0
doPrint = i >= skipTotal

-- I-bus interactions

@@ -143,6 +167,9 @@ main = do
<> ")"
putStrLn $ " - iS2M: " <> iResp <> " - " <> iRespData

when (err iBusS2M)
exitFailure

-- D-bus interactions

when (doPrint && dEnabled) $ do
@@ -192,6 +219,9 @@ main = do
<> writeDat
<> "> - "
putStrLn $ "dS2M: " <> dResp <> dRespData

when (err dBusS2M)
exitFailure
InspectWrites ->
forM_ (catMaybes $ sample_lazy @System writes) $ \(address, value) -> do
printf "W: % 8X <- % 8X\n" (toInteger address) (toInteger value)
13 changes: 13 additions & 0 deletions clash-vexriscv-sim/clash-vexriscv-sim.cabal
Original file line number Diff line number Diff line change
@@ -100,6 +100,19 @@ executable hdl-test
clash-vexriscv,
clash-vexriscv-sim,

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,

executable clash-vexriscv-bin
import: common-options
main-is: VexRiscvSimulation.hs
39 changes: 31 additions & 8 deletions clash-vexriscv-sim/src/Utils/Cpu.hs
Original file line number Diff line number Diff line change
@@ -5,19 +5,25 @@
{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE RecordWildCards #-}

{-# OPTIONS_GHC -Wno-orphans #-}

module Utils.Cpu where

import Clash.Prelude

import Protocols.Wishbone
import VexRiscv
import VexRiscv.JtagTcpBridge as JTag
import VexRiscv.VecToTuple (vecToTuple)

import GHC.Stack (HasCallStack)

import Utils.ProgramLoad (Memory)
import Utils.Interconnect (interconnectTwo)

createDomain vXilinxSystem{vName="Basic50", vPeriod= hzToPeriod 50_000_000}

{-
Address space
@@ -27,37 +33,54 @@ Address space
-}
cpu ::
(HasCallStack, HiddenClockResetEnable dom) =>
Maybe Integer ->
Memory dom ->
Memory dom ->
( Signal dom Output,
( Signal dom CpuOut,
-- writes
Signal dom (Maybe (BitVector 32, BitVector 32)),
-- iBus responses
Signal dom (WishboneS2M (BitVector 32)),
Signal dom (Maybe (WishboneS2M (BitVector 32))),
-- dBus responses
Signal dom (WishboneS2M (BitVector 32))
Signal dom (Maybe (WishboneS2M (BitVector 32)))
)
cpu jtagPort bootIMem bootDMem =
( output
, writes
, mux validI (Just <$> iS2M) (pure Nothing)
, mux validD (Just <$> dS2M) (pure Nothing)
)
cpu bootIMem bootDMem = (output, writes, iS2M, dS2M)
where
output = vexRiscv input
(output, jtagOut) = vexRiscv hasClock hasReset input jtagIn

jtagIn = case jtagPort of
Just port -> vexrJtagBridge (fromInteger port) jtagOut
Nothing -> pure JTag.defaultIn
-- (unbundle -> (jtagIn', _debugReset)) = unsafePerformIO $ jtagTcpBridge' 7894 hasReset (jtagOut <$> output)

dM2S = dBusWbM2S <$> output
validD = fmap busCycle dM2S .&&. fmap strobe dM2S

iM2S = unBusAddr . iBusWbM2S <$> output
validI = fmap busCycle iM2S .&&. fmap strobe iM2S

iS2M = bootIMem (mapAddr (\x -> x - 0x2000_0000) <$> iM2S)
iS2M = bootIMem (mapAddr (\x -> -- trace (printf "I-addr = % 8X (% 8X)\n" (toInteger $ x - 0x2000_0000) (toInteger x))
x - 0x2000_0000) <$> iM2S)

dummy = dummyWb

dummyS2M = dummy dummyM2S
bootDS2M = bootDMem bootDM2S

(dS2M, vecToTuple . unbundle -> (dummyM2S, bootDM2S)) = interconnectTwo
(unBusAddr <$> dM2S)
((\x ->
-- trace (printf "DBUS %08X" (toInteger (addr x)))
x) <$> (unBusAddr <$> dM2S))
((0x0000_0000, dummyS2M) :> (0x4000_0000, bootDS2M) :> Nil)

input =
( \iBus dBus ->
Input
CpuIn
{ timerInterrupt = low,
externalInterrupt = low,
softwareInterrupt = low,
9 changes: 6 additions & 3 deletions clash-vexriscv-sim/src/Utils/Interconnect.hs
Original file line number Diff line number Diff line change
@@ -35,9 +35,12 @@ interconnectTwo m2s ((aAddr', aS2M') :> (bAddr', bS2M') :> Nil) =
Vec 2 (WishboneM2S 32 4 (BitVector 32))
)
go m@WishboneM2S{..} aAddr aS2M bAddr bS2M
| not (busCycle && strobe) = (emptyWishboneS2M, m :> m :> Nil)
| addr >= bAddr = (bS2M, emptyWishboneM2S :> m { addr = addr - bAddr } :> Nil)
| addr >= aAddr = (aS2M, m { addr = addr - aAddr } :> emptyWishboneM2S :> Nil)
| not (busCycle && strobe) =
(emptyWishboneS2M, m :> m :> Nil)
| addr >= bAddr =
(bS2M, emptyWishboneM2S :> m { addr = addr - bAddr } :> Nil)
| addr >= aAddr =
(aS2M, m { addr = addr - aAddr } :> emptyWishboneM2S :> Nil)
| otherwise =
( emptyWishboneS2M { err = True },
emptyWishboneM2S :> emptyWishboneM2S :> Nil
2 changes: 1 addition & 1 deletion clash-vexriscv-sim/tests/tests.hs
Original file line number Diff line number Diff line change
@@ -39,7 +39,7 @@ runProgramExpect act n expected = withSystemTempFile "ELF" $ \fp _ -> do

let _all@(unbundle -> (_circuit, writes, _iBus, _dBus)) =
withClockResetEnable @System clockGen (resetGenN (SNat @2)) enableGen $
bundle (cpu iMem dMem)
bundle (cpu Nothing iMem dMem)

let output = L.take (BS.length expected) $
flip mapMaybe (sampleN_lazy n writes) $ \case
23 changes: 18 additions & 5 deletions clash-vexriscv/Makefile
Original file line number Diff line number Diff line change
@@ -7,13 +7,12 @@ OUT_DIR = build_out_dir
VERILATOR_DIR = $(OUT_DIR)/verilator_output
FFI_DIR = src/ffi


VERILATOR_FLAGS = -CFLAGS '-O3 -fPIC' -Wno-fatal +1364-2001ext+v
VERILATOR_FLAGS = -CFLAGS '-O3 -fPIC' -Wno-fatal +1364-2001ext+v --trace

VERILATOR_CFLAGS = $(shell pkg-config --cflags verilator)
FFI_CPPFLAGS = $(VERILATOR_CFLAGS) -fPIC -O3 -I$(VERILATOR_DIR)

all: $(OUT_DIR)/libVexRiscvFFI.a
all: $(OUT_DIR)/libVexRiscvFFI.a # $(OUT_DIR)/libVexRiscvFFI.so

clean:
rm $(VERILATOR_DIR) -rf
@@ -23,6 +22,8 @@ clean:
$(CPU_DIR)/VexRiscv.v: $(CPU_DIR)/src/main/scala/example/ExampleCpu.scala
cd $(CPU_DIR); sbt "runMain example.ExampleCpu"
cd $(CPU_DIR); sed -i -E '/\/\/ Git hash :.*$$/d' VexRiscv.v
# cd $(CPU_DIR); sed -i 's/module VexRiscv/module VexRiscvInner/g' VexRiscv.v
# cd $(CPU_DIR); cat ../example-cpu/VexRiscvWrapped.v >> VexRiscv.v

$(OUT_DIR)/VexRiscv.v: $(CPU_DIR)/VexRiscv.v
mkdir -p $(OUT_DIR)
@@ -37,6 +38,10 @@ $(VERILATOR_DIR)/VVexRiscv__ALL.a: $(VERILATOR_DIR)/VVexRiscv.mk
$(OUT_DIR)/impl.o: $(FFI_DIR)/impl.cpp $(FFI_DIR)/interface.h
$(CXX) $(FFI_CPPFLAGS) -c $(FFI_DIR)/impl.cpp -o $(OUT_DIR)/impl.o

$(OUT_DIR)/verilated_vcd_c.o: $(shell pkg-config --variable=includedir verilator)/verilated_vcd_c.cpp
$(CXX) $(FFI_CPPFLAGS) -c $(shell pkg-config --variable=includedir verilator)/verilated_vcd_c.cpp -o $(OUT_DIR)/verilated_vcd_c.o


$(OUT_DIR)/verilated.o: $(shell pkg-config --variable=includedir verilator)/verilated.cpp
$(CXX) $(FFI_CPPFLAGS) -c $(shell pkg-config --variable=includedir verilator)/verilated.cpp -o $(OUT_DIR)/verilated.o

@@ -46,11 +51,19 @@ $(OUT_DIR)/verilated_threads.o: $(shell pkg-config --variable=includedir verilat
$(OUT_DIR)/VVexRiscv__ALL.a: $(VERILATOR_DIR)/VVexRiscv__ALL.a
cp $(VERILATOR_DIR)/VVexRiscv__ALL.a $(OUT_DIR)/VVexRiscv__ALL.a

$(OUT_DIR)/libVexRiscvFFI.a: $(OUT_DIR)/VVexRiscv__ALL.a $(OUT_DIR)/impl.o $(OUT_DIR)/verilated.o $(OUT_DIR)/verilated_threads.o
$(OUT_DIR)/libVexRiscvFFI.a: $(OUT_DIR)/VVexRiscv__ALL.a $(OUT_DIR)/impl.o $(OUT_DIR)/verilated.o $(OUT_DIR)/verilated_threads.o $(OUT_DIR)/verilated_vcd_c.o
rm -f $(OUT_DIR)/libVexRiscvFFI.a
cp $(OUT_DIR)/VVexRiscv__ALL.a $(OUT_DIR)/libVexRiscvFFI.a
ar r \
$(OUT_DIR)/libVexRiscvFFI.a \
$(OUT_DIR)/impl.o \
$(OUT_DIR)/verilated.o \
$(OUT_DIR)/verilated_threads.o
$(OUT_DIR)/verilated_threads.o \
$(OUT_DIR)/verilated_vcd_c.o

$(OUT_DIR)/libVexRiscvFFI.so: $(OUT_DIR)/libVexRiscvFFI.a
rm -f $(OUT_DIR)/libVexRiscvFFI.so
$(CXX) -shared -o $(OUT_DIR)/libVexRiscvFFI.so \
-Wl,--whole-archive \
$(OUT_DIR)/libVexRiscvFFI.a \
-Wl,--no-whole-archive
2 changes: 2 additions & 0 deletions clash-vexriscv/clash-vexriscv.cabal
Original file line number Diff line number Diff line change
@@ -104,6 +104,7 @@ library
VexRiscv
VexRiscv.ClockTicks
VexRiscv.FFI
VexRiscv.JtagTcpBridge
VexRiscv.TH
VexRiscv.VecToTuple

@@ -116,6 +117,7 @@ library
directory >= 1.3 && < 1.4,
filepath,
Glob,
network,
process >= 1.6 && < 1.8,
string-interpolate,
tagged,
1 change: 1 addition & 0 deletions clash-vexriscv/example-cpu/ExampleCpu.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
debug: !!vexriscv.DebugReport {hardwareBreakpointCount: 0}
13,732 changes: 2,306 additions & 11,426 deletions clash-vexriscv/example-cpu/VexRiscv.v

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions clash-vexriscv/example-cpu/VexRiscvWithDebug.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
debug: !!vexriscv.DebugReport {hardwareBreakpointCount: 0}
260 changes: 260 additions & 0 deletions clash-vexriscv/example-cpu/VexRiscvWrapped.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
`timescale 1ns/1ps

module VexRiscv (
input timerInterrupt,
input externalInterrupt,
input softwareInterrupt,
output debug_resetOut,
output iBusWishbone_CYC,
output iBusWishbone_STB,
input iBusWishbone_ACK,
output iBusWishbone_WE,
output [29:0] iBusWishbone_ADR,
input [31:0] iBusWishbone_DAT_MISO,
output [31:0] iBusWishbone_DAT_MOSI,
output [3:0] iBusWishbone_SEL,
input iBusWishbone_ERR,
output [2:0] iBusWishbone_CTI,
output [1:0] iBusWishbone_BTE,
output dBusWishbone_CYC,
output dBusWishbone_STB,
input dBusWishbone_ACK,
output dBusWishbone_WE,
output [29:0] dBusWishbone_ADR,
input [31:0] dBusWishbone_DAT_MISO,
output [31:0] dBusWishbone_DAT_MOSI,
output reg [3:0] dBusWishbone_SEL,
input dBusWishbone_ERR,
output [2:0] dBusWishbone_CTI,
output [1:0] dBusWishbone_BTE,
input jtag_tms,
input jtag_tdi,
output jtag_tdo,
input jtag_tck,
input clk,
input reset
);
initial begin
// Specify the dump file name
$dumpfile("simulation_dump.vcd");

// Dump all signals to the VCD file
$dumpvars(1, VexRiscv);
end

wire i_iBusWishbone_ACK;
reg [31:0] i_iBusWishbone_DAT_MISO;
wire i_iBusWishbone_ERR = 1'b0;

wire i_dBusWishbone_ACK;
reg [31:0] i_dBusWishbone_DAT_MISO;
wire i_dBusWishbone_ERR = 1'b0;

reg reset_cpu;
wire reqCpuReset;

// Handle reset logic in Verilog instead of Haskell
assign debug_resetOut = 1'b0;
always @(posedge clk) begin
reset_cpu <= reset || reqCpuReset;
end

// Memory parameters
localparam dmem_lower = 'h40000000 / 4;
localparam imem_lower = 'h20000000 / 4;
localparam mem_size_bytes = 1048576; // 1 MiB
localparam mem_size_words = mem_size_bytes / 4;
localparam mem_addr_bits = 18; // log2(mem_size_words)
localparam dmem_upper = dmem_lower + mem_size_words;
localparam imem_upper = imem_lower + mem_size_words;

//============================================================
// Data memory
//============================================================
reg [7:0] dmem0[0:mem_size_words-1];
reg [7:0] dmem1[0:mem_size_words-1];
reg [7:0] dmem2[0:mem_size_words-1];
reg [7:0] dmem3[0:mem_size_words-1];

initial begin
$readmemh("dmem0.mem", dmem0);
$readmemh("dmem1.mem", dmem1);
$readmemh("dmem2.mem", dmem2);
$readmemh("dmem3.mem", dmem3);
end

// A commmand is valid on CYC and STB, and the response is valid on the next cycle
// provided that CYC and STB are still asserted.
wire dBus_cmd_valid;
reg prev_dBus_cmd_valid = 1'b0;
always @(posedge clk) begin
prev_dBus_cmd_valid <= dBus_cmd_valid;
end

reg dBus_rsp = 1'b0;
assign dBus_cmd_valid = dBusWishbone_CYC && dBusWishbone_STB && !prev_dBus_cmd_valid;
assign i_dBusWishbone_ACK = dBus_rsp && !i_dBusWishbone_ERR;
assign i_dBusWishbone_ERR = !(valid_data_address_d || valid_instr_address_d);
wire [mem_addr_bits-1:0] dmem_addr;
assign dmem_addr = dBusWishbone_ADR[mem_addr_bits-1:0];

wire valid_data_address_d, valid_instr_address_d;
assign valid_data_address_d = dmem_lower <= dBusWishbone_ADR && dBusWishbone_ADR < dmem_upper;
assign valid_instr_address_d = imem_lower <= dBusWishbone_ADR && dBusWishbone_ADR < imem_upper;

always @(posedge clk) begin
i_dBusWishbone_DAT_MISO[ 7: 0] <= 8'b00000000;
i_dBusWishbone_DAT_MISO[15: 8] <= 8'b00000000;
i_dBusWishbone_DAT_MISO[23:16] <= 8'b00000000;
i_dBusWishbone_DAT_MISO[31:24] <= 8'b00000000;
dBus_rsp <= dBus_cmd_valid;

if (dBus_cmd_valid) begin
if (valid_data_address_d) begin
// DAT
i_dBusWishbone_DAT_MISO[ 7: 0] <= dmem0[dmem_addr];
i_dBusWishbone_DAT_MISO[15: 8] <= dmem1[dmem_addr];
i_dBusWishbone_DAT_MISO[23:16] <= dmem2[dmem_addr];
i_dBusWishbone_DAT_MISO[31:24] <= dmem3[dmem_addr];

if (dBusWishbone_WE) begin
if (dBusWishbone_SEL[0]) begin
dmem0[dmem_addr] <= dBusWishbone_DAT_MOSI[ 7: 0];
i_dBusWishbone_DAT_MISO[ 7: 0] <= dBusWishbone_DAT_MOSI[ 7: 0];
end

if (dBusWishbone_SEL[1]) begin
dmem1[dmem_addr] <= dBusWishbone_DAT_MOSI[15: 8];
i_dBusWishbone_DAT_MISO[15: 8] <= dBusWishbone_DAT_MOSI[15: 8];
end

if (dBusWishbone_SEL[2]) begin
dmem2[dmem_addr] <= dBusWishbone_DAT_MOSI[23:16];
i_dBusWishbone_DAT_MISO[23:16] <= dBusWishbone_DAT_MOSI[23:16];
end

if (dBusWishbone_SEL[3]) begin
dmem3[dmem_addr] <= dBusWishbone_DAT_MOSI[31:24];
i_dBusWishbone_DAT_MISO[31:24] <= dBusWishbone_DAT_MOSI[31:24];
end
end

else
if (valid_instr_address_d) begin
// INSTR
i_dBusWishbone_DAT_MISO[ 7: 0] <= imem0[dmem_addr];
i_dBusWishbone_DAT_MISO[15: 8] <= imem1[dmem_addr];
i_dBusWishbone_DAT_MISO[23:16] <= imem2[dmem_addr];
i_dBusWishbone_DAT_MISO[31:24] <= imem3[dmem_addr];

if (dBusWishbone_WE) begin
if (iBusWishbone_SEL[0]) begin
imem0[dmem_addr] <= dBusWishbone_DAT_MOSI[ 7: 0];
i_dBusWishbone_DAT_MISO[ 7: 0] <= dBusWishbone_DAT_MOSI[ 7: 0];
end

if (iBusWishbone_SEL[1]) begin
imem1[dmem_addr] <= dBusWishbone_DAT_MOSI[15: 8];
i_dBusWishbone_DAT_MISO[15: 8] <= dBusWishbone_DAT_MOSI[15: 8];
end

if (iBusWishbone_SEL[2]) begin
imem2[dmem_addr] <= dBusWishbone_DAT_MOSI[23:16];
i_dBusWishbone_DAT_MISO[23:16] <= dBusWishbone_DAT_MOSI[23:16];
end

if (iBusWishbone_SEL[3]) begin
imem3[dmem_addr] <= dBusWishbone_DAT_MOSI[31:24];
i_dBusWishbone_DAT_MISO[31:24] <= dBusWishbone_DAT_MOSI[31:24];
end
end
end
end
end
end

//============================================================
// Instruction memory
//============================================================
reg [7:0] imem0[0:mem_size_words-1];
reg [7:0] imem1[0:mem_size_words-1];
reg [7:0] imem2[0:mem_size_words-1];
reg [7:0] imem3[0:mem_size_words-1];

initial begin
$readmemh("imem0.mem", imem0);
$readmemh("imem1.mem", imem1);
$readmemh("imem2.mem", imem2);
$readmemh("imem3.mem", imem3);
end

// A commmand is valid on CYC and STB, and the response is valid on the next cycle
// provided that CYC and STB are still asserted.
wire iBus_cmd_valid;
reg prev_iBus_cmd_valid;
reg prev_iBus_rsp_valid;
always @(posedge clk) begin
prev_iBus_cmd_valid <= iBus_cmd_valid;
prev_iBus_rsp_valid <= iBus_rsp_valid;
end

wire valid_instr_address_i;
assign valid_instr_address_i = imem_lower <= iBusWishbone_ADR && iBusWishbone_ADR < imem_upper;

assign iBus_cmd_valid = iBusWishbone_CYC && iBusWishbone_STB;
wire iBus_rsp_valid;
assign iBus_rsp_valid = prev_iBus_cmd_valid && iBus_cmd_valid && !prev_iBus_rsp_valid;
assign i_iBusWishbone_ACK = iBus_rsp_valid && valid_instr_address_i;
assign i_iBusWishbone_ERR = !valid_instr_address_i;
wire [mem_addr_bits-1:0] imem_addr;
assign imem_addr = iBusWishbone_ADR[mem_addr_bits-1:0];

always @(posedge clk) begin
i_iBusWishbone_DAT_MISO[ 7: 0] <= imem0[imem_addr];
i_iBusWishbone_DAT_MISO[15: 8] <= imem1[imem_addr];
i_iBusWishbone_DAT_MISO[23:16] <= imem2[imem_addr];
i_iBusWishbone_DAT_MISO[31:24] <= imem3[imem_addr];
end

//============================================================
// CPU instantiation
//============================================================
VexRiscvInner VexRiscvInner
( .timerInterrupt (timerInterrupt)
, .externalInterrupt (externalInterrupt)
, .softwareInterrupt (softwareInterrupt)
, .debug_resetOut (reqCpuReset)

, .iBusWishbone_CYC (iBusWishbone_CYC) // o
, .iBusWishbone_STB (iBusWishbone_STB) // o
, .iBusWishbone_ACK (i_iBusWishbone_ACK) // i
, .iBusWishbone_WE (iBusWishbone_WE) // o
, .iBusWishbone_ADR (iBusWishbone_ADR) // o
, .iBusWishbone_DAT_MISO (i_iBusWishbone_DAT_MISO) // i
, .iBusWishbone_DAT_MOSI (iBusWishbone_DAT_MOSI) // o
, .iBusWishbone_SEL (iBusWishbone_SEL) // o
, .iBusWishbone_ERR (i_iBusWishbone_ERR) // i
, .iBusWishbone_CTI (iBusWishbone_CTI) // o
, .iBusWishbone_BTE (iBusWishbone_BTE) // o

, .dBusWishbone_CYC (dBusWishbone_CYC) // o
, .dBusWishbone_STB (dBusWishbone_STB) // o
, .dBusWishbone_ACK (i_dBusWishbone_ACK) // i
, .dBusWishbone_WE (dBusWishbone_WE) // o
, .dBusWishbone_ADR (dBusWishbone_ADR) // o
, .dBusWishbone_DAT_MISO (i_dBusWishbone_DAT_MISO) // i
, .dBusWishbone_DAT_MOSI (dBusWishbone_DAT_MOSI) // o
, .dBusWishbone_SEL (dBusWishbone_SEL) // o
, .dBusWishbone_ERR (i_dBusWishbone_ERR) // i
, .dBusWishbone_CTI (dBusWishbone_CTI) // o
, .dBusWishbone_BTE (dBusWishbone_BTE) // o

, .jtag_tms (jtag_tms)
, .jtag_tdi (jtag_tdi)
, .jtag_tdo (jtag_tdo)
, .jtag_tck (jtag_tck)
, .clk (clk)
, .reset (reset_cpu)
);

endmodule
3 changes: 2 additions & 1 deletion clash-vexriscv/example-cpu/build.sbt
Original file line number Diff line number Diff line change
@@ -17,7 +17,8 @@ lazy val root = (project in file("."))
libraryDependencies ++= Seq(
"com.github.spinalhdl" % "spinalhdl-core_2.11" % spinalVersion,
"com.github.spinalhdl" % "spinalhdl-lib_2.11" % spinalVersion,
compilerPlugin("com.github.spinalhdl" % "spinalhdl-idsl-plugin_2.11" % spinalVersion)
compilerPlugin("com.github.spinalhdl" % "spinalhdl-idsl-plugin_2.11" % spinalVersion),
"org.yaml" % "snakeyaml" % "1.8"
)
)

167 changes: 83 additions & 84 deletions clash-vexriscv/example-cpu/src/main/scala/example/ExampleCpu.scala
Original file line number Diff line number Diff line change
@@ -6,102 +6,95 @@ package example

import spinal.core._
import spinal.lib._
import spinal.lib.com.jtag.Jtag
import spinal.lib.bus.wishbone.{Wishbone, WishboneConfig}

import vexriscv.plugin._
import vexriscv.{VexRiscv, VexRiscvConfig, plugin}
import vexriscv.ip.{DataCacheConfig}
import vexriscv.ip.fpu.FpuParameter

object ExampleCpu extends App {
def cpu() : VexRiscv = {
val config = VexRiscvConfig(
plugins = List(
new IBusSimplePlugin(
resetVector = 0x20000000l,
cmdForkOnSecondStage = false,
cmdForkPersistence = false,
prediction = NONE,
catchAccessFault = true,
compressedGen = true // C extension
),

// new DBusSimplePlugin(
// catchAddressMisaligned = true,
// catchAccessFault = true
// ),

new DBusCachedPlugin(
/*
config = new DataCacheConfig(
cacheSize = 2048,
bytePerLine = 32,
wayCount = 1,
addressWidth = 32,
cpuDataWidth = 32,
memDataWidth = 32,
catchAccessError = true,
catchIllegal = true,
catchUnaligned = true
)
*/
config = new DataCacheConfig(
cacheSize = 8,
bytePerLine = 8,
wayCount = 1,
addressWidth = 32,
cpuDataWidth = 32,
memDataWidth = 32,
catchAccessError = true,
catchIllegal = true,
catchUnaligned = true
)
),

new StaticMemoryTranslatorPlugin(
ioRange = _ => True
),

new CsrPlugin(
CsrPluginConfig.smallest.copy(ebreakGen = true, mtvecAccess = CsrAccess.READ_WRITE)
),
new DecoderSimplePlugin(
catchIllegalInstruction = true
),
new RegFilePlugin(
regFileReadyKind = plugin.SYNC,
zeroBoot = false
),
new IntAluPlugin,
new SrcPlugin(
separatedAddSub = false,
executeInsertion = false
),
val config = VexRiscvConfig(
plugins = List(
new IBusSimplePlugin(
resetVector = 0x20000000l,
cmdForkOnSecondStage = false,
cmdForkPersistence = false,
prediction = STATIC,
// Trap when an iBus access returns an error.
catchAccessFault = true,
compressedGen = true
),
new DBusSimplePlugin(
// Trap when a load or store access is misaligned.
catchAddressMisaligned = true,
// Trap when a load or store access results in a bus error
catchAccessFault = true
),

// M extension
new MulPlugin,
new DivPlugin,
new CsrPlugin(
// CsrPluginConfig.smallest.copy(ebreakGen = true, mtvecAccess = CsrAccess.READ_WRITE)
CsrPluginConfig(
catchIllegalAccess = false,
mvendorid = null,
marchid = null,
mimpid = null,
mhartid = null,
misaExtensionsInit = 66,
misaAccess = CsrAccess.NONE,
mtvecAccess = CsrAccess.READ_WRITE,
mtvecInit = 0x00000020,
mepcAccess = CsrAccess.READ_WRITE,
mscratchGen = false,
mcauseAccess = CsrAccess.READ_ONLY,
mbadaddrAccess = CsrAccess.READ_ONLY,
mcycleAccess = CsrAccess.NONE,
minstretAccess = CsrAccess.NONE,
ecallGen = false,
ebreakGen = true,
wfiGenAsWait = false,
ucycleAccess = CsrAccess.READ_ONLY,
uinstretAccess = CsrAccess.NONE
)
),
new DecoderSimplePlugin(
catchIllegalInstruction = true
),
new RegFilePlugin(
regFileReadyKind = plugin.SYNC,
zeroBoot = false
),
new IntAluPlugin,
new SrcPlugin(
separatedAddSub = false,
executeInsertion = false
),

// F extension
new FpuPlugin(
p = FpuParameter(
withDouble = false // enable for D extension
)
),
// M extension
new MulPlugin,
new DivPlugin,

new LightShifterPlugin,
new HazardSimplePlugin(
bypassExecute = false,
bypassMemory = false,
bypassWriteBack = false,
bypassWriteBackBuffer = false,
pessimisticUseSrc = false,
pessimisticWriteRegFile = false,
pessimisticAddressMatch = false
),
new BranchPlugin(
earlyBranch = false,
catchAddressMisaligned = true
new LightShifterPlugin,
new HazardSimplePlugin(
bypassExecute = true,
bypassMemory = true,
bypassWriteBack = true,
bypassWriteBackBuffer = true,
pessimisticUseSrc = false,
pessimisticWriteRegFile = false,
pessimisticAddressMatch = false
),
new BranchPlugin(
earlyBranch = false,
catchAddressMisaligned = true
),
new DebugPlugin(ClockDomain.current),
new YamlPlugin("ExampleCpu.yaml")
)
)

)

val cpu = new VexRiscv(config)
@@ -121,6 +114,12 @@ object ExampleCpu extends App {
plugin.dBus.setAsDirectionLess()
master(plugin.dBus.toWishbone()).setName("dBusWishbone")
}

case plugin: DebugPlugin => plugin.debugClockDomain {
plugin.io.bus.setAsDirectionLess()
val jtag = slave(new Jtag()).setName("jtag")
jtag <> plugin.io.bus.fromJtag()
}
case _ =>
}
}
122 changes: 106 additions & 16 deletions clash-vexriscv/src/VexRiscv.hs
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE TemplateHaskellQuotes #-}
{-# LANGUAGE QuasiQuotes #-}
-- {-# LANGUAGE AllowAmbiguousTypes #-}

{-# OPTIONS_GHC -fconstraint-solver-iterations=10 #-}

@@ -25,6 +26,7 @@ import Foreign.Storable
import GHC.IO (unsafePerformIO, unsafeInterleaveIO)
import GHC.Stack (HasCallStack)
import Language.Haskell.TH.Syntax
import Protocols
import Protocols.Wishbone

import VexRiscv.ClockTicks
@@ -34,7 +36,20 @@ import VexRiscv.VecToTuple

import qualified VexRiscv.FFI as FFI

data Input = Input
data JtagIn = JtagIn
{ testClock :: "TCK" ::: Bit
, testModeSelect :: "TMS" ::: Bit
, testDataIn :: "TDI" ::: Bit
}
deriving (Generic, Eq, NFDataX, ShowX, BitPack)

data JtagOut = JtagOut
{ testDataOut :: "TDO" ::: Bit
, debugReset :: "RST" ::: Bit
}
deriving (Generic, NFDataX, ShowX, Eq, BitPack)

data CpuIn = CpuIn
{ timerInterrupt :: "TIMER_INTERRUPT" ::: Bit
, externalInterrupt :: "EXTERNAL_INTERRUPT" ::: Bit
, softwareInterrupt :: "SOFTWARE_INTERRUPT" ::: Bit
@@ -43,12 +58,20 @@ data Input = Input
}
deriving (Generic, NFDataX, ShowX, Eq, BitPack)

data Output = Output
data CpuOut = CpuOut
{ iBusWbM2S :: "IBUS_OUT_" ::: WishboneM2S 30 4 (BitVector 32)
, dBusWbM2S :: "DBUS_OUT_" ::: WishboneM2S 30 4 (BitVector 32)
}
deriving (Generic, NFDataX, ShowX, Eq, BitPack)


data Jtag (dom :: Domain)

instance Protocol (Jtag dom) where
type Fwd (Jtag dom) = Signal dom JtagOut
type Bwd (Jtag dom) = Signal dom JtagIn


-- When passing S2M values from Haskell to VexRiscv over the FFI, undefined
-- bits/values cause errors when forcing their evaluation to something that can
-- be passed through the FFI.
@@ -62,9 +85,19 @@ defaultX dflt val
| hasUndefined val = dflt
| otherwise = val

vexRiscv :: (HasCallStack, HiddenClockResetEnable dom) => Signal dom Input -> Signal dom Output
vexRiscv input =
Output <$>
vexRiscv ::
forall dom .
( HasCallStack
, KnownDomain dom) =>
Clock dom ->
Reset dom ->
Signal dom CpuIn ->
Signal dom JtagIn ->
( Signal dom CpuOut
, Signal dom JtagOut
)
vexRiscv clk rst cpuInput jtagInput =
( CpuOut <$>
(WishboneM2S
<$> iBus_ADR
<*> iBus_DAT_MOSI
@@ -88,10 +121,19 @@ vexRiscv input =
<*> (unpack <$> dBus_CTI)
<*> (unpack <$> dBus_BTE)
)
, JtagOut <$> jtag_TDO1 <*> debug_resetOut1
)

where

jtag_TDO1 =
jtag_TDO

debug_resetOut1 =
debug_resetOut

(unbundle -> (timerInterrupt, externalInterrupt, softwareInterrupt, iBusS2M, dBusS2M))
= (\(Input a b c d e) -> (a, b, c, d, e)) <$> input
= (\(CpuIn a b c d e) -> (a, b, c, d, e)) <$> cpuInput

(unbundle -> (iBus_DAT_MISO, iBus_ACK, iBus_ERR))
= (\(WishboneS2M a b c _ _) -> (a, b, c))
@@ -105,11 +147,16 @@ vexRiscv input =
. (if clashSimulation then makeDefined else id)
<$> dBusS2M

(unbundle -> (jtag_TCK, jtag_TMS, jtag_TDI))
= bitCoerce <$> jtagInput

sourcePath = $(do
cpuSrcPath <- runIO $ getPackageRelFilePath "example-cpu/VexRiscv.v"
pure $ LitE $ StringL cpuSrcPath
)

-- TODO: Remove need for 'vexRiscv#' by doing construction / deconstruction of
-- product types in HDL using BlackBoxHaskell functions.
( iBus_CYC
, iBus_STB
, iBus_WE
@@ -126,7 +173,9 @@ vexRiscv input =
, dBus_SEL
, dBus_CTI
, dBus_BTE
) = vexRiscv# sourcePath hasClock hasReset
, debug_resetOut
, jtag_TDO
) = vexRiscv# sourcePath clk rst
timerInterrupt
externalInterrupt
softwareInterrupt
@@ -139,8 +188,9 @@ vexRiscv input =
dBus_ERR
dBus_DAT_MISO



jtag_TCK
jtag_TMS
jtag_TDI


vexRiscv#
@@ -161,6 +211,11 @@ vexRiscv#
-> Signal dom Bool -- ^ dBus_ERR
-> Signal dom (BitVector 32) -- ^ dBus_DAT_MISO

-> Signal dom Bit -- ^ jtag_TCK
-> Signal dom Bit -- ^ jtag_TMS
-> Signal dom Bit -- ^ jtag_TDI


-- output signals
->
(
@@ -183,6 +238,9 @@ vexRiscv#
, Signal dom (BitVector 4) -- ^ dBus_SEL
, Signal dom (BitVector 3) -- ^ dBus_CTI
, Signal dom (BitVector 2) -- ^ dBus_BTE

, Signal dom Bit -- ^ debug_resetOut
, Signal dom Bit -- ^ jtag_TDO
)
vexRiscv# !_sourcePath clk rst0
timerInterrupt
@@ -194,7 +252,11 @@ vexRiscv# !_sourcePath clk rst0

dBus_ACK
dBus_ERR
dBus_DAT_MISO = unsafePerformIO $ do
dBus_DAT_MISO

jtag_TCK
jtag_TMS
jtag_TDI = unsafePerformIO $ do
(v, initStage1, initStage2, stepRising, stepFalling, _shutDown) <- vexCPU

let
@@ -211,6 +273,9 @@ vexRiscv# !_sourcePath clk rst0
<*> (boolToBit <$> dBus_ACK)
<*> (unpack <$> dBus_DAT_MISO)
<*> (boolToBit <$> dBus_ERR)
<*> jtag_TCK
<*> jtag_TMS
<*> jtag_TDI

simInitThenCycles ::
Signal dom NON_COMB_INPUT ->
@@ -286,12 +351,20 @@ vexRiscv# !_sourcePath clk rst0
, truncateB . pack <$> dBus_SEL
, truncateB . pack <$> dBus_CTI
, truncateB . pack <$> dBus_BTE

-- JTAG
, FFI.jtag_debug_resetOut <$> output
, FFI.jtag_TDO <$> output
)
{-# NOINLINE vexRiscv# #-}
{-# ANN vexRiscv# (
let
primName = 'vexRiscv#
( _


(
-- ARGs
_
, srcPath
, clk
, rst
@@ -304,9 +377,12 @@ vexRiscv# !_sourcePath clk rst0
, dBus_ACK
, dBus_ERR
, dBus_DAT_MISO
) = vecToTuple (indicesI @13)
, jtag_TCK
, jtag_TMS
, jtag_TDI

( iBus_CYC
-- GENSYMs
, iBus_CYC
, iBus_STB
, iBus_WE
, iBus_ADR
@@ -322,9 +398,11 @@ vexRiscv# !_sourcePath clk rst0
, dBus_SEL
, dBus_CTI
, dBus_BTE
) = vecToTuple $ (\x -> extend @_ @16 @13 x + 1) <$> indicesI @16
, debug_resetOut
, jtag_TDO

cpu = extend @_ @_ @1 dBus_BTE + 1
, cpu
) = vecToTuple $ indicesI @35
in
InlineYamlPrimitive [Verilog] [__i|
BlackBox:
@@ -353,6 +431,9 @@ vexRiscv# !_sourcePath clk rst0
wire [2:0] ~GENSYM[dBus_CTI][#{dBus_CTI}];
wire [1:0] ~GENSYM[dBus_BTE][#{dBus_BTE}];

wire ~GENSYM[debug_resetOut][#{debug_resetOut}];
wire ~GENSYM[jtag_TDO][#{jtag_TDO}];

VexRiscv ~GENSYM[cpu][#{cpu}] (
.timerInterrupt ( ~ARG[#{timerInterrupt}] ),
.externalInterrupt ( ~ARG[#{externalInterrupt}] ),
@@ -382,6 +463,13 @@ vexRiscv# !_sourcePath clk rst0
.dBusWishbone_CTI ( ~SYM[#{dBus_CTI}] ),
.dBusWishbone_BTE ( ~SYM[#{dBus_BTE}] ),

.jtag_tms ( ~ARG[#{jtag_TMS}]),
.jtag_tdi ( ~ARG[#{jtag_TDI}]),
.jtag_tck ( ~ARG[#{jtag_TCK}]),
.jtag_tdo ( ~SYM[#{jtag_TDO}] ),

.debug_resetOut ( ~SYM[#{debug_resetOut}] ),

.clk ( ~ARG[#{clk}] ),
.reset ( ~ARG[#{rst}] )
);
@@ -402,7 +490,9 @@ vexRiscv# !_sourcePath clk rst0
~SYM[#{dBus_DAT_MOSI}],
~SYM[#{dBus_SEL}],
~SYM[#{dBus_CTI}],
~SYM[#{dBus_BTE}]
~SYM[#{dBus_BTE}],
~SYM[#{debug_resetOut}],
~SYM[#{jtag_TDO}]
};

// vexRiscv end
68 changes: 68 additions & 0 deletions clash-vexriscv/src/VexRiscv/FFI.hsc
Original file line number Diff line number Diff line change
@@ -18,6 +18,8 @@ import Data.Word

data VexRiscv

data VexRiscvJtagBridge

foreign import ccall unsafe "vexr_init" vexrInit :: IO (Ptr VexRiscv)
foreign import ccall unsafe "vexr_shutdown" vexrShutdown :: Ptr VexRiscv -> IO ()

@@ -26,6 +28,10 @@ foreign import ccall unsafe "vexr_init_stage2" vexrInitStage2 :: Ptr VexRiscv ->
foreign import ccall unsafe "vexr_step_rising_edge" vexrStepRisingEdge :: Ptr VexRiscv -> Word64 -> Ptr NON_COMB_INPUT -> Ptr OUTPUT -> IO ()
foreign import ccall unsafe "vexr_step_falling_edge" vexrStepFallingEdge :: Ptr VexRiscv -> Word64 -> Ptr COMB_INPUT -> IO ()

foreign import ccall unsafe "vexr_jtag_bridge_init" vexrJtagBridgeInit :: Word16 -> IO (Ptr VexRiscvJtagBridge)
foreign import ccall unsafe "vexr_jtag_bridge_step" vexrJtagBridgeStep :: Ptr VexRiscvJtagBridge -> Ptr JTAG_OUTPUT -> Ptr JTAG_INPUT -> IO ()
foreign import ccall unsafe "vexr_jtag_bridge_shutdown" vexrJtagBridgeShutdown :: Ptr VexRiscvJtagBridge -> IO ()

-- | CPU input that cannot combinatorially depend on the CPU output
data NON_COMB_INPUT = NON_COMB_INPUT
{ reset :: Bit
@@ -43,6 +49,10 @@ data COMB_INPUT = COMB_INPUT
, dBusWishbone_ACK :: Bit
, dBusWishbone_DAT_MISO :: Word32
, dBusWishbone_ERR :: Bit

, jtag_TCK :: Bit
, jtag_TMS :: Bit
, jtag_TDI :: Bit
}
deriving (Show)

@@ -64,6 +74,22 @@ data OUTPUT = OUTPUT
, dBusWishbone_SEL :: Word8
, dBusWishbone_CTI :: Word8
, dBusWishbone_BTE :: Word8

, jtag_debug_resetOut :: Bit
, jtag_TDO :: Bit
}
deriving (Show)

data JTAG_INPUT = JTAG_INPUT
{ tck :: Bit
, tms :: Bit
, tdi :: Bit
}
deriving (Show)

data JTAG_OUTPUT = JTAG_OUTPUT
{ debug_resetOut :: Bit
, tdo :: Bit
}
deriving (Show)

@@ -102,6 +128,9 @@ instance Storable COMB_INPUT where
<*> (#peek COMB_INPUT, dBusWishbone_ACK) ptr
<*> (#peek COMB_INPUT, dBusWishbone_DAT_MISO) ptr
<*> (#peek COMB_INPUT, dBusWishbone_ERR) ptr
<*> (#peek COMB_INPUT, jtag_TCK) ptr
<*> (#peek COMB_INPUT, jtag_TMS) ptr
<*> (#peek COMB_INPUT, jtag_TDI) ptr

{-# INLINE poke #-}
poke ptr this = do
@@ -112,6 +141,10 @@ instance Storable COMB_INPUT where
(#poke COMB_INPUT, dBusWishbone_ACK) ptr (dBusWishbone_ACK this)
(#poke COMB_INPUT, dBusWishbone_DAT_MISO) ptr (dBusWishbone_DAT_MISO this)
(#poke COMB_INPUT, dBusWishbone_ERR) ptr (dBusWishbone_ERR this)

(#poke COMB_INPUT, jtag_TCK) ptr (jtag_TCK this)
(#poke COMB_INPUT, jtag_TMS) ptr (jtag_TMS this)
(#poke COMB_INPUT, jtag_TDI) ptr (jtag_TDI this)
return ()

instance Storable OUTPUT where
@@ -137,6 +170,9 @@ instance Storable OUTPUT where
<*> (#peek OUTPUT, dBusWishbone_CTI) ptr
<*> (#peek OUTPUT, dBusWishbone_BTE) ptr

<*> (#peek OUTPUT, jtag_debug_resetOut) ptr
<*> (#peek OUTPUT, jtag_TDO) ptr

{-# INLINE poke #-}
poke ptr this = do
(#poke OUTPUT, iBusWishbone_CYC) ptr (iBusWishbone_CYC this)
@@ -156,4 +192,36 @@ instance Storable OUTPUT where
(#poke OUTPUT, dBusWishbone_SEL) ptr (dBusWishbone_SEL this)
(#poke OUTPUT, dBusWishbone_CTI) ptr (dBusWishbone_CTI this)
(#poke OUTPUT, dBusWishbone_BTE) ptr (dBusWishbone_BTE this)

(#poke OUTPUT, jtag_debug_resetOut) ptr (jtag_debug_resetOut this)
(#poke OUTPUT, jtag_TDO) ptr (jtag_TDO this)
return ()

instance Storable JTAG_OUTPUT where
alignment _ = #alignment JTAG_OUTPUT
sizeOf _ = #size JTAG_OUTPUT
{-# INLINE peek #-}
peek ptr = const JTAG_OUTPUT <$> pure ()
<*> (#peek JTAG_OUTPUT, debug_resetOut) ptr
<*> (#peek JTAG_OUTPUT, tdo) ptr

{-# INLINE poke #-}
poke ptr this = do
(#poke JTAG_OUTPUT, debug_resetOut) ptr (debug_resetOut this)
(#poke JTAG_OUTPUT, tdo) ptr (tdo this)

instance Storable JTAG_INPUT where
alignment _ = #alignment JTAG_INPUT
sizeOf _ = #size JTAG_INPUT
{-# INLINE peek #-}
peek ptr = const JTAG_INPUT <$> pure ()
<*> (#peek JTAG_INPUT, tck) ptr
<*> (#peek JTAG_INPUT, tms) ptr
<*> (#peek JTAG_INPUT, tdi) ptr

{-# INLINE poke #-}
poke ptr this = do
(#poke JTAG_INPUT, tck) ptr (tck this)
(#poke JTAG_INPUT, tms) ptr (tms this)
(#poke JTAG_INPUT, tdi) ptr (tdi this)
return ()
50 changes: 50 additions & 0 deletions clash-vexriscv/src/VexRiscv/JtagTcpBridge.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
-- SPDX-FileCopyrightText: 2023 Google LLC
--
-- SPDX-License-Identifier: Apache-2.0

{-# LANGUAGE RecordWildCards #-}
module VexRiscv.JtagTcpBridge (vexrJtagBridge, defaultIn) where

import Clash.Prelude

import Clash.Signal.Internal

import VexRiscv
import VexRiscv.FFI

import Foreign
import System.IO.Unsafe (unsafePerformIO)
import Network.Socket (PortNumber)

defaultIn :: JtagIn
defaultIn = JtagIn { testClock = low, testModeSelect = low, testDataIn = low }

{-# NOINLINE inner #-}
inner :: (t -> IO JtagIn) -> Signal dom t -> Signal dom JtagIn
inner jtagBridgeStep (o :- outs) = unsafePerformIO $ do
in' <- jtagBridgeStep o
let ins' = inner jtagBridgeStep outs
pure $ in' :- (in' `deepseqX` ins')

vexrJtagBridge :: PortNumber -> Signal dom JtagOut -> Signal dom JtagIn
vexrJtagBridge port out = inner jtagBridgeStep out
where
(_, jtagBridgeStep) = unsafePerformIO $ vexrJtagBridge' port

vexrJtagBridge' ::
PortNumber ->
IO ( IO () -- ^ delete function
, JtagOut -> IO JtagIn -- ^ step function
)
vexrJtagBridge' port = do
bridge <- vexrJtagBridgeInit (fromIntegral port)
let
shutDown = vexrJtagBridgeShutdown bridge

step JtagOut{..} = alloca $ \outFFI -> alloca $ \inFFI -> do
poke outFFI (JTAG_OUTPUT debugReset testDataOut)
vexrJtagBridgeStep bridge outFFI inFFI
JTAG_INPUT{..} <- peek inFFI
let input = JtagIn { testClock = tck, testModeSelect = tms, testDataIn = tdi }
pure input
pure (shutDown, step)
186 changes: 186 additions & 0 deletions clash-vexriscv/src/VexRiscv/JtagTcpBridgeHaskell.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
-- SPDX-FileCopyrightText: 2023 Google LLC
--
-- SPDX-License-Identifier: Apache-2.0

module VexRiscv.JtagTcpBridge where

import Clash.Prelude

import Clash.Signal.Internal
import Network.Socket
import Protocols
import Protocols.Internal (CSignal(..), Reverse)
import VexRiscv (JtagIn(..), JtagOut(..), Jtag)
import Control.Concurrent (MVar, forkIO, newEmptyMVar, putMVar, takeMVar, tryTakeMVar)
import Control.Monad (when)
import Network.Socket.ByteString (sendAll, recv)

import qualified Data.ByteString as BS
import System.IO.Unsafe (unsafePerformIO)
import Debug.Trace (trace)

data NetworkThreadToMainMsg
= Connected
| Disconnected
| DataReceived [BitVector 8]

data MainToNetworkThreadMsg
= ReadMore
| Send (BitVector 8)

data NetworkThreadState
= NoClient
| PerformRead Socket
| WaitForNextRead Socket
deriving (Show)

data MainThreadState
= MDisconnected
| MWaitForRead
| MProcessing (BitVector 2) [BitVector 8]
deriving (Show)

jtagTcpBridge ::
(HiddenClockResetEnable dom) =>
PortNumber ->
Circuit
(Jtag dom, Reverse (CSignal dom Bool))
()
jtagTcpBridge port =
Circuit $ \((jtagOut, _), ()) -> unsafePerformIO $ do
(enable, jtagIn) <- jtagTcpBridge' port jtagOut
pure ((jtagIn, CSignal $ fromEnable enable), ())

jtagTcpBridge' ::
(KnownDomain dom) =>
PortNumber ->
Signal dom JtagOut ->
IO (Enable dom, Signal dom JtagIn)
jtagTcpBridge' port jtagOut = do

(n2m, m2n) <- server port

(unbundle -> (enable, jtagIn)) <- client n2m m2n MDisconnected jtagOut

pure (toEnable enable, jtagIn)

{-# NOINLINE jtagTcpBridge' #-}

server :: PortNumber -> IO (MVar NetworkThreadToMainMsg, MVar MainToNetworkThreadMsg)
server port = withSocketsDo $ do
sock <- setup

threadToMainChan <- newEmptyMVar
mainToThreadChan <- newEmptyMVar

let
thread NoClient = do
(clientSock, _) <- accept sock
putMVar threadToMainChan Connected
thread (PerformRead clientSock)
thread (PerformRead clientSock) = do
buf <- recv clientSock 100
if BS.null buf then do
putMVar threadToMainChan Disconnected
thread NoClient
else do
let dat = pack <$> BS.unpack buf
putMVar threadToMainChan (DataReceived dat)
thread (WaitForNextRead clientSock)

thread (WaitForNextRead clientSock) = do
msg <- takeMVar mainToThreadChan
case msg of
ReadMore -> thread (PerformRead clientSock)
Send byte -> do
sendAll clientSock (BS.singleton $ unpack byte)
thread (WaitForNextRead clientSock)

_ <- forkIO $ thread NoClient

pure (threadToMainChan, mainToThreadChan)

where
setup = do
sock <- socket AF_INET Stream 0

setSocketOption sock NoDelay 0

bind sock (SockAddrInet port (tupleToHostAddress (127, 0, 0, 1)))

listen sock 1

pure sock

defaultIn :: JtagIn
defaultIn = JtagIn { testModeSelect = low, testDataIn = low }

dbg :: Show a => a -> a
dbg x =
trace (show x)
x

clientSleep :: BitVector 2
clientSleep = 4

client ::
(KnownDomain dom) =>
MVar NetworkThreadToMainMsg ->
MVar MainToNetworkThreadMsg ->
MainThreadState ->
Signal dom JtagOut ->
IO (Signal dom (Bool, JtagIn))
client n2m m2n MDisconnected (_out :- outs) = do
msg <- tryTakeMVar n2m
case msg of
Nothing ->
pure $ _out `deepseqX` (False, defaultIn) :- unsafePerformIO (client n2m m2n MDisconnected outs)
Just Connected -> do
pure $ _out `deepseqX` (False, defaultIn) :- unsafePerformIO (client n2m m2n MWaitForRead outs)
Just Disconnected -> do
errorX "????"
Just (DataReceived _xs) -> do
errorX "????"

client n2m m2n MWaitForRead (out :- outs) = do
msg <- tryTakeMVar n2m
case msg of
Nothing ->
pure $ out `deepseqX` (False, defaultIn) :- unsafePerformIO (client n2m m2n MWaitForRead outs)
Just Disconnected ->
pure $ out `deepseqX` (False, defaultIn) :- unsafePerformIO (client n2m m2n MDisconnected outs)
Just (DataReceived xs) ->
client n2m m2n (MProcessing 0 xs) (out :- outs)
Just Connected ->
errorX "????"

client n2m m2n (MProcessing _ []) (out :- outs) = do
putMVar m2n ReadMore
pure $ out `deepseqX` (False, defaultIn) :- unsafePerformIO (client n2m m2n MWaitForRead outs)
client n2m m2n (MProcessing 0 (x:xs)) (out :- outs) = do
let tms = x ! (0 :: Int)
tdi = x ! (1 :: Int)
tck = x ! (3 :: Int)

sendTdo = bitToBool $ x ! (2 :: Int)

enable = bitToBool tck

when sendTdo $ do
let tdo = bitToBool $ testDataOut out
-- putStrLn $ "send TDO " <> show tdo
putMVar m2n $ Send $ boolToBV tdo

let inDat = JtagIn { testModeSelect = tms, testDataIn = tdi }

when enable $ do
-- putStrLn "Enable"
-- putStrLn $ "IN " <> showX inDat
pure ()

pure $ (enable, inDat) :- unsafePerformIO (client n2m m2n (MProcessing clientSleep xs) outs)
client n2m m2n (MProcessing n xs) (out :- outs) = do
pure $ out `deepseqX` (False, defaultIn) :- unsafePerformIO (client n2m m2n (MProcessing (n - 1) xs) outs)


{-# NOINLINE client #-}
207 changes: 207 additions & 0 deletions clash-vexriscv/src/ffi/impl.cpp
Original file line number Diff line number Diff line change
@@ -6,6 +6,31 @@
#include "verilated.h"
#include "interface.h"

#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <netinet/tcp.h>

typedef struct {
int server_socket, client_handle;
struct sockaddr_in server_addr;
struct sockaddr_storage server_storage;

uint32_t timer;
socklen_t addr_size;
uint32_t self_sleep;
uint32_t check_new_connections_timer;
uint8_t rx_buffer[100];
int32_t rx_buffer_size;
int32_t rx_buffer_remaining;
JTAG_INPUT prev_input;

uint64_t tck_change_counter;
} vexr_jtag_bridge_data;

extern "C" {
VVexRiscv* vexr_init();
void vexr_shutdown(VVexRiscv *top);
@@ -14,9 +39,15 @@ extern "C" {
void vexr_init_stage2(VVexRiscv *top, const COMB_INPUT *input);
void vexr_step_rising_edge(VVexRiscv *top, uint64_t time_add, const NON_COMB_INPUT *input, OUTPUT *output);
void vexr_step_falling_edge(VVexRiscv *top, uint64_t time_add, const COMB_INPUT *input);

vexr_jtag_bridge_data *vexr_jtag_bridge_init(uint16_t port);
void vexr_jtag_bridge_step(vexr_jtag_bridge_data *d, const JTAG_OUTPUT *output, JTAG_INPUT *input);
void vexr_jtag_bridge_shutdown(vexr_jtag_bridge_data *bridge_data);
}

static VerilatedContext* contextp = 0;
static bool set_socket_blocking_enabled(int fd, bool blocking);
static void connection_reset(vexr_jtag_bridge_data *bridge_data);

VVexRiscv* vexr_init()
{
@@ -47,6 +78,10 @@ void set_comb_inputs(VVexRiscv *top, const COMB_INPUT *input)
top->dBusWishbone_ACK = input->dBusWishbone_ACK;
top->dBusWishbone_DAT_MISO = input->dBusWishbone_DAT_MISO;
top->dBusWishbone_ERR = input->dBusWishbone_ERR;

top->jtag_tck = input->jtag_TCK;
top->jtag_tms = input->jtag_TMS;
top->jtag_tdi = input->jtag_TDI;
}

// Set all outputs
@@ -68,6 +103,9 @@ void set_ouputs(VVexRiscv *top, OUTPUT *output)
output->dBusWishbone_SEL = top->dBusWishbone_SEL;
output->dBusWishbone_CTI = top->dBusWishbone_CTI;
output->dBusWishbone_BTE = top->dBusWishbone_BTE;

output->jtag_debug_resetOut = top->debug_resetOut;
output->jtag_TDO = top->jtag_tdo;
}

void vexr_init_stage1(VVexRiscv *top, const NON_COMB_INPUT *input, OUTPUT *output)
@@ -127,3 +165,172 @@ void vexr_step_falling_edge(VVexRiscv *top, uint64_t time_add, const COMB_INPUT
// Evaluate the simulation
top->eval();
}

vexr_jtag_bridge_data *vexr_jtag_bridge_init(uint16_t port)
{
vexr_jtag_bridge_data *d = new vexr_jtag_bridge_data;

d->prev_input = { 0, 0, 0 };

d->tck_change_counter = 0;

d->timer = 0;
d->self_sleep = 0;
d->check_new_connections_timer = 0;
d->rx_buffer_size = 0;
d->rx_buffer_remaining = 0;

d->server_socket = socket(PF_INET, SOCK_STREAM, 0);
assert(d->server_socket != -1);
int flag = 1;
setsockopt(
d->server_socket, /* socket affected */
IPPROTO_TCP, /* set option at TCP level */
TCP_NODELAY, /* name of option */
(char *) &flag, /* the cast is historical cruft */
sizeof(int) /* length of option value */
);

set_socket_blocking_enabled(d->server_socket, 0);

d->server_addr.sin_family = AF_INET;
d->server_addr.sin_port = htons(port);
d->server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
memset(d->server_addr.sin_zero, '\0', sizeof(d->server_addr.sin_zero));

bind(
d->server_socket,
(struct sockaddr *) &d->server_addr,
sizeof(d->server_addr)
);

listen(d->server_socket, 1);

d->client_handle = -1;
d->addr_size = sizeof(d->server_storage);

return d;
}

void vexr_jtag_bridge_step(vexr_jtag_bridge_data *d, const JTAG_OUTPUT *output, JTAG_INPUT *input)
{
const int WAIT_PERIOD = 83333;
// We set the input values to their last here
// so that only the "successful" path has to update them

*input = d->prev_input;

if(d->timer != 0) {
d->timer -= 1;
return;
}
d->check_new_connections_timer++;
if (d->check_new_connections_timer == 200) {
d->check_new_connections_timer = 0;
int new_client_handle = accept(
d->server_socket,
(struct sockaddr *) &d->server_storage,
&d->addr_size
);
if (new_client_handle != -1) {
if(d->client_handle != -1){
connection_reset(d);
}
d->client_handle = new_client_handle;
printf("\n[JTAG BRIDGE] got new connection\n");
} else {
if(d->client_handle == -1)
d->self_sleep = 200;
}
}
if (d->self_sleep) {
d->self_sleep--;
} else if (d->client_handle != -1) {
int n;

if (d->rx_buffer_remaining == 0) {
if (ioctl(d->client_handle, FIONREAD, &n) != 0) {
connection_reset(d);
} else if (n >= 1) {
d->rx_buffer_size = read(
d->client_handle,
&d->rx_buffer,
100
);
if (d->rx_buffer_size < 0) {
connection_reset(d);
} else {
d->rx_buffer_remaining = d->rx_buffer_size;
}
} else {
d->self_sleep = 30;
}
}

if (d->rx_buffer_remaining != 0){
uint8_t buffer = d->rx_buffer[d->rx_buffer_size - (d->rx_buffer_remaining--)];
input->tms = (buffer & 1) != 0;
input->tdi = (buffer & 2) != 0;
input->tck = (buffer & 8) != 0;

// printf("\n[JTAG_BRIDGE] ");
// printf("%d %d %d %d\n",
// d->tck_change_counter,
// input->jtag_TCK,
// input->jtag_TMS,
// input->jtag_TDI
// );

if (input->tck != d->prev_input.tck) {
d->tck_change_counter++;
}



d->prev_input = *input;
if(buffer & 4){
buffer = (output->tdo != 0);
// printf("\n[JTAG_BRIDGE] [TDO] %d\n", buffer);
if (-1 == send(d->client_handle, &buffer, 1, 0)) {
connection_reset(d);
}
}
}
}
d->timer = 27; // 3; value used by VexRiscv regression test
}

void vexr_jtag_bridge_shutdown(vexr_jtag_bridge_data *bridge_data)
{
if (bridge_data->client_handle != -1) {
shutdown(bridge_data->client_handle, SHUT_RDWR);
usleep(100);
}
if (bridge_data->server_socket != -1) {
close(bridge_data->server_socket);
usleep(100);
}
}


/** Returns true on success, or false if there was an error */
static bool set_socket_blocking_enabled(int fd, bool blocking)
{
if (fd < 0) return false;

#ifdef WIN32
unsigned long mode = blocking ? 0 : 1;
return (ioctlsocket(fd, FIONBIO, &mode) == 0) ? true : false;
#else
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) return false;
flags = blocking ? (flags&~O_NONBLOCK) : (flags|O_NONBLOCK);
return (fcntl(fd, F_SETFL, flags) == 0) ? true : false;
#endif
}

static void connection_reset(vexr_jtag_bridge_data *bridge_data) {
printf("[JTAG BRIDGE] closed connection\n");
shutdown(bridge_data->client_handle, SHUT_RDWR);
bridge_data->client_handle = -1;
}
18 changes: 18 additions & 0 deletions clash-vexriscv/src/ffi/interface.h
Original file line number Diff line number Diff line change
@@ -24,6 +24,10 @@ typedef struct {
bit dBusWishbone_ACK;
uint32_t dBusWishbone_DAT_MISO;
bit dBusWishbone_ERR;

bit jtag_TCK;
bit jtag_TMS;
bit jtag_TDI;
} COMB_INPUT;

typedef struct {
@@ -44,6 +48,20 @@ typedef struct {
uint8_t dBusWishbone_SEL;
uint8_t dBusWishbone_CTI;
uint8_t dBusWishbone_BTE;

bit jtag_debug_resetOut;
bit jtag_TDO;
} OUTPUT;

typedef struct {
bit tck;
bit tms;
bit tdi;
} JTAG_INPUT;

typedef struct {
bit debug_resetOut;
bit tdo;
} JTAG_OUTPUT;

#endif
18 changes: 18 additions & 0 deletions debug-test/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# SPDX-FileCopyrightText: 2022 Google LLC
#
# SPDX-License-Identifier: CC0-1.0

[package]
name = "debug-test"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
authors = ["Google LLC"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
riscv-rt = "0.11.0"
riscv = "^0.10"
heapless = { version = "0.7", default-features = false }
panic-halt = "0.2.0"
23 changes: 23 additions & 0 deletions debug-test/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2022 Google LLC
//
// SPDX-License-Identifier: Apache-2.0

use std::env;
use std::fs;
use std::path::Path;

/// Put the linker script somewhere the linker can find it.
fn main() {
let out_dir = env::var("OUT_DIR").expect("No out dir");
let dest_path = Path::new(&out_dir).join("memory.x");
fs::write(dest_path, include_bytes!("memory.x")).expect("Could not write file");

if env::var("CARGO_CFG_TARGET_ARCH").unwrap() == "riscv32" {
println!("cargo:rustc-link-arg=-Tmemory.x");
println!("cargo:rustc-link-arg=-Tlink.x"); // linker script from riscv-rt
}
println!("cargo:rustc-link-search={out_dir}");

println!("cargo:rerun-if-changed=memory.x");
println!("cargo:rerun-if-changed=build.rs");
}
18 changes: 18 additions & 0 deletions debug-test/memory.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
SPDX-FileCopyrightText: 2022 Google LLC
SPDX-License-Identifier: CC0-1.0
*/

MEMORY
{
DATA : ORIGIN = 0x40000000, LENGTH = 512K
INSTR : ORIGIN = 0x20000000, LENGTH = 512K
}

REGION_ALIAS("REGION_TEXT", INSTR);
REGION_ALIAS("REGION_RODATA", DATA);
REGION_ALIAS("REGION_DATA", DATA);
REGION_ALIAS("REGION_BSS", DATA);
REGION_ALIAS("REGION_HEAP", DATA);
REGION_ALIAS("REGION_STACK", DATA);
28 changes: 28 additions & 0 deletions debug-test/src/bin/print_a.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2022 Google LLC
//
// SPDX-License-Identifier: Apache-2.0

#![no_std]
#![cfg_attr(not(test), no_main)]

#[cfg(not(test))]
use riscv_rt::entry;

#[cfg(not(test))]
extern crate panic_halt;

const ADDR: *mut u8 = 0x0000_1000 as *mut u8;

fn print(s: &str) {
for b in s.bytes() {
unsafe {
ADDR.write_volatile(b);
}
}
}

#[cfg_attr(not(test), entry)]
fn main() -> ! {
print("[CPU] a\n");
loop { }
}
28 changes: 28 additions & 0 deletions debug-test/src/bin/print_b.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2022 Google LLC
//
// SPDX-License-Identifier: Apache-2.0

#![no_std]
#![cfg_attr(not(test), no_main)]

#[cfg(not(test))]
use riscv_rt::entry;

#[cfg(not(test))]
extern crate panic_halt;

const ADDR: *mut u8 = 0x0000_1000 as *mut u8;

fn print(s: &str) {
for b in s.bytes() {
unsafe {
ADDR.write_volatile(b);
}
}
}

#[cfg_attr(not(test), entry)]
fn main() -> ! {
print("[CPU] b\n");
loop {}
}
106 changes: 106 additions & 0 deletions debug-test/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: 2022 Google LLC
//
// SPDX-License-Identifier: Apache-2.0

#![no_std]
#![cfg_attr(not(test), no_main)]

use core::fmt::Write;

use heapless::String;
#[cfg(not(test))]
use riscv_rt::entry;

#[cfg(not(test))]
extern crate panic_halt;

const ADDR: *mut u8 = 0x0000_1000 as *mut u8;

fn print(s: &str) {
for b in s.bytes() {
unsafe {
ADDR.write_volatile(b);
}
}
}

#[cfg_attr(not(test), entry)]
fn main() -> ! {
print("hello, world.\n");

print("I am here to be debugged!\n");

loop {
for i in 0..30 {
let mut s = String::<16>::new();
let _ = writeln!(s, "Hey! {i}");
print(&s);
}

print("wheeeey!\n");

// unsafe {
// riscv::asm::ebreak();
// }
}
}

#[export_name = "UserSoft"]
fn user_soft_handler() {
loop {
print("INTERRUPT UserSoft");
}
}

#[export_name = "MachineSoft"]
fn machine_soft_handler() {
loop {
print("INTERRUPT MachineSoft");
}
}

#[export_name = "UserTimer"]
fn user_timer_handler() {
loop {
print("INTERRUPT UserTimer");
}
}

#[export_name = "MachineTimer"]
fn machine_timer_handler() {
loop {
print("INTERRUPT MachineTimer");
}
}

#[export_name = "UserExternal"]
fn user_ext_handler() {
loop {
print("INTERRUPT UserExternal");
}
}

#[export_name = "MachineExternal"]
fn machine_ext_handler() {
loop {
print("INTERRUPT MachineExternal");
}
}

#[export_name = "DefaultHandler"]
fn default_handler() {
loop {
print("INTERRUPT default handler");
}
}

#[export_name = "ExceptionHandler"]
fn exception_handler(_trap_frame: &riscv_rt::TrapFrame) -> ! {
riscv::interrupt::free(|| {
print("... caught an exception. Looping forever now.\n");
});
loop {
// print("");
continue;
}
}
2 changes: 1 addition & 1 deletion rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# SPDX-FileCopyrightText: 2022-2023 Google LLC
#
#
# SPDX-License-Identifier: CC0-1.0

[toolchain]
1 change: 1 addition & 0 deletions shell.nix
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@ pkgs.mkShell {

# VexRiscV needs a special openocd
pkgs.openocd-vexriscv
pkgs.gdb

# For Cabal to clone git repos
pkgs.git
25 changes: 25 additions & 0 deletions vexriscv_gdb.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Assume "print_a" is running on the CPU
file "target/riscv32imc-unknown-none-elf/release/print_a"

# Work around issues where simulation is too slow to respond to keep-alive messages,
# confusing either OpenOCD or GDB. Note that it will still complain about "missed"
# deadlines, but it won't fail..
set remotetimeout unlimited

# Connect to OpenOCD
target extended-remote :3333

# List registers
i r

# Jump to start address, should output "a"
jump *0x20000000

# Load program
load "target/riscv32imc-unknown-none-elf/release/print_b"

# Jump to start address. Should now output "b".
jump *0x20000000

# Stop running GDB
quit
61 changes: 61 additions & 0 deletions vexriscv_sim.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
adapter driver jtag_tcp
adapter speed 64000
transport select jtag


set _ENDIAN little
set _TAP_TYPE 1234

if { [info exists CPUTAPID] } {
set _CPUTAPID $CPUTAPID
} else {
# set useful default
set _CPUTAPID 0x10001fff
}

set _CHIPNAME vexrisc_ocd

# The JTAG TAP itself is given the name "bridge", because it refers to the
# JtagBridge that's part of the VexRiscv/SpinalHDL debug infrastructure.
# In the example design, there is the JtagBridge controls a single CPU, but
# the capability is there for 1 JTAG TAP + JtagBridge to control multiple
# VexRiscv CPUs.
jtag newtap $_CHIPNAME bridge -expected-id $_CPUTAPID -irlen 4 -ircapture 0x1 -irmask 0xF

# There is 1 CPU controlled by the "bridge" JTAG TAP, "cpu0"
target create $_CHIPNAME.cpu0 vexriscv -endian $_ENDIAN -chain-position $_CHIPNAME.bridge

# The JtagBridge/SystemDebugger receives commands in a serialized way. It gets synchronized into
# a parallel bus, and a response is received. Along the way, there may be various clock domain
# crossings or pipeline delays.
# readWaitCycles instructs OpenOCD to insert idle JTAG clock cycles before shifting out
# the response.
# There aren't many transactions where read-back throughput is important, so there's little
# points in lowballing this number.
vexriscv readWaitCycles 10

# When the Verilog of a SpinalHDL design with one or more VexRiscv CPUs is created, the system
# also creates a .yaml file with information that's sideband information that's important for
# OpenOCD to control the CPU correctly.
# A good example of this are the number of hardware breakpoints that are supported by the CPU.
vexriscv cpuConfigFile clash-vexriscv/example-cpu/ExampleCpu.yaml

# The rate at which OpenOCD polls active JTAG TAPs to check if there has been a notable
# event. (E.g. to check if the CPU has hit a breakpoint.)
# For some reason, making this number really low has an impact on the CPU while semihosting is
# enabled?
poll_period 50

# Initialize all JTAG TAPs and targets.
init

echo "Halting processor"

# Halts the CPU and issue a soft reset.
# The DebugPlugin has a resetOut signal that can be used reset external logic. It is not
# used to reset anything inside the VexRiscv itself though. In our small example,
# resetOut is not connected to anything, so we could have used "halt" instead.
# soft_reset_halt
halt

sleep 1000

0 comments on commit 3a57205

Please sign in to comment.