Skip to content

Commit

Permalink
Merge pull request #577 from bittide/add-deviceIdentifierWb
Browse files Browse the repository at this point in the history
Add `readDnaPortE2Wb`
  • Loading branch information
lmbollen authored Aug 2, 2024
2 parents e81fb30 + fae81b7 commit d3f3722
Show file tree
Hide file tree
Showing 15 changed files with 282 additions and 10 deletions.
1 change: 1 addition & 0 deletions bittide-instances/bittide-instances.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ test-suite unittests

other-modules:
Tests.OverflowResistantDiff
Wishbone.DnaPortE2
Wishbone.Time

build-depends:
Expand Down
6 changes: 3 additions & 3 deletions bittide-instances/tests/Tests/OverflowResistantDiff.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-- SPDX-FileCopyrightText: 2023 Google LLC
-- SPDX-FileCopyrightText: 2023-2024 Google LLC
--
-- SPDX-License-Identifier: Apache-2.0

Expand All @@ -21,8 +21,8 @@ import qualified GHC.TypeNats as TN

import Bittide.Instances.Hitl.IlaPlot

ordGroup :: TestTree
ordGroup = testGroup "OverflowResistantDiff"
tests :: TestTree
tests = testGroup "OverflowResistantDiff"
[ testPropertyNamed "test with step-wise incrementing counter"
"testStepwise" $ ordTest True
, testPropertyNamed "test with randomly incrementing counter"
Expand Down
95 changes: 95 additions & 0 deletions bittide-instances/tests/Wishbone/DnaPortE2.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
-- SPDX-FileCopyrightText: 2024 Google LLC
--
-- SPDX-License-Identifier: Apache-2.0
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NumericUnderscores #-}
{-# OPTIONS_GHC -fplugin=Protocols.Plugin #-}

module Wishbone.DnaPortE2 where

import Clash.Explicit.Prelude
import Clash.Prelude(withClockResetEnable)

import Clash.Cores.UART(uart, ValidBaud)
import Clash.Cores.Xilinx.Unisim.DnaPortE2
import Clash.Explicit.Testbench
import Clash.Xilinx.ClockGen
import Data.Char
import Data.Maybe
import Language.Haskell.TH
import Numeric
import Project.FilePath
import Protocols
import System.FilePath
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.TH
import VexRiscv

import Bittide.DoubleBufferedRam
import Bittide.Instances.Domains
import Bittide.ProcessingElement
import Bittide.ProcessingElement.Util
import Bittide.SharedTypes
import Bittide.Wishbone
import Clash.Cores.UART.Extra(MaxBaudRate)

import qualified Prelude as P

-- | Test whether we can read the DNA from the DNA port peripheral.
case_dna_port_self_test :: Assertion
case_dna_port_self_test = assertBool msg (receivedDna == simDna2)
where
msg = "Received dna " <> showHex receivedDna "" <> " not equal to expected dna " <> showHex simDna2 ""
receivedDna = parseResult simResult
baud = SNat @(MaxBaudRate Basic50)
clk = clockGen
rst = resetGen
ena = enableGen
simResult = fmap (chr . fromIntegral) $ catMaybes $ sampleN 100_000 uartStream
(uartStream, _, _) = withClockResetEnable (clockGen @Basic50) rst ena $ uart baud uartTx (pure Nothing)
uartTx = dut baud (clockToDiffClock clk) rst (pure 0)

-- | A simple instance containing just VexRisc with UART and the DNA peripheral which
-- runs the `dna_port_e2_test` binary from `firmware-binaries`.
dut ::
forall dom baud .
(KnownDomain dom, ValidBaud dom baud) =>
SNat baud ->
"SYSCLK_300" ::: DiffClock Ext300 ->
"CPU_RESET" ::: Reset dom ->
"USB_UART_TX" ::: Signal dom Bit ->
"USB_UART_RX" ::: Signal dom Bit
dut baud diffClk rst_in usbUartTx = usbUartRx
where
(_, usbUartRx) = go ((usbUartTx, pure $ JtagIn low low low), pure ())

go =
toSignals $ withClockResetEnable clk200 rst200 enableGen $
circuit $ \(uartRx, jtag) -> do
[uartBus, dnaWb] <- processingElement @dom peConfig -< jtag
(uartTx, _uartStatus) <- uartWb d256 d16 baud -< (uartBus, uartRx)
readDnaPortE2Wb simDna2 -< dnaWb
idC -< uartTx

(clk200 :: Clock dom, pllLock :: Reset dom) = clockWizardDifferential diffClk noReset
rst200 = resetSynchronizer clk200 (unsafeOrReset rst_in pllLock)

(iMem, dMem) = $(do
root <- runIO $ findParentContaining "cabal.project"
let
elfDir = root </> firmwareBinariesDir "riscv32imc-unknown-none-elf" Release
elfPath = elfDir </> "dna_port_e2_test"

memBlobsFromElf BigEndian (Nothing, Nothing) elfPath Nothing)

peConfig =
PeConfig (0b00 :> 0b01 :> 0b10 :> 0b11 :> Nil)
(Reloadable $ Blob iMem)
(Reloadable $ Blob dMem)

parseResult :: String -> BitVector 96
parseResult = pack . (read :: String -> Unsigned 96) . P.head . lines

tests :: TestTree
tests = $(testGroupGenerator)
8 changes: 6 additions & 2 deletions bittide-instances/tests/unittests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@
module Main where

import Prelude

import Test.Tasty

import qualified Tests.OverflowResistantDiff as Ord
import qualified Wishbone.DnaPortE2 as DnaPortE2
import qualified Wishbone.Time as Time

import Tests.OverflowResistantDiff

tests :: TestTree
tests = testGroup "Unittests"
[ ordGroup
[ DnaPortE2.tests
, Ord.tests
, Time.tests
]

Expand Down
28 changes: 26 additions & 2 deletions bittide/src/Bittide/DoubleBufferedRam.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-- SPDX-FileCopyrightText: 2022 Google LLC
-- SPDX-FileCopyrightText: 2022-2024 Google LLC
--
-- SPDX-License-Identifier: Apache-2.0

Expand All @@ -16,7 +16,8 @@ import Clash.Prelude

import Data.Constraint
import Data.Maybe
import Protocols (Circuit (Circuit))
import Protocols (Circuit (Circuit), CSignal, Df, Ack (Ack))
import Protocols.Df (dataToMaybe)
import Protocols.Wishbone

import Bittide.Extra.Maybe
Expand Down Expand Up @@ -386,6 +387,29 @@ blockRamByteAddressableU readAddr newEntry byteSelect =
rstFunc = clashCompileError "blockRamByteAddressableU: reset function undefined"

data RegisterWritePriority = CircuitPriority | WishbonePriority
deriving Eq

-- | Register with additional wishbone interface, this component has a configurable
-- priority that determines which value gets stored in the register during a write conflict.
-- The `RegisterWritePriority` determines if the wishbone write gets accepted or if the
-- `Df` write gets accepted. The other value is discarded.
registerWbC ::
forall dom a nBytes aw .
(HiddenClockResetEnable dom, Paddable a, KnownNat nBytes, 1 <= nBytes, KnownNat aw, 2 <= aw) =>
-- | Determines the write priority on write collisions
RegisterWritePriority ->
-- | Initial value.
a ->
Circuit (Wishbone dom 'Standard aw (Bytes nBytes), Df dom a) (CSignal dom a)
registerWbC prio initVal = case cancelMulDiv @nBytes @8 of
Dict -> Circuit go
where
go ((wbM2S, fmap dataToMaybe -> dfM2S), _) = ((wbS2M, fmap Ack dfS2M), aOut)
where
(aOut, wbS2M) = registerWb prio initVal wbM2S dfM2S
dfS2M
| prio == WishbonePriority = (\WishboneM2S{..} -> not (strobe && busCycle)) <$> wbM2S
| otherwise = pure True

-- | Register with additional wishbone interface, this component has a configurable
-- priority that determines which value gets stored in the register during a write conflict.
Expand Down
30 changes: 29 additions & 1 deletion bittide/src/Bittide/Wishbone.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ module Bittide.Wishbone where
import Clash.Prelude

import Bittide.Arithmetic.Time(DomainFrequency)
import Bittide.DoubleBufferedRam
import Bittide.SharedTypes

import Clash.Cores.UART (uart, ValidBaud)
import Clash.Cores.Xilinx.Ila (ila, ilaConfig, IlaConfig(..), Depth)
import Clash.Cores.Xilinx.Unisim.DnaPortE2
import Clash.Util.Interpolate

import Data.Bifunctor
Expand Down Expand Up @@ -324,7 +326,7 @@ uartWb txDepth@SNat rxDepth@SNat baud = circuit $ \(wb, uartRx) -> do
(alignedAddr, alignment) = split @_ @(addrW - 2) @2 addr
internalAddr = bitCoerce $ resize alignedAddr :: Index 2
addrLegal = alignedAddr <= 1 && alignment == 0
rxEmpty = not $ isJust rxData
rxEmpty = isNothing rxData
status = (rxEmpty, txFull)
invalidReq = deepErrorX
[i|uartWb: Invalid request.
Expand Down Expand Up @@ -471,3 +473,29 @@ timeWb = Circuit $ \(wbM2S, _) -> (mealy goMealy (0,0) wbM2S, ())
RegisterBank (splitAtI -> (freqMsbs, freqLsbs)) = getRegsBe @8 freq
(writes, wbS2M) = wbToVec
(0 :> fmap pack (frozenLsbs :> frozenMsbs :> freqLsbs :> freqMsbs :> Nil)) wbM2S

-- | Wishbone wrapper for DnaPortE2, adds extra register with wishbone interface
-- to access the DNA device identifier. The DNA device identifier is a 96-bit
-- value, stored in big-endian format.
readDnaPortE2Wb ::
forall dom addrW nBytes .
( HiddenClockResetEnable dom
, KnownNat addrW
, 2 <= addrW
, KnownNat nBytes
, 1 <= nBytes
) =>
-- | Simulation DNA value
BitVector 96 ->
Circuit (Wishbone dom 'Standard addrW (Bytes nBytes)) ()
readDnaPortE2Wb simDna = circuit $ \wb -> do
dnaDf <- dnaCircuit -< ()
_dna <- reg -< (wb, dnaDf)
idC -< ()
where
maybeDna = readDnaPortE2 hasClock hasReset hasEnable simDna
regRst = unsafeFromActiveHigh $ register True
$ fmap isNothing maybeDna .||. unsafeToActiveHigh hasReset
reg = withReset regRst $ registerWbC @dom @_ @nBytes @addrW WishbonePriority 0
dnaCircuit :: Circuit () (Df dom (BitVector 96))
dnaCircuit = Circuit $ const ((), Df.maybeToData <$> maybeDna)
4 changes: 4 additions & 0 deletions bittide/src/Clash/Cores/UART/Extra.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@ import Protocols
import Protocols.Df hiding (catMaybes, sample, pure)
import System.IO

import Bittide.Arithmetic.Time
import Bittide.Wishbone

import qualified Protocols.Df as Df

-- | The maximum baud rate for a given domain, useful for simulation purposes
type MaxBaudRate dom = Div (DomainFrequency dom) 16

-- | A simulation function for circuits that expose a UART connection.
-- This function reads from the provided input handle and feeds that to the UART circuit.
-- Incoming uart data is written the the output handle.
Expand Down
9 changes: 9 additions & 0 deletions firmware-binaries/Cargo.lock

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

6 changes: 4 additions & 2 deletions firmware-binaries/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ codegen-units = 1
[workspace]
members = [
"examples/hello",

"test-cases/dna_port_e2_test",
"test-cases/time_self_test",

"clock-control-reg-cpy",
"clock-control",
"processing-element-test",
"clock-control",
"clock-control-reg-cpy",
"processing-element-test",
]
resolver = "2"
17 changes: 17 additions & 0 deletions firmware-binaries/test-cases/dna_port_e2_test/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# SPDX-FileCopyrightText: 2024 Google LLC
#
# SPDX-License-Identifier: CC0-1.0

[package]
name = "dna_port_e2_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"
bittide-sys = { path = "../../../firmware-support/bittide-sys"}
ufmt = "0.2.0"
23 changes: 23 additions & 0 deletions firmware-binaries/test-cases/dna_port_e2_test/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2024 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 firmware-binaries/test-cases/dna_port_e2_test/memory.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
SPDX-FileCopyrightText: 2024 Google LLC
SPDX-License-Identifier: CC0-1.0
*/

MEMORY
{
IMEM : ORIGIN = 0x80000000, LENGTH = 64K
DMEM : ORIGIN = 0x40000000, LENGTH = 32K
}

REGION_ALIAS("REGION_TEXT", IMEM);
REGION_ALIAS("REGION_RODATA", DMEM);
REGION_ALIAS("REGION_DATA", DMEM);
REGION_ALIAS("REGION_BSS", DMEM);
REGION_ALIAS("REGION_HEAP", DMEM);
REGION_ALIAS("REGION_STACK", DMEM);
32 changes: 32 additions & 0 deletions firmware-binaries/test-cases/dna_port_e2_test/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2024 Google LLC
//
// SPDX-License-Identifier: Apache-2.0
#![no_std]
#![cfg_attr(not(test), no_main)]

use ufmt::uwriteln;

use bittide_sys::dna_port_e2::{dna_to_u128, DnaValue};
use bittide_sys::uart::Uart;
#[cfg(not(test))]
use riscv_rt::entry;

const DNA_ADDR: *const DnaValue = 0xC000_0000 as *const DnaValue;

#[cfg_attr(not(test), entry)]
fn main() -> ! {
// Initialize peripherals.
let mut uart = unsafe { Uart::new(0x8000_0000 as *mut u8) };
let dna = dna_to_u128(unsafe { *DNA_ADDR });
uwriteln!(uart, "{}", dna).unwrap();
loop {
continue;
}
}

#[panic_handler]
fn panic_handler(_info: &core::panic::PanicInfo) -> ! {
loop {
continue;
}
}
14 changes: 14 additions & 0 deletions firmware-support/bittide-sys/src/dna_port_e2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: 2024 Google LLC
//
// SPDX-License-Identifier: Apache-2.0

/// The `DnaValue` type is a unsigned 96-bit integer. We represent it as a 12-byte array
/// because Rust does not have a built-in 96-bit integer type.
pub type DnaValue = [u8; 12];

/// Convert a `DnaValue` to a `u128` integer.
pub fn dna_to_u128(dna: DnaValue) -> u128 {
let mut u128_array = [0u8; 16];
u128_array[..12].copy_from_slice(&dna);
u128::from_le_bytes(u128_array)
}
Loading

0 comments on commit d3f3722

Please sign in to comment.