<!– SPDX-FileCopyrightText: 2026 Ahmed Imamović SPDX-FileCopyrightText: 2026 Tarik Hamedović SPDX-License-Identifier: CC-BY-SA-4.0 –>
DAC
The DAC interface is the output-side converter block of the reusable DSP datapath. It takes two parallel 14-bit input words and drives them to two DAC channels using DDR output primitives.
dac/dac.v
This module is the main board-facing DAC wrapper. It accepts two 14-bit input
words, named data1 and data2, and presents them to two DAC channels as
DDR-driven outputs.
In the current design, the intended mapping is:
data1– first DAC channel input,data2– second DAC channel input.
Responsibilities
The block performs the following tasks:
generates forwarded DAC clocks on
da1_clkandda2_clk,generates DAC write strobes on
da1_wrtandda2_wrt,drives the 14-bit DAC data buses
da1_dataandda2_data,uses vendor
ODDRprimitives to place output timing directly at the FPGA pins.
Interface summary
System signals:
sys_clk– system clock used to generate the forwarded DAC clocks, write strobes, and data outputs,rst_n– active-low reset input, currently unused by the implementation.
Input data:
data1[13:0]– 14-bit sample word for DAC channel 1,data2[13:0]– 14-bit sample word for DAC channel 2.
DAC-side outputs:
da1_clk– forwarded clock for DAC channel 1,da1_wrt– write strobe for DAC channel 1,da1_data[13:0]– 14-bit output data bus for DAC channel 1,da2_clk– forwarded clock for DAC channel 2,da2_wrt– write strobe for DAC channel 2,da2_data[13:0]– 14-bit output data bus for DAC channel 2.
Implementation notes
The module uses ODDR primitives for all converter-facing outputs.
Clock and write-strobe generation
For each DAC channel, the forwarded clock and write strobe are generated using
an ODDR configured with:
.D1(1'b0),
.D2(1'b1)
This produces a repeating DDR output pattern derived from sys_clk and is
used to create the required converter-side timing signals.
Data output generation
Each bit of the 14-bit output bus is driven by its own ODDR instance.
Unlike a true dual-edge serializer carrying different values on rising and
falling edges, this implementation drives the same value on both edges:
.D1(data1[bit]),
.D2(data1[bit])
for channel 1, and similarly for channel 2.
As a result, each DAC data bit remains logically constant across the full DDR
cycle, while still being emitted through dedicated output DDR resources. This
approach is useful when the interface timing must be aligned with DDR-style
converter clocks and strobes, but the payload itself is updated once per
sys_clk cycle.
Structure
The module is organized into three parts:
ODDRgeneration ofda1_clkandda2_clk,ODDRgeneration ofda1_wrtandda2_wrt,per-bit
ODDRgeneration forda1_dataandda2_data.
RTL source
// ============================================================================
// dac.v
//
// This module takes two 14-bit words (data1 = sine, data2 = cosine) and
// drives them out via DDR ODDR cells on da?_clk, da?_wrt, da?_data.
// ============================================================================
module dac (
input wire sys_clk,
input wire rst_n,
// 14-bit input words
input wire [13:0] data1,
input wire [13:0] data2,
// DDR outputs for DAC #1
output wire da1_clk, // half-rate clock strobe
output wire da1_wrt, // write strobe (always “1” on positive edge)
output wire [13:0] da1_data, // 14-bit data bus
// DDR outputs for DAC #2
output wire da2_clk, // half-rate clock strobe
output wire da2_wrt, // write strobe (always “1” on positive edge)
output wire [13:0] da2_data // 14-bit data bus
);
genvar bit;
//----------------------------------------------------------------------------
// Clocks & write strobes (always toggling 0→1 on SYS_CLK via ODDR)
//----------------------------------------------------------------------------
ODDR #(
.DDR_CLK_EDGE("SAME_EDGE")
) oddr_clk1 (
.Q (da1_clk),
.C (sys_clk),
.CE (1'b1),
.D1 (1'b0),
.D2 (1'b1),
.R (1'b0),
.S (1'b0)
);
ODDR #(
.DDR_CLK_EDGE("SAME_EDGE")
) oddr_wrt1 (
.Q (da1_wrt),
.C (sys_clk),
.CE (1'b1),
.D1 (1'b0),
.D2 (1'b1),
.R (1'b0),
.S (1'b0)
);
ODDR #(
.DDR_CLK_EDGE("SAME_EDGE")
) oddr_clk2 (
.Q (da2_clk),
.C (sys_clk),
.CE (1'b1),
.D1 (1'b0),
.D2 (1'b1),
.R (1'b0),
.S (1'b0)
);
ODDR #(
.DDR_CLK_EDGE("SAME_EDGE")
) oddr_wrt2 (
.Q (da2_wrt),
.C (sys_clk),
.CE (1'b1),
.D1 (1'b0),
.D2 (1'b1),
.R (1'b0),
.S (1'b0)
);
//----------------------------------------------------------------------------
// DDR ODDR for each data bit: channel1 = data1, channel2 = data2
//----------------------------------------------------------------------------
generate
for (bit = 0; bit < 14; bit = bit + 1) begin : DAC1_DATA
ODDR #(
.DDR_CLK_EDGE("SAME_EDGE")
) oddr_d1 (
.Q (da1_data[bit]),
.C (sys_clk),
.CE (1'b1),
.D1 (data1[bit]),
.D2 (data1[bit]),
.R (1'b0),
.S (1'b0)
);
end
endgenerate
generate
for (bit = 0; bit < 14; bit = bit + 1) begin : DAC2_DATA
ODDR #(
.DDR_CLK_EDGE("SAME_EDGE")
) oddr_d2 (
.Q (da2_data[bit]),
.C (sys_clk),
.CE (1'b1),
.D1 (data2[bit]),
.D2 (data2[bit]),
.R (1'b0),
.S (1'b0)
);
end
endgenerate
endmodule