From 361c36a6e82e71a07bf13ac310ebe7f90332c827 Mon Sep 17 00:00:00 2001 From: Tifer King Date: Sat, 5 Apr 2025 02:57:08 +0800 Subject: [PATCH] Initial commit --- .gitignore | 7 + LICENSE | 2 +- README.md | 5 +- build.sbt | 16 ++ src/Config.scala | 18 +++ src/adds/Adds.scala | 297 +++++++++++++++++++++++++++++++++++++ src/adds/sim/AddsSim.scala | 95 ++++++++++++ 7 files changed, 438 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 build.sbt create mode 100644 src/Config.scala create mode 100644 src/adds/Adds.scala create mode 100644 src/adds/sim/AddsSim.scala diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3b64756 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/.bloop +/.metals +/.vscode +/project +/target +/simWorkspace +/output \ No newline at end of file diff --git a/LICENSE b/LICENSE index 0d21b9e..00cc045 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Public +Copyright (c) 2025 15inTech(www.15zk.net) 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: diff --git a/README.md b/README.md index de41e29..b6a019d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,5 @@ -# Adaptive_Direct_Digital_Synthesis +# 自适应直接信号合成器 +[使用手册](https://wiki.15zk.net/zh/opensource/Adaptive_Direct_Digital_Synthesis/manual) + +[应用案例: 使用IW-RFSOC-2T2R生成宽带信号]() \ No newline at end of file diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..65ba24b --- /dev/null +++ b/build.sbt @@ -0,0 +1,16 @@ +ThisBuild / version := "1.0" +ThisBuild / scalaVersion := "2.13.14" +ThisBuild / organization := "cn.tiferking" + +val spinalVersion = "1.12.0" +val spinalCore = "com.github.spinalhdl" %% "spinalhdl-core" % spinalVersion +val spinalLib = "com.github.spinalhdl" %% "spinalhdl-lib" % spinalVersion +val spinalIdslPlugin = compilerPlugin("com.github.spinalhdl" %% "spinalhdl-idsl-plugin" % spinalVersion) + +lazy val root = (project in file(".")) + .settings( + Compile / scalaSource := baseDirectory.value / "src", + libraryDependencies ++= Seq(spinalCore, spinalLib, spinalIdslPlugin) + ) + +fork := true \ No newline at end of file diff --git a/src/Config.scala b/src/Config.scala new file mode 100644 index 0000000..064a296 --- /dev/null +++ b/src/Config.scala @@ -0,0 +1,18 @@ +package project.config + +import spinal.core._ +import spinal.core.sim._ + +object Config { + def spinal = SpinalConfig( + targetDirectory = "output", + defaultConfigForClockDomains = ClockDomainConfig( + resetActiveLevel = LOW, + resetKind = SYNC, + clockEdge = RISING + ), + onlyStdLogicVectorAtTopLevelIo = true + ) + + def sim = SimConfig.withConfig(spinal) +} diff --git a/src/adds/Adds.scala b/src/adds/Adds.scala new file mode 100644 index 0000000..1154284 --- /dev/null +++ b/src/adds/Adds.scala @@ -0,0 +1,297 @@ +package adds + +import spinal.core._ +import spinal.lib._ +import spinal.lib.bus.amba4.axis._ +import spinal.lib.bus.amba4.axi.Axi4SpecRenamer +import spinal.lib.bus.amba4.axis.Axi4Stream._ +import spinal.lib.bus.amba4.axilite._ +import spinal.lib.io._ +import project.config._ + +case class Adds(phaseCh: Int, portBit: Int, resolutionBit: Int, sampleBit: Int, dualport: Boolean = false, simulation: Boolean = false) extends Component { + val sampleCount = Math.pow(2, sampleBit).toInt; + val regBit = 32; + + object Mode extends SpinalEnum(defaultEncoding = binarySequential) { + val TONE, LFM, SFM, FMCW = newElement() + } + + val io = new Bundle { + val AddsOut = master(Axi4Stream(Axi4StreamConfig(phaseCh * (portBit / 8)))) + val AddsOutQ = dualport generate master(Axi4Stream(Axi4StreamConfig(phaseCh * (portBit / 8)))) + val Cfg = slave(AxiLite4(10,32)) + + //Ports for simulations + val Sim = simulation generate (out Bits(portBit bits)) + val SimQ = simulation generate (out Bits(portBit bits)) + } + noIoPrefix(); + + Axi4SpecRenamer(io.AddsOut); + AxiLite4SpecRenamer(io.Cfg); + + val CfgClockDomain = ClockDomain.external("Cfg"); + + val CfgArea = new ClockingArea(CfgClockDomain) { + val resetSoftCfg = Reg(Bool()) init(False); + val enSoftCfg = Reg(Bool()) init(False); + val modeWorkCfg = Reg(Mode()) init(Mode.TONE); + val phaseCfg = Reg(UInt(32 bits)) init(0); + val dphaseCfg = Reg(UInt(32 bits)) init(0); + val ddphaseCfg = Reg(SInt(32 bits)) init(0); + val reloadCfg = Reg(UInt(32 bits)) init(0); + val thresholdCfg = Reg(UInt(32 bits)) init(0); + val stepholdCfg = Reg(UInt(32 bits)) init(0); + + val resetSoftReadbackX = Reg(Bool()) init(False) addTag(crossClockDomain); + val resetSoftReadback = RegNext(resetSoftReadbackX); + + when (resetSoftReadback) { + //Soft reset handshake + resetSoftCfg.clear(); + } + + when (resetSoftCfg) { + //Soft reset register, clear all + enSoftCfg.clearAll(); + modeWorkCfg.clearAll(); + phaseCfg.clearAll(); + dphaseCfg.clearAll(); + ddphaseCfg.clearAll(); + reloadCfg.clearAll(); + thresholdCfg.clearAll(); + stepholdCfg.clearAll(); + } + + val axiArea = new Area{ + // !simulation for fix the simulation master bug. + val axiSlave = new AxiLite4SlaveFactory(io.Cfg, !simulation); + axiSlave.readAndWrite(resetSoftCfg, 0x00, 0, "Soft reset"); + axiSlave.readAndWrite(enSoftCfg, 0x00, 1, "Soft enable"); + axiSlave.readAndWrite(modeWorkCfg, 0x00, 8, "Working mode"); + axiSlave.readAndWrite(phaseCfg, 0x04, 0, "Init phase"); + axiSlave.readAndWrite(dphaseCfg, 0x08, 0, "Init frequence"); + axiSlave.readAndWrite(ddphaseCfg, 0x0C, 0, "Init delt frequence"); + axiSlave.readAndWrite(reloadCfg, 0x10, 0, "Reload delay"); + axiSlave.readAndWrite(thresholdCfg, 0x14, 0, "Threshold for reload"); + axiSlave.readAndWrite(stepholdCfg, 0x18, 0, "Step hold for SFM"); + axiSlave.printDataModel(); + } + } + + //Reg cross clock domain + val resetSoftX = RegNext(CfgArea.resetSoftCfg) init(False) addTag(crossClockDomain); + val enSoftX = RegNext(CfgArea.enSoftCfg) init(False) addTag(crossClockDomain); + val modeWorkX = RegNext(CfgArea.modeWorkCfg) init(Mode.TONE) addTag(crossClockDomain); + val phaseX = RegNext(CfgArea.phaseCfg) init(0) addTag(crossClockDomain); + val dphaseX = RegNext(CfgArea.dphaseCfg) init(0) addTag(crossClockDomain); + val ddphaseX = RegNext(CfgArea.ddphaseCfg) init(0) addTag(crossClockDomain); + val reloadX = RegNext(CfgArea.reloadCfg) init(0) addTag(crossClockDomain); + val thresholdX = RegNext(CfgArea.thresholdCfg) init(0) addTag(crossClockDomain); + val stepholdX = RegNext(CfgArea.stepholdCfg) init(0) addTag(crossClockDomain); + + val resetSoft = RegNext(resetSoftX) init(False); + val enSoft = RegNext(enSoftX) init(False); + val modeWork = RegNext(modeWorkX) init(Mode.TONE); + val phase = RegNext(phaseX) init(0); + val dphase = RegNext(dphaseX) init(0); + val ddphase = RegNext(ddphaseX) init(0); + val reload = RegNext(reloadX) init(0); + val threshold = RegNext(thresholdX) init(0); + val stephold = RegNext(stepholdX) init(0); + + //Soft reset handshake + CfgArea.resetSoftReadbackX := resetSoft; + + def sinTable = for(sampleIndex <- 0 until sampleCount) yield { + val sinValue = Math.sin(2 * Math.PI * sampleIndex / sampleCount); + S((sinValue * ((1 << resolutionBit) / 2 - 1)).toInt, resolutionBit bits); + } + + val sinROM = Mem(SInt(resolutionBit bits), initialContent = sinTable); + + //Working register + val phaseWork = Vec.fill(phaseCh)(Reg(UInt(regBit bits)) init(0)); + val dphaseWork = Vec.fill(phaseCh)(Reg(UInt(regBit bits)) init(0)); + val ddphaseWork = Reg(SInt(regBit bits)) init(0); + + //Initialization procedure counter + val cntInit = Counter(0 to phaseCh + 5); + //Working register reload counter + val cntReload = Counter(regBit bits); + //Working register step hold counter + val cntStep = Counter(regBit bits); + //Reload event triggered by initialization procedure or self reload + val triggerReload = Reg(Bool()) init(False); + //Output buffer + val buffOut = Reg(Bits(phaseCh * portBit bits)) init(0); + //Output buffer for dual port + val buffOutQ = simulation generate (Reg(Bits(phaseCh * portBit bits)) init(0)); + //Disable the first uncertain output of the buffer + val buffFirst = Reg(Bool()) init(True); + + when(!enSoft) { + //Reset dds status and working register + phaseWork.clearAll(); + dphaseWork.clearAll(); + ddphaseWork.clearAll(); + buffOut.clearAll(); + dualport generate buffOutQ.clearAll(); + io.AddsOut.valid := False; + dualport generate (io.AddsOutQ.valid := False); + cntInit.clear(); + cntReload.clear(); + triggerReload.clear(); + cntStep.clear(); + buffFirst.set(); + } elsewhen(triggerReload) { + when(cntReload > reload || modeWork === Mode.FMCW) { + cntReload.clear(); + triggerReload.clear(); + cntInit.clear(); + } otherwise { + cntReload.increment(); + } + io.AddsOut.valid := True; + dualport generate (io.AddsOutQ.valid := True); + buffOut := 0; + dualport generate (buffOutQ := 0); + buffFirst.set(); + } elsewhen(cntInit < cntInit.end) { + when(modeWork === Mode.TONE) { + ddphaseWork := 0; + for(i <- 0 until phaseCh) + { + dphaseWork(i) := dphase; + } + } elsewhen(modeWork === Mode.LFM || modeWork === Mode.FMCW) { + ddphaseWork := ddphase; + dphaseWork(0) := dphase; + for(i <- 1 until phaseCh) + { + dphaseWork(i) := (dphaseWork(i - 1).asSInt + ddphaseWork).asUInt; + } + } elsewhen(modeWork === Mode.SFM) { + ddphaseWork := ddphase; + for(i <- 0 until phaseCh) + { + dphaseWork(i) := dphase; + } + } otherwise { + ddphaseWork := 0; + for(i <- 0 until phaseCh) + { + dphaseWork(i) := dphase; + } + } + + phaseWork(0) := phase; + for(i <- 1 until phaseCh) + { + phaseWork(i) := (phaseWork(i - 1) + dphaseWork(i)); + } + + io.AddsOut.valid := True; + dualport generate (io.AddsOutQ.valid := True); + buffOut := 0; + dualport generate (buffOutQ := 0); + cntInit.increment(); + cntReload.increment(); + cntStep.clear(); + buffFirst.set(); + + } otherwise { + when(modeWork === Mode.TONE) { + + ddphaseWork := 0; + for(i <- 0 until phaseCh) + { + dphaseWork(i) := dphase; + } + + } elsewhen(modeWork === Mode.LFM || modeWork === Mode.FMCW) { + + ddphaseWork := ddphase; + for(i <- 0 until phaseCh) + { + dphaseWork(i) := (dphaseWork(i).asSInt + (ddphaseWork * phaseCh).trim((log2Up(phaseCh) + 2) bits)).asUInt; + } + + when((!ddphase.sign && dphaseWork(phaseCh - 1) > threshold) || + (ddphase.sign && dphaseWork(phaseCh - 1) < threshold)) { + triggerReload := True; + } + + } elsewhen(modeWork === Mode.SFM) { + + when(cntStep < stephold) { + cntStep.increment(); + } elsewhen(cntStep === stephold) { + cntStep.increment(); + for(i <- 0 until phaseCh) + { + dphaseWork(i) := (dphaseWork(i).asSInt + ddphaseWork + (ddphaseWork * S(i).resize(log2Up(phaseCh) + 1)).trim((log2Up(phaseCh) + 1) bits)).asUInt; + } + } otherwise { + for(i <- 0 until phaseCh) + { + dphaseWork(i) := (dphaseWork(i).asSInt + (ddphaseWork * S(phaseCh - i -1).resize(log2Up(phaseCh) + 1)).trim((log2Up(phaseCh) + 1) bits)).asUInt; + } + cntStep.clear(); + } + + ddphaseWork := ddphase; + + when((!ddphase.sign && dphaseWork(phaseCh - 1) > threshold) || + (ddphase.sign && dphaseWork(phaseCh - 1) < threshold)) { + triggerReload := True; + } + } + + for(i <- 0 until phaseCh) + { + phaseWork(i) := phaseWork(i) + (dphaseWork(i) * phaseCh).trim((log2Up(phaseCh) + 1) bits); + } + cntReload.increment(); + io.AddsOut.valid := True; + dualport generate (io.AddsOutQ.valid := True); + when(buffFirst) { + buffOut := 0; + dualport generate (buffOutQ := 0); + buffFirst.clear(); + } otherwise { + for(i <- 0 until phaseCh) + { + buffOut(i * portBit, portBit bits) := sinROM.readSync(phaseWork(i).round(regBit - sampleBit)).asBits.resizeLeft(portBit); + dualport generate (buffOutQ(i * portBit, portBit bits) := sinROM.readSync(phaseWork(i).round(regBit - sampleBit) + sampleCount / 4).asBits.resizeLeft(portBit)); + } + } + } + // Init phase reg + + io.AddsOut.data := buffOut; + dualport generate (io.AddsOutQ.data := buffOutQ); + + val SimClockDomain = simulation generate ClockDomain.external("Sim"); + val clockSim = simulation generate Bool(); + simulation generate { + clockSim := clockDomain.readClockWire; + val SimArea = new ClockingArea(SimClockDomain) { + val cntSim = Counter(0 to phaseCh); + val clockIn = Bool() addTag(crossClockDomain); + clockIn := clockSim; + when(clockIn.rise()) { + cntSim.clear(); + } otherwise { + cntSim.increment(); + } + io.Sim := io.AddsOut.data(portBit * cntSim.value, portBit bits); + dualport generate (io.SimQ := buffOutQ(portBit * cntSim.value, portBit bits)); + } + } +} + +object GenAdds extends App { + Config.spinal.generateVerilog(Adds(16, 16, 12, 12)).printPruned(); +} diff --git a/src/adds/sim/AddsSim.scala b/src/adds/sim/AddsSim.scala new file mode 100644 index 0000000..2bb90ec --- /dev/null +++ b/src/adds/sim/AddsSim.scala @@ -0,0 +1,95 @@ +package adds.sim + +import spinal.core._ +import spinal.lib._ +import spinal.lib.bus.amba4.axis._ +import spinal.lib.bus.amba4.axi.Axi4SpecRenamer +import spinal.lib.bus.amba4.axis.Axi4Stream._ +import spinal.lib.bus.amba4.axilite._ +import spinal.lib.io._ +import project.config._ +import spinal.core.sim._ +import spinal.lib.bus.amba4.axilite.sim._ +import adds._ +import scala.util.control._ + +object AddsSim extends App { + Config.sim.withFstWave.compile(new Adds(16, 16, 12, 12, true, true)).doSim{ dut => + + dut.clockDomain.reset() + dut.CfgClockDomain.reset() + dut.CfgClockDomain.forkStimulus(250) + dut.clockDomain.forkStimulus(160) + dut.SimClockDomain.forkStimulus(10) + dut.clockDomain.waitRisingEdge() + dut.CfgClockDomain.waitRisingEdge() + dut.SimClockDomain.waitRisingEdge() + + val master = AxiLite4Master(dut.io.Cfg, dut.CfgClockDomain) + // TONE + master.reset(); + master.write(0x00, List(0x00.toByte, 0x00.toByte, 0x00.toByte, 0x00.toByte)); + master.write(0x04, List(0x00.toByte, 0x00.toByte, 0x00.toByte, 0x00.toByte)); + master.write(0x08, List(0xFF.toByte, 0xFF.toByte, 0xFF.toByte, 0x00.toByte)); + master.write(0x0C, List(0x00.toByte, 0x10.toByte, 0x00.toByte, 0x00.toByte)); + master.write(0x10, List(0x00.toByte, 0x00.toByte, 0x00.toByte, 0x01.toByte)); + master.write(0x14, List(0x00.toByte, 0x00.toByte, 0x00.toByte, 0x80.toByte)); + master.write(0x18, List(0x10.toByte, 0x00.toByte, 0x00.toByte, 0x00.toByte)); + master.write(0x00, List(0x02.toByte, 0x00.toByte, 0x00.toByte, 0x00.toByte)); + dut.io.AddsOut.ready #= true + + dut.clockDomain.waitRisingEdge(); + dut.clockDomain.waitEdge(100000); + + dut.clockDomain.waitRisingEdge(); + + // LFM + master.reset(); + master.write(0x00, List(0x00.toByte, 0x00.toByte, 0x00.toByte, 0x00.toByte)); + master.write(0x04, List(0x00.toByte, 0x00.toByte, 0x00.toByte, 0x00.toByte)); + master.write(0x08, List(0xFF.toByte, 0xFF.toByte, 0xFF.toByte, 0x00.toByte)); + master.write(0x0C, List(0x00.toByte, 0x10.toByte, 0x00.toByte, 0x00.toByte)); + master.write(0x10, List(0x00.toByte, 0x00.toByte, 0x00.toByte, 0x01.toByte)); + master.write(0x14, List(0x00.toByte, 0x00.toByte, 0x00.toByte, 0x80.toByte)); + master.write(0x18, List(0x10.toByte, 0x00.toByte, 0x00.toByte, 0x00.toByte)); + master.write(0x00, List(0x02.toByte, 0x01.toByte, 0x00.toByte, 0x00.toByte)); + dut.io.AddsOut.ready #= true + + dut.clockDomain.waitRisingEdge(); + dut.clockDomain.waitEdge(100000); + + dut.clockDomain.waitRisingEdge(); + + // SFM + master.reset(); + master.write(0x00, List(0x00.toByte, 0x00.toByte, 0x00.toByte, 0x00.toByte)); + master.write(0x04, List(0x00.toByte, 0x00.toByte, 0x00.toByte, 0x00.toByte)); + master.write(0x08, List(0xFF.toByte, 0xFF.toByte, 0xFF.toByte, 0x00.toByte)); + master.write(0x0C, List(0x00.toByte, 0x10.toByte, 0x00.toByte, 0x00.toByte)); + master.write(0x10, List(0x00.toByte, 0x00.toByte, 0x00.toByte, 0x01.toByte)); + master.write(0x14, List(0x00.toByte, 0x00.toByte, 0x00.toByte, 0x80.toByte)); + master.write(0x18, List(0x10.toByte, 0x00.toByte, 0x00.toByte, 0x00.toByte)); + master.write(0x00, List(0x02.toByte, 0x02.toByte, 0x00.toByte, 0x00.toByte)); + dut.io.AddsOut.ready #= true + + dut.clockDomain.waitRisingEdge(); + dut.clockDomain.waitEdge(100000); + + dut.clockDomain.waitRisingEdge(); + + // FMCW + master.reset(); + master.write(0x00, List(0x00.toByte, 0x00.toByte, 0x00.toByte, 0x00.toByte)); + master.write(0x04, List(0x00.toByte, 0x00.toByte, 0x00.toByte, 0x00.toByte)); + master.write(0x08, List(0xFF.toByte, 0xFF.toByte, 0xFF.toByte, 0x00.toByte)); + master.write(0x0C, List(0x00.toByte, 0x10.toByte, 0x00.toByte, 0x00.toByte)); + master.write(0x10, List(0x00.toByte, 0x00.toByte, 0x00.toByte, 0x01.toByte)); + master.write(0x14, List(0x00.toByte, 0x00.toByte, 0x00.toByte, 0x80.toByte)); + master.write(0x18, List(0x10.toByte, 0x00.toByte, 0x00.toByte, 0x00.toByte)); + master.write(0x00, List(0x02.toByte, 0x03.toByte, 0x00.toByte, 0x00.toByte)); + dut.io.AddsOut.ready #= true + + dut.clockDomain.waitRisingEdge(); + dut.clockDomain.waitEdge(100000); + } +} \ No newline at end of file