Skip to content

Commit

Permalink
Serializing fence implementation and test
Browse files Browse the repository at this point in the history
Co-authored-by: Seppe Vanderhallen <seppe.vanderhallen@hotmail.com>
  • Loading branch information
martonbognar and SeppeVDHH committed Jan 1, 2025
1 parent 46534bf commit d48439e
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 2 deletions.
3 changes: 2 additions & 1 deletion src/main/scala/riscv/Core.scala
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,8 @@ object createDynamicPipeline {
new TrapHandler(pipeline.retirementStage),
new MachineMode(pipeline.intAlu1),
new Interrupts(pipeline.retirementStage),
new Timers
new Timers,
new Fence
) ++ extraPlugins
)

Expand Down
1 change: 1 addition & 0 deletions src/main/scala/riscv/DynamicPipeline.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ trait DynamicPipeline extends Pipeline {

service[BranchTargetPredictorService].predictedPc(stage)
service[JumpService].jumpRequested(stage)
service[FenceService].isFence(stage)
}

// HACK make sure that all pipeline regs are routed through *all* exe stages.
Expand Down
2 changes: 2 additions & 0 deletions src/main/scala/riscv/RiscV.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ object Opcodes {
val DIVU = M"0000001----------101-----0110011"
val REM = M"0000001----------110-----0110011"
val REMU = M"0000001----------111-----0110011"

val FENCE = M"0000-------------000-----0001111"
}

