Compare commits

..

25 Commits

Author SHA1 Message Date
3c13e3289a Merge branch 'new_structure' 2026-03-02 19:38:21 +01:00
4e3521e94a Added missing signal modules 2026-03-02 19:28:36 +01:00
50f71a2985 Added new master checker and changed to synchronous reset checks 2026-03-02 18:02:47 +01:00
628972ccaa New slave checker and updated gpio and gpio_banks 2026-03-02 13:48:30 +01:00
a6a5c6ea3f Made timer synthesizable 2026-03-01 21:11:08 +01:00
5b940758b6 Added formal verification set to timer internally 2026-03-01 21:00:57 +01:00
abe0668787 new timer 2026-03-01 17:19:46 +01:00
7b46ae5e87 Some cleanup and added formal for the banks and timer 2026-03-01 14:12:12 +01:00
8289b0d090 Added wb formal script and added other sby tasks 2026-03-01 13:52:41 +01:00
cf483decad Added everything from the other system 2026-02-28 21:52:06 +01:00
907f244b24 Added libjtag_wb_bridge 2026-02-28 18:39:50 +01:00
cf7e03b9fe Added some stuff from modem and added formal 2026-02-28 18:23:39 +01:00
fa641b1eab Initial commit
Added nco_q15
2026-02-28 13:52:08 +01:00
105dbed8e4 Added back in the jtag bridge
Now talking over the bus instead of using dpram
2026-02-27 17:39:43 +01:00
6f680377db jtag memory selectable 2026-02-27 16:09:33 +01:00
3a9b2acf9e New wishbone-jtag bridge 2026-02-27 15:56:56 +01:00
838204653a TImer working with tests
TODO: think of other way of shifting in data. Bit errors make uploading difficult
2026-02-25 22:01:28 +01:00
3a3c951409 Added timer, still wip 2026-02-25 20:54:12 +01:00
f2f9644830 Added qerv files 2026-02-25 20:52:07 +01:00
13f72e698f jtag memory interface working 2026-02-25 16:14:37 +01:00
9930ce4461 Working CPP way of writing data 2026-02-24 16:40:17 +01:00
8f4e887b9d Added JTAG interface with testbench 2026-02-23 15:37:49 +01:00
20cfece6e3 Added soclet with gpio banks to top 2026-02-22 20:00:42 +01:00
a97028c2ba cleanup 2026-02-22 18:49:03 +01:00
5e951f9b61 Working SERV cpu 2026-02-22 18:48:17 +01:00
158 changed files with 6720 additions and 5789 deletions

5
.gitignore vendored
View File

@@ -1,3 +1,2 @@
out
build
env
build/
out/

6
.gitmodules vendored
View File

@@ -0,0 +1,6 @@
[submodule "fusesoc_libraries/serv"]
path = fusesoc_libraries/serv
url = git@github.com:Jojojoppe/serv.git
[submodule "fusesoc_libraries/fusesoc-cores"]
path = fusesoc_libraries/fusesoc-cores
url = https://github.com/fusesoc/fusesoc-cores

View File

@@ -1,30 +0,0 @@
# Main clock input
NET "aclk" LOC = P126;
NET "aclk" TNM_NET = "SYS_CLK_PIN";
TIMESPEC TS_SYS_CLK_PIN = PERIOD "SYS_CLK_PIN" 10 ns HIGH 50 %;
# Generated clocks
NET "clk_15" TNM_NET = "SYS_CLK_15";
TIMESPEC TS_SYS_CLK_15 = PERIOD "SYS_CLK_15" 13.334 ns HIGH 50%;
# Boards button row
NET "aresetn" LOC = P120;
NET "aresetn" IOSTANDARD = LVCMOS33;
NET "aresetn" PULLUP;
NET "led_green" LOC = P29;
NET "led_green" IOSTANDARD = LVCMOS33;
NET "led_red" LOC = P26;
NET "led_red" IOSTANDARD = LVCMOS33;
NET "r2r[0]" LOC = P131;
NET "r2r[1]" LOC = P133;
NET "r2r[2]" LOC = P137;
NET "r2r[3]" LOC = P139;
NET "r2r[4]" LOC = P141;
NET "r2r[5]" LOC = P1;
NET "r2r[0]" IOSTANDARD = LVCMOS33;
NET "r2r[1]" IOSTANDARD = LVCMOS33;
NET "r2r[2]" IOSTANDARD = LVCMOS33;
NET "r2r[3]" IOSTANDARD = LVCMOS33;
NET "r2r[4]" IOSTANDARD = LVCMOS33;
NET "r2r[5]" IOSTANDARD = LVCMOS33;

View File

@@ -1,226 +0,0 @@
SELECT Clocking_Wizard xilinx.com:ip:clk_wiz:3.6
CSET calc_done=DONE
CSET clk_in_sel_port=CLK_IN_SEL
CSET clk_out1_port=CLK_OUT1
CSET clk_out1_use_fine_ps_gui=false
CSET clk_out2_port=CLK_OUT2
CSET clk_out2_use_fine_ps_gui=false
CSET clk_out3_port=CLK_OUT3
CSET clk_out3_use_fine_ps_gui=false
CSET clk_out4_port=CLK_OUT4
CSET clk_out4_use_fine_ps_gui=false
CSET clk_out5_port=CLK_OUT5
CSET clk_out5_use_fine_ps_gui=false
CSET clk_out6_port=CLK_OUT6
CSET clk_out6_use_fine_ps_gui=false
CSET clk_out7_port=CLK_OUT7
CSET clk_out7_use_fine_ps_gui=false
CSET clk_valid_port=CLK_VALID
CSET clkfb_in_n_port=CLKFB_IN_N
CSET clkfb_in_p_port=CLKFB_IN_P
CSET clkfb_in_port=CLKFB_IN
CSET clkfb_in_signaling=SINGLE
CSET clkfb_out_n_port=CLKFB_OUT_N
CSET clkfb_out_p_port=CLKFB_OUT_P
CSET clkfb_out_port=CLKFB_OUT
CSET clkfb_stopped_port=CLKFB_STOPPED
CSET clkin1_jitter_ps=100.0
CSET clkin1_ui_jitter=0.010
CSET clkin2_jitter_ps=100.0
CSET clkin2_ui_jitter=0.010
CSET clkout1_drives=BUFG
CSET clkout1_requested_duty_cycle=50.000
CSET clkout1_requested_out_freq=15.000
CSET clkout1_requested_phase=0.000
CSET clkout2_drives=BUFG
CSET clkout2_requested_duty_cycle=50.000
CSET clkout2_requested_out_freq=100.000
CSET clkout2_requested_phase=0.000
CSET clkout2_used=false
CSET clkout3_drives=BUFG
CSET clkout3_requested_duty_cycle=50.000
CSET clkout3_requested_out_freq=100.000
CSET clkout3_requested_phase=0.000
CSET clkout3_used=false
CSET clkout4_drives=BUFG
CSET clkout4_requested_duty_cycle=50.000
CSET clkout4_requested_out_freq=100.000
CSET clkout4_requested_phase=0.000
CSET clkout4_used=false
CSET clkout5_drives=BUFG
CSET clkout5_requested_duty_cycle=50.000
CSET clkout5_requested_out_freq=100.000
CSET clkout5_requested_phase=0.000
CSET clkout5_used=false
CSET clkout6_drives=BUFG
CSET clkout6_requested_duty_cycle=50.000
CSET clkout6_requested_out_freq=100.000
CSET clkout6_requested_phase=0.000
CSET clkout6_used=false
CSET clkout7_drives=BUFG
CSET clkout7_requested_duty_cycle=50.000
CSET clkout7_requested_out_freq=100.000
CSET clkout7_requested_phase=0.000
CSET clkout7_used=false
CSET clock_mgr_type=AUTO
CSET component_name=clk_gen
CSET daddr_port=DADDR
CSET dclk_port=DCLK
CSET dcm_clk_feedback=1X
CSET dcm_clk_out1_port=CLKFX
CSET dcm_clk_out2_port=CLK0
CSET dcm_clk_out3_port=CLK0
CSET dcm_clk_out4_port=CLK0
CSET dcm_clk_out5_port=CLK0
CSET dcm_clk_out6_port=CLK0
CSET dcm_clkdv_divide=2.0
CSET dcm_clkfx_divide=20
CSET dcm_clkfx_multiply=3
CSET dcm_clkgen_clk_out1_port=CLKFX
CSET dcm_clkgen_clk_out2_port=CLKFX
CSET dcm_clkgen_clk_out3_port=CLKFX
CSET dcm_clkgen_clkfx_divide=1
CSET dcm_clkgen_clkfx_md_max=0.000
CSET dcm_clkgen_clkfx_multiply=4
CSET dcm_clkgen_clkfxdv_divide=2
CSET dcm_clkgen_clkin_period=10.000
CSET dcm_clkgen_notes=None
CSET dcm_clkgen_spread_spectrum=NONE
CSET dcm_clkgen_startup_wait=false
CSET dcm_clkin_divide_by_2=false
CSET dcm_clkin_period=10.000
CSET dcm_clkout_phase_shift=NONE
CSET dcm_deskew_adjust=SYSTEM_SYNCHRONOUS
CSET dcm_notes=None
CSET dcm_phase_shift=0
CSET dcm_pll_cascade=NONE
CSET dcm_startup_wait=false
CSET den_port=DEN
CSET din_port=DIN
CSET dout_port=DOUT
CSET drdy_port=DRDY
CSET dwe_port=DWE
CSET feedback_source=FDBK_AUTO
CSET in_freq_units=Units_MHz
CSET in_jitter_units=Units_UI
CSET input_clk_stopped_port=INPUT_CLK_STOPPED
CSET jitter_options=UI
CSET jitter_sel=No_Jitter
CSET locked_port=LOCKED
CSET mmcm_bandwidth=OPTIMIZED
CSET mmcm_clkfbout_mult_f=4.000
CSET mmcm_clkfbout_phase=0.000
CSET mmcm_clkfbout_use_fine_ps=false
CSET mmcm_clkin1_period=10.000
CSET mmcm_clkin2_period=10.000
CSET mmcm_clkout0_divide_f=4.000
CSET mmcm_clkout0_duty_cycle=0.500
CSET mmcm_clkout0_phase=0.000
CSET mmcm_clkout0_use_fine_ps=false
CSET mmcm_clkout1_divide=1
CSET mmcm_clkout1_duty_cycle=0.500
CSET mmcm_clkout1_phase=0.000
CSET mmcm_clkout1_use_fine_ps=false
CSET mmcm_clkout2_divide=1
CSET mmcm_clkout2_duty_cycle=0.500
CSET mmcm_clkout2_phase=0.000
CSET mmcm_clkout2_use_fine_ps=false
CSET mmcm_clkout3_divide=1
CSET mmcm_clkout3_duty_cycle=0.500
CSET mmcm_clkout3_phase=0.000
CSET mmcm_clkout3_use_fine_ps=false
CSET mmcm_clkout4_cascade=false
CSET mmcm_clkout4_divide=1
CSET mmcm_clkout4_duty_cycle=0.500
CSET mmcm_clkout4_phase=0.000
CSET mmcm_clkout4_use_fine_ps=false
CSET mmcm_clkout5_divide=1
CSET mmcm_clkout5_duty_cycle=0.500
CSET mmcm_clkout5_phase=0.000
CSET mmcm_clkout5_use_fine_ps=false
CSET mmcm_clkout6_divide=1
CSET mmcm_clkout6_duty_cycle=0.500
CSET mmcm_clkout6_phase=0.000
CSET mmcm_clkout6_use_fine_ps=false
CSET mmcm_clock_hold=false
CSET mmcm_compensation=ZHOLD
CSET mmcm_divclk_divide=1
CSET mmcm_notes=None
CSET mmcm_ref_jitter1=0.010
CSET mmcm_ref_jitter2=0.010
CSET mmcm_startup_wait=false
CSET num_out_clks=1
CSET override_dcm=false
CSET override_dcm_clkgen=false
CSET override_mmcm=false
CSET override_pll=false
CSET platform=lin
CSET pll_bandwidth=OPTIMIZED
CSET pll_clk_feedback=CLKFBOUT
CSET pll_clkfbout_mult=4
CSET pll_clkfbout_phase=0.000
CSET pll_clkin_period=10.0
CSET pll_clkout0_divide=128
CSET pll_clkout0_duty_cycle=0.500
CSET pll_clkout0_phase=0.000
CSET pll_clkout1_divide=1
CSET pll_clkout1_duty_cycle=0.500
CSET pll_clkout1_phase=0.000
CSET pll_clkout2_divide=1
CSET pll_clkout2_duty_cycle=0.500
CSET pll_clkout2_phase=0.000
CSET pll_clkout3_divide=1
CSET pll_clkout3_duty_cycle=0.500
CSET pll_clkout3_phase=0.000
CSET pll_clkout4_divide=1
CSET pll_clkout4_duty_cycle=0.500
CSET pll_clkout4_phase=0.000
CSET pll_clkout5_divide=1
CSET pll_clkout5_duty_cycle=0.500
CSET pll_clkout5_phase=0.000
CSET pll_compensation=SYSTEM_SYNCHRONOUS
CSET pll_divclk_divide=1
CSET pll_notes=None
CSET pll_ref_jitter=0.010
CSET power_down_port=POWER_DOWN
CSET prim_in_freq=100.000
CSET prim_in_jitter=0.010
CSET prim_source=Global_buffer
CSET primary_port=CLK_IN1
CSET primitive=MMCM
CSET primtype_sel=PLL_BASE
CSET psclk_port=PSCLK
CSET psdone_port=PSDONE
CSET psen_port=PSEN
CSET psincdec_port=PSINCDEC
CSET relative_inclk=REL_PRIMARY
CSET reset_port=RESET
CSET secondary_in_freq=100.000
CSET secondary_in_jitter=0.010
CSET secondary_port=CLK_IN2
CSET secondary_source=Single_ended_clock_capable_pin
CSET ss_mod_freq=250
CSET ss_mode=CENTER_HIGH
CSET status_port=STATUS
CSET summary_strings=empty
CSET use_clk_valid=false
CSET use_clkfb_stopped=false
CSET use_dyn_phase_shift=false
CSET use_dyn_reconfig=false
CSET use_freeze=false
CSET use_freq_synth=true
CSET use_inclk_stopped=false
CSET use_inclk_switchover=false
CSET use_locked=false
CSET use_max_i_jitter=false
CSET use_min_o_jitter=false
CSET use_min_power=false
CSET use_phase_alignment=true
CSET use_power_down=false
CSET use_reset=false
CSET use_spread_spectrum=false
CSET use_spread_spectrum_1=false
CSET use_status=false
GENERATE

95
check_formal.sh Executable file
View File

@@ -0,0 +1,95 @@
#!/bin/sh
# Add or remove cores here. Each entry should be a full FuseSoC VLNV.
CORES="
joppeb:wb:wb_mem32
joppeb:wb:wb_gpio
joppeb:wb:wb_gpio_banks
joppeb:wb:jtag_wb_bridge
joppeb:wb:wb_timer
"
if [ -n "$1" ]; then
CORES="$1"
fi
# Add or remove formal tasks here.
TASKS="
bmc
cover
prove
"
total=0
passed=0
failed=0
passed_runs=""
failed_runs=""
run_task() {
core="$1"
task="$2"
label="$core [$task]"
log_file=""
total=$((total + 1))
printf '\n[%d] Running formal %s for %s\n' "$total" "$task" "$core"
log_file=$(mktemp /tmp/check_formal.XXXXXX) || exit 2
if \
FUSESOC_CORE="$core" \
FUSESOC_TASK="$task" \
script -qefc 'fusesoc run --target formal "$FUSESOC_CORE" --taskname "$FUSESOC_TASK"' "$log_file" \
>/dev/null 2>&1
then
passed=$((passed + 1))
passed_runs="$passed_runs
$label"
printf 'Result: PASS (%s)\n' "$label"
rm -f "$log_file"
return 0
else
failed=$((failed + 1))
failed_runs="$failed_runs
$label"
printf 'Result: FAIL (%s)\n' "$label"
printf 'Captured log for %s:\n' "$label"
cat "$log_file" #| grep summary
rm -f "$log_file"
return 1
fi
}
for core in $CORES; do
for task in $TASKS; do
run_task "$core" "$task"
done
done
printf '\nFormal run summary\n'
printf ' Total: %d\n' "$total"
printf ' Passed: %d\n' "$passed"
printf ' Failed: %d\n' "$failed"
if [ -n "$passed_runs" ]; then
printf '\nPassed runs:\n'
printf '%s\n' "$passed_runs" | while IFS= read -r run; do
if [ -n "$run" ]; then
printf ' - %s\n' "$run"
fi
done
fi
if [ -n "$failed_runs" ]; then
printf '\nFailed runs:\n'
printf '%s\n' "$failed_runs" | while IFS= read -r run; do
if [ -n "$run" ]; then
printf ' - %s\n' "$run"
fi
done
fi
if [ "$failed" -ne 0 ]; then
exit 1
fi

View File

@@ -0,0 +1,49 @@
CAPI=2:
name: joppeb:primitive:clkgen:1.0
description: Parameterized clock generator wrapper
filesets:
wrapper:
files:
- clkgen.v
file_type: verilogSource
generic:
files:
- clkgen_generic_impl.v
file_type: verilogSource
spartan6:
files:
- clkgen_spartan6.v
file_type: verilogSource
targets:
default:
filesets:
- wrapper
- generic
- spartan6
toplevel: clkgen
parameters:
- CLK_IN_HZ
- CLKFX_DIVIDE
- CLKFX_MULTIPLY
- CLKDV_DIVIDE
parameters:
CLK_IN_HZ:
datatype: int
description: Input clock frequency in Hz
paramtype: vlogparam
CLKFX_DIVIDE:
datatype: int
description: DCM CLKFX divide value
paramtype: vlogparam
CLKFX_MULTIPLY:
datatype: int
description: DCM CLKFX multiply value
paramtype: vlogparam
CLKDV_DIVIDE:
datatype: real
description: DCM CLKDV divide value
paramtype: vlogparam

View File

@@ -0,0 +1,37 @@
`timescale 1ns/1ps
// =============================================================================
// Clock generator
// Stable public wrapper that selects the implementation.
// =============================================================================
module clkgen #(
parameter integer CLK_IN_HZ = 100000000,
parameter integer CLKFX_DIVIDE = 20,
parameter integer CLKFX_MULTIPLY = 3,
parameter real CLKDV_DIVIDE = 2.0
)(
input wire clk_in,
output wire clk_out
);
`ifdef FPGA_SPARTAN6
clkgen_spartan6_impl #(
.CLK_IN_HZ(CLK_IN_HZ),
.CLKFX_DIVIDE(CLKFX_DIVIDE),
.CLKFX_MULTIPLY(CLKFX_MULTIPLY),
.CLKDV_DIVIDE(CLKDV_DIVIDE)
) impl_i (
.clk_in(clk_in),
.clk_out(clk_out)
);
`else
clkgen_generic_impl #(
.CLK_IN_HZ(CLK_IN_HZ),
.CLKFX_DIVIDE(CLKFX_DIVIDE),
.CLKFX_MULTIPLY(CLKFX_MULTIPLY),
.CLKDV_DIVIDE(CLKDV_DIVIDE)
) impl_i (
.clk_in(clk_in),
.clk_out(clk_out)
);
`endif
endmodule

View File

@@ -0,0 +1,29 @@
`timescale 1ns/1ps
// =============================================================================
// Clock generator
// Generic behavioural model. This is intended for simulation only.
// =============================================================================
module clkgen_generic_impl #(
parameter integer CLK_IN_HZ = 100000000,
parameter integer CLKFX_DIVIDE = 20,
parameter integer CLKFX_MULTIPLY = 3,
parameter real CLKDV_DIVIDE = 2.0
)(
input wire clk_in,
output reg clk_out
);
real half_period_ns;
initial begin
clk_out = 1'b0;
half_period_ns = (500000000.0 * CLKFX_DIVIDE) / (CLK_IN_HZ * CLKFX_MULTIPLY);
// Start oscillation after the source clock becomes active.
@(posedge clk_in);
forever #(half_period_ns) clk_out = ~clk_out;
end
wire _unused_clkdv_divide;
assign _unused_clkdv_divide = (CLKDV_DIVIDE != 0.0);
endmodule

View File

@@ -0,0 +1,79 @@
`timescale 1ns/1ps
// =============================================================================
// Clock generator
// Spartan-6 DCM wrapper with parameterized input and output ratios.
// =============================================================================
module clkgen_spartan6_impl #(
parameter integer CLK_IN_HZ = 100000000,
parameter integer CLKFX_DIVIDE = 20,
parameter integer CLKFX_MULTIPLY = 3,
parameter real CLKDV_DIVIDE = 2.0
)(
input wire clk_in,
output wire clk_out
);
`ifdef FPGA_SPARTAN6
localparam real CLKIN_PERIOD_NS = 1000000000.0 / CLK_IN_HZ;
wire clkfb;
wire clk0;
wire clkfx;
wire locked_unused;
wire [7:0] status_unused;
DCM_SP #(
.CLKDV_DIVIDE(CLKDV_DIVIDE),
.CLKFX_DIVIDE(CLKFX_DIVIDE),
.CLKFX_MULTIPLY(CLKFX_MULTIPLY),
.CLKIN_DIVIDE_BY_2("FALSE"),
.CLKIN_PERIOD(CLKIN_PERIOD_NS),
.CLKOUT_PHASE_SHIFT("NONE"),
.CLK_FEEDBACK("1X"),
.DESKEW_ADJUST("SYSTEM_SYNCHRONOUS"),
.PHASE_SHIFT(0),
.STARTUP_WAIT("FALSE")
) dcm_sp_i (
.CLKIN(clk_in),
.CLKFB(clkfb),
.CLK0(clk0),
.CLK90(),
.CLK180(),
.CLK270(),
.CLK2X(),
.CLK2X180(),
.CLKFX(clkfx),
.CLKFX180(),
.CLKDV(),
.PSCLK(1'b0),
.PSEN(1'b0),
.PSINCDEC(1'b0),
.PSDONE(),
.LOCKED(locked_unused),
.STATUS(status_unused),
.RST(1'b0),
.DSSEN(1'b0)
);
BUFG clkfb_buf_i (
.I(clk0),
.O(clkfb)
);
BUFG clkout_buf_i (
.I(clkfx),
.O(clk_out)
);
`else
assign clk_out = 1'b0;
wire _unused_clk_in;
wire _unused_clkfx_divide;
wire _unused_clkfx_multiply;
wire _unused_clkdv_divide;
assign _unused_clk_in = clk_in;
assign _unused_clkfx_divide = CLKFX_DIVIDE[0];
assign _unused_clkfx_multiply = CLKFX_MULTIPLY[0];
assign _unused_clkdv_divide = (CLKDV_DIVIDE != 0.0);
`endif
endmodule

View File

@@ -0,0 +1,34 @@
CAPI=2:
name: joppeb:primitive:jtag_if:1.0
description: JTAG user chain interface
filesets:
wrapper:
files:
- jtag_if.v
file_type: verilogSource
generic:
files:
- jtag_if_generic_impl.v
file_type: verilogSource
spartan6:
files:
- jtag_if_spartan6.v
file_type: verilogSource
targets:
default:
filesets:
- wrapper
- generic
- spartan6
toplevel: jtag_if
parameters:
- chain
parameters:
chain:
datatype: int
description: User chain
paramtype: vlogparam

View File

@@ -0,0 +1,52 @@
`timescale 1ns/1ps
// =============================================================================
// JTAG interface
// Stable public wrapper that selects an implementation.
// =============================================================================
module jtag_if #(
parameter chain = 1
)(
input wire i_tdo,
output wire o_tck,
output wire o_tdi,
output wire o_drck,
output wire o_capture,
output wire o_shift,
output wire o_update,
output wire o_runtest,
output wire o_reset,
output wire o_sel
);
`ifdef FPGA_SPARTAN6
jtag_if_spartan6_impl #(
.chain(chain)
) impl_i (
.i_tdo(i_tdo),
.o_tck(o_tck),
.o_tdi(o_tdi),
.o_drck(o_drck),
.o_capture(o_capture),
.o_shift(o_shift),
.o_update(o_update),
.o_runtest(o_runtest),
.o_reset(o_reset),
.o_sel(o_sel)
);
`else
jtag_if_generic_impl #(
.chain(chain)
) impl_i (
.i_tdo(i_tdo),
.o_tck(o_tck),
.o_tdi(o_tdi),
.o_drck(o_drck),
.o_capture(o_capture),
.o_shift(o_shift),
.o_update(o_update),
.o_runtest(o_runtest),
.o_reset(o_reset),
.o_sel(o_sel)
);
`endif
endmodule

View File

@@ -0,0 +1,36 @@
`timescale 1ns/1ps
// =============================================================================
// JTAG interface
// Generic stub model with inactive/tied-off outputs.
// =============================================================================
module jtag_if_generic_impl #(
parameter chain = 1
)(
input wire i_tdo,
output wire o_tck,
output wire o_tdi,
output wire o_drck,
output wire o_capture,
output wire o_shift,
output wire o_update,
output wire o_runtest,
output wire o_reset,
output wire o_sel
);
assign o_tck = 1'b0;
assign o_tdi = 1'b0;
assign o_drck = 1'b0;
assign o_capture = 1'b0;
assign o_shift = 1'b0;
assign o_update = 1'b0;
assign o_runtest = 1'b0;
assign o_reset = 1'b0;
assign o_sel = 1'b0;
// Keep lint tools quiet in generic builds where TDO is unused.
wire _unused_tdo;
wire _unused_chain;
assign _unused_tdo = i_tdo;
assign _unused_chain = chain[0];
endmodule

View File

@@ -0,0 +1,52 @@
`timescale 1ns/1ps
// =============================================================================
// JTAG interface
// Spartan-6 BSCAN primitive wrapper (USER1 chain).
// =============================================================================
module jtag_if_spartan6_impl #(
parameter chain = 1
)(
input wire i_tdo,
output wire o_tck,
output wire o_tdi,
output wire o_drck,
output wire o_capture,
output wire o_shift,
output wire o_update,
output wire o_runtest,
output wire o_reset,
output wire o_sel
);
`ifdef FPGA_SPARTAN6
BSCAN_SPARTAN6 #(
.JTAG_CHAIN(chain)
) bscan_i (
.CAPTURE(o_capture),
.DRCK(o_drck),
.RESET(o_reset),
.RUNTEST(o_runtest),
.SEL(o_sel),
.SHIFT(o_shift),
.TCK(o_tck),
.TDI(o_tdi),
.TDO(i_tdo),
.UPDATE(o_update)
);
`else
assign o_tck = 1'b0;
assign o_tdi = 1'b0;
assign o_drck = 1'b0;
assign o_capture = 1'b0;
assign o_shift = 1'b0;
assign o_update = 1'b0;
assign o_runtest = 1'b0;
assign o_reset = 1'b0;
assign o_sel = 1'b0;
wire _unused_tdo;
wire _unused_chain;
assign _unused_tdo = i_tdo;
assign _unused_chain = chain[0];
`endif
endmodule

View File

@@ -0,0 +1,26 @@
CAPI=2:
name: joppeb:primitive:lvds_comparator:1.0
description: LVDS comparator wrapper
filesets:
wrapper:
files:
- lvds_comparator.v
file_type: verilogSource
generic:
files:
- lvds_comparator_generic_impl.v
file_type: verilogSource
spartan6:
files:
- lvds_comparator_spartan6.v
file_type: verilogSource
targets:
default:
filesets:
- wrapper
- generic
- spartan6
toplevel: lvds_comparator

View File

