3. Verilog基础与量化数据类型:组合逻辑与时序逻辑回顾、定点数表示(Q格式)、浮点数与定点数转换、量化交易中的特殊数据类型

好,咱们直接进入正题。这一节我打算聊聊Verilog里最基础、但也最容易翻车的地方——组合逻辑和时序逻辑的区别,以及量化交易里那些让人头疼的数据类型。你想想看,在FPGA上做高频交易,数据表示不对,策略写得再漂亮也是白搭。

3.1 组合逻辑 vs 时序逻辑:别搞混了

先说组合逻辑。说白了,就是输入一变,输出立马跟着变,没有记忆功能。比如一个简单的加法器:

assign sum = a + b;

a和b一变,sum立刻更新。这在量化交易里用来做实时计算很常见,比如计算买卖价差。

时序逻辑就不一样了。它靠时钟驱动,只在时钟沿更新。我刚开始做FPGA时,总想把所有东西都塞进组合逻辑里,结果时序一塌糊涂。后来才明白,该用寄存器的地方千万别省。

核心区别:

  • 组合逻辑:无记忆,输出只依赖当前输入
  • 时序逻辑:有记忆,输出依赖当前输入和过去状态

举个例子,在订单簿更新时,我们需要记住上一笔的买卖挂单量。这就是典型的时序逻辑场景:

always @(posedge clk) begin
    if (update_valid)
        last_bid_qty <= new_bid_qty;
end

嗯,这里要注意:千万别在同一个always块里混用组合和时序逻辑。我见过有人把assign和always @(posedge clk)混在一起写,结果仿真对得上,上板就乱套。

3.2 定点数表示:Q格式

做量化交易,价格和成交量都是小数。FPGA里用浮点数?太慢了。我们一般用定点数,也就是Q格式。

Q格式长什么样?比如Q16.16,意思就是16位整数位,16位小数位。总共32位。我习惯用Q格式来表示价格,因为精度可控,计算也快。

Q格式 总位数 整数位 小数位 表示范围
Q8.8 16 8 8 -128 ~ 127.996
Q16.16 32 16 16 -32768 ~ 32767.99998
Q1.31 32 1 31 -1 ~ 0.9999999995

我个人习惯用Q16.16来表示价格。为什么?因为大部分股票价格在0.01到1000之间,16位整数位足够,16位小数位能精确到1/65536,约0.000015,比交易所的最小报价单位还细。

小技巧:在Verilog里做Q格式乘法时,记得移位。比如两个Q16.16数相乘,结果是Q32.32,你得右移16位才能回到Q16.16。我曾经忘了移位,结果算出来的价格差了65536倍,差点把策略搞崩。

3.3 浮点数与定点数转换

有时候我们得从外部接口拿到浮点数,比如从网络包解析出来的价格。这时候就得转成定点数。

转换公式很简单:

定点数 = 浮点数 × 2^小数位数

比如浮点数100.25转成Q16.16:

100.25 × 65536 = 6569984 (十六进制0x643D00)

反过来,定点数转浮点数:

浮点数 = 定点数 / 2^小数位数

在Verilog里,我一般用组合逻辑做转换:

// 浮点数转定点数(假设输入是32位IEEE754)
wire [31:0] float_in;
wire [31:0] fixed_out;

// 这里省略了具体的浮点解析逻辑
// 实际项目中我会调用一个IP核来做
assign fixed_out = float_to_fixed(float_in, 16);

注意:浮点数转定点数时,小心溢出。比如Q16.16最大只能表示32767.99998,如果浮点数超过这个范围,结果就错了。我建议在转换前加一个饱和处理。

3.4 量化交易中的特殊数据类型

做量化交易,有几个数据类型特别重要:时间戳、价格、成交量。每个都有坑。

3.4.1 时间戳

交易所的时间戳精度越来越高。以前是毫秒级,现在纳秒级都很常见。在FPGA里,我一般用64位计数器来记录时间戳,精度1纳秒。

reg [63:0] timestamp_ns;

always @(posedge clk_1GHz) begin
    timestamp_ns <= timestamp_ns + 1;
end

嗯,这里要注意:不同交易所的时间基准可能不一样。有的用UTC,有的用本地时间。我建议统一转成UTC纳秒时间戳,方便做跨交易所的延迟分析。

3.4.2 价格

价格用定点数,前面说过了。但有个细节:不同交易所的最小报价单位不一样。比如A股是0.01元,美股是0.0001美元。我建议在系统里统一用一个Q格式,然后在接口层做转换。

我的经验:统一用Q16.16表示价格,然后在每个交易所的适配器里做缩放。这样核心策略代码不用改,只改适配器就行。

3.4.3 成交量

成交量一般是整数,但可能很大。比如某只股票一天成交几亿股。我建议用32位无符号整数,最大能表示42亿,够用了。

但有个坑:累计成交量。如果你要计算一段时间内的累计成交量,小心32位溢出。我一般用64位来存累计值。

reg [63:0] cumulative_volume;

always @(posedge clk) begin
    if (trade_valid)
        cumulative_volume <= cumulative_volume + trade_volume;
end

3.5 知识体系总览

为了让你更直观地理解这一章的内容,我画了一张图。它把组合逻辑、时序逻辑、定点数、浮点数转换,以及交易中的特殊数据类型串在了一起。你可以把它当作一个快速参考。

Verilog基础与量化数据类型知识体系 Verilog基础 组合逻辑:assign, always @(*) 时序逻辑:always @(posedge clk) 区别:有无记忆、时钟驱动 量化数据类型 时间戳:64位纳秒计数器 价格:Q16.16定点数 成交量:32位/64位无符号整数 定点数表示(Q格式) Qm.n:m位整数,n位小数 常用:Q8.8, Q16.16, Q1.31 乘法后需移位对齐 浮点数 ↔ 定点数转换 定点 = 浮点 × 2^n 浮点 = 定点 / 2^n 注意溢出和饱和处理 实际应用:FPGA量化交易系统 订单簿更新、策略计算、延迟分析

这张图把这一章的核心内容串起来了。左边是Verilog基础,右边是量化数据类型,中间是定点数和浮点数的转换。最下面是实际应用。你写代码的时候,可以对照这张图,看看自己卡在哪一层。

避坑指南:我曾经在时间戳处理上吃过亏。当时用了32位计数器,结果系统跑了几个小时就溢出了,导致所有订单的时间戳都乱了。后来改成64位,再也没出过问题。记住:时间戳、累计成交量这种会不断累加的值,一定要用64位。

好了,这一节就到这里。记住:组合逻辑和时序逻辑要分清楚,定点数选好Q格式,时间戳用64位。这些基础打牢了,后面做策略实现才不容易翻车。