├── .gitignore ├── src ├── .gitignore ├── examples │ ├── adder.sv │ ├── adder.v │ ├── button.v │ ├── button.sv │ ├── adder_tb.v │ ├── adder.vhdl │ ├── button_vhdl.syn.v │ ├── initial_vhdl.syn.v │ ├── adder_vhdl.syn.v │ ├── initial.v │ ├── button.vhdl │ ├── timer_tb.v │ ├── adder_sv.syn.v │ ├── initial.syn.v │ ├── timer.v │ ├── initial.vhdl │ ├── button_sv.syn.v │ ├── priority_encoder_vhdl.syn.v │ ├── priority_encoder.v │ ├── button.syn.v │ ├── priority_encoder.vhdl │ ├── timer.vhdl │ ├── adder.syn.v │ ├── timer.syn.v │ ├── priority_encoder.syn.v │ ├── counter.v │ ├── rr_arbiter_vhdl.syn.v │ ├── counter.vhdl │ ├── rr_arbiter.v │ ├── rr_arbiter.vhdl │ ├── timer_vhdl.syn.v │ ├── rr_arbiter.syn.v │ └── counter_vhdl.syn.v ├── tests │ ├── toggle.v │ ├── and2.v │ ├── divide.v │ ├── mux21a.v │ ├── add2.v │ ├── sync_reset.v │ ├── sync_reset_n.v │ ├── async_reset.v │ ├── async_reset_n.v │ ├── mux2_1.v │ ├── toggle.syn.v │ ├── sync_reset.syn.v │ ├── add2.syn.v │ ├── async_reset.syn.v │ ├── sync_reset_n.syn.v │ ├── async_reset_n.syn.v │ ├── fsm.sv │ ├── fsm_sv.syn.v │ └── divide.syn.v ├── process.py ├── synth_v.sh ├── synth_sv.sh └── synth_vhdl.sh ├── docs ├── hardware │ ├── image.png │ ├── control.png │ ├── pmod_io.png │ ├── board_xilinx.md │ ├── onboard_xilinx.md │ └── peripheral.md ├── img │ ├── board_xilinx.png │ ├── board_xilinx_anno.png │ ├── pipeline_structure.svg │ └── centralized_structure.svg ├── static │ ├── systemverilog.pdf │ ├── ethernet-example.zip │ └── digital-design-template.zip ├── hdl-by-example │ ├── imgs │ │ ├── async_reset.png │ │ ├── sync_reset.png │ │ ├── sync_reset_n.png │ │ ├── timer.drawio.png │ │ ├── async_reset_n.png │ │ ├── counter.drawio.png │ │ ├── counter.drawio │ │ ├── timer.drawio │ │ ├── button.svg │ │ ├── counter.svg │ │ └── adder.svg │ ├── overview.md │ ├── labs.md │ ├── model.md │ ├── adder.md │ ├── button.md │ ├── priority_encoder.md │ └── simulation.md ├── javascripts │ └── mathjax.js ├── software.md ├── hdl │ ├── tri.md │ ├── reset.md │ ├── debug.md │ ├── bus.md │ ├── clocking.md │ └── peripheral.md ├── index.md ├── project.md ├── faq.md └── vivado.md ├── .github └── workflows │ └── doc.yml ├── .vscode └── settings.json ├── pyproject.toml ├── README.md ├── theme-override └── main.html └── mkdocs.yml /.gitignore: -------------------------------------------------------------------------------- 1 | site/ 2 | deploy.sh 3 | *.vcd 4 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | *.png 2 | *.dot 3 | *.svg 4 | *.json 5 | a.out 6 | -------------------------------------------------------------------------------- /docs/hardware/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thu-cs-lab/Digital-Design-Docs/HEAD/docs/hardware/image.png -------------------------------------------------------------------------------- /docs/hardware/control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thu-cs-lab/Digital-Design-Docs/HEAD/docs/hardware/control.png -------------------------------------------------------------------------------- /docs/hardware/pmod_io.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thu-cs-lab/Digital-Design-Docs/HEAD/docs/hardware/pmod_io.png -------------------------------------------------------------------------------- /docs/img/board_xilinx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thu-cs-lab/Digital-Design-Docs/HEAD/docs/img/board_xilinx.png -------------------------------------------------------------------------------- /docs/img/board_xilinx_anno.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thu-cs-lab/Digital-Design-Docs/HEAD/docs/img/board_xilinx_anno.png -------------------------------------------------------------------------------- /docs/static/systemverilog.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thu-cs-lab/Digital-Design-Docs/HEAD/docs/static/systemverilog.pdf -------------------------------------------------------------------------------- /docs/static/ethernet-example.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thu-cs-lab/Digital-Design-Docs/HEAD/docs/static/ethernet-example.zip -------------------------------------------------------------------------------- /src/examples/adder.sv: -------------------------------------------------------------------------------- 1 | module add2 ( 2 | input [1:0] a, 3 | input [1:0] b, 4 | output [1:0] c 5 | ); 6 | assign c = a + b; 7 | endmodule -------------------------------------------------------------------------------- /src/examples/adder.v: -------------------------------------------------------------------------------- 1 | module add2 ( 2 | input [1:0] a, 3 | input [1:0] b, 4 | output [1:0] c 5 | ); 6 | assign c = a + b; 7 | endmodule -------------------------------------------------------------------------------- /docs/hdl-by-example/imgs/async_reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thu-cs-lab/Digital-Design-Docs/HEAD/docs/hdl-by-example/imgs/async_reset.png -------------------------------------------------------------------------------- /docs/hdl-by-example/imgs/sync_reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thu-cs-lab/Digital-Design-Docs/HEAD/docs/hdl-by-example/imgs/sync_reset.png -------------------------------------------------------------------------------- /docs/hdl-by-example/imgs/sync_reset_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thu-cs-lab/Digital-Design-Docs/HEAD/docs/hdl-by-example/imgs/sync_reset_n.png -------------------------------------------------------------------------------- /docs/hdl-by-example/imgs/timer.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thu-cs-lab/Digital-Design-Docs/HEAD/docs/hdl-by-example/imgs/timer.drawio.png -------------------------------------------------------------------------------- /docs/static/digital-design-template.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thu-cs-lab/Digital-Design-Docs/HEAD/docs/static/digital-design-template.zip -------------------------------------------------------------------------------- /docs/hdl-by-example/imgs/async_reset_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thu-cs-lab/Digital-Design-Docs/HEAD/docs/hdl-by-example/imgs/async_reset_n.png -------------------------------------------------------------------------------- /docs/hdl-by-example/imgs/counter.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thu-cs-lab/Digital-Design-Docs/HEAD/docs/hdl-by-example/imgs/counter.drawio.png -------------------------------------------------------------------------------- /src/tests/toggle.v: -------------------------------------------------------------------------------- 1 | module toggle ( 2 | input button, 3 | output light 4 | ); 5 | always @(posedge button) begin 6 | light <= ~light; 7 | end 8 | endmodule -------------------------------------------------------------------------------- /src/tests/and2.v: -------------------------------------------------------------------------------- 1 | module and2( 2 | input wire a, 3 | input wire b, 4 | output wire y 5 | ); 6 | 7 | always @ (*) begin 8 | y = a & b; 9 | end 10 | 11 | endmodule 12 | -------------------------------------------------------------------------------- /src/tests/divide.v: -------------------------------------------------------------------------------- 1 | module divide ( 2 | input [6:0] counter, 3 | output [3:0] tens, 4 | output [3:0] ones 5 | ); 6 | 7 | assign ones = counter % 10; 8 | assign tens = counter / 10; 9 | 10 | endmodule -------------------------------------------------------------------------------- /src/tests/mux21a.v: -------------------------------------------------------------------------------- 1 | module mux21a( 2 | input wire a, 3 | input wire b, 4 | input wire s, 5 | output wire y 6 | ); 7 | 8 | always @ (*) begin 9 | y = (b & s) | (a & (~s)); 10 | end 11 | endmodule 12 | -------------------------------------------------------------------------------- /src/examples/button.v: -------------------------------------------------------------------------------- 1 | module button ( 2 | input button, 3 | output light 4 | ); 5 | reg light_reg; 6 | 7 | always @ (posedge button) begin 8 | light_reg <= ~light_reg; 9 | end 10 | 11 | assign light = light_reg; 12 | 13 | endmodule -------------------------------------------------------------------------------- /src/examples/button.sv: -------------------------------------------------------------------------------- 1 | module button ( 2 | input button, 3 | output light 4 | ); 5 | reg light_reg; 6 | 7 | always_ff @ (posedge button) begin 8 | light_reg <= ~light_reg; 9 | end 10 | 11 | assign light = light_reg; 12 | 13 | endmodule -------------------------------------------------------------------------------- /src/tests/add2.v: -------------------------------------------------------------------------------- 1 | module add2( 2 | input a_0, 3 | input a_1, 4 | input b_0, 5 | input b_1, 6 | output c_0, 7 | output c_1 8 | ); 9 | 10 | always @ (*) begin 11 | {c_1, c_0} = {a_1, a_0} + {b_1, b_0}; 12 | end 13 | 14 | endmodule 15 | -------------------------------------------------------------------------------- /src/process.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import re 3 | 4 | lines = open(sys.argv[1]).readlines() 5 | with open(sys.argv[1], 'w') as f: 6 | for line in lines: 7 | if "shape=record, label=" in line: 8 | line = re.sub(r'\$[0-9]+\\n\$_+([A-Z0-9_]+)_+', r'\1', line) 9 | print(line, end='', file=f) -------------------------------------------------------------------------------- /docs/hdl-by-example/overview.md: -------------------------------------------------------------------------------- 1 | # 通过例子学硬件描述语言 2 | 3 | 本章通过一系列的 HDL 设计的例子来告诉大家如何用 Verilog/System Verilog/VHDL 编写数字逻辑电路。 4 | 5 | 每一个例子都是按照如下四步进行设计的: 6 | 7 | 1. 需求:先确定输入输出信号和行为 8 | 2. 波形:其次根据目标行为,绘制波形图 9 | 3. 电路:然后确定哪些地方采用时序逻辑,哪些地方采用组合逻辑 10 | 4. 代码:最后编写 HDL 代码 11 | 12 | 希望同学在进行 HDL 编程的时候,也可以按照这个顺序来思考,而不是一开始就编写 HDL 代码。 -------------------------------------------------------------------------------- /src/tests/sync_reset.v: -------------------------------------------------------------------------------- 1 | module sync_reset ( 2 | input clock, 3 | input reset, 4 | input d, 5 | output q 6 | ); 7 | reg q_reg; 8 | 9 | always @ (posedge clock) begin 10 | if (reset) begin 11 | q_reg <= 1'b0; 12 | end else begin 13 | q_reg <= d; 14 | end 15 | end 16 | 17 | assign q = q_reg; 18 | 19 | endmodule -------------------------------------------------------------------------------- /src/tests/sync_reset_n.v: -------------------------------------------------------------------------------- 1 | module sync_reset_n ( 2 | input clock, 3 | input reset_n, 4 | input d, 5 | output q 6 | ); 7 | reg q_reg; 8 | 9 | always @ (posedge clock) begin 10 | if (~reset_n) begin 11 | q_reg <= 1'b0; 12 | end else begin 13 | q_reg <= d; 14 | end 15 | end 16 | 17 | assign q = q_reg; 18 | 19 | endmodule -------------------------------------------------------------------------------- /src/tests/async_reset.v: -------------------------------------------------------------------------------- 1 | module async_reset ( 2 | input clock, 3 | input reset, 4 | input d, 5 | output q 6 | ); 7 | reg q_reg; 8 | 9 | always @ (posedge clock, posedge reset) begin 10 | if (reset) begin 11 | q_reg <= 1'b0; 12 | end else begin 13 | q_reg <= d; 14 | end 15 | end 16 | 17 | assign q = q_reg; 18 | 19 | endmodule -------------------------------------------------------------------------------- /src/tests/async_reset_n.v: -------------------------------------------------------------------------------- 1 | module async_reset_n ( 2 | input clock, 3 | input reset_n, 4 | input d, 5 | output q 6 | ); 7 | reg q_reg; 8 | 9 | always @ (posedge clock, negedge reset_n) begin 10 | if (~reset_n) begin 11 | q_reg <= 1'b0; 12 | end else begin 13 | q_reg <= d; 14 | end 15 | end 16 | 17 | assign q = q_reg; 18 | 19 | endmodule -------------------------------------------------------------------------------- /docs/javascripts/mathjax.js: -------------------------------------------------------------------------------- 1 | window.MathJax = { 2 | tex: { 3 | inlineMath: [["\\(", "\\)"]], 4 | displayMath: [["\\[", "\\]"]], 5 | processEscapes: true, 6 | processEnvironments: true 7 | }, 8 | options: { 9 | ignoreHtmlClass: ".*|", 10 | processHtmlClass: "arithmatex" 11 | } 12 | }; 13 | 14 | document$.subscribe(() => { 15 | MathJax.typesetPromise() 16 | }) 17 | -------------------------------------------------------------------------------- /src/examples/adder_tb.v: -------------------------------------------------------------------------------- 1 | `timescale 1ns/1ps 2 | module add2_tb (); 3 | reg [1:0] a; 4 | reg [1:0] b; 5 | wire [1:0] c; 6 | 7 | initial begin 8 | $dumpfile("test.vcd"); 9 | $dumpvars(0, add2_tb); 10 | 11 | a <= 2'b01; 12 | b <= 2'b10; 13 | #1; 14 | $finish; 15 | end 16 | 17 | add2 inst ( 18 | .a(a), 19 | .b(b), 20 | .c(c) 21 | ); 22 | endmodule -------------------------------------------------------------------------------- /src/examples/adder.vhdl: -------------------------------------------------------------------------------- 1 | library IEEE; 2 | use IEEE.STD_LOGIC_1164.ALL; 3 | use IEEE.STD_LOGIC_UNSIGNED.ALL; 4 | 5 | entity add2 is 6 | Port ( a : in STD_LOGIC_VECTOR (1 downto 0); 7 | b : in STD_LOGIC_VECTOR (1 downto 0); 8 | c : out STD_LOGIC_VECTOR (1 downto 0)); 9 | end add2; 10 | 11 | architecture behavior of add2 is 12 | begin 13 | c <= a + b; 14 | end behavior; -------------------------------------------------------------------------------- /src/tests/mux2_1.v: -------------------------------------------------------------------------------- 1 | module mux2_1( 2 | input wire d0, 3 | input wire d1, 4 | input wire sel, 5 | output wire q 6 | ); 7 | 8 | wire tmp1, tmp2, tmp3; 9 | 10 | always @ (*) begin 11 | tmp1 = d0 & sel; 12 | tmp2 = d1 & (~sel); 13 | end 14 | 15 | always @ (*) begin 16 | tmp3 = tmp1 | tmp2; 17 | q = tmp3; 18 | end 19 | 20 | endmodule -------------------------------------------------------------------------------- /src/examples/button_vhdl.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+6 (git sha1 e0ba32423, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | module button(button, light); 4 | wire _0_; 5 | input button; 6 | wire button; 7 | output light; 8 | wire light; 9 | reg light_reg; 10 | assign _0_ = ~light_reg; 11 | always @(posedge button) 12 | light_reg <= _0_; 13 | assign light = light_reg; 14 | endmodule 15 | -------------------------------------------------------------------------------- /src/examples/initial_vhdl.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.17+94 (git sha1 d1b2beab1, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | module initial_reg(clock, reset, d, q); 4 | input clock; 5 | wire clock; 6 | input d; 7 | wire d; 8 | output q; 9 | reg q = 1'h0; 10 | wire r; 11 | input reset; 12 | wire reset; 13 | always @(posedge clock) 14 | if (reset) q <= 1'h0; 15 | else q <= d; 16 | assign r = q; 17 | endmodule 18 | -------------------------------------------------------------------------------- /src/examples/adder_vhdl.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+6 (git sha1 e0ba32423, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | module add2(a, b, c); 4 | wire _0_; 5 | wire _1_; 6 | input [1:0] a; 7 | wire [1:0] a; 8 | input [1:0] b; 9 | wire [1:0] b; 10 | output [1:0] c; 11 | wire [1:0] c; 12 | assign _0_ = b[0] & a[0]; 13 | assign _1_ = b[1] ^ a[1]; 14 | assign c[1] = _0_ ^ _1_; 15 | assign c[0] = b[0] ^ a[0]; 16 | endmodule 17 | -------------------------------------------------------------------------------- /src/examples/initial.v: -------------------------------------------------------------------------------- 1 | module initial_reg ( 2 | input clock, 3 | input reset, 4 | input d, 5 | output q 6 | ); 7 | 8 | reg r; 9 | 10 | //initial r = 1'b0; 11 | //initial begin 12 | //r = 1'b0; 13 | //end 14 | 15 | always @ (posedge clock) begin 16 | if (reset) begin 17 | r <= 1'b0; 18 | end else begin 19 | r <= d; 20 | end 21 | end 22 | 23 | //assign q = r; 24 | initial q = 1'b0; 25 | assign q = r; 26 | 27 | endmodule -------------------------------------------------------------------------------- /docs/software.md: -------------------------------------------------------------------------------- 1 | # 常用软件 2 | 3 | 本节列举了开发中可能用到的常用软件。 4 | 5 | ## CoeConverter 6 | 7 | 地址:[GitHub](https://github.com/thu-cs-lab/CoeConverter) 8 | 9 | 用于将图片或者二进制文件转换为 COE 格式,供 EDA 工具使用。 10 | 11 | ## 串口调试相关 12 | 13 | * Windows 10 UWP 串口调试助手:[Microsoft Store](https://www.microsoft.com/zh-cn/p/%E4%B8%B2%E5%8F%A3%E8%B0%83%E8%AF%95%E5%8A%A9%E6%89%8B/9nblggh43hdm) 14 | * Linux:使用 `screen`,或者 Python 的 `pyserial` 库 15 | * [QSerial](https://github.com/tuna/QSerial) 16 | -------------------------------------------------------------------------------- /src/synth_v.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | OUTPUT_PNG=${1/.v/.png} 4 | OUTPUT_SVG=${1/.v/.svg} 5 | OUTPUT_JSON=${1/.v/.json} 6 | OUTPUT_SYN_V=${1/.v/.syn.v} 7 | yosys -p "read_verilog $1" -p "prep -top $2" -p "synth" -p "dffunmap" -p "abc -g AND,OR,XOR,MUX,NAND,NOR -dff" -p "select -module $2" -p "write_json ${OUTPUT_JSON}" -p "write_verilog ${OUTPUT_SYN_V}" 8 | netlistsvg ${OUTPUT_JSON} -o ${OUTPUT_SVG} 9 | rsvg-convert ${OUTPUT_SVG} -b white --height 800 --keep-aspect-ratio -o ${OUTPUT_PNG} -------------------------------------------------------------------------------- /src/examples/button.vhdl: -------------------------------------------------------------------------------- 1 | library IEEE; 2 | use IEEE.STD_LOGIC_1164.ALL; 3 | 4 | entity button is 5 | Port ( button : in STD_LOGIC; 6 | light : out STD_LOGIC); 7 | end button; 8 | 9 | architecture behavior of button is 10 | signal light_reg : STD_LOGIC; 11 | begin 12 | -- sequential 13 | process(button) 14 | begin 15 | if button='1' and button'event then 16 | light_reg <= not light_reg; 17 | end if; 18 | end process; 19 | 20 | -- combinatorial 21 | light <= light_reg; 22 | end behavior; -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | name: Build documentation with mkdocs 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Set up Python 3.12 12 | uses: actions/setup-python@v1 13 | with: 14 | python-version: 3.12 15 | - name: Install dependencies 16 | run: | 17 | python -m pip install --upgrade poetry 18 | poetry install 19 | - name: Build with mkdocs 20 | run: poetry run mkdocs build 21 | -------------------------------------------------------------------------------- /docs/hdl/tri.md: -------------------------------------------------------------------------------- 1 | # 三态门 2 | 3 | 在一些接口协议中,比如 I2C、SPI 等等,为了节省引脚数量,都出现了同一个信号在不同的时间传输不同方向的数据的现象。因此,为了防止两端设备同时输出,设备在不输出信号时需要设置高阻态。在 Verilog 代码中,我们通常会将三态门 `signal_io` 拆分成三个信号:`signal_i`、`signal_o` 和 `signal_t`,分别表示输入、输出和高阻态。对应的代码如下: 4 | 5 | ```verilog 6 | module tri_state_logic ( 7 | inout signal_io 8 | ); 9 | 10 | wire signal_i; 11 | wire signal_o; 12 | wire signal_t; 13 | 14 | assign signal_io = signal_t ? 1'bz : signal_o; 15 | assign signal_i = signal_io; 16 | 17 | endmodule 18 | ``` 19 | 20 | 这样实现以后,内部就可以很方便地处理三态逻辑了。 -------------------------------------------------------------------------------- /src/examples/timer_tb.v: -------------------------------------------------------------------------------- 1 | module timer_tb (); 2 | reg clock; 3 | reg reset; 4 | wire [3:0] timer; 5 | 6 | initial begin 7 | $dumpfile("test.vcd"); 8 | $dumpvars(0, timer_tb); 9 | 10 | reset <= 1'b0; 11 | clock <= 1'b1; 12 | 13 | #10; 14 | 15 | reset <= 1'b1; 16 | 17 | #10; 18 | 19 | reset <= 1'b0; 20 | 21 | #1000; 22 | $finish; 23 | end 24 | 25 | always #10 clock = ~clock; 26 | 27 | timer inst ( 28 | .clock(clock), 29 | .reset(reset), 30 | .timer(timer) 31 | ); 32 | endmodule -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "languageTool.language": "zh", 3 | "cSpell.words": [ 4 | "AMBA", 5 | "clk'event", 6 | "CMOS", 7 | "conv", 8 | "CPLD", 9 | "datasheet", 10 | "debouncer", 11 | "Digilent", 12 | "downto", 13 | "drawio", 14 | "EEPROM", 15 | "endcase", 16 | "endmodule", 17 | "GMII", 18 | "GPIO", 19 | "imgs", 20 | "inout", 21 | "JTAG", 22 | "MMCM", 23 | "Pmod", 24 | "posedge", 25 | "Quartus", 26 | "SDFF", 27 | "SDRAM", 28 | "SRAM", 29 | "testbench", 30 | "UART", 31 | "Verilog", 32 | "VHDL", 33 | "Vivado", 34 | "WaveDrom" 35 | ] 36 | } -------------------------------------------------------------------------------- /src/tests/toggle.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+41 (git sha1 29c0a5958, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | (* top = 1 *) 4 | (* src = "tests/toggle.v:1.1-8.10" *) 5 | module toggle(button, light); 6 | (* src = "tests/toggle.v:5.2-7.5" *) 7 | wire _0_; 8 | (* src = "tests/toggle.v:2.9-2.15" *) 9 | input button; 10 | wire button; 11 | (* src = "tests/toggle.v:3.10-3.15" *) 12 | output light; 13 | reg light; 14 | assign _0_ = ~ (* src = "tests/toggle.v:6.12-6.18" *) light; 15 | (* src = "tests/toggle.v:5.2-7.5" *) 16 | always @(posedge button) 17 | light <= _0_; 18 | endmodule 19 | -------------------------------------------------------------------------------- /src/synth_sv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | TEMP=$(cat /dev/urandom | base64 | env LC_CTYPE=C tr -cd 'a-f0-9' | head -c 32) 4 | OUTPUT_DOT=${1/.sv/_sv.dot} 5 | OUTPUT_PNG=${1/.sv/_sv.png} 6 | OUTPUT_SVG=${1/.sv/_sv.svg} 7 | OUTPUT_SYN_V=${1/.sv/_sv.syn.v} 8 | yosys -p "read_verilog -sv $1" -p synth -p "abc -g AND,OR,XOR,NAND,NOR" -p "clean" -p "select -module $2" -p "show -prefix $TEMP -format png" -p "write_verilog ${OUTPUT_SYN_V}" 9 | mv ${TEMP}.dot ${OUTPUT_DOT} 10 | rm ${TEMP}.png 11 | python3 process.py ${OUTPUT_DOT} 12 | dot -Tpng ${OUTPUT_DOT} > ${OUTPUT_PNG} 13 | dot -Tsvg ${OUTPUT_DOT} > ${OUTPUT_SVG} 14 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "digital-design-docs" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Jiajie Chen "] 6 | package-mode = false 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.8" 10 | mkdocs-wavedrom-plugin = {git = "https://github.com/jiegec/mkdocs-wavedrom-plugin.git"} 11 | 12 | [tool.poetry.dev-dependencies] 13 | mkdocs = "^1.3.1" 14 | mkdocs-material = "^9.4.8" 15 | mkdocs-git-authors-plugin = "^0.7.0" 16 | mkdocs-git-revision-date-localized-plugin = "^1.1.0" 17 | 18 | [build-system] 19 | requires = ["poetry-core>=1.0.0"] 20 | build-backend = "poetry.core.masonry.api" 21 | -------------------------------------------------------------------------------- /src/examples/adder_sv.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+41 (git sha1 29c0a5958, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | (* src = "examples/adder.sv:1.1-7.10" *) 4 | module add2(a, b, c); 5 | wire _0_; 6 | wire _1_; 7 | (* src = "examples/adder.sv:2.15-2.16" *) 8 | input [1:0] a; 9 | wire [1:0] a; 10 | (* src = "examples/adder.sv:3.15-3.16" *) 11 | input [1:0] b; 12 | wire [1:0] b; 13 | (* src = "examples/adder.sv:4.16-4.17" *) 14 | output [1:0] c; 15 | wire [1:0] c; 16 | assign _0_ = b[0] & a[0]; 17 | assign _1_ = b[1] ^ a[1]; 18 | assign c[1] = _0_ ^ _1_; 19 | assign c[0] = b[0] ^ a[0]; 20 | endmodule 21 | -------------------------------------------------------------------------------- /src/examples/initial.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+41 (git sha1 29c0a5958, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | (* src = "examples/initial.v:1.1-27.10" *) 4 | module initial_reg(clock, reset, d, q); 5 | (* src = "examples/initial.v:2.8-2.13" *) 6 | input clock; 7 | wire clock; 8 | (* src = "examples/initial.v:4.8-4.9" *) 9 | input d; 10 | wire d; 11 | (* src = "examples/initial.v:5.9-5.10" *) 12 | output q; 13 | wire q; 14 | (* src = "examples/initial.v:8.6-8.7" *) 15 | wire r; 16 | (* src = "examples/initial.v:3.8-3.13" *) 17 | input reset; 18 | wire reset; 19 | assign q = 1'h0; 20 | assign r = 1'h0; 21 | endmodule 22 | -------------------------------------------------------------------------------- /src/examples/timer.v: -------------------------------------------------------------------------------- 1 | module timer ( 2 | input clock, 3 | input reset, 4 | output [3:0] timer 5 | ); 6 | 7 | reg [3:0] timer_reg; 8 | reg [19:0] counter_reg; 9 | 10 | // sequential 11 | always @ (posedge clock) begin 12 | if (reset) begin 13 | timer_reg <= 4'b0; 14 | counter_reg <= 20'b0; 15 | end else begin 16 | if (counter_reg == 20'd999999) begin 17 | timer_reg <= timer_reg + 4'b1; 18 | counter_reg <= 20'b0; 19 | end else begin 20 | counter_reg <= counter_reg + 20'b1; 21 | end 22 | end 23 | end 24 | 25 | // combinatorial 26 | assign timer = timer_reg; 27 | 28 | endmodule -------------------------------------------------------------------------------- /src/examples/initial.vhdl: -------------------------------------------------------------------------------- 1 | library IEEE; 2 | use IEEE.STD_LOGIC_1164.ALL; 3 | 4 | entity initial_reg is 5 | Port ( clock : in STD_LOGIC; 6 | reset : in STD_LOGIC; 7 | d : in STD_LOGIC; 8 | q : out STD_LOGIC); 9 | end initial_reg; 10 | 11 | architecture behavior of initial_reg is 12 | signal r : STD_LOGIC := '0'; 13 | begin 14 | -- sequential 15 | process(clock) 16 | begin 17 | if rising_edge(clock) then 18 | if reset='1' then 19 | r <= '0'; 20 | else 21 | r <= d; 22 | end if; 23 | end if; 24 | end process; 25 | 26 | -- combinatorial 27 | q <= r; 28 | end behavior; -------------------------------------------------------------------------------- /src/synth_vhdl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | TEMP=$(cat /dev/urandom | base64 | env LC_CTYPE=C tr -cd 'a-f0-9' | head -c 32) 4 | OUTPUT_DOT=${1/.vhdl/_vhdl.dot} 5 | OUTPUT_PNG=${1/.vhdl/_vhdl.png} 6 | OUTPUT_SYN_V=${1/.vhdl/_vhdl.syn.v} 7 | sudo docker run -it --rm -t -v $PWD:/src -w /src hdlc/ghdl:yosys yosys -m ghdl -p "ghdl -fsynopsys -fexplicit $1 -e $2" -p synth -p "abc -g AND,OR,XOR,NAND,NOR" -p "clean" -p "select -module $2" -p "show -prefix $TEMP -format png" -p "write_verilog ${OUTPUT_SYN_V}" 8 | sudo chown -R ${USER}:${USER} . 9 | mv ${TEMP}.dot ${OUTPUT_DOT} 10 | rm -rf ${TEMP}.png 11 | python3 process.py ${OUTPUT_DOT} 12 | dot -Tpng ${OUTPUT_DOT} > ${OUTPUT_PNG} 13 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # 总述 2 | 3 | !!! danger "版权声明" 4 | 5 | 本项目为 2020 年春季学期起清华大学计算机系开设的《数字逻辑设计》课程的实验说明。 6 | **所有内容(包括文档、代码等)未经作者授权,禁止用作任何其他用途,包括且不限于在其他课程或者其他学校中使用。** 7 | 8 | 如需使用授权,可通过 shengqi dot chen at tuna dot tsinghua dot edu dot cn 联系作者。作者保留一切追究侵权责任的权利。 9 | 10 | ## 实验说明 11 | 12 | 本文档中的一些措辞定义如下: 13 | 14 | * **“可以”** 表示可能的选项,一般也有其他方案,可根据实际情况确定; 15 | * **”推荐“/”不要“** 表示推荐的最佳实践,可以不遵照执行,但可能会有不必要的麻烦; 16 | * **“应该”/”不应该“** 表示强烈的建议,如果不执行,很可能无法达到预期的效果; 17 | * **“必须”/”禁止“** 表示强制的做法,如果不严格执行,将导致设计无法完成。 18 | 19 | 20 | !!! info "及时咨询" 21 | 22 | 数字逻辑设计的很多概念与软件完全不同。无论你遇到什么问题(尤其是与实验板或者外设相关时),不要犹豫,及时咨询老师或者助教。 23 | 24 | ## 项目作者 25 | 26 | 陈晟祺(@Harry-Chen)、高一川(@gaoyichuan) 27 | -------------------------------------------------------------------------------- /src/examples/button_sv.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+41 (git sha1 29c0a5958, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | (* src = "examples/button.sv:1.1-13.10" *) 4 | module button(button, light); 5 | (* src = "examples/button.sv:7.3-9.6" *) 6 | wire _0_; 7 | (* src = "examples/button.sv:2.9-2.15" *) 8 | input button; 9 | wire button; 10 | (* src = "examples/button.sv:3.10-3.15" *) 11 | output light; 12 | wire light; 13 | (* src = "examples/button.sv:5.7-5.16" *) 14 | reg light_reg; 15 | assign _0_ = ~light_reg; 16 | (* \always_ff = 32'd1 *) 17 | (* src = "examples/button.sv:7.3-9.6" *) 18 | always @(posedge button) 19 | light_reg <= _0_; 20 | assign light = light_reg; 21 | endmodule 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Digital Design Documentation 2 | 3 | ![Build documentation with mkdocs](https://github.com/thu-cs-lab/Digital-Design-Docs/workflows/Build%20documentation%20with%20mkdocs/badge.svg) 4 | 5 | 本项目为数字逻辑实验课程的实验文档,采用 `mkdocs` 编写。 6 | 7 | 本站点的自动编译版本在 [这里](https://lab.cs.tsinghua.edu.cn/digital-design/doc/) 发布。 8 | 9 | ## 撰写 10 | 11 | 本站点内容使用 Markdown 进行编写。具体可查看 [mkdocs](https://www.mkdocs.org/) 和 [mkdocs-material](https://squidfunk.github.io/mkdocs-material/extensions/pymdown/) 文档。 12 | 13 | 如果创建了新页面,需要插入到 `mkdocs.yml` 的 `nav` 部分,否则将不会出现在编译结果中。 14 | 15 | ## 编译 16 | 17 | 首先安装依赖,而后编译即可: 18 | 19 | ```bash 20 | python3 -m pip install --user -r requirements.txt # 安装 Python 依赖包 21 | mkdocs serve # 直接在本地 serve,或者: 22 | mkdocs build --clean # 生成于 site/ 文件夹中 23 | ``` 24 | -------------------------------------------------------------------------------- /src/tests/sync_reset.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+41 (git sha1 29c0a5958, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | (* src = "tests/sync_reset.v:1.1-19.10" *) 4 | module sync_reset(clock, reset, d, q); 5 | (* src = "tests/sync_reset.v:2.9-2.14" *) 6 | input clock; 7 | wire clock; 8 | (* src = "tests/sync_reset.v:4.9-4.10" *) 9 | input d; 10 | wire d; 11 | (* src = "tests/sync_reset.v:5.10-5.11" *) 12 | output q; 13 | wire q; 14 | (* src = "tests/sync_reset.v:7.7-7.12" *) 15 | reg q_reg; 16 | (* src = "tests/sync_reset.v:3.9-3.14" *) 17 | input reset; 18 | wire reset; 19 | (* src = "tests/sync_reset.v:9.3-15.6" *) 20 | always @(posedge clock) 21 | if (reset) q_reg <= 1'h0; 22 | else q_reg <= d; 23 | assign q = q_reg; 24 | endmodule 25 | -------------------------------------------------------------------------------- /src/tests/add2.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+41 (git sha1 29c0a5958, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | (* top = 1 *) 4 | (* src = "tests/add2.v:1.1-14.10" *) 5 | module add2(a_0, a_1, b_0, b_1, c_0, c_1); 6 | (* src = "tests/add2.v:2.9-2.12" *) 7 | input a_0; 8 | wire a_0; 9 | (* src = "tests/add2.v:3.9-3.12" *) 10 | input a_1; 11 | wire a_1; 12 | (* src = "tests/add2.v:4.9-4.12" *) 13 | input b_0; 14 | wire b_0; 15 | (* src = "tests/add2.v:5.9-5.12" *) 16 | input b_1; 17 | wire b_1; 18 | (* src = "tests/add2.v:6.10-6.13" *) 19 | output c_0; 20 | wire c_0; 21 | (* src = "tests/add2.v:7.10-7.13" *) 22 | output c_1; 23 | wire c_1; 24 | assign { c_1, c_0 } = { a_1, a_0 } + (* src = "tests/add2.v:11.16-11.39" *) { b_1, b_0 }; 25 | endmodule 26 | -------------------------------------------------------------------------------- /src/tests/async_reset.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+41 (git sha1 29c0a5958, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | (* src = "tests/async_reset.v:1.1-19.10" *) 4 | module async_reset(clock, reset, d, q); 5 | (* src = "tests/async_reset.v:2.9-2.14" *) 6 | input clock; 7 | wire clock; 8 | (* src = "tests/async_reset.v:4.9-4.10" *) 9 | input d; 10 | wire d; 11 | (* src = "tests/async_reset.v:5.10-5.11" *) 12 | output q; 13 | wire q; 14 | (* src = "tests/async_reset.v:7.7-7.12" *) 15 | reg q_reg; 16 | (* src = "tests/async_reset.v:3.9-3.14" *) 17 | input reset; 18 | wire reset; 19 | (* src = "tests/async_reset.v:9.3-15.6" *) 20 | always @(posedge clock, posedge reset) 21 | if (reset) q_reg <= 1'h0; 22 | else q_reg <= d; 23 | assign q = q_reg; 24 | endmodule 25 | -------------------------------------------------------------------------------- /src/tests/sync_reset_n.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+41 (git sha1 29c0a5958, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | (* src = "tests/sync_reset_n.v:1.1-19.10" *) 4 | module sync_reset_n(clock, reset_n, d, q); 5 | (* src = "tests/sync_reset_n.v:2.9-2.14" *) 6 | input clock; 7 | wire clock; 8 | (* src = "tests/sync_reset_n.v:4.9-4.10" *) 9 | input d; 10 | wire d; 11 | (* src = "tests/sync_reset_n.v:5.10-5.11" *) 12 | output q; 13 | wire q; 14 | (* src = "tests/sync_reset_n.v:7.7-7.12" *) 15 | reg q_reg; 16 | (* src = "tests/sync_reset_n.v:3.9-3.16" *) 17 | input reset_n; 18 | wire reset_n; 19 | (* src = "tests/sync_reset_n.v:9.3-15.6" *) 20 | always @(posedge clock) 21 | if (!reset_n) q_reg <= 1'h0; 22 | else q_reg <= d; 23 | assign q = q_reg; 24 | endmodule 25 | -------------------------------------------------------------------------------- /src/examples/priority_encoder_vhdl.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+6 (git sha1 e0ba32423, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | module priority_encoder(request, valid, user); 4 | wire _00_; 5 | wire _01_; 6 | wire _02_; 7 | wire _03_; 8 | wire _04_; 9 | wire _05_; 10 | input [3:0] request; 11 | wire [3:0] request; 12 | output [1:0] user; 13 | wire [1:0] user; 14 | output valid; 15 | wire valid; 16 | assign _04_ = ~request[2]; 17 | assign _05_ = ~(request[2] | request[3]); 18 | assign _00_ = request[2] | request[3]; 19 | assign _01_ = ~(request[1] | request[0]); 20 | assign user[1] = _00_ & _01_; 21 | assign valid = ~(_05_ & _01_); 22 | assign _02_ = _04_ & request[3]; 23 | assign _03_ = ~(request[1] | _02_); 24 | assign user[0] = ~(request[0] | _03_); 25 | endmodule 26 | -------------------------------------------------------------------------------- /src/tests/async_reset_n.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+41 (git sha1 29c0a5958, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | (* src = "tests/async_reset_n.v:1.1-19.10" *) 4 | module async_reset_n(clock, reset_n, d, q); 5 | (* src = "tests/async_reset_n.v:2.9-2.14" *) 6 | input clock; 7 | wire clock; 8 | (* src = "tests/async_reset_n.v:4.9-4.10" *) 9 | input d; 10 | wire d; 11 | (* src = "tests/async_reset_n.v:5.10-5.11" *) 12 | output q; 13 | wire q; 14 | (* src = "tests/async_reset_n.v:7.7-7.12" *) 15 | reg q_reg; 16 | (* src = "tests/async_reset_n.v:3.9-3.16" *) 17 | input reset_n; 18 | wire reset_n; 19 | (* src = "tests/async_reset_n.v:9.3-15.6" *) 20 | always @(posedge clock, negedge reset_n) 21 | if (!reset_n) q_reg <= 1'h0; 22 | else q_reg <= d; 23 | assign q = q_reg; 24 | endmodule 25 | -------------------------------------------------------------------------------- /src/examples/priority_encoder.v: -------------------------------------------------------------------------------- 1 | module priority_encoder ( 2 | input [3:0] request, 3 | output valid, 4 | output [1:0] user 5 | ); 6 | reg valid_comb; 7 | reg [1:0] user_comb; 8 | 9 | always @ (*) begin 10 | // default 11 | valid_comb = 1'b0; 12 | user_comb = 2'd0; 13 | 14 | // cases 15 | casez (request) 16 | 4'b???1: begin 17 | valid_comb = 1'b1; 18 | user_comb = 2'd0; 19 | end 20 | 4'b??10: begin 21 | valid_comb = 1'b1; 22 | user_comb = 2'd1; 23 | end 24 | 4'b?100: begin 25 | valid_comb = 1'b1; 26 | user_comb = 2'd2; 27 | end 28 | 4'b1000: begin 29 | valid_comb = 1'b1; 30 | user_comb = 2'd3; 31 | end 32 | endcase 33 | 34 | end 35 | 36 | assign valid = valid_comb; 37 | assign user = user_comb; 38 | endmodule -------------------------------------------------------------------------------- /src/examples/button.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+41 (git sha1 29c0a5958, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | (* top = 1 *) 4 | (* src = "examples/button.v:1.1-13.10" *) 5 | module button(button, light); 6 | (* src = "examples/button.v:7.3-9.6" *) 7 | wire _0_; 8 | (* src = "examples/button.v:7.3-9.6" *) 9 | wire _1_; 10 | (* src = "examples/button.v:5.7-5.16" *) 11 | wire _2_; 12 | (* src = "examples/button.v:2.9-2.15" *) 13 | input button; 14 | wire button; 15 | (* src = "examples/button.v:3.10-3.15" *) 16 | output light; 17 | wire light; 18 | (* src = "examples/button.v:5.7-5.16" *) 19 | reg light_reg; 20 | assign _1_ = ~_2_; 21 | (* src = "examples/button.v:7.3-9.6" *) 22 | always @(posedge button) 23 | light_reg <= _0_; 24 | assign light = light_reg; 25 | assign _2_ = light_reg; 26 | assign _0_ = _1_; 27 | endmodule 28 | -------------------------------------------------------------------------------- /src/examples/priority_encoder.vhdl: -------------------------------------------------------------------------------- 1 | library IEEE; 2 | use IEEE.STD_LOGIC_1164.ALL; 3 | use IEEE.STD_LOGIC_ARITH.ALL; 4 | use IEEE.STD_LOGIC_UNSIGNED.ALL; 5 | 6 | entity priority_encoder is 7 | Port ( request : in STD_LOGIC_VECTOR (3 downto 0); 8 | valid : out STD_LOGIC; 9 | user : out STD_LOGIC_VECTOR (1 downto 0)); 10 | end priority_encoder; 11 | 12 | architecture behavior of priority_encoder is 13 | begin 14 | process (request) begin 15 | -- default 16 | valid <= '0'; 17 | user <= "00"; 18 | 19 | -- cases 20 | if request(0)='1' then 21 | valid <= '1'; 22 | user <= "00"; 23 | elsif request(1)='1' then 24 | valid <= '1'; 25 | user <= "01"; 26 | elsif request(2)='1' then 27 | valid <= '1'; 28 | user <= "10"; 29 | elsif request(3)='1' then 30 | valid <= '1'; 31 | user <= "11"; 32 | end if; 33 | end process; 34 | end behavior; -------------------------------------------------------------------------------- /src/examples/timer.vhdl: -------------------------------------------------------------------------------- 1 | library IEEE; 2 | use IEEE.STD_LOGIC_1164.ALL; 3 | use IEEE.STD_LOGIC_ARITH.ALL; 4 | use IEEE.STD_LOGIC_UNSIGNED.ALL; 5 | 6 | entity timer is 7 | Port ( clock : in STD_LOGIC; 8 | reset : in STD_LOGIC; 9 | timer : out STD_LOGIC_VECTOR (3 downto 0)); 10 | end timer; 11 | 12 | architecture behavior of timer is 13 | signal timer_reg : STD_LOGIC_VECTOR (3 downto 0); 14 | signal counter_reg : STD_LOGIC_VECTOR (19 downto 0); 15 | begin 16 | -- sequential 17 | process(clock, reset) 18 | begin 19 | if clock='1' and clock'event then 20 | if reset='1' then 21 | timer_reg <= X"0"; 22 | counter_reg <= X"00000"; 23 | else 24 | if counter_reg=999_999 then 25 | timer_reg <= timer_reg + 1; 26 | counter_reg <= X"00000"; 27 | else 28 | counter_reg <= counter_reg + 1; 29 | end if; 30 | end if; 31 | end if; 32 | end process; 33 | 34 | -- combinatorial 35 | timer <= timer_reg; 36 | end behavior; -------------------------------------------------------------------------------- /src/tests/fsm.sv: -------------------------------------------------------------------------------- 1 | module fsm ( 2 | input clock, 3 | input reset, 4 | input STB_I, 5 | input CYC_I, 6 | input WE_I, 7 | output [2:0] out 8 | ); 9 | 10 | typedef enum logic [2:0] { 11 | STATE_IDLE = 0, 12 | STATE_READ = 1, 13 | STATE_READ_2 = 2, 14 | STATE_WRITE = 3, 15 | STATE_WRITE_2 = 4, 16 | STATE_WRITE_3 = 5, 17 | STATE_DONE = 6 18 | } state_t; 19 | 20 | state_t state; 21 | 22 | always_ff @ (posedge clock) begin 23 | if (reset) begin 24 | state <= STATE_IDLE; 25 | end else begin 26 | case (state) 27 | STATE_IDLE: begin 28 | if (STB_I && CYC_I) begin 29 | if (WE_I) begin 30 | state <= STATE_WRITE; 31 | end else begin 32 | state <= STATE_READ; 33 | end 34 | end 35 | end 36 | STATE_READ: begin 37 | state <= STATE_READ_2; 38 | end 39 | // ... 40 | endcase 41 | end 42 | end 43 | 44 | assign out = state; 45 | 46 | endmodule -------------------------------------------------------------------------------- /src/examples/adder.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+41 (git sha1 29c0a5958, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | (* top = 1 *) 4 | (* src = "examples/adder.v:1.1-7.10" *) 5 | module add2(a, b, c); 6 | wire _00_; 7 | wire _01_; 8 | (* src = "examples/adder.v:2.15-2.16" *) 9 | wire _02_; 10 | (* src = "examples/adder.v:2.15-2.16" *) 11 | wire _03_; 12 | (* src = "examples/adder.v:3.15-3.16" *) 13 | wire _04_; 14 | (* src = "examples/adder.v:3.15-3.16" *) 15 | wire _05_; 16 | (* src = "examples/adder.v:4.16-4.17" *) 17 | wire _06_; 18 | (* src = "examples/adder.v:4.16-4.17" *) 19 | wire _07_; 20 | wire _08_; 21 | wire _09_; 22 | (* src = "examples/adder.v:2.15-2.16" *) 23 | input [1:0] a; 24 | wire [1:0] a; 25 | (* src = "examples/adder.v:3.15-3.16" *) 26 | input [1:0] b; 27 | wire [1:0] b; 28 | (* src = "examples/adder.v:4.16-4.17" *) 29 | output [1:0] c; 30 | wire [1:0] c; 31 | assign _08_ = _04_ & _02_; 32 | assign _09_ = _05_ ^ _03_; 33 | assign _07_ = _08_ ^ _09_; 34 | assign _06_ = _04_ ^ _02_; 35 | assign _05_ = b[1]; 36 | assign _03_ = a[1]; 37 | assign _04_ = b[0]; 38 | assign _02_ = a[0]; 39 | assign c[1] = _07_; 40 | assign c[0] = _06_; 41 | endmodule 42 | -------------------------------------------------------------------------------- /docs/hdl-by-example/labs.md: -------------------------------------------------------------------------------- 1 | # 实验解析 2 | 3 | 接下来,让我们用刚学习的知识来分析实验的设计。 4 | 5 | ## 点亮数字人生 6 | 7 | 题目要求显示三个数列:从 0 到 9、从 1 到 9 的奇数数列和从 0 到 8 的偶数数列。 8 | 9 | 这个实验主要是考察组合逻辑和时序逻辑的应用。 10 | 11 | 由于这里的数列显示输出是内部状态,所以应该用寄存器来分别保存三个数列的当前取值,也就是用 **时序逻辑** 。在时序逻辑中,所有的更新都应该在时钟上升沿。在上升沿的时候,如果数列要取下一项,就根据当前的结果,计算出下一个结果。为了解决溢出的问题,可以用 `if` 来实现。 12 | 13 | 最后,为了要输出到不带译码的数码管上,要在 CPLD 内部实现译码。译码的输出完全取决于输出,因此是 **组合逻辑** 。考虑到这里的输入情况从 0 到 9 比较多,可以用 `case` 语句来实现。在组合逻辑中,所有的情况下输出信号都要有取值,因此不要忘记 `case` 的 `default` 情况。 14 | 15 | ## 四位加法器设计 16 | 17 | 题目要求实现一个四位的加法器。 18 | 19 | 这个实验主要是考察组合逻辑和模块化编程的应用。 20 | 21 | 本题比较特殊,要求同学例化半加器来实现全加器,再用全加器来实现一个四位的加法器。在这里,半加器和全加器就是子模块,因此我们要先编写子模块,再用子模块实现最后的四位加法器设计。 22 | 23 | 这个题目里,输出完全由输入决定,因此是 **组合逻辑** 。半加器比较简单,按照真值表可以推导出表达式。把两个半加器拼起来,就可以得到全加器。把四个全加器拼接起来,就可以得到四位加法器。 24 | 25 | ## 计数器设计 26 | 27 | 题目要求显示两位数的计数器。 28 | 29 | 这个实验主要是考察组合逻辑和时序逻辑的应用。 30 | 31 | 遇到计数的问题,首先要考虑到 **时序逻辑** ,因为需要保存内部状态。第一个想法是直接用一组寄存器来保存整个计数,然后输出的时候,再使用 **组合逻辑**,采用除法和取模的方式把十位数和个位数取出来。但是在硬件上,我们要避免除法和取模的操作,因为其面积消耗比较大,延迟也比较大。 32 | 33 | 考虑到这里每次计数器只会加一,那么可以把个位数和十位数分别存储,当个位数要溢出的时候,再对十位数加一。这样就免去了除法和取模的操作。 34 | 35 | 最后,输出的时候,要将数字译码为数码管的输出,这时候就可以用前面实验编写好的译码器。这就是模块化设计的好处。 36 | 37 | ## 串行密码锁 38 | 39 | 题目要求实现一个密码锁。 40 | 41 | 这个实验主要是考察组合逻辑、时序逻辑和状态机的应用。 42 | 43 | 首先要分析,内部需要存储哪些东西,这样就知道哪些是 **时序逻辑**,哪些是 **组合逻辑**。首先,肯定要保存密码,还有当前正在输入的密码,由于密码输入是一位一位输入的,还需要记录当前输入密码的进度,还有目前是解锁还是失败的状态。具体实现的时候,可以用状态机来记录当前输入密码的进度,例如正在输入的是第一位密码、正在输入的是第二位密码、密码错误、成功解锁等。 44 | 45 | 进一步思考,这里的输出比较简单,因此除了寄存器之间的组合逻辑,没有额外的从寄存器到输出、从输入到输出的组合逻辑。所以如果你发现写了很多逻辑,却没有出现时钟的时候,可能就说明实现出了错误。 46 | 47 | 最后,这个题目要求在复位的时候,根据不同的输入设定不同的复位值,这个功能在编写的时候,在 FPGA 上可能会因为复位不是常数而产生锁存器(latch)。这个问题可以通过换一种写法来解决。 -------------------------------------------------------------------------------- /docs/hdl-by-example/imgs/counter.drawio: -------------------------------------------------------------------------------- 1 | 7Vpbk6I6EP41PmoB4fq4o7O75+w5VVtn9vq0FSEj7CDhhDjq/voNknBJVNQBdaydhynShCbp/vpLd8sAjOerdwSm4b84QPHA0ILVAEwGhmG4DvufC9aFQHfcQjAjUcBFleAh+oW4UOPSRRSgrDGRYhzTKG0KfZwkyKcNGSQEL5vTHnHcfGsKZ0gRPPgwVqVfo4CGXKprWnXjPYpmoXi1I+7MoZjNBVkIA7ysicD9AIwJxrS4mq/GKM6NJwxTPPd2x91yZQQl9JAHDDL8/nlu/Qx+TePPT39pAZj+P+RanmG84DueoCleJD4ifNV0LWxBmDxAuTZtAO6WYUTRQwr9/O6SOZ/JQjqP2Uhnl+rqxKsQoWhVE/HVvkN4jihZsykleLjlBHaEJZc1TwhZWHeCEELu/Vmpu7IPu+Am2m6uD2FCf5pfPiTvs3V6//f0v7cUbTHXmBmFdmMsxTJb7Hewscrx5YxlKMZSrISS4E0epWzkxzDLIr9pmKYVmSnI+hsbDLWRpgEh+Z7fHumOGE9W9fmTdX30EZGI7Y45rBBmFBIqlpDgBJXeQIHCDAqWM7wgPtpjAotTFiQzRNvCUPVtzXfWFtcJGUExpNFzc7nb3Mnf8BFHbCMVdDQpzkwJEcU2+VN1hpEUmZ6kyJEUFXZQFG3QVW77dMCZ/QLOlQBn2a8DcbuZ7GKIM7tCnATdEsotiGP2h+vatDSfkB2+YMPzJAAXGjuFs30UnDmU9mP5GKAeDspWsFk3ATbD8UYa8Ko/W8KEO3IYAMWfbr4Qil3To9WOJ5ajppsUBa4368j56glRP+SYeIzi+IFPz3Ccv+4uK5L2nAw3zyBy/4zyLRQo5Pth6q27gTXhSsY4xqRCLUFMC5xu9Gob6FJIa+O0hk0f5e+oofUuS2D6CReW7S6hMtqzT3MLamV0dZZOOX/ooAs60OWq4iV0YJuyrlGdIDzjrPmPqyDEj7H/pMKEOfEfOGWVegMgMI5mSRlgTJDHT8Rq4Tf8xjwKgnhbuKoxvjMAeaHOHx6UUVbH0W7474zWYZ6PmW4nCBFEyZ061CWA4MfHDPXiQO+oED8mgS0HRe5qtTDAacF+faWNEuxuV4mmDIqeQ1tXOw8sCDkKX31se22xbVoW6Ca2nUvFtt5fO+QswX2FVaScoZ07uI+uIqUFA7PRse2nitRBO+7+pP27m2H2hdN+vb+ulsQbTlfEcS2nvaF7pxGC0kyXFfV92qul+nRBKU5u47gvIL3vvLeB3k0ub1zsuD+ue3dI2K4i+o3fya9rhz0bVSGbD44/6uuN5evL44ElBaR9atEuRTaQc4aOjnpTbjq62t51yfPFBnfuw37ZfOCeI/VQW1YFjf0I+I/MwY0Qmt1GaLptSd3iTuJiaAKJ4KSOU48Ep3abeiE4ow+GO6aY4abWRpoh9iyCWk0NXwMHylwDDix3KkVnQJfaCmMlQ3YjdOG20YVmAf1loCkJQXqkP5eJ7XdPCNrI85wGKdi2u5cV8sHpP2C8dqYwdSnA5U98TmYKmXKugCkMtb9BUXIjTFH7UHAH0izD7IgoOiiN2LD6nrGYXn0VCu5/Aw== -------------------------------------------------------------------------------- /docs/hdl/reset.md: -------------------------------------------------------------------------------- 1 | # 复位相关 2 | 3 | 设计中通常需要复位信号,用于重置电路状态。本页是关于复位的一些注意事项。 4 | 5 | !!! warning "尽量不要使用异步复位" 6 | 7 | Vivado Design Suite User Guide: Synthesis (UG901) 的第四章 HDL Coding Techniques 的 Coding Guidelines 部分指出: 8 | 9 | Do not asynchronously set or reset registers: 10 | 11 | * Control set remapping becomes impossible. 12 | * Sequential functionality in device resources such as block RAM components and DSP blocks can be set or reset synchronously only. 13 | * If you use asynchronously set or reset registers, you cannot leverage device resources, or those resources are configured sub-optimally. 14 | 15 | 考虑到 FPGA 复位大多来源于按钮触发,时间足够长,请遵守官方的指导,尽量只使用同步复位。 16 | 17 | ## 信号极性 18 | 19 | 复位信号通常分为低有效和高有效,为了防止混淆,通常命名为 `rst_n` 和 `rst`。 20 | 21 | ## 同步与异步 22 | 23 | 最常用的复位方式分为两种,即同步复位和异步复位,代码如下: 24 | 25 | ```verilog 26 | reg [3:0] counter; 27 | // synchronous reset 28 | always @(posedge clk) begin 29 | // asynchronous reset 30 | always @(posedge clk, posedge rst) begin 31 | if (rst) begin 32 | counter <= 0; 33 | end else begin 34 | counter <= counter + 1'b1; 35 | end 36 | end 37 | ``` 38 | 39 | 其语法区别在于 `rst` 是否在 `always` 的敏感信号列表中,而表现差异是复位信号是否立即生效/失效。 40 | 在 Xilinx FPGA 上,这两种不同的语义会被使用不同的寄存器原语表达(同步使用 FDRE、异步使用 FDCE),在逻辑复杂度上没有区别。 41 | 42 | 由于复位的异步释放可能会导致亚稳态,而同步复位又可能无法采样到短暂的复位信号,因此通常使用的复位方式是两者的结合,被称为“异步复位,同步释放”。代码如下: 43 | 44 | ```verilog 45 | reg [1:0] rst_sync; 46 | wire rst_synced; 47 | 48 | // use rst_synced as asynchronous reset of all modules 49 | assign rst_synced = rst_sync[1]; 50 | 51 | always @(posedge clk, posedge rst) begin 52 | if (rst) begin 53 | rst_sync <= 2'b11; 54 | end else begin 55 | rst_sync <= {rst_sync[0], rst}; 56 | end 57 | end 58 | ``` 59 | 60 | 事实上,这种做法就是将 `rst` 的释放延迟两周期采样,与跨时钟域的同步原理相同(采样信号也可以视作来自外部时钟域)。我们推荐在需要异步复位时,**只** 采用这种形式。 61 | 62 | ??? question "`rst_synced` 应该用做其他需要复位模块的同步还是异步信号?" 63 | 64 | 异步复位,否则就无法做到立刻捕获复位信号,同步释放也失去意义了。 65 | 66 | -------------------------------------------------------------------------------- /src/examples/timer.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+41 (git sha1 29c0a5958, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | (* top = 1 *) 4 | (* src = "examples/timer.v:1.1-28.10" *) 5 | module timer(clock, reset, timer); 6 | (* src = "examples/timer.v:11.3-23.6" *) 7 | wire [19:0] _00_; 8 | (* src = "examples/timer.v:11.3-23.6" *) 9 | wire [3:0] _01_; 10 | (* src = "examples/timer.v:17.22-17.38" *) 11 | wire [3:0] _02_; 12 | (* src = "examples/timer.v:20.24-20.43" *) 13 | wire [19:0] _03_; 14 | (* src = "examples/timer.v:16.11-16.36" *) 15 | wire _04_; 16 | wire [3:0] _05_; 17 | wire [19:0] _06_; 18 | (* src = "examples/timer.v:2.9-2.14" *) 19 | input clock; 20 | wire clock; 21 | (* src = "examples/timer.v:8.14-8.25" *) 22 | reg [19:0] counter_reg; 23 | (* src = "examples/timer.v:3.9-3.14" *) 24 | input reset; 25 | wire reset; 26 | (* src = "examples/timer.v:4.16-4.21" *) 27 | output [3:0] timer; 28 | wire [3:0] timer; 29 | (* src = "examples/timer.v:7.13-7.22" *) 30 | reg [3:0] timer_reg; 31 | assign _02_ = timer_reg + (* src = "examples/timer.v:17.22-17.38" *) 1'h1; 32 | assign _03_ = counter_reg + (* src = "examples/timer.v:20.24-20.43" *) 1'h1; 33 | assign _04_ = counter_reg == (* src = "examples/timer.v:16.11-16.36" *) 20'hf423f; 34 | (* src = "examples/timer.v:11.3-23.6" *) 35 | always @(posedge clock) 36 | timer_reg <= _01_; 37 | (* src = "examples/timer.v:11.3-23.6" *) 38 | always @(posedge clock) 39 | counter_reg <= _00_; 40 | assign _05_ = _04_ ? (* full_case = 32'd1 *) (* src = "examples/timer.v:16.11-16.36|examples/timer.v:16.7-21.10" *) _02_ : timer_reg; 41 | assign _01_ = reset ? (* full_case = 32'd1 *) (* src = "examples/timer.v:12.9-12.14|examples/timer.v:12.5-22.8" *) 4'h0 : _05_; 42 | assign _06_ = _04_ ? (* full_case = 32'd1 *) (* src = "examples/timer.v:16.11-16.36|examples/timer.v:16.7-21.10" *) 20'h00000 : _03_; 43 | assign _00_ = reset ? (* full_case = 32'd1 *) (* src = "examples/timer.v:12.9-12.14|examples/timer.v:12.5-22.8" *) 20'h00000 : _06_; 44 | assign timer = timer_reg; 45 | endmodule 46 | -------------------------------------------------------------------------------- /src/tests/fsm_sv.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+41 (git sha1 29c0a5958, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | (* src = "tests/fsm.sv:1.1-46.10" *) 4 | module fsm(clock, reset, STB_I, CYC_I, WE_I, out); 5 | wire _00_; 6 | wire _01_; 7 | wire _02_; 8 | wire _03_; 9 | wire _04_; 10 | wire _05_; 11 | wire _06_; 12 | (* src = "tests/fsm.sv:5.9-5.14" *) 13 | input CYC_I; 14 | wire CYC_I; 15 | (* src = "tests/fsm.sv:4.9-4.14" *) 16 | input STB_I; 17 | wire STB_I; 18 | (* src = "tests/fsm.sv:6.9-6.13" *) 19 | input WE_I; 20 | wire WE_I; 21 | (* src = "tests/fsm.sv:2.9-2.14" *) 22 | input clock; 23 | wire clock; 24 | (* src = "tests/fsm.sv:7.16-7.19" *) 25 | output [2:0] out; 26 | wire [2:0] out; 27 | (* src = "tests/fsm.sv:3.9-3.14" *) 28 | input reset; 29 | wire reset; 30 | (* enum_value_000 = "\\STATE_IDLE" *) 31 | (* enum_value_001 = "\\STATE_READ" *) 32 | (* enum_value_010 = "\\STATE_READ_2" *) 33 | (* enum_value_011 = "\\STATE_WRITE" *) 34 | (* enum_value_100 = "\\STATE_WRITE_2" *) 35 | (* enum_value_101 = "\\STATE_WRITE_3" *) 36 | (* enum_value_110 = "\\STATE_DONE" *) 37 | (* src = "tests/fsm.sv:20.11-20.16" *) 38 | (* wiretype = "\\state_t" *) 39 | wire [2:0] state; 40 | assign _06_ = ~state[1]; 41 | assign _03_ = ~WE_I; 42 | assign _01_ = ~(_06_ & state[0]); 43 | assign _04_ = _06_ & CYC_I; 44 | assign _05_ = ~(STB_I & _04_); 45 | assign _00_ = ~(_01_ & _05_); 46 | assign _02_ = ~(_03_ & _01_); 47 | reg \state_reg[0] ; 48 | (* \always_ff = 32'd1 *) 49 | (* src = "tests/fsm.sv:22.3-42.6" *) 50 | always @(posedge clock) 51 | if (reset) \state_reg[0] <= 1'h0; 52 | else if (_00_) \state_reg[0] <= _01_; 53 | assign state[0] = \state_reg[0] ; 54 | reg \state_reg[1] ; 55 | (* \always_ff = 32'd1 *) 56 | (* src = "tests/fsm.sv:22.3-42.6" *) 57 | always @(posedge clock) 58 | if (reset) \state_reg[1] <= 1'h0; 59 | else if (_00_) \state_reg[1] <= _02_; 60 | assign state[1] = \state_reg[1] ; 61 | assign out = { 1'h0, state[1:0] }; 62 | assign state[2] = 1'h0; 63 | endmodule 64 | -------------------------------------------------------------------------------- /src/examples/priority_encoder.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+41 (git sha1 29c0a5958, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | (* top = 1 *) 4 | (* src = "examples/priority_encoder.v:1.1-38.10" *) 5 | module priority_encoder(request, valid, user); 6 | wire _00_; 7 | wire _01_; 8 | wire _02_; 9 | wire _03_; 10 | wire _04_; 11 | wire _05_; 12 | wire _06_; 13 | wire _07_; 14 | wire _08_; 15 | wire _09_; 16 | wire _10_; 17 | wire _11_; 18 | wire _12_; 19 | wire _13_; 20 | wire _14_; 21 | wire _15_; 22 | (* src = "examples/priority_encoder.v:2.15-2.22" *) 23 | wire _16_; 24 | (* src = "examples/priority_encoder.v:2.15-2.22" *) 25 | wire _17_; 26 | (* src = "examples/priority_encoder.v:2.15-2.22" *) 27 | wire _18_; 28 | (* src = "examples/priority_encoder.v:2.15-2.22" *) 29 | wire _19_; 30 | (* src = "examples/priority_encoder.v:4.16-4.20" *) 31 | wire _20_; 32 | (* src = "examples/priority_encoder.v:4.16-4.20" *) 33 | wire _21_; 34 | (* src = "examples/priority_encoder.v:3.10-3.15" *) 35 | wire _22_; 36 | (* src = "examples/priority_encoder.v:2.15-2.22" *) 37 | input [3:0] request; 38 | wire [3:0] request; 39 | (* src = "examples/priority_encoder.v:4.16-4.20" *) 40 | output [1:0] user; 41 | wire [1:0] user; 42 | (* src = "examples/priority_encoder.v:7.13-7.22" *) 43 | wire [1:0] user_comb; 44 | (* src = "examples/priority_encoder.v:3.10-3.15" *) 45 | output valid; 46 | wire valid; 47 | (* src = "examples/priority_encoder.v:6.7-6.17" *) 48 | wire valid_comb; 49 | assign _14_ = ~_18_; 50 | assign _15_ = _19_ & _14_; 51 | assign _10_ = ~(_17_ | _15_); 52 | assign _20_ = ~(_16_ | _10_); 53 | assign _11_ = ~(_16_ | _17_); 54 | assign _12_ = ~(_19_ | _18_); 55 | assign _13_ = _19_ | _18_; 56 | assign _21_ = _11_ & _13_; 57 | assign _22_ = ~(_11_ & _12_); 58 | assign user_comb = user; 59 | assign valid_comb = valid; 60 | assign user[0] = _20_; 61 | assign _16_ = request[0]; 62 | assign _17_ = request[1]; 63 | assign user[1] = _21_; 64 | assign _19_ = request[3]; 65 | assign _18_ = request[2]; 66 | assign valid = _22_; 67 | endmodule 68 | -------------------------------------------------------------------------------- /docs/hdl-by-example/imgs/timer.drawio: -------------------------------------------------------------------------------- 1 | 7Vvfc6M2EP5rPNM+JKMfQOzH2MnddeY6k056095TRzaKzQUjF5TEzl9fCSQMkm2wDTbuJQ8eWK0W2P20+rRSeng0X36OyWL2O/Np2EPAX/bwXQ+hgeOJXylYZQK3388E0zjwMxFcCx6Dd6qEQElfAp8mJUXOWMiDRVk4YVFEJ7wkI3HM3spqTywsP3VBptQSPE5IaEv/Cnw+U1IIwLrhCw2mM/3oG90yJ1pbCZIZ8dlbQYTve3gUM8azq/lyREPpPO2YrN+nLa35m8U04nU6oPjq+7e5+8N/H4ffnn8DPh7/e6WsvJLwRX3xhL1EnMb/xHSq3puvtDdi0eZTaQ/08PBtFnD6uCAT2fomwi9kMz4PxR0Ul09BGI5YyGJxH7FIKA1DMqbhA0sCHrBIiCdUPko0vNKYB8LrXw0FzqRREgbTjeq3qmHMOGdz0WD7RH+g6EGXBZHy0WfK5pTHK6GiWpGj4qUQC3X83grx17JZMfRaSBTmprntdVTEhQrMHkFCVpDuGg/NiTznbHCc05bfsOW30SX4DYEz+w26lpuoL9KiumUxn7Epi0h4v5YOy45c63xlcgyn7vtBOV+pHE9eOCs7ly4D/nfh+rs0de2qu7ulspzerPRNJL5XdroC1wC4WpJ1hZ6nBeve6V2p+wONA+E3mVhS4daIJuwlntAdfnPUBEXiKeW7/KsUpVN3AiSmIeHBa3kuajzcjjVM/ujiMMGoY+nFtfxmD5vIv5UsRE5eIUmSYFL2VNmtOZpBGchuBYy3OrcSibgmEAsudje4WMtq41U94YEF4o3zCPeNPGhOqNkAVJ2KTMewgweGIdcwlPnFMpSiIP/qw4Hh2aQqZJPnjUk1ZTxlUFTSnXng+1nOpUnwTsapPQmEhfyi9BvdYc+92wiNnVA2B2NOrNVDekXqummQyjyM3EHJ/ZpkHoqO3IzRhT09JbSVAN5YAeRiivjgxPmA6p+dE/etEF0EJ97kuZNOWgPLbxfBibF3bk4MLMddBEs6O+CgXWKwJ8KO0yQ1aDpCkyBuiieZhm7q8SQRK7IqqCnaUfuFcb9UqRIXmcVG53BoV02Oxd3WZWgiHMW1paqxXLmMRG6n4IahEb1D4eYZ/B6D09JyaNeDUlpnw6IzvDwH8dHEHFw7GA6ayT8mvTfC2B4v10++oMJUU3NUZdaAdatPqGZ22Q8l+84JnldOBrA8J1Trg936yKwW7avvnGKOsktvInXIsSOw8KmHvFAEcjgWGcqbyiuQNtyK319K2zRA9pXdwSD90/3BuoOhjwTSAPzVGk4dYLBOjW2EvER+msUmtAsCx3KJA/NDs0wDo5o5o3bF+iyFwv6h/NfIKdYiqCH+ize/79bX6u9Mje2kIj12WktFhQqaTj+5CXARiQjXTUQmHpvbCD7ZWhq1t5jWXuxILnGAEWVzVVI3mzjmarqlbGK98CmYigba+WfA/w/wtjDO7gLPeGE9fbYLPHvR3hLwbn4a5G0jGJ1FnvnCJykgIntxdizyLqKQoMFaiep+t0A9aGoeN7fsap4eOLYCYqTTSn1UoW9WNPbVx+AUg6zGIZpkRhYp7yer1KdyGfxM+WSmoC7Zvy4BJiyUzxsmWTHP08VXGt+/0qwGCzcWZDcsIcwqbsw44cWqbuHgWFYTLhwlGyYRWfzJMh9nD906JPfY3TOPyJx7dw/ZZ112JUnl2UYz5OGZDtbNdF3bkTEzx6Gp7tw7Msg+xfEx+ncUJIyV39lPQOr64Uf86mVvcw/j3Ida8H6Lq8rs3c4ZVdSp5NtQ7dna5TANnaT23A6pw1u3tUxsdWajPR8JTZyAhdCF5Xm1EeRdQVw2q8+pHrPxLm7X/4KWqa//kQ/f/wc= -------------------------------------------------------------------------------- /docs/hdl/debug.md: -------------------------------------------------------------------------------- 1 | # 调试相关 2 | 3 | ## ILA 集成逻辑分析仪 4 | 5 | Xilinx 的 FPGA,支持在 FPGA 内部进行“调试”:实际上,就是在 FPGA 内部内嵌一个逻辑分析仪,不断地对内部信号进行采样,保存下来,传输到电脑上进行展示。这个功能,在 Vivado 中叫做 ILA(Integrated Logic Analyzer)。 6 | 7 | 为了让 Vivado 插入集成逻辑分析仪,需要进行如下步骤: 8 | 9 | 1. (可选)修改代码,在想要调试的信号上添加 (* mark_debug = "true" *) 标记 10 | 2. 点击 Run Synthesis 进行综合 11 | 3. 综合完成后然后点击 Open Synthesized Design 12 | 4. 点击 Setup Debug,Vivado 会显示你已经配置了 ILA 调试或者标记了 mark_debug 的信号 13 | 5. 从中选择要观察的信号,如果没有 mark_debug,也可以手动搜索并添加信号,注意信号综合后,名称可能和源码不完全一致 14 | 6. 给每个要观察的信号,设置采样的时钟域,建议选择该信号所在时钟域的时钟 15 | 7. 按照提示完成配置,完成配置后,点击保存(非常重要,不要忘记) 16 | 8. 如果是第一次配置 ILA,Vivado 会提示保存的文件名,建议选择保存到新文件 debug.xdc,防止污染 io.xdc 的内容 17 | 9. 观察 debug.xdc 的内容,确认出现了自己配置 ILA 的相关信号的名称 18 | 10. 重新生成 Bitstream 19 | 11. Program Device 后,即可在打开的 Hardware Manager 中找到 ILA 界面,观察信号的状态 20 | 21 | 对于 ILA 界面的使用方式,建议阅读 [Vivado Design Suite User Guide](https://www.xilinx.com/support/documents/sw_manuals/xilinx2022_1/ug908-vivado-programming-debugging.pdf) 第 11 章。 22 | 23 | !!! note "注意时钟" 24 | 25 | 因为逻辑分析仪的原理是用指定的时钟不断地采样 FPGA 内部信号,所以在使用 ILA 采样信号时,被采样的信号与所使用的时钟要对应,因此通常情况下不同的时钟域会使用不同的时钟,也就可能需要创建多个集成逻辑分析仪实例。 26 | 27 | 在 ILA 中,可以从多个来源选择要采样的信号。通常,`Pre-Synthesis` 中的结构保留最为完整,而 `Post-Fit` 中已经是优化后的网表,可能丢失部分信号(或者名称被修改)。 28 | 29 | 如果 ILA 始终无法找到/采样某些信号,可以考虑将对应信号标记为 `dont_touch` 以防止 EDA 工具优化。不同语言中用法如下: 30 | 31 | === "Verilog / SystemVerilog" 32 | 33 | ```verilog 34 | (* dont_touch = "true" *) wire sig1; 35 | assign sig1 = in1 & in2; 36 | assign out1 = sig1 & in2; 37 | ``` 38 | 39 | === "VHDL" 40 | 41 | ```vhdl 42 | signal sig1 : std_logic 43 | attribute dont_touch : string; 44 | attribute dont_touch of sig1 : signal is "true"; 45 | .... 46 | .... 47 | sig1 <= in1 and in2; 48 | out1 <= sig1 and in3; 49 | ``` 50 | 51 | === "Chisel" 52 | 53 | ```scala 54 | import chisel3.dontTouch 55 | class MyModule extends Module { 56 | val io = IO(new Bundle { 57 | val in = Input(UInt(8.W)) 58 | val out = Output(UInt(8.W)) 59 | }) 60 | val wire = dontTouch(Wire(UInt(8.W))) 61 | wire := io.in 62 | io.out := wire 63 | } 64 | ``` 65 | 66 | 67 | !!! warning "不要滥用" 68 | 69 | `dont_touch` 会导致综合器放弃大量优化。如非必要,不要轻易使用。 70 | 71 | -------------------------------------------------------------------------------- /docs/hdl/bus.md: -------------------------------------------------------------------------------- 1 | # 总线协议 2 | 3 | 本节介绍了开发过程中可能使用的总线协议。总线协议用于仲裁多个主从设备的读写请求,通常分为内存映射协议与流式传输协议。 4 | 5 | ## 握手信号 6 | 7 | 总线协议通常使用 ready / valid 信号进行握手。在使用时,需要严格遵守相应文档中的说明,否则可能产生死锁等不正确状态。一些参考说明可见: 8 | 9 | * 10 | * 11 | * 12 | * 13 | * 14 | 15 | 16 | ## 内存映射协议 17 | 18 | 内存映射协议(memory-mapped protocol)用于可寻址的数据传输,通常用于 CPU 等复杂设备。其传输的单位是“事务”(transaction),通常有数据、地址、响应等多个通道(channel),在传输前需要进行协商,包括方向(读/写)、地址、数据大小、传输模式(单次、突发、回绕)等。 19 | 20 | 由 ARM 牵头定制的 AMBA (Advanced Microcontroller Bus Architecture) 规范中包含了名为 AXI 和 AXI-Lite 的内存映射协议,被 Xilinx 等平台广泛使用。其文档可见 [Xilinx UG761](https://www.xilinx.com/support/documentation/ip_documentation/ug761_axi_reference_guide.pdf),以及 [AMBA 规范](https://developer.arm.com/documentation/ihi0022/hc) 的 Part A/B。 21 | 22 | 在一些开源设计中,例如 Opencores 网站上的 IP,通常会使用 Wishbone 内存映射协议,它相对上面的两种协议来说比较简单,可以阅读 [计算机组成原理实验 4: Wishbone 总线协议](https://lab.cs.tsinghua.edu.cn/cod-lab-docs/labs/lab4/wishbone/)、[Wishbone 总线协议介绍](https://jia.je/hardware/2022/06/19/wishbone/) 和 [Wishbone 总线协议标准](https://cdn.opencores.org/downloads/wbspec_b4.pdf)。 23 | 24 | 在使用内存映射协议时,以下的 IP 可能会对设计有帮助: 25 | 26 | * Interconnect / Crossbar:在多个主/从设备间进行仲裁,并可能包含下面列举的所有功能 27 | * Width Converter:转换主从接口的数据宽度 28 | * Clock Converter:转换主从接口的时钟频率 29 | * FIFO:在接口间缓存数据 30 | * Register Slice:插入寄存器,切断组合逻辑,改善时序 31 | 32 | Vivado 中有相应的 IP 可供使用。 33 | 34 | ## 流式传输协议 35 | 36 | 流式协议(streaming protocol)通常用于大量数据的传输,通常只有一个传输方向和通道,传输单位为“帧”(frame),无地址概念。常见的流式传输协议如下: 37 | 38 | AMBA 中包含了名为 AXI Stream 的流式协议,也被 Xilinx 平台广泛使用。其文档可见 [Xilinx UG761](https://www.xilinx.com/support/documentation/ip_documentation/ug761_axi_reference_guide.pdf) 的第 45 页,以及 [AMBA 规范](https://developer.arm.com/documentation/ihi0051/a/Introduction/About-the-AXI4-Stream-protocol)。 39 | 40 | 流式协议同样存在 Interconnect、Crossbar、Width Converter、Clock Converter、Register Slice 等辅助组件,Quartus 与 Vivado 中都有相应的 IP 可供使用。 41 | 42 | ## 协议检查器 43 | 44 | 在实现总线协议的时候,可能会遇到一些实现上不满足标准的情况,此时可能会遇到例如死锁、数据错误等问题。为了解决这个问题,可以使用一些现成的协议检查器(Protocol Checker)。你可以将协议检查器接到你实现的总线协议接口上,它就会按照标准的要求进行检查。如果出现错误了,在仿真的时候,它会在仿真输出窗口打印错误信息;在 FPGA 上会输出错误信号,你可以把错误信号接到 LED 或者 ILA 上,从而判断是否在运行时出现了问题。 -------------------------------------------------------------------------------- /src/examples/counter.v: -------------------------------------------------------------------------------- 1 | module debouncer ( 2 | input clock, 3 | input reset, 4 | input button, 5 | output button_debounced 6 | ); 7 | reg last_button_reg; 8 | reg [15:0] counter_reg; 9 | reg button_debounced_reg; 10 | 11 | always @ (posedge clock) begin 12 | if (reset) begin 13 | last_button_reg <= 1'b0; 14 | counter_reg <= 16'b0; 15 | button_debounced_reg <= 1'b0; 16 | end else begin 17 | last_button_reg <= button; 18 | 19 | if (button == last_button_reg) begin 20 | if (counter_reg == 16'd10000) begin 21 | button_debounced_reg <= last_button_reg; 22 | end else begin 23 | counter_reg <= counter_reg + 16'b1; 24 | end 25 | end else begin 26 | counter_reg <= 16'b0; 27 | end 28 | end 29 | end 30 | 31 | assign button_debounced = button_debounced_reg; 32 | endmodule 33 | 34 | module counter ( 35 | input clock, 36 | input reset, 37 | input button_debounced, 38 | output [3:0] ones, 39 | output [3:0] tens 40 | ); 41 | 42 | reg [3:0] ones_reg; 43 | reg [3:0] tens_reg; 44 | reg button_debounced_reg; 45 | 46 | always @ (posedge clock) begin 47 | if (reset) begin 48 | ones_reg <= 4'b0; 49 | tens_reg <= 4'b0; 50 | button_debounced_reg <= 1'b0; 51 | end else begin 52 | button_debounced_reg <= button_debounced; 53 | 54 | if (button_debounced && !button_debounced_reg) begin 55 | if (ones_reg == 4'd9) begin 56 | ones_reg <= 4'b0; 57 | tens_reg <= tens_reg + 4'b1; 58 | end else begin 59 | ones_reg <= ones_reg + 4'b1; 60 | end 61 | end 62 | end 63 | end 64 | 65 | assign ones = ones_reg; 66 | assign tens = tens_reg; 67 | 68 | endmodule 69 | 70 | module counter_top ( 71 | input clock, 72 | input reset, 73 | input button, 74 | output [3:0] ones, 75 | output [3:0] tens 76 | ); 77 | 78 | wire button_debounced; 79 | 80 | debouncer debouncer_component ( 81 | .clock(clock), 82 | .reset(reset), 83 | .button(button), 84 | .button_debounced(button_debounced) 85 | ); 86 | 87 | counter counter_component ( 88 | .clock(clock), 89 | .reset(reset), 90 | .button_debounced(button_debounced), 91 | .ones(ones), 92 | .tens(tens) 93 | ); 94 | 95 | endmodule -------------------------------------------------------------------------------- /docs/project.md: -------------------------------------------------------------------------------- 1 | # 项目设计 2 | 3 | 本页提供了关于项目设计的一些建议和要求。 4 | 5 | ## 结构划分(中心控制) 6 | 7 | 本节以最常见的接受外设输入、输出视频的设计为例,说明常见的中心控制模式的模块划分方式。 8 | 9 | ![project structure](img/centralized_structure.svg) 10 | 11 | ### 控制逻辑 12 | 13 | 控制逻辑是设计的核心部分,维护了设计的整体状态,通常由一个到多个状态机构成。它根据用户的输入,计算并更改内部状态。 14 | ### 外设控制 15 | 16 | 此部分与各类外设进行通信(图中的三个协议是常用的例子),向游戏逻辑报告用户输入,并(可能)使用外设进行反馈。 17 | 推荐与控制逻辑使用相同的时钟域,仅仅在需要时对外设进行过采样。 18 | 19 | ### 图形渲染 20 | 21 | 此部分将内部状态根据需要进行渲染,并写入显存(可以是内部 Block RAM 或者 SRAM / SDRAM)。通常来说渲染可以分成多个阶段或多个层进行,如游戏的渲染可分为背景层、人物层、状态层等,逐层进行覆盖绘制。通常来说,此模块的时钟域也应该与控制逻辑相同,通过显存来隔离不同的时钟。 22 | 23 | 作为进阶功能,为了防止撕裂和图像抖动,渲染和输出可以使用 **双缓冲**(途中虚线框的部分),即使用两块同样的显存 A 和 B。在渲染模块写入 A 时,另显示模块绘制 B;渲染完成后,立刻交换两块显存,继续下一帧的渲染。这样用户不会看到渲染了一半的状态,显示效果会更好。 24 | 25 | !!! warning "必须使用显存" 26 | 27 | 画面的渲染必须使用中间存储间接进行, **不能** 直接使用显示输出模块行列值,驱动组合逻辑计算像素颜色,否则将导致时序问题。 28 | 如果使用单端口片上存储或者片外存储,则需要额外对渲染与显示的读写进行仲裁;如果使用双端口片上存储,则无此问题。 29 | 30 | ### 显示输出 31 | 32 | 显示输出模块需要产生 VGA 时序,并根据当前位置从显存中读取数据并输出。显示输出模块的时钟频率应该与 VGA 时序保持一致。 33 | 34 | !!! warning "必须遵守时序" 35 | 36 | VGA 时序必须严格遵守,包括时钟频率、行列数量、消隐区的位置和大小,否则可能导致画面输出不正常。 37 | 必须保证显示模块每个周期都能够读到需要输出的数据,但同时不应该阻塞渲染。一般来说,可以使用较宽的存储,一次读出多个渲染周期需要的数据,避免显示模块持续占用存储,阻塞渲染。 38 | 39 | ## 结构划分(流水线) 40 | 41 | 本节说明了使用流水线的项目常见的模块划分方式。通常来说,各种流量处理(如音频、视频、网络)等需要较强的实时性和吞吐量,应该使用流水线的方式设计。 42 | 43 | ![pipeline structure](img/pipeline_structure.svg) 44 | 45 | 上图中的 **输入解析—流水级 1—流水级 2—$\cdots$—流水级 n—输出生成** 模块组成了一条流水线(pipeline),通过握手信号逐级传递数据。它们应该具有相同的时钟域,每个组件都能独立的处理上游的数据、向下游发送数据。通常来说,流水线的每个阶段(stage)需要能在每个时钟周期内完成一次任务,称为全流水(fully pipelined);如果流水线阶段出现阻塞(如使用了状态机等非单周期的组件),则将导致流水线的延时增加,吞吐率下降。许多流的处理(如网络转发、音视频处理)由于协议有固定的速率,对流水线的最大延迟有要求,否则就会出现数据丢失。 46 | 47 | 流水线的结构设计中,同样需要控制逻辑,用于配置每一个流水线阶段的行为。与中心化控制相同的是,控制逻辑也可以通过外设控制模块连接不同的外设,图中略去没有画出。 48 | 49 | 在较为复杂的设计中,流水线可以与中心控制的模式混合使用,互不影响。 50 | 51 | ## 提交要求 52 | 53 | 实验结束后,每组的一位同学提交一份大实验相关材料,形式为包含以下内容的 ZIP 压缩包: 54 | 55 | * `design/`:实验完整工程(包括 RTL、Vivado IP、资源文件、testbench 等),请确保 Vivado 打开即可直接编译综合。代码要有必要的注释。为了减小体积,提交时请删除编译的中间文件,使用 git 的同学可借助 `git clean` 命令整理。同时,请保留一份编译后的 bitstream 二进制文件(即 `bit` 文件)。 56 | * `tools/`:实验中涉及的其他工具,如 PC 端 / 单片机软件源码等,或者自行编写的其他工具。最好包含使用方法。 57 | * `doc/`:实验说明文档(即实验报告)和展示 PPT。文档中应该包含整体架构设计、每一部分设计思路等。推荐使用 PDF 格式。如果使用 Markdown 等标记文本格式,请确保引用的图片等都包含于其中。 58 | * `ip/`:可选,如果实验中设计了比较通用的模块(如 FFT 等),可作为 IP 核单独再提取出来。需要包含 RTL 源码和使用说明(比如各个信号的定义),最好有相应的 testbench。 59 | * `README` 文件:包含上面未涉及的,但是需要说明的其他内容,比如 LICENSE 等。 60 | 61 | !!! note "版权声明" 62 | 63 | 默认情况下,视作同学们授权将大作业所有内容分享给以后各届的数设同学用于参考或复用(仅限自主设计的 IP 核)。 64 | 未经额外授权的情况下,课程组不会将同学们的作业用作其他用途。如有异议,可以与助教或者老师提出。 65 | -------------------------------------------------------------------------------- /docs/hdl/clocking.md: -------------------------------------------------------------------------------- 1 | # 时钟相关 2 | 3 | 时钟是时序逻辑中最重要的组成部分,驱动电路的状态发生变化。在 FPGA 逻辑设计中,正确使用时钟非常重要。 4 | 5 | ## 时钟产生 6 | 7 | FPGA 中,时钟可以由以下两种方式产生: 8 | 9 | * 外部输入:由 FPGA 芯片外部的晶振的周期震荡产生并输入到 FPGA 中; 10 | * 内部生成:FPGA 内部具有 PLL (Phase-Locked Loops) 或 MMCM (Mixed-Mode Clock Manager) 等时钟管理组件可以根据已有的时钟产生新的时钟,并更改频率、相位等属性。 11 | 12 | 通常,如果需要的时钟频率与输入的不一致(更高或者更低),就需要使用 PLL/MMCM 来生成新的时钟。Vivado 提供了相应的 IP Core,称为 Clocking Wizard。 13 | 在初始化 IP 时,提供输入时钟的频率、抖动(可取默认值)等信息,而后指定需要的时钟信息,即可生成新的时钟供使用。 14 | 一般来说,此类 IP 会提供一个名称类似 `locked` 的信号,表明输出是否稳定,可以用作其生成的时钟对应的(异步)复位信号(注意极性)。 15 | 16 | 17 | !!! danger "禁止分频" 18 | 19 | 在非 testbench 代码中,禁止使用分频的方法生成时钟,否则可能会导致不可预料的时序问题。 20 | 21 | 对于少数需要输出分频后的时钟信号到外设的协议,需要十分谨慎地实现(例如从寄存器直接输出,减少延迟),并辅以相应的时序约束(保证输出信号到芯片输入端时满足 setup/hold 等条件),实现前请查阅资料并咨询助教。 22 | 23 | 24 | !!! note "频率不准确" 25 | 26 | 由于内部器件的限制,可能无法得到需要的精确频率(配置 IP 时会告知实际频率),并且同一个 PLL 产生的不同时钟频率也很可能会相互制约。在这种情况下,可以使用多个 PLL 尝试缓解问题,但同时也要注意 FPGA 的 PLL 数量是有限的。 27 | 28 | 29 | 每个(输入或者生成的)时钟与它驱动的所有寄存器,构成了一个独立的 **时钟域(clock domain)** 。注意,这是时钟域的 **唯一判断方式** 。即使两个时钟的频率和相位相同,甚至由同一个 PLL 生成,也不能轻易认为属于一个时钟域。 30 | 31 | 32 | ## 注意事项 33 | 34 | 在使用时钟信号时,需要注意以下事项,否则可能导致时序问题: 35 | 36 | * **禁止** 使用时钟信号的下降沿。 37 | * **禁止** 将时钟信号进行手动分频或者与其他信号进行逻辑运算后,再作为时钟信号使用(称为门控时钟,gate-controlled clock)。 38 | * **不建议** 用 PLL 生成频率太高(如数百 MHz)或者过低(如低于 1000Hz)的时钟,这通常并不能成功。在设计中,通常也不需要这么做。 39 | * **不推荐** 在设计中使用过多的时钟,核心逻辑尽量使用单一时钟域。 40 | * **建议** 在时钟信号名称中标注频率或用途,以防混淆。 41 | 42 | ## 跨时钟域 43 | 44 | 由于两个时钟域的信号变化周期不同,因此通常不能直接使用跨过时钟域采样的信号;否则,可能由于数据建立时间不足产生的亚稳态获得错误的结果。当需要在两个时钟域间(包括 FPGA 与外部器件间)传递数据时,一般采取以下方法: 45 | 46 | !!! warning "谨慎思考" 47 | 48 | 有很多常见的操作不需要跨时钟域完成,尤其是每隔一段固定时间间隔进行某项操作。为达成这一目的,使用状态机计数即可。通常来说,VGA、SFP 等对于时序要求比较严格的信号,才需要自己的时钟域。在查看下面的说明之前,请务必仔细思考跨时钟域的必要性。 49 | 50 | ### 寄存器同步 51 | 52 | 对于少量控制信号,使用(至少)两个寄存器进行采样即可(`clk` 为需要使用此信号的时钟域): 53 | 54 | ```verilog 55 | wire other_domain; 56 | reg [1:0] sync; 57 | wire this_domain; 58 | 59 | assign this_domain = sync[1]; 60 | 61 | always @(posedge clk) begin 62 | if (rst) begin 63 | sync <= 2'b0; 64 | end else begin 65 | sync <= {sync[0], other_domain}; 66 | end 67 | end 68 | ``` 69 | 70 | 可以将此逻辑封装成模块,以便使用。对于 Vivado 用户,也可使用 XPM (Xilinx Parameterized Macros, UG974) 中的 `XPM_CDC_SINGLE` 和 `XPM_CDC_ARRAY_SINGLE` 等模块来直接进行同步(其中同步寄存器级数等都是可配置的参数)。如有更高级的需求,可以参见 `XPM_CDC_HANDSHAKE` 等模块 71 | 72 | ### 异步 FIFO 73 | 74 | 对于分隔于两个时钟域的生产者——消费者模型,可以通过具有两个端口的 FIFO 来进行同步,每端使用自己的时钟进行 `enqueue` 和 `dequeue` 即可。 75 | 76 | Vivado 中 IP Core 为 `FIFO Generator` (PG057),XPM 中也有相应的 `XPM_FIFO_ASYNC` 模块。 77 | 78 | ### 异步双口 RAM 79 | 80 | 对于更一般的需求,可以通过具有两个时钟域的两个读写端口的 Block RAM 来传递数据,每端使用自己的时钟进行操作。 81 | 82 | Vivado 中 IP Core 为 `Block Memory Generator`(配置时同样可选前述两种模式),XPM 中有相应的 `XPM_MEMORY_SDPRAM` 和 `XPM_MEMORY_TDPRAM` 模块。 83 | -------------------------------------------------------------------------------- /theme-override/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | {{ super() }} 5 | 6 | {% if git_page_authors %} 7 |
8 | 9 | 作者:{{ git_page_authors | default('enable mkdocs-git-authors-plugin') }} 10 | 11 |
12 | {% endif %} 13 | 14 |