@@ -0,0 +1,19 @@
module lvds_comparator(
input wire a,
input wire b,
output wire o
);
`ifdef FPGA_SPARTAN6
lvds_comparator_spartan6_impl impl_i (
.a(a),
.b(b),
.o(o)
);
`else
lvds_comparator_generic_impl impl_i (
.a(a),
.b(b),
.o(o)
);
`endif
endmodule

View File

@@ -0,0 +1,7 @@
module lvds_comparator_generic_impl (
input wire a,
input wire b,
output wire o
);
assign o = a;
endmodule

View File

@@ -0,0 +1,14 @@
module lvds_comparator_spartan6_impl (
input wire a,
input wire b,
output wire o
);
IBUFDS #(
.DIFF_TERM("FALSE"),
.IOSTANDARD("LVDS33")
) lvds_buf (
.O(o),
.I(a),
.IB(b)
);
endmodule

View File

@@ -0,0 +1,29 @@
CAPI=2:
name: joppeb:signal:decimate_by_r_q15:1.0
description: Q1.15 integer-rate decimator
filesets:
rtl:
files:
- decimate_by_r_q15.v
file_type: verilogSource
targets:
default:
filesets:
- rtl
toplevel: decimate_by_r_q15
parameters:
- R
- CNT_W
parameters:
R:
datatype: int
description: Integer decimation ratio
paramtype: vlogparam
CNT_W:
datatype: int
description: Counter width for the decimation counter
paramtype: vlogparam

View File

@@ -3,15 +3,15 @@
// =============================================================================
// Decimator by R
// Reduces the effective sample rate by an integer factor R by selecting every
// R-th input sample. Generates a one-cycle 'out_valid' pulse each time a new
// R-th input sample. Generates a one-cycle 'o_valid' pulse each time a new
// decimated sample is produced.
//
// Implements:
// For each valid input sample:
// if (count == R-1):
// count <= 0
// out_q15 <= in_q15
// out_valid <= 1
// o_q15 <= i_q15
// o_valid <= 1
// else:
// count <= count + 1
//
@@ -21,12 +21,12 @@
// -- CNT_W : counter bit width, must satisfy 2^CNT_W > R
//
// inout:
// -- clk : input clock (same rate as 'in_valid')
// -- rst_n : active-low synchronous reset
// -- in_valid : input data strobe; assert 1'b1 if input is always valid
// -- in_q15 : signed 16-bit Q1.15 input sample (full-rate)
// -- out_valid : single-cycle pulse every R samples (decimated rate strobe)
// -- out_q15 : signed 16-bit Q1.15 output sample (decimated stream)
// -- i_clk : input clock (same rate as 'i_valid')
// -- i_rst_n : active-low synchronous reset
// -- i_valid : input data strobe; assert 1'b1 if input is always valid
// -- i_q15 : signed 16-bit Q1.15 input sample (full-rate)
// -- o_valid : single-cycle pulse every R samples (decimated rate strobe)
// -- o_q15 : signed 16-bit Q1.15 output sample (decimated stream)
//
// Notes:
// - This module performs *pure downsampling* (sample selection only).
@@ -43,28 +43,28 @@ module decimate_by_r_q15 #(
parameter integer R = 400, // decimation factor
parameter integer CNT_W = 10 // width so that 2^CNT_W > R (e.g., 10 for 750)
)(
input wire clk,
input wire rst_n,
input wire in_valid, // assert 1'b1 if always valid
input wire signed [15:0] in_q15, // Q1.15 sample at full rate
output reg out_valid, // 1-cycle pulse every R samples
output reg signed [15:0] out_q15 // Q1.15 sample at decimated rate
input wire i_clk,
input wire i_rst_n,
input wire i_valid, // assert 1'b1 if always valid
input wire signed [15:0] i_q15, // Q1.15 sample at full rate
output reg o_valid, // 1-cycle pulse every R samples
output reg signed [15:0] o_q15 // Q1.15 sample at decimated rate
);
reg [CNT_W-1:0] cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
cnt <= {CNT_W{1'b0}};
out_valid <= 1'b0;
out_q15 <= 16'sd0;
o_valid <= 1'b0;
o_q15 <= 16'sd0;
end else begin
out_valid <= 1'b0;
o_valid <= 1'b0;
if (in_valid) begin
if (i_valid) begin
if (cnt == R-1) begin
cnt <= {CNT_W{1'b0}};
out_q15 <= in_q15;
out_valid <= 1'b1;
o_q15 <= i_q15;
o_valid <= 1'b1;
end else begin
cnt <= cnt + 1'b1;
end

View File

@@ -0,0 +1,24 @@
CAPI=2:
name: joppeb:signal:lpf_iir_q15_k:1.0
description: First-order Q1.15 IIR low-pass filter
filesets:
rtl:
files:
- lpf_iir_q15_k.v
file_type: verilogSource
targets:
default:
filesets:
- rtl
toplevel: lpf_iir_q15_k
parameters:
- K
parameters:
K:
datatype: int
description: Filter shift factor
paramtype: vlogparam

View File

@@ -21,10 +21,10 @@
// larger K lower cutoff
//
// inout:
// -- clk : input clock
// -- rst_n : active-low reset
// -- x_q15 : signed 16-bit Q1.15 input sample (e.g., 0..0x7FFF)
// -- y_q15 : signed 16-bit Q1.15 filtered output
// -- i_clk : input clock
// -- i_rst_n : active-low reset
// -- i_x_q15 : signed 16-bit Q1.15 input sample (e.g., 0..0x7FFF)
// -- o_y_q15 : signed 16-bit Q1.15 filtered output
//
// Notes:
// - The arithmetic right shift implements division by 2^K.
@@ -36,14 +36,14 @@
module lpf_iir_q15_k #(
parameter integer K = 10 // try 8..12; bigger = more smoothing
)(
input wire clk,
input wire rst_n,
input wire signed [15:0] x_q15, // Q1.15 input (e.g., 0..0x7FFF)
output reg signed [15:0] y_q15 // Q1.15 output
input wire i_clk,
input wire i_rst_n,
input wire signed [15:0] i_x_q15, // Q1.15 input (e.g., 0..0x7FFF)
output reg signed [15:0] o_y_q15 // Q1.15 output
);
wire signed [15:0] e_q15 = x_q15 - y_q15;
wire signed [15:0] e_q15 = i_x_q15 - o_y_q15;
wire signed [15:0] delta_q15 = e_q15 >>> K; // arithmetic shift
wire signed [15:0] y_next = y_q15 + delta_q15; // clamp to [0, 0x7FFF] (handy if your signal is non-negative)
wire signed [15:0] y_next = o_y_q15 + delta_q15; // clamp to [0, 0x7FFF] (handy if your signal is non-negative)
function signed [15:0] clamp01;
input signed [15:0] v;
@@ -57,8 +57,8 @@ module lpf_iir_q15_k #(
end
endfunction
always @(posedge clk or negedge rst_n) begin
if (!rst_n) y_q15 <= 16'sd0;
else y_q15 <= clamp01(y_next);
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) o_y_q15 <= 16'sd0;
else o_y_q15 <= clamp01(y_next);
end
endmodule

View File

@@ -0,0 +1,40 @@
CAPI=2:
name: joppeb:signal:nco_q15:1.0
description: A number controlled sine and cosine oscillator
filesets:
rtl:
files:
- rtl/nco_q15.v
file_type: verilogSource
tb:
files:
- tb/tb_nco_q15.v
file_type: verilogSource
targets:
default:
filesets:
- rtl
toplevel: nco_q15
parameters:
- CLK_HZ
- FS_HZ
sim:
default_tool: icarus
filesets:
- rtl
- tb
toplevel: tb_nco_q15
parameters:
CLK_HZ:
datatype: int
description: Frequency of the input clock
paramtype: vlogparam
FS_HZ:
datatype: int
description: Sample Frequency
paramtype: vlogparam

View File

@@ -0,0 +1,163 @@
`timescale 1ns/1ps
// =============================================================================
// Small number controlled oscillator
// Generates a sine and cosine and uses no multiplications, just some logic and
// a 64-entry LUT. It outputs Q15 data (but the LUT is 8 bits wide)
// params:
// -- CLK_HZ : input clock frequency in Hz
// -- FS_HZ : output sample frequency in Hz
// inout:
// -- i_clk : input clock
// -- i_rst_n : reset
// -- i_freq_hz : decimal number of desired generated frequency in Hz, 0-FS/2
// -- o_sin_q15/o_cos_q15 : I and Q outputs
// -- o_clk_en : output valid strobe
// =============================================================================
module nco_q15 #(
parameter integer CLK_HZ = 120_000_000, // input clock
parameter integer FS_HZ = 40_000 // sample rate
)(
input wire i_clk, // CLK_HZ domain
input wire i_rst_n, // async active-low reset
input wire [31:0] i_freq_hz, // desired output frequency (Hz), 0..FS_HZ/2
output reg signed [15:0] o_sin_q15, // Q1.15 sine
output reg signed [15:0] o_cos_q15, // Q1.15 cosine
output reg o_clk_en // 1-cycle strobe @ FS_HZ
);
localparam integer PHASE_FRAC_BITS = 6;
localparam integer QTR_ADDR_BITS = 6;
localparam integer PHASE_BITS = 2 + QTR_ADDR_BITS + PHASE_FRAC_BITS;
localparam integer DIV = CLK_HZ / FS_HZ;
localparam integer SHIFT = 32;
// Fixed-point reciprocal (constant): RECIP = round( (2^PHASE_BITS * 2^SHIFT) / FS_HZ )
localparam [63:0] RECIP = ( ((64'd1 << PHASE_BITS) << SHIFT) + (FS_HZ/2) ) / FS_HZ;
// Sample-rate tick
function integer clog2;
input integer v; integer r; begin r=0; v=v-1; while (v>0) begin v=v>>1; r=r+1; end clog2=r; end
endfunction
reg [clog2(DIV)-1:0] tick_cnt;
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin tick_cnt <= 0; o_clk_en <= 1'b0; end
else begin
o_clk_en <= 1'b0;
if (tick_cnt == DIV-1) begin tick_cnt <= 0; o_clk_en <= 1'b1; end
else tick_cnt <= tick_cnt + 1'b1;
end
end
// 32-cycle shift-add multiply: prod = i_freq_hz * RECIP (no multiplications themself)
// Starts at o_clk_en, finishes in 32 cycles (<< available cycles per sample).
reg mul_busy;
reg [5:0] mul_i; // 0..31
reg [31:0] f_reg;
reg [95:0] acc; // accumulator for product (32x64 -> 96b)
wire [95:0] recip_shift = {{32{1'b0}}, RECIP} << mul_i; // shift constant by i
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
mul_busy <= 1'b0; mul_i <= 6'd0; f_reg <= 32'd0; acc <= 96'd0;
end else begin
if (o_clk_en && !mul_busy) begin
// kick off a new multiply this sample
mul_busy <= 1'b1;
mul_i <= 6'd0;
f_reg <= (i_freq_hz > (FS_HZ>>1)) ? (FS_HZ>>1) : i_freq_hz; // clamp to Nyquist
acc <= 96'd0;
end else if (mul_busy) begin
// add shifted RECIP if bit is set
if (f_reg[mul_i]) acc <= acc + recip_shift;
// next bit
if (mul_i == 6'd31) begin
mul_busy <= 1'b0; // done in 32 cycles
end
mul_i <= mul_i + 6'd1;
end
end
end
// Rounding shift to get FTW; latch when multiply finishes.
reg [PHASE_BITS-1:0] ftw_q;
wire [95:0] acc_round = acc + (96'd1 << (SHIFT-1));
wire [PHASE_BITS-1:0] ftw_next = acc_round[SHIFT +: PHASE_BITS]; // >> SHIFT
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) ftw_q <= {PHASE_BITS{1'b0}};
else if (!mul_busy) ftw_q <= ftw_next; // update once product ready
end
// Phase accumulator (advance at FS_HZ)
reg [PHASE_BITS-1:0] phase;
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) phase <= {PHASE_BITS{1'b0}};
else if (o_clk_en) phase <= phase + ftw_q;
end
// Cosine phase = sine phase + 90°
wire [PHASE_BITS-1:0] phase_cos = phase + ({{(PHASE_BITS-2){1'b0}}, 2'b01} << (PHASE_BITS-2));
// Quadrant & LUT index
wire [1:0] q_sin = phase [PHASE_BITS-1 -: 2];
wire [1:0] q_cos = phase_cos[PHASE_BITS-1 -: 2];
wire [QTR_ADDR_BITS-1:0] idx_sin_raw = phase [PHASE_BITS-3 -: QTR_ADDR_BITS];
wire [QTR_ADDR_BITS-1:0] idx_cos_raw = phase_cos[PHASE_BITS-3 -: QTR_ADDR_BITS];
wire [QTR_ADDR_BITS-1:0] idx_sin = q_sin[0] ? ({QTR_ADDR_BITS{1'b1}} - idx_sin_raw) : idx_sin_raw;
wire [QTR_ADDR_BITS-1:0] idx_cos = q_cos[0] ? ({QTR_ADDR_BITS{1'b1}} - idx_cos_raw) : idx_cos_raw;
// 64-entry quarter-wave LUT
wire [7:0] mag_sin_u8, mag_cos_u8;
sine_qtr_lut64 u_lut_s (.i_addr(idx_sin), .o_dout(mag_sin_u8));
sine_qtr_lut64 u_lut_c (.i_addr(idx_cos), .o_dout(mag_cos_u8));
// Scale to Q1.15 and apply sign
wire signed [15:0] mag_sin_q15 = {1'b0, mag_sin_u8, 7'd0};
wire signed [15:0] mag_cos_q15 = {1'b0, mag_cos_u8, 7'd0};
wire sin_neg = (q_sin >= 2);
wire cos_neg = (q_cos >= 2);
wire signed [15:0] sin_next = sin_neg ? -mag_sin_q15 : mag_sin_q15;
wire signed [15:0] cos_next = cos_neg ? -mag_cos_q15 : mag_cos_q15;
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
o_sin_q15 <= 16'sd0; o_cos_q15 <= 16'sd0;
end else if (o_clk_en) begin
o_sin_q15 <= sin_next; o_cos_q15 <= cos_next;
end
end
endmodule
module sine_qtr_lut64(
input wire [5:0] i_addr,
output reg [7:0] o_dout
);
always @* begin
case (i_addr)
6'd0: o_dout = 8'd0; 6'd1: o_dout = 8'd6; 6'd2: o_dout = 8'd13; 6'd3: o_dout = 8'd19;
6'd4: o_dout = 8'd25; 6'd5: o_dout = 8'd31; 6'd6: o_dout = 8'd37; 6'd7: o_dout = 8'd44;
6'd8: o_dout = 8'd50; 6'd9: o_dout = 8'd56; 6'd10: o_dout = 8'd62; 6'd11: o_dout = 8'd68;
6'd12: o_dout = 8'd74; 6'd13: o_dout = 8'd80; 6'd14: o_dout = 8'd86; 6'd15: o_dout = 8'd92;
6'd16: o_dout = 8'd98; 6'd17: o_dout = 8'd103; 6'd18: o_dout = 8'd109; 6'd19: o_dout = 8'd115;
6'd20: o_dout = 8'd120; 6'd21: o_dout = 8'd126; 6'd22: o_dout = 8'd131; 6'd23: o_dout = 8'd136;
6'd24: o_dout = 8'd142; 6'd25: o_dout = 8'd147; 6'd26: o_dout = 8'd152; 6'd27: o_dout = 8'd157;
6'd28: o_dout = 8'd162; 6'd29: o_dout = 8'd167; 6'd30: o_dout = 8'd171; 6'd31: o_dout = 8'd176;
6'd32: o_dout = 8'd180; 6'd33: o_dout = 8'd185; 6'd34: o_dout = 8'd189; 6'd35: o_dout = 8'd193;
6'd36: o_dout = 8'd197; 6'd37: o_dout = 8'd201; 6'd38: o_dout = 8'd205; 6'd39: o_dout = 8'd208;
6'd40: o_dout = 8'd212; 6'd41: o_dout = 8'd215; 6'd42: o_dout = 8'd219; 6'd43: o_dout = 8'd222;
6'd44: o_dout = 8'd225; 6'd45: o_dout = 8'd228; 6'd46: o_dout = 8'd231; 6'd47: o_dout = 8'd233;
6'd48: o_dout = 8'd236; 6'd49: o_dout = 8'd238; 6'd50: o_dout = 8'd240; 6'd51: o_dout = 8'd242;
6'd52: o_dout = 8'd244; 6'd53: o_dout = 8'd246; 6'd54: o_dout = 8'd247; 6'd55: o_dout = 8'd249;
6'd56: o_dout = 8'd250; 6'd57: o_dout = 8'd251; 6'd58: o_dout = 8'd252; 6'd59: o_dout = 8'd253;
6'd60: o_dout = 8'd254; 6'd61: o_dout = 8'd254; 6'd62: o_dout = 8'd255; 6'd63: o_dout = 8'd255;
default: o_dout=8'd0;
endcase
end
endmodule

View File

@@ -9,35 +9,34 @@ module tb_nco_q15();
always #4.17 clk <= !clk;
initial #40 resetn <= 1'b1;
// Default run
initial begin
$dumpfile("out.vcd");
$dumpvars;
#5_000_000
$finish;
end;
reg [31:0] freq;
wire [15:0] sin_q15;
wire [15:0] cos_q15;
wire out_en;
nco_q15 #(.CLK_HZ(120_000_000), .FS_HZ(40_000)) nco (
.clk (clk),
.rst_n (resetn),
.freq_hz(freq),
.sin_q15(sin_q15),
.cos_q15(cos_q15),
.clk_en (out_en)
.i_clk (clk),
.i_rst_n (resetn),
.i_freq_hz(freq),
.o_sin_q15(sin_q15),
.o_cos_q15(cos_q15),
.o_clk_en (out_en)
);
initial begin
$dumpfile("out.vcd");
$dumpvars;
freq = 32'h0;
#100
freq = 32'd1000;
#2_500_000
freq = 32'd2000;
#2_500_000
freq = 32'd600;
#2_500_000
$finish;
end;
endmodule

View File

@@ -9,26 +9,33 @@
// rounded to only use two bits (0b3b -> 0b00), the less
// bits the better
// inout:
// -- clk : input clock
// -- resetn : reset signal
// -- sd_sample : 1 bit sample output from sd sampler
// -- sample_q15 : output samples in q.15
// -- i_clk : input clock
// -- i_rst_n : reset signal
// -- i_sd_sample : 1 bit sample output from sd sampler
// -- o_sample_q15 : output samples in q.15
// =============================================================================
module sigmadelta_rcmodel_q15 #(
module rcmodel_q15 #(
parameter integer alpha_q15 = 16'sh0b00
)(
input wire clk,
input wire resetn,
input wire sd_sample,
output wire [15:0] sample_q15
input wire i_clk,
input wire i_rst_n,
input wire i_sd_sample,
output wire [15:0] o_sample_q15
);
reg signed [15:0] y_q15;
wire signed [15:0] sd_q15 = sd_sample ? 16'sh7fff : 16'sh0000;
wire signed [15:0] sd_q15 = i_sd_sample ? 16'sh7fff : 16'sh0000;
wire signed [15:0] e_q15 = sd_q15 - y_q15;
// wire signed [31:0] prod_q30 = $signed(e_q15) * $signed(alpha_q15);
wire signed [31:0] prod_q30;
// Use shift-add algorithm for multiplication
mul_const_shiftadd #(.C($signed(alpha_q15))) alpha_times_e ($signed(e_q15), prod_q30);
mul_const_shiftadd #(
.C($signed(alpha_q15)),
.IN_W(16),
.OUT_W(32)
) alpha_times_e (
.i_x(e_q15),
.o_y(prod_q30)
);
wire signed [15:0] y_next_q15 = y_q15 + (prod_q30>>>15);
// clamp to [0, 0x7FFF] (keeps signal view tidy)
@@ -38,11 +45,11 @@ module sigmadelta_rcmodel_q15 #(
else clamp01_q15 = v;
endfunction
always @(posedge clk or negedge resetn) begin
if (!resetn) y_q15 <= 16'sd0000;
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) y_q15 <= 16'sd0000;
else y_q15 <= clamp01_q15(y_next_q15);
end
assign sample_q15 = y_q15;
assign o_sample_q15 = y_q15;
endmodule

View File

@@ -0,0 +1,57 @@
module sd_adc_q15 #(
parameter integer R_OHM = 3300,
parameter integer C_PF = 220
)(
input wire i_clk_15,
input wire i_rst_n,
input wire i_adc_a,
input wire i_adc_b,
output wire o_adc,
output wire signed [15:0] o_signal_q15,
output wire o_signal_valid
);
`include "rc_alpha_q15.vh"
wire sd_signal;
wire signed [15:0] raw_sample_biased;
wire signed [15:0] raw_sample_q15;
wire signed [15:0] lpf_sample_q15;
sd_sampler sd_sampler(
.i_clk(i_clk_15),
.i_a(i_adc_a), .i_b(i_adc_b),
.o_sample(sd_signal)
);
assign o_adc = sd_signal;
localparam integer alpha_q15_int = alpha_q15_from_rc(R_OHM, C_PF, 15000000);
localparam signed [15:0] alpha_q15 = alpha_q15_int[15:0];
localparam signed [15:0] alpha_q15_top = alpha_q15 & 16'hff00;
rcmodel_q15 #(
.alpha_q15(alpha_q15_top)
) rc_model (
.i_clk(i_clk_15), .i_rst_n(i_rst_n),
.i_sd_sample(sd_signal),
.o_sample_q15(raw_sample_q15)
);
lpf_iir_q15_k #(
.K(10)
) lpf (
.i_clk(i_clk_15), .i_rst_n(i_rst_n),
.i_x_q15(raw_sample_q15),
.o_y_q15(lpf_sample_q15)
);
decimate_by_r_q15 #(
.R(375), // 15MHz/375 = 40KHz
.CNT_W(10)
) decimate (
.i_clk(i_clk_15), .i_rst_n(i_rst_n),
.i_valid(1'b1), .i_q15(lpf_sample_q15),
.o_valid(o_signal_valid), .o_q15(o_signal_q15)
);
endmodule

View File

@@ -0,0 +1,18 @@
module sd_sampler(
input wire i_clk,
input wire i_a,
input wire i_b,
output wire o_sample
);
wire comp_out;
lvds_comparator comp (
.a(i_a), .b(i_b), .o(comp_out)
);
reg registered_comp_out;
always @(posedge i_clk)
registered_comp_out <= comp_out;
assign o_sample = registered_comp_out;
endmodule

View File

@@ -0,0 +1,57 @@
CAPI=2:
name: joppeb:signal:sd_adc_q15:1.0
description: Sigma-delta ADC front-end with Q1.15 output
filesets:
rtl_common:
depend:
- joppeb:primitive:lvds_comparator
- joppeb:signal:lpf_iir_q15_k
- joppeb:signal:decimate_by_r_q15
- joppeb:util:mul_const
files:
- rtl/rc_alpha_q15.vh:
is_include_file: true
- rtl/rcmodel_q15.v
- rtl/sd_adc_q15.v
file_type: verilogSource
rtl_sampler:
files:
- rtl/sd_sampler.v
file_type: verilogSource
sim_sampler:
files:
- sim/sd_sampler.v
file_type: verilogSource
tb:
files:
- tb/tb_sd_adc_q15.v
file_type: verilogSource
targets:
default:
filesets:
- rtl_common
- rtl_sampler
toplevel: sd_adc_q15
parameters:
- R_OHM
- C_PF
sim:
default_tool: icarus
filesets:
- rtl_common
- sim_sampler
- tb
toplevel: tb_sd_adc_q15
parameters:
R_OHM:
datatype: int
description: RC filter resistor value in ohms
paramtype: vlogparam
C_PF:
datatype: int
description: RC filter capacitor value in pF
paramtype: vlogparam

View File

@@ -2,17 +2,17 @@
// =============================================================================
// Sigma-Delta sampler
// Simulates an RC circuit between O and B and a sine at A
// Simulates an RC circuit between o_sample and i_b and i_a sine at i_a
// =============================================================================
module sigmadelta_sampler(
input wire clk,
input wire a,
input wire b,
output wire o
module sd_sampler(
input wire i_clk,
input wire i_a,
input wire i_b,
output wire o_sample
);
// Sine source (A input / P)
parameter real F_HZ = 1.5e3; // input sine frequency (1 kHz)
// Sine source (i_a input / P)
parameter real F_HZ = 5000; // input sine frequency (1 kHz)
parameter real AMP = 1.5; // sine amplitude (V)
parameter real VCM = 1.65; // common-mode (V), centered in 0..3.3V
@@ -25,7 +25,7 @@ module sigmadelta_sampler(
parameter real VLOW = 0.0; // DAC 0 (V)
parameter real VHIGH = 3.3; // DAC 1 (V)
// RC filter (B input / N)
// RC filter (i_b input / N)
parameter real R_OHMS = 3300.0; // 3.3k
parameter real C_FARADS = 220e-12; // 220 pF
@@ -33,9 +33,9 @@ module sigmadelta_sampler(
parameter integer TSTEP_NS = 10; // sim step in ns (choose << tau)
// ===== Internal state (simulation only) =====
real vp, vn; // comparator A/B inputs
real vp, vn; // comparator i_a/i_b inputs
real v_rc; // RC node voltage (== vn)
real v_dac; // DAC output voltage from O
real v_dac; // DAC output voltage from o_sample
real t_s; // time in seconds
real dt_s; // step in seconds
real tau_s; // R*C time constant in seconds
@@ -45,10 +45,10 @@ module sigmadelta_sampler(
reg sampler;
initial sampler <= 1'b0;
always @(posedge clk) begin
always @(posedge i_clk) begin
sampler <= out;
end
assign o = sampler;
assign o_sample = sampler;
// Helper task: update comparator with optional hysteresis
@@ -96,7 +96,7 @@ module sigmadelta_sampler(
v_rc = v_rc + (v_dac - v_rc) * (dt_s / tau_s);
vn = v_rc;
// 3) Input sine on A
// 3) Input sine on i_a
vp = VCM + AMP * $sin(two_pi * F_HZ * t_s);
// 4) Comparator decision (with optional hysteresis)

View File

@@ -1,6 +1,6 @@
`timescale 1ns/1ps
module tb_sigmadelta();
module tb_sd_adc_q15();
// Clock and reset generation
reg clk;
reg resetn;
@@ -23,14 +23,14 @@ module tb_sigmadelta();
wire signed [15:0] decimated_q15;
wire decimated_valid;
sigmadelta_input #(
sd_adc_q15 #(
.R_OHM(3300),
.C_PF(220)
) dut(
.clk_15(clk), .resetn(resetn),
.adc_a(sd_a), .adc_b(sd_b), .adc_o(sd_o),
.signal_q15(decimated_q15),
.signal_valid(decimated_valid)
.i_clk_15(clk), .i_rst_n(resetn),
.i_adc_a(sd_a), .i_adc_b(sd_b), .o_adc(sd_o),
.o_signal_q15(decimated_q15),
.o_signal_valid(decimated_valid)
);
endmodule

48
cores/system/mcu/mcu.core Normal file
View File

@@ -0,0 +1,48 @@
CAPI=2:
name: joppeb:system:mcu:1.0
description: basic RISC-V MCU system
filesets:
rtl:
depend:
- "^award-winning:serv:servile:1.4.0"
- joppeb:util:clog2
- joppeb:wb:jtag_wb_bridge
- joppeb:wb:wb_gpio_banks
- joppeb:wb:wb_timer
- joppeb:wb:wb_arbiter
- joppeb:wb:wb_mux
files:
- rtl/mcu.v
- rtl/mcu_peripherals.v
file_type: verilogSource
targets:
default:
filesets:
- rtl
toplevel: mcu
parameters:
- memfile
- memsize
- sim
- jtag
parameters:
memfile:
datatype: str
description: Memory initialization file passed to the internal RAM
paramtype: vlogparam
memsize:
datatype: int
description: Internal RAM size in bytes
paramtype: vlogparam
sim:
datatype: int
description: Enable simulation-friendly RAM initialization behavior
paramtype: vlogparam
jtag:
datatype: int
description: Enable the JTAG Wishbone bridge and arbiter path
paramtype: vlogparam

382
cores/system/mcu/rtl/mcu.v Normal file
View File

@@ -0,0 +1,382 @@
`timescale 1ns/1ps
`include "clog2.vh"
module mcu #(
parameter memfile = "",
parameter memsize = 8192,
parameter sim = 1'b0,
parameter jtag = 1
)(
input wire i_clk,
input wire i_rst,
input wire [31:0] i_GPI_A,
input wire [31:0] i_GPI_B,
input wire [31:0] i_GPI_C,
input wire [31:0] i_GPI_D,
output wire [31:0] o_GPO_A,
output wire [31:0] o_GPO_B,
output wire [31:0] o_GPO_C,
output wire [31:0] o_GPO_D
);
localparam WITH_CSR = 1;
localparam rf_width = 8;
wire rst;
wire rst_wb;
wire rst_mem_peripherals;
wire rst_cmd_jtag;
wire timer_irq;
assign rst = i_rst | rst_mem_peripherals | rst_cmd_jtag;
// Keep the Wishbone path alive during JTAG "core reset" so memory can be programmed.
assign rst_wb = i_rst;
// Busses
// CPU<->memory interconnect (CPU is a WB master)
wire [31:0] wb_mem_adr;
wire [31:0] wb_mem_dat;
wire [3:0] wb_mem_sel;
wire wb_mem_we;
wire wb_mem_stb;
wire [31:0] wb_mem_rdt_cpu;
wire wb_mem_ack_cpu;
// Interconnect->memory (shared WB slave side)
wire [31:0] wb_mem_adr_s;
wire [31:0] wb_mem_dat_s;
wire [3:0] wb_mem_sel_s;
wire wb_mem_we_s;
wire wb_mem_stb_s;
wire [31:0] wb_mem_rdt_s;
wire wb_mem_ack_s;
// CPU->peripherals
wire [31:0] wb_ext_adr;
wire [31:0] wb_ext_dat;
wire [3:0] wb_ext_sel;
wire wb_ext_we;
wire wb_ext_stb;
wire [31:0] wb_ext_rdt;
wire wb_ext_ack;
// GPIO
wire [4*32-1:0] GPO;
wire [4*32-1:0] GPI;
assign o_GPO_A = GPO[32*1-1:32*0];
assign o_GPO_B = GPO[32*2-1:32*1];
assign o_GPO_C = GPO[32*3-1:32*2];
assign o_GPO_D = GPO[32*4-1:32*3];
assign GPI[32*1-1:32*0] = i_GPI_A;
assign GPI[32*2-1:32*1] = i_GPI_B;
assign GPI[32*3-1:32*2] = i_GPI_C;
assign GPI[32*4-1:32*3] = i_GPI_D;
cpu #(
.sim(sim),
.WITH_CSR(WITH_CSR),
.rf_width(rf_width)
) cpu (
.i_clk(i_clk),
.i_rst(rst),
.i_timer_irq(timer_irq),
//Memory interface
.o_wb_mem_adr(wb_mem_adr),
.o_wb_mem_dat(wb_mem_dat),
.o_wb_mem_sel(wb_mem_sel),
.o_wb_mem_we(wb_mem_we),
.o_wb_mem_stb(wb_mem_stb),
.i_wb_mem_rdt(wb_mem_rdt_cpu),
.i_wb_mem_ack(wb_mem_ack_cpu),
//Extension interface
.o_wb_ext_adr(wb_ext_adr),
.o_wb_ext_dat(wb_ext_dat),
.o_wb_ext_sel(wb_ext_sel),
.o_wb_ext_we(wb_ext_we),
.o_wb_ext_stb(wb_ext_stb),
.i_wb_ext_rdt(wb_ext_rdt),
.i_wb_ext_ack(wb_ext_ack)
);
generate
if (jtag) begin : gen_jtag_wb
wire [31:0] wb_jtag_adr;
wire [31:0] wb_jtag_dat;
wire [3:0] wb_jtag_sel;
wire wb_jtag_we;
wire wb_jtag_cyc;
wire wb_jtag_stb;
wire [31:0] wb_jtag_rdt;
wire wb_jtag_ack;
wire [2*32-1:0] wbm_adr_i;
wire [2*32-1:0] wbm_dat_i;
wire [2*4-1:0] wbm_sel_i;
wire [1:0] wbm_we_i;
wire [1:0] wbm_cyc_i;
wire [1:0] wbm_stb_i;
wire [2*3-1:0] wbm_cti_i;
wire [2*2-1:0] wbm_bte_i;
wire [2*32-1:0] wbm_dat_o;
wire [1:0] wbm_ack_o;
wire [1:0] wbm_err_o;
wire [1:0] wbm_rty_o;
assign wbm_adr_i = {wb_jtag_adr, wb_mem_adr};
assign wbm_dat_i = {wb_jtag_dat, wb_mem_dat};
assign wbm_sel_i = {wb_jtag_sel, wb_mem_sel};
assign wbm_we_i = {wb_jtag_we, wb_mem_we};
assign wbm_cyc_i = {wb_jtag_cyc, wb_mem_stb};
assign wbm_stb_i = {wb_jtag_stb, wb_mem_stb};
assign wbm_cti_i = 6'b0;
assign wbm_bte_i = 4'b0;
assign wb_mem_rdt_cpu = wbm_dat_o[31:0];
assign wb_mem_ack_cpu = wbm_ack_o[0];
assign wb_jtag_rdt = wbm_dat_o[63:32];
assign wb_jtag_ack = wbm_ack_o[1];
wb_arbiter #(
.dw(32),
.aw(32),
.num_masters(2)
) wb_mem_arbiter (
.wb_clk_i(i_clk),
.wb_rst_i(rst_wb),
.wbm_adr_i(wbm_adr_i),
.wbm_dat_i(wbm_dat_i),
.wbm_sel_i(wbm_sel_i),
.wbm_we_i(wbm_we_i),
.wbm_cyc_i(wbm_cyc_i),
.wbm_stb_i(wbm_stb_i),
.wbm_cti_i(wbm_cti_i),
.wbm_bte_i(wbm_bte_i),
.wbm_dat_o(wbm_dat_o),
.wbm_ack_o(wbm_ack_o),
.wbm_err_o(wbm_err_o),
.wbm_rty_o(wbm_rty_o),
.wbs_adr_o(wb_mem_adr_s),
.wbs_dat_o(wb_mem_dat_s),
.wbs_sel_o(wb_mem_sel_s),
.wbs_we_o(wb_mem_we_s),
.wbs_cyc_o(),
.wbs_stb_o(wb_mem_stb_s),
.wbs_cti_o(),
.wbs_bte_o(),
.wbs_dat_i(wb_mem_rdt_s),
.wbs_ack_i(wb_mem_ack_s),
.wbs_err_i(1'b0),
.wbs_rty_i(1'b0)
);
jtag_wb_bridge #(
.chain(1)
) jtag_wb (
.i_clk(i_clk),
.i_rst(i_rst),
.o_wb_adr(wb_jtag_adr),
.o_wb_dat(wb_jtag_dat),
.o_wb_sel(wb_jtag_sel),
.o_wb_we(wb_jtag_we),
.o_wb_cyc(wb_jtag_cyc),
.o_wb_stb(wb_jtag_stb),
.i_wb_rdt(wb_jtag_rdt),
.i_wb_ack(wb_jtag_ack),
.o_cmd_reset(rst_cmd_jtag)
);
end else begin : gen_no_jtag_wb
assign wb_mem_adr_s = wb_mem_adr;
assign wb_mem_dat_s = wb_mem_dat;
assign wb_mem_sel_s = wb_mem_sel;
assign wb_mem_we_s = wb_mem_we;
assign wb_mem_stb_s = wb_mem_stb;
assign wb_mem_rdt_cpu = wb_mem_rdt_s;
assign wb_mem_ack_cpu = wb_mem_ack_s;
assign rst_cmd_jtag = 1'b0;
end
endgenerate
memory #(
.memfile(memfile),
.memsize(memsize),
.sim(sim)
) memory (
.i_clk(i_clk),
.i_rst(i_rst),
.i_wb_rst(rst_wb),
.i_wb_adr(wb_mem_adr_s),
.i_wb_dat(wb_mem_dat_s),
.i_wb_sel(wb_mem_sel_s),
.i_wb_we(wb_mem_we_s),
.i_wb_stb(wb_mem_stb_s),
.o_wb_rdt(wb_mem_rdt_s),
.o_wb_ack(wb_mem_ack_s)
);
mcu_peripherals peripherals (
.i_clk(i_clk),
.i_rst(rst),
.i_wb_adr(wb_ext_adr),
.i_wb_dat(wb_ext_dat),
.i_wb_sel(wb_ext_sel),
.i_wb_we(wb_ext_we),
.i_wb_stb(wb_ext_stb),
.o_wb_rdt(wb_ext_rdt),
.o_wb_ack(wb_ext_ack),
// Peripheral IO
.i_gpio(GPI),
.o_gpio(GPO),
.o_timer_irq(timer_irq),
.o_core_reset(rst_mem_peripherals)
);
endmodule
module cpu #(
parameter sim = 1'b0,
parameter WITH_CSR = 1,
parameter rf_width = 8
)(
input wire i_clk,
input wire i_rst,
input wire i_timer_irq,
// CPU->memory
output wire [31:0] o_wb_mem_adr,
output wire [31:0] o_wb_mem_dat,
output wire [3:0] o_wb_mem_sel,
output wire o_wb_mem_we,
output wire o_wb_mem_stb,
input wire [31:0] i_wb_mem_rdt,
input wire i_wb_mem_ack,
// CPU->peripherals
output wire [31:0] o_wb_ext_adr,
output wire [31:0] o_wb_ext_dat,
output wire [3:0] o_wb_ext_sel,
output wire o_wb_ext_we,
output wire o_wb_ext_stb,
input wire [31:0] i_wb_ext_rdt,
input wire i_wb_ext_ack
);
wire [6+WITH_CSR:0] rf_waddr;
wire [rf_width-1:0] rf_wdata;
wire rf_wen;
wire [6+WITH_CSR:0] rf_raddr;
wire [rf_width-1:0] rf_rdata;
wire rf_ren;
// SERV core with mux splitting dbus into mem and ext and
// arbiter combining mem and ibus.
servile #(
.reset_pc(32'h0000_0000),
.reset_strategy("MINI"),
.rf_width(rf_width),
.sim(sim),
.with_csr(WITH_CSR),
.with_c(0),
.with_mdu(0)
) servile (
.i_clk(i_clk),
.i_rst(i_rst),
.i_timer_irq(i_timer_irq),
.o_wb_mem_adr(o_wb_mem_adr),
.o_wb_mem_dat(o_wb_mem_dat),
.o_wb_mem_sel(o_wb_mem_sel),
.o_wb_mem_we(o_wb_mem_we),
.o_wb_mem_stb(o_wb_mem_stb),
.i_wb_mem_rdt(i_wb_mem_rdt),
.i_wb_mem_ack(i_wb_mem_ack),
.o_wb_ext_adr(o_wb_ext_adr),
.o_wb_ext_dat(o_wb_ext_dat),
.o_wb_ext_sel(o_wb_ext_sel),
.o_wb_ext_we(o_wb_ext_we),
.o_wb_ext_stb(o_wb_ext_stb),
.i_wb_ext_rdt(i_wb_ext_rdt),
.i_wb_ext_ack(i_wb_ext_ack),
.o_rf_waddr(rf_waddr),
.o_rf_wdata(rf_wdata),
.o_rf_wen(rf_wen),
.o_rf_raddr(rf_raddr),
.o_rf_ren(rf_ren),
.i_rf_rdata(rf_rdata)
);
serv_rf_ram #(
.width(rf_width),
.csr_regs(WITH_CSR*4)
) rf_ram (
.i_clk(i_clk),
.i_waddr(rf_waddr),
.i_wdata(rf_wdata),
.i_wen(rf_wen),
.i_raddr(rf_raddr),
.i_ren(rf_ren),
.o_rdata(rf_rdata)
);
endmodule
module memory #(
parameter memfile = "",
parameter memsize = 8192,
parameter sim = 1'b0
)(
input wire i_clk,
input wire i_rst,
input wire i_wb_rst,
input wire [31:0] i_wb_adr,
input wire [31:0] i_wb_dat,
input wire [3:0] i_wb_sel,
input wire i_wb_we,
input wire i_wb_stb,
output wire [31:0] o_wb_rdt,
output wire o_wb_ack
);
localparam mem_depth = memsize/4;
localparam mem_aw = `CLOG2(mem_depth);
reg [31:0] mem [0:mem_depth-1] /* verilator public */;
reg [31:0] wb_rdt_r;
reg wb_ack_r;
wire [mem_aw-1:0] wb_word_adr = i_wb_adr[mem_aw+1:2];
assign o_wb_rdt = wb_rdt_r;
assign o_wb_ack = wb_ack_r;
always @(posedge i_clk) begin
if (i_rst || i_wb_rst) begin
wb_ack_r <= 1'b0;
wb_rdt_r <= 32'b0;
end else begin
wb_ack_r <= i_wb_stb & ~wb_ack_r;
if (i_wb_stb & ~wb_ack_r) begin
wb_rdt_r <= mem[wb_word_adr];
if (i_wb_we) begin
if (i_wb_sel[0]) mem[wb_word_adr][7:0] <= i_wb_dat[7:0];
if (i_wb_sel[1]) mem[wb_word_adr][15:8] <= i_wb_dat[15:8];
if (i_wb_sel[2]) mem[wb_word_adr][23:16] <= i_wb_dat[23:16];
if (i_wb_sel[3]) mem[wb_word_adr][31:24] <= i_wb_dat[31:24];
end
end
end
end
integer i;
initial begin
if (sim == 1'b1) begin
for (i = 0; i < mem_depth; i = i + 1)
mem[i] = 32'h00000000;
end
if (|memfile) begin
$display("Preloading %m from %s", memfile);
$readmemh(memfile, mem);
end
wb_rdt_r = 32'b0;
wb_ack_r = 1'b0;
end
endmodule

View File

@@ -0,0 +1,131 @@
`timescale 1ns/1ps
module mcu_peripherals (
input wire i_clk,
input wire i_rst,
input wire [31:0] i_wb_adr,
input wire [31:0] i_wb_dat,
input wire [3:0] i_wb_sel,
input wire i_wb_we,
input wire i_wb_stb,
output wire [31:0] o_wb_rdt,
output wire o_wb_ack,
input wire [4*32-1:0] i_gpio,
output wire [4*32-1:0] o_gpio,
output wire o_timer_irq,
output wire o_core_reset
);
localparam [31:0] GPIO_BASE_ADDR = 32'h4000_0000;
localparam [31:0] GPIO_ADDR_MASK = 32'hFFFF_0000;
localparam [31:0] TIMER_BASE_ADDR = 32'h4001_0000;
localparam [31:0] TIMER_ADDR_MASK = 32'hFFFF_0000;
assign o_core_reset = 1'b0;
wire [2*32-1:0] wbs_adr;
wire [2*32-1:0] wbs_dat_w;
wire [2*4-1:0] wbs_sel;
wire [1:0] wbs_we;
wire [1:0] wbs_cyc;
wire [1:0] wbs_stb;
wire [2*3-1:0] wbs_cti;
wire [2*2-1:0] wbs_bte;
wire [2*32-1:0] wbs_dat_r;
wire [1:0] wbs_ack;
wire [31:0] gpio_wbs_adr = wbs_adr[0*32 +: 32];
wire [31:0] gpio_wbs_dat_w = wbs_dat_w[0*32 +: 32];
wire [3:0] gpio_wbs_sel = wbs_sel[0*4 +: 4];
wire gpio_wbs_we = wbs_we[0];
wire gpio_wbs_cyc = wbs_cyc[0];
wire gpio_wbs_stb = wbs_stb[0];
wire [31:0] gpio_wbs_dat_r;
wire gpio_wbs_ack;
wire [31:0] timer_wbs_adr = wbs_adr[1*32 +: 32];
wire [31:0] timer_wbs_dat_w = wbs_dat_w[1*32 +: 32];
wire [3:0] timer_wbs_sel = wbs_sel[1*4 +: 4];
wire timer_wbs_we = wbs_we[1];
wire timer_wbs_cyc = wbs_cyc[1];
wire timer_wbs_stb = wbs_stb[1];
wire [31:0] timer_wbs_dat_r;
wire timer_wbs_ack;
wb_mux #(
.dw(32),
.aw(32),
.num_slaves(2),
.MATCH_ADDR({TIMER_BASE_ADDR, GPIO_BASE_ADDR}),
.MATCH_MASK({TIMER_ADDR_MASK, GPIO_ADDR_MASK})
) ext_mux (
.wb_clk_i(i_clk),
.wb_rst_i(i_rst),
.wbm_adr_i(i_wb_adr),
.wbm_dat_i(i_wb_dat),
.wbm_sel_i(i_wb_sel),
.wbm_we_i(i_wb_we),
.wbm_cyc_i(i_wb_stb),
.wbm_stb_i(i_wb_stb),
.wbm_cti_i(3'b000),
.wbm_bte_i(2'b00),
.wbm_dat_o(o_wb_rdt),
.wbm_ack_o(o_wb_ack),
.wbm_err_o(),
.wbm_rty_o(),
.wbs_adr_o(wbs_adr),
.wbs_dat_o(wbs_dat_w),
.wbs_sel_o(wbs_sel),
.wbs_we_o(wbs_we),
.wbs_cyc_o(wbs_cyc),
.wbs_stb_o(wbs_stb),
.wbs_cti_o(wbs_cti),
.wbs_bte_o(wbs_bte),
.wbs_dat_i(wbs_dat_r),
.wbs_ack_i(wbs_ack),
.wbs_err_i(2'b00),
.wbs_rty_i(2'b00)
);
wb_gpio_banks #(
.BASE_ADDR(GPIO_BASE_ADDR),
.NUM_BANKS(4)
) gpio (
.i_wb_clk(i_clk),
.i_wb_rst(i_rst),
.i_wb_dat(gpio_wbs_dat_w),
.i_wb_adr(gpio_wbs_adr),
.i_wb_we(gpio_wbs_we),
.i_wb_stb(gpio_wbs_stb),
.i_wb_cyc(gpio_wbs_cyc),
.i_wb_sel(gpio_wbs_sel),
.o_wb_rdt(gpio_wbs_dat_r),
.o_wb_ack(gpio_wbs_ack),
.i_gpio(i_gpio),
.o_gpio(o_gpio)
);
assign wbs_dat_r[0*32 +: 32] = gpio_wbs_dat_r;
assign wbs_ack[0] = gpio_wbs_ack;
wb_countdown_timer timer (
.i_clk(i_clk),
.i_rst(i_rst),
.o_irq(o_timer_irq),
.i_wb_adr(timer_wbs_adr),
.i_wb_dat(timer_wbs_dat_w),
.o_wb_dat(timer_wbs_dat_r),
.i_wb_sel(timer_wbs_sel),
.i_wb_we(timer_wbs_we),
.i_wb_cyc(timer_wbs_cyc),
.i_wb_stb(timer_wbs_stb),
.o_wb_ack(timer_wbs_ack)
);
assign wbs_dat_r[1*32 +: 32] = timer_wbs_dat_r;
assign wbs_ack[1] = timer_wbs_ack;
endmodule

View File

@@ -0,0 +1,52 @@
# Main clock input
NET "aclk" LOC = P126;
NET "aclk" TNM_NET = "SYS_CLK_PIN";
TIMESPEC TS_SYS_CLK_PIN = PERIOD "SYS_CLK_PIN" 10 ns HIGH 50 %;
# Boards button row
NET "aresetn" LOC = P120;
NET "aresetn" IOSTANDARD = LVCMOS33;
NET "aresetn" PULLUP;
NET "led_green" LOC = P29;
NET "led_green" IOSTANDARD = LVCMOS33;
NET "led_red" LOC = P26;
NET "led_red" IOSTANDARD = LVCMOS33;
NET "r2r[0]" LOC = P131;
NET "r2r[1]" LOC = P133;
NET "r2r[2]" LOC = P137;
NET "r2r[3]" LOC = P139;
NET "r2r[4]" LOC = P141;
NET "r2r[5]" LOC = P1;
NET "r2r[0]" IOSTANDARD = LVCMOS33;
NET "r2r[1]" IOSTANDARD = LVCMOS33;
NET "r2r[2]" IOSTANDARD = LVCMOS33;
NET "r2r[3]" IOSTANDARD = LVCMOS33;
NET "r2r[4]" IOSTANDARD = LVCMOS33;
NET "r2r[5]" IOSTANDARD = LVCMOS33;
NET "LED[0]" LOC = P119;
NET "LED[0]" IOSTANDARD = LVCMOS33;
NET "LED[0]" DRIVE = 8;
NET "LED[1]" LOC = P118;
NET "LED[1]" IOSTANDARD = LVCMOS33;
NET "LED[1]" DRIVE = 8;
NET "LED[2]" LOC = P117;
NET "LED[2]" IOSTANDARD = LVCMOS33;
NET "LED[2]" DRIVE = 8;
NET "LED[3]" LOC = P116;
NET "LED[3]" IOSTANDARD = LVCMOS33;
NET "LED[3]" DRIVE = 8;
NET "LED[4]" LOC = P115;
NET "LED[4]" IOSTANDARD = LVCMOS33;
NET "LED[4]" DRIVE = 8;
NET "LED[5]" LOC = P114;
NET "LED[5]" IOSTANDARD = LVCMOS33;
NET "LED[5]" DRIVE = 8;
NET "LED[6]" LOC = P112;
NET "LED[6]" IOSTANDARD = LVCMOS33;
NET "LED[6]" DRIVE = 8;
NET "LED[7]" LOC = P111;
NET "LED[7]" IOSTANDARD = LVCMOS33;
NET "LED[7]" DRIVE = 8;

View File

@@ -0,0 +1 @@
project set "Create Binary Configuration File" TRUE -process "Generate Programming File"

View File

@@ -0,0 +1,103 @@
`timescale 1ns/1ps
module toplevel #(
parameter sim = 0,
parameter memfile = "sweep.hex"
)(
input wire aclk,
input wire aresetn,
output wire led_green,
output wire led_red,
output wire[5:0] r2r,
output wire[7:0] LED
);
`include "conv.vh"
// Clocking
wire clk_100;
assign clk_100 = aclk;
wire clk_15;
clkgen #(
.CLK_IN_HZ(100000000),
.CLKFX_DIVIDE(20),
.CLKFX_MULTIPLY(3)
) clk_gen_15 (
.clk_in(clk_100),
.clk_out(clk_15)
);
// Reset conditioning for button input:
// - asynchronous assert when button is pressed (aresetn=0)
// - synchronous, debounced deassert in clk_15 domain
localparam [17:0] RESET_RELEASE_CYCLES = sim ? 18'd16 : 18'd150000; // ~10 ms @ 15 MHz on hardware
reg [17:0] rst_cnt = 18'd0;
reg sys_reset_r = 1'b1;
always @(posedge clk_15 or negedge aresetn) begin
if (!aresetn) begin
rst_cnt <= 18'd0;
sys_reset_r <= 1'b1;
end else if (sys_reset_r) begin
if (rst_cnt == RESET_RELEASE_CYCLES - 1'b1)
sys_reset_r <= 1'b0;
else
rst_cnt <= rst_cnt + 1'b1;
end
end
wire sys_reset = sys_reset_r;
wire sys_resetn = !sys_reset_r;
wire [31:0] GPIO_A;
wire [31:0] GPIO_B;
wire [31:0] GPIO_C;
wire [31:0] GPIO_D;
wire test;
mcu #(
.memfile(memfile),
.sim(sim),
.jtag(1)
) mcu (
.i_clk(clk_15),
.i_rst(sys_reset),
.i_GPI_A(GPIO_A),
.i_GPI_B(GPIO_B),
.i_GPI_C(GPIO_C),
.i_GPI_D(GPIO_D),
.o_GPO_A(GPIO_A),
.o_GPO_B(GPIO_B),
.o_GPO_C(GPIO_C),
.o_GPO_D(GPIO_D)
);
wire [15:0] sin_q15;
wire clk_en;
nco_q15 #(
.CLK_HZ(15_000_000),
.FS_HZ(80_000)
) nco (
.i_clk (clk_15),
.i_rst_n (sys_resetn),
.i_freq_hz(GPIO_A),
.o_sin_q15(sin_q15),
.o_cos_q15(),
.o_clk_en (clk_en)
);
reg [5:0] dac_code;
always @(posedge clk_15) begin
dac_code <= q15_to_uq16(sin_q15) >> 10;
end
assign r2r = dac_code;
assign LED = GPIO_B[7:0];
assign led_green = GPIO_C[0];
assign led_red = GPIO_C[1];
endmodule

8
cores/system/test/sw/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
*.o
*.hex
*.bin
*.map
*.elf.asm
*.elf
*.coe
*.mif

View File

@@ -0,0 +1,47 @@
TOOLCHAIN_PREFIX ?= riscv64-elf-
CC := $(TOOLCHAIN_PREFIX)gcc
OBJCOPY := $(TOOLCHAIN_PREFIX)objcopy
OBJDUMP := $(TOOLCHAIN_PREFIX)objdump
SIZE := $(TOOLCHAIN_PREFIX)size
TARGET := sweep
SRCS_C := sweep.c
SRCS_S := start.s
OBJS := $(SRCS_C:.c=.o) $(SRCS_S:.s=.o)
ARCH_FLAGS := -march=rv32i_zicsr -mabi=ilp32
CFLAGS := $(ARCH_FLAGS) -Os -ffreestanding -fno-builtin -Wall -Wextra
ASFLAGS := $(ARCH_FLAGS)
LDFLAGS := $(ARCH_FLAGS) -nostdlib -nostartfiles -Wl,-Bstatic,-Tlink.ld,--gc-sections,-Map,$(TARGET).map
.PHONY: all clean disasm size
all: $(TARGET).elf $(TARGET).bin $(TARGET).hex $(TARGET).elf.asm
$(TARGET).elf: $(OBJS) link.ld
$(CC) $(LDFLAGS) -o $@ $(OBJS)
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
%.o: %.s
$(CC) $(ASFLAGS) -c -o $@ $<
$(TARGET).bin: $(TARGET).elf
$(OBJCOPY) -O binary $< $@
$(TARGET).hex: $(TARGET).bin
hexdump -v -e '1/4 "%08x\n"' $< > $@
$(TARGET).elf.asm: $(TARGET).elf
$(OBJDUMP) -d -S $< > $@
disasm: $(TARGET).elf.asm
size: $(TARGET).elf
$(SIZE) $<
clean:
rm -f $(TARGET).elf $(TARGET).bin $(TARGET).hex $(TARGET).coe $(TARGET).mif \
$(TARGET).elf.asm $(TARGET).map $(OBJS)

View File

@@ -0,0 +1,35 @@
OUTPUT_ARCH("riscv")
ENTRY(_start)
MEMORY
{
RAM (rwx) : ORIGIN = 0x00000000, LENGTH = 8192
}
SECTIONS
{
.text :
{
KEEP(*(.text.init))
*(.text .text.*)
*(.rodata .rodata.*)
} > RAM
.data :
{
*(.data .data.*)
} > RAM
.bss (NOLOAD) :
{
__bss_start = .;
*(.bss .bss.*)
*(.sbss .sbss.*)
*(.scommon)
*(COMMON)
__bss_end = .;
} > RAM
. = ALIGN(4);
__stack_top = ORIGIN(RAM) + LENGTH(RAM);
}

View File

@@ -0,0 +1,99 @@
.section .text.init
.globl _start
.type _start, @function
_start:
la sp, __stack_top
# Zero .bss
la t0, __bss_start
la t1, __bss_end
1:
bgeu t0, t1, 2f
sw zero, 0(t0)
addi t0, t0, 4
j 1b
2:
call main
3:
j 3b
.size _start, .-_start
.section .text
.globl trap_entry
.type trap_entry, @function
trap_entry:
# Save full integer context (except x0/x2) because an interrupt can
# preempt code with live values in any register, not just caller-saved.
addi sp, sp, -128
sw ra, 124(sp)
sw gp, 120(sp)
sw tp, 116(sp)
sw t0, 112(sp)
sw t1, 108(sp)
sw t2, 104(sp)
sw s0, 100(sp)
sw s1, 96(sp)
sw a0, 92(sp)
sw a1, 88(sp)
sw a2, 84(sp)
sw a3, 80(sp)
sw a4, 76(sp)
sw a5, 72(sp)
sw a6, 68(sp)
sw a7, 64(sp)
sw s2, 60(sp)
sw s3, 56(sp)
sw s4, 52(sp)
sw s5, 48(sp)
sw s6, 44(sp)
sw s7, 40(sp)
sw s8, 36(sp)
sw s9, 32(sp)
sw s10, 28(sp)
sw s11, 24(sp)
sw t3, 20(sp)
sw t4, 16(sp)
sw t5, 12(sp)
sw t6, 8(sp)
csrr t0, mcause
li t1, 0x80000007 # machine timer interrupt (RV32)
bne t0, t1, 1f
call timer_isr # C function that ACKs/clears the timer so i_timer_irq goes low
1:
lw t6, 8(sp)
lw t5, 12(sp)
lw t4, 16(sp)
lw t3, 20(sp)
lw s11, 24(sp)
lw s10, 28(sp)
lw s9, 32(sp)
lw s8, 36(sp)
lw s7, 40(sp)
lw s6, 44(sp)
lw s5, 48(sp)
lw s4, 52(sp)
lw s3, 56(sp)
lw s2, 60(sp)
lw a7, 64(sp)
lw a6, 68(sp)
lw a5, 72(sp)
lw a4, 76(sp)
lw a3, 80(sp)
lw a2, 84(sp)
lw a1, 88(sp)
lw a0, 92(sp)
lw s1, 96(sp)
lw s0, 100(sp)
lw t2, 104(sp)
lw t1, 108(sp)
lw t0, 112(sp)
lw tp, 116(sp)
lw gp, 120(sp)
lw ra, 124(sp)
addi sp, sp, 128
mret

View File

@@ -0,0 +1,47 @@
#include <stdint.h>
#define GPIO_BASE 0x40000000u
static volatile uint32_t * const R_FREQ = (volatile uint32_t *)(GPIO_BASE+0);
static volatile uint32_t * const LEDS = (volatile uint32_t *)(GPIO_BASE+4);
static volatile uint32_t * const LEDGR = (volatile uint32_t *)(GPIO_BASE+8);
#define TIMER_BASE 0x40010000u
static volatile uint32_t * const TIMER_CNT = (volatile uint32_t *)(TIMER_BASE+0);
static volatile uint32_t * const TIMER_LD = (volatile uint32_t *)(TIMER_BASE+4);
static volatile uint32_t * const TIMER_ACK = (volatile uint32_t *)(TIMER_BASE+8);
#define MSTATUS_MIE (1u << 3)
#define MIE_MTIE (1u << 7)
extern void trap_entry();
static inline void irq_init() {
/* mtvec first */
asm volatile ("csrw mtvec, %0" :: "r"(trap_entry));
/* enable machine timer interrupt */
asm volatile ("csrs mie, %0" :: "r"(MIE_MTIE));
/* global enable last */
asm volatile ("csrs mstatus, %0" :: "r"(MSTATUS_MIE));
}
void timer_isr(){
*TIMER_ACK = 1;
*LEDGR = ~(*LEDGR);
}
void main(){
irq_init();
*LEDGR = 1;
*TIMER_LD = 2 * 15000000/1000;
for(;;){
for(int i=1000; i<10000; i+=10){
*R_FREQ = i;
*LEDS = i>>4;
// for(int j=0; j<80; j++) asm volatile("nop");
}
}
}

View File

@@ -0,0 +1,37 @@
`timescale 1ns/1ps
module tb_toplevel;
reg aclk;
reg aresetn;
wire led_green;
wire led_red;
wire [5:0] r2r;
wire [7:0] LED;
toplevel #(
.sim(1)
) dut (
.aclk(aclk),
.aresetn(aresetn),
.led_green(led_green),
.led_red(led_red),
.r2r(r2r),
.LED(LED)
);
initial aclk = 1'b0;
always #33.33 aclk = ~aclk;
initial begin
$dumpfile("toplevel.vcd");
$dumpvars(1, tb_toplevel);
aresetn = 1'b0;
#100;
aresetn = 1'b1;
#10_000_000;
$finish;
end
endmodule

View File

@@ -0,0 +1,64 @@
CAPI=2:
name: joppeb:system:test:1.0
description: Example top-level
filesets:
rtl:
depend:
- joppeb:primitive:clkgen
- joppeb:system:mcu
- joppeb:signal:nco_q15
- joppeb:util:conv
files:
- rtl/toplevel.v
file_type: verilogSource
tb:
files:
- tb/tb_toplevel.v
file_type: verilogSource
sw:
files:
- sw/sweep/sweep.hex : {copyto : sweep.hex}
file_type: user
mimas:
files:
- mimas.ucf : {file_type : UCF}
- options.tcl : {file_type : tclSource}
targets:
default:
filesets:
- rtl
toplevel: toplevel
sim:
default_tool: icarus
filesets:
- rtl
- sw
- tb
toplevel: tb_toplevel
mimas:
filesets:
- rtl
- mimas
- sw
toplevel: toplevel
parameters:
- FPGA_SPARTAN6=true
default_tool: ise
tools:
ise:
family: Spartan6
device: xc6slx9
package: tqg144
speed: -2
parameters:
FPGA_SPARTAN6:
datatype: bool
description: Select Spartan-6 family specific implementations
paramtype: vlogdefine

16
cores/util/cdc/cdc.core Normal file
View File

@@ -0,0 +1,16 @@
CAPI=2:
name: joppeb:util:cdc:1.0
description: Clock-domain crossing helpers
filesets:
rtl:
files:
- rtl/cdc_strobe_data.v
- rtl/cdc_req_resp.v
file_type: verilogSource
targets:
default:
filesets:
- rtl

View File

@@ -0,0 +1,70 @@
`timescale 1 ns/1 ps
// =============================================================================
// cdc_req_resp
// Bidirectional channel made from two cdc_strobe_data mailboxes.
// =============================================================================
module cdc_req_resp #(
parameter integer REQ_W = 32,
parameter integer RESP_W = 32,
parameter integer STABLE_SAMPLES = 2
)(
// Side A (e.g., JTAG/TCK)
input wire a_clk,
input wire a_rst,
input wire a_req_pulse,
input wire [REQ_W-1:0] a_req_data,
output wire a_req_busy,
output wire a_req_accepted,
output wire a_resp_pulse,
output wire [RESP_W-1:0] a_resp_data,
// Side B (e.g., system/i_clk)
input wire b_clk,
input wire b_rst,
output wire b_req_pulse,
output wire [REQ_W-1:0] b_req_data,
input wire b_resp_pulse,
input wire [RESP_W-1:0] b_resp_data,
output wire b_resp_busy,
output wire b_resp_accepted
);
cdc_strobe_data #(
.WIDTH(REQ_W),
.STABLE_SAMPLES(STABLE_SAMPLES)
) u_req (
.s_clk(a_clk),
.s_rst(a_rst),
.s_pulse(a_req_pulse),
.s_data(a_req_data),
.s_busy(a_req_busy),
.s_accepted(a_req_accepted),
.d_clk(b_clk),
.d_rst(b_rst),
.d_pulse(b_req_pulse),
.d_data(b_req_data)
);
cdc_strobe_data #(
.WIDTH(RESP_W),
.STABLE_SAMPLES(STABLE_SAMPLES)
) u_resp (
.s_clk(b_clk),
.s_rst(b_rst),
.s_pulse(b_resp_pulse),
.s_data(b_resp_data),
.s_busy(b_resp_busy),
.s_accepted(b_resp_accepted),
.d_clk(a_clk),
.d_rst(a_rst),
.d_pulse(a_resp_pulse),
.d_data(a_resp_data)
);
endmodule

View File

@@ -0,0 +1,130 @@
`timescale 1 ns/1 ps
// =============================================================================
// cdc_strobe_data
// - One-deep mailbox for (strobe + data) crossing clock domains.
// - Uses toggle req/ack with 2FF sync for toggles.
// - Wide bus is held stable by source until ack, destination samples-until-stable.
// =============================================================================
module cdc_strobe_data #(
parameter integer WIDTH = 32,
parameter integer STABLE_SAMPLES = 2 // >=2 recommended
)(
// Source domain
input wire s_clk,
input wire s_rst, // async OK (posedge) if used consistently
input wire s_pulse, // strobe (1+ cycles). Accepted when not busy.
input wire [WIDTH-1:0] s_data,
output wire s_busy, // 1 = mailbox full / waiting for ack
output wire s_accepted, // 1-cycle pulse when we accepted s_pulse
// Destination domain
input wire d_clk,
input wire d_rst,
output reg d_pulse, // 1-cycle pulse on new data
output reg [WIDTH-1:0] d_data // updated when d_pulse asserted; held otherwise
);
// ----------------------------
// Source: hold + req toggle
// ----------------------------
reg [WIDTH-1:0] s_hold;
reg s_req_tog;
reg s_inflight;
// Ack toggle synchronized into source domain
(* ASYNC_REG="TRUE" *) reg s_ack_sync1, s_ack_sync2;
assign s_busy = s_inflight;
wire do_accept = s_pulse && !s_inflight;
assign s_accepted = do_accept;
// d_ack_tog is generated in destination domain (declared below as reg)
// and is synced here with 2FF.
always @(posedge s_clk or posedge s_rst) begin
if (s_rst) begin
s_hold <= {WIDTH{1'b0}};
s_req_tog <= 1'b0;
s_inflight <= 1'b0;
s_ack_sync1 <= 1'b0;
s_ack_sync2 <= 1'b0;
end else begin
s_ack_sync1 <= d_ack_tog;
s_ack_sync2 <= s_ack_sync1;
// clear inflight when ack matches current req toggle
if (s_inflight && (s_ack_sync2 == s_req_tog))
s_inflight <= 1'b0;
// accept new item
if (do_accept) begin
s_hold <= s_data;
s_req_tog <= ~s_req_tog;
s_inflight <= 1'b1;
end
end
end
// ----------------------------
// Destination: sync req toggle, sample-until-stable, then ack toggle
// ----------------------------
(* ASYNC_REG="TRUE" *) reg d_req_sync1, d_req_sync2;
reg d_req_seen;
reg d_ack_tog;
reg [WIDTH-1:0] samp;
reg [WIDTH-1:0] samp_prev;
integer stable_cnt;
reg capturing;
wire d_new_req = (d_req_sync2 != d_req_seen);
always @(posedge d_clk or posedge d_rst) begin
if (d_rst) begin
d_req_sync1 <= 1'b0;
d_req_sync2 <= 1'b0;
d_req_seen <= 1'b0;
d_ack_tog <= 1'b0;
d_pulse <= 1'b0;
d_data <= {WIDTH{1'b0}};
samp <= {WIDTH{1'b0}};
samp_prev <= {WIDTH{1'b0}};
stable_cnt <= 0;
capturing <= 1'b0;
end else begin
d_pulse <= 1'b0;
d_req_sync1 <= s_req_tog;
d_req_sync2 <= d_req_sync1;
if (d_new_req && !capturing) begin
capturing <= 1'b1;
stable_cnt <= 0;
samp_prev <= s_hold;
samp <= s_hold;
end else if (capturing) begin
samp <= s_hold;
if (samp == samp_prev) begin
if (stable_cnt < (STABLE_SAMPLES-1))
stable_cnt <= stable_cnt + 1;
else begin
// accept
d_data <= samp;
d_pulse <= 1'b1;
d_req_seen <= d_req_sync2;
d_ack_tog <= ~d_ack_tog;
capturing <= 1'b0;
end
end else begin
stable_cnt <= 0;
end
samp_prev <= samp;
end
end
end
endmodule

View File

@@ -0,0 +1,16 @@
CAPI=2:
name: joppeb:util:clog2:1.0
description: Verilog-2001 compatible ceil(log2(x)) macro header
filesets:
include:
files:
- clog2.vh:
is_include_file: true
file_type: verilogSource
targets:
default:
filesets:
- include

41
cores/util/clog2/clog2.vh Normal file
View File

@@ -0,0 +1,41 @@
`ifndef CLOG2_VH
`define CLOG2_VH
// Verilog-2001 compatible ceil(log2(x)) macro (matches $clog2 semantics).
`ifndef CLOG2
`define CLOG2(x) \
(((x) <= 1) ? 0 : \
((x) <= 2) ? 1 : \
((x) <= 4) ? 2 : \
((x) <= 8) ? 3 : \
((x) <= 16) ? 4 : \
((x) <= 32) ? 5 : \
((x) <= 64) ? 6 : \
((x) <= 128) ? 7 : \
((x) <= 256) ? 8 : \
((x) <= 512) ? 9 : \
((x) <= 1024) ? 10 : \
((x) <= 2048) ? 11 : \
((x) <= 4096) ? 12 : \
((x) <= 8192) ? 13 : \
((x) <= 16384) ? 14 : \
((x) <= 32768) ? 15 : \
((x) <= 65536) ? 16 : \
((x) <= 131072) ? 17 : \
((x) <= 262144) ? 18 : \
((x) <= 524288) ? 19 : \
((x) <= 1048576) ? 20 : \
((x) <= 2097152) ? 21 : \
((x) <= 4194304) ? 22 : \
((x) <= 8388608) ? 23 : \
((x) <= 16777216) ? 24 : \
((x) <= 33554432) ? 25 : \
((x) <= 67108864) ? 26 : \
((x) <= 134217728) ? 27 : \
((x) <= 268435456) ? 28 : \
((x) <= 536870912) ? 29 : \
((x) <= 1073741824) ? 30 : \
((x) <= 2147483648) ? 31 : 32)
`endif
`endif

16
cores/util/conv/conv.core Normal file
View File

@@ -0,0 +1,16 @@
CAPI=2:
name: joppeb:util:conv:1.0
description: Verilog conversion helper header
filesets:
include:
files:
- conv.vh:
is_include_file: true
file_type: verilogSource
targets:
default:
filesets:
- include

View File

@@ -0,0 +1,15 @@
CAPI=2:
name: joppeb:util:mul_const:1.0
description: Constant multiplier helpers implemented with shift-add logic
filesets:
rtl:
files:
- rtl/mul_const.v
file_type: verilogSource
targets:
default:
filesets:
- rtl

View File

@@ -0,0 +1,31 @@
`timescale 1ns/1ps
module mul_const_shiftadd #(
parameter integer C = 0,
parameter integer IN_W = 16,
parameter integer OUT_W = 32
)(
input wire signed [IN_W-1:0] i_x,
output reg signed [OUT_W-1:0] o_y
);
integer k;
integer abs_c;
reg signed [OUT_W-1:0] acc;
reg signed [OUT_W-1:0] x_ext;
always @* begin
abs_c = (C < 0) ? -C : C;
acc = {OUT_W{1'b0}};
x_ext = {{(OUT_W-IN_W){i_x[IN_W-1]}}, i_x};
for (k = 0; k < 32; k = k + 1) begin
if (abs_c[k])
acc = acc + (x_ext <<< k);
end
if (C < 0)
o_y = -acc;
else
o_y = acc;
end
endmodule

View File

@@ -0,0 +1,205 @@
// formal_wb_master_checker.v
//
// Wishbone Classic master-side protocol checker (plain Verilog).
// Use when your DUT is a *master* and the bus/slave is the environment.
module formal_wb_master_checker #(
parameter OPT_USE_ERR = 0,
parameter OPT_USE_RTY = 0,
// If 1: require responses only when STB=1 (stricter than spec permission).
// If 0: allow "ghost" responses with STB=0; we only COUNT termination when STB=1.
parameter OPT_STRICT_RESP_WITH_STB = 0,
// If 1: require ACK/ERR/RTY to be single-cycle pulses.
// If 0: allow them to be held.
parameter OPT_PULSE_RESP = 1,
// If 1: after a synchronous reset has been asserted for one clock,
// require the master to hold CYC/STB low.
parameter OPT_STRICT_RESET = 1,
// Optional widths
parameter AW = 32,
parameter DW = 32,
parameter SW = 4
) (
input wire i_clk,
input wire i_rst,
input wire i_wb_rst,
// Master -> bus
input wire i_wb_cyc,
input wire i_wb_stb,
input wire i_wb_we,
input wire [AW-1:0] i_wb_adr,
input wire [DW-1:0] i_wb_dat,
input wire [SW-1:0] i_wb_sel,
// Bus/slave -> master
input wire o_wb_ack,
input wire o_wb_err,
input wire o_wb_rty,
input wire [DW-1:0] o_wb_rdt
);
`ifdef FORMAL
// -----------------------------
// Reset combine
// -----------------------------
wire rst_any;
assign rst_any = i_rst | i_wb_rst;
// -----------------------------
// Formal infrastructure
// -----------------------------
reg f_past_valid;
initial f_past_valid = 1'b0;
always @(posedge i_clk)
f_past_valid <= 1'b1;
wire f_resp_any;
assign f_resp_any = o_wb_ack
| (OPT_USE_ERR ? o_wb_err : 1'b0)
| (OPT_USE_RTY ? o_wb_rty : 1'b0);
// A "real" termination in Classic requires STB high (handshake qualifies the transfer)
wire f_terminate;
assign f_terminate = i_wb_cyc && i_wb_stb && f_resp_any;
// -----------------------------
// Track exactly one outstanding request (Classic)
// -----------------------------
reg f_pending;
initial f_pending = 1'b0;
always @(posedge i_clk or posedge rst_any) begin
if (rst_any) begin
f_pending <= 1'b0;
end else if (!i_wb_cyc) begin
// Dropping CYC ends the bus cycle and clears any outstanding request
f_pending <= 1'b0;
end else begin
// Start pending when STB is asserted and none is pending
if (!f_pending && i_wb_stb)
f_pending <= 1'b1;
// Clear pending only on a real termination (STB && response)
if (f_terminate)
f_pending <= 1'b0;
end
end
// -----------------------------
// Reset rules (Wishbone synchronous reset semantics)
// -----------------------------
always @(posedge i_clk) if (f_past_valid) begin
if (rst_any) begin
// R00: Monitor pending state must be cleared during reset
R00: assert(f_pending == 1'b0);
if (OPT_STRICT_RESET && $past(rst_any)) begin
// R01: After reset was asserted on the prior clock, master must be idle
R01: assert(!i_wb_cyc);
// R02: After reset was asserted on the prior clock, master must not strobe
R02: assert(!i_wb_stb);
end
if ($past(rst_any)) begin
// A00: After reset was asserted on the prior clock, environment should not respond
A00: assume(!o_wb_ack);
if (OPT_USE_ERR) A01: assume(!o_wb_err);
if (OPT_USE_RTY) A02: assume(!o_wb_rty);
end
end
end
// -----------------------------
// Master-side RULES (ASSERTIONS)
// -----------------------------
always @(posedge i_clk) if (f_past_valid && !rst_any) begin
// R10: STB must imply CYC (no strobe outside of a cycle)
if (i_wb_stb) begin
R10: assert(i_wb_cyc);
end
// R11: While a request is pending and NOT terminated, master must keep CYC asserted
if ($past(f_pending) && !$past(f_terminate)) begin
R11: assert(i_wb_cyc);
end
// R12: While a request is pending and NOT terminated, master must keep STB asserted
if ($past(f_pending) && !$past(f_terminate)) begin
R12: assert(i_wb_stb);
end
// R13: Address stable while pending and not terminated
if ($past(f_pending) && !$past(f_terminate)) begin
R13: assert(i_wb_adr == $past(i_wb_adr));
end
// R14: WE stable while pending and not terminated
if ($past(f_pending) && !$past(f_terminate)) begin
R14: assert(i_wb_we == $past(i_wb_we));
end
// R15: SEL stable while pending and not terminated
if ($past(f_pending) && !$past(f_terminate)) begin
R15: assert(i_wb_sel == $past(i_wb_sel));
end
// R16: Write data stable while pending and not terminated
if ($past(f_pending) && !$past(f_terminate)) begin
R16: assert(i_wb_dat == $past(i_wb_dat));
end
end
// -----------------------------
// Environment SLAVE assumptions
// -----------------------------
always @(posedge i_clk) if (f_past_valid && !rst_any) begin
// A10: Any response must occur only during an active cycle
if (f_resp_any) begin
A10: assume(i_wb_cyc);
end
// A11: Optional strict profile: response only when STB=1
if (OPT_STRICT_RESP_WITH_STB && f_resp_any) begin
A11: assume(i_wb_stb);
end
// A12-A14: Mutual exclusion between ACK/ERR/RTY (recommended)
if (o_wb_ack) begin
if (OPT_USE_ERR) A12s1: assume(!o_wb_err);
if (OPT_USE_RTY) A12s2: assume(!o_wb_rty);
end
if (OPT_USE_ERR && o_wb_err) begin
A13s1: assume(!o_wb_ack);
if (OPT_USE_RTY) A13s2: assume(!o_wb_rty);
end
if (OPT_USE_RTY && o_wb_rty) begin
A14s1: assume(!o_wb_ack);
if (OPT_USE_ERR) A14s2: assume(!o_wb_err);
end
// A15-A17: Optional pulse-only responses
if (OPT_PULSE_RESP) begin
if ($past(o_wb_ack)) begin
A15: assume(!o_wb_ack);
end
if (OPT_USE_ERR && $past(o_wb_err)) begin
A16: assume(!o_wb_err);
end
if (OPT_USE_RTY && $past(o_wb_rty)) begin
A17: assume(!o_wb_rty);
end
end
end
// -----------------------------
// Coverage: explore protocol space ("state diagram" witnesses)
// -----------------------------
// TODO
`endif
endmodule

View File

@@ -0,0 +1,222 @@
// formal_wb_slave_checker.v
//
// Wishbone Classic slave-side protocol checker (plain Verilog).
// Use when your DUT is a *slave* and the bus/master is the environment.
module formal_wb_slave_checker #(
parameter OPT_USE_ERR = 0,
parameter OPT_USE_RTY = 0,
// If 1: require slave to only assert responses when STB=1 (stricter profile).
// If 0: allow "ghost" responses with STB=0; termination still only counts when STB=1.
parameter OPT_STRICT_RESP_WITH_STB = 0,
// If 1: require termination signals to be pulses (1 cycle).
// If 0: allow them to be held.
parameter OPT_PULSE_RESP = 1,
// If 1: after a synchronous reset has been asserted for one clock,
// require the master to hold CYC/STB low.
parameter OPT_STRICT_RESET = 1,
// If 1: assert read data stable while ACK is held (useful if you allow ACK-hold).
parameter OPT_ASSERT_RDATA_STABLE_DURING_ACK = 1,
// Optional widths
parameter AW = 32,
parameter DW = 32,
parameter SW = 4
) (
input wire i_clk,
input wire i_rst,
input wire i_wb_rst,
// Master -> slave
input wire i_wb_cyc,
input wire i_wb_stb,
input wire i_wb_we,
input wire [AW-1:0] i_wb_adr,
input wire [DW-1:0] i_wb_dat,
input wire [SW-1:0] i_wb_sel,
// Slave -> master
input wire o_wb_ack,
input wire o_wb_err,
input wire o_wb_rty,
input wire [DW-1:0] o_wb_rdt
);
`ifdef FORMAL
// -----------------------------
// Reset combine
// -----------------------------
wire rst_any;
assign rst_any = i_rst | i_wb_rst;
// -----------------------------
// Formal infrastructure
// -----------------------------
reg f_past_valid;
initial f_past_valid = 1'b0;
always @(posedge i_clk)
f_past_valid <= 1'b1;
wire f_resp_any;
assign f_resp_any = o_wb_ack
| (OPT_USE_ERR ? o_wb_err : 1'b0)
| (OPT_USE_RTY ? o_wb_rty : 1'b0);
// Real termination only counts when STB=1
wire f_terminate;
assign f_terminate = i_wb_cyc && i_wb_stb && f_resp_any;
// -----------------------------
// Outstanding request tracking (Classic)
// -----------------------------
reg f_pending;
initial f_pending = 1'b0;
always @(posedge i_clk or posedge rst_any) begin
if (rst_any) begin
f_pending <= 1'b0;
end else if (!i_wb_cyc) begin
f_pending <= 1'b0;
end else begin
if (!f_pending && i_wb_stb)
f_pending <= 1'b1;
if (f_terminate)
f_pending <= 1'b0;
end
end
// -----------------------------
// Reset rules (Wishbone synchronous reset semantics)
// -----------------------------
always @(posedge i_clk) if (f_past_valid) begin
if (rst_any) begin
// R00: Monitor pending state must be cleared during reset
R00: assert(f_pending == 1'b0);
if (OPT_STRICT_RESET && $past(rst_any)) begin
// A00: After reset was asserted on the prior clock, assume master is idle
A00: assume(!i_wb_cyc);
// A01: After reset was asserted on the prior clock, assume master is not strobing
A01: assume(!i_wb_stb);
end
if ($past(rst_any)) begin
// R01: After reset was asserted on the prior clock, slave must not respond
R01: assert(!o_wb_ack);
if (OPT_USE_ERR) R02: assert(!o_wb_err);
if (OPT_USE_RTY) R03: assert(!o_wb_rty);
end
end
end
// -----------------------------
// Master/environment assumptions (Classic rules)
// -----------------------------
always @(posedge i_clk) if (f_past_valid && !rst_any) begin
// A10: STB must imply CYC
if (i_wb_stb) begin
A10: assume(i_wb_cyc);
end
// A11: While pending and not terminated, master holds CYC asserted
if ($past(f_pending) && !$past(f_terminate)) begin
A11: assume(i_wb_cyc);
end
// A12: While pending and not terminated, master holds STB asserted
if ($past(f_pending) && !$past(f_terminate)) begin
A12: assume(i_wb_stb);
end
// A13: Address stable while pending and not terminated
if ($past(f_pending) && !$past(f_terminate)) begin
A13: assume(i_wb_adr == $past(i_wb_adr));
end
// A14: WE stable while pending and not terminated
if ($past(f_pending) && !$past(f_terminate)) begin
A14: assume(i_wb_we == $past(i_wb_we));
end
// A15: SEL stable while pending and not terminated
if ($past(f_pending) && !$past(f_terminate)) begin
A15: assume(i_wb_sel == $past(i_wb_sel));
end
// A16: Write data stable while pending and not terminated
if ($past(f_pending) && !$past(f_terminate)) begin
A16: assume(i_wb_dat == $past(i_wb_dat));
end
end
// -----------------------------
// Slave/DUT assertions (response sanity)
// -----------------------------
always @(posedge i_clk) if (f_past_valid && !rst_any) begin
// R10: Any response must occur only during an active cycle
if (f_resp_any) begin
R10: assert(i_wb_cyc);
end
// R11: Optional strict profile: response only when STB=1
if (OPT_STRICT_RESP_WITH_STB && f_resp_any) begin
R11: assert(i_wb_stb);
end
// R12-R14: Mutual exclusion of termination signals (recommended)
if (o_wb_ack) begin
if (OPT_USE_ERR) R12s1: assert(!o_wb_err);
if (OPT_USE_RTY) R12s2: assert(!o_wb_rty);
end
if (OPT_USE_ERR && o_wb_err) begin
R13s1: assert(!o_wb_ack);
if (OPT_USE_RTY) R13s2: assert(!o_wb_rty);
end
if (OPT_USE_RTY && o_wb_rty) begin
R14s1: assert(!o_wb_ack);
if (OPT_USE_ERR) R14s2: assert(!o_wb_err);
end
// R15-R17: Optional pulse-only responses
if (OPT_PULSE_RESP) begin
if ($past(o_wb_ack)) begin
R15: assert(!o_wb_ack);
end
if (OPT_USE_ERR && $past(o_wb_err)) begin
R16: assert(!o_wb_err);
end
if (OPT_USE_RTY && $past(o_wb_rty)) begin
R17: assert(!o_wb_rty);
end
end
// R18: A real termination (STB && response) should only happen when a request is pending.
if (i_wb_stb && f_resp_any) begin
R18: assert(f_pending || $past(f_pending));
end
// R19: Optional read-data stability while ACK is held (only relevant if ACK can be held)
if (OPT_ASSERT_RDATA_STABLE_DURING_ACK) begin
if (o_wb_ack && !i_wb_we && $past(o_wb_ack)) begin
R19: assert(o_wb_rdt == $past(o_wb_rdt));
end
end
// R20: If no cycle, no responses (strong sanity rule)
if (!i_wb_cyc) begin
R20: assert(!f_resp_any);
end
end
// -----------------------------
// Coverage: exercise the slave (useful witness traces)
// -----------------------------
// TODO
`endif
endmodule

View File

@@ -0,0 +1,16 @@
CAPI=2:
name: joppeb:wb:formal_checker:1.0
description: Reusable formal Wishbone protocol checkers
filesets:
formal_rtl:
files:
- formal/formal_wb_slave_checker.v
- formal/formal_wb_master_checker.v
file_type: verilogSource
targets:
default:
filesets:
- formal_rtl

View File

@@ -0,0 +1,63 @@
`timescale 1ns/1ps
module formal_jtag_wb_bridge;
(* gclk *) reg i_clk;
(* anyseq *) reg i_rst;
(* anyseq *) reg [31:0] i_wb_rdt;
(* anyseq *) reg i_wb_ack;
reg f_past_valid;
wire [31:0] o_wb_adr;
wire [31:0] o_wb_dat;
wire [3:0] o_wb_sel;
wire o_wb_we;
wire o_wb_cyc;
wire o_wb_stb;
wire o_cmd_reset;
// This bridge has no dedicated Wishbone reset output.
wire f_wb_rst = 1'b0;
jtag_wb_bridge #(
.chain(1),
.byte_aligned(0)
) dut (
.i_clk(i_clk),
.i_rst(i_rst),
.o_wb_adr(o_wb_adr),
.o_wb_dat(o_wb_dat),
.o_wb_sel(o_wb_sel),
.o_wb_we(o_wb_we),
.o_wb_cyc(o_wb_cyc),
.o_wb_stb(o_wb_stb),
.i_wb_rdt(i_wb_rdt),
.i_wb_ack(i_wb_ack),
.o_cmd_reset(o_cmd_reset)
);
formal_wb_master_checker wb_checker (
.i_clk(i_clk),
.i_rst(i_rst),
.i_wb_rst(f_wb_rst),
.i_wb_adr(o_wb_adr),
.i_wb_dat(o_wb_dat),
.i_wb_sel(o_wb_sel),
.i_wb_we(o_wb_we),
.i_wb_stb(o_wb_stb),
.i_wb_cyc(o_wb_cyc),
.o_wb_rdt(i_wb_rdt),
.o_wb_ack(i_wb_ack)
);
initial f_past_valid = 1'b0;
always @(posedge i_clk) begin
f_past_valid <= 1'b1;
// A1: Start in reset so the bridge state is initialized
if (!f_past_valid)
assume(i_rst);
end
wire unused = &{1'b0, o_cmd_reset};
endmodule

View File

@@ -0,0 +1,24 @@
[tasks]
prove
cover
bmc
[options]
bmc: mode bmc
bmc: depth 16
cover: mode cover
cover: depth 16
prove: mode prove
[engines]
bmc: smtbmc yices
cover: smtbmc yices
prove: abc pdr
[script]
{{"-formal"|gen_reads}}
prep -top {{top_level}}
clk2fflogic
[files]
{{files}}

View File

@@ -0,0 +1,13 @@
[options]
mode prove
[engines]
abc pdr
[script]
{{"-formal"|gen_reads}}
prep -top {{top_level}}
clk2fflogic
[files]
{{files}}

View File

@@ -0,0 +1,2 @@
SBY 17:52:48 [/data/joppe/projects/fusesoc_test/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge_prove] Removing directory '/data/joppe/projects/fusesoc_test/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge_prove'.
SBY 17:52:48 [/data/joppe/projects/fusesoc_test/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge_prove] Copy '/data/joppe/projects/fusesoc_test/cores/wb/jtag_wb_bridge/formal/{{files}}' to '/data/joppe/projects/fusesoc_test/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge_prove/src/{{files}}'.

View File

@@ -0,0 +1 @@
../jtag_wb_bridge/status.sqlite

View File

@@ -0,0 +1,63 @@
`timescale 1ns/1ps
module cdc_req_resp #(
parameter integer REQ_W = 32,
parameter integer RESP_W = 32,
parameter integer STABLE_SAMPLES = 2
)(
input wire a_clk,
input wire a_rst,
input wire a_req_pulse,
input wire [REQ_W-1:0] a_req_data,
output wire a_req_busy,
output wire a_req_accepted,
output wire a_resp_pulse,
output wire [RESP_W-1:0] a_resp_data,
input wire b_clk,
input wire b_rst,
output reg b_req_pulse,
output reg [REQ_W-1:0] b_req_data,
input wire b_resp_pulse,
input wire [RESP_W-1:0] b_resp_data,
output wire b_resp_busy,
output wire b_resp_accepted
);
(* anyseq *) reg f_req_pulse;
(* anyseq *) reg [REQ_W-1:0] f_req_data;
reg f_past_valid;
assign a_req_busy = 1'b0;
assign a_req_accepted = 1'b0;
assign a_resp_pulse = 1'b0;
assign a_resp_data = {RESP_W{1'b0}};
assign b_resp_busy = 1'b0;
assign b_resp_accepted = 1'b0;
initial f_past_valid = 1'b0;
always @(posedge b_clk) begin
f_past_valid <= 1'b1;
b_req_pulse <= f_req_pulse;
b_req_data <= f_req_data;
// A1: no abstract request while system reset is asserted
if (b_rst)
assume(!f_req_pulse);
// A2: abstract requests are single-cycle pulses
if (f_past_valid && $past(f_req_pulse))
assume(!f_req_pulse);
end
wire unused = &{
1'b0,
a_clk,
a_rst,
a_req_pulse,
a_req_data,
b_resp_pulse,
b_resp_data,
STABLE_SAMPLES[0]
};
endmodule

View File

@@ -0,0 +1,28 @@
`timescale 1ns/1ps
module jtag_if #(
parameter chain = 1
)(
input wire i_tdo,
output wire o_tck,
output wire o_tdi,
output wire o_drck,
output wire o_capture,
output wire o_shift,
output wire o_update,
output wire o_runtest,
output wire o_reset,
output wire o_sel
);
assign o_tck = 1'b0;
assign o_tdi = 1'b0;
assign o_drck = 1'b0;
assign o_capture = 1'b0;
assign o_shift = 1'b0;
assign o_update = 1'b0;
assign o_runtest = 1'b0;
assign o_reset = 1'b0;
assign o_sel = 1'b0;
wire unused = &{1'b0, i_tdo, chain[0]};
endmodule

View File

@@ -0,0 +1,64 @@
CAPI=2:
name: joppeb:wb:jtag_wb_bridge:1.0
description: Generic JTAG boundary scan to Wishbone classic bridge
filesets:
rtl:
depend:
- joppeb:primitive:jtag_if
- joppeb:util:cdc
files:
- rtl/jtag_wb_bridge.v
file_type: verilogSource
formal_rtl:
depend:
- joppeb:wb:formal_checker
files:
- formal/formal_jtag_wb_bridge.v
file_type: verilogSource
formal_abstract_rtl:
depend:
- joppeb:wb:formal_checker
files:
- rtl/jtag_wb_bridge.v
- formal/stub_jtag_if.v
- formal/stub_cdc_req_resp.v
- formal/formal_jtag_wb_bridge.v
file_type: verilogSource
formal_cfg:
files:
- formal/jtag_wb_bridge.sby
file_type: sbyConfigTemplate
targets:
default:
filesets:
- rtl
toplevel: jtag_wb_bridge
parameters:
- chain
- byte_aligned
formal:
default_tool: symbiyosys
filesets:
- rtl
- formal_rtl
- formal_cfg
toplevel: formal_jtag_wb_bridge
formal_abstract:
default_tool: symbiyosys
filesets:
- formal_abstract_rtl
- formal_cfg
toplevel: formal_jtag_wb_bridge
parameters:
chain:
datatype: int
description: User chain
paramtype: vlogparam
byte_aligned:
datatype: int
description: use addr[1:0] for byte lane on 32-bit WB when 0, always use lane 0 when 1
paramtype: vlogparam

View File

@@ -0,0 +1,538 @@
`timescale 1 ns/1 ps
module jtag_wb_bridge #(
parameter integer chain = 1,
// 0: use addr[1:0] for byte lane on 32-bit WB
// 1: always use lane 0
parameter integer byte_aligned = 0
)(
input wire i_clk,
input wire i_rst,
output wire [31:0] o_wb_adr,
output wire [31:0] o_wb_dat,
output wire [3:0] o_wb_sel,
output wire o_wb_we,
output wire o_wb_cyc,
output wire o_wb_stb,
input wire [31:0] i_wb_rdt,
input wire i_wb_ack,
output wire o_cmd_reset
);
// ===========================================================================
// JTAG interface (Spartan-6 BSCAN wrapper)
// ===========================================================================
wire jtag_tck;
wire jtag_tdi;
wire jtag_drck;
wire jtag_capture;
wire jtag_shift;
wire jtag_update;
wire jtag_runtest;
wire jtag_reset;
wire jtag_sel;
localparam integer JTAG_DR_W = 72;
// 72-bit DR (symmetrical command/response)
// Command layout: [71:64] opcode, [63:32] addr, [31:0] data
// Response layout: [71:64] resp_seq, [63:56] status, [55:48] cmd_seq,
// [47:16] data, [15:8] flags, [7:0] last_op
reg [JTAG_DR_W-1:0] jtag_shreg;
jtag_if #(
.chain(chain)
) u_jtag (
.i_tdo(jtag_shreg[0]),
.o_tck(jtag_tck),
.o_tdi(jtag_tdi),
.o_drck(jtag_drck),
.o_capture(jtag_capture),
.o_shift(jtag_shift),
.o_update(jtag_update),
.o_runtest(jtag_runtest),
.o_reset(jtag_reset),
.o_sel(jtag_sel)
);
wire jtag_async_reset = jtag_reset || i_rst;
// ===========================================================================
// CDC request/response channel (72/72 symmetric)
// Side A: JTAG/TCK domain
// Side B: system/i_clk domain
// ===========================================================================
wire a_req_busy;
wire a_req_accepted;
wire a_resp_pulse;
wire [JTAG_DR_W-1:0] a_resp_data;
wire b_req_pulse;
wire [JTAG_DR_W-1:0] b_req_data;
reg b_resp_pulse;
reg [JTAG_DR_W-1:0] b_resp_data;
wire b_resp_busy;
wire b_resp_accepted;
// Accept UPDATE as a request strobe (qualified by SEL and !busy)
wire a_req_pulse = jtag_sel && jtag_update && !a_req_busy;
wire [JTAG_DR_W-1:0] a_req_data = jtag_shreg;
cdc_req_resp #(
.REQ_W(JTAG_DR_W),
.RESP_W(JTAG_DR_W),
.STABLE_SAMPLES(2)
) u_cdc (
.a_clk(jtag_tck),
.a_rst(jtag_async_reset),
.a_req_pulse(a_req_pulse),
.a_req_data(a_req_data),
.a_req_busy(a_req_busy),
.a_req_accepted(a_req_accepted),
.a_resp_pulse(a_resp_pulse),
.a_resp_data(a_resp_data),
.b_clk(i_clk),
.b_rst(i_rst),
.b_req_pulse(b_req_pulse),
.b_req_data(b_req_data),
.b_resp_pulse(b_resp_pulse),
.b_resp_data(b_resp_data),
.b_resp_busy(b_resp_busy),
.b_resp_accepted(b_resp_accepted)
);
// ===========================================================================
// JTAG/TCK domain shift/capture
// ===========================================================================
reg [JTAG_DR_W-1:0] resp_hold_tck;
always @(posedge jtag_tck or posedge jtag_async_reset) begin
if (jtag_async_reset) begin
jtag_shreg <= {JTAG_DR_W{1'b0}};
resp_hold_tck <= {JTAG_DR_W{1'b0}};
end else begin
// Latch new response word from CDC when it arrives (independent of CAPTURE)
if (a_resp_pulse) begin
resp_hold_tck <= a_resp_data;
end
if (jtag_sel && jtag_capture) begin
// Load response into shift register for host readout
jtag_shreg <= resp_hold_tck;
end else if (jtag_sel && jtag_shift) begin
// Shift: MSB in, LSB out to TDO
jtag_shreg <= {jtag_tdi, jtag_shreg[JTAG_DR_W-1:1]};
end
end
end
// ===========================================================================
// System domain: Wishbone master + small command queue + response pending
// ===========================================================================
// Opcodes
localparam [7:0] OP_NOP = 8'h00;
localparam [7:0] OP_RESET_ON = 8'h10;
localparam [7:0] OP_RESET_OFF = 8'h11;
localparam [7:0] OP_WRITE8 = 8'h20;
localparam [7:0] OP_READ8 = 8'h21;
localparam [7:0] OP_WRITE32 = 8'h22;
localparam [7:0] OP_READ32 = 8'h23;
localparam [7:0] OP_PING = 8'h30;
localparam [7:0] OP_CLEAR_FLAGS = 8'h40;
// Wishbone regs
reg wb_busy;
reg [31:0] wb_adr_r;
reg [31:0] wb_dat_r;
reg [3:0] wb_sel_r;
reg wb_we_r;
assign o_wb_adr = wb_adr_r;
assign o_wb_dat = wb_dat_r;
assign o_wb_sel = wb_sel_r;
assign o_wb_we = wb_we_r;
assign o_wb_cyc = wb_busy;
assign o_wb_stb = wb_busy;
// Reset control
reg cmd_reset_level_r;
assign o_cmd_reset = cmd_reset_level_r;
// For reporting only: sync a_req_busy (TCK domain) into i_clk
(* ASYNC_REG="TRUE" *) reg req_busy_sync1, req_busy_sync2;
wire req_busy_tck_sync = req_busy_sync2;
// Sequencing
reg [7:0] cmd_seq_r;
reg [7:0] resp_seq_r;
// Sticky flags (cleared by CLEAR_FLAGS or reset)
reg flag_cmd_overflow;
reg flag_illegal;
reg flag_wb_busy_at_req;
// Snapshot info
reg last_we_r;
reg [7:0] last_opcode_r;
// Active command / queued command
reg act_valid;
reg [7:0] act_opcode;
reg [31:0] act_addr;
reg [31:0] act_data;
reg [7:0] act_seq;
reg q_valid;
reg [7:0] q_opcode;
reg [31:0] q_addr;
reg [31:0] q_data;
reg [7:0] q_seq;
// Response pending buffer (to avoid dropping if resp mailbox busy)
reg resp_pending;
reg [JTAG_DR_W-1:0] resp_pending_word;
// Lane selection
wire [1:0] addr_lane = byte_aligned ? 2'b00 : act_addr[1:0];
// Helpers: form SEL/DAT for byte write
function [3:0] sel_from_lane(input [1:0] lane);
case (lane)
2'b00: sel_from_lane = 4'b0001;
2'b01: sel_from_lane = 4'b0010;
2'b10: sel_from_lane = 4'b0100;
default: sel_from_lane = 4'b1000;
endcase
endfunction
function [31:0] dat_from_lane_byte(input [1:0] lane, input [7:0] b);
case (lane)
2'b00: dat_from_lane_byte = {24'b0, b};
2'b01: dat_from_lane_byte = {16'b0, b, 8'b0};
2'b10: dat_from_lane_byte = {8'b0, b, 16'b0};
default: dat_from_lane_byte = {b, 24'b0};
endcase
endfunction
function [7:0] byte_from_lane(input [1:0] lane, input [31:0] w);
case (lane)
2'b00: byte_from_lane = w[7:0];
2'b01: byte_from_lane = w[15:8];
2'b10: byte_from_lane = w[23:16];
default: byte_from_lane = w[31:24];
endcase
endfunction
// Build response word
function [JTAG_DR_W-1:0] pack_resp(
input [7:0] resp_seq,
input [7:0] status,
input [7:0] cmd_seq,
input [31:0] data,
input [7:0] flags,
input [7:0] last_op
);
pack_resp = {resp_seq, status, cmd_seq, data, flags, last_op};
endfunction
// STATUS bits (snapshot)
wire [7:0] status_snapshot = {
2'b00, // [7:6]
1'b1, // [5] resp_valid
last_we_r, // [4] last_we
cmd_reset_level_r, // [3] reset_level
b_resp_busy, // [2] resp_busy (system domain)
req_busy_tck_sync, // [1] req_busy (synced from TCK just for reporting)
wb_busy // [0] wb_busy
};
// FLAGS bits (sticky)
wire [7:0] flags_sticky = {
4'b0000, // [7:4] reserved
1'b0, // [3] reserved
flag_wb_busy_at_req, // [2]
flag_illegal, // [1]
flag_cmd_overflow // [0]
};
// Queue a command (or set overflow sticky if queue full)
task automatic enqueue_cmd(
input [7:0] op,
input [31:0] addr,
input [31:0] dat,
input [7:0] seq
);
begin
if (!q_valid) begin
q_valid <= 1'b1;
q_opcode <= op;
q_addr <= addr;
q_data <= dat;
q_seq <= seq;
end else begin
// Already have one queued; mark overflow and drop this command
flag_cmd_overflow <= 1'b1;
end
end
endtask
// Start executing a command (for non-WB ops may immediately create a response)
task automatic start_active_cmd(
input [7:0] cmd_opcode,
input [31:0] cmd_addr,
input [31:0] cmd_data,
input [7:0] cmd_seq
);
reg [1:0] cmd_addr_lane;
begin
cmd_addr_lane = byte_aligned ? 2'b00 : cmd_addr[1:0];
last_opcode_r <= cmd_opcode;
last_we_r <= (cmd_opcode == OP_WRITE8) || (cmd_opcode == OP_WRITE32);
// If we're already mid-flight or holding a response, note it (diagnostic)
if (wb_busy || resp_pending)
flag_wb_busy_at_req <= 1'b1;
case (cmd_opcode)
OP_NOP: begin
// immediate response
resp_pending_word <= pack_resp(resp_seq_r, status_snapshot, cmd_seq, 8'h00, flags_sticky, cmd_opcode);
resp_pending <= 1'b1;
end
OP_PING: begin
resp_pending_word <= pack_resp(resp_seq_r, status_snapshot, cmd_seq, 8'hA5, flags_sticky, cmd_opcode);
resp_pending <= 1'b1;
end
OP_CLEAR_FLAGS: begin
flag_cmd_overflow <= 1'b0;
flag_illegal <= 1'b0;
flag_wb_busy_at_req <= 1'b0;
resp_pending_word <= pack_resp(resp_seq_r, status_snapshot, cmd_seq, 8'h00, 8'h00, cmd_opcode);
resp_pending <= 1'b1;
end
OP_RESET_ON: begin
cmd_reset_level_r <= 1'b1;
resp_pending_word <= pack_resp(resp_seq_r, status_snapshot, cmd_seq, 8'h00, flags_sticky, cmd_opcode);
resp_pending <= 1'b1;
end
OP_RESET_OFF: begin
cmd_reset_level_r <= 1'b0;
resp_pending_word <= pack_resp(resp_seq_r, status_snapshot, cmd_seq, 8'h00, flags_sticky, cmd_opcode);
resp_pending <= 1'b1;
end
OP_WRITE8: begin
// launch WB write (byte)
wb_busy <= 1'b1;
wb_we_r <= 1'b1;
wb_adr_r <= cmd_addr;
wb_sel_r <= sel_from_lane(cmd_addr_lane);
wb_dat_r <= dat_from_lane_byte(cmd_addr_lane, cmd_data[7:0]);
end
OP_READ8: begin
// launch WB read (byte select)
wb_busy <= 1'b1;
wb_we_r <= 1'b0;
wb_adr_r <= cmd_addr;
wb_sel_r <= sel_from_lane(cmd_addr_lane);
wb_dat_r <= 32'b0;
end
OP_WRITE32: begin
// launch WB write (full word)
wb_busy <= 1'b1;
wb_we_r <= 1'b1;
wb_adr_r <= cmd_addr;
wb_sel_r <= 4'b1111;
wb_dat_r <= cmd_data;
end
OP_READ32: begin
// launch WB read (full word)
wb_busy <= 1'b1;
wb_we_r <= 1'b0;
wb_adr_r <= cmd_addr;
wb_sel_r <= 4'b1111;
wb_dat_r <= 32'b0;
end
default: begin
flag_illegal <= 1'b1;
resp_pending_word <= pack_resp(resp_seq_r, status_snapshot, cmd_seq, 8'h00, flags_sticky, cmd_opcode);
resp_pending <= 1'b1;
end
endcase
end
endtask
// System main
always @(posedge i_clk) begin
if (i_rst) begin
wb_busy <= 1'b0;
wb_adr_r <= 32'b0;
wb_dat_r <= 32'b0;
wb_sel_r <= 4'b0000;
wb_we_r <= 1'b0;
cmd_reset_level_r<= 1'b0;
req_busy_sync1 <= 1'b0;
req_busy_sync2 <= 1'b0;
cmd_seq_r <= 8'd0;
resp_seq_r <= 8'd0;
flag_cmd_overflow<= 1'b0;
flag_illegal <= 1'b0;
flag_wb_busy_at_req <= 1'b0;
last_we_r <= 1'b0;
last_opcode_r <= 8'h00;
act_valid <= 1'b0;
act_opcode <= 8'h00;
act_addr <= 32'h0;
act_data <= 32'h0000_0000;
act_seq <= 8'h00;
q_valid <= 1'b0;
q_opcode <= 8'h00;
q_addr <= 32'h0;
q_data <= 32'h0000_0000;
q_seq <= 8'h00;
resp_pending <= 1'b0;
resp_pending_word<= {JTAG_DR_W{1'b0}};
b_resp_pulse <= 1'b0;
b_resp_data <= {JTAG_DR_W{1'b0}};
end else begin
b_resp_pulse <= 1'b0;
// Sync req-busy level (reporting only)
req_busy_sync1 <= a_req_busy;
req_busy_sync2 <= req_busy_sync1;
// -----------------------------------------------------------------------
// Accept incoming command from CDC (always delivered; we buffer internally)
// -----------------------------------------------------------------------
if (b_req_pulse) begin
// assign a sequence number to each received command
cmd_seq_r <= cmd_seq_r + 8'd1;
// If we can start immediately (no active, no wb, no pending response), do so.
if (!act_valid && !wb_busy && !resp_pending) begin
act_valid <= 1'b1;
act_opcode <= b_req_data[71:64];
act_addr <= b_req_data[63:32];
act_data <= b_req_data[31:0];
act_seq <= cmd_seq_r;
// Start it right away
start_active_cmd(b_req_data[71:64], b_req_data[63:32], b_req_data[31:0], cmd_seq_r);
end else begin
// Otherwise enqueue one-deep
enqueue_cmd(b_req_data[71:64], b_req_data[63:32], b_req_data[31:0], cmd_seq_r);
end
end
// -----------------------------------------------------------------------
// Wishbone completion -> create response (but don't drop; buffer pending)
// -----------------------------------------------------------------------
if (wb_busy && i_wb_ack) begin
wb_busy <= 1'b0;
wb_we_r <= 1'b0;
// Determine response data
case (act_opcode)
OP_READ8: begin
resp_pending_word <= pack_resp(
resp_seq_r,
status_snapshot,
act_seq,
{24'b0, byte_from_lane(addr_lane, i_wb_rdt)},
flags_sticky,
act_opcode
);
end
OP_READ32: begin
resp_pending_word <= pack_resp(
resp_seq_r,
status_snapshot,
act_seq,
i_wb_rdt,
flags_sticky,
act_opcode
);
end
default: begin
// WRITE8/WRITE32: echo written data
resp_pending_word <= pack_resp(
resp_seq_r,
status_snapshot,
act_seq,
act_data,
flags_sticky,
act_opcode
);
end
endcase
resp_pending <= 1'b1;
end
// -----------------------------------------------------------------------
// If we have a pending response and response mailbox is free, send it
// -----------------------------------------------------------------------
if (resp_pending && !b_resp_busy) begin
b_resp_data <= resp_pending_word;
b_resp_pulse <= 1'b1;
resp_pending <= 1'b0;
resp_seq_r <= resp_seq_r + 8'd1;
// Mark active command complete
act_valid <= 1'b0;
// If there is a queued command and the WB port is idle, promote it now.
if (q_valid && !wb_busy) begin
act_valid <= 1'b1;
act_opcode <= q_opcode;
act_addr <= q_addr;
act_data <= q_data;
act_seq <= q_seq;
q_valid <= 1'b0;
start_active_cmd(q_opcode, q_addr, q_data, q_seq);
end
end
// -----------------------------------------------------------------------
// If no active command but there is a queued one (and we're not busy), start it
// -----------------------------------------------------------------------
if (!act_valid && q_valid && !wb_busy && !resp_pending) begin
act_valid <= 1'b1;
act_opcode <= q_opcode;
act_addr <= q_addr;
act_data <= q_data;
act_seq <= q_seq;
q_valid <= 1'b0;
start_active_cmd(q_opcode, q_addr, q_data, q_seq);
end
end
end
endmodule

View File

@@ -0,0 +1,6 @@
__pycache__/
*.d
*.o
*.a
*.so
prog

View File

@@ -0,0 +1,43 @@
TOOLCHAIN_PREFIX ?=
CXX := $(TOOLCHAIN_PREFIX)g++
AR := $(TOOLCHAIN_PREFIX)ar
TARGET := prog
STATIC_LIB := libjtag_wb_bridge.a
SHARED_LIB := libjtag_wb_bridge.so
ADEPT_LIBDIR := /opt/packages/digilent.adept.runtime_2.27.9-x86_64/lib64
CPPFLAGS := -MMD -MP
CXXFLAGS := -O2 -Wall -Wextra -std=c++17 -fPIC
LDFLAGS := -L$(ADEPT_LIBDIR) -Wl,--disable-new-dtags -Wl,-rpath,$(ADEPT_LIBDIR)
LIBS := -ldjtg -ldmgr -ldpcomm -ldabs -ldftd2xx
LIB_SRCS := difilent_jtag.cpp jtag_wb_bridge_client.cpp jtag_wb_bridge_c.cpp
APP_SRCS := prog.cpp argparse.cpp
LIB_OBJS := $(LIB_SRCS:.cpp=.o)
APP_OBJS := $(APP_SRCS:.cpp=.o)
DEPS := $(LIB_OBJS:.o=.d) $(APP_OBJS:.o=.d)
.PHONY: all clean
all: $(STATIC_LIB) $(SHARED_LIB) $(TARGET)
$(STATIC_LIB): $(LIB_OBJS)
$(AR) rcs $@ $(LIB_OBJS)
$(SHARED_LIB): $(LIB_OBJS)
$(CXX) -shared $(LDFLAGS) -o $@ $(LIB_OBJS) $(LIBS)
$(TARGET): $(APP_OBJS) $(STATIC_LIB)
$(CXX) $(LDFLAGS) -o $@ $(APP_OBJS) -L. $(STATIC_LIB) $(LIBS)
%.o: %.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<
clean:
rm -f $(TARGET) $(STATIC_LIB) $(SHARED_LIB) $(LIB_OBJS) $(APP_OBJS) $(DEPS)
-include $(DEPS)

View File

@@ -0,0 +1,285 @@
#include "argparse.hpp"
#include <cerrno>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <sstream>
ArgParser::ArgParser(std::string program_name)
: program_name_(std::move(program_name)) {}
void ArgParser::addString(const std::string &name,
const std::string &default_value,
const std::string &help,
bool required,
const std::string &short_name) {
order_.push_back(name);
meta_[name] = {OptionType::kString, help, required, short_name};
string_values_[name] = default_value;
provided_[name] = false;
if (!short_name.empty()) {
short_to_long_[short_name] = name;
}
}
void ArgParser::addInt(const std::string &name,
int default_value,
const std::string &help,
bool required,
const std::string &short_name) {
order_.push_back(name);
meta_[name] = {OptionType::kInt, help, required, short_name};
int_values_[name] = default_value;
provided_[name] = false;
if (!short_name.empty()) {
short_to_long_[short_name] = name;
}
}
void ArgParser::addFlag(const std::string &name,
const std::string &help,
const std::string &short_name) {
order_.push_back(name);
meta_[name] = {OptionType::kFlag, help, false, short_name};
flag_values_[name] = false;
provided_[name] = false;
if (!short_name.empty()) {
short_to_long_[short_name] = name;
}
}
bool ArgParser::parse(int argc, char **argv, std::string *error) {
for (int i = 1; i < argc; ++i) {
std::string token(argv[i]);
if (token == "--help" || token == "-h") {
if (error) {
*error = "help";
}
return false;
}
if (token.rfind("--", 0) != 0 && token.rfind("-", 0) == 0) {
std::string short_key = token.substr(1);
std::string short_value;
size_t short_eq = short_key.find('=');
if (short_eq != std::string::npos) {
short_value = short_key.substr(short_eq + 1);
short_key = short_key.substr(0, short_eq);
}
auto sk = short_to_long_.find(short_key);
if (sk == short_to_long_.end()) {
if (error) {
*error = "Unknown option: -" + short_key;
}
return false;
}
auto m = meta_.find(sk->second);
if (m == meta_.end()) {
if (error) {
*error = "Unknown option: -" + short_key;
}
return false;
}
if (m->second.type == OptionType::kFlag) {
if (short_eq != std::string::npos) {
if (error) {
*error = "Flag does not take a value: -" + short_key;
}
return false;
}
flag_values_[sk->second] = true;
provided_[sk->second] = true;
} else if (m->second.type == OptionType::kString) {
if (short_eq == std::string::npos) {
if (i + 1 >= argc) {
if (error) {
*error = "Missing value for -" + short_key;
}
return false;
}
short_value = argv[++i];
}
string_values_[sk->second] = short_value;
provided_[sk->second] = true;
} else if (m->second.type == OptionType::kInt) {
long parsed;
if (short_eq == std::string::npos) {
if (i + 1 >= argc) {
if (error) {
*error = "Missing value for -" + short_key;
}
return false;
}
short_value = argv[++i];
}
errno = 0;
char *endp = nullptr;
parsed = std::strtol(short_value.c_str(), &endp, 0);
if (errno != 0 || endp == short_value.c_str() || *endp != '\0' ||
parsed < INT_MIN || parsed > INT_MAX) {
if (error) {
*error = "Invalid integer for -" + short_key + ": " + short_value;
}
return false;
}
int_values_[sk->second] = static_cast<int>(parsed);
provided_[sk->second] = true;
}
continue;
}
if (token.rfind("--", 0) != 0) {
if (error) {
*error = "Unexpected positional argument: " + token;
}
return false;
}
std::string key;
std::string value;
size_t eq = token.find('=');
if (eq == std::string::npos) {
key = token.substr(2);
} else {
key = token.substr(2, eq - 2);
value = token.substr(eq + 1);
}
auto m = meta_.find(key);
if (m == meta_.end()) {
if (error) {
*error = "Unknown option: --" + key;
}
return false;
}
if (m->second.type == OptionType::kFlag) {
if (eq != std::string::npos) {
if (error) {
*error = "Flag does not take a value: --" + key;
}
return false;
}
flag_values_[key] = true;
provided_[key] = true;
continue;
}
if (eq == std::string::npos) {
if (i + 1 >= argc) {
if (error) {
*error = "Missing value for --" + key;
}
return false;
}
value = argv[++i];
}
if (m->second.type == OptionType::kString) {
string_values_[key] = value;
provided_[key] = true;
} else if (m->second.type == OptionType::kInt) {
errno = 0;
char *endp = nullptr;
long parsed = std::strtol(value.c_str(), &endp, 0);
if (errno != 0 || endp == value.c_str() || *endp != '\0' ||
parsed < INT_MIN || parsed > INT_MAX) {
if (error) {
*error = "Invalid integer for --" + key + ": " + value;
}
return false;
}
int_values_[key] = static_cast<int>(parsed);
provided_[key] = true;
}
}
for (const auto &key : order_) {
auto m = meta_.find(key);
if (m != meta_.end() && m->second.required && !has(key)) {
if (error) {
*error = "Missing required option: --" + key;
}
return false;
}
}
return true;
}
bool ArgParser::has(const std::string &name) const {
auto p = provided_.find(name);
return p != provided_.end() && p->second;
}
std::string ArgParser::getString(const std::string &name) const {
auto it = string_values_.find(name);
if (it == string_values_.end()) {
return std::string();
}
return it->second;
}
int ArgParser::getInt(const std::string &name) const {
auto it = int_values_.find(name);
if (it == int_values_.end()) {
return 0;
}
return it->second;
}
bool ArgParser::getFlag(const std::string &name) const {
auto it = flag_values_.find(name);
if (it == flag_values_.end()) {
return false;
}
return it->second;
}
std::string ArgParser::helpText() const {
std::ostringstream oss;
oss << "Usage: " << program_name_ << " [options]\n\n";
oss << "Options:\n";
oss << " -h, --help Show this help\n";
for (const auto &key : order_) {
auto m = meta_.find(key);
if (m == meta_.end()) {
continue;
}
oss << " ";
if (!m->second.short_name.empty()) {
oss << "-" << m->second.short_name << ", ";
} else {
oss << " ";
}
oss << "--" << key;
if (m->second.type != OptionType::kFlag) {
oss << " <value>";
}
if (m->second.required) {
oss << " (required)";
}
oss << "\n";
oss << " " << m->second.help;
if (m->second.type == OptionType::kString) {
auto s = string_values_.find(key);
if (s != string_values_.end()) {
oss << " [default: '" << s->second << "']";
}
} else {
if (m->second.type == OptionType::kInt) {
auto iv = int_values_.find(key);
if (iv != int_values_.end()) {
oss << " [default: " << iv->second << "]";
}
}
}
oss << "\n";
}
return oss.str();
}

View File

@@ -0,0 +1,74 @@
#pragma once
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
class ArgParser {
public:
struct StringOption {
std::string name;
std::string default_value;
std::string help;
bool required;
};
struct IntOption {
std::string name;
int default_value;
std::string help;
bool required;
};
explicit ArgParser(std::string program_name);
void addString(const std::string &name,
const std::string &default_value,
const std::string &help,
bool required = false,
const std::string &short_name = "");
void addInt(const std::string &name,
int default_value,
const std::string &help,
bool required = false,
const std::string &short_name = "");
void addFlag(const std::string &name,
const std::string &help,
const std::string &short_name = "");
bool parse(int argc, char **argv, std::string *error);
bool has(const std::string &name) const;
std::string getString(const std::string &name) const;
int getInt(const std::string &name) const;
bool getFlag(const std::string &name) const;
std::string helpText() const;
private:
enum class OptionType {
kString,
kInt,
kFlag
};
struct OptionMeta {
OptionType type;
std::string help;
bool required;
std::string short_name;
};
std::string program_name_;
std::vector<std::string> order_;
std::unordered_map<std::string, OptionMeta> meta_;
std::unordered_map<std::string, std::string> string_values_;
std::unordered_map<std::string, int> int_values_;
std::unordered_map<std::string, bool> flag_values_;
std::unordered_map<std::string, bool> provided_;
std::unordered_map<std::string, std::string> short_to_long_;
};

View File

@@ -0,0 +1,276 @@
#include "digilent_jtag.hpp"
#include <algorithm>
#include <cstring>
#include <digilent/adept/dmgr.h>
#include <digilent/adept/djtg.h>
namespace {
constexpr int kDefaultIrBits = 6;
std::string ercToString(ERC erc) {
char code[cchErcMax] = {0};
char msg[cchErcMsgMax] = {0};
if (DmgrSzFromErc(erc, code, msg)) {
return std::string(code) + ": " + msg;
}
return "ERC=" + std::to_string(erc);
}
inline uint8_t getBit(const uint8_t* packed_bits, int bit_idx) {
return static_cast<uint8_t>((packed_bits[bit_idx / 8] >> (bit_idx % 8)) & 0x1u);
}
inline void setBit(uint8_t* packed_bits, int bit_idx, uint8_t bit) {
const uint8_t mask = static_cast<uint8_t>(1u << (bit_idx % 8));
if (bit & 0x1u) {
packed_bits[bit_idx / 8] |= mask;
} else {
packed_bits[bit_idx / 8] &= static_cast<uint8_t>(~mask);
}
}
} // namespace
DigilentJtag::DigilentJtag() : hif_(hifInvalid), enabled_port_(-1), last_error_() {}
DigilentJtag::~DigilentJtag() { close(); }
bool DigilentJtag::open(int port) {
close();
int count = 0;
if (!DmgrEnumDevices(&count)) {
return setErrorFromDmgr("DmgrEnumDevices");
}
if (count <= 0) {
return setError("open: no Digilent devices found");
}
DVC dvc{};
if (!DmgrGetDvc(0, &dvc)) {
return setErrorFromDmgr("DmgrGetDvc");
}
return open(std::string(dvc.szConn), port);
}
bool DigilentJtag::open(const std::string& selector, int port) {
close();
if (selector.empty()) {
return setError("open: selector is empty");
}
std::vector<char> sel(selector.begin(), selector.end());
sel.push_back('\0');
if (!DmgrOpen(&hif_, sel.data())) {
hif_ = hifInvalid;
return setErrorFromDmgr("DmgrOpen");
}
if (!DjtgEnableEx(hif_, static_cast<INT32>(port))) {
if (!DjtgEnable(hif_)) {
DmgrClose(hif_);
hif_ = hifInvalid;
return setErrorFromDmgr("DjtgEnableEx/DjtgEnable");
}
enabled_port_ = 0;
} else {
enabled_port_ = port;
}
last_error_.clear();
return true;
}
void DigilentJtag::close() {
if (hif_ != hifInvalid) {
(void)DjtgDisable(hif_);
(void)DmgrClose(hif_);
}
hif_ = hifInvalid;
enabled_port_ = -1;
}
bool DigilentJtag::isOpen() const { return hif_ != hifInvalid; }
HIF DigilentJtag::handle() const { return hif_; }
bool DigilentJtag::setSpeed(uint32_t requested_hz, uint32_t* actual_hz) {
if (!isOpen()) {
return setError("setSpeed: device not open");
}
DWORD actual = 0;
if (!DjtgSetSpeed(hif_, static_cast<DWORD>(requested_hz), &actual)) {
return setErrorFromDmgr("DjtgSetSpeed");
}
if (actual_hz) {
*actual_hz = static_cast<uint32_t>(actual);
}
last_error_.clear();
return true;
}
bool DigilentJtag::setChain(int chain, int ir_bits) {
uint32_t opcode = 0;
if (chain == 1) {
opcode = 0x02; // USER1 on Spartan-6
} else if (chain == 2) {
opcode = 0x03; // USER2 on Spartan-6
} else {
return setError("setChain: unsupported chain index (expected 1 or 2)");
}
return setInstruction(opcode, ir_bits);
}
bool DigilentJtag::setInstruction(uint32_t instruction, int ir_bits) {
if (!isOpen()) {
return setError("setInstruction: device not open");
}
if (ir_bits <= 0 || ir_bits > 64) {
return setError("setInstruction: ir_bits out of range");
}
// Force Test-Logic-Reset, then RTI.
uint8_t tlr = 0x3f; // 6 ones
if (!putTmsBits(&tlr, 6)) return false;
uint8_t rti = 0x00; // one zero
if (!putTmsBits(&rti, 1)) return false;
// RTI -> Shift-IR : 1,1,0,0 (LSB-first 0b0011)
uint8_t to_shift_ir = 0x03;
if (!putTmsBits(&to_shift_ir, 4)) return false;
std::vector<uint8_t> tx(static_cast<size_t>((ir_bits + 7) / 8), 0);
for (int i = 0; i < ir_bits && i < 64; ++i) {
if ((instruction >> i) & 0x1u) {
tx[static_cast<size_t>(i / 8)] |= static_cast<uint8_t>(1u << (i % 8));
}
}
std::vector<uint8_t> rx(tx.size(), 0);
if (ir_bits > 1) {
if (!putTdiBits(false, tx.data(), rx.data(), ir_bits - 1)) return false;
}
const uint8_t last_tx = getBit(tx.data(), ir_bits - 1);
uint8_t last_rx = 0;
if (!putTdiBits(true, &last_tx, &last_rx, 1)) return false;
// Exit1-IR -> Update-IR -> Idle: 1,0
uint8_t to_idle = 0x01;
if (!putTmsBits(&to_idle, 2)) return false;
last_error_.clear();
return true;
}
bool DigilentJtag::shiftData(const uint8_t* tx_bits, uint8_t* rx_bits, int bit_count) {
if (!isOpen()) {
return setError("shiftData: device not open");
}
if (bit_count <= 0) {
return setError("shiftData: bit_count must be > 0");
}
const size_t nbytes = static_cast<size_t>((bit_count + 7) / 8);
std::vector<uint8_t> tx_zeros;
if (!tx_bits) {
tx_zeros.assign(nbytes, 0);
tx_bits = tx_zeros.data();
}
std::vector<uint8_t> rx_tmp;
if (!rx_bits) {
rx_tmp.assign(nbytes, 0);
rx_bits = rx_tmp.data();
} else {
std::memset(rx_bits, 0, nbytes);
}
if (!enterShiftDR()) return false;
if (bit_count > 1) {
if (!putTdiBits(false, tx_bits, rx_bits, bit_count - 1)) return false;
}
const uint8_t tx_last = getBit(tx_bits, bit_count - 1);
uint8_t rx_last = 0;
if (!putTdiBits(true, &tx_last, &rx_last, 1)) return false;
setBit(rx_bits, bit_count - 1, rx_last & 0x1u);
if (!leaveShiftToIdle()) return false;
last_error_.clear();
return true;
}
bool DigilentJtag::shiftData(const std::vector<uint8_t>& tx_bits, std::vector<uint8_t>* rx_bits, int bit_count) {
if (bit_count <= 0) {
return setError("shiftData(vector): bit_count must be > 0");
}
const size_t nbytes = static_cast<size_t>((bit_count + 7) / 8);
if (tx_bits.size() < nbytes) {
return setError("shiftData(vector): tx_bits is smaller than required bit_count");
}
std::vector<uint8_t> local_rx;
local_rx.assign(nbytes, 0);
if (!shiftData(tx_bits.data(), local_rx.data(), bit_count)) {
return false;
}
if (rx_bits) {
*rx_bits = std::move(local_rx);
}
return true;
}
const std::string& DigilentJtag::lastError() const { return last_error_; }
bool DigilentJtag::enterShiftDR() {
// Idle -> Select-DR -> Capture-DR -> Shift-DR : 1,0,0
uint8_t to_shift_dr = 0x01;
return putTmsBits(&to_shift_dr, 3);
}
bool DigilentJtag::leaveShiftToIdle() {
// Exit1-DR -> Update-DR -> Idle : 1,0
uint8_t to_idle = 0x01;
return putTmsBits(&to_idle, 2);
}
bool DigilentJtag::putTmsBits(const uint8_t* tms_bits, int bit_count) {
if (!DjtgPutTmsBits(hif_, fFalse, const_cast<uint8_t*>(tms_bits), nullptr, static_cast<DWORD>(bit_count), fFalse)) {
return setErrorFromDmgr("DjtgPutTmsBits");
}
return true;
}
bool DigilentJtag::putTdiBits(bool tms, const uint8_t* tx_bits, uint8_t* rx_bits, int bit_count) {
if (!DjtgPutTdiBits(
hif_,
tms ? fTrue : fFalse,
const_cast<uint8_t*>(tx_bits),
rx_bits,
static_cast<DWORD>(bit_count),
fFalse)) {
return setErrorFromDmgr("DjtgPutTdiBits");
}
return true;
}
bool DigilentJtag::setError(const std::string& msg) {
last_error_ = msg;
return false;
}
bool DigilentJtag::setErrorFromDmgr(const std::string& where) {
last_error_ = where + " failed: " + ercToString(DmgrGetLastError());
return false;
}

View File

@@ -0,0 +1,52 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include <digilent/adept/dpcdecl.h>
class DigilentJtag {
public:
DigilentJtag();
~DigilentJtag();
DigilentJtag(const DigilentJtag&) = delete;
DigilentJtag& operator=(const DigilentJtag&) = delete;
bool open(int port = 0);
bool open(const std::string& selector, int port = 0);
void close();
bool isOpen() const;
HIF handle() const;
bool setSpeed(uint32_t requested_hz, uint32_t* actual_hz = nullptr);
// For Spartan-6 style USER chains:
// chain=1 -> USER1 opcode 0x02, chain=2 -> USER2 opcode 0x03
bool setChain(int chain, int ir_bits = 6);
bool setInstruction(uint32_t instruction, int ir_bits);
// Shifts one DR transaction from Idle -> ShiftDR -> UpdateDR -> Idle.
// tx_bits is LSB-first in packed byte form.
// rx_bits, when non-null, receives captured TDO bits in the same packing.
bool shiftData(const uint8_t* tx_bits, uint8_t* rx_bits, int bit_count);
bool shiftData(const std::vector<uint8_t>& tx_bits, std::vector<uint8_t>* rx_bits, int bit_count);
const std::string& lastError() const;
private:
bool enterShiftDR();
bool leaveShiftToIdle();
bool putTmsBits(const uint8_t* tms_bits, int bit_count);
bool putTdiBits(bool tms, const uint8_t* tx_bits, uint8_t* rx_bits, int bit_count);
bool setError(const std::string& msg);
bool setErrorFromDmgr(const std::string& where);
HIF hif_;
int enabled_port_;
std::string last_error_;
};

View File

@@ -0,0 +1,146 @@
import ctypes
import os
from pathlib import Path
def _load_library(path=None):
if path is None:
env_path = os.environ.get("JTAG_BRIDGE_LIB")
if env_path:
path = env_path
else:
path = Path(__file__).with_name("libjtag_wb_bridge.so")
return ctypes.CDLL(str(path))
class JtagBridgeError(RuntimeError):
pass
class JtagBridge:
def __init__(self, library_path=None):
self._handle = None
self._lib = _load_library(library_path)
self._configure()
self._handle = self._lib.jtag_bridge_create()
if not self._handle:
raise JtagBridgeError("failed to allocate bridge handle")
def _configure(self):
self._lib.jtag_bridge_create.restype = ctypes.c_void_p
self._lib.jtag_bridge_destroy.argtypes = [ctypes.c_void_p]
self._lib.jtag_bridge_open.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int]
self._lib.jtag_bridge_open.restype = ctypes.c_int
self._lib.jtag_bridge_open_selector.argtypes = [
ctypes.c_void_p,
ctypes.c_char_p,
ctypes.c_int,
ctypes.c_int,
]
self._lib.jtag_bridge_open_selector.restype = ctypes.c_int
self._lib.jtag_bridge_close.argtypes = [ctypes.c_void_p]
self._lib.jtag_bridge_set_speed.argtypes = [
ctypes.c_void_p,
ctypes.c_uint32,
ctypes.POINTER(ctypes.c_uint32),
]
self._lib.jtag_bridge_set_speed.restype = ctypes.c_int
self._lib.jtag_bridge_set_chain.argtypes = [ctypes.c_void_p, ctypes.c_int]
self._lib.jtag_bridge_set_chain.restype = ctypes.c_int
self._lib.jtag_bridge_clear_flags.argtypes = [ctypes.c_void_p]
self._lib.jtag_bridge_clear_flags.restype = ctypes.c_int
self._lib.jtag_bridge_ping.argtypes = [ctypes.c_void_p]
self._lib.jtag_bridge_ping.restype = ctypes.c_int
self._lib.jtag_bridge_set_reset.argtypes = [ctypes.c_void_p, ctypes.c_int]
self._lib.jtag_bridge_set_reset.restype = ctypes.c_int
self._lib.jtag_bridge_write8.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint8]
self._lib.jtag_bridge_write8.restype = ctypes.c_int
self._lib.jtag_bridge_read8.argtypes = [
ctypes.c_void_p,
ctypes.c_uint32,
ctypes.POINTER(ctypes.c_uint8),
]
self._lib.jtag_bridge_read8.restype = ctypes.c_int
self._lib.jtag_bridge_write32.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint32]
self._lib.jtag_bridge_write32.restype = ctypes.c_int
self._lib.jtag_bridge_read32.argtypes = [
ctypes.c_void_p,
ctypes.c_uint32,
ctypes.POINTER(ctypes.c_uint32),
]
self._lib.jtag_bridge_read32.restype = ctypes.c_int
self._lib.jtag_bridge_last_error.argtypes = [ctypes.c_void_p]
self._lib.jtag_bridge_last_error.restype = ctypes.c_char_p
def close(self):
if self._handle:
self._lib.jtag_bridge_close(self._handle)
def destroy(self):
if self._handle:
self._lib.jtag_bridge_destroy(self._handle)
self._handle = None
def __del__(self):
self.destroy()
def __enter__(self):
return self
def __exit__(self, exc_type, exc, tb):
self.destroy()
return False
def _check(self, ok):
if not ok:
message = self._lib.jtag_bridge_last_error(self._handle)
if not message:
raise JtagBridgeError("operation failed")
decoded = message.decode("utf-8", errors="replace")
if not decoded:
decoded = "operation failed"
raise JtagBridgeError(decoded)
def open(self, port=0, chain=1):
self._check(self._lib.jtag_bridge_open(self._handle, port, chain))
def open_selector(self, selector, port=0, chain=1):
self._check(
self._lib.jtag_bridge_open_selector(
self._handle, selector.encode("utf-8"), port, chain
)
)
def set_speed(self, requested_hz):
actual = ctypes.c_uint32()
self._check(self._lib.jtag_bridge_set_speed(self._handle, requested_hz, ctypes.byref(actual)))
return actual.value
def set_chain(self, chain):
self._check(self._lib.jtag_bridge_set_chain(self._handle, chain))
def clear_flags(self):
self._check(self._lib.jtag_bridge_clear_flags(self._handle))
def ping(self):
self._check(self._lib.jtag_bridge_ping(self._handle))
return True
def set_reset(self, enabled):
self._check(self._lib.jtag_bridge_set_reset(self._handle, int(bool(enabled))))
def write8(self, addr, value):
self._check(self._lib.jtag_bridge_write8(self._handle, addr, value))
def read8(self, addr):
value = ctypes.c_uint8()
self._check(self._lib.jtag_bridge_read8(self._handle, addr, ctypes.byref(value)))
return value.value
def write32(self, addr, value):
self._check(self._lib.jtag_bridge_write32(self._handle, addr, value))
def read32(self, addr):
value = ctypes.c_uint32()
self._check(self._lib.jtag_bridge_read32(self._handle, addr, ctypes.byref(value)))
return value.value

View File

@@ -0,0 +1,113 @@
#include "jtag_wb_bridge_c.h"
#include "jtag_wb_bridge_client.hpp"
#include <new>
struct JtagBridgeHandle {
JtagWishboneBridge bridge;
};
namespace {
template <typename Fn>
int callBridge(JtagBridgeHandle* handle, Fn&& fn) {
if (!handle) {
return 0;
}
return fn(handle->bridge) ? 1 : 0;
}
} // namespace
extern "C" {
JtagBridgeHandle* jtag_bridge_create(void) {
return new (std::nothrow) JtagBridgeHandle();
}
void jtag_bridge_destroy(JtagBridgeHandle* handle) {
delete handle;
}
int jtag_bridge_open(JtagBridgeHandle* handle, int port, int chain) {
return callBridge(handle, [port, chain](JtagWishboneBridge& bridge) {
return bridge.open(port, chain);
});
}
int jtag_bridge_open_selector(JtagBridgeHandle* handle, const char* selector, int port, int chain) {
if (!handle || !selector) {
return 0;
}
return handle->bridge.open(selector, port, chain) ? 1 : 0;
}
void jtag_bridge_close(JtagBridgeHandle* handle) {
if (handle) {
handle->bridge.close();
}
}
int jtag_bridge_set_speed(JtagBridgeHandle* handle, uint32_t requested_hz, uint32_t* actual_hz) {
return callBridge(handle, [requested_hz, actual_hz](JtagWishboneBridge& bridge) {
return bridge.setSpeed(requested_hz, actual_hz);
});
}
int jtag_bridge_set_chain(JtagBridgeHandle* handle, int chain) {
return callBridge(handle, [chain](JtagWishboneBridge& bridge) {
return bridge.setChain(chain);
});
}
int jtag_bridge_clear_flags(JtagBridgeHandle* handle) {
return callBridge(handle, [](JtagWishboneBridge& bridge) {
return bridge.clearFlags();
});
}
int jtag_bridge_ping(JtagBridgeHandle* handle) {
return callBridge(handle, [](JtagWishboneBridge& bridge) {
return bridge.ping();
});
}
int jtag_bridge_set_reset(JtagBridgeHandle* handle, int enabled) {
return callBridge(handle, [enabled](JtagWishboneBridge& bridge) {
return bridge.setReset(enabled != 0);
});
}
int jtag_bridge_write8(JtagBridgeHandle* handle, uint32_t addr, uint8_t value) {
return callBridge(handle, [addr, value](JtagWishboneBridge& bridge) {
return bridge.write8(addr, value);
});
}
int jtag_bridge_read8(JtagBridgeHandle* handle, uint32_t addr, uint8_t* value) {
return callBridge(handle, [addr, value](JtagWishboneBridge& bridge) {
return bridge.read8(addr, value);
});
}
int jtag_bridge_write32(JtagBridgeHandle* handle, uint32_t addr, uint32_t value) {
return callBridge(handle, [addr, value](JtagWishboneBridge& bridge) {
return bridge.write32(addr, value);
});
}
int jtag_bridge_read32(JtagBridgeHandle* handle, uint32_t addr, uint32_t* value) {
return callBridge(handle, [addr, value](JtagWishboneBridge& bridge) {
return bridge.read32(addr, value);
});
}
const char* jtag_bridge_last_error(const JtagBridgeHandle* handle) {
if (!handle) {
return "invalid bridge handle";
}
return handle->bridge.lastError().c_str();
}
} // extern "C"

View File

@@ -0,0 +1,34 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct JtagBridgeHandle JtagBridgeHandle;
JtagBridgeHandle* jtag_bridge_create(void);
void jtag_bridge_destroy(JtagBridgeHandle* handle);
int jtag_bridge_open(JtagBridgeHandle* handle, int port, int chain);
int jtag_bridge_open_selector(JtagBridgeHandle* handle, const char* selector, int port, int chain);
void jtag_bridge_close(JtagBridgeHandle* handle);
int jtag_bridge_set_speed(JtagBridgeHandle* handle, uint32_t requested_hz, uint32_t* actual_hz);
int jtag_bridge_set_chain(JtagBridgeHandle* handle, int chain);
int jtag_bridge_clear_flags(JtagBridgeHandle* handle);
int jtag_bridge_ping(JtagBridgeHandle* handle);
int jtag_bridge_set_reset(JtagBridgeHandle* handle, int enabled);
int jtag_bridge_write8(JtagBridgeHandle* handle, uint32_t addr, uint8_t value);
int jtag_bridge_read8(JtagBridgeHandle* handle, uint32_t addr, uint8_t* value);
int jtag_bridge_write32(JtagBridgeHandle* handle, uint32_t addr, uint32_t value);
int jtag_bridge_read32(JtagBridgeHandle* handle, uint32_t addr, uint32_t* value);
const char* jtag_bridge_last_error(const JtagBridgeHandle* handle);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,186 @@
#include "jtag_wb_bridge_client.hpp"
#include <cstdio>
namespace {
constexpr uint8_t kOpNop = 0x00;
constexpr uint8_t kOpResetOn = 0x10;
constexpr uint8_t kOpResetOff = 0x11;
constexpr uint8_t kOpWrite8 = 0x20;
constexpr uint8_t kOpRead8 = 0x21;
constexpr uint8_t kOpWrite32 = 0x22;
constexpr uint8_t kOpRead32 = 0x23;
constexpr uint8_t kOpPing = 0x30;
constexpr uint8_t kOpClearFlags = 0x40;
constexpr int kPacketBytes = 9;
constexpr int kPacketBits = 72;
constexpr int kMaxPollAttempts = 32;
void makeCommand(uint8_t out[kPacketBytes], uint8_t opcode, uint32_t addr, uint32_t data) {
out[0] = static_cast<uint8_t>(data);
out[1] = static_cast<uint8_t>(data >> 8);
out[2] = static_cast<uint8_t>(data >> 16);
out[3] = static_cast<uint8_t>(data >> 24);
out[4] = static_cast<uint8_t>(addr);
out[5] = static_cast<uint8_t>(addr >> 8);
out[6] = static_cast<uint8_t>(addr >> 16);
out[7] = static_cast<uint8_t>(addr >> 24);
out[8] = opcode;
}
uint32_t getResponseData32(const uint8_t rx[kPacketBytes]) {
return static_cast<uint32_t>(rx[2]) |
(static_cast<uint32_t>(rx[3]) << 8) |
(static_cast<uint32_t>(rx[4]) << 16) |
(static_cast<uint32_t>(rx[5]) << 24);
}
uint8_t getLastOpcode(const uint8_t rx[kPacketBytes]) {
return rx[0];
}
} // namespace
bool JtagWishboneBridge::open(int port, int chain) {
if (!jtag_.open(port)) {
return setError(jtag_.lastError());
}
if (!jtag_.setChain(chain)) {
const std::string msg = jtag_.lastError();
jtag_.close();
return setError(msg);
}
last_error_.clear();
return true;
}
bool JtagWishboneBridge::open(const std::string& selector, int port, int chain) {
if (!jtag_.open(selector, port)) {
return setError(jtag_.lastError());
}
if (!jtag_.setChain(chain)) {
const std::string msg = jtag_.lastError();
jtag_.close();
return setError(msg);
}
last_error_.clear();
return true;
}
void JtagWishboneBridge::close() {
jtag_.close();
last_error_.clear();
}
bool JtagWishboneBridge::isOpen() const {
return jtag_.isOpen();
}
bool JtagWishboneBridge::setSpeed(uint32_t requested_hz, uint32_t* actual_hz) {
if (!jtag_.setSpeed(requested_hz, actual_hz)) {
return setError(jtag_.lastError());
}
last_error_.clear();
return true;
}
bool JtagWishboneBridge::setChain(int chain) {
if (!jtag_.setChain(chain)) {
return setError(jtag_.lastError());
}
last_error_.clear();
return true;
}
bool JtagWishboneBridge::clearFlags() {
return executeCommand(kOpClearFlags, 0, 0, nullptr);
}
bool JtagWishboneBridge::ping() {
uint32_t response = 0;
uint8_t ping_value;
if (!executeCommand(kOpPing, 0, 0, &response)) {
return false;
}
ping_value = static_cast<uint8_t>(response & 0xffu);
if (ping_value != 0xa5) {
char msg[96];
std::snprintf(msg, sizeof(msg), "ping mismatch: expected 0xa5, got 0x%02x", ping_value);
return setError(msg);
}
last_error_.clear();
return true;
}
bool JtagWishboneBridge::setReset(bool enabled) {
return executeCommand(enabled ? kOpResetOn : kOpResetOff, 0, 0, nullptr);
}
bool JtagWishboneBridge::write8(uint32_t addr, uint8_t value) {
return executeCommand(kOpWrite8, addr, value, nullptr);
}
bool JtagWishboneBridge::read8(uint32_t addr, uint8_t* value) {
uint32_t response = 0;
if (!value) {
return setError("read8: value pointer is null");
}
if (!executeCommand(kOpRead8, addr, 0, &response)) {
return false;
}
*value = static_cast<uint8_t>(response & 0xffu);
return true;
}
bool JtagWishboneBridge::write32(uint32_t addr, uint32_t value) {
return executeCommand(kOpWrite32, addr, value, nullptr);
}
bool JtagWishboneBridge::read32(uint32_t addr, uint32_t* value) {
if (!value) {
return setError("read32: value pointer is null");
}
return executeCommand(kOpRead32, addr, 0, value);
}
const std::string& JtagWishboneBridge::lastError() const {
return last_error_;
}
bool JtagWishboneBridge::executeCommand(uint8_t opcode, uint32_t addr, uint32_t data, uint32_t* response_data) {
if (!jtag_.isOpen()) {
return setError("executeCommand: device not open");
}
uint8_t tx[kPacketBytes] = {0};
uint8_t rx[kPacketBytes] = {0};
makeCommand(tx, opcode, addr, data);
if (!jtag_.shiftData(tx, rx, kPacketBits)) {
return setError(jtag_.lastError());
}
for (int i = 0; i < kMaxPollAttempts; ++i) {
makeCommand(tx, kOpNop, 0, 0);
if (!jtag_.shiftData(tx, rx, kPacketBits)) {
return setError(jtag_.lastError());
}
if (getLastOpcode(rx) == opcode) {
if (response_data) {
*response_data = getResponseData32(rx);
}
last_error_.clear();
return true;
}
}
char msg[96];
std::snprintf(msg, sizeof(msg), "command 0x%02x timed out after %d polls", opcode, kMaxPollAttempts);
return setError(msg);
}
bool JtagWishboneBridge::setError(const std::string& msg) {
last_error_ = msg;
return false;
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include "digilent_jtag.hpp"
#include <cstdint>
#include <string>
class JtagWishboneBridge {
public:
JtagWishboneBridge() = default;
~JtagWishboneBridge() = default;
JtagWishboneBridge(const JtagWishboneBridge&) = delete;
JtagWishboneBridge& operator=(const JtagWishboneBridge&) = delete;
bool open(int port = 0, int chain = 1);
bool open(const std::string& selector, int port = 0, int chain = 1);
void close();
bool isOpen() const;
bool setSpeed(uint32_t requested_hz, uint32_t* actual_hz = nullptr);
bool setChain(int chain);
bool clearFlags();
bool ping();
bool setReset(bool enabled);
bool write8(uint32_t addr, uint8_t value);
bool read8(uint32_t addr, uint8_t* value);
bool write32(uint32_t addr, uint32_t value);
bool read32(uint32_t addr, uint32_t* value);
const std::string& lastError() const;
private:
bool executeCommand(uint8_t opcode, uint32_t addr, uint32_t data, uint32_t* response_data);
bool setError(const std::string& msg);
DigilentJtag jtag_;
std::string last_error_;
};

View File

@@ -0,0 +1,99 @@
#include "argparse.hpp"
#include "jtag_wb_bridge_client.hpp"
#include <cstdio>
#include <string>
int main(int argc, char** argv) {
ArgParser parser(argc > 0 ? argv[0] : "test");
parser.addString("file", "", "File to write", true, "f");
parser.addFlag("verify", "Verify", "v");
std::string parse_error;
if (!parser.parse(argc, argv, &parse_error)) {
if (parse_error == "help") {
std::printf("%s", parser.helpText().c_str());
return 0;
}
std::printf("Argument error: %s\n\n", parse_error.c_str());
std::printf("%s", parser.helpText().c_str());
return -1;
}
JtagWishboneBridge bridge;
if (!bridge.open()) {
std::printf("Could not open programmer: %s\n", bridge.lastError().c_str());
return -1;
}
if (!bridge.clearFlags()) {
std::printf("Could not clear flags: %s\n", bridge.lastError().c_str());
return -1;
}
if (!bridge.ping()) {
std::printf("PING command failed: %s\n", bridge.lastError().c_str());
return -1;
}
const std::string file = parser.getString("file");
FILE* f = std::fopen(file.c_str(), "rb");
if (!f) {
std::printf("Could not open file\n");
return -1;
}
if (!bridge.setReset(true)) {
std::printf("Could not assert reset: %s\n", bridge.lastError().c_str());
std::fclose(f);
return -1;
}
int nr = 0;
uint32_t addr = 0;
do {
uint32_t buf[32];
nr = static_cast<int>(std::fread(buf, sizeof(uint32_t), 32, f));
for (int i = 0; i < nr; ++i) {
if (!bridge.write32(addr + static_cast<uint32_t>(i * 4), buf[i])) {
std::printf("Write failed at %04x: %s\n",
addr + static_cast<uint32_t>(i * 4),
bridge.lastError().c_str());
std::fclose(f);
return -1;
}
std::printf(".");
}
std::printf("\n");
if (parser.getFlag("verify")) {
for (int i = 0; i < nr; ++i) {
uint32_t value = 0;
if (!bridge.read32(addr + static_cast<uint32_t>(i * 4), &value)) {
std::printf("Read failed at %04x: %s\n",
addr + static_cast<uint32_t>(i * 4),
bridge.lastError().c_str());
std::fclose(f);
return -1;
}
if (value != buf[i]) {
std::printf(" -- Verify failed at %04x : %08x != %08x\n",
addr + static_cast<uint32_t>(i * 4),
value,
buf[i]);
}
}
}
addr += static_cast<uint32_t>(nr * 4);
} while (nr > 0);
if (!bridge.setReset(false)) {
std::printf("Could not deassert reset: %s\n", bridge.lastError().c_str());
std::fclose(f);
return -1;
}
std::fclose(f);
return 0;
}

View File

@@ -0,0 +1,8 @@
from libjtag_wb_bridge.jtag_bridge import JtagBridge
with JtagBridge() as bridge:
bridge.open(port=0, chain=1)
bridge.clear_flags()
bridge.ping()
bridge.write8(0x0, 0xAA)

View File

@@ -0,0 +1,138 @@
/**
* Module: arbiter
*
* Description:
* A look ahead, round-robing parameterized arbiter.
*
* <> request
* each bit is controlled by an actor and each actor can 'request' ownership
* of the shared resource by bring high its request bit.
*
* <> grant
* when an actor has been given ownership of shared resource its 'grant' bit
* is driven high
*
* <> select
* binary representation of the grant signal (optional use)
*
* <> active
* is brought high by the arbiter when (any) actor has been given ownership
* of shared resource.
*
*
* Created: Sat Jun 1 20:26:44 EDT 2013
*
* Author: Berin Martini // berin.martini@gmail.com
*/
`ifndef _arbiter_ `define _arbiter_
`include "clog2.vh"
module arbiter
#(parameter
NUM_PORTS = 6,
SEL_WIDTH = ((NUM_PORTS > 1) ? `CLOG2(NUM_PORTS) : 1))
(input wire clk,
input wire rst,
input wire [NUM_PORTS-1:0] request,
output reg [NUM_PORTS-1:0] grant,
output reg [SEL_WIDTH-1:0] select,
output reg active
);
/**
* Local parameters
*/
localparam WRAP_LENGTH = 2*NUM_PORTS;
// Find First 1 - Start from MSB and count downwards, returns 0 when no
// bit set
function [SEL_WIDTH-1:0] ff1 (
input [NUM_PORTS-1:0] in
);
reg set;
integer i;
begin
set = 1'b0;
ff1 = 'b0;
for (i = 0; i < NUM_PORTS; i = i + 1) begin
if (in[i] & ~set) begin
set = 1'b1;
ff1 = i[0 +: SEL_WIDTH];
end
end
end
endfunction
`ifdef VERBOSE
initial $display("Bus arbiter with %d units", NUM_PORTS);
`endif
/**
* Internal signals
*/
integer yy;
wire next;
wire [NUM_PORTS-1:0] order;
reg [NUM_PORTS-1:0] token;
wire [NUM_PORTS-1:0] token_lookahead [NUM_PORTS-1:0];
wire [WRAP_LENGTH-1:0] token_wrap;
/**
* Implementation
*/
assign token_wrap = {token, token};
assign next = ~|(token & request);
always @(posedge clk)
grant <= token & request;
always @(posedge clk)
select <= ff1(token & request);
always @(posedge clk)
active <= |(token & request);
always @(posedge clk)
if (rst) token <= 'b1;
else if (next) begin
for (yy = 0; yy < NUM_PORTS; yy = yy + 1) begin : TOKEN_
if (order[yy]) begin
token <= token_lookahead[yy];
end
end
end
genvar xx;
generate
for (xx = 0; xx < NUM_PORTS; xx = xx + 1) begin : ORDER_
assign token_lookahead[xx] = token_wrap[xx +: NUM_PORTS];
assign order[xx] = |(token_lookahead[xx] & request);
end
endgenerate
endmodule
`endif // `ifndef _arbiter_

View File

@@ -0,0 +1,101 @@
/* wb_arbiter. Part of wb_intercon
*
* ISC License
*
* Copyright (C) 2013-2019 Olof Kindgren <olof.kindgren@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
Wishbone arbiter, burst-compatible
Simple round-robin arbiter for multiple Wishbone masters
*/
`include "clog2.vh"
module wb_arbiter
#(parameter dw = 32,
parameter aw = 32,
parameter num_hosts = 0,
parameter num_masters = num_hosts)
(
input wire wb_clk_i,
input wire wb_rst_i,
// Wishbone Master Interface
input wire [num_masters*aw-1:0] wbm_adr_i,
input wire [num_masters*dw-1:0] wbm_dat_i,
input wire [num_masters*4-1:0] wbm_sel_i,
input wire [num_masters-1:0] wbm_we_i,
input wire [num_masters-1:0] wbm_cyc_i,
input wire [num_masters-1:0] wbm_stb_i,
input wire [num_masters*3-1:0] wbm_cti_i,
input wire [num_masters*2-1:0] wbm_bte_i,
output wire [num_masters*dw-1:0] wbm_dat_o,
output wire [num_masters-1:0] wbm_ack_o,
output wire [num_masters-1:0] wbm_err_o,
output wire [num_masters-1:0] wbm_rty_o,
// Wishbone Slave interface
output wire [aw-1:0] wbs_adr_o,
output wire [dw-1:0] wbs_dat_o,
output wire [3:0] wbs_sel_o,
output wire wbs_we_o,
output wire wbs_cyc_o,
output wire wbs_stb_o,
output wire [2:0] wbs_cti_o,
output wire [1:0] wbs_bte_o,
input wire [dw-1:0] wbs_dat_i,
input wire wbs_ack_i,
input wire wbs_err_i,
input wire wbs_rty_i);
///////////////////////////////////////////////////////////////////////////////
// Parameters
///////////////////////////////////////////////////////////////////////////////
//Use parameter instead of localparam to work around a bug in Xilinx ISE
parameter master_sel_bits = num_masters > 1 ? `CLOG2(num_masters) : 1;
wire [num_masters-1:0] grant;
wire [master_sel_bits-1:0] master_sel;
wire active;
arbiter
#(.NUM_PORTS (num_masters))
arbiter0
(.clk (wb_clk_i),
.rst (wb_rst_i),
.request (wbm_cyc_i),
.grant (grant),
.select (master_sel),
.active (active));
/* verilator lint_off WIDTH */
//Mux active master
assign wbs_adr_o = wbm_adr_i[master_sel*aw+:aw];
assign wbs_dat_o = wbm_dat_i[master_sel*dw+:dw];
assign wbs_sel_o = wbm_sel_i[master_sel*4+:4];
assign wbs_we_o = wbm_we_i [master_sel];
assign wbs_cyc_o = wbm_cyc_i[master_sel] & active;
assign wbs_stb_o = wbm_stb_i[master_sel];
assign wbs_cti_o = wbm_cti_i[master_sel*3+:3];
assign wbs_bte_o = wbm_bte_i[master_sel*2+:2];
assign wbm_dat_o = {num_masters{wbs_dat_i}};
assign wbm_ack_o = ((wbs_ack_i & active) << master_sel);
assign wbm_err_o = ((wbs_err_i & active) << master_sel);
assign wbm_rty_o = ((wbs_rty_i & active) << master_sel);
/* verilator lint_on WIDTH */
endmodule // wb_arbiter

View File

@@ -0,0 +1,42 @@
CAPI=2:
name: joppeb:wb:wb_arbiter:1.0
description: Wishbone round-robin arbiter
filesets:
rtl:
depend:
- joppeb:util:clog2
files:
- rtl/arbiter.v
- rtl/wb_arbiter.v
file_type: verilogSource
targets:
default:
filesets:
- rtl
toplevel: wb_arbiter
parameters:
- dw
- aw
- num_hosts
- num_masters
parameters:
dw:
datatype: int
description: Wishbone data width
paramtype: vlogparam
aw:
datatype: int
description: Wishbone address width
paramtype: vlogparam
num_hosts:
datatype: int
description: Deprecated alias for num_masters
paramtype: vlogparam
num_masters:
datatype: int
description: Number of wishbone masters
paramtype: vlogparam

View File

@@ -0,0 +1,67 @@
`timescale 1ns/1ps
module formal_wb_gpio;
(* gclk *) reg i_wb_clk;
(* anyseq *) reg i_wb_rst;
(* anyseq *) reg [31:0] i_wb_adr;
(* anyseq *) reg [31:0] i_wb_dat;
(* anyseq *) reg [3:0] i_wb_sel;
(* anyseq *) reg i_wb_we;
(* anyseq *) reg i_wb_stb;
(* anyseq *) reg i_wb_cyc;
(* anyseq *) reg [31:0] i_gpio;
wire [31:0] o_wb_rdt;
wire o_wb_ack;
wire [31:0] o_gpio;
reg f_past_valid;
wb_gpio dut (
.i_clk(i_wb_clk),
.i_rst(i_wb_rst),
.i_wb_adr(i_wb_adr),
.i_wb_dat(i_wb_dat),
.i_wb_sel(i_wb_sel),
.i_wb_we(i_wb_we),
.i_wb_stb(i_wb_stb),
.i_wb_cyc(i_wb_cyc),
.i_gpio(i_gpio),
.o_wb_rdt(o_wb_rdt),
.o_wb_ack(o_wb_ack),
.o_gpio(o_gpio)
);
formal_wb_slave_checker wb_checker (
.i_clk(i_wb_clk),
.i_rst(i_wb_rst),
.i_wb_rst(i_wb_rst),
.i_wb_adr(i_wb_adr),
.i_wb_dat(i_wb_dat),
.i_wb_sel(i_wb_sel),
.i_wb_we(i_wb_we),
.i_wb_stb(i_wb_stb),
.i_wb_cyc(i_wb_cyc),
.o_wb_rdt(o_wb_rdt),
.o_wb_ack(o_wb_ack)
);
initial f_past_valid = 1'b0;
always @(posedge i_wb_clk) begin
f_past_valid <= 1'b1;
// R1: reads return the sampled GPIO input on the following cycle
if(f_past_valid &&
!i_wb_rst && $past(!i_wb_rst) &&
o_wb_ack &&
$past(i_wb_sel)==4'hf && i_wb_sel==4'hf &&
$past(i_wb_cyc & i_wb_stb & !i_wb_we) &&
(i_wb_cyc & i_wb_stb & !i_wb_we))
assert(o_wb_rdt == $past(i_gpio));
// R2: reset clears the output register and read data register
if (f_past_valid && $past(i_wb_rst)) begin
assert(o_gpio == 32'h00000000);
assert(o_wb_rdt == 32'h00000000);
end
end
endmodule

View File

@@ -0,0 +1,23 @@
[tasks]
prove
cover
bmc
[options]
bmc: mode bmc
bmc: depth 50
cover: mode cover
cover: depth 50
prove: mode prove
[engines]
bmc: smtbmc yices
cover: smtbmc yices
prove: abc pdr
[script]
{{"-formal"|gen_reads}}
prep -top {{top_level}}
[files]
{{files}}

View File

@@ -0,0 +1,53 @@
module wb_gpio (
input wire i_clk,
input wire i_rst,
input wire [31:0] i_wb_adr,
input wire [31:0] i_wb_dat,
output reg [31:0] o_wb_rdt,
input wire [3:0] i_wb_sel,
input wire i_wb_we,
input wire i_wb_cyc,
input wire i_wb_stb,
output wire o_wb_ack,
input wire [31:0] i_gpio,
output wire [31:0] o_gpio
);
// Registers
reg [31:0] gpo;
wire [31:0] gpi;
assign o_gpio = gpo;
assign gpi = i_gpio;
reg wb_ack = 0;
assign o_wb_ack = wb_ack & i_wb_cyc & i_wb_stb;
always @(posedge i_clk) begin
if(i_rst) begin
gpo <= 0;
wb_ack <= 0;
o_wb_rdt <= 0;
end else begin
// Ack generation
wb_ack <= i_wb_cyc & i_wb_stb & !wb_ack;
// Read cycle
if(i_wb_cyc && i_wb_stb && !i_wb_we) begin
if(i_wb_sel[0]) o_wb_rdt[7:0] <= gpi[7:0];
if(i_wb_sel[1]) o_wb_rdt[15:8] <= gpi[15:8];
if(i_wb_sel[2]) o_wb_rdt[23:16] <= gpi[23:16];
if(i_wb_sel[3]) o_wb_rdt[31:24] <= gpi[31:24];
end
// write cycle
if(i_wb_cyc && i_wb_stb && i_wb_we) begin
if(i_wb_sel[0]) gpo[7:0] <= i_wb_dat[7:0];
if(i_wb_sel[1]) gpo[15:8] <= i_wb_dat[15:8];
if(i_wb_sel[2]) gpo[23:16] <= i_wb_dat[23:16];
if(i_wb_sel[3]) gpo[31:24] <= i_wb_dat[31:24];
end
end
end
endmodule

View File

@@ -0,0 +1,35 @@
CAPI=2:
name: joppeb:wb:wb_gpio:1.0
description: Wishbone GPIO peripheral
filesets:
rtl:
files:
- rtl/wb_gpio.v
file_type: verilogSource
formal_rtl:
depend:
- joppeb:wb:formal_checker
files:
- formal/formal_wb_gpio.v
file_type: verilogSource
formal_cfg:
files:
- formal/wb_gpio.sby
file_type: sbyConfigTemplate
targets:
default:
filesets:
- rtl
toplevel: wb_gpio
formal:
default_tool: symbiyosys
filesets:
- rtl
- formal_rtl
- formal_cfg
toplevel: formal_wb_gpio

View File

@@ -0,0 +1,50 @@
`timescale 1ns/1ps
module formal_wb_gpio_banks #(
parameter integer num_banks = 2,
);
(* gclk *) reg i_clk;
(* anyseq *) reg i_rst;
(* anyseq *) reg [31:0] i_wb_adr;
(* anyseq *) reg [31:0] i_wb_dat;
(* anyseq *) reg [3:0] i_wb_sel;
(* anyseq *) reg i_wb_we;
(* anyseq *) reg i_wb_stb;
(* anyseq *) reg [num_banks*32-1:0] i_gpio;
wire [31:0] o_wb_rdt;
wire o_wb_ack;
wire [num_banks*32-1:0] o_gpio;
wire i_wb_cyc;
assign i_wb_cyc = i_wb_stb || o_wb_ack;
wb_gpio_banks #(
.num_banks(num_banks)
) dut (
.i_clk(i_clk),
.i_rst(i_rst),
.i_wb_adr(i_wb_adr),
.i_wb_dat(i_wb_dat),
.i_wb_sel(i_wb_sel),
.i_wb_we(i_wb_we),
.i_wb_stb(i_wb_stb),
.i_gpio(i_gpio),
.o_wb_rdt(o_wb_rdt),
.o_wb_ack(o_wb_ack),
.o_gpio(o_gpio)
);
formal_wb_slave_checker wb_checker (
.i_clk(i_clk),
.i_rst(i_rst),
.i_wb_rst(i_rst),
.i_wb_adr(i_wb_adr),
.i_wb_dat(i_wb_dat),
.i_wb_sel(i_wb_sel),
.i_wb_we(i_wb_we),
.i_wb_stb(i_wb_stb),
.i_wb_cyc(i_wb_cyc),
.o_wb_rdt(o_wb_rdt),
.o_wb_ack(o_wb_ack)
);
endmodule

View File

@@ -0,0 +1,25 @@
[tasks]
prove
cover
bmc
[options]
bmc: mode bmc
bmc: depth 50
cover: mode cover
cover: depth 50
prove: mode prove
[engines]
bmc: smtbmc yices
cover: smtbmc yices
prove: abc pdr
[script]
read -formal clog2.vh
{{"-formal"|gen_reads}}
prep -top {{top_level}}
[files]
src/joppeb_util_clog2_1.0/clog2.vh
{{files}}

View File

@@ -0,0 +1,61 @@
`include "clog2.vh"
module wb_gpio_banks #(
parameter num_banks = 4
)(
input wire i_clk,
input wire i_rst,
input wire [31:0] i_wb_adr,
input wire [31:0] i_wb_dat,
output reg [31:0] o_wb_rdt,
input wire [3:0] i_wb_sel,
input wire i_wb_we,
input wire i_wb_cyc,
input wire i_wb_stb,
output wire o_wb_ack,
input wire [num_banks*32-1:0] i_gpio,
output wire [num_banks*32-1:0] o_gpio
);
localparam sw = `CLOG2(num_banks);
wire [num_banks-1:0] bank_sel;
wire [num_banks-1:0] bank_ack;
wire [num_banks*32-1:0] bank_rdt;
genvar gi;
generate
for(gi=0; gi<num_banks; gi=gi+1) begin : gen_gpio
localparam [2+sw-1:0] addr = gi*4;
assign bank_sel[gi] = (i_wb_adr[2+sw-1:0] == addr);
wb_gpio u_gpio(
.i_clk(i_clk),
.i_rst(i_rst),
.i_wb_adr(i_wb_adr),
.i_wb_dat(i_wb_dat),
.i_wb_cyc(i_wb_cyc & bank_sel[gi]),
.i_wb_stb(i_wb_stb & bank_sel[gi]),
.i_wb_we(i_wb_we),
.i_wb_sel(i_wb_sel),
.o_wb_ack(bank_ack[gi]),
.o_wb_rdt(bank_rdt[gi*32 +: 32]),
.i_gpio(i_gpio[gi*32 +: 32]),
.o_gpio(o_gpio[gi*32 +: 32])
);
end
endgenerate
integer bi;
always @* begin
o_wb_rdt = 0;
o_wb_ack = 0;
for(bi=0; bi<num_banks; bi=bi+1) begin
if(bank_sel[bi]) begin
o_wb_rdt = bank_rdt[bi*32 +: 32];
o_wb_ack = bank_ack[bi];
end
end
end;
endmodule

View File

@@ -0,0 +1,52 @@
CAPI=2:
name: joppeb:wb:wb_gpio_banks:1.0
description: Wishbone GPIO bank wrapper
filesets:
rtl:
depend:
- joppeb:wb:wb_gpio
- joppeb:util:clog2
files:
- rtl/wb_gpio_banks.v
file_type: verilogSource
formal_rtl:
depend:
- joppeb:wb:formal_checker
files:
- formal/formal_wb_gpio_banks.v
file_type: verilogSource
formal_cfg:
files:
- formal/wb_gpio_banks.sby
file_type: sbyConfigTemplate
targets:
default:
filesets:
- rtl
toplevel: wb_gpio_banks
parameters:
- NUM_BANKS
- BASE_ADDR
formal:
default_tool: symbiyosys
filesets:
- rtl
- formal_rtl
- formal_cfg
toplevel: formal_wb_gpio_banks
parameters:
- NUM_BANKS
- BASE_ADDR
parameters:
NUM_BANKS:
datatype: int
description: Number of GPIO banks to instantiate
paramtype: vlogparam
BASE_ADDR:
datatype: int
description: Base wishbone address for bank 0
paramtype: vlogparam

View File

@@ -0,0 +1,46 @@
`timescale 1ns/1ps
module formal_wb_mem32;
(* gclk *) reg i_clk;
(* anyseq *) reg i_rst;
(* anyseq *) reg i_wb_rst;
(* anyseq *) reg [31:0] i_wb_adr;
(* anyseq *) reg [31:0] i_wb_dat;
(* anyseq *) reg [3:0] i_wb_sel;
(* anyseq *) reg i_wb_we;
(* anyseq *) reg i_wb_stb;
(* anyseq *) reg i_wb_cyc;
wire [31:0] o_wb_rdt;
wire o_wb_ack;
wb_mem32 #(
.memsize(16),
.sim(1)
) dut (
.i_clk(i_clk),
.i_rst(i_rst),
.i_wb_rst(i_wb_rst),
.i_wb_adr(i_wb_adr),
.i_wb_dat(i_wb_dat),
.i_wb_sel(i_wb_sel),
.i_wb_we(i_wb_we),
.i_wb_stb(i_wb_stb),
.i_wb_cyc(i_wb_cyc),
.o_wb_rdt(o_wb_rdt),
.o_wb_ack(o_wb_ack)
);
formal_wb_slave_checker wb_checker (
.i_clk(i_clk),
.i_rst(i_rst),
.i_wb_rst(i_wb_rst),
.i_wb_adr(i_wb_adr),
.i_wb_dat(i_wb_dat),
.i_wb_sel(i_wb_sel),
.i_wb_we(i_wb_we),
.i_wb_stb(i_wb_stb),
.i_wb_cyc(i_wb_cyc),
.o_wb_rdt(o_wb_rdt),
.o_wb_ack(o_wb_ack)
);
endmodule

View File

@@ -0,0 +1,25 @@
[tasks]
prove
cover
bmc
[options]
bmc: mode bmc
bmc: depth 50
cover: mode cover
cover: depth 50
prove: mode prove
[engines]
bmc: smtbmc yices
cover: smtbmc yices
prove: abc pdr
[script]
read -formal clog2.vh
{{"-formal"|gen_reads}}
prep -top {{top_level}}
[files]
src/joppeb_util_clog2_1.0/clog2.vh
{{files}}

View File

@@ -0,0 +1,66 @@
`timescale 1ns/1ps
`include "clog2.vh"
module wb_mem32 #(
parameter memfile = "",
parameter memsize = 8192,
parameter sim = 1'b0
)(
input wire i_clk,
input wire i_rst,
input wire i_wb_rst,
input wire [31:0] i_wb_adr,
input wire [31:0] i_wb_dat,
input wire [3:0] i_wb_sel,
input wire i_wb_we,
input wire i_wb_stb,
input wire i_wb_cyc,
output wire [31:0] o_wb_rdt,
output wire o_wb_ack
);
localparam integer mem_depth = memsize/4;
localparam integer mem_aw = (mem_depth <= 1) ? 1 : `CLOG2(mem_depth);
reg [31:0] mem [0:mem_depth-1] /* verilator public */;
reg [31:0] wb_rdt_r;
reg wb_ack_r;
wire [mem_aw-1:0] wb_word_adr = i_wb_adr[mem_aw+1:2];
assign o_wb_rdt = wb_rdt_r;
assign o_wb_ack = wb_ack_r & i_wb_cyc & i_wb_stb;
always @(posedge i_clk) begin
if (i_rst || i_wb_rst) begin
wb_ack_r <= 1'b0;
wb_rdt_r <= 32'b0;
end else begin
wb_ack_r <= i_wb_stb & i_wb_cyc & ~wb_ack_r;
if (i_wb_stb & i_wb_cyc & ~wb_ack_r) begin
wb_rdt_r <= mem[wb_word_adr];
if (i_wb_we) begin
if (i_wb_sel[0]) mem[wb_word_adr][7:0] <= i_wb_dat[7:0];
if (i_wb_sel[1]) mem[wb_word_adr][15:8] <= i_wb_dat[15:8];
if (i_wb_sel[2]) mem[wb_word_adr][23:16] <= i_wb_dat[23:16];
if (i_wb_sel[3]) mem[wb_word_adr][31:24] <= i_wb_dat[31:24];
end
end
end
end
integer i;
initial begin
if (sim == 1'b1) begin
for (i = 0; i < mem_depth; i = i + 1)
mem[i] = 32'h00000000;
end
if (|memfile) begin
$display("Preloading %m from %s", memfile);
$readmemh(memfile, mem);
end
wb_rdt_r = 32'b0;
wb_ack_r = 1'b0;
end
endmodule

View File

@@ -0,0 +1,178 @@
`timescale 1ns/1ps
module tb_wb_mem32;
reg i_clk;
reg i_rst;
reg i_wb_rst;
reg [31:0] i_wb_adr;
reg [31:0] i_wb_dat;
reg [3:0] i_wb_sel;
reg i_wb_we;
reg i_wb_stb;
reg i_wb_cyc;
wire [31:0] o_wb_rdt;
wire o_wb_ack;
reg [31:0] read_data;
wb_mem32 #(
.memsize(64),
.sim(1)
) dut (
.i_clk(i_clk),
.i_rst(i_rst),
.i_wb_rst(i_wb_rst),
.i_wb_adr(i_wb_adr),
.i_wb_dat(i_wb_dat),
.i_wb_sel(i_wb_sel),
.i_wb_we(i_wb_we),
.i_wb_stb(i_wb_stb),
.i_wb_cyc(i_wb_cyc),
.o_wb_rdt(o_wb_rdt),
.o_wb_ack(o_wb_ack)
);
initial i_clk = 1'b0;
always #5 i_clk = ~i_clk;
task automatic wb_write;
input [31:0] addr;
input [31:0] data;
input [3:0] sel;
begin
@(negedge i_clk);
i_wb_adr <= addr;
i_wb_dat <= data;
i_wb_sel <= sel;
i_wb_we <= 1'b1;
i_wb_stb <= 1'b1;
i_wb_cyc <= 1'b1;
@(posedge i_clk);
#1;
if (!o_wb_ack) begin
$display("ERROR: write ack missing at time %0t", $time);
$finish;
end
@(posedge i_clk);
i_wb_stb <= 1'b0;
i_wb_cyc <= 1'b0;
i_wb_we <= 1'b0;
i_wb_sel <= 4'b0000;
i_wb_dat <= 32'h0;
@(posedge i_clk);
#1;
if (o_wb_ack) begin
$display("ERROR: write ack did not clear at time %0t", $time);
$finish;
end
end
endtask
task automatic wb_read;
input [31:0] addr;
output [31:0] data;
begin
@(negedge i_clk);
i_wb_adr <= addr;
i_wb_dat <= 32'h0;
i_wb_sel <= 4'b1111;
i_wb_we <= 1'b0;
i_wb_stb <= 1'b1;
i_wb_cyc <= 1'b1;
@(posedge i_clk);
#1;
if (!o_wb_ack) begin
$display("ERROR: read ack missing at time %0t", $time);
$finish;
end
data = o_wb_rdt;
@(posedge i_clk);
i_wb_stb <= 1'b0;
i_wb_cyc <= 1'b0;
i_wb_sel <= 4'b0000;
@(posedge i_clk);
#1;
if (o_wb_ack) begin
$display("ERROR: read ack did not clear at time %0t", $time);
$finish;
end
end
endtask
initial begin
$dumpfile("wb_mem32.vcd");
$dumpvars(0, tb_wb_mem32);
i_rst = 1'b1;
i_wb_rst = 1'b0;
i_wb_adr = 32'h0;
i_wb_dat = 32'h0;
i_wb_sel = 4'b0000;
i_wb_we = 1'b0;
i_wb_stb = 1'b0;
i_wb_cyc = 1'b0;
repeat (2) @(posedge i_clk);
i_rst = 1'b0;
@(negedge i_clk);
i_wb_adr <= 32'h0000_0000;
i_wb_sel <= 4'b1111;
i_wb_stb <= 1'b1;
i_wb_cyc <= 1'b0;
@(posedge i_clk);
#1;
if (o_wb_ack) begin
$display("ERROR: ack asserted without cyc at time %0t", $time);
$finish;
end
@(negedge i_clk);
i_wb_stb <= 1'b0;
i_wb_sel <= 4'b0000;
wb_read(32'h0000_0000, read_data);
if (read_data !== 32'h0000_0000) begin
$display("ERROR: reset contents mismatch, got %08x", read_data);
$finish;
end
wb_write(32'h0000_0000, 32'hA1B2_C3D4, 4'b1111);
wb_read(32'h0000_0000, read_data);
if (read_data !== 32'hA1B2_C3D4) begin
$display("ERROR: full-word write mismatch, got %08x", read_data);
$finish;
end
wb_write(32'h0000_0000, 32'h5566_7788, 4'b0101);
wb_read(32'h0000_0000, read_data);
if (read_data !== 32'hA166_C388) begin
$display("ERROR: byte-enable write mismatch, got %08x", read_data);
$finish;
end
wb_write(32'h0000_0004, 32'hDEAD_BEEF, 4'b1111);
wb_read(32'h0000_0004, read_data);
if (read_data !== 32'hDEAD_BEEF) begin
$display("ERROR: second word mismatch, got %08x", read_data);
$finish;
end
wb_read(32'h0000_0000, read_data);
if (read_data !== 32'hA166_C388) begin
$display("ERROR: first word changed unexpectedly, got %08x", read_data);
$finish;
end
$display("PASS: wb_mem32 testbench completed successfully");
$finish;
end
endmodule

View File

@@ -0,0 +1,63 @@
CAPI=2:
name: joppeb:wb:wb_mem32:1.0
description: Wishbone classic block ram
filesets:
rtl:
depend:
- joppeb:util:clog2
files:
- rtl/wb_mem32.v
file_type: verilogSource
tb:
files:
- tb/tb_wb_mem32.v
file_type: verilogSource
formal_rtl:
depend:
- joppeb:wb:formal_checker
files:
- formal/formal_wb_mem32.v
file_type: verilogSource
formal_cfg:
files:
- formal/wb_mem32.sby
file_type: sbyConfigTemplate
targets:
default:
filesets:
- rtl
toplevel: wb_mem32
parameters:
- memfile
- memsize
- sim
sim:
default_tool: icarus
filesets:
- rtl
- tb
toplevel: tb_wb_mem32
formal:
default_tool: symbiyosys
filesets:
- rtl
- formal_rtl
- formal_cfg
toplevel: formal_wb_mem32
parameters:
memfile:
datatype: str
description: Data to fill the mem
paramtype: vlogparam
memsize:
datatype: int
description: Size of memory in bytes, should be a multiple of 32bit
paramtype: vlogparam
sim:
datatype: int
description: Simulation version, fills rest memory with 0
paramtype: vlogparam

View File

@@ -0,0 +1,145 @@
/* wb_mux. Part of wb_intercon
*
* ISC License
*
* Copyright (C) 2013-2019 Olof Kindgren <olof.kindgren@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
Wishbone multiplexer, burst-compatible
Simple mux with an arbitrary number of slaves.
The parameters MATCH_ADDR and MATCH_MASK are flattened arrays
aw*NUM_SLAVES sized arrays that are used to calculate the
active slave. slave i is selected when
(wb_adr_i & MATCH_MASK[(i+1)*aw-1:i*aw] is equal to
MATCH_ADDR[(i+1)*aw-1:i*aw]
If several regions are overlapping, the slave with the lowest
index is selected. This can be used to have fallback
functionality in the last slave, in case no other slave was
selected.
If no match is found, the wishbone transaction will stall and
an external watchdog is required to abort the transaction
Todo:
Registered master/slave connections
Rewrite with System Verilog 2D arrays when tools support them
*/
`include "clog2.vh"
module wb_mux
#(parameter dw = 32, // Data width
parameter aw = 32, // Address width
parameter num_devices = 2, // Number of devices
parameter num_slaves = num_devices, // Number of devices (deprecated)
parameter [num_slaves*aw-1:0] MATCH_ADDR = 0,
parameter [num_slaves*aw-1:0] MATCH_MASK = 0)
(
input wire wb_clk_i,
input wire wb_rst_i,
// Master Interface
input wire [aw-1:0] wbm_adr_i,
input wire [dw-1:0] wbm_dat_i,
input wire [3:0] wbm_sel_i,
input wire wbm_we_i,
input wire wbm_cyc_i,
input wire wbm_stb_i,
input wire [2:0] wbm_cti_i,
input wire [1:0] wbm_bte_i,
output wire [dw-1:0] wbm_dat_o,
output wire wbm_ack_o,
output wire wbm_err_o,
output wire wbm_rty_o,
// Wishbone Slave interface
output wire [num_slaves*aw-1:0] wbs_adr_o,
output wire [num_slaves*dw-1:0] wbs_dat_o,
output wire [num_slaves*4-1:0] wbs_sel_o,
output wire [num_slaves-1:0] wbs_we_o,
output wire [num_slaves-1:0] wbs_cyc_o,
output wire [num_slaves-1:0] wbs_stb_o,
output wire [num_slaves*3-1:0] wbs_cti_o,
output wire [num_slaves*2-1:0] wbs_bte_o,
input wire [num_slaves*dw-1:0] wbs_dat_i,
input wire [num_slaves-1:0] wbs_ack_i,
input wire [num_slaves-1:0] wbs_err_i,
input wire [num_slaves-1:0] wbs_rty_i);
///////////////////////////////////////////////////////////////////////////////
// Master/slave connection
///////////////////////////////////////////////////////////////////////////////
//Use parameter instead of localparam to work around a bug in Xilinx ISE
parameter slave_sel_bits = num_slaves > 1 ? `CLOG2(num_slaves) : 1;
reg wbm_err;
wire [slave_sel_bits-1:0] slave_sel;
wire [num_slaves-1:0] match;
genvar idx;
generate
for(idx=0; idx<num_slaves ; idx=idx+1) begin : addr_match
assign match[idx] = (wbm_adr_i & MATCH_MASK[idx*aw+:aw]) == MATCH_ADDR[idx*aw+:aw];
end
endgenerate
//
// Find First 1 - Start from MSB and count downwards, returns 0 when no bit set
//
function [slave_sel_bits-1:0] ff1;
input [num_slaves-1:0] in;
integer i;
begin
ff1 = 0;
for (i = num_slaves-1; i >= 0; i=i-1) begin
if (in[i])
/* verilator lint_off WIDTH */
ff1 = i;
/* verilator lint_on WIDTH */
end
end
endfunction
assign slave_sel = ff1(match);
always @(posedge wb_clk_i)
wbm_err <= wbm_cyc_i & !(|match);
assign wbs_adr_o = {num_slaves{wbm_adr_i}};
assign wbs_dat_o = {num_slaves{wbm_dat_i}};
assign wbs_sel_o = {num_slaves{wbm_sel_i}};
assign wbs_we_o = {num_slaves{wbm_we_i}};
/* verilator lint_off WIDTH */
// Expand master CYC to slave bus width before shifting to one-hot select.
// Shifting a 1-bit signal would otherwise zero out all but slave 0.
assign wbs_cyc_o = match & ({num_slaves{wbm_cyc_i}} << slave_sel);
/* verilator lint_on WIDTH */
assign wbs_stb_o = {num_slaves{wbm_stb_i}};
assign wbs_cti_o = {num_slaves{wbm_cti_i}};
assign wbs_bte_o = {num_slaves{wbm_bte_i}};
assign wbm_dat_o = wbs_dat_i[slave_sel*dw+:dw];
assign wbm_ack_o = wbs_ack_i[slave_sel];
assign wbm_err_o = wbs_err_i[slave_sel] | wbm_err;
assign wbm_rty_o = wbs_rty_i[slave_sel];
endmodule

View File

@@ -0,0 +1,51 @@
CAPI=2:
name: joppeb:wb:wb_mux:1.0
description: Wishbone address decoder and multiplexer
filesets:
rtl:
depend:
- joppeb:util:clog2
files:
- rtl/wb_mux.v
file_type: verilogSource
targets:
default:
filesets:
- rtl
toplevel: wb_mux
parameters:
- dw
- aw
- num_devices
- num_slaves
- MATCH_ADDR
- MATCH_MASK
parameters:
dw:
datatype: int
description: Wishbone data width
paramtype: vlogparam
aw:
datatype: int
description: Wishbone address width
paramtype: vlogparam
num_devices:
datatype: int
description: Deprecated alias for num_slaves
paramtype: vlogparam
num_slaves:
datatype: int
description: Number of wishbone slaves
paramtype: vlogparam
MATCH_ADDR:
datatype: int
description: Flattened slave address match table
paramtype: vlogparam
MATCH_MASK:
datatype: int
description: Flattened slave address mask table
paramtype: vlogparam

View File

@@ -0,0 +1,60 @@
`timescale 1ns/1ps
module formal_wb_timer;
(* gclk *) reg i_clk;
(* anyseq *) reg i_rst;
(* anyseq *) reg [31:0] i_wb_adr;
(* anyseq *) reg [31:0] i_wb_dat;
(* anyseq *) reg [3:0] i_wb_sel;
(* anyseq *) reg i_wb_we;
(* anyseq *) reg i_wb_cyc;
(* anyseq *) reg i_wb_stb;
wire [31:0] o_wb_dat;
wire o_wb_ack;
wire o_irq;
wire i_wb_rst;
reg f_past_valid;
assign i_wb_rst = 1'b0;
wb_countdown_timer dut (
.i_clk(i_clk),
.i_rst(i_rst),
.o_irq(o_irq),
.i_wb_adr(i_wb_adr),
.i_wb_dat(i_wb_dat),
.o_wb_dat(o_wb_dat),
.i_wb_sel(i_wb_sel),
.i_wb_we(i_wb_we),
.i_wb_cyc(i_wb_cyc),
.i_wb_stb(i_wb_stb),
.o_wb_ack(o_wb_ack)
);
formal_wb_slave_checker wb_checker (
.i_clk(i_clk),
.i_rst(i_rst),
.i_wb_rst(i_wb_rst),
.i_wb_adr(i_wb_adr),
.i_wb_dat(i_wb_dat),
.i_wb_sel(i_wb_sel),
.i_wb_we(i_wb_we),
.i_wb_stb(i_wb_stb),
.i_wb_cyc(i_wb_cyc),
.o_wb_rdt(o_wb_dat),
.o_wb_ack(o_wb_ack)
);
initial f_past_valid = 1'b0;
always @(posedge i_clk) begin
f_past_valid <= 1'b1;
// Keep the bus idle on the first cycle after reset so the zero-wait ACK
// does not collide with the generic slave checker's post-reset rule.
if (f_past_valid && $past(i_rst)) begin
assume(!i_wb_cyc);
assume(!i_wb_stb);
end
end
endmodule

View File

@@ -0,0 +1,23 @@
[tasks]
prove
cover
bmc
[options]
bmc: mode bmc
bmc: depth 50
cover: mode cover
cover: depth 50
prove: mode prove
[engines]
bmc: smtbmc yices
cover: smtbmc yices
prove: smtbmc yices
[script]
{{"-formal"|gen_reads}}
prep -top {{top_level}}
[files]
{{files}}

View File

@@ -0,0 +1,185 @@
`timescale 1ns/1ps
module wb_countdown_timer (
input wire i_clk,
input wire i_rst,
output wire o_irq,
input wire [31:0] i_wb_adr,
input wire [31:0] i_wb_dat,
output reg [31:0] o_wb_dat,
input wire [3:0] i_wb_sel,
input wire i_wb_we,
input wire i_wb_cyc,
input wire i_wb_stb,
output wire o_wb_ack
);
// Registers
reg [31:0] counter; // The actual counter. Generates an interrupt when it reaches 0
reg [31:0] preload; // The value with which the counter gets loaded after it reaches 0. 0 to keep the timer off
reg wb_ack = 0;
reg irq_fired = 0;
reg counter_started = 0;
reg counter_running = 0;
reg prev_counter_running = 0;
assign o_wb_ack = wb_ack & i_wb_cyc & i_wb_stb; // Make sure the ack only happend during a cycle
assign o_irq = irq_fired;
always @(posedge i_clk) begin
if(i_rst) begin
counter <= 0;
preload <= 0;
wb_ack <= 0;
o_wb_dat <= 0;
irq_fired <= 0;
counter_started <= 0;
counter_running <= 0;
prev_counter_running <= 0;
end else begin
prev_counter_running <= counter_running;
counter_running <= counter>0;
if(!irq_fired && prev_counter_running && !counter_running)
irq_fired <= 1'b1;
if(counter>0 && counter_started)
counter <= counter - 1;
if(counter == 0 && preload>0 && counter_started)
counter <= preload;
if(counter == 0 && preload == 0)
counter_started <= 1'b0;
// Ack generation
wb_ack <= i_wb_cyc & i_wb_stb & !wb_ack;
// Read cycle
if(i_wb_cyc && i_wb_stb && !i_wb_we) begin
if(i_wb_adr[3:0] == 4'b0000) begin
if(i_wb_sel[0]) o_wb_dat[7:0] <= counter[7:0];
if(i_wb_sel[1]) o_wb_dat[15:8] <= counter[15:8];
if(i_wb_sel[2]) o_wb_dat[23:16] <= counter[23:16];
if(i_wb_sel[3]) o_wb_dat[31:24] <= counter[31:24];
end else if(i_wb_adr[3:0] == 4'b0100) begin
if(i_wb_sel[0]) o_wb_dat[7:0] <= preload[7:0];
if(i_wb_sel[1]) o_wb_dat[15:8] <= preload[15:8];
if(i_wb_sel[2]) o_wb_dat[23:16] <= preload[23:16];
if(i_wb_sel[3]) o_wb_dat[31:24] <= preload[31:24];
end
end
// write cycle
if(i_wb_cyc && i_wb_stb && i_wb_we) begin
if(i_wb_adr[3:0] == 4'b0000) begin
if(i_wb_sel[0]) counter[7:0] <= i_wb_dat[7:0];
if(i_wb_sel[1]) counter[15:8] <= i_wb_dat[15:8];
if(i_wb_sel[2]) counter[23:16] <= i_wb_dat[23:16];
if(i_wb_sel[3]) counter[31:24] <= i_wb_dat[31:24];
counter_started <= 1'b1;
end else if(i_wb_adr[3:0] == 4'b0100) begin
if(i_wb_sel[0]) preload[7:0] <= i_wb_dat[7:0];
if(i_wb_sel[1]) preload[15:8] <= i_wb_dat[15:8];
if(i_wb_sel[2]) preload[23:16] <= i_wb_dat[23:16];
if(i_wb_sel[3]) preload[31:24] <= i_wb_dat[31:24];
counter_started <= 1'b1;
end else if(i_wb_adr[3:0] == 4'b1000) begin
// Any write to BASE+8 will ack the IRQ
irq_fired <= 1'b0;
end
end
end
end
`ifdef FORMAL
// Formal verification
reg f_past_valid = 1'b0;
wire cnt_write = i_wb_cyc && i_wb_stb && i_wb_we && (i_wb_adr[3:0] == 4'h0);
wire pld_write = i_wb_cyc && i_wb_stb && i_wb_we && (i_wb_adr[3:0] == 4'h4);
wire ack_write = i_wb_cyc && i_wb_stb && i_wb_we && (i_wb_adr[3:0] == 4'h8);
reg [31:0] past_counter;
always @(posedge i_clk) begin
f_past_valid <= 1'b1;
past_counter <= counter;
// R1: Reset clears all timer state on the following cycle.
// Check counter, preload, wb_ack, o_wb_dat, irq_fired,
// counter_started, counter_running, and prev_counter_running.
if(f_past_valid && $past(i_rst)) begin
R1s1: assert(counter==0);
R1s2: assert(preload==0);
R1s3: assert(!wb_ack);
R1s4: assert(o_wb_dat==0);
R1s5: assert(!irq_fired);
R1s6: assert(!counter_started);
R1s7: assert(!counter_running);
end
// R2: irq_fired is sticky until reset or a write to BASE+8 clears it.
// -> if last cycle was irq and last cycle was not reset and not ack write then now still irq
if(f_past_valid && $past(!i_rst) && $past(irq_fired) && $past(!ack_write))
R2: assert(irq_fired);
// R3: A write to BASE+8 clears irq_fired on the following cycle.
// -> if last cycle was ack write and irq was high it must be low now
if(f_past_valid && $past(!i_rst) && $past(irq_fired) && $past(ack_write))
R3: assert(!irq_fired);
// R4: While the timer is running and no counter write overrides it,
// counter decrements by exactly one each cycle.
if(f_past_valid && $past(!i_rst) && $past(counter>1) && $past(counter_started) && $past(!cnt_write))
R4: assert(counter == $past(counter)-1);
// R5: When counter reaches zero with preload > 0 and the timer is started,
// the counter reloads from preload.
if(f_past_valid && $past(!i_rst) && $past(counter==0) && $past(preload>0) && $past(counter_started) && $past(!cnt_write))
R5: assert(counter == $past(preload));
// R6: When counter == 0 and preload == 0, the timer stops
// (counter_started deasserts unless a write rearms it).
if(f_past_valid && $past(!i_rst) && $past(counter==0) && $past(preload==0) && $past(!cnt_write) && $past(!pld_write)) begin
R6s1: assert(counter==0);
R6s2: assert(counter_started==0);
end
// R7: A write to BASE+0 or BASE+4 arms the timer
// (counter_started asserts on the following cycle).
if(f_past_valid && $past(!i_rst) && ($past(cnt_write) || $past(pld_write)))
R7: assert(counter_started==1);
// R8: o_irq always reflects irq_fired.
R8: assert(o_irq == irq_fired);
// R9: Interrupt only fired after counter was 0 two clock cycles ago
if(f_past_valid && $past(!i_rst) && $past(!irq_fired) && irq_fired)
R9: assert($past(past_counter) == 0);
// C1: Cover a counter write that starts the timer.
C1s1: cover(f_past_valid && !i_rst && cnt_write);
if(f_past_valid && $past(!i_rst) && $past(cnt_write))
C1s2: cover(counter_started);
// C2: Cover a preload write.
C2: cover(f_past_valid && !i_rst && pld_write);
// C3: Cover the counter decrementing at least once.
C3: cover(f_past_valid && $past(!i_rst) && $past(counter)==counter-1 && $past(!cnt_write) && $past(!pld_write));
// C4: Cover the counter reloading from preload.
C4: cover(f_past_valid && $past(!i_rst) && $past(counter==0) && $past(preload>0) && counter>0 && $past(!cnt_write) && $past(!pld_write));
// C5: Cover irq_fired asserting when the timer expires.
C5: cover(f_past_valid && $past(!i_rst) && $past(!irq_fired) && irq_fired && $past(counter==0));
// C6: Cover irq_fired being cleared by a write to BASE+8.
C6: cover(f_past_valid && $past(!i_rst) && $past(irq_fired) && !irq_fired && $past(ack_write));
end
`endif
endmodule

Some files were not shown because too many files have changed in this diff Show More