case class Extension(char: Char) {
Expand Down
4 changes: 4 additions & 0 deletions src/main/scala/riscv/Services.scala
Original file line number Diff line number Diff line change
Expand Up @@ -442,3 +442,7 @@ trait FormalService {
trait Resettable {
def pipelineReset(): Unit
}

trait FenceService {
def isFence(stage: Stage): Bool
}
36 changes: 36 additions & 0 deletions src/main/scala/riscv/plugins/Fence.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package riscv.plugins

import riscv._
import spinal.core._

class Fence extends Plugin[DynamicPipeline] with FenceService {

object Data {
object FENCE extends PipelineData(Bool())
}

override def setup(): Unit = {
val issuer = pipeline.service[IssueService]
issuer.setDestinations(Opcodes.FENCE, pipeline.rsStages.toSet)

pipeline.service[DecoderService].configure { config =>
config.addDefault(
Map(
Data.FENCE -> False
)
)

config.addDecoding(
Opcodes.FENCE,
InstructionType.I,
Map(
Data.FENCE -> True
)
)
}
}

override def isFence(stage: Stage): Bool = {
stage.output(Data.FENCE)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ class ReorderBuffer(
private val isFullNext = Bool()
private val isFull = RegNext(isFullNext).init(False)
private val willRetire = False
val isAvailable = !isFull || willRetire

private val fenceDetectedNext = Bool()
private val fenceDetected = RegNext(fenceDetectedNext).init(False)
val isAvailable = (!isFull || willRetire) && !fenceDetectedNext

val pushInCycle = Bool()
pushInCycle := False
Expand All @@ -69,6 +72,7 @@ class ReorderBuffer(
oldestIndex.clear()
newestIndex.clear()
isFull := False
fenceDetected := False
}

private def byte2WordAddress(address: UInt) = {
Expand Down Expand Up @@ -138,6 +142,10 @@ class ReorderBuffer(
.operationOutput(issueStage)
pipeline.service[LsuService].addressValidOfBundle(pushedEntry.registerMap) := False

when(pipeline.service[FenceService].isFence(issueStage)) {
fenceDetected := True
}

val rs1 = Flow(UInt(5 bits))
val rs2 = Flow(UInt(5 bits))

Expand Down Expand Up @@ -242,6 +250,7 @@ class ReorderBuffer(

def build(): Unit = {
isFullNext := isFull
fenceDetectedNext := fenceDetected
val oldestEntry = robEntries(oldestIndex.value)
val updatedOldestIndex = UInt(indexBits)
updatedOldestIndex := oldestIndex.value
Expand All @@ -251,6 +260,10 @@ class ReorderBuffer(
ret.arbitration.isValid := False
ret.arbitration.isStalled := False

when(pipeline.service[FenceService].isFence(ret)) {
fenceDetectedNext := False
}

for (register <- retirementRegisters.keys) {
ret.input(register) := oldestEntry.registerMap.element(register)
}
Expand Down
2 changes: 2 additions & 0 deletions tests/fence-test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.bin
*_stripped.s
9 changes: 9 additions & 0 deletions tests/fence-test/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
%.elf: %.s
riscv32-unknown-elf-gcc -mabi=ilp32 -march=rv32im_zicsr -c -o $@ $<
riscv32-unknown-elf-gcc -mabi=ilp32 -march=rv32im_zicsr -ffreestanding -nostdlib -T ../tests.ld -o $@ $<

%.ihex: %.elf
riscv32-unknown-elf-objcopy -O ihex $< $@

%.bin: %.elf
riscv32-unknown-elf-objcopy -O binary $< $@
50 changes: 50 additions & 0 deletions tests/fence-test/eval.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env python3

import vcdvcd
import subprocess
import os


def evaluate():
vcd = vcdvcd.VCDVCD("sim.vcd", [])
addresses = vcd["TOP.Core.pipeline.dbus_cmd_payload_address[31:0]"]

violation = False

for (_, val) in addresses.tv:
addr = int(val, 2)
if str(hex(addr)) == "0xdead0":
violation = True

return violation

proteus_bin = os.path.join(os.path.abspath(os.path.dirname(__file__)), '../../sim/build/sim')

test_cases = [
"secret-before-branch",
"secret-after-branch",
]

for case in test_cases:
print(f"TEST {case}:")
# run test case with secure variant
subprocess.call(
["make", f"{case}.bin"])
subprocess.call([f"{proteus_bin}", f"{case}.bin"])
print("SECURE VARIANT: ", end='\t')
print("🗲 Secret leaked!" if evaluate() else "✔ Secret did not leak!")

# run test case with insecure variant:
# 1. remove fence instructions from code
with open(f"{case}.s") as source:
lines = source.readlines()
stripped = [
line for line in lines if not line.strip().startswith("fence") and not line.strip().startswith("sfence")]
with open(f"{case}_stripped.s", 'w') as stripped_file:
stripped_file.writelines(stripped)

subprocess.call(["make", f"{case}_stripped.bin"])
subprocess.call([f"{proteus_bin}", f"{case}_stripped.bin"])
print("INSECURE VARIANT:", end='\t')
print("🗲 Secret leaked!" if evaluate() else "✔ Secret did not leak!")
print()
48 changes: 48 additions & 0 deletions tests/fence-test/secret-after-branch.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
.globl _start
.data
public_value: .word 0
.space 1024 # padding to avoid caching
public_address: .word 0
.space 1024 # padding to avoid caching
secret: .word 0xDEAD0

.text
_start:

setup:
# public_address = &public_value;

fence

la t0, public_value
la t1, public_address
sw t0, (t1)

la t6, secret

condition:
# t0 = *public_address
# t0 = *t0
# if (t0 == 0) { goto finish; }

la t0, public_address
lw t0, (t0)
lw t0, (t0)
beqz t0, finish

# ----- BEGIN TRANSIENT BLOCK -----

# t6 = *secret;
lw t6, (t6)

fence

# leak(t6);
lw zero, (t6)

# ----- END TRANSIENT BLOCK -----

finish:
lui ra,0x10000
li sp,4
sb sp,0(ra)
49 changes: 49 additions & 0 deletions tests/fence-test/secret-before-branch.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
.globl _start
.data
public_value: .word 0
.space 1024 # padding to avoid caching
public_address: .word 0
.space 1024 # padding to avoid caching
secret: .word 0xDEAD0

.text
_start:

setup:
# public_address = &public_value;

la t0, public_value
la t1, public_address
sw t0, (t1)

nop
nop

# t6 = *secret // load secret architecturally
lw t6, secret

condition:
# t0 = *public_address
# t0 = *t0
# if (t0 == 0) { goto finish; }

la t0, public_address
lw t0, (t0)
lw t0, (t0)
beqz t0, finish

# ----- BEGIN TRANSIENT BLOCK -----

fence

# leak(t6);

lw zero, (t6)

# ----- END TRANSIENT BLOCK -----

finish:
lui ra,0x10000
li sp,4
sb sp,0(ra)

0 comments on commit d48439e

Please sign in to comment.