{{ lang.t("meta.comments") }}

15 | 29 | {% endblock %} 30 | 31 | {% block analytics %} 32 | {{ super() }} 33 | 86 | {% endblock %} 87 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ## 修改了一些代码以后,为什么 VGA 不工作了? 4 | 5 | VGA 不工作,常见的原因有: 6 | 7 | - 没有输出像素时钟 video_clk 8 | - 像素时钟与 VGA 时序不匹配,常见的原因是修改了 PLL 的设置,改了时钟频率,但是没有改 vga 的参数 9 | 10 | 因此如果需要 50MHz 以外的时钟频率,有两种办法实现: 11 | 12 | 1. 在 PLL IP 设置中新增一个时钟输出,把这个时钟频率设置成自己想要的频率;需要解决跨时钟域的问题 13 | 2. 把 VGA 时钟频率改成自己想要的时钟频率,把 VGA 的时钟同时用于其他逻辑;前提是要找到匹配的 VGA Timing,使得 Pixel Clock 是期望的时钟频率,然后相应地修改 vga 模块的参数。 14 | 15 | ## SD 卡读取出的数据与预期结果不一致? 16 | 17 | SD 卡读取的时候,不同 SD 卡的地址编码不一样: 18 | 19 | - SDSC 读取的地址单位是字节 20 | - SDHC 和 SDXC 读取的地址单位是扇区,每个扇区 512 字节 21 | 22 | 目前示例代码没有内置自动检测 SDSC 或 SDHC 的逻辑,如果想要实现,可以发送 CMD58 命令获取 CCS,如果 CCS 为 0 就是 SDSC,CCS 为 1 就是 SDHC 或 SDXC。可以从 SD 卡表面区分 SDSC/SDHC/SDXC。 23 | 24 | 如果使用 WinHex 工具来查看文件的地址,需要注意的是,默认显示的地址可能是相对于分区开头的,而不是 SD 卡开头。例如地址 0x00044000 对应逻辑扇区 544,但是分区前还有 32 个扇区,物理扇区号是 544+32=576,因此在读取 SD 卡的时候,应该要读取第 576 个扇区。这些数据在 WinHex 的右侧边栏中会显示。 25 | 26 | ## COE 文件的格式是什么样的? 27 | 28 | 官方文档:[COE File Syntax](https://docs.amd.com/r/en-US/ug896-vivado-ip/COE-File-Syntax),文档中给出了几个例子: 29 | 30 | ```ini 31 | ; This .COE file specifies the contents for a block memory 32 | ; of depth=16, and width=4. In this case, values are specified 33 | ; in binary format. 34 | memory_initialization_radix=2; 35 | memory_initialization_vector= 36 | 1111, 37 | 1111, 38 | 1111, 39 | 1111, 40 | 1111, 41 | 0000, 42 | 0101, 43 | 0011, 44 | 0000, 45 | 1111, 46 | 1111, 47 | 1111, 48 | 1111, 49 | 1111, 50 | 1111, 51 | 1111; 52 | ``` 53 | 54 | ```ini 55 | ; The example specifies initialization values for a memory of depth= 32, 56 | ; and width=16. In this case, values are specified in hexadecimal 57 | ; format. 58 | memory_initialization_radix = 16; 59 | memory_initialization_vector = 23f4 0721 11ff ABe1 0001 1 0A 0 60 | 23f4 0721 11ff ABe1 0001 1 0A 0 61 | 23f4 721 11ff ABe1 0001 1 A 0 62 | 23f4 721 11ff ABe1 0001 1 A 0; 63 | ``` 64 | 65 | ## 出现 Incorrect bitstream assigned to device 错误 66 | 67 | 实验模板已经配置好了实验 FPGA 的型号,因此如果没有修改过实验模板的配置,那么项目配置是没有问题的。此时,可以去 Hardware Manager 查看一下,如果识别到的 FPGA 型号是 xc7z020,那就说明 JTAG 插错地方了,识别了错误的 FPGA,应该插到板子左下角的 JTAG 插座上。 68 | 69 | ## 出现 A LUT3 cell in the design is missing a connection 错误 70 | 71 | 这通常是使用了某个 Vivado 提供的 IP,例化以后,部分信号没有连接,导致 Vivado 在优化的时候,发现部分信号空悬,这才报了错。 72 | 73 | 这个时候,打开 IP 配置界面,或者查看 IP 的 Instantiaion Wrapper,可以看到 IP 都有哪些输入输出信号,结合 IP 的文档了解这些信号的含义,进行正确的连接或赋值。 74 | 75 | ## Vivado 提示奇怪的 Verilog 语法错误,无法直接定位到问题 76 | 77 | 这是因为 Vivado 的 Verilog 报错功能实现地比较粗暴,可能前面某个地方写错了一点,导致后面的代码都被解析成错误的语法,然后出现不知所云的错误。此时就要人工浏览一遍代码,仔细找找语法错误。 78 | 79 | 类比一下,写 C/C++ 的时候,如果出现了类似的问题,GCC/Clang 等编译器做的会比较好,会找出程序员一些常见的错误并指出,而不是简单地汇报 lexer/parser 分析时出现的直接问题。Vivado 显然没有花那么多功夫。 80 | 81 | ## Windows 下无法连接 FPGA,显示 Unable to launch local hw_server executable 82 | 83 | 见 [Windows 下 Vivado hw_server 由于端口绑定失败无法启动的解决方法](https://zhuanlan.zhihu.com/p/670343325),简而言之,用管理员权限运行: 84 | 85 | ```cmd 86 | net stop winnat 87 | netsh int ip add excludedportrange protocol=tcp numberofports=3 startport=3000 88 | netsh int ip add excludedportrange protocol=tcp numberofports=1 startport=3121 89 | net start winnat 90 | ``` 91 | 92 | 重试,可以发现 hw_server 正常工作。 93 | -------------------------------------------------------------------------------- /src/tests/divide.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+41 (git sha1 29c0a5958, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | (* src = "tests/divide.v:1.1-10.10" *) 4 | module divide(counter, tens, ones); 5 | wire _00_; 6 | wire _01_; 7 | wire _02_; 8 | wire _03_; 9 | wire _04_; 10 | wire _05_; 11 | wire _06_; 12 | wire _07_; 13 | wire _08_; 14 | wire _09_; 15 | wire _10_; 16 | wire _11_; 17 | wire _12_; 18 | wire _13_; 19 | wire _14_; 20 | wire _15_; 21 | wire _16_; 22 | wire _17_; 23 | wire _18_; 24 | wire _19_; 25 | wire _20_; 26 | wire _21_; 27 | wire _22_; 28 | wire _23_; 29 | wire _24_; 30 | wire _25_; 31 | wire _26_; 32 | wire _27_; 33 | wire _28_; 34 | wire _29_; 35 | wire _30_; 36 | wire _31_; 37 | wire _32_; 38 | wire _33_; 39 | wire _34_; 40 | wire _35_; 41 | wire _36_; 42 | wire _37_; 43 | wire _38_; 44 | (* src = "tests/divide.v:2.15-2.22" *) 45 | input [6:0] counter; 46 | wire [6:0] counter; 47 | (* src = "tests/divide.v:4.16-4.20" *) 48 | output [3:0] ones; 49 | wire [3:0] ones; 50 | (* src = "tests/divide.v:3.16-3.20" *) 51 | output [3:0] tens; 52 | wire [3:0] tens; 53 | assign _00_ = ~counter[4]; 54 | assign _01_ = ~counter[3]; 55 | assign _02_ = ~counter[2]; 56 | assign _03_ = ~counter[1]; 57 | assign _04_ = ~(counter[6] & _00_); 58 | assign _05_ = counter[5] | _04_; 59 | assign _06_ = counter[5] & _04_; 60 | assign _07_ = ~(counter[6] & counter[4]); 61 | assign _08_ = ~(counter[6] & counter[5]); 62 | assign tens[3] = ~(_07_ & _08_); 63 | assign _09_ = ~(_00_ & _08_); 64 | assign _10_ = _07_ & _09_; 65 | assign _11_ = ~(counter[3] | _10_); 66 | assign _12_ = counter[3] | _10_; 67 | assign _13_ = ~(_06_ & _12_); 68 | assign tens[2] = ~(_05_ & _13_); 69 | assign _14_ = ~(_01_ & tens[2]); 70 | assign _15_ = counter[3] ^ tens[2]; 71 | assign _16_ = counter[2] | _15_; 72 | assign _17_ = counter[3] | _05_; 73 | assign _18_ = ~(_10_ & _14_); 74 | assign _19_ = ~(_17_ & _18_); 75 | assign _20_ = ~(_16_ & _19_); 76 | assign _21_ = ~(_06_ & _11_); 77 | assign _22_ = _01_ | _05_; 78 | assign _23_ = _21_ & _22_; 79 | assign _24_ = ~_23_; 80 | assign _25_ = _16_ & _24_; 81 | assign _26_ = _19_ | _25_; 82 | assign _27_ = _20_ & _26_; 83 | assign _28_ = ~(_20_ & _26_); 84 | assign tens[1] = ~(_20_ & _23_); 85 | assign _29_ = ~(_02_ & tens[1]); 86 | assign _30_ = counter[2] ^ tens[1]; 87 | assign _31_ = counter[1] | _30_; 88 | assign _32_ = ~(_15_ & _29_); 89 | assign _33_ = _16_ | _23_; 90 | assign _34_ = ~(_32_ & _33_); 91 | assign _35_ = ~(_31_ & _34_); 92 | assign tens[0] = ~(_28_ & _35_); 93 | assign _36_ = _03_ & tens[0]; 94 | assign ones[1] = counter[1] ^ tens[0]; 95 | assign ones[2] = _30_ ^ _36_; 96 | assign _37_ = _27_ & _31_; 97 | assign _38_ = _34_ | _37_; 98 | assign ones[3] = _35_ & _38_; 99 | assign ones[0] = counter[0]; 100 | endmodule 101 | -------------------------------------------------------------------------------- /docs/hdl-by-example/imgs/button.svg: -------------------------------------------------------------------------------- 1 | buttonlight -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: 数字逻辑设计实验文档 2 | site_description: 'Documentation for labs of Digital Logic Design course' 3 | site_author: 'Digital Logic Design Course Team' 4 | site_url: https://lab.cs.tsinghua.edu.cn/digital-design/doc/ 5 | copyright: 'Copyright © 2020-2023 Department of Computer Science and Technology, Tsinghua University. All Rights Reserved.' 6 | 7 | theme: 8 | name: 'material' 9 | language: 'zh' 10 | custom_dir: 'theme-override/' 11 | icon: 12 | logo: material/developer-board 13 | repo: fontawesome/brands/github 14 | features: 15 | - navigation.instant 16 | - content.tabs.link 17 | 18 | 19 | repo_name: 'thu-cs-lab/Digital-Design-Docs' 20 | repo_url: 'https://github.com/thu-cs-lab/Digital-Design-Docs' 21 | 22 | nav: 23 | - 数字逻辑设计实验: 24 | - 总述: index.md 25 | - 项目设计: project.md 26 | - HDL 编程: 27 | - 时钟相关: hdl/clocking.md 28 | - 复位相关: hdl/reset.md 29 | - 总线协议: hdl/bus.md 30 | - 调试相关: hdl/debug.md 31 | - 三态门: hdl/tri.md 32 | - 外设相关: hdl/peripheral.md 33 | - 硬件相关: 34 | - 实验板: hardware/board_xilinx.md 35 | - 板载外设: hardware/onboard_xilinx.md 36 | - 外接外设: hardware/peripheral.md 37 | - Vivado 使用: vivado.md 38 | - 常用软件: software.md 39 | - 常见问题: faq.md 40 | - 通过例子学硬件描述语言: 41 | - 总述: hdl-by-example/overview.md 42 | - 加法器: hdl-by-example/adder.md 43 | - 按钮开关: hdl-by-example/button.md 44 | - 秒表: hdl-by-example/timer.md 45 | - 计数器: hdl-by-example/counter.md 46 | - 无状态仲裁器: hdl-by-example/priority_encoder.md 47 | - 循环优先级仲裁器: hdl-by-example/rr_arbiter.md 48 | - 代码规范: hdl-by-example/coding_standard.md 49 | - 仿真: hdl-by-example/simulation.md 50 | 51 | extra_javascript: 52 | - javascripts/mathjax.js 53 | - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js 54 | 55 | plugins: 56 | - search 57 | - git-revision-date-localized: 58 | fallback_to_build_date: true 59 | locale: zh 60 | - git-authors: 61 | show_contribution: true 62 | show_line_count: true 63 | count_empty_lines: true 64 | fallback_to_empty: false 65 | - wavedrom: 66 | embed_svg: true 67 | pymdownx: true 68 | 69 | markdown_extensions: 70 | - admonition 71 | - codehilite: 72 | guess_lang: false 73 | linenums: true 74 | - footnotes 75 | - def_list 76 | - meta 77 | - toc: 78 | permalink: true 79 | - pymdownx.arithmatex: 80 | generic: true 81 | - pymdownx.betterem: 82 | smart_enable: all 83 | - pymdownx.caret 84 | - pymdownx.critic 85 | - pymdownx.details 86 | - pymdownx.emoji: 87 | emoji_generator: !!python/name:pymdownx.emoji.to_svg 88 | - pymdownx.inlinehilite 89 | - pymdownx.magiclink 90 | - pymdownx.mark 91 | - pymdownx.smartsymbols 92 | - pymdownx.superfences: 93 | custom_fences: 94 | - name: wavedrom 95 | class: wavedrom 96 | format: !!python/name:markdownwavedrom.plugin.fence_wavedrom_format 97 | - pymdownx.tabbed: 98 | alternate_style: true 99 | - pymdownx.tasklist: 100 | custom_checkbox: true 101 | - pymdownx.tilde 102 | 103 | extra: 104 | pagetime: 'on' 105 | analytics: 106 | provider: google 107 | property: G-69YPS2LP7R 108 | xapi: 109 | ident: digital-logic-design 110 | auth: 'XAPI_TOKEN' 111 | endpoint: 'XAPI_URL' 112 | -------------------------------------------------------------------------------- /docs/hdl-by-example/model.md: -------------------------------------------------------------------------------- 1 | # 模型 2 | 3 | 为了进一步理解时序逻辑和组合逻辑的关系,本节介绍一个简单的描述同步时序逻辑的模型,来进一步理解时序逻辑和组合逻辑的工作方式。 4 | 5 | 这个模型假设: 6 | 7 | 1. 只有一个时钟信号 8 | 2. 寄存器都在该时钟的上升沿触发,不使用下降沿 9 | 3. 不考虑寄存器输出延迟,组合逻辑延迟,setup 和 hold 时间 10 | 4. 顶层模块的输入信号只在时钟上升沿变化 11 | 12 | 经过这些简化以后,可以建立一个简单的模型,来描述硬件的行为: 13 | 14 | 1. 所有信号仅在时钟上升沿的时候变化,其他时候所有信号的值都不变; 15 | 2. 在时序逻辑中,寄存器 **新** 的值是根据上升沿之前 **旧** 的值计算,所有寄存器在一瞬间内同时变化; 16 | 3. 所有的组合逻辑也在上升沿立即按照寄存器 **新** 的值进行计算。 17 | 18 | 用伪代码表示就是: 19 | 20 | ``` 21 | loop { 22 | // 等待时钟上升沿 23 | wait_until_clock_posedge() 24 | 25 | // 保存上升沿之前所有的寄存器和组合信号的取值和顶层模块的输入信号 26 | values = get_values() 27 | top_level_input = get_top_level_input() 28 | 29 | // 根据寄存器取值和顶层模块的输入信号,计算出新的寄存器取值 30 | new_reg_values = compute_next_reg_values(values, top_level_input) 31 | save_reg_values(new_reg_values) 32 | 33 | // 根据新的寄存器取值和顶层模块的输入信号,计算出其余组合逻辑信号 34 | comb_values = compute_comb(new_reg_values, top_level_input) 35 | } 36 | ``` 37 | 38 | 实际上,这也是仿真时会进行的计算流程。 39 | 40 | 下面来看一个计数器的例子: 41 | 42 | ```verilog 43 | module counter ( 44 | input wire clk, 45 | input wire increment, 46 | 47 | output wire [3:0] count, 48 | output wire odd, 49 | ); 50 | reg [3:0] count_reg; 51 | wire [3:0] next_count_comb; 52 | 53 | always @ (posedge clk) begin 54 | count_reg <= next_count_comb; 55 | end 56 | 57 | assign next_count_comb = count_reg + increment; 58 | assign odd = count_reg[0] == 1'b1; 59 | assign count = count_reg; 60 | 61 | endmodule 62 | ``` 63 | 64 | 那么上面的代码对应下面的伪代码: 65 | 66 | ``` 67 | fn compute_next_reg_values(values, top_level_input) { 68 | map { 69 | count_reg: values.next_count_comb 70 | } 71 | } 72 | 73 | fn compute_comb(new_reg_values, top_level_input) { 74 | map { 75 | next_count_comb: new_reg_values.count_reg + top_level_input.increment 76 | odd: new_reg_values.count_reg[0] == 1'b1 77 | count: new_reg_values.count_reg 78 | } 79 | } 80 | 81 | fn simulate() { 82 | loop { 83 | wait_until_clock_posedge() 84 | 85 | reg_values = get_reg_values() 86 | top_level_input = get_top_level_input() 87 | 88 | new_reg_values = compute_next_reg_values(reg_values, top_level_input) 89 | save_reg_values(new_reg_values) 90 | 91 | comb_values = compute_comb(new_reg_values, top_level_input) 92 | } 93 | } 94 | ``` 95 | 96 | 可能的波形图: 97 | 98 | 115 | 116 | 对应的伪代码计算流程: 117 | 118 | 1. 初始情况下 count_reg = 0, next_count_comb = 1 119 | 2. 时钟上升沿 1 来临,compute_next_reg_values() 得到 count_reg = 1 120 | 3. compute_comb() 得到 next_count_comb = 2, odd = 1, count = 1 121 | 4. 显示到波形 122 | 5. 时钟上升沿 2 来临,compute_next_reg_values() 得到 count_reg = 2 123 | 6. compute_comb() 得到 next_count_comb = 4, odd = 0, count = 2 124 | 7. 显示到波形 125 | 8. 时钟上升沿 3 来临,compute_next_reg_values() 得到 count_reg = 4 126 | 9. compute_comb() 得到 next_count_comb = 6, odd = 0, count = 4 127 | 10. 显示到波形 -------------------------------------------------------------------------------- /docs/hdl-by-example/adder.md: -------------------------------------------------------------------------------- 1 | # 加法器 2 | 3 | 4 | ## 需求 5 | 6 | 让我们来实现一个 2 位加法器:即输入两个非负整数,输出这两个数的和。要求只要输入变化,输出就随之变化。 7 | 8 | 根据上面的需求,可以设计如下的输入输出信号: 9 | 10 | 输入: 11 | 12 | 1. `a`: 宽度为 2,表示输入的第一个非负整数 13 | 2. `b`: 宽度为 2,表示输入的第二个非负整数 14 | 15 | 输出: 16 | 17 | 1. `c`: 宽度为 2,表示 `a+b`,溢出的部分舍弃 18 | 19 | ## 波形 20 | 21 | 根据上面的需求,既然输出 `c` 信号是随着输入信号变化而变化,我们就可以画出下面的波形: 22 | 23 | 33 | 34 | 可以看到,`a` 和 `b` 可以随时变化,而 `c` 也会立即更新为求和以后的结果。 35 | 36 | 37 | ## 电路 38 | 39 | 对于这一类 **输出仅随着输入变化而变化** 的信号,我们通常使用 **组合逻辑** 来实现。它的特点是输出完全依赖于输入,没有内部状态,和时间无关。 40 | 41 | 根据真值表,可以得到输出与输入的关系(`a_0` 表示 `a` 的最低位): 42 | 43 | - $c_0=a_0 \oplus b_0$ 最低位异或 44 | - $c_1=(a_1 \oplus b_1) \oplus (a_0 \land b_0)$ 第 1 位异或再加进位 45 | 46 | 电路图如下: 47 | 48 | ![](imgs/adder.svg) 49 | 50 | ## 代码 51 | 52 | 最后再用 HDL 来实现如上的功能。虽然上面我们推导了加法的逻辑电路,但实际上写 HDL 的时候,我们直接写 `a+b` 就可以了,EDA 工具会自动完成逻辑的转换。 53 | 54 | === "Verilog/System Verilog" 55 | 56 | 首先,根据前面确定的输出输出信号编写 `module`: 57 | 58 | ```verilog 59 | module add2 ( 60 | input wire [1:0] a, 61 | input wire [1:0] b, 62 | output wire [1:0] c 63 | ); 64 | // TODO 65 | endmodule 66 | ``` 67 | 68 | 通常,当我们声明一个宽度为 `n` 的信号的时候,采用的是 `[n-1:0]` 的写法,可以理解为一共有 `n` 位,下标从高到底是 `n-1` 到 `0`。其余部分可以忽略其含义,将它们作为模板记住即可。这里有一个很容易犯的错误是在 `output wire [1:0] c` 后面多写了一个逗号,通常是在复制粘贴的时候忘记删除。 69 | 70 | 接着,我们要把电路实现放在 `module` 内部。对于组合电路,直接构造 `a+b` 的电路,然后把结果 **连接** 到输出信号 `c` 即可。 71 | 72 | 73 | ```verilog 74 | assign c = a + b; 75 | ``` 76 | 77 | 请注意,不要把这里的 `assign c = a + b` 理解为赋值,而是把它看成信号的连接:通过一系列的逻辑门(比如上面提到的异或门 XOR、与门 AND),计算得到 `a+b` 的结果,再把结果连接到输出信号 `c` 上。 78 | 79 | 80 | === "VHDL" 81 | 82 | 首先,根据前面确定的输出信号编写 `entity`: 83 | 84 | ```vhdl 85 | library IEEE; 86 | use IEEE.STD_LOGIC_1164.ALL; 87 | use IEEE.STD_LOGIC_UNSIGNED.ALL; 88 | 89 | entity add2 is 90 | Port ( a : in STD_LOGIC_VECTOR (1 downto 0); 91 | b : in STD_LOGIC_VECTOR (1 downto 0); 92 | c : out STD_LOGIC_VECTOR (1 downto 0)); 93 | end add2; 94 | ``` 95 | 96 | 通常,当我们声明一个宽度为 `n` 的信号的时候,采用的是 `STD_LOGIC_VECTOR (n-1 downto 0)` 的写法,可以理解为一共有 `n` 位,下标从高到底是 `n-1` 到 `0`。其余部分可以忽略其含义,将它们作为模板记住即可。 97 | 98 | 接着,我们要把电路实现放在 `architecture` 内部。对于组合电路,直接构造 `a+b` 的电路,然后把结果 **连接** 到输出信号 `c` 即可。 99 | 100 | 101 | ```vhdl 102 | architecture behavior of add2 is 103 | begin 104 | c <= a + b; 105 | end behavior; 106 | ``` 107 | 108 | 请注意,不要把这里的 `<=` 理解为赋值,而是把它看成信号的连接:通过一系列的逻辑门(比如上面提到的异或门 XOR、与门 AND),计算得到 `a+b` 的结果,再把结果连接到输出信号 `c` 上。 109 | 110 | ## 总结 111 | 112 | 回顾上面的电路,可以看到它最大的特点是 **输入一变化,输出就跟着变**,并且与时间无关,只要给定了输入,那么它的输出就是确定的(类比数学上的函数 `output = f(input)`),这种电路我们称之为 **组合电路**(**组合逻辑电路**)。 113 | 114 | 为了在硬件上搭建一个组合电路,在 VHDL 中,我们直接对输入信号进行一系列的计算(`a+b`),把结果通过 `output <= a+b;` 语句 **连接** 到了输出信号;在 Verilog 中,我们同样对输入信号进行了计算(`a+b`),把结果通过 `assign output = a+b;` 语句 **连接** 到了输出信号。 115 | 116 | !!! question "组合逻辑很复杂怎么办,比如 `if-then-else` 的逻辑?" 117 | 118 | 看到这里你可能会有一个疑问,如果这个计算过程很复杂怎么办?按照目前的代码编写方式,我们只能把 `if (a) then b else c` 改写成 `a ? b : c`,但是逻辑更加复杂以后,代码可读性会急剧下降,例如 `a ? (b ? c : d) : (e ? f : g)`。之后我们会介绍如何在代码中实现更复杂的组合逻辑。 -------------------------------------------------------------------------------- /docs/hdl/peripheral.md: -------------------------------------------------------------------------------- 1 | # 外设相关 2 | 3 | ## 低速率外部接口 4 | 5 | 无论是 UART,I2C,PS/2,I2S 还是 SPI,这些外部接口的传输速率/时钟频率相对来说都是比较低的(例如 UART 115200 bps,I2C 几百 kHz 等等,部分外设的 SPI 比较快,达到 50 MHz,可以不按照下面的方法做),这个时候一个直接的想法可能是,用 PLL 生成一个对应频率的时钟输出(例如 UART 就生成一个 115.2 KHz 的时钟),然后实现控制逻辑。但这样做并不好: 6 | 7 | 1. 在设置 PLL 的时候会发现它不能直接生成这么低的频率,如果真要用 PLL 生成,那就需要串联多级 PLL,逐渐往下降 8 | 2. 一些协议的数据需要在时钟信号的负半周期修改,从而保留足够的时序余量,那就意味着需要用负边沿触发的寄存器做输出,同时还要在正边沿采样,这时候跨时钟域就比较复杂 9 | 10 | 因此一般的解决办法是,用一个更高频率的时钟,通过数周期的形式,模拟一个低频率下的行为。例如 I2C 时钟设定为 100 kHz,为了输出 100 kHz 的时钟给外设,FPGA 内部可以用 50 MHz 的时钟频率驱动寄存器,然后计数,每 `50 MHz / 100 kHz / 2 = 250` 个周期取反一次,然后把寄存器结果输出到 I2C 的时钟信号上,那么外设看到的就是一个 100kHz 的时钟。要修改数据的时候,通过计数,就可以知道现在是处于 I2C 的时钟的正半周期还是负半周期、时钟是否即将出现上升沿/下降沿,然后假装自己在 100 kHz 下对数据进行读和写。 11 | 12 | 注意手动分频生成的时钟仅用于输出,不用于内部寄存器的时钟输入。 13 | 14 | ## 例子 15 | 16 | 下面是一些例子: 17 | 18 | ### I2C 19 | 20 | 以 I2C 为例,假如 FPGA 内部时钟频率采用 50 MHz,I2C 采用 250 kHz,那么每 `50 MHz / 250 kHz / 2 = 100` 个周期就要翻转一次 I2C 的时钟输出,也就是 SCL: 21 | 22 | ```verilog 23 | always @ (posedge clk_50M) begin 24 | if (reset) begin 25 | i2c_scl_counter <= 8'b0; 26 | i2c_scl_reg <= 1'b0; 27 | end else begin 28 | if (i2c_scl_counter == 8'd100) begin 29 | i2c_scl_counter <= 8'b0; 30 | i2c_scl_reg <= ~i2c_scl_reg; 31 | end else begin 32 | i2c_scl_counter <= i2c_scl_counter + 8'b1; 33 | end 34 | end 35 | end 36 | 37 | // output i2c scl 38 | assign i2c_scl = i2c_scl_reg; 39 | ``` 40 | 41 | 当然了,这是一个简化的实现,实际上 SCL 只会在需要传输数据的时候才会翻转,此时就会和状态机的转移结合在一起。为了验证输出的 SCL 信号是否真的是 250 kHz,可以在仿真环境中输入一个 50 MHz 频率的时钟,然后观察 SCL 信号一个周期是不是花了 4000 ns。 42 | 43 | 除了输出 SCL 信号,还需要在 SDA 上输入或输出数据。既然协议要求要在 SCL 的负半周期修改 SDA 上的数据用于传输,可以这么做: 44 | 45 | ```verilog 46 | if (i2c_scl_counter == 8'd50 && i2c_scl_reg == 1'd0) begin 47 | // update i2c_sda 48 | // state machine transition 49 | end 50 | ``` 51 | 52 | 50 表示在负半周期的中点(严格来说,其实 49 才是),实际上也不一定要选中点,只要留足够的余量即可。 53 | 54 | ### I2S 55 | 56 | I2S 是用来传输音频的协议,它的实现方法也是类似地,只不过它要输出 BCLK 和 LRCLK 两个时钟信号:LRCLK 的频率是采样频率,也就是 48 kHz;BCLK 的频率是采样频率的 48 倍(两个通道,每个通道 24 bit),也就是 `48 * 48 = 2.304 MHz`。代码中为了方便整数分频,选取了 `73.728 MHz` 作为 FPGA 内部频率,那么 LRCLK 需要每 `73.728 MHz / 48 kHz / 2 = 768` 个周期翻转一次,BCLK 需要每 `73.728 MHz / 2.304 MHz / 2 = 16` 个周期翻转一次: 57 | 58 | ```verilog 59 | module top( 60 | // sample rate fs = 48kHz 61 | // clk = 6*256*fs = 73.728MHz 62 | input wire clk, 63 | // other signals omitted 64 | ); 65 | 66 | always @(posedge clk) begin 67 | if (rst) begin 68 | i2s_lrclk_counter <= 16'b0; 69 | i2s_lrclk_reg <= 1'b0; 70 | end else begin 71 | // divide by 1536 72 | if (i2s_lrclk_counter == 16'd767) begin 73 | i2s_lrclk_reg <= ~i2s_lrclk_reg; 74 | i2s_lrclk_counter <= 16'b0; 75 | end else begin 76 | i2s_lrclk_counter <= i2s_lrclk_counter + 16'b1; 77 | end 78 | end 79 | end 80 | assign i2s_lrclk = i2s_lrclk_reg; 81 | 82 | always @(posedge clk) begin 83 | if (rst) begin 84 | i2s_bclk_counter <= 16'b0; 85 | i2s_bclk_reg <= 1'b0; 86 | end else begin 87 | // divide by 32 88 | if (i2s_bclk_counter == 8'd15) begin 89 | i2s_bclk_reg <= ~i2s_bclk_reg; 90 | i2s_bclk_counter <= 8'b0; 91 | end else begin 92 | i2s_bclk_counter <= i2s_bclk_counter + 8'b1; 93 | end 94 | end 95 | end 96 | endmodule 97 | ``` 98 | 99 | 剩下的就是在翻转 BCLK/LRCLK 的同时,读取/写入 I2S 上的音频数据了,这个就交给状态机来实现。 -------------------------------------------------------------------------------- /src/examples/rr_arbiter_vhdl.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+6 (git sha1 e0ba32423, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | module rr_arbiter(clock, reset, request, valid, user); 4 | wire _00_; 5 | wire _01_; 6 | wire _02_; 7 | wire _03_; 8 | wire _04_; 9 | wire _05_; 10 | wire _06_; 11 | wire _07_; 12 | wire _08_; 13 | wire _09_; 14 | wire _10_; 15 | wire _11_; 16 | input clock; 17 | wire clock; 18 | wire [1:0] priority_encoder_user_comb; 19 | wire priority_encoder_valid_comb; 20 | input [3:0] request; 21 | wire [3:0] request; 22 | input reset; 23 | wire reset; 24 | output [1:0] user; 25 | wire [1:0] user; 26 | reg [1:0] user_reg; 27 | output valid; 28 | wire valid; 29 | reg valid_reg; 30 | assign _01_ = ~user_reg[0]; 31 | assign _02_ = ~(request[0] & _01_); 32 | assign _03_ = request[1] & user_reg[0]; 33 | assign _04_ = ~(user_reg[1] | _03_); 34 | assign _05_ = ~(_02_ & _04_); 35 | assign _06_ = ~(_01_ & request[2]); 36 | assign _07_ = ~(user_reg[0] & request[3]); 37 | assign _08_ = user_reg[1] & _07_; 38 | assign _09_ = ~(_06_ & _08_); 39 | assign _10_ = valid_reg & _05_; 40 | assign _11_ = ~(_09_ & _10_); 41 | assign _00_ = priority_encoder_valid_comb & _11_; 42 | assign user[0] = priority_encoder_user_comb[0] & _00_; 43 | assign user[1] = priority_encoder_user_comb[1] & _00_; 44 | always @(posedge clock) 45 | if (reset) user_reg[0] <= 1'h0; 46 | else if (_00_) user_reg[0] <= priority_encoder_user_comb[0]; 47 | always @(posedge clock) 48 | if (reset) user_reg[1] <= 1'h0; 49 | else if (_00_) user_reg[1] <= priority_encoder_user_comb[1]; 50 | always @(posedge clock) 51 | if (reset) valid_reg <= 1'h0; 52 | else valid_reg <= priority_encoder_valid_comb; 53 | rr_priority_encoder rr_priority_encoder_component ( 54 | .last_user(user_reg), 55 | .request(request), 56 | .user(priority_encoder_user_comb), 57 | .valid(priority_encoder_valid_comb) 58 | ); 59 | assign valid = priority_encoder_valid_comb; 60 | endmodule 61 | 62 | module rr_priority_encoder(request, last_user, valid, user); 63 | wire _00_; 64 | wire _01_; 65 | wire _02_; 66 | wire _03_; 67 | wire _04_; 68 | wire _05_; 69 | wire _06_; 70 | wire _07_; 71 | wire _08_; 72 | wire _09_; 73 | wire _10_; 74 | wire _11_; 75 | wire _12_; 76 | wire _13_; 77 | wire _14_; 78 | wire _15_; 79 | wire _16_; 80 | wire _17_; 81 | wire _18_; 82 | input [1:0] last_user; 83 | wire [1:0] last_user; 84 | input [3:0] request; 85 | wire [3:0] request; 86 | output [1:0] user; 87 | wire [1:0] user; 88 | output valid; 89 | wire valid; 90 | assign _00_ = ~request[1]; 91 | assign _01_ = ~last_user[0]; 92 | assign _02_ = ~(request[2] | request[3]); 93 | assign _03_ = ~(request[1] | request[0]); 94 | assign valid = ~(_02_ & _03_); 95 | assign _04_ = request[0] & last_user[1]; 96 | assign _05_ = ~(_01_ & last_user[1]); 97 | assign _06_ = last_user[0] ^ last_user[1]; 98 | assign _07_ = ~(_04_ | _06_); 99 | assign _08_ = ~(request[1] & _07_); 100 | assign _09_ = ~(_00_ | request[0]); 101 | assign _10_ = request[3] | _09_; 102 | assign _11_ = request[2] | _04_; 103 | assign _12_ = ~(_05_ & _11_); 104 | assign _13_ = ~(_10_ & _12_); 105 | assign user[0] = ~(_08_ & _13_); 106 | assign _14_ = ~(_00_ & _07_); 107 | assign _15_ = request[3] | last_user[0]; 108 | assign _16_ = _03_ | _15_; 109 | assign _17_ = ~(_06_ & _16_); 110 | assign _18_ = _14_ & _17_; 111 | assign user[1] = ~(_02_ | _18_); 112 | endmodule 113 | -------------------------------------------------------------------------------- /src/examples/counter.vhdl: -------------------------------------------------------------------------------- 1 | library IEEE; 2 | use IEEE.STD_LOGIC_1164.ALL; 3 | use IEEE.STD_LOGIC_ARITH.ALL; 4 | use IEEE.STD_LOGIC_UNSIGNED.ALL; 5 | 6 | entity debouncer is 7 | Port ( clock : in STD_LOGIC; 8 | reset : in STD_LOGIC; 9 | button : in STD_LOGIC; 10 | button_debounced : out STD_LOGIC); 11 | end debouncer; 12 | 13 | architecture behavior of debouncer is 14 | signal last_button_reg : STD_LOGIC; 15 | signal counter_reg : STD_LOGIC_VECTOR (15 downto 0); 16 | signal button_debounced_reg : STD_LOGIC; 17 | begin 18 | -- sequential 19 | process(clock, reset, button) begin 20 | if rising_edge(clock) then 21 | if reset='1' then 22 | last_button_reg <= '0'; 23 | counter_reg <= X"0000"; 24 | button_debounced_reg <= '0'; 25 | else 26 | last_button_reg <= button; 27 | 28 | if button=last_button_reg then 29 | if counter_reg=10000 then 30 | button_debounced_reg <= last_button_reg; 31 | else 32 | counter_reg <= counter_reg + 1; 33 | end if; 34 | else 35 | counter_reg <= X"0000"; 36 | end if; 37 | end if; 38 | end if; 39 | end process; 40 | 41 | -- combinatorial 42 | button_debounced <= button_debounced_reg; 43 | end behavior; 44 | 45 | library IEEE; 46 | use IEEE.STD_LOGIC_1164.ALL; 47 | use IEEE.STD_LOGIC_ARITH.ALL; 48 | use IEEE.STD_LOGIC_UNSIGNED.ALL; 49 | 50 | entity counter is 51 | Port ( clock : in STD_LOGIC; 52 | reset : in STD_LOGIC; 53 | button_debounced : in STD_LOGIC; 54 | ones : out STD_LOGIC_VECTOR (3 downto 0); 55 | tens : out STD_LOGIC_VECTOR (3 downto 0)); 56 | end counter; 57 | 58 | architecture behavior of counter is 59 | signal ones_reg : STD_LOGIC_VECTOR (3 downto 0); 60 | signal tens_reg : STD_LOGIC_VECTOR (3 downto 0); 61 | signal button_debounced_reg : STD_LOGIC; 62 | begin 63 | -- sequential 64 | process(clock, reset, button_debounced) begin 65 | if rising_edge(clock) then 66 | if reset='1' then 67 | ones_reg <= X"0"; 68 | tens_reg <= X"0"; 69 | button_debounced_reg <= '0'; 70 | else 71 | button_debounced_reg <= button_debounced; 72 | 73 | if button_debounced='1' and button_debounced_reg='0' then 74 | if ones_reg=X"9" then 75 | ones_reg <= X"0"; 76 | tens_reg <= tens_reg + 1; 77 | else 78 | ones_reg <= ones_reg + 1; 79 | end if; 80 | end if; 81 | end if; 82 | end if; 83 | end process; 84 | 85 | -- combinatorial 86 | ones <= ones_reg; 87 | tens <= tens_reg; 88 | end behavior; 89 | 90 | library IEEE; 91 | use IEEE.STD_LOGIC_1164.ALL; 92 | use IEEE.STD_LOGIC_ARITH.ALL; 93 | use IEEE.STD_LOGIC_UNSIGNED.ALL; 94 | 95 | entity counter_top is 96 | Port ( clock : in STD_LOGIC; 97 | reset : in STD_LOGIC; 98 | button : in STD_LOGIC; 99 | ones : out STD_LOGIC_VECTOR (3 downto 0); 100 | tens : out STD_LOGIC_VECTOR (3 downto 0)); 101 | end counter_top; 102 | 103 | architecture behavior of counter_top is 104 | signal button_debounced : STD_LOGIC; 105 | begin 106 | -- debouncer 107 | debouncer_component : entity work.debouncer 108 | port map( 109 | clock => clock, 110 | reset => reset, 111 | button => button, 112 | button_debounced => button_debounced 113 | ); 114 | 115 | -- counter 116 | counter_component : entity work.counter 117 | port map( 118 | clock => clock, 119 | reset => reset, 120 | button_debounced => button_debounced, 121 | ones => ones, 122 | tens => tens 123 | ); 124 | end behavior; -------------------------------------------------------------------------------- /src/examples/rr_arbiter.v: -------------------------------------------------------------------------------- 1 | module rr_priority_encoder ( 2 | input [3:0] request, 3 | input [1:0] last_user, 4 | output valid, 5 | output [1:0] user 6 | ); 7 | reg valid_comb; 8 | reg [1:0] user_comb; 9 | 10 | always @ (*) begin 11 | // default 12 | valid_comb = 1'b0; 13 | user_comb = 2'd0; 14 | 15 | // naive way 16 | if (last_user == 2'd3) begin 17 | casez (request) 18 | 4'b???1: begin 19 | valid_comb = 1'b1; 20 | user_comb = 2'd0; 21 | end 22 | 4'b??10: begin 23 | valid_comb = 1'b1; 24 | user_comb = 2'd1; 25 | end 26 | 4'b?100: begin 27 | valid_comb = 1'b1; 28 | user_comb = 2'd2; 29 | end 30 | 4'b1000: begin 31 | valid_comb = 1'b1; 32 | user_comb = 2'd3; 33 | end 34 | endcase 35 | end else if (last_user == 2'd0) begin 36 | casez (request) 37 | 4'b??1?: begin 38 | valid_comb = 1'b1; 39 | user_comb = 2'd1; 40 | end 41 | 4'b?10?: begin 42 | valid_comb = 1'b1; 43 | user_comb = 2'd2; 44 | end 45 | 4'b100?: begin 46 | valid_comb = 1'b1; 47 | user_comb = 2'd3; 48 | end 49 | 4'b0001: begin 50 | valid_comb = 1'b1; 51 | user_comb = 2'd0; 52 | end 53 | endcase 54 | end else if (last_user == 2'd1) begin 55 | casez (request) 56 | 4'b?1??: begin 57 | valid_comb = 1'b1; 58 | user_comb = 2'd2; 59 | end 60 | 4'b10??: begin 61 | valid_comb = 1'b1; 62 | user_comb = 2'd3; 63 | end 64 | 4'b00?1: begin 65 | valid_comb = 1'b1; 66 | user_comb = 2'd0; 67 | end 68 | 4'b0010: begin 69 | valid_comb = 1'b1; 70 | user_comb = 2'd1; 71 | end 72 | endcase 73 | end else if (last_user == 2'd2) begin 74 | casez (request) 75 | 4'b1???: begin 76 | valid_comb = 1'b1; 77 | user_comb = 2'd3; 78 | end 79 | 4'b0??1: begin 80 | valid_comb = 1'b1; 81 | user_comb = 2'd0; 82 | end 83 | 4'b0?10: begin 84 | valid_comb = 1'b1; 85 | user_comb = 2'd1; 86 | end 87 | 4'b0100: begin 88 | valid_comb = 1'b1; 89 | user_comb = 2'd2; 90 | end 91 | endcase 92 | end 93 | 94 | end 95 | 96 | assign valid = valid_comb; 97 | assign user = user_comb; 98 | 99 | endmodule 100 | 101 | module rr_arbiter ( 102 | input clock, 103 | input reset, 104 | 105 | input [3:0] request, 106 | output valid, 107 | output [1:0] user 108 | ); 109 | reg [1:0] user_reg; 110 | reg valid_reg; 111 | 112 | reg [1:0] user_comb; 113 | reg [1:0] priority_encoder_user_comb; 114 | 115 | rr_priority_encoder rr_priority_encoder_inst ( 116 | .request(request), 117 | .last_user(user_reg), 118 | .valid(valid), 119 | .user(priority_encoder_user_comb) 120 | ); 121 | 122 | // sequential 123 | always @ (posedge clock) begin 124 | if (reset) begin 125 | user_reg <= 2'd0; 126 | valid_reg <= 1'b0; 127 | end else begin 128 | valid_reg <= valid; 129 | if (!valid_reg && valid) begin 130 | // case 1: non valid -> valid 131 | user_reg <= priority_encoder_user_comb; 132 | end else if (valid_reg && valid && request[user_reg]) begin 133 | // case 2: persist 134 | end else if (valid_reg && valid && !request[user_reg]) begin 135 | // case 3: next user 136 | user_reg <= priority_encoder_user_comb; 137 | end 138 | end 139 | end 140 | 141 | // combinatorial 142 | always @ (*) begin 143 | // default 144 | user_comb = 2'b0; 145 | if (!valid_reg && valid) begin 146 | // case 1: non valid -> valid 147 | user_comb = priority_encoder_user_comb; 148 | end else if (valid_reg && valid && request[user_reg]) begin 149 | // case 2: persist 150 | user_comb = user_reg; 151 | end else if (valid_reg && valid && !request[user_reg]) begin 152 | // case 3: next user 153 | user_comb = priority_encoder_user_comb; 154 | end 155 | end 156 | 157 | assign user = user_comb; 158 | 159 | endmodule -------------------------------------------------------------------------------- /src/examples/rr_arbiter.vhdl: -------------------------------------------------------------------------------- 1 | library IEEE; 2 | use IEEE.STD_LOGIC_1164.ALL; 3 | use IEEE.STD_LOGIC_ARITH.ALL; 4 | use IEEE.STD_LOGIC_UNSIGNED.ALL; 5 | 6 | entity rr_priority_encoder is 7 | Port ( request : in STD_LOGIC_VECTOR (3 downto 0); 8 | last_user : in STD_LOGIC_VECTOR (1 downto 0); 9 | valid : out STD_LOGIC; 10 | user : out STD_LOGIC_VECTOR (1 downto 0)); 11 | end rr_priority_encoder; 12 | 13 | architecture behavior of rr_priority_encoder is 14 | begin 15 | process (request, last_user) begin 16 | -- default 17 | valid <= '0'; 18 | user <= "00"; 19 | 20 | -- naive way 21 | if last_user="11" then 22 | if request(0)='1' then 23 | valid <= '1'; 24 | user <= "00"; 25 | elsif request(1)='1' then 26 | valid <= '1'; 27 | user <= "01"; 28 | elsif request(2)='1' then 29 | valid <= '1'; 30 | user <= "10"; 31 | elsif request(3)='1' then 32 | valid <= '1'; 33 | user <= "11"; 34 | end if; 35 | elsif last_user="00" then 36 | if request(1)='1' then 37 | valid <= '1'; 38 | user <= "01"; 39 | elsif request(2)='1' then 40 | valid <= '1'; 41 | user <= "10"; 42 | elsif request(3)='1' then 43 | valid <= '1'; 44 | user <= "11"; 45 | elsif request(0)='1' then 46 | valid <= '1'; 47 | user <= "00"; 48 | end if; 49 | elsif last_user="01" then 50 | if request(2)='1' then 51 | valid <= '1'; 52 | user <= "10"; 53 | elsif request(3)='1' then 54 | valid <= '1'; 55 | user <= "11"; 56 | elsif request(0)='1' then 57 | valid <= '1'; 58 | user <= "00"; 59 | elsif request(1)='1' then 60 | valid <= '1'; 61 | user <= "01"; 62 | end if; 63 | elsif last_user="10" then 64 | if request(3)='1' then 65 | valid <= '1'; 66 | user <= "11"; 67 | elsif request(0)='1' then 68 | valid <= '1'; 69 | user <= "00"; 70 | elsif request(1)='1' then 71 | valid <= '1'; 72 | user <= "01"; 73 | elsif request(2)='1' then 74 | valid <= '1'; 75 | user <= "10"; 76 | end if; 77 | end if; 78 | end process; 79 | end behavior; 80 | 81 | library IEEE; 82 | use IEEE.STD_LOGIC_1164.ALL; 83 | use IEEE.STD_LOGIC_ARITH.ALL; 84 | use IEEE.STD_LOGIC_UNSIGNED.ALL; 85 | 86 | entity rr_arbiter is 87 | Port ( clock : in STD_LOGIC; 88 | reset : in STD_LOGIC; 89 | request : in STD_LOGIC_VECTOR (3 downto 0); 90 | valid : out STD_LOGIC; 91 | user : out STD_LOGIC_VECTOR (1 downto 0)); 92 | end rr_arbiter; 93 | 94 | architecture behavior of rr_arbiter is 95 | signal user_reg : STD_LOGIC_VECTOR (1 downto 0); 96 | signal valid_reg : STD_LOGIC; 97 | signal priority_encoder_valid_comb : STD_LOGIC; 98 | signal priority_encoder_user_comb : STD_LOGIC_VECTOR (1 downto 0); 99 | begin 100 | -- rr_priority_encoder 101 | rr_priority_encoder_component : entity work.rr_priority_encoder 102 | port map( 103 | request => request, 104 | last_user => user_reg, 105 | valid => priority_encoder_valid_comb, 106 | user => priority_encoder_user_comb 107 | ); 108 | 109 | -- sequential 110 | process (clock, reset) begin 111 | if clock='1' and clock'event then 112 | if reset='1' then 113 | user_reg <= "00"; 114 | valid_reg <= '0'; 115 | else 116 | valid_reg <= priority_encoder_valid_comb; 117 | if valid_reg='0' and priority_encoder_valid_comb='1' then 118 | -- case 1: non valid -> valid 119 | user_reg <= priority_encoder_user_comb; 120 | elsif valid_reg='1' and priority_encoder_valid_comb='1' and request(conv_integer(user_reg))='1' then 121 | -- case 2: persist 122 | elsif valid_reg='1' and priority_encoder_valid_comb='1' and request(conv_integer(user_reg))='0' then 123 | -- case 3: next user 124 | user_reg <= priority_encoder_user_comb; 125 | end if; 126 | end if; 127 | end if; 128 | end process; 129 | 130 | -- combinatorial 131 | process (valid_reg, priority_encoder_valid_comb, request, user_reg, priority_encoder_user_comb) begin 132 | -- default 133 | user <= "00"; 134 | 135 | if valid_reg='0' and priority_encoder_valid_comb='1' then 136 | -- case 1: non valid -> valid 137 | user <= priority_encoder_user_comb; 138 | elsif valid_reg='1' and priority_encoder_valid_comb='1' and request(conv_integer(user_reg))='1' then 139 | -- case 2: persist 140 | elsif valid_reg='1' and priority_encoder_valid_comb='1' and request(conv_integer(user_reg))='0' then 141 | -- case 3: next user 142 | user <= priority_encoder_user_comb; 143 | end if; 144 | 145 | valid <= priority_encoder_valid_comb; 146 | end process; 147 | end behavior; -------------------------------------------------------------------------------- /docs/img/pipeline_structure.svg: -------------------------------------------------------------------------------- 1 | 输入解析流水级1流水级2流水级n流输入输出生成流输出控制逻辑……外设控制 -------------------------------------------------------------------------------- /docs/hardware/board_xilinx.md: -------------------------------------------------------------------------------- 1 | # 实验板(Xilinx FPGA 版) 2 | 3 | ## 实验板外观 4 | 5 | !!! warning "了解硬件" 6 | 7 | 在开始进行你的实验之前,请务必仔细阅读本章,了解实验板各组件的功能,防止错误的硬件连接损坏实验板。 8 | 9 | 《数字逻辑设计》课程所用的实验板(Xilinx FPGA 版)外观如图: 10 | 11 | ![Board](../img/board_xilinx.png) 12 | 13 | 实验时可能用到的组件标号如下图,具体描述及功能请阅读下一节了解。 14 | 15 | ![Board with annotation](../img/board_xilinx_anno.png) 16 | 17 | ## 各组件说明 18 | 19 | 图片中标注的组件描述如下: 20 | 21 | 1. 千兆 RJ45 以太网接口(LAB\_ETH):使用 RTL8211 PHY 芯片,通过 RGMII 总线与 FPGA 相连; 22 | 2. PS/2 鼠标接口(LAB\_MOUSE); 23 | 3. PS/2 键盘接口(LAB\_KBD); 24 | 4. HDMI 视频接口(LAB\_HDMI):最高支持 1080P 24 位色,样例代码请参考工程模板; 25 | 5. RS-232 串口(LAB\_RS232):可连接使用 RS-232 电平的串口外设; 26 | 6. USB 转 TTL 串口(LAB\_UART):连接电脑可用于 FPGA 的调试输出; 27 | 7. SD 卡座(LAB\_SD):建议使用小容量低速 SD 卡,并通过 SPI 协议访问;按照标准,除了 SDUC 以外,其他的 SD 卡都应该支持 SPI 协议,但一些 SD 卡型号违背了标准,不支持 SPI 协议,见 [来源 1](https://forum.4dsystems.com.au/node/1869) 和 [来源 2](https://github.com/MarlinFirmware/Marlin/issues/2082#issuecomment-102381964); 28 | 8. 32 位 LED 灯:用于调试,使用方法请参考工程模板; 29 | 9. 8 位扫描式数码管(DPY1-2):使用方法请参考工程模板; 30 | 10. 512MB DDR3-1866 SDRAM 内存:512 Mb x 8,型号为 [MT41K512M8RH-107IT](https://media-www.micron.com/-/media/client/global/documents/products/data-sheet/dram/ddr3/4gb_automotive_ddr3l.pdf); 31 | 11. 4MB(并行)SRAM 内存:32 位宽,理论延迟为 10ns,是两片型号为 [IS61WV102416BLL-10TLI](https://www.issi.com/WW/pdf/61WV102416ALL.pdf) 各 2MB 的 SRAM 数据线并联而成(两组 SRAM 连接同样的 `addr` `ce_n` `we_n` `oe_n` 信号,两组 `data` 拼接成为 32 位,两对 `ub_n` `lb_n` 组合成了 4 位的 `be_n`); 32 | 12. 16MB SPI NOR Flash:型号为 [W25Q128JVSIQTR](https://www.winbond.com/hq/product/code-storage-flash-memory/serial-nor-flash/?__locale=en&partNo=W25Q128JV); 33 | 13. 8MB SPI SRAM 内存:型号为 [VTI7064](https://www.lcsc.com/datasheet/lcsc_datasheet_1811151432_Vilsion-Tech-VTI7064MSME_C139966.pdf); 34 | 14. FPGA JTAG 调试接口(LAB\_JTAG):用于连接 Xilinx 下载器下载程序; 35 | 15. 2 个带去抖按键(CLK、RST):自带硬件去抖电路,按下时为高电平; 36 | 16. 4 个带去抖按键(KEY1-4):自带硬件去抖电路,按下时为高电平; 37 | 17. 16 位拨码开关(LAB_SW1-2):拨上时为低电平; 38 | 18. 电源输出接口:可用于给外设模块供电,支持 5V 和 3.3V 两种电压; 39 | 19. 电源输入接口:**只允许** 连接提供的 12V 直流电源,用于给实验板供电。 40 | 20. USB 控制接口(CTL\_OTG):连接电脑,可用于读写 SRAM 内存; 41 | 42 | 另外,我们还提供了 5 个 Pmod 扩展接口,可连接兼容 Pmod 标准的外设模块,或使用杜邦线连接其他外设模块。具体使用方法参见 [常见外设](peripheral.md) 章节。 43 | 44 | FPGA 型号是 [XC7A200T-2FBG484I](https://docs.amd.com/v/u/en-US/7-series-product-selection-guide),内置资源有: 45 | 46 | - Logic Cells: 215360 47 | - Total Block RAM: 13140 Kb 48 | - DSP Slices: 740 49 | - PLL: 10 50 | 51 | ## 控制模块使用方法 52 | 53 | 实验板上提供了用于读写 SRAM 内存的控制器模块,通过 USB 接口与电脑连接。使用时,先将开发板上电,等待 30-50s 后,数码管将会显示为 "00000000",此时控制模块已经启动完成。在控制模块启动完成之前,不建议烧写 bitstream 到 FPGA,否则烧写的内容可能会被控制模块覆盖。 54 | 55 | 将 USB 与板上的 USB 控制接口(图中 20 号,CTL_OTG)相连,开发板将会虚拟成为一个以太网卡。计算机通常会自动从该网卡使用 DHCP 获取 IP,获取到 IP 后,在 Chrome 或 Firefox 等现代浏览器中打开 http://192.168.9.9/ (注意不是 https)即可访问实验板控制面板。 56 | 57 | 需要注意的是,部分 macOS 系统连到控制模块后,看不到虚拟的以太网卡。此时可以起 Linux 虚拟机,把 USB 设备直通到虚拟机中,然后在虚拟机里访问控制模块。 58 | 59 | 如果发现控制模块一直启动失败,可以确认一下控制模块上的拨码(显示 ON、1 和 2)的位置,1 对应的拨码应当在 1 这一侧,2 对应的拨码应当在 ON 这一侧(对应 MIO5=1,MIO4=0,SPI 模式)。如果拨码正确,复位后还是不工作,请联系助教。 60 | 61 | 注意控制模块也有自己的 JTAG 插座,和实验 FPGA 的 JTAG 插座的外型一样的,只是位置不同。如果错误地插到控制模块的 JTAG 上,Vivado 将会看到 xc7z020 的 FPGA,而不是正确的 xc7a200t。 62 | 63 | ### SRAM 读写操作 64 | 65 | 实验板控制面板的主要功能为对板载的 4MB SRAM 进行读写操作。在面板上输入读写起始地址,选择电脑上的二进制文件,即可向 SRAM 中写入数据;输入起始地址和长度,即可从 SRAM 中读取数据,并显示在网页上,或下载为二进制文件。需要注意的是,读写 SRAM 时,为了避免出现冲突,实验 FPGA 会“复位”到数码管显示 "00000000"的状态。之后如果想要运行自己的设计,需要重新下载 Bitstream。 66 | 67 | ![](./control.png) 68 | 69 | 读写起始地址均要求为十六进制,且地址和长度均须为 4 的整数倍数,即至少要完整写入或读出 SRAM 的一个 32 位字。 70 | 71 | !!! danger "注意格式" 72 | 73 | 该 SRAM 读写工具与 Vivado 的片内 ROM 初始化工具不同,使用的不是 COE 文件,而是原始的二进制文件。二进制文件中的每个字节,将会被原样写入 SRAM 中的对应位置中。因此,如果要写入一张图片等数据,请自行编写程序,按照需求生成二进制图片文件。 74 | 75 | !!! note "注意端序" 76 | 77 | 该工具读写内存使用的是小端序,同时使用的地址单位为字节,而非 SRAM 的 32 位字。即 SRAM 每个地址读出的 32 位数据中,最低 8 位为写入的二进制文件靠前的字节,最高 8 位为写入的二进制文件靠后的字节,以此类推。 78 | 79 | 80 | ## macOS 用户 81 | 82 | 如果你使用 macOS,由于 Vivado 不支持 macOS,需要在虚拟机或者远程 Linux 环境下运行 Vivado,用 VSCode Remote 进行远程开发。生成 Bitstream .bit 文件后(路径为项目目录下的 `project-template-xilinx.runs/impl_1/mod_top.bit`),复制到本地,再用 openFPGALoader 下载: 83 | 84 | ```shell 85 | # in macOS 86 | $ brew install openfpgaloader 87 | $ openFPGALoader -c ft2232 --detect 88 | empty 89 | Jtag frequency : requested 6.00MHz -> real 6.00MHz 90 | index 0: 91 | idcode 0x3636093 92 | manufacturer xilinx 93 | family artix a7 200t 94 | model xc7a200 95 | irlength 6 96 | $ scp linux:/path/to/mod_top.bit . 97 | $ openFPGALoader -c ft2232 --fpga-part xc7a200t mod_top.bit 98 | ``` 99 | 100 | 另一种办法是,在 macOS 上运行 jtag-remote-server,通过 xvc 协议暴露出 FPGA 的 JTAG 接口,通过网络转发到 Linux 机器上,再让 Vivado 通过 xvc 连接 FPGA。这样的好处是 ILA 等功能也可以正常使用,缺点是受网络延迟和带宽影响比较大。 101 | 102 | 更进一步,还可以在虚拟机内启动 Linux,在 Linux 虚拟机内启动 Xilinx Vivado 的 `hw_server` 程序,把下载器的 USB 设备直通到 Linux 虚拟机中,然后再设法把 `hw_server` 监听的 3121 端口转发到实际运行 Vivado 的 Linux 系统上,就可以获得比较好的 Vivado 使用体验。 103 | 104 | 此外,部分 macOS 系统无法连接到控制模块,可以使用 USB 直通到 Linux 虚拟机的方法。 105 | 106 | 107 | ## 与 Intel FPGA 版实验板的区别 108 | 109 | Xilinx FPGA 版实验板是 2023 年新设计的数字逻辑设计实验开发板,相比上一个 Intel FPGA 版实验板,做出了如下的改变: 110 | 111 | 1. FPGA 从 Intel 的 EP4CE115F29I7 换为 Xilinx 的 XC7A200T-2FBG484I,新旧 FPGA 对比,制程从 60nm 提升到 28nm,同时逻辑单元数量提升到 2 倍,内置的 Block RAM 容量提升到 3 倍,可以给同学的设计留下更充足的空间; 112 | 2. 将 32MB SDR SDRAM 内存升级到 512MB DDR3 SDRAM 内存,在速度和容量上获得了巨大的提升; 113 | 3. 把 PS/2 接口扩展到两个,不再需要鼠标键盘二选一,可以全都要; 114 | 4. 由于引脚个数限制,减少了 PMOD 扩展接口的个数:从 8 减少到 5,不过不用担心,根据往年的经验,5 个也是用不完的; 115 | 5. 由于引脚个数限制,减少了一组 SRAM,只剩下了一组(并行)SRAM(实际上 Intel FPGA 版实验板也只焊接了一组),不过作为补偿,添加了 16MB 的 SPI NOR Flash 和 8MB 串行 SPI SRAM 内存。 116 | 117 | 备注:XC7A200T 是 Xilinx Artix 7 系列里最高端的一款 FPGA。Xilinx(AMD) FPGA 一共有这些产品系列: 118 | 119 | 1. Spartan-6:45nm 120 | 2. 7 Series:28nm,从低端到高端依次是 Spartan 7,Artix 7,Kintex 7,Virtex 7 121 | 3. UltraScale:20nm,从低端到高端依次是 Kintex UltraScale,Virtex UltraScale 122 | 4. UltraScale+:16nm,从低端到高端依次是 Spartan UltraScale+,Artix UltraScale+,Kintex UltraScale+,Virtex UltraScale+ 123 | 124 | 此外,Xilinx(AMD) 还有系列 SoC 产品,它在 FPGA 旁边还加入了 ARM 处理器核心,有这些产品系列: 125 | 126 | 1. Zynq 7000:对应 7 Series,28nm 127 | 2. Zynq UltraScale+ MPSoC/RFSoC:对应 UltraScale+,16nm 128 | 3. Versal: 7nm -------------------------------------------------------------------------------- /docs/hdl-by-example/button.md: -------------------------------------------------------------------------------- 1 | # 按钮开关 2 | 3 | ## 需求 4 | 5 | 让我们来实现一个控制台灯的按钮开关:按下开关的时候,灯会亮起来;再次按下开关的时候,灯就熄灭了。 6 | 7 | 根据上面的需求,可以设计如下的输入输出信号: 8 | 9 | 输入: 10 | 11 | 1. `button`:1 表示按钮被按下,0 表示按钮处于弹起的状态 12 | 13 | 输出: 14 | 15 | 1. `light`:1 表示灯亮起,0 表示灯熄灭 16 | 17 | ## 波形 18 | 19 | 分析上面的需求,可以发现,此时不再满足我们刚刚学到的 **组合逻辑** 的特点,虽然输出(`light`)也是随着输入信号变化而变化,但是第一次按的时候输出 `1`,第二次按的时候输出了 `0`,说明输出不仅随着输入变化而变化。 20 | 21 | 仔细一想,我们可以发现一个规律:按钮被按下的时候,灯会从亮起到熄灭,或者从熄灭到亮起,说明输出的值是之前输出的值取反。而按钮按下(从 `0` 变成 `1`)是用来触发取反的动作。 22 | 23 | 根据分析,可以得到下面的波形: 24 | 25 | 34 | 35 | ## 电路 36 | 37 | 经过分析,可以发现 `light` 输出与它本身的历史状态有关,并且正好是取反的关系。如果我们依然采用组合逻辑来实现,就会写出形如 `light <= ~light;` 的代码,对应的电路就出现了环路,此时 `light` 会不断在 `0` 和 `1` 之间震荡,不能实现我们想要的效果。 38 | 39 | 对于这一类 **输出与历史状态相关**,并且 **输出在某个信号的上升沿变化** 的信号,我们通常使用 **时序逻辑** 来实现。那么时序逻辑对应什么样的电路呢?回顾一下,只有触发器的真值表中出现了上升沿: 40 | 41 | | 输入 D | 输入 C | 输出 Q | 42 | | ---- | -------- | ---- | 43 | | X | 0 | 维持不变 | 44 | | X | 1 | 维持不变 | 45 | | 0 | 0->1 上升沿 | 0 | 46 | | 1 | 0->1 上升沿 | 1 | 47 | 48 | 这时候,我们把 `button` 连接到触发器的 `C` 端口,就实现了上升沿触发的目的;为了实现每次触发,让输出的结果取反,可以把触发器的 `Q` 经过一个非门(NOT)再连接到触发器的 `D` 端口: 49 | 50 | ![](imgs/button.svg) 51 | 52 | !!! question "这个电路图不也成了一个环吗,为什么不会出现循环振荡?" 53 | 54 | 这就是引入触发器的作用。用通俗的方式理解,可以认为组合电路是持续在变化,才会出现 A 变化导致 B 变化,B 变化导致 A 变化,一直循环;而触发器使得只有在时钟上升沿的时候,触发器的输入 D 会引发输出 Q 的变化,而当时钟上升沿结束以后,输出 Q 也许会导致输入 D 变化(比如上面的例子),但此时时钟已经稳定了,此时输出 Q 是稳定不变的,因此不会出现循环振荡的问题。 55 | 56 | ## 代码 57 | 58 | 最后再用 HDL 来实现如上的功能。之前我们已经学过,组合电路比较简单,直接把计算结果 **连接** 到输出即可。但时序逻辑里,我们需要显式的声明一个寄存器(对应电路里的触发器),并且 **严格** 按照下面的方式,把信号 **连接** 到触发器的输入 D 端口。 59 | 60 | === "System Verilog" 61 | 62 | 首先,还是根据前面确定的输出信号编写 `module`: 63 | 64 | ```verilog 65 | module button ( 66 | input wire button, 67 | output wire light 68 | ); 69 | // TODO 70 | endmodule 71 | ``` 72 | 73 | 这里的输入输出信号都只有 `1` 位,所以就直接写 `input wire button` 和 `output wire light` 即可,不需要写 `[n-1:0]`。 74 | 75 | 接着,我们要把电路的实现放在 `module` 中。前面提到过,我们需要显式声明一个触发器,称为 `light_reg`: 76 | 77 | ```sv 78 | logic light_reg; 79 | assign light = light_reg; 80 | ``` 81 | 82 | 然后采用 `assign light = light_reg` 语句把触发器的输出 Q 端口连接到输出信号 `light` 上。那么,接下来我们要实现 `light_reg` 在 `button` 上升沿的时候,将当前的值取反: 83 | 84 | ```sv 85 | always_ff @ (posedge button) begin 86 | light_reg <= ~light_reg; 87 | end 88 | ``` 89 | 90 | 可以看到这里引入了一个 `always_ff @ (posedge button)` 块,表示这一块内部的逻辑在 `button` 的上升沿触发。然后在 `always_ff` 块的内部,编写代码 `light_reg <= ~light_reg` 来实现更新。注意,这里的意思是把 `light_reg` 的输出 Q 经过非门连接到 `light_reg` 的输入 D 中。换句话说,出现在 `<=` 右侧的都是触发器的输出 Q 端口,而出现在 `<=` 左侧的都是触发器的输入 D 端口。 91 | 92 | 再次提醒同学,这里的 `<=` 要理解为信号的连接,而不是软件编程中的赋值。 93 | 94 | === "Verilog" 95 | 96 | 首先,还是根据前面确定的输出信号编写 `module`: 97 | 98 | ```verilog 99 | module button ( 100 | input wire button, 101 | output wire light 102 | ); 103 | // TODO 104 | endmodule 105 | ``` 106 | 107 | 这里的输入输出信号都只有 `1` 位,所以就直接写 `input wire button` 和 `output wire light` 即可,不需要写 `[n-1:0]`。 108 | 109 | 接着,我们要把电路的实现放在 `module` 中。前面提到过,我们需要显式声明一个触发器,称为 `light_reg`: 110 | 111 | ```verilog 112 | reg light_reg; 113 | assign light = light_reg; 114 | ``` 115 | 116 | 然后采用 `assign light = light_reg` 语句把触发器的输出 Q 端口连接到输出信号 `light` 上。那么,接下来我们要实现 `light_reg` 在 `button` 上升沿的时候,将当前的值取反: 117 | 118 | ```verilog 119 | always @ (posedge button) begin 120 | light_reg <= ~light_reg; 121 | end 122 | ``` 123 | 124 | 可以看到这里引入了一个 `always @ (posedge button)` 块,表示这一块内部的逻辑在 `button` 的上升沿触发。然后在 `always` 块的内部,编写代码 `light_reg <= ~light_reg` 来实现更新。注意,这里的意思是把 `light_reg` 的输出 Q 经过非门连接到 `light_reg` 的输入 D 中。换句话说,出现在 `<=` 右侧的都是触发器的输出 Q 端口,而出现在 `<=` 左侧的都是触发器的输入 D 端口。 125 | 126 | 再次提醒同学,这里的 `<=` 要理解为信号的连接,而不是软件编程中的赋值。 127 | 128 | 129 | === "VHDL" 130 | 131 | 首先,还是根据前面确定的输出信号编写 `entity`: 132 | 133 | ```vhdl 134 | library IEEE; 135 | use IEEE.STD_LOGIC_1164.ALL; 136 | 137 | entity button is 138 | Port ( button : in STD_LOGIC; 139 | light : out STD_LOGIC); 140 | end button; 141 | ``` 142 | 143 | 这里的输入输出信号都只有 `1` 位,所以可以这里用 `STD_LOGIC`;如果有更多位,则要用 `STD_LOGIC_VECTOR (n-1 downto 0)` 表示 `n` 位的信号。 144 | 145 | 接着,我们要把电路的实现放在 `architecture` 中。前面提到过,我们需要显式声明一个触发器,称为 `light_reg`: 146 | 147 | ```vhdl 148 | architecture behavior of button is 149 | signal light_reg : STD_LOGIC; 150 | begin 151 | -- sequential 152 | -- how to write? 153 | 154 | -- combinatorial 155 | light <= light_reg; 156 | end behavior; 157 | ``` 158 | 159 | 然后采用 `light <= light_reg` 语句把触发器的输出 Q 端口连接到输出信号 `light` 上。那么,接下来我们要实现 `light_reg` 在 `button` 上升沿的时候,将当前的值取反: 160 | 161 | ```vhdl 162 | architecture behavior of button is 163 | signal light_reg : STD_LOGIC; 164 | begin 165 | -- sequential 166 | process(button) 167 | begin 168 | if button='1' and button'event then 169 | light_reg <= not light_reg; 170 | end if; 171 | end process; 172 | 173 | -- combinatorial 174 | light <= light_reg; 175 | end behavior; 176 | ``` 177 | 178 | 可以看到这里引入了一个 `process(button)` 块,表示这一块内部的逻辑敏感于 `button` 信号;VHDL 表示上升沿的方式是 `button='1' and button'event`,可以理解为,`button` 变成了 `1`,并且这是一个边沿事件(event),也就是从 `0` 变成了 `1`,即上升沿。 179 | 180 | !!! tip "使用 VHDL 的 rising_edge()" 181 | 182 | 事实上 `button='1' and button'event` 可以改写成更简洁的 `rising_edge(button)`,详细对比见 [clk'event vs rising_edge()](https://stackoverflow.com/questions/15205202/clkevent-vs-rising-edge) 183 | 184 | 然后在 `if` 判断的内部,编写代码 `light_reg <= not light_reg` 来实现更新。注意,这里的意思是把 `light_reg` 的输出 Q 经过非门连接到 `light_reg` 的输入 D 中。换句话说,出现在 `<=` 右侧的都是触发器的输出 Q 端口,而出现在 `<=` 左侧的都是触发器的输入 D 端口。 185 | 186 | 再次提醒同学,这里的 `<=` 要理解为信号的连接,而不是软件编程中的赋值。 187 | 188 | ## 总结 189 | 190 | 回顾一下时序逻辑电路,它和组合逻辑电路最大的区别在于,它可以 **记录历史,并且在一定的条件(输入信号 C 的上升沿)下触发更新** 。根据这个特点,我们就可以保存状态,并且随着时间的推进,根据当前的内部状态和外部输入,在上升沿事件的“带领”下更新内部状态。实际电路中,我们通常需要思考和规划哪些地方使用时序逻辑,哪些地方使用组合逻辑。在之后,我们也会讲到一些分析的技巧。 191 | 192 | -------------------------------------------------------------------------------- /docs/hdl-by-example/imgs/counter.svg: -------------------------------------------------------------------------------- 1 | counterbutton_debouncedclockresetonestensdebouncerbuttonclockresetbutton_debouncedclockresetbuttononestens/4//4/ -------------------------------------------------------------------------------- /docs/hardware/onboard_xilinx.md: -------------------------------------------------------------------------------- 1 | # 板载外设(Xilinx FPGA 版) 2 | 3 | ## HDMI 接口 4 | 5 | 实验板上提供了 HDMI 视频输出接口,用于输出视频信号。FPGA 直接产生 HDMI 信号并输出。我们的工程模板中已经提供了输出视频的样例代码,代码使用的是 800x600 分辨率,72Hz 的视频格式,此时像素时钟正好是 50MHz。 6 | 7 | 其他支持的视频格式,可参考 [VGA 时序列表](http://tinyvga.com/vga-timing),注意修改分辨率时,需要同时修改 `video` 模块例化中的时序参数以及 `rgb2dvi` IP 的设置(注意 TMDS clock range 选项),如无必要,不建议使用样例之外的时序格式。受 FPGA 限制(见 [Artix 7 Datasheet](https://docs.amd.com/v/u/en-US/ds181_Artix_7_Data_Sheet)),像素时钟不应超过 136 MHz(`F_{MAX\_BUFIO} / 5 = 680 / 5 = 136`)。 8 | 9 | 由于 VGA 和 HDMI 只是物理接口和传输数据的方式不同,而传输的数据、时序都是一样的,所以本实验中 VGA 或者 HDMI 实际上说的是同一回事。 10 | 11 | 样例代码中使用了如下参数,和 [VESA Signal 800 x 600 @ 72 Hz](http://tinyvga.com/vga-timing/800x600@72Hz) 对应关系如下: 12 | 13 | - Pixel clock: 50MHz 14 | - HSIZE: 800 (Visible Area) 15 | - HFP: 856 = 800 (Visible Area) + 56 (Front proch) 16 | - HSP: 976 = 856 + 120 (Sync pulse) 17 | - HMAX: 1040 (Whole line) 18 | - VSIZE: 600 (Visible Area) 19 | - VFP: 637 = 600 (Visible Area) + 37 (Front porch) 20 | - VSP: 643 = 637 + 6 (Sync pulse) 21 | - VMAX: 666 (Whole frame) 22 | 23 | !!! warning "使用显存" 24 | 25 | 在工程模板中,使用了 VGA 时序模块 `video.v` 输出的横纵坐标信息,驱动显示了 24 位彩色条。在实际项目开发中,严禁此类渲染逻辑,请使用显存作为图像的缓冲区。如有必要,可以采用双缓存、ring buffer 等机制,改善渲染的效果。 26 | 27 | !!! warning "不建议用除法和取模" 28 | 29 | 在使用 VGA/HDMI 输出的时候,可能下意识想对坐标进行除法或者取模运算。但是使用组合逻辑实现除法和取模,需要很大的面积,时序上也会产生很长的路径。正确做法是,由于坐标的更新是有规律的,可以在每次更新坐标的同时,数周期,设置一个更新比较慢的寄存器,每 n 个周期才加 1,这样就可以持续保持和坐标有一个倍数关系。取模也是类似的。 30 | 31 | !!! note "降低色深和分辨率" 32 | 33 | 显存是项目中对内存消耗最大的模块。例如样例中使用的 800x600 24 位色,将会占用 `800*600*24/8/1048576 = 1.37MB` 的内存资源。而大部分设计中,无需使用 24 位真彩色(RGB888 格式),而是采用 RGB565 格式,甚至更低的色深(例如 RGB232),以尽量减少内存资源占用。对于低色深的图片,输出时可直接将其连接至视频数据的高位,将低位置 0;或采用各类转换算法完成色深之间的线性插值转换。有关这些颜色格式和相互转换,可参考 [博客文章](http://www.barth-dev.de/about-rgb565-and-how-to-convert-into-it/)。 34 | 35 | 除了降低色深以外,也可以降低分辨率,例如显存只保存 400x300 个像素,然后在输出到 VGA/HDMI 的时候,在 x 和 y 方向上重复输出像素。 36 | 37 | 结合色深和分辨率的压缩,可以在牺牲一定显示效果的代价下,把显存放到 FPGA 内部进行存储,此时可以用一个双端口的 RAM 作为显存。 38 | 39 | 不用太追求分辨率和 24 位色,如果担心效果不好,就自己在电脑上用图片测试一下降分辨率和颜色深度的效果,看看是否能接受。 40 | 41 | 也可以把显存放到 FPGA 外部,例如 SRAM 和 SDRAM,此时需要考虑的是如何把读取内存的逻辑与 VGA/HDMI 控制器连接起来。由于 SRAM/SDRAM 只有单端口,不能同时进行读和写操作,可以使用仲裁的方式,当 VGA/HDMI 控制器不需要读的时候(例如在消隐区),其他逻辑就可以对内存进行写入。如果把 SRAM/SDRAM 用于显存,从 SRAM/SDRAM 读取的数据要通过 VGA/HDMI 显示到显示器上,那么需注意 SRAM/SDRAM 控制器的时钟和显示输出的像素时钟,如果这两个时钟不是同一个,就需要考虑跨时钟域的问题。如果想避免跨时钟域的问题,可以让 SRAM 控制器以显示输出的像素时钟作为时钟,不过代价是读写 SRAM 需要的时间变长,设计上需要做一些取舍;SDRAM 控制器通常无法简单地任意设置时钟频率,因此需要针对跨时钟域进行处理。 42 | 43 | !!! note "不要猜,动动手,算一下!" 44 | 45 | 在软件上,图片是用一个数组来表示的,要修改这个数组,很自然地会想到用一个循环去更新它的内容。而在硬件中,要实现类似的更新,则会用状态机的方式,每个周期修改一点,用很多个周期完成整个更新。但你可能会担心,每个周期只改一点,例如只改一个像素,把整个 800x600 的画面都更新一遍,会不会很慢? 46 | 47 | 不要猜,动动手,算一下!假设 800x600 的画面,每个周期更新一个像素,那就需要 `800 * 600 * 1 = 480000` 个周期,这个周期是在 50MHz 的频率下,那么换算成时间,就是 `480000 / 50 / 1000000 = 0.0096`,也就是 9.6 ms,人眼是看不出来的。所以不要猜一个事情是快还是慢,既然知道频率,要用多少个周期也可以算出来,自己动手乘一下,就可以知道到底多快多慢了。 48 | 49 | ## SDRAM 内存 50 | 51 | 实验板上提供了 512MB 容量的 DDR3 SDRAM 内存,型号为 MT41K512M8RH-107IT,关于它的特性可参考 [数据手册](https://media-www.micron.com/-/media/client/global/documents/products/data-sheet/dram/ddr3/4gb_automotive_ddr3l.pdf)。SDRAM 采用较为复杂的同步访问时序,可以支持更高的时钟频率,但需要使用多个周期,并且通过专用的控制器进行访问,有关它与 SRAM 内存的区别,请参考 [文章](http://www.differencebetween.net/object/difference-between-sram-and-sdram/)。 52 | 53 | 在实验框架的 `sdram` 分支中,已经给出了一个 SDRAM 内存的访问示例,它例化了 Xilinx Vivado 提供的 MIG IP,通过它的 User Interface 接口读写 SDRAM 内存,这个接口分为三部分: 54 | 55 | 1. Command:把 Command 以及内存地址传给 MIG,Command 可以是读或者写 56 | 2. Write Data:把要写入的数据传给 MIG 57 | 3. Read Data:MIG 会输出从 SDRAM 读取到的内存数据 58 | 59 | 因此,要完成一次写操作,要在 Command 接口上发送写命令,并且在 Write Data 接口上传要写入的数据;要完成一次读操作,要在 Command 接口上发送读命令,在 Read Data 接口上等待读取完成并获取数据。 60 | 61 | 完整的接口描述,请阅读 [Zynq-7000 SoC and 7 Series Devices Memory Interface Solutions](https://docs.amd.com/v/u/en-US/ug586_7Series_MIS) 第一章中相关内容。 62 | 63 | 对于 SDRAM 在硬件上是如何实现的,推荐阅读 [What Every Programmer Should Know About Memory](https://people.freebsd.org/~lstewart/articles/cpumemory.pdf) 的相关部分。 64 | 65 | ## 千兆以太网 66 | 67 | 实验板上提供了千兆以太网 PHY 芯片,其型号为 RTL8211。我们将其配置成了 RGMII 接口,与实验 FPGA 相连。我们的 FPGA 同时扮演了“CPU”和 MAC 芯片的角色。PHY 芯片将网线上的比特流进行接收,通过 RGMII 接口发送至 FPGA;FPGA 中实现的 MAC 模块将其进行解析后,将原始的以太网帧通过各类流式总线发送至 FPGA 中的其他逻辑,即可完成各类处理。 68 | 69 | 由于 Vivado 软件中提供的 Tri Mode Ethernet MAC IP 核需要专用的 license,助教团队使用 [verilog-ethernet](https://github.com/alexforencich/verilog-ethernet) 项目中,开源的 `eth_mac_1g_rgmii` 模块进行了调试。该模块将 RGMII 中到来的以太网帧,从 AXI Stream 接口中发出,该接口的位宽为 8bit,时钟频率为 125MHz。可以从 [模板仓库的 ethernet 分支](https://git.tsinghua.edu.cn/digital-design-lab/project-template-xilinx/-/tree/ethernet) 找到样例工程,参考其中的使用方法。该项目中同时提供了带缓存的 `eth_mac_1g_rgmii_fifo` 模块,如果需要对以太网帧进行短时间的 FIFO 缓存,可以将 MAC 替换为该模块。 70 | 71 | 同时,针对 AXI Stream 协议,[verilog-axis](https://github.com/alexforencich/verilog-axis) 项目提供了许多有用的模块,例如 FIFO、数据宽度转换等,建议根据实际需要,使用其中的一些开源模块搭建项目。在此对这两个项目的作者 [Alex Forencich](https://github.com/alexforencich) 表示感谢,同学们也可以查看他编写的更多 Verilog 模块,从中获得启发。 72 | 73 | ## SRAM 74 | 75 | 实验板上提供了 4MB 的 SRAM,是两片型号为 [IS61WV102416BLL-10TLI](https://www.issi.com/WW/pdf/61WV102416ALL.pdf) 各 2MB 的 SRAM 数据线并联而成(两组 SRAM 连接同样的 `addr` `ce_n` `we_n` `oe_n` 信号,两组 `data` 拼接成为 32 位,两对 `ub_n` `lb_n` 组合成了 4 位的 `be_n`)。 76 | 77 | SRAM 读写需要满足一定的时序,可以按照下面的文档,学习如何编写 SRAM 控制器: 78 | 79 | - [计算机组成原理实验 4:总线实验之 SRAM 控制器实验](https://lab.cs.tsinghua.edu.cn/cod-lab-docs/labs/lab4/sram/) 80 | - [异步 SRAM 的时序和控制器编写(进阶)](https://jia.je/hardware/2022/05/19/async-sram-timing/) 81 | 82 | 需要注意 SRAM 的数据信号要使用三态门。 83 | 84 | 如果把 SRAM 用于显存,从 SRAM 读取的数据要通过 HDMI 显示到显示器上,那么需注意 SRAM 控制器的时钟和显示输出的像素时钟,如果这两个时钟不是同一个,需要考虑跨时钟域的问题。如果想避免跨时钟域的问题,可以让 SRAM 控制器以显示输出的像素时钟作为时钟,不过代价是读写 SRAM 需要的时间变长,设计上需要做一些取舍。 85 | 86 | 对于 SRAM 在硬件上是如何实现的,推荐阅读 [What Every Programmer Should Know About Memory](https://people.freebsd.org/~lstewart/articles/cpumemory.pdf) 的相关部分。 87 | 88 | ## SPI NOR Flash 89 | 90 | 实验板子还提供了 16MB 的 SPI NOR Flash,型号为 [W25Q128JVSIQTR](https://www.winbond.com/hq/product/code-storage-flash-memory/serial-nor-flash/?__locale=en&partNo=W25Q128JV)。它是一片非易失的 Flash 存储,可以用来保存自己的数据。FPGA 通过 SPI 或者 QSPI 接口,可以快速地随机访问 SPI NOR Flash 的内容。Flash 写入时,需要先擦除,再写入新的数据。 91 | 92 | ## SPI SRAM 93 | 94 | 实验板子还提供了 8MB SPI SRAM 内存,型号为 [VTI7064](https://www.lcsc.com/datasheet/lcsc_datasheet_1811151432_Vilsion-Tech-VTI7064MSME_C139966.pdf)。它和前面所述的(并行)SRAM 的区别主要是接口不同,这里的 SPI SRAM 接口需要通过 SPI 接口访问,实现更简单,速度会慢一些,但是容量更大。 95 | 96 | ## PS/2 97 | 98 | 实验板提供了两个 PS/2 接口,可以用于同时连接一个键盘和一个鼠标,但实际上,无论是键盘还是鼠标,接口和协议都是一样的。其中 PS/2 键盘更简单,如果不考虑大小写锁定的灯的显示,就不需要实现从 FPGA 向 PS/2 键盘发送命令;而 PS/2 鼠标比较复杂,需要 FPGA 向 PS/2 鼠标发送命令,告诉鼠标按照什么格式向 FPGA 发送信息。实验框架针对 PS/2 键盘和鼠标分别提供了样例代码。 99 | 100 | ## UART 串口 101 | 102 | 实验板提供了 UART 串口,并且内置了 USB 转串口芯片,只需要拿一根 USB Type-B 的线,把实验板的 LAB_UART 口连接到电脑上,就可以在电脑上读写串口。另一侧,FPGA 可以通过 UART 协议进行串口通信,可以用于一些低速的通信,也可以用来调试。 103 | 104 | 类似地,实验板还提供了 RS232 电平的串口,可以用来连接 RS-232 电平的外设。不过近几年还没有遇到过这个需求。 105 | 106 | ## SD 卡 107 | 108 | 实验板提供了 SD 卡座,可以用来插入 SD 卡。SD 卡直接连接到 FPGA,FPGA 可以用不同协议来实现访问 SD 卡的功能。其中比较简单的是 SPI 协议,按照标准,除了 SDUC 以外,其他的 SD 卡都应该支持 SPI 协议,但一些 SD 卡型号违背了标准,不支持 SPI 协议,见 [来源 1](https://forum.4dsystems.com.au/node/1869) 和 [来源 2](https://github.com/MarlinFirmware/Marlin/issues/2082#issuecomment-102381964)。因此建议使用小容量低速 SD 卡,并通过 SPI 协议访问。实验框架提供了一个通过 SPI 协议访问 SD 卡的样例。 109 | 110 | 需要注意的是,SD 卡访问的时候,直接使用扇区地址去读写数据,而实际电脑上看到的文件,要经过文件系统和分区表两层映射才会对应到具体的扇区上。建议用一些软件直接操作 SD 卡的扇区,绕过文件系统和分区表,这样可以保证电脑上写入指定的扇区,从 FPGA 可以正确地读出来。 -------------------------------------------------------------------------------- /src/examples/timer_vhdl.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+6 (git sha1 e0ba32423, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | module timer(clock, reset, timer); 4 | wire _000_; 5 | wire _001_; 6 | wire _002_; 7 | wire _003_; 8 | wire _004_; 9 | wire _005_; 10 | wire _006_; 11 | wire _007_; 12 | wire _008_; 13 | wire _009_; 14 | wire _010_; 15 | wire _011_; 16 | wire _012_; 17 | wire _013_; 18 | wire _014_; 19 | wire _015_; 20 | wire _016_; 21 | wire _017_; 22 | wire _018_; 23 | wire _019_; 24 | wire _020_; 25 | wire _021_; 26 | wire _022_; 27 | wire _023_; 28 | wire _024_; 29 | wire _025_; 30 | wire _026_; 31 | wire _027_; 32 | wire _028_; 33 | wire _029_; 34 | wire _030_; 35 | wire _031_; 36 | wire _032_; 37 | wire _033_; 38 | wire _034_; 39 | wire _035_; 40 | wire _036_; 41 | wire _037_; 42 | (* force_downto = 32'd1 *) 43 | (* src = "/usr/local/bin/../share/yosys/techmap.v:270.23-270.24" *) 44 | wire [3:0] _038_; 45 | (* force_downto = 32'd1 *) 46 | (* src = "/usr/local/bin/../share/yosys/techmap.v:270.26-270.27" *) 47 | wire [3:0] _039_; 48 | (* force_downto = 32'd1 *) 49 | (* src = "/usr/local/bin/../share/yosys/techmap.v:270.23-270.24" *) 50 | wire [19:0] _040_; 51 | (* force_downto = 32'd1 *) 52 | (* src = "/usr/local/bin/../share/yosys/techmap.v:270.26-270.27" *) 53 | wire [19:0] _041_; 54 | input clock; 55 | wire clock; 56 | reg [19:0] counter_reg; 57 | input reset; 58 | wire reset; 59 | output [3:0] timer; 60 | wire [3:0] timer; 61 | reg [3:0] timer_reg; 62 | assign _040_[0] = ~counter_reg[0]; 63 | assign _008_ = ~counter_reg[6]; 64 | assign _009_ = ~counter_reg[11]; 65 | assign _010_ = ~reset; 66 | assign _038_[0] = ~timer_reg[0]; 67 | assign _011_ = counter_reg[0] & counter_reg[1]; 68 | assign _012_ = counter_reg[2] & _011_; 69 | assign _013_ = counter_reg[3] & _012_; 70 | assign _014_ = counter_reg[4] & _013_; 71 | assign _015_ = counter_reg[5] & _014_; 72 | assign _016_ = _008_ & _015_; 73 | assign _017_ = counter_reg[17] & counter_reg[18]; 74 | assign _018_ = ~(counter_reg[7] | counter_reg[8]); 75 | assign _019_ = counter_reg[9] & _009_; 76 | assign _020_ = _018_ & _019_; 77 | assign _021_ = _017_ & _020_; 78 | assign _022_ = counter_reg[14] & counter_reg[16]; 79 | assign _023_ = counter_reg[19] & _022_; 80 | assign _024_ = ~(counter_reg[10] | counter_reg[13]); 81 | assign _025_ = ~(counter_reg[12] | counter_reg[15]); 82 | assign _026_ = _024_ & _025_; 83 | assign _027_ = _023_ & _026_; 84 | assign _028_ = _021_ & _027_; 85 | assign _001_ = ~(_016_ & _028_); 86 | assign _000_ = ~(_010_ & _001_); 87 | assign _029_ = timer_reg[0] & timer_reg[1]; 88 | assign _039_[1] = timer_reg[0] ^ timer_reg[1]; 89 | assign _030_ = timer_reg[2] & _029_; 90 | assign _039_[2] = timer_reg[2] ^ _029_; 91 | assign _039_[3] = timer_reg[3] ^ _030_; 92 | assign _041_[1] = counter_reg[0] ^ counter_reg[1]; 93 | assign _041_[2] = counter_reg[2] ^ _011_; 94 | assign _041_[3] = counter_reg[3] ^ _012_; 95 | assign _041_[4] = counter_reg[4] ^ _013_; 96 | assign _041_[5] = counter_reg[5] ^ _014_; 97 | assign _041_[6] = counter_reg[6] ^ _015_; 98 | assign _031_ = counter_reg[6] & _015_; 99 | assign _032_ = counter_reg[7] & _031_; 100 | assign _041_[7] = counter_reg[7] ^ _031_; 101 | assign _033_ = counter_reg[8] & _032_; 102 | assign _041_[8] = counter_reg[8] ^ _032_; 103 | assign _034_ = counter_reg[9] & _033_; 104 | assign _041_[9] = counter_reg[9] ^ _033_; 105 | assign _035_ = counter_reg[10] & _034_; 106 | assign _041_[10] = counter_reg[10] ^ _034_; 107 | assign _036_ = counter_reg[11] & _035_; 108 | assign _041_[11] = counter_reg[11] ^ _035_; 109 | assign _037_ = counter_reg[12] & _036_; 110 | assign _041_[12] = counter_reg[12] ^ _036_; 111 | assign _002_ = counter_reg[13] & _037_; 112 | assign _041_[13] = counter_reg[13] ^ _037_; 113 | assign _003_ = counter_reg[14] & _002_; 114 | assign _041_[14] = counter_reg[14] ^ _002_; 115 | assign _004_ = counter_reg[15] & _003_; 116 | assign _041_[15] = counter_reg[15] ^ _003_; 117 | assign _005_ = counter_reg[16] & _004_; 118 | assign _041_[16] = counter_reg[16] ^ _004_; 119 | assign _006_ = counter_reg[17] & _005_; 120 | assign _041_[17] = counter_reg[17] ^ _005_; 121 | assign _007_ = _017_ & _005_; 122 | assign _041_[18] = counter_reg[18] ^ _006_; 123 | assign _041_[19] = counter_reg[19] ^ _007_; 124 | always @(posedge clock) 125 | if (_000_) counter_reg[0] <= 1'h0; 126 | else counter_reg[0] <= _040_[0]; 127 | always @(posedge clock) 128 | if (_000_) counter_reg[1] <= 1'h0; 129 | else counter_reg[1] <= _041_[1]; 130 | always @(posedge clock) 131 | if (_000_) counter_reg[2] <= 1'h0; 132 | else counter_reg[2] <= _041_[2]; 133 | always @(posedge clock) 134 | if (_000_) counter_reg[3] <= 1'h0; 135 | else counter_reg[3] <= _041_[3]; 136 | always @(posedge clock) 137 | if (_000_) counter_reg[4] <= 1'h0; 138 | else counter_reg[4] <= _041_[4]; 139 | always @(posedge clock) 140 | if (_000_) counter_reg[5] <= 1'h0; 141 | else counter_reg[5] <= _041_[5]; 142 | always @(posedge clock) 143 | if (_000_) counter_reg[6] <= 1'h0; 144 | else counter_reg[6] <= _041_[6]; 145 | always @(posedge clock) 146 | if (_000_) counter_reg[7] <= 1'h0; 147 | else counter_reg[7] <= _041_[7]; 148 | always @(posedge clock) 149 | if (_000_) counter_reg[8] <= 1'h0; 150 | else counter_reg[8] <= _041_[8]; 151 | always @(posedge clock) 152 | if (_000_) counter_reg[9] <= 1'h0; 153 | else counter_reg[9] <= _041_[9]; 154 | always @(posedge clock) 155 | if (_000_) counter_reg[10] <= 1'h0; 156 | else counter_reg[10] <= _041_[10]; 157 | always @(posedge clock) 158 | if (_000_) counter_reg[11] <= 1'h0; 159 | else counter_reg[11] <= _041_[11]; 160 | always @(posedge clock) 161 | if (_000_) counter_reg[12] <= 1'h0; 162 | else counter_reg[12] <= _041_[12]; 163 | always @(posedge clock) 164 | if (_000_) counter_reg[13] <= 1'h0; 165 | else counter_reg[13] <= _041_[13]; 166 | always @(posedge clock) 167 | if (_000_) counter_reg[14] <= 1'h0; 168 | else counter_reg[14] <= _041_[14]; 169 | always @(posedge clock) 170 | if (_000_) counter_reg[15] <= 1'h0; 171 | else counter_reg[15] <= _041_[15]; 172 | always @(posedge clock) 173 | if (_000_) counter_reg[16] <= 1'h0; 174 | else counter_reg[16] <= _041_[16]; 175 | always @(posedge clock) 176 | if (_000_) counter_reg[17] <= 1'h0; 177 | else counter_reg[17] <= _041_[17]; 178 | always @(posedge clock) 179 | if (_000_) counter_reg[18] <= 1'h0; 180 | else counter_reg[18] <= _041_[18]; 181 | always @(posedge clock) 182 | if (_000_) counter_reg[19] <= 1'h0; 183 | else counter_reg[19] <= _041_[19]; 184 | always @(posedge clock) 185 | if (reset) timer_reg[0] <= 1'h0; 186 | else if (!_001_) timer_reg[0] <= _038_[0]; 187 | always @(posedge clock) 188 | if (reset) timer_reg[1] <= 1'h0; 189 | else if (!_001_) timer_reg[1] <= _039_[1]; 190 | always @(posedge clock) 191 | if (reset) timer_reg[2] <= 1'h0; 192 | else if (!_001_) timer_reg[2] <= _039_[2]; 193 | always @(posedge clock) 194 | if (reset) timer_reg[3] <= 1'h0; 195 | else if (!_001_) timer_reg[3] <= _039_[3]; 196 | assign _038_[3:1] = timer_reg[3:1]; 197 | assign _039_[0] = _038_[0]; 198 | assign _040_[19:1] = counter_reg[19:1]; 199 | assign _041_[0] = _040_[0]; 200 | assign timer = timer_reg; 201 | endmodule 202 | -------------------------------------------------------------------------------- /docs/hdl-by-example/imgs/adder.svg: -------------------------------------------------------------------------------- 1 | abc010101/2//2//2/ -------------------------------------------------------------------------------- /docs/vivado.md: -------------------------------------------------------------------------------- 1 | # Vivado 使用 2 | 3 | Vivado 是用于 Xilinx FPGA 的 EDA 开发工具。 4 | 5 | ## 下载安装 6 | 7 | 本实验使用的 EDA 软件为 Xilinx Viavdo 2019.2,安装过程可以参考 [数字逻辑实验课程的 Vivado 安装文档](https://lab.cs.tsinghua.edu.cn/digital-logic-lab/doc/vivado-install/)。请保证硬盘至少有 50GB 的可用空间。 8 | 9 | !!! info "为什么不用新版本" 10 | 11 | Vivado 的新版本安装包体积急剧增长,动辄大几十 GB,而在线安装器又需要很长的时间下载,这给安装新版 Vivado 带来了极大的困扰。当然了,新版本的 Vivado 在性能方面可能有一些提升。下面给出了几个版本的参考安装大小(实际大小与选择安装的组件有关): 12 | 13 | - Vivado 2019.2: 22 GB 14 | - Vivado 2020.2: 33 GB 15 | - Vivado 2022.1: 69 GB 16 | 17 | 如果你觉得上述问题可以解决,可以和同组同学协商,一起更新到新版本。 18 | 19 | ## 工程模板 20 | 21 | 工程模板的仓库为 。我们为每个组在清华 GitLab 上创建了项目,仓库地址为 `https://git.tsinghua.edu.cn/digital-design-lab/2024-spring/digital-design-grp-XX`,其中 `XX` 为分配的组号。仓库中已经预置了最新的工程模板,通常可以直接使用。 22 | 23 | 注:切换分支的时候如果 Vivado 已经打开了项目,那么 Vivado 是不会自动从硬盘读取新的 xpr 项目文件的,因此建议用 Vivado 重新打开一次项目。注意 Vivado 在退出的时候,也会保存项目文件 xpr 到硬盘。所以切换分支时,应该先退出 Vivado,切换分支,确定 xpr 项目文件的内容是新分支的,再打开 Vivado。 24 | 25 | 工程模板还提供了一些外设的样例,下面描述了这些样例的功能,以及这些样例大概做了哪些改动: 26 | 27 | - [ethernet](https://git.tsinghua.edu.cn/digital-design-lab/project-template/-/tree/ethernet) [diff](https://git.tsinghua.edu.cn/digital-design-lab/project-template-xilinx/-/compare/master...ethernet): 以太网 IP,收到的数据求和后显示在数码管上 28 | - 把 [verilog-ethernet](https://github.com/alexforencich/verilog-ethernet) 作为 git submodule 加入到 git 仓库中,并把需要用到的文件加入到 vivado 项目 29 | - 修改 `io.xdc`,取消涉及到 RGMII 部分约束的注释 30 | - 修改 `mod_top.sv`,取消 RGMII 的顶层信号的注释,例化 `eth_mac_1g_rgmii_fifo` 模块 31 | - 修改 `ip_pll` 设置,添加两个额外的时钟输出:第一个是 125MHz 时钟,第二个是带 90 度相位差的 125 MHz 时钟 32 | - [ps2_keyboard](https://git.tsinghua.edu.cn/digital-design-lab/project-template-xilinx/-/tree/ps2_keyboard) [diff](https://git.tsinghua.edu.cn/digital-design-lab/project-template-xilinx/-/compare/master...ps2_keyboard): PS/2 键盘,敲击键盘,键盘的 scancode 会显示在数码管上 33 | - 修改 `io.xdc`,取消涉及到 PS/2 Keyboard 部分约束的注释 34 | - 修改 `mod_top.sv`,取消 PS/2 Keyboard 的顶层信号的注释,例化 `ps2_keyboard` 模块 35 | - 在 `ps2_keyboard.sv` 中实现 PS/2 Keyboard 控制器的逻辑 36 | - 修改 `mod_top_tb.v` ,在仿真环境中验证 PS/2 Keyboard 控制器的正确性 37 | - [ps2_mouse](https://git.tsinghua.edu.cn/digital-design-lab/project-template-xilinx/-/tree/ps2_mouse) [diff](https://git.tsinghua.edu.cn/digital-design-lab/project-template-xilinx/-/compare/master...ps2_mouse): PS/2 鼠标,移动鼠标,鼠标的 x-y 坐标会显示在数码管上 38 | - 修改 `io.xdc`,取消涉及到 PS/2 Mouse 部分约束的注释 39 | - 修改 `mod_top.sv`,取消 PS/2 Mouse 的顶层信号的注释,例化 `ps2_mouse` 模块 40 | - 在 `ps2_mouse.sv` 中基于 [PS/2 控制器 IP](https://github.com/jiegec/ps2) 实现 PS/2 Mouse 控制器的逻辑 41 | - 修改 `mod_top_tb.v` ,在仿真环境中验证 PS/2 Mouse 控制器的正确性 42 | - [sdcard](https://git.tsinghua.edu.cn/digital-design-lab/project-template-xilinx/-/tree/sdcard) [diff](https://git.tsinghua.edu.cn/digital-design-lab/project-template-xilinx/-/compare/master...sdcard): SD 卡,读取 SD 卡的第一个扇区的内容,滚动显示在数码管上 43 | - 修改 `io.xdc`,取消涉及到 SDCard SPI 部分约束的注释 44 | - 修改 `mod_top.sv`,取消 SDCard SPI 的顶层信号的注释,例化 `sd_controller` 模块 45 | - 在 `sd_controller.sv` 中实现 SDCard SPI 控制器的逻辑 46 | - 修改 `ip_pll` 设置,添加一个额外的 5MHz 时钟输出用于 SPI 协议 47 | - 修改 `mod_top_tb.v` ,在仿真环境中验证 SDCard SPI 控制器的正确性 48 | - [sram](https://git.tsinghua.edu.cn/digital-design-lab/project-template-xilinx/-/tree/sram) [diff](https://git.tsinghua.edu.cn/digital-design-lab/project-template-xilinx/-/compare/master...sram): SRAM,添加了 SRAM 仿真模型,没有附带 SRAM 控制器,在实验板子上不会有任何输出;可以在这个项目的基础上,测试自己编写的 SRAM 控制器代码 49 | - 修改 `io.xdc`,取消涉及到 Base RAM 部分约束的注释 50 | - 修改 `mod_top.sv`,取消 SDCard SPI 的顶层信号的注释 51 | - 修改 `mod_top_tb.v` ,在仿真环境中添加 SRAM 仿真模型 52 | - [pmod_i2c](https://git.tsinghua.edu.cn/digital-design-lab/project-template/-/tree/pmod_i2c) [diff](https://git.tsinghua.edu.cn/digital-design-lab/project-template-xilinx/-/compare/master...pmod_i2c): 只实现了针对 WM8731 的 I2C 写入,没有处理 ACK;可以在这个项目的的基础上,修改引脚定义(`io.xdc`),修改写入的寄存器(`i2c.sv`),添加 ACK 判断等等 53 | - 修改 `io.xdc`,复制 PMOD 的 IO 约束,把信号名称改为 I2C 的信号 54 | - 修改 `mod_top.sv`,添加 I2C 的顶层信号,例化 I2C 控制器 `i2c.sv` 55 | - 在 `i2c.sv` 中实现 I2C 控制器的逻辑 56 | - [ila](https://git.tsinghua.edu.cn/digital-design-lab/project-template/-/tree/ila) [diff](https://git.tsinghua.edu.cn/digital-design-lab/project-template-xilinx/-/compare/master...ila): 使用 Vivado 提供的内嵌逻辑分析仪(ILA),实时观察 FPGA 内部信号的取值 57 | - 在源码中对要进行调试的信号添加 `(* mark_debug = "true" *)`,点击 `Run Synthesis` 开始综合 58 | - 综合完成后,点击 `Open Synthesized Design`,点击 `Set Up Debug`,按照提示,把信号加入到 ILA 中,设置好对应的时钟域 59 | - 点击保存,把更新后的约束保存在 debug.xdc 中,重新 `Generate Bitstream` 60 | - 在 `Program Device` 之后,就可以观察到 ILA 的界面,进而观察到 FPGA 内部的信号 61 | - [sdram](https://git.tsinghua.edu.cn/digital-design-lab/project-template/-/tree/sdram) [diff](https://git.tsinghua.edu.cn/digital-design-lab/project-template-xilinx/-/compare/master...sdram): 向 DDR3 SDRAM 写入数据,再读出来,显示在数码管上 62 | - 例化 MIG(Memory Interface Generator),作为 DDR3 SDRAM 内存控制器 63 | - 不需要修改 `io.xdc`,因为 MIG 会自己生成一份约束文件 64 | - 修改 `mod_top.sv`,添加 DDR3 SDRAM 的顶层信号,例化 DDR3 SDRAM 控制器 65 | - 在 `mod_top.sv` 中实现通过 MIG 的接口读写 DDR3 SDRAM 的逻辑 66 | 67 | !!! success "必须使用 Git" 68 | 69 | 课程强制使用 Git 进行版本控制管理,而不是使用微信交换文件。助教提供的 `.gitignore` 文件可直接使用,用来忽略 Vivado 生成的中间文件。 70 | 71 | 如果原始框架代码有更新(届时会通过多种渠道通知),你可以如下合并这些更新: 72 | 73 | ```shell 74 | > git remote add upstream git@git.tsinghua.edu.cn:digital-design-lab/project-template-xilinx.git 75 | > git fetch upstream 76 | > git merge upstream/master 77 | ``` 78 | 79 | 或者,更简单地,直接: 80 | 81 | ```shell 82 | > git pull git@git.tsinghua.edu.cn:digital-design-lab/project-template-xilinx.git master 83 | ``` 84 | 85 | !!! info "注意更新方式" 86 | 87 | 用户对于自己的项目仓库没有 force push 权限,所以请不要使用 rebase 来合并上游更新,平时也不要随意修改已经 push 的 commit。如果出现问题,请自行查询并使用 `git reflog` 解决。 88 | 89 | 模板中的重要文件和目录包括: 90 | 91 | * `project-template-xilinx.xpr`:Vivado 项目文件,可以在 Vivado 中通过 File → Project → Open... 打开 92 | * `project-template-xilinx.srcs/constrs_1/new/io.xdc`:IO 管脚绑定约束 93 | * `project-template-xilinx.srcs/sources_1/new/`:放置你编写的 RTL 代码 94 | * `mod_top.sv`:顶层模块,请根据需要取消信号列表中的注释,注意列表末尾的逗号 95 | * `dpy_scan.sv`:数码管扫描、译码模块 96 | * `led_scan.sv`:LED 扫描模块 97 | * `video.sv`:使用 VGA 时序驱动 HDMI 接口的样例 98 | * `project-template-xilinx.srcs/sources_1/ip`:用于放置 Vivado 生成的各类 IP 99 | * `ip_pll`:预生成的 PLL 模块,用于从输入的 100M 时钟生成 50M 时钟提供给 VGA 模块 100 | 101 | 在新建文件时,你也应当遵循这一规范,合理放置文件。 102 | 103 | ### RTL 代码 104 | 105 | 推荐使用 SystemVerilog 语言编写项目,当然你可以自由选择任何 Vivado 支持的语言。简单的 SystemVerilog 介绍可见 [此课件](static/systemverilog.pdf),需要注意你只能使用 SystemVerilog 中的 **可综合部分**。 106 | 107 | !!! error "禁止使用坐标驱动图像输出" 108 | 109 | 提供的 `mod_top.sv` 中使用了横纵坐标来计算出某个像素的颜色,请不要使用这种方法驱动复杂的渲染逻辑,否则将导致 **严重的时序问题**! 110 | 111 | RTL 代码应该具有良好的风格,如规范的缩进、清晰的命名和恰当的注释。 112 | 113 | 114 | ### 管脚绑定约束 115 | 116 | 为了正确地使用外设,需要配置顶层模块的输入/输出信号对应的 FPGA 管脚,此部分内容都包含在 `io.xdc` 中。由于顶层模块中没有声明所有的信号,有部分约束被注释了,包括: 117 | 118 | - PS/2 键盘和鼠标 119 | - UART/RS232 串口 120 | - DDR3 SDRAM 121 | - BaseRAM(SRAM) 122 | - 所有的 PMOD 接口 123 | - 以太网 RGMI 124 | - QSPI NOR Flash 125 | - QSPI PSRAM 126 | - SDCard 的 SD/SPI 模式 127 | 128 | 在使用时,请根据需要修改此文件,但 **不要随意修改管脚名**。 129 | 130 | ### IP 使用 131 | 132 | Vivado 提供了丰富的 IP Core,你可以根据需要自由生成和选用(在 Vivado 的 IP Catalog 中选择)。一些常用的 IP Core 包括: 133 | 134 | * RAM / ROM: [Block Memory Generator](https://docs.xilinx.com/v/u/en-US/pg058-blk-mem-gen) [Xilinx Parameterized Macros](https://docs.amd.com/r/en-US/ug953-vivado-7series-libraries/Xilinx-Parameterized-Macros) 135 | * FIFO(用于时钟域同步或者任务队列):[FIFO Generator](https://docs.xilinx.com/v/u/en-US/pg057-fifo-generator) [Xilinx Parameterized Macros](https://docs.amd.com/r/en-US/ug953-vivado-7series-libraries/Xilinx-Parameterized-Macros) 136 | * 各类 DSP(乘除法、开方、乘方、三角函数等数学运算):[Floating-Point Operator](https://docs.amd.com/v/u/en-US/pg060-floating-point) 137 | * Tri Mode Ethernet MAC(用于驱动 RGMII 以太网 PHY) 138 | 139 | 在使用任何 IP 前,请 **完整阅读** 它的使用手册(通常 Vivado 自带,或可以从 Xilinx(AMD) 官网获取)。如果部分 IP 不包含在基础版本的 Vivado 软件中,请联系助教团队寻求帮助。 140 | 141 | ## 连接到 FPGA 142 | 143 | 将发给同学的 Xilinx Cable 的 USB 端插到电脑上,另一端插到实验板的 LAB_JTAG 上,就可以在 Vivado 中,点击 `Open Hardware Manager`,连接到 FPGA。检测到 FPGA 后,可以把编译出来的 bitstream 烧写到 FPGA 中,如果配置了 ILA,还可以使用 ILA 观察 FPGA 内部信号的状态。 144 | 145 | ## 语言支持 146 | 147 | Vivado 并不支持 Verilog/SystemVerilog 的所有功能,具体支持哪些功能,可以在下列文档中找到: 148 | 149 | - [Vivado Design Suite User Guide: Logic Simulation Appendix B SystemVerilog Support in Vivado Simulator](https://docs.amd.com/v/u/2019.2-English/ug900-vivado-logic-simulation) 150 | - [Vivado Design Suite User Guide: Synthesis Chapter 8 SystemVerilog Support](https://docs.amd.com/v/u/2019.2-English/ug901-vivado-synthesis) 151 | -------------------------------------------------------------------------------- /docs/img/centralized_structure.svg: -------------------------------------------------------------------------------- 1 | 控制逻辑外设控制PS/2UARTI2C用户输入图形渲染当前状态显存1显示输出VGA输出反馈显存2 -------------------------------------------------------------------------------- /docs/hdl-by-example/priority_encoder.md: -------------------------------------------------------------------------------- 1 | # 无状态仲裁器(优先级编码器) 2 | 3 | ## 需求 4 | 5 | 这次,我们需要设计一个仲裁器:假想有多个用户同时要访问同一个资源,但是资源同时只能给一个用户使用,应该怎么实现?这时候,需要使用一个仲裁器,如果同时有多个用户请求访问,选择出一个幸运儿,只有这个幸运儿可以访问资源,其他用户则需要等待。并且假设资源的访问是“立即”完成的,所以不用担心资源正在使用的时候,使用权被其他用户抢走了的问题。 6 | 7 | 根据上面的需求,假设有四个用户(编号 `0` 到 `3`),可以设计如下的输入输出信号: 8 | 9 | 输入: 10 | 11 | 1. `request`: 宽度为 4,每一位 1 表示对应的用户请求访问资源,0 表示不请求 12 | 13 | 输出: 14 | 15 | 1. `valid`: 1 表示有用户请求访问资源,0 表示无用户请求访问资源 16 | 2. `user`: 宽度为 2,如果有用户请求访问资源时,输出获得资源的用户的编号 17 | 18 | ## 波形 19 | 20 | 根据上面的需求,可以发现,当同时有多个用户请求访问资源的时候(也就是 `request` 有不止一个位是 `1`时)答案是不唯一的。我们这里选择一种简单的策略:选择编号最小的那一个,这种方式也叫优先级编码器(priority encoder)。可以得到如下的波形: 21 | 22 | 32 | 33 | 出现多个用户请求访问的时候,也可以选择编号最大的那一个,此时可以得到如下的波形: 34 | 35 | 45 | 46 | ## 电路 47 | 48 | 首先分析一下,在仲裁器的,输出完全由输入决定,没有内部状态,所以可以用 **组合逻辑** 来实现。 49 | 50 | 接着思考,`valid` 信号比较简单,只要有一个输入为 `1`,那么就输出 `valid=1`,所以直接把所有输入用或门连接在一起即可。 51 | 52 | 那么,如何找到请求的用户里,编号最小的那一个呢?我们可以分情况讨论,下面的 `?` 表示任意值: 53 | 54 | 1. `request=0000`,那么输出的 `user` 可以是任意值,因为此时 `valid=0` 55 | 2. `request=???1`,此时 `user=0, valid=1` 56 | 3. `request=??10`,此时 `user=1, valid=1` 57 | 4. `request=?100`,此时 `user=2, valid=1` 58 | 5. `request=1000`,此时 `user=3, valid=1` 59 | 60 | 可以发现,上面的五个条件 **不全不漏** 地遍历了所有可能的情况。在实现组合逻辑的时候,一定要 **考虑所有情况,并且每个情况下每个信号都要得到一个结果** ,否则电路上是无法构造纯组合逻辑的,不可避免会引入锁存器(latch),这时候逻辑的工作方式可能不符合预期。 61 | 62 | 把上面的逻辑实现成电路,可以得到如下的电路图: 63 | 64 | ![](imgs/priority_encoder.svg) 65 | 66 | ## 代码 67 | 68 | 既然电路的实现已经很清晰了,让我们用 HDL 语言来实现上述的功能吧。 69 | 70 | === "System Verilog" 71 | 72 | 首先根据输入输出信号,声明 `module`: 73 | 74 | ```verilog 75 | module priority_encoder ( 76 | input wire [3:0] request, 77 | output wire valid, 78 | output wire [1:0] user 79 | ); 80 | // TODO 81 | endmodule 82 | ``` 83 | 84 | 接着来实现组合逻辑电路。一种方法是用之前提到的 `assign` 方法,比如 `assign valid = request[0] || request[1] || request[2] || request[3]` 或者更简洁的 `assign valid = |request`;但是要涉及到更加复杂的组合逻辑的时候,这样写会比较复杂。下面我们使用 `always_comb` 块来实现这个功能: 85 | 86 | ```sv 87 | logic valid_comb; 88 | logic [1:0] user_comb; 89 | 90 | always_comb begin 91 | // default 92 | valid_comb = 1'b0; 93 | user_comb = 2'd0; 94 | 95 | // cases 96 | casez (request) 97 | 4'b???1: begin 98 | valid_comb = 1'b1; 99 | user_comb = 2'd0; 100 | end 101 | 4'b??10: begin 102 | valid_comb = 1'b1; 103 | user_comb = 2'd1; 104 | end 105 | 4'b?100: begin 106 | valid_comb = 1'b1; 107 | user_comb = 2'd2; 108 | end 109 | 4'b1000: begin 110 | valid_comb = 1'b1; 111 | user_comb = 2'd3; 112 | end 113 | endcase 114 | 115 | end 116 | 117 | assign valid = valid_comb; 118 | assign user = user_comb; 119 | ``` 120 | 121 | !!! question "为什么这里要用 `logic` 而不是 `wire`?明明是组合逻辑呀。" 122 | 123 | 相信第一次看到这段代码的你一定会有这样的疑惑,这其实是 System Verilog 语法上的一个特殊要求,所有在 `always` 块中赋值的对象,无论是组合逻辑的 `always_comb` 还是时序逻辑的 `always_ff @(posedge clock)`,都需要放在一个 `logic` (或者具有类似语义的类型)中。归根结底是因为 System Verilog 描述的是功能模型,它与仿真过程是可以对应的,导致写代码的时候容易混淆。因此,在代码中,我们把这一类 `logic` 的命名都加上了 `_comb` 的后缀表示是组合逻辑。细心的读者也会发现,之前我们所有的寄存器命名都加上了 `_reg` 的后缀,就是为了区分这两种用途。 124 | 125 | 此时,我们就可以在 `always_comb` 块中灵活地使用各种条件语句,包括这里使用的 `casez` 语句。可以看到,这里首先设置了一个默认的结果,这样如果下面所有的 `casez` 都不满足,那么输出的就是默认值。由于组合逻辑电路中,不允许出现某个情况下没有取值的情况,所以这里必须人为保证 **所有可能性下,每个组合信号都有取值**。为了达成这个目的,并且防止自己遗忘在某些条件下进行赋值,可以在开头设置一个默认值。 126 | 127 | !!! question "为什么这里的赋值是 `=`,而之前在 `always_ff @ (posedge clock)` 中用的是 `<=`?" 128 | 129 | 这依然是 System Verilog 的设计问题。具体的原因不详述了,只要记住在时序逻辑 `always_ff @ (posedge clock)` 中始终用 `<=`(非阻塞赋值),而组合逻辑 `always_comb` 中始终用 `=`(阻塞赋值)。 130 | 131 | === "Verilog" 132 | 133 | 首先根据输入输出信号,声明 `module`: 134 | 135 | ```verilog 136 | module priority_encoder ( 137 | input wire [3:0] request, 138 | output wire valid, 139 | output wire [1:0] user 140 | ); 141 | // TODO 142 | endmodule 143 | ``` 144 | 145 | 接着来实现组合逻辑电路。一种方法是用之前提到的 `assign` 方法,比如 `assign valid = request[0] || request[1] || request[2] || request[3]` 或者更简洁的 `assign valid = |request`;但是要涉及到更加复杂的组合逻辑的时候,这样写会比较复杂。下面我们使用 `always @ (*)` 块来实现这个功能: 146 | 147 | ```verilog 148 | reg valid_comb; 149 | reg [1:0] user_comb; 150 | 151 | always @ (*) begin 152 | // default 153 | valid_comb = 1'b0; 154 | user_comb = 2'd0; 155 | 156 | // cases 157 | casez (request) 158 | 4'b???1: begin 159 | valid_comb = 1'b1; 160 | user_comb = 2'd0; 161 | end 162 | 4'b??10: begin 163 | valid_comb = 1'b1; 164 | user_comb = 2'd1; 165 | end 166 | 4'b?100: begin 167 | valid_comb = 1'b1; 168 | user_comb = 2'd2; 169 | end 170 | 4'b1000: begin 171 | valid_comb = 1'b1; 172 | user_comb = 2'd3; 173 | end 174 | endcase 175 | 176 | end 177 | 178 | assign valid = valid_comb; 179 | assign user = user_comb; 180 | ``` 181 | 182 | !!! question "为什么这里要用 `reg` 而不是 `wire`?明明是组合逻辑呀。" 183 | 184 | 相信第一次看到这段代码的你一定会有这样的疑惑,这其实是 Verilog 语法上的一个特殊要求,所有在 `always` 块中赋值的对象,无论是组合逻辑的 `always @ (*)` 还是时序逻辑的 `always @(posedge clock)`,都需要放在一个 `reg` (或者具有类似语义的类型)中。归根结底是因为 Verilog 描述的是功能模型,它与仿真过程是可以对应的,导致写代码的时候容易混淆。因此,在代码中,我们把这一类 `reg` 的命名都加上了 `_comb` 的后缀表示是组合逻辑。细心的读者也会发现,之前我们所有的寄存器命名都加上了 `_reg` 的后缀,就是为了区分这两种用途。 185 | 186 | 此时,我们就可以在 `always @ (*)` 块中灵活地使用各种条件语句,包括这里使用的 `casez` 语句。可以看到,这里首先设置了一个默认的结果,这样如果下面所有的 `casez` 都不满足,那么输出的就是默认值。由于组合逻辑电路中,不允许出现某个情况下没有取值的情况,所以这里必须人为保证 **所有可能性下,每个组合信号都有取值**。为了达成这个目的,并且防止自己遗忘在某些条件下进行赋值,可以在开头设置一个默认值。 187 | 188 | !!! question "为什么这里的赋值是 `=`,而之前在 `always @ (posedge clock)` 中用的是 `<=`?" 189 | 190 | 这依然是 Verilog 的设计问题。具体的原因不详述了,只要记住在时序逻辑 `always @ (posedge clock)` 中始终用 `<=`(非阻塞赋值),而组合逻辑 `always @ (*)` 中始终用 `=`(阻塞赋值)。 191 | 192 | 193 | 194 | === "VHDL" 195 | 196 | 首先根据输入输出信号,声明 `entity`: 197 | 198 | ```vhdl 199 | library IEEE; 200 | use IEEE.STD_LOGIC_1164.ALL; 201 | use IEEE.STD_LOGIC_ARITH.ALL; 202 | use IEEE.STD_LOGIC_UNSIGNED.ALL; 203 | 204 | entity priority_encoder is 205 | Port ( request : in STD_LOGIC_VECTOR (3 downto 0); 206 | valid : out STD_LOGIC; 207 | user : out STD_LOGIC_VECTOR (1 downto 0)); 208 | end priority_encoder; 209 | ``` 210 | 211 | 接着来实现主要的逻辑部分。由于这里是纯组合逻辑,所以没有时钟,也不会形如 `clk'event` 或者 `rising_edge(clk)` 的判断。由于这里的输入信号只有 `request`,所以可以把所有组合逻辑放在一个 `process(request)` 块中实现: 212 | 213 | ```vhdl 214 | architecture behavior of priority_encoder is 215 | begin 216 | process (request) begin 217 | -- default 218 | valid <= '0'; 219 | user <= "00"; 220 | 221 | -- cases 222 | if request(0)='1' then 223 | valid <= '1'; 224 | user <= "00"; 225 | elsif request(1)='1' then 226 | valid <= '1'; 227 | user <= "01"; 228 | elsif request(2)='1' then 229 | valid <= '1'; 230 | user <= "10"; 231 | elsif request(3)='1' then 232 | valid <= '1'; 233 | user <= "11"; 234 | end if; 235 | end process; 236 | end behavior; 237 | ``` 238 | 239 | 可以看到,这里首先设置了一个默认的结果,这样如果下面所有的 `if-elsif` 都不满足,那么输出的就是默认值。由于组合逻辑电路中,不允许出现某个情况下没有取值的情况,所以这里必须人为保证 **所有可能性下,每个组合信号都有取值**。为了达成这个目的,并且防止自己遗忘在某些条件下进行赋值,可以在开头设置一个默认值。 240 | 241 | ## 总结 242 | 243 | 通过这几个例子学习,我们学会了简单的纯组合逻辑电路、复杂的纯组合逻辑电路还有两种逻辑同时使用的电路的设计方法和代码实现。之后的各种电路设计中,除非少数特殊的电路,其他的所有电路都可以用上面提到的方法来实现。如果要总结规律的话,那就是: 244 | 245 | 1. 确定输入输出 246 | 2. 确定需要哪些寄存器 247 | 3. 实现时序逻辑,VHDL 中就是 `clock=1 and clock'event`,Verilog 中就是 `always @ (posedge clock)` 248 | 4. 实现组合逻辑,VHDL 中就是直接赋值,Verilog 中就是 `assign` 或者 `always @ (*)` -------------------------------------------------------------------------------- /src/examples/rr_arbiter.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+41 (git sha1 29c0a5958, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | (* top = 1 *) 4 | (* src = "examples/rr_arbiter.v:101.1-159.10" *) 5 | module rr_arbiter(clock, reset, request, valid, user); 6 | wire _00_; 7 | wire _01_; 8 | wire _02_; 9 | wire _03_; 10 | wire _04_; 11 | wire _05_; 12 | wire _06_; 13 | wire _07_; 14 | wire _08_; 15 | wire _09_; 16 | wire _10_; 17 | wire _11_; 18 | wire _12_; 19 | wire _13_; 20 | wire _14_; 21 | wire _15_; 22 | wire _16_; 23 | reg _17_; 24 | reg _18_; 25 | reg _19_; 26 | wire _20_; 27 | wire _21_; 28 | wire _22_; 29 | wire _23_; 30 | wire _24_; 31 | wire _25_; 32 | wire _26_; 33 | wire _27_; 34 | wire _28_; 35 | wire _29_; 36 | wire _30_; 37 | wire _31_; 38 | wire _32_; 39 | wire _33_; 40 | wire _34_; 41 | (* src = "examples/rr_arbiter.v:113.13-113.39" *) 42 | wire _35_; 43 | (* src = "examples/rr_arbiter.v:113.13-113.39" *) 44 | wire _36_; 45 | (* src = "examples/rr_arbiter.v:105.15-105.22" *) 46 | wire _37_; 47 | (* src = "examples/rr_arbiter.v:105.15-105.22" *) 48 | wire _38_; 49 | (* src = "examples/rr_arbiter.v:105.15-105.22" *) 50 | wire _39_; 51 | (* src = "examples/rr_arbiter.v:105.15-105.22" *) 52 | wire _40_; 53 | (* src = "examples/rr_arbiter.v:103.9-103.14" *) 54 | wire _41_; 55 | (* src = "examples/rr_arbiter.v:107.16-107.20" *) 56 | wire _42_; 57 | (* src = "examples/rr_arbiter.v:107.16-107.20" *) 58 | wire _43_; 59 | (* src = "examples/rr_arbiter.v:109.13-109.21" *) 60 | wire _44_; 61 | (* src = "examples/rr_arbiter.v:109.13-109.21" *) 62 | wire _45_; 63 | (* src = "examples/rr_arbiter.v:106.10-106.15" *) 64 | wire _46_; 65 | wire _47_; 66 | wire _48_; 67 | wire _49_; 68 | wire _50_; 69 | wire _51_; 70 | (* src = "examples/rr_arbiter.v:102.9-102.14" *) 71 | input clock; 72 | wire clock; 73 | (* src = "examples/rr_arbiter.v:113.13-113.39" *) 74 | wire [1:0] priority_encoder_user_comb; 75 | (* src = "examples/rr_arbiter.v:105.15-105.22" *) 76 | input [3:0] request; 77 | wire [3:0] request; 78 | (* src = "examples/rr_arbiter.v:103.9-103.14" *) 79 | input reset; 80 | wire reset; 81 | (* src = "examples/rr_arbiter.v:107.16-107.20" *) 82 | output [1:0] user; 83 | wire [1:0] user; 84 | (* src = "examples/rr_arbiter.v:112.13-112.22" *) 85 | wire [1:0] user_comb; 86 | (* src = "examples/rr_arbiter.v:109.13-109.21" *) 87 | wire [1:0] user_reg; 88 | (* src = "examples/rr_arbiter.v:106.10-106.15" *) 89 | output valid; 90 | wire valid; 91 | (* src = "examples/rr_arbiter.v:110.7-110.16" *) 92 | wire valid_reg; 93 | always @(posedge clock) 94 | _17_ <= _14_; 95 | always @(posedge clock) 96 | _18_ <= _15_; 97 | always @(posedge clock) 98 | _19_ <= _16_; 99 | assign _20_ = ~_41_; 100 | assign _21_ = ~_46_; 101 | assign _22_ = _18_ ? _40_ : _39_; 102 | assign _23_ = _18_ ? _38_ : _37_; 103 | assign _24_ = _17_ ? _22_ : _23_; 104 | assign _25_ = ~(_19_ & _24_); 105 | assign _26_ = ~(_21_ | _25_); 106 | assign _27_ = ~(_18_ & _26_); 107 | assign _28_ = _46_ & _25_; 108 | assign _29_ = ~(_46_ & _25_); 109 | assign _30_ = ~(_35_ & _28_); 110 | assign _42_ = ~(_27_ & _30_); 111 | assign _31_ = ~(_17_ & _26_); 112 | assign _32_ = ~(_36_ & _28_); 113 | assign _43_ = ~(_31_ & _32_); 114 | assign _33_ = _29_ ? _17_ : _36_; 115 | assign _14_ = _20_ & _33_; 116 | assign _34_ = _29_ ? _18_ : _35_; 117 | assign _15_ = _20_ & _34_; 118 | assign _16_ = _20_ & _46_; 119 | (* module_not_derived = 32'd1 *) 120 | (* src = "examples/rr_arbiter.v:115.23-120.4" *) 121 | rr_priority_encoder rr_priority_encoder_inst ( 122 | .last_user(user_reg), 123 | .request(request), 124 | .user(priority_encoder_user_comb), 125 | .valid(valid) 126 | ); 127 | assign user_comb = user; 128 | assign _44_ = _18_; 129 | assign _45_ = _17_; 130 | assign user_reg[1] = _45_; 131 | assign user_reg[0] = _44_; 132 | assign _41_ = reset; 133 | assign _35_ = priority_encoder_user_comb[0]; 134 | assign _39_ = request[2]; 135 | assign _40_ = request[3]; 136 | assign _37_ = request[0]; 137 | assign _38_ = request[1]; 138 | assign _36_ = priority_encoder_user_comb[1]; 139 | assign _46_ = valid; 140 | assign user[0] = _42_; 141 | assign user[1] = _43_; 142 | endmodule 143 | 144 | (* src = "examples/rr_arbiter.v:1.1-99.10" *) 145 | module rr_priority_encoder(request, last_user, valid, user); 146 | wire _000_; 147 | wire _001_; 148 | wire _002_; 149 | wire _003_; 150 | wire _004_; 151 | wire _005_; 152 | wire _006_; 153 | wire _007_; 154 | wire _008_; 155 | wire _009_; 156 | wire _010_; 157 | wire _011_; 158 | wire _012_; 159 | wire _013_; 160 | wire _014_; 161 | wire _015_; 162 | wire _016_; 163 | wire _017_; 164 | wire _018_; 165 | wire _019_; 166 | wire _020_; 167 | wire _021_; 168 | wire _022_; 169 | wire _023_; 170 | wire _024_; 171 | wire _025_; 172 | wire _026_; 173 | wire _027_; 174 | wire _028_; 175 | wire _029_; 176 | wire _030_; 177 | wire _031_; 178 | wire _032_; 179 | wire _033_; 180 | wire _034_; 181 | wire _035_; 182 | wire _036_; 183 | wire _037_; 184 | wire _038_; 185 | wire _039_; 186 | wire _040_; 187 | wire _041_; 188 | wire _042_; 189 | wire _043_; 190 | wire _044_; 191 | wire _045_; 192 | wire _046_; 193 | wire _047_; 194 | wire _048_; 195 | wire _049_; 196 | wire _050_; 197 | wire _051_; 198 | wire _052_; 199 | wire _053_; 200 | wire _054_; 201 | wire _055_; 202 | wire _056_; 203 | wire _057_; 204 | (* src = "examples/rr_arbiter.v:3.15-3.24" *) 205 | wire _058_; 206 | (* src = "examples/rr_arbiter.v:3.15-3.24" *) 207 | wire _059_; 208 | wire _060_; 209 | wire _061_; 210 | wire _062_; 211 | wire _063_; 212 | wire _064_; 213 | wire _065_; 214 | wire _066_; 215 | wire _067_; 216 | wire _068_; 217 | wire _069_; 218 | wire _070_; 219 | wire _071_; 220 | wire _072_; 221 | wire _073_; 222 | wire _074_; 223 | wire _075_; 224 | wire _076_; 225 | wire _077_; 226 | wire _078_; 227 | wire _079_; 228 | wire _080_; 229 | wire _081_; 230 | (* src = "examples/rr_arbiter.v:2.15-2.22" *) 231 | wire _082_; 232 | (* src = "examples/rr_arbiter.v:2.15-2.22" *) 233 | wire _083_; 234 | (* src = "examples/rr_arbiter.v:2.15-2.22" *) 235 | wire _084_; 236 | (* src = "examples/rr_arbiter.v:2.15-2.22" *) 237 | wire _085_; 238 | (* src = "examples/rr_arbiter.v:5.16-5.20" *) 239 | wire _086_; 240 | (* src = "examples/rr_arbiter.v:5.16-5.20" *) 241 | wire _087_; 242 | (* src = "examples/rr_arbiter.v:4.10-4.15" *) 243 | wire _088_; 244 | (* src = "examples/rr_arbiter.v:3.15-3.24" *) 245 | input [1:0] last_user; 246 | wire [1:0] last_user; 247 | (* src = "examples/rr_arbiter.v:2.15-2.22" *) 248 | input [3:0] request; 249 | wire [3:0] request; 250 | (* src = "examples/rr_arbiter.v:5.16-5.20" *) 251 | output [1:0] user; 252 | wire [1:0] user; 253 | (* src = "examples/rr_arbiter.v:8.13-8.22" *) 254 | wire [1:0] user_comb; 255 | (* src = "examples/rr_arbiter.v:4.10-4.15" *) 256 | output valid; 257 | wire valid; 258 | (* src = "examples/rr_arbiter.v:7.7-7.17" *) 259 | wire valid_comb; 260 | assign _060_ = ~_082_; 261 | assign _061_ = ~_085_; 262 | assign _062_ = ~_058_; 263 | assign _063_ = ~(_083_ & _060_); 264 | assign _064_ = ~(_061_ & _063_); 265 | assign _065_ = ~(_062_ & _059_); 266 | assign _066_ = _062_ | _059_; 267 | assign _067_ = ~(_065_ & _066_); 268 | assign _068_ = _084_ | _066_; 269 | assign _069_ = ~(_065_ & _068_); 270 | assign _070_ = ~(_064_ & _069_); 271 | assign _071_ = ~(_061_ | _084_); 272 | assign _072_ = ~(_083_ | _071_); 273 | assign _073_ = ~(_082_ & _059_); 274 | assign _074_ = ~(_067_ | _072_); 275 | assign _075_ = ~(_073_ & _074_); 276 | assign _086_ = ~(_070_ & _075_); 277 | assign _076_ = _061_ | _065_; 278 | assign _077_ = ~(_083_ & _066_); 279 | assign _078_ = _085_ | _084_; 280 | assign _079_ = _073_ & _078_; 281 | assign _080_ = ~(_077_ & _079_); 282 | assign _081_ = _083_ | _082_; 283 | assign _087_ = ~(_076_ & _080_); 284 | assign _088_ = _078_ | _081_; 285 | assign user_comb = user; 286 | assign valid_comb = valid; 287 | assign _083_ = request[1]; 288 | assign _082_ = request[0]; 289 | assign _085_ = request[3]; 290 | assign _084_ = request[2]; 291 | assign user[0] = _086_; 292 | assign _058_ = last_user[0]; 293 | assign _059_ = last_user[1]; 294 | assign user[1] = _087_; 295 | assign valid = _088_; 296 | endmodule 297 | -------------------------------------------------------------------------------- /docs/hdl-by-example/simulation.md: -------------------------------------------------------------------------------- 1 | # 仿真 2 | 3 | 在前面的文档里,我们学习了如何使用硬件描述语言来实现数字电路。实现了电路后,为了验证数字电路是否正确地工作,需要进行仿真。 4 | 5 | 本文将会讲述如何用 Verilog 语言来编写仿真的代码。**需要注意的是,描述数字电路的 Verilog 和用来仿真的 Verilog 可以认为是两种语言,使用完全不同的编写思路和实现方法。** 前者与电路一一对应,而后者更像是 C 这种过程式的编程语言,不是描述数字电路,而是给数字电路设定好输入的信号。 6 | 7 | ## 例子 8 | 9 | 下面来看一个简单的例子,采用的是前文出现过的加法器: 10 | 11 | ```verilog 12 | module add2 ( 13 | input wire [1:0] a, 14 | input wire [1:0] b, 15 | output wire [1:0] c 16 | ); 17 | assign c = a + b; 18 | endmodule 19 | ``` 20 | 21 | 把仿真器中运行上面的代码,会得到下面的波形: 22 | 23 | 33 | 34 | 你会发现所有的信号都显示 `x`,因为仿真的时候,模块外部什么也没有,所以这里的 `a` 和 `b` 什么也没有连接,在仿真器中表示为 `x`,表示数值未知。那是不是代码出了问题?但其实这段代码描述了正确的加法器的数字电路,只不过缺少来自外部的输入。那么思路就明确了:接下来,我要给这个模块输入数据。为了输入数据,要人为地在外面再套一层,就好像从上帝视角,去设置模块的输入: 35 | 36 | ```verilog 37 | `timescale 1ns/1ps 38 | module add2_tb (); 39 | reg [1:0] a; 40 | reg [1:0] b; 41 | wire [1:0] c; 42 | 43 | initial begin 44 | a = 2'b01; 45 | b = 2'b10; 46 | #1; 47 | $finish; 48 | end 49 | 50 | add2 inst ( 51 | .a(a), 52 | .b(b), 53 | .c(c) 54 | ); 55 | endmodule 56 | ``` 57 | 58 | 这段代码例化了一个 `add2` 模块,也就是要被测试的模块。首先,声明了 `a` `b` `c` 三个信号,由于这里的 `c` 连接到 `add2` 模块的输出,所以要用 `wire`;其他则是要输入到 `add2` 模块中,所以用 `reg`。 59 | 60 | 接着,在 `initial` 块中编写仿真的输入。例如,这里给 `a` 和 `b` 赋值,然后运行 `#1;` 命令,表示等待 `1ns`,然后再运行 `$finish;`,表示仿真结束。此时的仿真波形里就有了数据: 61 | 62 | 72 | 73 | 可以看到,上面的代码并不是在描述一个数字电路,而是在描述一个操作流程:先设置 `a` 为 `2'b01`,再设置 `b` 为 `2'b10`,等待 1ns,最后结束仿真。回顾一下数字电路实验,你在搭建好电路以后,会人为地按下按键开关,然后就可以观察到电路的变化。这里也是一样的,只不过是用 Verilog 来描述人的行为,让仿真器按照既定的流程进行操作。 74 | 75 | 继续强调:描述数字电路的 Verilog,或者说可综合的 Verilog,是奔着电路去编写的,如果写出了无法用电路实现的代码,就会出现问题;而用于仿真的 Verilog,并不对应电路,而是代替人去修改电路的输入和观察电路的输出。只是恰巧二者都是用 Verilog 语言来编写,实际上可以用不同的语言,例如用 Python 来编写仿真代码,见 [cocotb](https://www.cocotb.org/)。写代码的时候,请不要混淆两种 Verilog 语言。 76 | 77 | ## 时钟 78 | 79 | 接下来讲讲 **用于仿真** 的 Verilog,一般有哪些常用的写法。上面的例子里,已经出现过: 80 | 81 | - 声明信号,然后连接到被测试的模块的输入输出上,例如上面的 `a` `b` `c`。 82 | - 在 `initial` 块中编写仿真的过程。 83 | - 修改输入信号,直接赋值即可:`a = 2'b01`。 84 | - 等待一段时间,例如 `#1;`,结合最开头的 ``timescale 1ns/1ps`,就是等待 1ns 的意思。 85 | - 调用内置函数,如 `$finish;` 表示结束仿真。 86 | 87 | 接下来来仿真一个带有时序逻辑的模块,看看如何仿真时钟信号,使用前面的秒表的例子: 88 | 89 | ```verilog 90 | module timer ( 91 | input wire clock, 92 | input wire reset, 93 | output wire [3:0] timer 94 | ); 95 | reg [3:0] timer_reg; 96 | reg [19:0] counter_reg; 97 | 98 | // sequential 99 | always @ (posedge clock) begin 100 | if (reset) begin 101 | timer_reg <= 4'b0; 102 | counter_reg <= 20'b0; 103 | end else begin 104 | if (counter_reg == 20'd999999) begin 105 | timer_reg <= timer_reg + 4'b1; 106 | counter_reg <= 20'b0; 107 | end else begin 108 | counter_reg <= counter_reg + 20'b1; 109 | end 110 | end 111 | end 112 | 113 | // combinatorial 114 | assign timer = timer_reg; 115 | endmodule 116 | ``` 117 | 118 | 我们希望给它提供一个时钟信号,然后观察 `timer` 的变化。首先,还是按照前面的规律,例化 `timer` 模块,连接输入输出信号: 119 | 120 | ```verilog 121 | module timer_tb (); 122 | reg clock; 123 | reg reset; 124 | wire [3:0] timer; 125 | 126 | initial begin 127 | // TODO 128 | end 129 | 130 | timer inst ( 131 | .clock(clock), 132 | .reset(reset), 133 | .timer(timer) 134 | ); 135 | endmodule 136 | ``` 137 | 138 | 接着,我们来思考如何来生成时钟信号。我们知道,时钟信号以一个固定的频率在 0 和 1 之间变化。如果频率是 50MHz,那么一个周期就是 `1 / 50MHz = 20ns`,也就是每 10ns 变化一次。按照这个思想,设定时钟信号为 1,然后等待 10ns,再设定时钟信号为 0,再等待 10ns,这样下去就可以构造出一个时钟信号来: 139 | 140 | ```verilog 141 | initial begin 142 | reset = 1'b0; 143 | clock = 1'b1; 144 | 145 | #10; 146 | 147 | clock = 1'b0; 148 | 149 | #10; 150 | 151 | clock = 1'b1; 152 | 153 | #10; 154 | 155 | clock = 1'b0; 156 | 157 | #10; 158 | 159 | end 160 | ``` 161 | 162 | 仿真上面的代码,就会得到下面的波形: 163 | 164 | 173 | 174 | 但是我们希望仿真更多时钟周期,按照上面的写法,每仿真一个时钟周期,就需要反复地设置 `clock` 信号和等待 `#10;`,十分麻烦,能否自动生成时钟信号? 175 | 176 | 答案是可以,写法是在 `initial` 之外,写一句 `always #10 clock = ~clock;`: 177 | 178 | ```verilog 179 | initial begin 180 | reset = 1'b0; 181 | clock = 1'b1; 182 | end 183 | 184 | always #10 clock = ~clock; 185 | ``` 186 | 187 | 代码 `always #10 clock = ~clock;` 的含义就是一直重复执行 `#10 clock = ~clock;` 代码,这句话的意思就是等待 10ns,然后 clock 取反,再等待 10ns,clock 再取反,永远循环下去。只需要一开始初始化了 `clock <= 1'b1`,后面就不再需要设置时钟了。 188 | 189 | ## 复位 190 | 191 | 处理好时钟以后,仿真上面的代码,你会发现 `timer` 输出一直是 `x`,这是因为 `timer` 没有被复位,仿真的整个过程中,也没有出现 `reset` 为 1 的时候。因此,我们需要进行复位:先设置 `reset` 为 1,再设置 `reset` 为 0: 192 | 193 | ```verilog 194 | initial begin 195 | reset = 1'b0; 196 | clock = 1'b1; 197 | 198 | #10; 199 | 200 | reset = 1'b1; 201 | 202 | #10; 203 | 204 | reset = 1'b0; 205 | end 206 | 207 | always #10 clock = ~clock; 208 | ``` 209 | 210 | 修改以后,得到了正确的波形: 211 | 212 | 222 | 223 | 可以看到,`timer` 被成功复位成了 `0`,不再是 `x`。但是继续仿真下去,你会发现 `timer` 很长时间里一直是 `0`,这是不是很奇怪?计时器的功能是每过一段时间加一,按理说仿真里也要看到 `timer` 变成 1 才对。 224 | 225 | 但转念一想,这里一个周期只有 20ns,但是在 `timer` 中,需要计数到 1,000,000 个周期才会给输出加一。这意味着,只有仿真到 1,000,000 个周期以后,才会看到 `timer` 的变化。仿真的时间尺度是很小的,人感知的时间通常是 ms 级别,仿真里就好像时间过得特别缓慢,仿真了很久波形也才跑了多少个 ms。所以为了仿真,有时候可以人为的“加速”,例如把 1,000,000 改成 100,那么就可以很容易地在仿真中观测到变化。 226 | 227 | 学习到这里,就足够编写一些简单的仿真测试代码了。通常流程是,先设想要测试的情况,然后设计出波形,把波形里面的输入部分翻译成 Verilog 代码。启动仿真,然后在波形中观察输出是否符合自己的预期结果。 228 | 229 | ## 构造输入 230 | 231 | 目前的仿真顶层模块只提供了时钟信号和复位信号,没有提供要测试的模块的其他输入信号,那么如果仿真一些需要解析输入数据的模块,例如 PS/2 键盘控制器,只提供时钟和复位信号的情况下,测试不出输入部分的逻辑的问题。 232 | 233 | 因此,仿真顶层模块还需要针对特定的协议,人为构造输入。具体做法和上面类似,只不过要修改的是协议相关的输入信号。下面以 PS/2 键盘控制器为例,例如如果要构造 `scancode=0xF0` 的输入,需要做哪些事情: 234 | 235 | 首先,声明 ps2 相关的信号并连接到要测试的模块: 236 | 237 | ```verilog 238 | reg ps2_clock; 239 | reg ps2_data; 240 | 241 | ps2_keyboard dut ( 242 | .clock(clock), 243 | .reset(reset), 244 | 245 | .ps2_clock(ps2_clock), 246 | .ps2_data(ps2_data) 247 | ); 248 | ``` 249 | 250 | 接着,按照 PS/2 的协议,按一定的顺序给 ps2_clock 和 ps2_data 赋值,中间穿插着延迟语句,这样就构造了满足要求的输入: 251 | 252 | ```verilog 253 | ps2_data = 1'b1; 254 | ps2_clock = 1'b1; 255 | #5; 256 | 257 | // start bit 258 | ps2_data = 1'b0; 259 | #5; 260 | ps2_clock = 1'b0; 261 | #5; 262 | ps2_clock = 1'b1; 263 | 264 | // scancode[0]=0 265 | ps2_data = 1'b0; 266 | #5; 267 | ps2_clock = 1'b0; 268 | #5; 269 | ps2_clock = 1'b1; 270 | 271 | // scancode[1]=0 272 | ps2_data = 1'b0; 273 | #5; 274 | ps2_clock = 1'b0; 275 | #5; 276 | ps2_clock = 1'b1; 277 | 278 | // omitted, repeat until scancode[7] 279 | 280 | // scancode[7]=1 281 | ps2_data = 1'b1; 282 | #5; 283 | ps2_clock = 1'b0; 284 | #5; 285 | ps2_clock = 1'b1; 286 | 287 | // parity=1 288 | ps2_data = 1'b1; 289 | #5; 290 | ps2_clock = 1'b0; 291 | #5; 292 | ps2_clock = 1'b1; 293 | 294 | // stop 295 | ps2_data = 1'b1; 296 | #5; 297 | ps2_clock = 1'b0; 298 | #5; 299 | ps2_clock = 1'b1; 300 | ``` 301 | 302 | 类似地,其他协议也可以用类似的方法来构造。构造的时候,边改仿真代码,边观察仿真波形,直到实现想要的波形为止。需要注意延迟的时间,是否满足时钟频率的要求。 303 | 304 | 更进一步,如果想要重复发送 scancode,只不过 scancode 的内容会更改,可以把这一个步骤封装成一个 task,完整写法见 [Tsinghua GitLab](https://git.tsinghua.edu.cn/digital-design-lab/project-template/-/blob/2076e9ffc1ff3e923365a9e79d6a944544a3b8e8/src/keyboard_tb.v#L12)。 305 | 306 | ## 解析输出 307 | 308 | 在上面的部分里,模块的输出的检查,需要人去观察波形,在大脑中和预期结果比对。实际上,也可以在仿真的顶层模块中,实现输出的解析,把内容打印到标准输出中,也可以将结果与预期值进行比对,减少了人的负担。 309 | 310 | 在 Verilog 中,可以用 `$display` 命令进行打印。如果模块给出的结果与预期值不符,可以用 `$display` 命令输出错误信息,然后用 `$fatal` 命令来结束仿真。[Tsinghua GitLab](https://git.tsinghua.edu.cn/digital-design-lab/project-template/-/blob/647126c61870eede01e200844fb1c2d48f8acf31/src/sdcard_tb.v#L76) 中提供了一个解析 SPI SD 卡控制器输出的实现,可以打印出控制器发送的命令内容。 311 | 312 | ## 总结 313 | 314 | 总结一下上面提到的如何编写用于仿真的 Verilog: 315 | 316 | - 单独写一个用于仿真的顶层模块,例化要测试的模块(Device Under Test)。 317 | - 把要测试的模块的输入输出都接到 `reg` 或者 `wire` 上。 318 | - 对于时序逻辑,在 `initial` 块开头初始化时钟信号,然后用 `always #10 clock = ~clock;` 代码来自动生成时钟信号。 319 | - 生成复位信号,在 `initial` 块内,模拟仿真信号由 0 变成 1,再由 1 变成 0 的过程。 320 | - 生成输入信号,在 `initial` 块内,对输入信号对应的 `reg` 信号进行赋值。 321 | - 在波形中观察模块输出和内部信号的变化。 322 | - 如果要测试的模块需要从外部获取数据,可以在仿真的顶层模块中按照协议,生成信号并输入到要测试的模块中。 323 | - 如果从波形上不容易看出数据,可以在仿真的顶层模块中进行解析和打印。 324 | - 可以用仿真来做单元测试,如果结果与预期不符,就用 `$fatal` 表示仿真失败。 325 | 326 | ## 命令行仿真 327 | 328 | 上面的教程假设了你在 GUI 中仿真,可以直接看到仿真的波形。如果你希望在命令行中仿真,那么需要额外的工作来生成波形文件。一个比较通用的做法是,在 Verilog 中添加代码: 329 | 330 | ```verilog 331 | initial begin 332 | $dumpfile("dump.vcd"); 333 | $dumpvars(0, top_module_name); 334 | end 335 | ``` 336 | 337 | 意思是指定输出波形问题名为 `dump.vcd`,输出从模块 `top_module_name` 以下的所有信号。仿真完成后,可以用 gtkwave 打开 dump.vcd 查看生成的波形文件。 338 | 339 | 下面讲述如何在命令行中运行仿真器,假设源代码包括 `a.v` 和 `b.v`,顶层模块名字为 `sim_top`: 340 | 341 | 1. Icarus Verilog: 342 | 1. 运行 `iverilog -s sim_top -o sim a.v b.v` 343 | 2. 运行 `./sim` 344 | 2. ModelSim: 345 | 1. 把 ModelSim 的 bin 目录加到 PATH 中 346 | 2. 运行 `vlib work` 347 | 3. 运行 `vlog a.v` 348 | 4. 运行 `vlog b.v` 349 | 5. 运行 `vsim -c work.sim_top -do "run -all"` 350 | 3. Vivado: 351 | 1. 把 Vivado 的 bin 目录加到 PATH 中 352 | 2. 运行 `xvlog a.v` 353 | 3. 运行 `xvlog b.v` 354 | 4. 运行 `xelab -debug all --snapshot sim_top sim_top` 355 | 5. 运行 `xsim sim_top` 356 | -------------------------------------------------------------------------------- /src/examples/counter_vhdl.syn.v: -------------------------------------------------------------------------------- 1 | /* Generated by Yosys 0.16+6 (git sha1 e0ba32423, clang 11.0.1-2 -fPIC -Os) */ 2 | 3 | module counter(clock, reset, button_debounced, ones, tens); 4 | wire _00_; 5 | wire _01_; 6 | wire _02_; 7 | wire _03_; 8 | wire _04_; 9 | wire _05_; 10 | wire _06_; 11 | wire _07_; 12 | wire _08_; 13 | wire _09_; 14 | wire _10_; 15 | wire _11_; 16 | wire _12_; 17 | wire _13_; 18 | wire _14_; 19 | wire _15_; 20 | wire _16_; 21 | wire _17_; 22 | wire _18_; 23 | (* force_downto = 32'd1 *) 24 | (* src = "/usr/local/bin/../share/yosys/techmap.v:270.23-270.24" *) 25 | wire [3:0] _19_; 26 | (* force_downto = 32'd1 *) 27 | (* src = "/usr/local/bin/../share/yosys/techmap.v:270.26-270.27" *) 28 | wire [3:0] _20_; 29 | input button_debounced; 30 | wire button_debounced; 31 | reg button_debounced_reg; 32 | input clock; 33 | wire clock; 34 | output [3:0] ones; 35 | wire [3:0] ones; 36 | reg [3:0] ones_reg; 37 | input reset; 38 | wire reset; 39 | output [3:0] tens; 40 | wire [3:0] tens; 41 | reg [3:0] tens_reg; 42 | assign _19_[0] = ~tens_reg[0]; 43 | assign _01_ = ~ones_reg[0]; 44 | assign _06_ = ~ones_reg[2]; 45 | assign _07_ = ~button_debounced_reg; 46 | assign _08_ = ~(_01_ & ones_reg[1]); 47 | assign _09_ = _06_ & ones_reg[3]; 48 | assign _10_ = ~(_06_ & ones_reg[3]); 49 | assign _11_ = ~(_01_ | ones_reg[1]); 50 | assign _12_ = ~(_10_ & _11_); 51 | assign _13_ = ones_reg[0] & ones_reg[1]; 52 | assign _02_ = ~(_08_ & _12_); 53 | assign _14_ = ~(ones_reg[2] & _13_); 54 | assign _03_ = ones_reg[2] ^ _13_; 55 | assign _15_ = _09_ & _11_; 56 | assign _16_ = ones_reg[3] ^ _14_; 57 | assign _04_ = ~(_15_ | _16_); 58 | assign _00_ = button_debounced & _07_; 59 | assign _05_ = _15_ & _00_; 60 | assign _17_ = tens_reg[0] & tens_reg[1]; 61 | assign _20_[1] = tens_reg[0] ^ tens_reg[1]; 62 | assign _18_ = tens_reg[2] & _17_; 63 | assign _20_[2] = tens_reg[2] ^ _17_; 64 | assign _20_[3] = tens_reg[3] ^ _18_; 65 | always @(posedge clock) 66 | if (reset) button_debounced_reg <= 1'h0; 67 | else button_debounced_reg <= button_debounced; 68 | always @(posedge clock) 69 | if (reset) ones_reg[0] <= 1'h0; 70 | else if (_00_) ones_reg[0] <= _01_; 71 | always @(posedge clock) 72 | if (reset) ones_reg[1] <= 1'h0; 73 | else if (_00_) ones_reg[1] <= _02_; 74 | always @(posedge clock) 75 | if (reset) ones_reg[2] <= 1'h0; 76 | else if (_00_) ones_reg[2] <= _03_; 77 | always @(posedge clock) 78 | if (reset) ones_reg[3] <= 1'h0; 79 | else if (_00_) ones_reg[3] <= _04_; 80 | always @(posedge clock) 81 | if (reset) tens_reg[0] <= 1'h0; 82 | else if (_05_) tens_reg[0] <= _19_[0]; 83 | always @(posedge clock) 84 | if (reset) tens_reg[1] <= 1'h0; 85 | else if (_05_) tens_reg[1] <= _20_[1]; 86 | always @(posedge clock) 87 | if (reset) tens_reg[2] <= 1'h0; 88 | else if (_05_) tens_reg[2] <= _20_[2]; 89 | always @(posedge clock) 90 | if (reset) tens_reg[3] <= 1'h0; 91 | else if (_05_) tens_reg[3] <= _20_[3]; 92 | assign _19_[3:1] = tens_reg[3:1]; 93 | assign _20_[0] = _19_[0]; 94 | assign ones = ones_reg; 95 | assign tens = tens_reg; 96 | endmodule 97 | 98 | module counter_top(clock, reset, button, ones, tens); 99 | input button; 100 | wire button; 101 | wire button_debounced; 102 | input clock; 103 | wire clock; 104 | output [3:0] ones; 105 | wire [3:0] ones; 106 | input reset; 107 | wire reset; 108 | output [3:0] tens; 109 | wire [3:0] tens; 110 | counter counter_component ( 111 | .button_debounced(button_debounced), 112 | .clock(clock), 113 | .ones(ones), 114 | .reset(reset), 115 | .tens(tens) 116 | ); 117 | debouncer debouncer_component ( 118 | .button(button), 119 | .button_debounced(button_debounced), 120 | .clock(clock), 121 | .reset(reset) 122 | ); 123 | endmodule 124 | 125 | module debouncer(clock, reset, button, button_debounced); 126 | wire _000_; 127 | wire _001_; 128 | wire _002_; 129 | wire _003_; 130 | wire _004_; 131 | wire _005_; 132 | wire _006_; 133 | wire _007_; 134 | wire _008_; 135 | wire _009_; 136 | wire _010_; 137 | wire _011_; 138 | wire _012_; 139 | wire _013_; 140 | wire _014_; 141 | wire _015_; 142 | wire _016_; 143 | wire _017_; 144 | wire _018_; 145 | wire _019_; 146 | wire _020_; 147 | wire _021_; 148 | wire _022_; 149 | wire _023_; 150 | wire _024_; 151 | wire _025_; 152 | wire _026_; 153 | wire _027_; 154 | wire _028_; 155 | wire _029_; 156 | wire _030_; 157 | wire _031_; 158 | wire _032_; 159 | wire _033_; 160 | wire _034_; 161 | (* force_downto = 32'd1 *) 162 | (* src = "/usr/local/bin/../share/yosys/techmap.v:270.23-270.24" *) 163 | wire [15:0] _035_; 164 | (* force_downto = 32'd1 *) 165 | (* src = "/usr/local/bin/../share/yosys/techmap.v:270.26-270.27" *) 166 | wire [15:0] _036_; 167 | input button; 168 | wire button; 169 | output button_debounced; 170 | wire button_debounced; 171 | reg button_debounced_reg; 172 | input clock; 173 | wire clock; 174 | reg [15:0] counter_reg; 175 | reg last_button_reg; 176 | input reset; 177 | wire reset; 178 | assign _035_[0] = ~counter_reg[0]; 179 | assign _003_ = ~counter_reg[5]; 180 | assign _004_ = ~counter_reg[11]; 181 | assign _005_ = ~counter_reg[12]; 182 | assign _006_ = last_button_reg ^ button; 183 | assign _001_ = reset | _006_; 184 | assign _007_ = _003_ & counter_reg[4]; 185 | assign _008_ = ~(counter_reg[7] | counter_reg[6]); 186 | assign _009_ = _007_ & _008_; 187 | assign _010_ = ~(counter_reg[1] | counter_reg[0]); 188 | assign _011_ = ~(counter_reg[3] | counter_reg[2]); 189 | assign _012_ = _010_ & _011_; 190 | assign _013_ = _009_ & _012_; 191 | assign _014_ = _005_ & counter_reg[13]; 192 | assign _015_ = ~(counter_reg[15] | counter_reg[14]); 193 | assign _016_ = _014_ & _015_; 194 | assign _017_ = counter_reg[9] & counter_reg[8]; 195 | assign _018_ = _004_ & counter_reg[10]; 196 | assign _019_ = _017_ & _018_; 197 | assign _020_ = _016_ & _019_; 198 | assign _002_ = ~(_013_ & _020_); 199 | assign _000_ = ~(_006_ | _002_); 200 | assign _021_ = counter_reg[1] & counter_reg[0]; 201 | assign _036_[1] = ~(_010_ | _021_); 202 | assign _022_ = counter_reg[2] & _021_; 203 | assign _036_[2] = counter_reg[2] ^ _021_; 204 | assign _023_ = counter_reg[3] & _022_; 205 | assign _036_[3] = counter_reg[3] ^ _022_; 206 | assign _024_ = counter_reg[4] & _023_; 207 | assign _036_[4] = counter_reg[4] ^ _023_; 208 | assign _025_ = counter_reg[5] & _024_; 209 | assign _036_[5] = counter_reg[5] ^ _024_; 210 | assign _026_ = counter_reg[6] & _025_; 211 | assign _036_[6] = counter_reg[6] ^ _025_; 212 | assign _027_ = counter_reg[7] & _026_; 213 | assign _036_[7] = counter_reg[7] ^ _026_; 214 | assign _028_ = counter_reg[8] & _027_; 215 | assign _036_[8] = counter_reg[8] ^ _027_; 216 | assign _029_ = counter_reg[9] & _028_; 217 | assign _036_[9] = counter_reg[9] ^ _028_; 218 | assign _030_ = counter_reg[10] & _029_; 219 | assign _036_[10] = counter_reg[10] ^ _029_; 220 | assign _031_ = counter_reg[11] & _030_; 221 | assign _036_[11] = counter_reg[11] ^ _030_; 222 | assign _032_ = counter_reg[12] & _031_; 223 | assign _036_[12] = counter_reg[12] ^ _031_; 224 | assign _033_ = counter_reg[13] & _032_; 225 | assign _036_[13] = counter_reg[13] ^ _032_; 226 | assign _034_ = counter_reg[14] & _033_; 227 | assign _036_[14] = counter_reg[14] ^ _033_; 228 | assign _036_[15] = counter_reg[15] ^ _034_; 229 | always @(posedge clock) 230 | if (_001_) counter_reg[0] <= 1'h0; 231 | else if (_002_) counter_reg[0] <= _035_[0]; 232 | always @(posedge clock) 233 | if (_001_) counter_reg[1] <= 1'h0; 234 | else if (_002_) counter_reg[1] <= _036_[1]; 235 | always @(posedge clock) 236 | if (_001_) counter_reg[2] <= 1'h0; 237 | else if (_002_) counter_reg[2] <= _036_[2]; 238 | always @(posedge clock) 239 | if (_001_) counter_reg[3] <= 1'h0; 240 | else if (_002_) counter_reg[3] <= _036_[3]; 241 | always @(posedge clock) 242 | if (_001_) counter_reg[4] <= 1'h0; 243 | else if (_002_) counter_reg[4] <= _036_[4]; 244 | always @(posedge clock) 245 | if (_001_) counter_reg[5] <= 1'h0; 246 | else if (_002_) counter_reg[5] <= _036_[5]; 247 | always @(posedge clock) 248 | if (_001_) counter_reg[6] <= 1'h0; 249 | else if (_002_) counter_reg[6] <= _036_[6]; 250 | always @(posedge clock) 251 | if (_001_) counter_reg[7] <= 1'h0; 252 | else if (_002_) counter_reg[7] <= _036_[7]; 253 | always @(posedge clock) 254 | if (_001_) counter_reg[8] <= 1'h0; 255 | else if (_002_) counter_reg[8] <= _036_[8]; 256 | always @(posedge clock) 257 | if (_001_) counter_reg[9] <= 1'h0; 258 | else if (_002_) counter_reg[9] <= _036_[9]; 259 | always @(posedge clock) 260 | if (_001_) counter_reg[10] <= 1'h0; 261 | else if (_002_) counter_reg[10] <= _036_[10]; 262 | always @(posedge clock) 263 | if (_001_) counter_reg[11] <= 1'h0; 264 | else if (_002_) counter_reg[11] <= _036_[11]; 265 | always @(posedge clock) 266 | if (_001_) counter_reg[12] <= 1'h0; 267 | else if (_002_) counter_reg[12] <= _036_[12]; 268 | always @(posedge clock) 269 | if (_001_) counter_reg[13] <= 1'h0; 270 | else if (_002_) counter_reg[13] <= _036_[13]; 271 | always @(posedge clock) 272 | if (_001_) counter_reg[14] <= 1'h0; 273 | else if (_002_) counter_reg[14] <= _036_[14]; 274 | always @(posedge clock) 275 | if (_001_) counter_reg[15] <= 1'h0; 276 | else if (_002_) counter_reg[15] <= _036_[15]; 277 | always @(posedge clock) 278 | if (reset) button_debounced_reg <= 1'h0; 279 | else if (_000_) button_debounced_reg <= last_button_reg; 280 | always @(posedge clock) 281 | if (reset) last_button_reg <= 1'h0; 282 | else last_button_reg <= button; 283 | assign _035_[15:1] = counter_reg[15:1]; 284 | assign _036_[0] = _035_[0]; 285 | assign button_debounced = button_debounced_reg; 286 | endmodule 287 | -------------------------------------------------------------------------------- /docs/hardware/peripheral.md: -------------------------------------------------------------------------------- 1 | # 外接外设 2 | 3 | ## 常见接口或协议 4 | 5 | !!! warning "小心损坏" 6 | 7 | 注意:下面的各类接口只负责数据的传输,一般外设模块都另外需要连接电源(VCC)、接地(GND)引脚。 8 | **千万注意选择正确的电平/电压(一般为 CMOS 电平,注意 FPGA 只能连接 3.3V IO 电平的模块),并且避免短路、插反,不允许带电插拔,避免损坏芯片或 FPGA。** 9 | 10 | 关于 UART/SPI/I2C 三种接口的介绍视频(搬运自 [YouTube](https://www.youtube.com/watch?v=IyGwvGzrqp8)):[Bilibili](https://www.bilibili.com/video/BV1D7411m7gh?from=search&seid=17456041608147636146)。 11 | 12 | ### Pmod 规范 13 | 14 | Pmod(Peripheral Module Interface)是 Digilent 提出的,适用于各类开发板的接口扩展规范。它定义了引脚的物理外观、电气特性、接口定义等,能够兼容各类协议。遵循 PMOD 规范的外设能够互相兼容,减少了外设的复杂性。相关介绍可见: 15 | 16 | * 博客介绍: 17 | * 接口规范: 18 | 19 | 各种常用的通信协议都被 Pmod 标准所支持,当然用户也可以自行规定引脚含义。实验板上共提供了 8 个标准 12 Pin Pmod 接口,用于连接外设模块,其供电电压与 IO 电平均为 3.3V。同时,相邻的 Pmod 接口可以用于连接宽体的模块,以提供更多 IO 信号。 20 | 21 | ![](pmod_io.png) 22 | 23 | 在项目模板的 `io.xdc` 中定义了各个 Pmod 接口的引脚约束,其中 `pmod_io[0]` 到 `pmod_io[3]` 对应 Pmod 接口的 1-4 引脚,`pmod_io[4]` 到 `pmod_io[7]` 对应 Pmod 接口的 7-10 引脚,如上图。板子上的引脚旁标记了引脚编号,上面一排是 1-6,下面一排是 7-12。 24 | 25 | ### UART 协议 26 | 27 | 通用非同步收发传输器(Universal Asynchronous Receiver/Transmitter),又称为“串口”,一般有两个引脚(`RX`、`TX`)。每个引脚负责一个方向,两个方向异步发送,传送时不包含时钟,速率需要事先协定好(称为波特率/Baud)。可参考实现: 28 | 29 | * 30 | * 31 | 32 | ### I²C 协议 33 | 34 | I²C(Inter-Integrated Circuit),同步协议,一般有两个引脚:数据(SDA)、时钟(SCL),每个引脚都是双向的,在 FPGA 中需要使用特别的 **三态数据类型**(`inout`)。 35 | 36 | 按照规范,两根数据线均需要上拉电阻,可按照 [此教程](https://electronics.stackexchange.com/questions/248248/altera-fpga-i-o-weak-pull-ups) 开启 FPGA 片内相应引脚的上拉电阻。 37 | 38 | 一般来说,由发送方提供时钟,接收方需要在每次接受数据后进行应答。可参考 。 39 | 40 | ### I²S 协议 41 | 42 | I²S(Inter-Integrated Sound),一般用于数字音频传输,包含时钟(`SCLK/BCLK`)、帧使用(`LRCK/WS`)、数据(`SDATA`)。适合需要自定义音频的场合。如果只需要播放背景音乐等固定的音乐,建议直接购买 MP3 语音模块。 43 | 44 | ### SPI 协议 45 | 46 | SPI(Serial Peripheral Interface),同步协议,一般有四个引脚:时钟(`SCK`)、`MOSI`、`MISO`、片选(`SSEL/CS`)。 47 | 48 | 两个方向可全双工发送。由 master 端确定时钟速率。可参考 。 49 | 50 | ### 脉冲接口 51 | 52 | 最简单的接口,直接输出或输入脉冲信号(由高低电平变化表示)。如超声波模块中,使用一个信号线控制超声波的发射,经过一段时间后,另一个引脚会收到返回的脉冲。或者类轨迹球模块中,可计数一段时间内的脉冲个数,表示向某个方向移动的距离。 53 | 54 | ### 类 SRAM 接口 55 | 56 | 类似于 SRAM 或者内置 ROM IP 等的使用方法,可以是同步或者异步接口。包括(可选)时钟(`CLK`)、片选(`CE`)、读使能(`RE`)、写使能(`WE`)、地址、数据。 57 | 58 | ## 模块推荐 59 | 60 | !!! warning "注意电平" 61 | 62 | 以下的模块为我们推荐的型号,但选择时 **并未确认模块的供电电压和 IO 电平**,购买和使用前请务必查看手册或向店家确认 IO 电平为 3.3V,以免损坏实验板。 63 | 64 | 实验板对外可提供 **5V/3.3V** 的供电电源,如果模块需要其他电压的电源,需要自行购买 DCDC 模块以转换电源电压。 65 | 66 | 如果需要同时连接多个模块,建议购买 **面包板**,并自学其使用/回忆电子学实验内容,方便公用电源、地线。 67 | 68 | 选购之前请务必对所选的模块有 **完整的了解**,包括其功能、接口、通讯协议等,使用前必须 **通读整篇文档或 datasheet**! 69 | 70 | ### Pmod 模块 71 | 72 | 下面列出的模块适用于实验板的 Pmod 接口。如需其他模块,也可先在各类电商平台搜索,实验板可以支持几乎所有 Pmod 模块。 73 | 74 | | 功能 | 型号 | 协议 | 简介 | 购买链接 | 75 | |---------------|----------------------------|----------------|----------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| 76 | | 音频输入/输出 | [WM8731](https://cdn.sparkfun.com/datasheets/Dev/Arduino/Shields/WolfsonWM8731.pdf) | I2S | 音频输入/输出模块 | 自制模块,请联系助教团队获取([原理图](https://github.com/jiegec/WM8731PMOD) [样例代码](https://github.com/jiegec/Arty-A7-WM8731PMOD)) | 77 | | 音频输入/输出 | MES-Linein-MIC/MES-Speaker | I2S | 音频输入/输出模块 | https://item.taobao.com/item.htm?id=762086873533 | 78 | | 音频输入/输出 | PSI-PMOD-AUDIO-001 | AC97 | 音频输入/输出模块 | https://item.taobao.com/item.htm?id=616327384190 | 79 | | 音频输出 | PMOD-AUDIO | I2S | 音频输出模块 | https://item.taobao.com/item.htm?id=617206846401 | 80 | | NOR Flash | PMOD-SPI-NOR-FLASH | SPI | 16MB NOR Flash,可以用来存储数据 | 自制模块,请联系助教团队获取 | 81 | | 无线通信 | PSI-EMOD-BLUETOOTH | UART | 蓝牙串口传输模块,可与手机连接传输数据 | https://item.taobao.com/item.htm?id=602102213182 (已下架) | 82 | | 无线通信 | PmodBT2 | UART | 蓝牙串口传输模块 | https://www.mouser.cn/ProductDetail/Digilent/410-214?qs=s%2FdyVPQMB4z05kZmem7h5Q%3D%3D | 83 | | 温度传感器 | PSI-EMOD-SENSOR-001 | 1-Wire | 基于 DS18B20 的温度传感器模块 | https://item.taobao.com/item.htm?id=616627001082 (已下架) | 84 | | EEPROM 存储器 | PSI-EMOD-MEMORY-001 | I2C | 2K 字节 EEPROM 存储器,可方便地存储少量持久化数据 | https://item.taobao.com/item.htm?id=617136619926 | 85 | | 摄像头 | PSI-EMOD-OV7670 | 并行 | OV7670 摄像头模组,较低分辨率(30 万像素,640x480),适合 FPGA 使用 | https://item.taobao.com/item.htm?id=625385869412 (已下架) | 86 | | 摄像头 | PMOD_CAM_5M | 并行 | OV5640 摄像头模组,500 万像素(2592x1944) | https://item.taobao.com/item.htm?id=524514493918(已下架) | 87 | | 摄像头 | PMOD-CAMERA | 并行 | OV2640 摄像头模组,200 万像素(1622x1200) | https://item.taobao.com/item.htm?id=710247485191 | 88 | | 颜色传感器 | PMOD COLOR MODULE TCS3472 | I2C | TCS3472 颜色传感器 | https://item.taobao.com/item.htm?id=712469416493#detail(已下架)  https://www.mouser.cn/ProductDetail/Digilent/410-348?qs=f9yNj16SXrJeCy%252BYmMCoTQ%3D%3D | 89 | | 旋钮 | PMOD ENC ROTARY ENCODER | Rotary Encoder | N/A | https://www.digikey.cn/en/products/detail/digilent-inc/410-117/4090075 | 90 | 91 | 此外,可以前往以下店铺,尝试发现更多 Pmod 模块,注意其中部分模块的功能实验板已经内置,请勿重复购买。 92 | 93 | 1. [PSI 皮赛电子](https://pisai.taobao.com/category-1731295189.htm) 94 | 2. [Muse Lab](https://shop446922193.taobao.com/category-1573912201.htm) 95 | 3. [迪芝伦](https://digilent.taobao.com/category-1636623609.htm)(模块种类极为丰富,但价格昂贵,购买前请尽量寻找替代方案,或使用学术优惠价格) 96 | 4. [迪芝伦国际站](https://digilent.com/shop/boards-and-components/system-board-expansion-modules/)(在模块页面下面找中国经销商购买,价格昂贵) 97 | 98 | 一些 PMOD 模块在 PCB 上标明了引脚的信号名称。如果没有的话,可以询问商家获取文档,找到其中的原理图,根据原理图就可以知道 PMOD 各个引脚对应的信号名称。 99 | 100 | ### 传感器类 101 | 102 | 下面的模块需要使用杜邦线,手工连接到实验板的 GPIO 接口上。 103 | 104 | | 功能 | 型号 | 接口 | 简介 | 购买链接 | 105 | |---------------|-----------|------|------------------------------------------------|-------------------------------------------------------------------------| 106 | | 运动、姿态传感器 | [JY901S](https://wit-motion.yuque.com/wumwnr/docs/khbgzd?singleDoc#%20%E3%80%8AJY901S%E4%BA%A7%E5%93%81%E8%B5%84%E6%96%99%E3%80%8B) | UART | 三轴向加速度、陀螺仪、角度、绝对方向传感器,自带滤波 | https://item.taobao.com/item.htm?id=634627673077 购买时联系卖家焊好排针 | 107 | | 激光测距 | [VL53L0X](https://www.st.com/resource/en/datasheet/vl53l0x.pdf) | I2C | 激光 ToF 测距,量程 3~200cm,精度 3%,测量时间 20ms | https://detail.tmall.com/item.htm?id=609293677802 | 108 | | 超声测距 | —— | 脉冲 | 精度较低,量程大,可根据需求选择型号 | https://detail.tmall.com/item.htm?id=12632417946 | 109 | | 手势传感 | [PAJ7620U2](https://www.waveshare.com/w/upload/1/15/PAJ7620U2_GDS-R1.0_29032016_41002AEN.pdf) | I2C | 可识别上下左右挥动、顺时针、逆时针旋转等手势 | https://detail.tmall.com/item.htm?id=569499698342&skuId=4401524435517 | 110 | 111 | ### 模拟接口类 112 | 113 | 下面列出一些常用的模拟接口,主要为音频输入、输出相关模块。 114 | 115 | | 功能 | 型号 | 接口 | 简介 | 购买链接 | 116 | |--------|------------|------|---------------------------------------------------------------|--------------------------------------------------------------------------| 117 | | 蜂鸣器 | —— | —— | 无源蜂鸣器,可以直接用 FPGA IO 引脚播放简单的频率,产生音乐 | https://item.taobao.com/item.htm?id=12773366240 | 118 | | 音频播放 | VS1053B | SPI | 自带 TF 卡槽,可以由 SPI 控制,播放其中的 mp3 音乐,或录制一段音频 | https://detail.tmall.com/item.htm?id=604374710689 | 119 | | 音频播放 | MP3-TF-16P | UART | 自带 TF 卡槽,由串口控制播放音乐 | https://item.taobao.com/item.htm?id=555798813610 | 120 | | 小喇叭 | —— | —— | 配合上面的音频播放模块一起购买 | https://item.taobao.com/item.htm?id=542182665254 联系卖家,接口改成杜邦线 | 121 | 122 | ### 无线通信 123 | 124 | 汇承 HC-12 433MHz 无线通讯(UART 串口透明传输,可理解为无线的串口线)。 **必须成对购买**,两端直接连接到 UART 接口,不需要其他硬件:。 125 | 126 | 非板子的一侧,需要准备一个充电宝供电,并购买 等类似模块,方便连线。 127 | 128 | ## 调试方法 129 | 130 | 可以购买模块供 PC 调试相应的协议使用(强烈建议购买外设时搭配购买,能极大地方便调试工作): 131 | 132 | * CP2102 模块(UART 串口转 USB): 133 | * 逻辑分析仪(类似示波器,可解码任何数字协议,但不能发送数据): (已下架) 134 | 135 | 如果有不同接口调试需要,建议另行购买单片机(如 Arduino)进行。在使用中如果遇到问题,建议使用 ILA/逻辑分析仪采集信号进行观察。 136 | 137 | ## 注意事项 138 | 139 | 1. 购买任何物品记得开发票(除非不需要报销)。发票抬头:清华大学,税号:12100000400000624D。 140 | 2. 购买模块时,记得让店家帮忙 **焊好排针**。自己选择模块时,要注意选择 2.54mm 间距排针/排母接口的,或者同时向店家询问,购买将模块接口转换成杜邦线的转接线,以免无法连接。 141 | 3. 购买足够的杜邦线,在长度和数量上留出裕量,反正也很便宜。注意不要买错极性(即公母),以及间距(2.54mm),建议直接购买大量母 - 母、公 - 公线缆,确保足够。 142 | 4. 使用任何芯片前,仔细阅读附带的文档(datasheet),尤其是时序图相关部分。寻找文档时, **优先使用 Google,寻找相应主芯片的英文手册**。建议有条件的先行使用 PC 进行调试,确保芯片功能完好并掌握时序后,再上板使用。一些可能需要的工具模块已经在上面列出。推荐使用“串口助手”等软件进行辅助调试,也可用 Python 等语言的相关第三方库进行,可自行搜索。 143 | 5. 芯片需要的 VCC、GND 一般可以直接从实验板获得。如果带不动(比如表现诡异或者无响应,尤其是音频播放模块较为耗电),则需要额外供电,请一定联系助教确认。 144 | 6. 如果对于自己选择的模块、连线方式有疑问,请务必在购买前 **联系店家或助教确认**,以免耽误开发时间。 145 | 146 | ## FAQ 147 | 148 | 这里会记录同学们的常见问题,和助教的回答。 149 | 150 | !!! question "杜邦线是否可以接得很长?" 151 | 152 | 可以,但是要注意以下几点:线路上的电流不能过大(如音频模块的电源、地线),信号频率不能过高(建议低于 1MHz),需要妥善保护连接处避免线路中断。如果需要很长的线缆,建议直接购买长杜邦线,或考虑使用无线传输。 153 | 154 | !!! question "使用充电宝对无线传输的模块供电,需要注意的事情?" 155 | 156 | 充电宝通常输出电压均为 5V,需要检查被供电的所有模块的电压范围,如果均可以接受 5V 供电,可以直接购买 USB 公头转杜邦线的模块进行供电,否则需要购买 USB 接口的稳压模块等,以转换电源电压。这些模块推荐到淘宝 Telesky 旗舰店选购。 157 | --------------------------------------------------------------------------------